Compare commits
10 Commits
7929afeb47
...
86dceac617
Author | SHA1 | Date | |
---|---|---|---|
86dceac617 | |||
32e2dcf347 | |||
2caff58f73 | |||
a5b5256793 | |||
f9cdc8f9d2 | |||
a3013078e0 | |||
431ceec69f | |||
9208ddf779 | |||
2d04c8ccbb | |||
daf44c7339 |
@ -13,3 +13,6 @@ trim_trailing_whitespace = false
|
|||||||
|
|
||||||
[*.{yml,yaml}]
|
[*.{yml,yaml}]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
|
[docker-compose.yml]
|
||||||
|
indent_size = 4
|
||||||
|
27
.env.example
27
.env.example
@ -1,22 +1,25 @@
|
|||||||
|
APP_ADMIN=
|
||||||
APP_NAME=Laravel
|
APP_NAME=Laravel
|
||||||
APP_NAME_HTML_LONG="<b>Laravel</b>Application"
|
APP_NAME_HTML_LONG="<b>Laravel</b>Application"
|
||||||
APP_ENV=local
|
APP_ENV=local
|
||||||
APP_KEY=
|
APP_KEY=
|
||||||
APP_DEBUG=true
|
APP_DEBUG=false
|
||||||
|
APP_TIMEZONE=Australia/Melbourne
|
||||||
APP_URL=http://localhost
|
APP_URL=http://localhost
|
||||||
|
|
||||||
LOG_CHANNEL=stack
|
LOG_CHANNEL=daily
|
||||||
|
|
||||||
DB_CONNECTION=mysql
|
DB_CONNECTION=pgsql
|
||||||
DB_HOST=database
|
DB_HOST=postgres
|
||||||
DB_PORT=3306
|
DB_PORT=5432
|
||||||
DB_DATABASE=database
|
DB_DATABASE=photo
|
||||||
DB_USERNAME=web
|
DB_USERNAME=photo
|
||||||
DB_PASSWORD=
|
DB_PASSWORD=
|
||||||
|
#DB_SCHEMA=photo
|
||||||
|
|
||||||
BROADCAST_DRIVER=log
|
BROADCAST_DRIVER=log
|
||||||
CACHE_DRIVER=file
|
CACHE_STORE=file
|
||||||
QUEUE_CONNECTION=database
|
QUEUE_DRIVER=database
|
||||||
SESSION_DRIVER=file
|
SESSION_DRIVER=file
|
||||||
SESSION_LIFETIME=120
|
SESSION_LIFETIME=120
|
||||||
|
|
||||||
@ -24,9 +27,9 @@ REDIS_HOST=127.0.0.1
|
|||||||
REDIS_PASSWORD=null
|
REDIS_PASSWORD=null
|
||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
|
|
||||||
MAIL_DRIVER=smtp
|
MAIL_MAILER=smtp
|
||||||
MAIL_HOST=smtp.mailtrap.io
|
MAIL_HOST=
|
||||||
MAIL_PORT=2525
|
MAIL_PORT=
|
||||||
MAIL_USERNAME=null
|
MAIL_USERNAME=null
|
||||||
MAIL_PASSWORD=null
|
MAIL_PASSWORD=null
|
||||||
MAIL_ENCRYPTION=null
|
MAIL_ENCRYPTION=null
|
||||||
|
35
app/Casts/PostgresBytea.php
Normal file
35
app/Casts/PostgresBytea.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Casts;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class PostgresBytea implements CastsAttributes
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Cast the given value.
|
||||||
|
*
|
||||||
|
* @param array<string, mixed> $attributes
|
||||||
|
*/
|
||||||
|
public function get(Model $model, string $key, mixed $value, array $attributes): mixed
|
||||||
|
{
|
||||||
|
// For stream resources, we need to fseek in case we've already read it.
|
||||||
|
if (is_resource($value)) {
|
||||||
|
rewind($value);
|
||||||
|
$value = stream_get_contents($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hex2bin($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare the given value for storage.
|
||||||
|
*
|
||||||
|
* @param array<string, mixed> $attributes
|
||||||
|
*/
|
||||||
|
public function set(Model $model, string $key, mixed $value, array $attributes): mixed
|
||||||
|
{
|
||||||
|
return bin2hex($value);
|
||||||
|
}
|
||||||
|
}
|
@ -1,73 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
|
||||||
|
|
||||||
use App\Jobs\{PhotoDelete,VideoDelete};
|
|
||||||
use App\Traits\Type;
|
|
||||||
|
|
||||||
class CatalogAutoDelete extends Command
|
|
||||||
{
|
|
||||||
use DispatchesJobs,Type;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name and signature of the console command.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $signature = 'catalog:autodelete {type : Photo | Video }';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The console command description.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $description = 'Auto Delete Catalog Items';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new command instance.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the console command.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
$class = $this->getModelType($this->argument('type'));
|
|
||||||
|
|
||||||
$class::where('remove',1)->each(function($o) {
|
|
||||||
foreach ($o->myduplicates()->get() as $oo) {
|
|
||||||
if (! $oo->signature OR ! $oo->file_signature)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if ($oo->signature == $o->signature AND $oo->file_signature == $o->file_signature) {
|
|
||||||
$this->info(sprintf('Removing: %s (%s)',$o->id,$o->filename));
|
|
||||||
|
|
||||||
// Dispatch Job to move file.
|
|
||||||
switch (strtolower($this->argument('type'))) {
|
|
||||||
case 'photo':
|
|
||||||
$this->dispatch((new PhotoDelete($o))->onQueue('delete'));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'video':
|
|
||||||
$this->dispatch((new VideoDelete($o))->onQueue('delete'));
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
$this->error('Dont know how to handle: ',$this->argument('type'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
|
|
||||||
use App\Traits\Type;
|
|
||||||
|
|
||||||
class CatalogDump extends Command
|
|
||||||
{
|
|
||||||
use Type;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name and signature of the console command.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $signature = 'catalog:dump {type : Photo | Video } {id : Photo ID}';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The console command description.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $description = 'Scan Photo for metadata';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the console command.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
$class = $this->getModelType($this->argument('type'));
|
|
||||||
|
|
||||||
$o = $class::findOrFail($this->argument('id'));
|
|
||||||
|
|
||||||
if (! $o->isReadable()) {
|
|
||||||
$this->warn(sprintf('Ignoring [%s], it is not readable',$o->file_path()));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
dump($o->properties());
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,27 +3,29 @@
|
|||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
|
||||||
|
|
||||||
use App\Jobs\{PhotoMove,VideoMove};
|
use App\Jobs\CatalogMove as Job;
|
||||||
|
use App\Traits\Type;
|
||||||
|
|
||||||
class CatalogMove extends Command
|
class CatalogMove extends Command
|
||||||
{
|
{
|
||||||
use DispatchesJobs;
|
use Type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name and signature of the console command.
|
* The name and signature of the console command.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'catalog:move {type : Photo | Video }';
|
protected $signature = 'catalog:move'
|
||||||
|
.' {type : Photo | Video }'
|
||||||
|
.' {id : Photo ID}';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $description = 'Trigger Moves';
|
protected $description = 'Move Photo/Video based on their meta data';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the console command.
|
* Execute the console command.
|
||||||
@ -32,35 +34,10 @@ class CatalogMove extends Command
|
|||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$class = 'App\Models\\'.$this->argument('type');
|
$class = $this->getModelType($this->argument('type'));
|
||||||
|
|
||||||
if (! class_exists($class))
|
$o = $class::findOrFail($this->argument('id'));
|
||||||
abort(500,sprintf('No class [%s]',$this->argument('type')));
|
|
||||||
|
|
||||||
foreach ($class::where('filename','LIKE','%INCOMING%')->get() as $o) {
|
return Job::dispatchSync($o);
|
||||||
// Catch any files that are already there.
|
|
||||||
if ($o->moveable() === 1) {
|
|
||||||
$o->duplicate = TRUE;
|
|
||||||
$o->save();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$x = NULL;
|
|
||||||
if (! $o->scanned OR $o->duplicate OR $o->remove OR ($x=$o->moveable()) !== TRUE) {
|
|
||||||
$this->warn(sprintf('Ignoring (%s)[%s]... [%s]',$o->id,$o->file_path(),$x));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (strtolower($this->argument('type'))) {
|
|
||||||
case 'photo':
|
|
||||||
$this->dispatch((new PhotoMove($o))->onQueue('move'));
|
|
||||||
break;
|
|
||||||
case 'video':
|
|
||||||
$this->dispatch((new VideoMove($o))->onQueue('move'));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$this->error('Dont know how to handle: ',$this->argument('type'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,6 +4,7 @@ namespace App\Console\Commands;
|
|||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
use App\Jobs\CatalogScan as Job;
|
||||||
use App\Traits\Type;
|
use App\Traits\Type;
|
||||||
|
|
||||||
class CatalogScan extends Command
|
class CatalogScan extends Command
|
||||||
@ -15,17 +16,17 @@ class CatalogScan extends Command
|
|||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'catalog:scan'.
|
protected $signature = 'catalog:scan'
|
||||||
' {type : Photo | Video }'.
|
.' {type : Photo | Video }'
|
||||||
' {id : Photo ID}'.
|
.' {id : Photo ID}'
|
||||||
' {--dirty : Show Dirty}';
|
.' {--dirty : Show Dirty}';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $description = 'Scan Photo for metadata';
|
protected $description = 'Scan Photo/Video for metadata';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the console command.
|
* Execute the console command.
|
||||||
@ -38,54 +39,6 @@ class CatalogScan extends Command
|
|||||||
|
|
||||||
$o = $class::findOrFail($this->argument('id'));
|
$o = $class::findOrFail($this->argument('id'));
|
||||||
|
|
||||||
if (! $o->isReadable()) {
|
return Job::dispatchSync($o,$this->option('dirty'));
|
||||||
$this->warn(sprintf('Ignoring [%s], it is not readable',$o->file_path()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$o->setDateCreated();
|
|
||||||
$o->setSubSecTime();
|
|
||||||
$o->setSignature();
|
|
||||||
$o->setMakeModel();
|
|
||||||
$o->setLocation();
|
|
||||||
$o->setHeightWidth();
|
|
||||||
$o->setThumbnail();
|
|
||||||
|
|
||||||
// If this is a duplicate
|
|
||||||
$x = $o->myduplicates()->get();
|
|
||||||
if (count($x)) {
|
|
||||||
foreach ($x as $oo) {
|
|
||||||
// And that photo is not marked as a duplicate
|
|
||||||
if (! $oo->duplicate) {
|
|
||||||
$o->duplicate = '1';
|
|
||||||
$this->warn(sprintf('Image [%s] marked as a duplicate',$o->filename));
|
|
||||||
|
|
||||||
// If the file signature also matches, we'll mark it for deletion
|
|
||||||
if ($oo->file_signature AND $o->file_signature == $oo->file_signature) {
|
|
||||||
$this->warn(sprintf('Image [%s] marked for deletion',$o->filename));
|
|
||||||
$o->remove = '1';
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$o->scanned = '1';
|
|
||||||
|
|
||||||
if ($o->getDirty()) {
|
|
||||||
$this->warn(sprintf('Image [%s] metadata changed',$o->filename));
|
|
||||||
|
|
||||||
if ($this->option('dirty'))
|
|
||||||
dump(['id'=>$o->id,'data'=>$o->getDirty()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the file signature changed, abort the update.
|
|
||||||
if ($o->getOriginal('file_signature') AND $o->wasChanged('file_signature')) {
|
|
||||||
dump(['old'=>$o->getOriginal('file_signature'),'new'=>$o->file_signature]);
|
|
||||||
abort(500,'File Signature Changed?');
|
|
||||||
}
|
|
||||||
|
|
||||||
$o->save();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,9 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
use App\Jobs\CatalogScan;
|
use App\Jobs\CatalogScan;
|
||||||
use App\Traits\Type;
|
use App\Traits\Type;
|
||||||
@ -13,14 +16,18 @@ class CatalogScanAll extends Command
|
|||||||
{
|
{
|
||||||
use DispatchesJobs,Type;
|
use DispatchesJobs,Type;
|
||||||
|
|
||||||
|
private int|bool $depth = true;
|
||||||
|
protected const chunk_size = 5;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name and signature of the console command.
|
* The name and signature of the console command.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'catalog:scanall'.
|
protected $signature = 'catalog:scanall'
|
||||||
' {type : Photo | Video }'.
|
.' {type : Photo | Video }'
|
||||||
' {--scanned : Rescan Scanned Photos}';
|
.' {--i|ignore : Ignore missing files}'
|
||||||
|
.' {--s|scan : Force Rescan of all files }';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
@ -30,40 +37,128 @@ class CatalogScanAll extends Command
|
|||||||
protected $description = '(re)Scan Media';
|
protected $description = '(re)Scan Media';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new command instance.
|
* Execute the console command.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return int
|
||||||
*/
|
*/
|
||||||
public function __construct()
|
public function handle(): int
|
||||||
{
|
{
|
||||||
parent::__construct();
|
$started = Carbon::now();
|
||||||
|
$class = $this->getModelType($this->argument('type'));
|
||||||
|
|
||||||
|
Log::info('Scanning disk: '.Storage::disk('nas')->path(''));
|
||||||
|
|
||||||
|
$c = 0;
|
||||||
|
// Scan files in dir, and make sure file lives in DB, (touch it if it does), otherwise create it
|
||||||
|
foreach (Storage::disk('nas')->directories($class::dir_prefix()) as $dir) {
|
||||||
|
Log::info(sprintf(' - DIR: %s',$dir));
|
||||||
|
|
||||||
|
// Take x files at a time and check the DB
|
||||||
|
foreach ($this->files($dir,$class::config,$class::dir_prefix())->chunk(self::chunk_size) as $chunk) {
|
||||||
|
$list = $class::whereIn('filename',$chunk)->get();
|
||||||
|
|
||||||
|
// If there is a new file found it wont be in the DB
|
||||||
|
if ($list->count() !== self::chunk_size)
|
||||||
|
foreach ($chunk->diff($list->pluck('filename')) as $file) {
|
||||||
|
Log::info(sprintf('Found new file [%s] - queueing scan',$file));
|
||||||
|
|
||||||
|
$o = new $class;
|
||||||
|
$o->filename = $file;
|
||||||
|
$o->file_signature = $o->getObjectOriginal('file_signature');
|
||||||
|
$o->save();
|
||||||
|
|
||||||
|
CatalogScan::dispatch($o)
|
||||||
|
->onQueue('scan');
|
||||||
|
|
||||||
|
$c++;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($list as $o) {
|
||||||
|
// Check the details are valid
|
||||||
|
if ($o->file_signature === $o->getObjectOriginal('file_signature')) {
|
||||||
|
// For sanity, we'll check a couple of other attrs
|
||||||
|
if (($o->width !== $o->getObjectOriginal('width')) || ($o->height !== $o->getObjectOriginal('height')))
|
||||||
|
Log::alert(sprintf('Dimensions [%s] (%s x %s) mismatch for [%s]',
|
||||||
|
$o->dimensions,
|
||||||
|
$o->getObjectOriginal('width'),
|
||||||
|
$o->getObjectOriginal('height'),
|
||||||
|
$o->file_name(FALSE)));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Log::alert(sprintf('File Signature [%s] doesnt match [%s] for [%s]',
|
||||||
|
$o->getObjectOriginal('file_signature'),
|
||||||
|
$o->file_signature,
|
||||||
|
$o->file_name(FALSE)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($o->signature !== $o->getObjectOriginal('signature')) {
|
||||||
|
Log::notice(sprintf('Updating image signature for [%s] to [%s] was [%s]',$o->filename,$o->signature,$o->getObjectOriginal('signature')));
|
||||||
|
|
||||||
|
$o->signature = $o->getObjectOriginal('signature');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($o->isDirty())
|
||||||
|
$o->save();
|
||||||
|
else
|
||||||
|
$o->touch();
|
||||||
|
|
||||||
|
if ($this->option('scan')) {
|
||||||
|
Log::info(sprintf('Forcing re-scan of [%s] - queued',$o->filename));
|
||||||
|
|
||||||
|
CatalogScan::dispatch($o)
|
||||||
|
->onQueue('scan');
|
||||||
|
}
|
||||||
|
|
||||||
|
$c++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::info('Checking for missing files');
|
||||||
|
|
||||||
|
// Find DB records before $started, check they exist (they shouldnt), and delete if not
|
||||||
|
if (! $this->option('ignore'))
|
||||||
|
foreach ($class::select(['id','filename'])->where('updated_at','<',$started)->cursor() as $o)
|
||||||
|
Log::error(sprintf('It appears that file [%s] is missing (%d)',$o->filename,$o->id));
|
||||||
|
|
||||||
|
Log::info(sprintf('Processed [%s]',$c));
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the console command.
|
* Recursively find files that we should catalog
|
||||||
*
|
*
|
||||||
* @return mixed
|
* @param string $dir Directory to get files from
|
||||||
|
* @param string $type Configuration key to refer to config())
|
||||||
|
* @param string $prefix Remove the prefix from the filename
|
||||||
|
* @return Collection
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function files(string $dir,string $type,string $prefix): Collection
|
||||||
{
|
{
|
||||||
$class = $this->getModelType($this->argument('type'));
|
$files = collect(Storage::disk('nas')->files($dir))
|
||||||
|
->map(fn($item)=>preg_replace('#^'.$prefix.'#','',$item))
|
||||||
|
->filter(function($item) use ($type) {
|
||||||
|
return ((! ($x=strrpos($item,'.')))
|
||||||
|
|| (! in_array(strtolower(substr($item,$x+1)),config($type.'.import.accepted'))))
|
||||||
|
? NULL
|
||||||
|
: $item;
|
||||||
|
});
|
||||||
|
|
||||||
if ($this->option('scanned')) {
|
if (! $this->depth)
|
||||||
$class::whereNotNull('scanned')
|
return $files;
|
||||||
->update(['scanned'=>NULL]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$c = 0;
|
if (is_numeric($this->depth))
|
||||||
$class::NotScanned()->each(function ($item) use ($c) {
|
$this->depth--;
|
||||||
if ($item->remove) {
|
|
||||||
Log::warning(sprintf('Not scanning [%s], marked for removal',$item->id));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->dispatch((new CatalogScan($item))->onQueue('scan'));
|
foreach (Storage::disk('nas')->directories($dir) as $dir)
|
||||||
$c++;
|
$files = $files->merge($this->files($dir,$type,$prefix));
|
||||||
});
|
|
||||||
|
|
||||||
Log::info(sprintf('Processed [%s]',$c));
|
if (is_numeric($this->depth))
|
||||||
|
$this->depth++;
|
||||||
|
|
||||||
|
return $files;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,50 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
|
||||||
|
|
||||||
use App\Jobs\CatalogVerify as Job;
|
|
||||||
|
|
||||||
class CatalogVerify extends Command
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The name and signature of the console command.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $signature = 'catalog:verify'.
|
|
||||||
' {type : Photo | Video }';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The console command description.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $description = 'Verify media on disk and in the DB';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new command instance.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the console command.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
if ($this->argument('type')) {
|
|
||||||
Job::dispatch($this->argument('type'))->onQueue('scan');
|
|
||||||
|
|
||||||
} else {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,145 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
|
||||||
|
|
||||||
use App\Models\{Person,Photo,Tag};
|
|
||||||
use App\Traits\Files;
|
|
||||||
|
|
||||||
class PhotoImport extends Command
|
|
||||||
{
|
|
||||||
use DispatchesJobs;
|
|
||||||
use Files;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name and signature of the console command.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $signature = 'photo:import
|
|
||||||
{--dir= : Directory to Parse}
|
|
||||||
{--file= : File to Import}
|
|
||||||
{--ignoredupe : Ignore duplicate files}
|
|
||||||
{--deletedupe : Delete duplicate files}
|
|
||||||
{--people= : People to reference in photo}
|
|
||||||
{--tags= : Add tag to photo}';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The console command description.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $description = 'Import photos into the database';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new command instance.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the console command.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
$files = $this->getFiles([
|
|
||||||
'dir'=>$this->option('dir'),
|
|
||||||
'file'=>$this->option('file')
|
|
||||||
],'photo');
|
|
||||||
|
|
||||||
if (! count($files))
|
|
||||||
exit;
|
|
||||||
|
|
||||||
// Show a progress bar
|
|
||||||
$bar = $this->output->createProgressBar(count($files));
|
|
||||||
$bar->setFormat("%current%/%max% [%bar%] %percent:3s%% (%memory%) (%remaining%) ");
|
|
||||||
|
|
||||||
$tags = NULL;
|
|
||||||
$t = $p = array();
|
|
||||||
|
|
||||||
// Tags
|
|
||||||
if ($this->option('tags')) {
|
|
||||||
$tags = explode(',',$this->option('tags'));
|
|
||||||
$t = Tag::whereIn('tag',$tags)->pluck('id')->toArray();
|
|
||||||
|
|
||||||
if (! $t OR count($t) != count($tags)) {
|
|
||||||
$this->error(sprintf('Tag [%s] dont exist',join('|',$tags)));
|
|
||||||
abort(501);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// People
|
|
||||||
if ($this->option('people')) {
|
|
||||||
$tags = explode(',',$this->option('people'));
|
|
||||||
$p = Person::whereIn('tag',$tags)->pluck('id')->toArray();
|
|
||||||
|
|
||||||
if (! $p OR count($p) != count($tags))
|
|
||||||
{
|
|
||||||
$this->error(sprintf('People [%s] dont exist',join('|',$tags)));
|
|
||||||
abort(501);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$c = 0;
|
|
||||||
foreach ($files as $file) {
|
|
||||||
$bar->advance();
|
|
||||||
|
|
||||||
if (preg_match('/@__thumb/',$file) OR preg_match('/\/._/',$file)) {
|
|
||||||
$this->warn(sprintf('Ignoring file [%s]',$file));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! in_array(strtolower(pathinfo($file,PATHINFO_EXTENSION)),config('photo.import.accepted'))) {
|
|
||||||
$this->warn(sprintf('Ignoring [%s]',$file));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->option('verbose'))
|
|
||||||
$this->info(sprintf('Processing file [%s]',$file));
|
|
||||||
|
|
||||||
$c++;
|
|
||||||
|
|
||||||
$o = Photo::where('filename',$file)->first();
|
|
||||||
|
|
||||||
// The photo doesnt exist
|
|
||||||
if (is_null($o)) {
|
|
||||||
$o = new Photo;
|
|
||||||
$o->filename = $file;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($o->exists)
|
|
||||||
$this->warn(sprintf('%s [%s] already in DB: %s',$o->objectType(),$file,$o->id));
|
|
||||||
|
|
||||||
// Make sure we can read the photo.
|
|
||||||
if (! is_readable($o->file_path())) {
|
|
||||||
$this->warn(sprintf('Ignoring [%s], it is not readable',$o->file_path()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$o->save();
|
|
||||||
|
|
||||||
if ($o->wasRecentlyCreated)
|
|
||||||
$this->info(sprintf('%s [%s] stored in DB: %s',$o->objectType(),$file,$o->id));
|
|
||||||
|
|
||||||
// Record our people and tags
|
|
||||||
if ($p)
|
|
||||||
$o->People()->sync($p);
|
|
||||||
if ($t)
|
|
||||||
$o->Tags()->sync($t);
|
|
||||||
|
|
||||||
$this->dispatch((new \App\Jobs\CatalogScan($o))->onQueue('scan'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$bar->finish();
|
|
||||||
|
|
||||||
return $this->info(sprintf('Photos processed: %s',$c));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,107 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use Log;
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use App\Model\Photo;
|
|
||||||
|
|
||||||
class PhotoUpdate extends Command
|
|
||||||
{
|
|
||||||
use \App\Traits\Files;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name and signature of the console command.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $signature = 'photo:update
|
|
||||||
{--dir= : Directory to Parse}
|
|
||||||
{--file= : File to import}';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The console command description.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $description = 'Update Signatures';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new command instance.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the console command.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
$files = $this->getFiles(['dir'=>$this->option('dir'),'file'=>$this->option('file')]);
|
|
||||||
if (! count($files))
|
|
||||||
exit;
|
|
||||||
|
|
||||||
// Show a progress bar
|
|
||||||
$bar = $this->output->createProgressBar(count($files));
|
|
||||||
$bar->setFormat("%current%/%max% [%bar%] %percent:3s%% (%memory%) (%remaining%) ");
|
|
||||||
|
|
||||||
$c = 0;
|
|
||||||
foreach ($files as $file)
|
|
||||||
{
|
|
||||||
$bar->advance();
|
|
||||||
|
|
||||||
if (preg_match('/@__thumb/',$file) OR preg_match('/\/._/',$file))
|
|
||||||
{
|
|
||||||
$this->warn(sprintf('Ignoring file [%s]',$file));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! in_array(strtolower(pathinfo($file,PATHINFO_EXTENSION)),config('photo.import.accepted')))
|
|
||||||
{
|
|
||||||
$this->warn(sprintf('Ignoring [%s]',$file));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->option('verbose'))
|
|
||||||
$this->info(sprintf('Processing file [%s]',$file));
|
|
||||||
|
|
||||||
$c++;
|
|
||||||
|
|
||||||
$po = Photo::where('filename',$file)->first();
|
|
||||||
|
|
||||||
if (is_null($po))
|
|
||||||
{
|
|
||||||
$this->error(sprintf('File is not in the database [%s]?',$file));
|
|
||||||
Log::error(sprintf('%s: File is not in the database [%s]?',__METHOD__,$file));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$po->signature = $po->property('signature');
|
|
||||||
|
|
||||||
try {
|
|
||||||
$po->thumbnail = exif_thumbnail($po->file_path());
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
// @todo Couldnt get the thumbnail, so we should create one.
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($po->isDirty())
|
|
||||||
{
|
|
||||||
if (count($po->getDirty()) > 1 OR ! array_key_exists('signature',$po->getDirty()))
|
|
||||||
$this->error(sprintf('More than the signature changed for [%s] (%s)?',$po->id,join('|',array_keys($po->getDirty()))));
|
|
||||||
|
|
||||||
$this->info(sprintf('Signature update for [%s]',$po->id));
|
|
||||||
$po->save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$bar->finish();
|
|
||||||
|
|
||||||
return $this->info(sprintf('Images processed: %s',$c));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,149 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
|
||||||
|
|
||||||
use App\Models\{Person,Video,Tag};
|
|
||||||
use App\Traits\Files;
|
|
||||||
|
|
||||||
class VideoImport extends Command
|
|
||||||
{
|
|
||||||
use DispatchesJobs;
|
|
||||||
use Files;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name and signature of the console command.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $signature = 'video:import
|
|
||||||
{--dir= : Directory to Parse}
|
|
||||||
{--file= : File to Import}
|
|
||||||
{--ignoredupe : Ignore duplicate files}
|
|
||||||
{--deletedupe : Delete duplicate files}
|
|
||||||
{--dumpid3 : Dump ID3 data}
|
|
||||||
{--people= : People to reference in video}
|
|
||||||
{--tags= : Add tag to video}';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The console command description.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $description = 'Import videos into the database';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new command instance.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the console command.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
$files = $this->getFiles([
|
|
||||||
'dir'=>$this->option('dir'),
|
|
||||||
'file'=>$this->option('file')
|
|
||||||
],'video');
|
|
||||||
|
|
||||||
if (! count($files))
|
|
||||||
exit;
|
|
||||||
|
|
||||||
// Show a progress bar
|
|
||||||
$bar = $this->output->createProgressBar(count($files));
|
|
||||||
$bar->setFormat("%current%/%max% [%bar%] %percent:3s%% (%memory%) (%remaining%) ");
|
|
||||||
|
|
||||||
$tags = NULL;
|
|
||||||
$t = $p = array();
|
|
||||||
|
|
||||||
// Tags
|
|
||||||
if ($this->option('tags')) {
|
|
||||||
$tags = explode(',',$this->option('tags'));
|
|
||||||
$t = Tag::whereIn('tag',$tags)->pluck('id')->toArray();
|
|
||||||
|
|
||||||
if (! $t OR count($t) != count($tags)) {
|
|
||||||
$this->error(sprintf('Tag [%s] dont exist',join('|',$tags)));
|
|
||||||
abort(501);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// People
|
|
||||||
if ($this->option('people')) {
|
|
||||||
$tags = explode(',',$this->option('people'));
|
|
||||||
$p = Person::whereIn('tag',$tags)->pluck('id')->toArray();
|
|
||||||
|
|
||||||
if (! $p OR count($p) != count($tags))
|
|
||||||
{
|
|
||||||
$this->error(sprintf('People [%s] dont exist',join('|',$tags)));
|
|
||||||
abort(501);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$c = 0;
|
|
||||||
foreach ($files as $file) {
|
|
||||||
$bar->advance();
|
|
||||||
|
|
||||||
if (preg_match('/@__thumb/',$file) OR preg_match('/\/._/',$file)) {
|
|
||||||
$this->warn(sprintf('Ignoring file [%s]',$file));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! in_array(strtolower(pathinfo($file,PATHINFO_EXTENSION)),config('video.import.accepted'))) {
|
|
||||||
$this->warn(sprintf('Ignoring [%s]',$file));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->option('verbose'))
|
|
||||||
$this->info(sprintf('Processing file [%s]',$file));
|
|
||||||
|
|
||||||
$c++;
|
|
||||||
|
|
||||||
$o = Video::where('filename',$file)->first();
|
|
||||||
|
|
||||||
// The video doesnt exist
|
|
||||||
if (is_null($o)) {
|
|
||||||
$o = new Video;
|
|
||||||
$o->filename = $file;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($o->exists)
|
|
||||||
$this->warn(sprintf('%s [%s] already in DB: %s',$o->objectType(),$file,$o->id));
|
|
||||||
|
|
||||||
// Make sure we can read the video.
|
|
||||||
if (! is_readable($o->file_path())) {
|
|
||||||
$this->warn(sprintf('Ignoring [%s], it is not readable',$o->file_path()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$o->save();
|
|
||||||
|
|
||||||
if ($o->wasRecentlyCreated)
|
|
||||||
$this->info(sprintf('%s [%s] stored in DB: %s',$o->objectType(),$file,$o->id));
|
|
||||||
|
|
||||||
// Record our people and tags
|
|
||||||
if ($p)
|
|
||||||
$o->People()->sync($p);
|
|
||||||
if ($t)
|
|
||||||
$o->Tags()->sync($t);
|
|
||||||
|
|
||||||
$this->dispatch((new \App\Jobs\CatalogScan($o))->onQueue('scan'));
|
|
||||||
|
|
||||||
if ($this->option('dumpid3'))
|
|
||||||
dd($o->properties());
|
|
||||||
}
|
|
||||||
|
|
||||||
$bar->finish();
|
|
||||||
|
|
||||||
return $this->info(sprintf('Videos processed: %s',$c));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,102 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
|
|
||||||
use App\Traits\Files;
|
|
||||||
use App\Models\Video;
|
|
||||||
|
|
||||||
class VideoMove extends Command
|
|
||||||
{
|
|
||||||
use Files;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name and signature of the console command.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $signature = 'video:move
|
|
||||||
{--file= : Video File}
|
|
||||||
{--id= : Video ID}';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The console command description.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $description = 'Moves Videos to their new location';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new command instance.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the console command.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
if ($this->option('file')) {
|
|
||||||
$vo = Video::notRemove()->notDuplicate()->where('filename',Video::path($this->option('file')));
|
|
||||||
|
|
||||||
} elseif ($this->option('id')) {
|
|
||||||
$vo = Video::notRemove()->notDuplicate()->where('id',$this->option('id'));
|
|
||||||
|
|
||||||
} else {
|
|
||||||
$vo = Video::notRemove()->notDuplicate();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $vo)
|
|
||||||
exit;
|
|
||||||
|
|
||||||
// Show a progress bar
|
|
||||||
$bar = $this->output->createProgressBar($vo->count());
|
|
||||||
$bar->setFormat("%current%/%max% [%bar%] %percent:3s%% (%memory%) (%remaining%) ");
|
|
||||||
$bar->setRedrawFrequency(100);
|
|
||||||
|
|
||||||
$vo->chunk(1,function($video) use ($bar) {
|
|
||||||
while ($o = $video->shift()) {
|
|
||||||
if (($x = $o->moveable()) === TRUE) {
|
|
||||||
Log::info(sprintf('%s: Moving (%s)[%s]',__METHOD__,$o->id,$o->filename));
|
|
||||||
|
|
||||||
if ($this->makeParentDir(dirname($o->file_path(FALSE,TRUE))) AND rename($o->file_path(),$o->file_path(FALSE,TRUE))) {
|
|
||||||
// Convert the path to a relative one.
|
|
||||||
$o->filename = $o->file_path(TRUE,TRUE);
|
|
||||||
|
|
||||||
// @todo If the DB update failed, move it back.
|
|
||||||
if (! $o->save()) # AND ! rename($path,$o->file_path()))
|
|
||||||
{
|
|
||||||
$this->error(sprintf('Save after rename failed for (%s)',$o->id));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
$this->error(sprintf('Rename failed for (%s)',$o->id));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
chmod($o->file_path(),0444);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if ($x > 0) {
|
|
||||||
$this->warn(sprintf('Unable to move (%s) [%s] to [%s], movable returned (%s)',$o->id,$o->file_path(),$o->file_path(FALSE,TRUE),$x));
|
|
||||||
|
|
||||||
if ($x == 1 AND $v = Video::where('filename',$o->file_path(TRUE,TRUE))->first())
|
|
||||||
$this->warn(sprintf('File is id (%s) [%s]',$v->file_path(),$v->id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$bar->advance();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Console;
|
|
||||||
|
|
||||||
use Illuminate\Console\Scheduling\Schedule;
|
|
||||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
|
||||||
|
|
||||||
class Kernel extends ConsoleKernel
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The Artisan commands provided by your application.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $commands = [
|
|
||||||
//
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define the application's command schedule.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Console\Scheduling\Schedule $schedule
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
protected function schedule(Schedule $schedule)
|
|
||||||
{
|
|
||||||
// $schedule->command('inspire')
|
|
||||||
// ->hourly();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register the commands for the application.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
protected function commands()
|
|
||||||
{
|
|
||||||
$this->load(__DIR__.'/Commands');
|
|
||||||
|
|
||||||
require base_path('routes/console.php');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Exceptions;
|
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
|
||||||
|
|
||||||
class Handler extends ExceptionHandler
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* A list of the exception types that are not reported.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $dontReport = [
|
|
||||||
//
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of the inputs that are never flashed for validation exceptions.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $dontFlash = [
|
|
||||||
'password',
|
|
||||||
'password_confirmation',
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Report or log an exception.
|
|
||||||
*
|
|
||||||
* @param \Exception $exception
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function report(Exception $exception)
|
|
||||||
{
|
|
||||||
parent::report($exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render an exception into an HTTP response.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param \Exception $exception
|
|
||||||
* @return \Illuminate\Http\Response
|
|
||||||
*/
|
|
||||||
public function render($request, Exception $exception)
|
|
||||||
{
|
|
||||||
return parent::render($request, $exception);
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,20 +6,22 @@ namespace App\Helpers;
|
|||||||
* Description of VideoStream
|
* Description of VideoStream
|
||||||
*
|
*
|
||||||
* @author Rana
|
* @author Rana
|
||||||
|
* modified by HazCod to use stream_get_contents and correct session shutoff
|
||||||
|
* https://github.com/HazCod
|
||||||
* @link http://codesamplez.com/programming/php-html5-video-streaming-tutorial
|
* @link http://codesamplez.com/programming/php-html5-video-streaming-tutorial
|
||||||
**/
|
**/
|
||||||
class VideoStream
|
class VideoStream
|
||||||
{
|
{
|
||||||
private $path = '';
|
private string $path;
|
||||||
private $stream = '';
|
private mixed $stream;
|
||||||
private $buffer = 102400;
|
private int $buffer = 102400;
|
||||||
private $start = -1;
|
private int $start = -1;
|
||||||
private $end = -1;
|
private int $end = -1;
|
||||||
private $size = 0;
|
private int $size = 0;
|
||||||
|
|
||||||
function __construct($filePath)
|
function __construct(string $filename)
|
||||||
{
|
{
|
||||||
$this->path = $filePath;
|
$this->path = $filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,9 +29,8 @@ class VideoStream
|
|||||||
**/
|
**/
|
||||||
private function open()
|
private function open()
|
||||||
{
|
{
|
||||||
if (! ($this->stream = fopen($this->path,'rb')) ) {
|
if (! ($this->stream=fopen($this->path,'rb',false, stream_context_create())))
|
||||||
die('Could not open stream for reading');
|
die('Could not open stream for reading');
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,53 +39,55 @@ class VideoStream
|
|||||||
private function setHeader()
|
private function setHeader()
|
||||||
{
|
{
|
||||||
ob_get_clean();
|
ob_get_clean();
|
||||||
header('Content-Type: video/mp4');
|
header('Content-Type: '.mime_content_type($this->path));
|
||||||
header('Cache-Control: max-age=2592000, public');
|
header(sprintf('Cache-Control: max-age=%d, public',$x=60*60*24*30));
|
||||||
header('Expires: '.gmdate('D, d M Y H:i:s',time()+2592000) . ' GMT');
|
header(sprintf('Expires: %s GMT',gmdate('D, d M Y H:i:s',time()+$x)));
|
||||||
header('Last-Modified: '.gmdate('D, d M Y H:i:s',@filemtime($this->path)) . ' GMT' );
|
header(sprintf('Last-Modified: %s GMT',gmdate('D, d M Y H:i:s',filemtime($this->path))));
|
||||||
|
|
||||||
$this->start = 0;
|
$this->start = 0;
|
||||||
$this->size = filesize($this->path);
|
$this->size = filesize($this->path);
|
||||||
$this->end = $this->size - 1;
|
$this->end = $this->size - 1;
|
||||||
header('Accept-Ranges: 0-'.$this->end);
|
header('Accept-Ranges: 0-'.$this->end);
|
||||||
|
|
||||||
if (isset($_SERVER['HTTP_RANGE'])) {
|
if (isset($_SERVER['HTTP_RANGE'])) {
|
||||||
|
|
||||||
$c_start = $this->start;
|
|
||||||
$c_end = $this->end;
|
$c_end = $this->end;
|
||||||
|
|
||||||
list(,$range) = explode('=',$_SERVER['HTTP_RANGE'],2);
|
list(,$range) = explode('=',$_SERVER['HTTP_RANGE'],2);
|
||||||
if (strpos($range,',') !== false) {
|
if (strpos($range,',') !== FALSE) {
|
||||||
header('HTTP/1.1 416 Requested Range Not Satisfiable');
|
header('HTTP/1.1 416 Requested Range Not Satisfiable');
|
||||||
header("Content-Range: bytes $this->start-$this->end/$this->size");
|
header(sprintf('Content-Range: bytes %d-%d/%d',$this->start,$this->end,$this->size));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($range == '-') {
|
if ($range == '-') {
|
||||||
$c_start = $this->size - substr($range,1);
|
$c_start = $this->size - substr($range,1);
|
||||||
}else{
|
|
||||||
|
} else {
|
||||||
$range = explode('-',$range);
|
$range = explode('-',$range);
|
||||||
$c_start = $range[0];
|
$c_start = $range[0];
|
||||||
|
|
||||||
$c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $c_end;
|
$c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $c_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
$c_end = ($c_end > $this->end) ? $this->end : $c_end;
|
$c_end = ($c_end > $this->end) ? $this->end : $c_end;
|
||||||
|
|
||||||
if ($c_start > $c_end || $c_start > $this->size - 1 || $c_end >= $this->size) {
|
if ($c_start > $c_end || $c_start > $this->size - 1 || $c_end >= $this->size) {
|
||||||
header('HTTP/1.1 416 Requested Range Not Satisfiable');
|
header('HTTP/1.1 416 Requested Range Not Satisfiable');
|
||||||
header("Content-Range: bytes $this->start-$this->end/$this->size");
|
header(sprintf('Content-Range: bytes %d-%d/%d',$this->start,$this->end,$this->size));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->start = $c_start;
|
$this->start = $c_start;
|
||||||
$this->end = $c_end;
|
$this->end = $c_end;
|
||||||
$length = $this->end - $this->start + 1;
|
$length = $this->end - $this->start + 1;
|
||||||
|
|
||||||
fseek($this->stream,$this->start);
|
fseek($this->stream,$this->start);
|
||||||
header('HTTP/1.1 206 Partial Content');
|
header('HTTP/1.1 206 Partial Content');
|
||||||
header("Content-Length: ".$length);
|
header("Content-Length: ".$length);
|
||||||
header("Content-Range: bytes $this->start-$this->end/".$this->size);
|
header(sprintf('Content-Range: bytes %d-%d/%d',$this->start,$this->end,$this->size));
|
||||||
}
|
|
||||||
else
|
} else {
|
||||||
{
|
|
||||||
header("Content-Length: ".$this->size);
|
header("Content-Length: ".$this->size);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -103,13 +106,14 @@ class VideoStream
|
|||||||
{
|
{
|
||||||
$i = $this->start;
|
$i = $this->start;
|
||||||
set_time_limit(0);
|
set_time_limit(0);
|
||||||
while(!feof($this->stream) && $i <= $this->end) {
|
while ((! feof($this->stream)) && ($i <= $this->end) && (connection_aborted() == 0)) {
|
||||||
$bytesToRead = $this->buffer;
|
$bytesToRead = $this->buffer;
|
||||||
|
|
||||||
if (($i+$bytesToRead) > $this->end) {
|
if (($i+$bytesToRead) > $this->end) {
|
||||||
$bytesToRead = $this->end - $i + 1;
|
$bytesToRead = $this->end - $i + 1;
|
||||||
}
|
}
|
||||||
$data = fread($this->stream,$bytesToRead);
|
|
||||||
echo $data;
|
echo stream_get_contents($this->stream,$bytesToRead);
|
||||||
flush();
|
flush();
|
||||||
$i += $bytesToRead;
|
$i += $bytesToRead;
|
||||||
}
|
}
|
||||||
@ -120,6 +124,7 @@ class VideoStream
|
|||||||
**/
|
**/
|
||||||
function start()
|
function start()
|
||||||
{
|
{
|
||||||
|
session_write_close();
|
||||||
$this->open();
|
$this->open();
|
||||||
$this->setHeader();
|
$this->setHeader();
|
||||||
$this->stream();
|
$this->stream();
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Auth;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use Illuminate\Foundation\Auth\ConfirmsPasswords;
|
|
||||||
|
|
||||||
class ConfirmPasswordController extends Controller
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Confirm Password Controller
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This controller is responsible for handling password confirmations and
|
|
||||||
| uses a simple trait to include the behavior. You're free to explore
|
|
||||||
| this trait and override any functions that require customization.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
use ConfirmsPasswords;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Where to redirect users when the intended url fails.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $redirectTo = '/home';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new controller instance.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->middleware('auth');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Auth;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
|
|
||||||
|
|
||||||
class ForgotPasswordController extends Controller
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Password Reset Controller
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This controller is responsible for handling password reset emails and
|
|
||||||
| includes a trait which assists in sending these notifications from
|
|
||||||
| your application to your users. Feel free to explore this trait.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
use SendsPasswordResetEmails;
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Auth;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
|
||||||
|
|
||||||
class LoginController extends Controller
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Login Controller
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This controller handles authenticating users for the application and
|
|
||||||
| redirecting them to your home screen. The controller uses a trait
|
|
||||||
| to conveniently provide its functionality to your applications.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
use AuthenticatesUsers;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Where to redirect users after login.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $redirectTo = '/home';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new controller instance.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->middleware('guest')->except('logout');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show our themed login page
|
|
||||||
*/
|
|
||||||
public function showLoginForm()
|
|
||||||
{
|
|
||||||
$login_note = '';
|
|
||||||
|
|
||||||
if (file_exists('login_note.txt'))
|
|
||||||
$login_note = file_get_contents('login_note.txt');
|
|
||||||
|
|
||||||
return view('adminlte::auth.login')->with('login_note',$login_note);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Auth;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\User;
|
|
||||||
use Illuminate\Foundation\Auth\RegistersUsers;
|
|
||||||
use Illuminate\Support\Facades\Hash;
|
|
||||||
use Illuminate\Support\Facades\Validator;
|
|
||||||
|
|
||||||
class RegisterController extends Controller
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Register Controller
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This controller handles the registration of new users as well as their
|
|
||||||
| validation and creation. By default this controller uses a trait to
|
|
||||||
| provide this functionality without requiring any additional code.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
use RegistersUsers;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Where to redirect users after registration.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $redirectTo = '/home';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new controller instance.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->middleware('guest');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a validator for an incoming registration request.
|
|
||||||
*
|
|
||||||
* @param array $data
|
|
||||||
* @return \Illuminate\Contracts\Validation\Validator
|
|
||||||
*/
|
|
||||||
protected function validator(array $data)
|
|
||||||
{
|
|
||||||
return Validator::make($data, [
|
|
||||||
'name' => ['required', 'string', 'max:255'],
|
|
||||||
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
|
|
||||||
'password' => ['required', 'string', 'min:8', 'confirmed'],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new user instance after a valid registration.
|
|
||||||
*
|
|
||||||
* @param array $data
|
|
||||||
* @return \App\User
|
|
||||||
*/
|
|
||||||
protected function create(array $data)
|
|
||||||
{
|
|
||||||
return User::create([
|
|
||||||
'name' => $data['name'],
|
|
||||||
'email' => $data['email'],
|
|
||||||
'password' => Hash::make($data['password']),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Auth;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
|
||||||
|
|
||||||
class ResetPasswordController extends Controller
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Password Reset Controller
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This controller is responsible for handling password reset requests
|
|
||||||
| and uses a simple trait to include this behavior. You're free to
|
|
||||||
| explore this trait and override any methods you wish to tweak.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
use ResetsPasswords;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Where to redirect users after resetting their password.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $redirectTo = '/home';
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Auth;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use Illuminate\Foundation\Auth\VerifiesEmails;
|
|
||||||
|
|
||||||
class VerificationController extends Controller
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Email Verification Controller
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This controller is responsible for handling email verification for any
|
|
||||||
| user that recently registered with the application. Emails may also
|
|
||||||
| be re-sent if the user didn't receive the original email message.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
use VerifiesEmails;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Where to redirect users after verification.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $redirectTo = '/home';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new controller instance.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->middleware('auth');
|
|
||||||
$this->middleware('signed')->only('verify');
|
|
||||||
$this->middleware('throttle:6,1')->only('verify', 'resend');
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,11 +3,9 @@
|
|||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
|
||||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||||
use Illuminate\Routing\Controller as BaseController;
|
|
||||||
|
|
||||||
class Controller extends BaseController
|
abstract class Controller extends \Illuminate\Routing\Controller
|
||||||
{
|
{
|
||||||
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
|
use AuthorizesRequests, ValidatesRequests;
|
||||||
}
|
}
|
@ -1,15 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
|
||||||
|
|
||||||
class HomeController extends Controller
|
|
||||||
{
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
//$this->middleware('guest');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function home() {
|
|
||||||
return view('home');
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,16 +12,6 @@ class PhotoController extends Controller
|
|||||||
protected $list_duplicates = 20;
|
protected $list_duplicates = 20;
|
||||||
protected $list_deletes = 50;
|
protected $list_deletes = 50;
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new controller instance.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->middleware('guest');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete(Photo $o)
|
public function delete(Photo $o)
|
||||||
{
|
{
|
||||||
$o->remove = TRUE;
|
$o->remove = TRUE;
|
||||||
@ -48,14 +38,9 @@ class PhotoController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function info(Photo $o)
|
|
||||||
{
|
|
||||||
return view('photo.view',['o'=>$o]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function thumbnail(Photo $o)
|
public function thumbnail(Photo $o)
|
||||||
{
|
{
|
||||||
return response($o->thumbnail(TRUE))
|
return response($o->thumbnail())
|
||||||
->header('Content-Type','image/jpeg');
|
->header('Content-Type','image/jpeg');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,9 +49,16 @@ class PhotoController extends Controller
|
|||||||
$o->remove = NULL;
|
$o->remove = NULL;
|
||||||
$o->save();
|
$o->save();
|
||||||
|
|
||||||
return redirect()->action('PhotoController@info',[$o->id]);
|
return redirect()
|
||||||
|
->action('PhotoController@info',[$o->id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the photo to the browser
|
||||||
|
*
|
||||||
|
* @param Photo $o
|
||||||
|
* @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Foundation\Application|\Illuminate\Http\Response
|
||||||
|
*/
|
||||||
public function view(Photo $o)
|
public function view(Photo $o)
|
||||||
{
|
{
|
||||||
return response($o->image())
|
return response($o->image())
|
||||||
|
@ -13,16 +13,6 @@ class VideoController extends Controller
|
|||||||
protected $list_duplicates = 20;
|
protected $list_duplicates = 20;
|
||||||
protected $list_deletes = 50;
|
protected $list_deletes = 50;
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new controller instance.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->middleware('guest');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete(Video $o)
|
public function delete(Video $o)
|
||||||
{
|
{
|
||||||
$o->remove = TRUE;
|
$o->remove = TRUE;
|
||||||
@ -49,11 +39,6 @@ class VideoController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function info(Video $o)
|
|
||||||
{
|
|
||||||
return view('video.view',['o'=>$o]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function undelete(Video $o)
|
public function undelete(Video $o)
|
||||||
{
|
{
|
||||||
$o->remove = NULL;
|
$o->remove = NULL;
|
||||||
@ -65,6 +50,7 @@ class VideoController extends Controller
|
|||||||
public function view(Video $o)
|
public function view(Video $o)
|
||||||
{
|
{
|
||||||
if ($o->isReadable())
|
if ($o->isReadable())
|
||||||
(new VideoStream($o->file_path()))->start();
|
(new VideoStream($o->file_name(FALSE)))
|
||||||
|
->start();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,82 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http;
|
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
|
||||||
|
|
||||||
class Kernel extends HttpKernel
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The application's global HTTP middleware stack.
|
|
||||||
*
|
|
||||||
* These middleware are run during every request to your application.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $middleware = [
|
|
||||||
\App\Http\Middleware\TrustProxies::class,
|
|
||||||
\App\Http\Middleware\CheckForMaintenanceMode::class,
|
|
||||||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
|
||||||
\App\Http\Middleware\TrimStrings::class,
|
|
||||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The application's route middleware groups.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $middlewareGroups = [
|
|
||||||
'web' => [
|
|
||||||
\App\Http\Middleware\EncryptCookies::class,
|
|
||||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
|
||||||
\Illuminate\Session\Middleware\StartSession::class,
|
|
||||||
// \Illuminate\Session\Middleware\AuthenticateSession::class,
|
|
||||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
|
||||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
|
||||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
|
||||||
],
|
|
||||||
|
|
||||||
'api' => [
|
|
||||||
'throttle:60,1',
|
|
||||||
'bindings',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The application's route middleware.
|
|
||||||
*
|
|
||||||
* These middleware may be assigned to groups or used individually.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $routeMiddleware = [
|
|
||||||
'auth' => \App\Http\Middleware\Authenticate::class,
|
|
||||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
|
||||||
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
|
|
||||||
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
|
|
||||||
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
|
||||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
|
||||||
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
|
|
||||||
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
|
|
||||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
|
||||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The priority-sorted list of middleware.
|
|
||||||
*
|
|
||||||
* This forces non-global middleware to always be in the given order.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $middlewarePriority = [
|
|
||||||
\Illuminate\Session\Middleware\StartSession::class,
|
|
||||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
|
||||||
\App\Http\Middleware\Authenticate::class,
|
|
||||||
\Illuminate\Routing\Middleware\ThrottleRequests::class,
|
|
||||||
\Illuminate\Session\Middleware\AuthenticateSession::class,
|
|
||||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
|
||||||
\Illuminate\Auth\Middleware\Authorize::class,
|
|
||||||
];
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Middleware;
|
|
||||||
|
|
||||||
use Illuminate\Auth\Middleware\Authenticate as Middleware;
|
|
||||||
|
|
||||||
class Authenticate extends Middleware
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Get the path the user should be redirected to when they are not authenticated.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function redirectTo($request)
|
|
||||||
{
|
|
||||||
if (! $request->expectsJson()) {
|
|
||||||
return route('login');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Middleware;
|
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode as Middleware;
|
|
||||||
|
|
||||||
class CheckForMaintenanceMode extends Middleware
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The URIs that should be reachable while maintenance mode is enabled.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $except = [
|
|
||||||
//
|
|
||||||
];
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Middleware;
|
|
||||||
|
|
||||||
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
|
|
||||||
|
|
||||||
class EncryptCookies extends Middleware
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The names of the cookies that should not be encrypted.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $except = [
|
|
||||||
//
|
|
||||||
];
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Middleware;
|
|
||||||
|
|
||||||
use Closure;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
|
|
||||||
class RedirectIfAuthenticated
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Handle an incoming request.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param \Closure $next
|
|
||||||
* @param string|null $guard
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function handle($request, Closure $next, $guard = null)
|
|
||||||
{
|
|
||||||
if (Auth::guard($guard)->check()) {
|
|
||||||
return redirect('/home');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $next($request);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Middleware;
|
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
|
|
||||||
|
|
||||||
class TrimStrings extends Middleware
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The names of the attributes that should not be trimmed.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $except = [
|
|
||||||
'password',
|
|
||||||
'password_confirmation',
|
|
||||||
];
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Middleware;
|
|
||||||
|
|
||||||
use Fideloper\Proxy\TrustProxies as Middleware;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class TrustProxies extends Middleware
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The trusted proxies for this application.
|
|
||||||
*
|
|
||||||
* @var array|string
|
|
||||||
*/
|
|
||||||
protected $proxies;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The headers that should be used to detect proxies.
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
protected $headers = Request::HEADER_X_FORWARDED_ALL;
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Middleware;
|
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
|
|
||||||
|
|
||||||
class VerifyCsrfToken extends Middleware
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Indicates whether the XSRF-TOKEN cookie should be set on the response.
|
|
||||||
*
|
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
protected $addHttpCookie = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The URIs that should be excluded from CSRF verification.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $except = [
|
|
||||||
//
|
|
||||||
];
|
|
||||||
}
|
|
76
app/Jobs/CatalogMove.php
Normal file
76
app/Jobs/CatalogMove.php
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Queue\Queueable;
|
||||||
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
use App\Models\Abstracted\Catalog;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class CatalogMove implements ShouldQueue,ShouldBeUnique
|
||||||
|
{
|
||||||
|
use InteractsWithQueue,Queueable,SerializesModels;
|
||||||
|
|
||||||
|
// Our object
|
||||||
|
private Catalog $o;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(Catalog $o) {
|
||||||
|
$this->o = $o->withoutRelations();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function middleware(): array
|
||||||
|
{
|
||||||
|
return [new WithoutOverlapping($this->o->id)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$from = $this->o->file_name_rel(TRUE);
|
||||||
|
$to = $this->o->file_name_rel(FALSE);
|
||||||
|
|
||||||
|
Log::info(sprintf('%s: Moving [%s|%s] from [%s] to [%s]',
|
||||||
|
__METHOD__,
|
||||||
|
$this->o->objecttype(),
|
||||||
|
$this->o->id,
|
||||||
|
$from,
|
||||||
|
$to,
|
||||||
|
));
|
||||||
|
|
||||||
|
// If the move is successful, commit the transaction
|
||||||
|
if ($this->o->isMoveable()) {
|
||||||
|
// If our move fails, we'll abort the update
|
||||||
|
DB::beginTransaction();
|
||||||
|
$this->o->filename = $this->o->file_name();
|
||||||
|
$this->o->save();
|
||||||
|
|
||||||
|
if (Storage::disk($this->o::fs)->move($from,$to))
|
||||||
|
DB::commit();
|
||||||
|
else
|
||||||
|
Log::error(sprintf('%s: Move failed for file [%s]',__METHOD__,$from));
|
||||||
|
|
||||||
|
} else
|
||||||
|
Log::alert(sprintf('%s: Unable to move file [%s] with reason [%s]',
|
||||||
|
__METHOD__,
|
||||||
|
$from,
|
||||||
|
$this->o->isMoveableReason(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
@ -2,39 +2,102 @@
|
|||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Queue\Queueable;
|
||||||
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Artisan;
|
|
||||||
|
|
||||||
use App\Models\Abstracted\Catalog;
|
use App\Models\Abstracted\Catalog;
|
||||||
|
|
||||||
class CatalogScan extends Job implements ShouldQueue
|
class CatalogScan implements ShouldQueue, ShouldBeUnique
|
||||||
{
|
{
|
||||||
use InteractsWithQueue, SerializesModels;
|
use InteractsWithQueue,Queueable,SerializesModels;
|
||||||
|
|
||||||
// Our object
|
// Our object
|
||||||
private $o = NULL;
|
private Catalog $o;
|
||||||
|
private bool $show_dirty;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new job instance.
|
* Create a new job instance.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function __construct(Catalog $o) {
|
public function __construct(Catalog $o,bool $show_dirty=FALSE) {
|
||||||
$this->o = $o;
|
$this->o = $o->withoutRelations();
|
||||||
|
$this->show_dirty = $show_dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function middleware(): array
|
||||||
|
{
|
||||||
|
return [new WithoutOverlapping($this->o->id)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the unique ID for the job.
|
||||||
|
*/
|
||||||
|
public function uniqueId(): string
|
||||||
|
{
|
||||||
|
return $this->o->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the job.
|
* Execute the job.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
Log::info(sprintf('%s: Scanning [%s|%s]',__METHOD__,$this->o->objecttype(),$this->o->id));
|
Log::info(sprintf('%s: Scanning [%s|%s]',__METHOD__,$this->o->objecttype(),$this->o->id));
|
||||||
|
|
||||||
Artisan::call('catalog:scan',['type'=>$this->o->objecttype(),'id'=>$this->o->id]);
|
if (! $this->o->isReadable()) {
|
||||||
|
Log::alert(sprintf('Ignoring [%s], it is not readable',$this->o->file_name(FALSE)));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->o->init();
|
||||||
|
|
||||||
|
// If this is a duplicate
|
||||||
|
$x = $this->o->myduplicates()->get();
|
||||||
|
if (count($x)) {
|
||||||
|
foreach ($x as $this->oo) {
|
||||||
|
// And that catalog item is not marked as a duplicate
|
||||||
|
if (! $this->oo->duplicate) {
|
||||||
|
$this->o->duplicate = TRUE;
|
||||||
|
Log::alert(sprintf('Image [%s] marked as a duplicate',$this->o->filename));
|
||||||
|
|
||||||
|
// If the file signature also matches, we'll mark it for deletion
|
||||||
|
if ($this->oo->file_signature && ($this->o->file_signature == $this->oo->file_signature)) {
|
||||||
|
Log::alert(sprintf('Image [%s] marked for deletion',$this->o->filename));
|
||||||
|
$this->o->remove = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->o->scanned = TRUE;
|
||||||
|
|
||||||
|
if ($this->o->getDirty()) {
|
||||||
|
Log::alert(sprintf('Image [%s] metadata changed',$this->o->filename));
|
||||||
|
|
||||||
|
if ($this->show_dirty)
|
||||||
|
dump(['id'=>$this->o->id,'data'=>$this->o->getDirty()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the file signature changed, abort the update.
|
||||||
|
if ($this->o->getOriginal('file_signature') && $this->o->wasChanged('file_signature'))
|
||||||
|
throw new \Exception(sprintf('File Signature Changed for [%s] DB: [%s], File: [%s]?',
|
||||||
|
$this->o->file_name(),
|
||||||
|
$this->o->file_signature,
|
||||||
|
$this->o->getOriginal('file_signature'),
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->o->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,100 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Jobs;
|
|
||||||
|
|
||||||
use Illuminate\Bus\Queueable;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
use App\Traits\Files;
|
|
||||||
use App\Traits\Type;
|
|
||||||
|
|
||||||
class CatalogVerify extends Job implements ShouldQueue
|
|
||||||
{
|
|
||||||
use Dispatchable, Queueable, InteractsWithQueue, SerializesModels, Type, Files;
|
|
||||||
|
|
||||||
// What we should verify
|
|
||||||
private $type = NULL;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new job instance.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function __construct(string $type) {
|
|
||||||
$this->type = $type;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the job.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
Log::info(sprintf('%s: Scanning [%s]',__METHOD__,$this->type));
|
|
||||||
|
|
||||||
// Go through DB and verify files exist
|
|
||||||
$class = $this->getModelType($this->type);
|
|
||||||
|
|
||||||
$good = $bad = $ugly = 0;
|
|
||||||
|
|
||||||
$class::select('*')->each(function($o) use ($good,$bad,$ugly) {
|
|
||||||
if (! file_exists($o->file_name_current(FALSE))) {
|
|
||||||
Log::error(sprintf('Media doesnt exist: [%s] (%d:%s)',$this->type,$o->id,$o->file_name_current(FALSE)));
|
|
||||||
$bad++;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (($x=md5_file($o->file_name_current(FALSE))) !== $o->file_signature) {
|
|
||||||
Log::error(sprintf('Media MD5 doesnt match DB: [%s] (%d:%s) [%s:%s]',$this->type,$o->id,$o->file_name_current(FALSE),$x,$o->file_signature));
|
|
||||||
$ugly++;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$good++;
|
|
||||||
});
|
|
||||||
|
|
||||||
Log::info(sprintf('DB Media Verify Complete: [%s] Good: [%d], Missing: [%d], Changed: [%d]',$this->type,$good,$bad,$ugly));
|
|
||||||
|
|
||||||
// Go through filesystem and see that a record exists in the DB, if not add it.
|
|
||||||
$parentdir = config($this->type.'.dir');
|
|
||||||
|
|
||||||
$good = $bad = 0;
|
|
||||||
|
|
||||||
foreach ($this->dirlist($parentdir) as $dir) {
|
|
||||||
foreach ($this->getFiles(['dir'=>$dir,'file'=>NULL],$this->type) as $file) {
|
|
||||||
if (! $class::where('filename',$file)->count()) {
|
|
||||||
$bad++;
|
|
||||||
Log::error(sprintf('File not in DB: [%s] (%s)',$this->type,$file));
|
|
||||||
} else {
|
|
||||||
$good++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Log::info(sprintf('File Media Verify Complete: [%s] Good: [%d], Not In DB: [%d]',$this->type,$good,$bad));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively get a list of dirs
|
|
||||||
* @param $path
|
|
||||||
* @return \Illuminate\Support\Collection
|
|
||||||
*/
|
|
||||||
private function dirlist($path)
|
|
||||||
{
|
|
||||||
$list = collect();
|
|
||||||
|
|
||||||
$list->push($path);
|
|
||||||
|
|
||||||
foreach (glob($path.'/*',GLOB_ONLYDIR) as $dir) {
|
|
||||||
foreach ($this->dirlist($dir) as $subdir)
|
|
||||||
$list->push($subdir);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $list;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Jobs;
|
|
||||||
|
|
||||||
use Illuminate\Bus\Queueable;
|
|
||||||
|
|
||||||
abstract class Job
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Queueable Jobs
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This job base class provides a central location to place any logic that
|
|
||||||
| is shared across all of your jobs. The trait included with the class
|
|
||||||
| provides access to the "onQueue" and "delay" queue helper methods.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
use Queueable;
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Jobs;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Support\Facades\Artisan;
|
|
||||||
|
|
||||||
use App\Models\Photo;
|
|
||||||
|
|
||||||
class PhotoMove extends Job implements ShouldQueue
|
|
||||||
{
|
|
||||||
use InteractsWithQueue,SerializesModels;
|
|
||||||
|
|
||||||
public $photo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new job instance.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function __construct(Photo $o)
|
|
||||||
{
|
|
||||||
$this->photo = $o;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the job.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
Log::info(sprintf('%s: Moving [%s]',__METHOD__,$this->photo->id));
|
|
||||||
Artisan::call('photo:move',['--id' => $this->photo->id]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Jobs;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Support\Facades\Artisan;
|
|
||||||
|
|
||||||
use App\Models\Video;
|
|
||||||
|
|
||||||
class VideoMove extends Job implements ShouldQueue
|
|
||||||
{
|
|
||||||
use InteractsWithQueue,SerializesModels;
|
|
||||||
|
|
||||||
public $video;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new job instance.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function __construct(Video $o)
|
|
||||||
{
|
|
||||||
$this->video = $o;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the job.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
Log::info(sprintf('%s: Moving [%s]',__METHOD__,$this->video->id));
|
|
||||||
Artisan::call('video:move',['--id' => $this->video->id]);
|
|
||||||
}
|
|
||||||
}
|
|
42
app/Media/Base.php
Normal file
42
app/Media/Base.php
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
abstract class Base
|
||||||
|
{
|
||||||
|
protected const BLOCK_SIZE = 4096;
|
||||||
|
|
||||||
|
/** Full path to the file */
|
||||||
|
protected string $filename;
|
||||||
|
protected int $filesize;
|
||||||
|
protected string $type;
|
||||||
|
|
||||||
|
public function __construct(string $filename,string $type)
|
||||||
|
{
|
||||||
|
Log::info(sprintf('Create a media type [%s] for [%s]',get_class($this),$filename));
|
||||||
|
|
||||||
|
$this->filename = $filename;
|
||||||
|
$this->filesize = filesize($filename);
|
||||||
|
$this->type = $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable getting values for keys in the response
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @return mixed|object
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function __get(string $key): mixed
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'type':
|
||||||
|
return $this->type;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new \Exception('Unknown key: '.$key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
app/Media/Factory.php
Normal file
30
app/Media/Factory.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media;
|
||||||
|
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
|
class Factory {
|
||||||
|
private const LOGKEY = 'MF-';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array event type to event class mapping
|
||||||
|
*/
|
||||||
|
public const map = [
|
||||||
|
'video/quicktime' => QuickTime::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns new event instance
|
||||||
|
*
|
||||||
|
* @param string $file
|
||||||
|
* @return Base
|
||||||
|
*/
|
||||||
|
public static function create(string $file): Base
|
||||||
|
{
|
||||||
|
$type = mime_content_type($file);
|
||||||
|
$class = Arr::get(self::map,$type,Unknown::class);
|
||||||
|
|
||||||
|
return new $class($file,$type);
|
||||||
|
}
|
||||||
|
}
|
126
app/Media/QuickTime.php
Normal file
126
app/Media/QuickTime.php
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media;
|
||||||
|
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atoms\{mdat,moov,Unknown};
|
||||||
|
use App\Traits\FindQuicktimeAtoms;
|
||||||
|
|
||||||
|
// https://developer.apple.com/documentation/quicktime-file-format/quicktime_movie_files
|
||||||
|
|
||||||
|
class QuickTime extends Base {
|
||||||
|
use FindQuicktimeAtoms;
|
||||||
|
|
||||||
|
private const LOGKEY = 'MFQ';
|
||||||
|
|
||||||
|
private Collection $atoms;
|
||||||
|
|
||||||
|
private const atom_classes = 'App\\Media\\QuickTime\\Atoms\\';
|
||||||
|
|
||||||
|
public function __construct(string $filename,string $type)
|
||||||
|
{
|
||||||
|
parent::__construct($filename,$type);
|
||||||
|
|
||||||
|
// Parse the atoms
|
||||||
|
$this->atoms = $this->get_atoms(self::atom_classes,Unknown::class,0,$this->filesize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __get(string $key): mixed
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'audio_channels':
|
||||||
|
case 'audio_codec':
|
||||||
|
case 'audio_samplerate':
|
||||||
|
return $this->getAudioAtoms()
|
||||||
|
->map(fn($item)=>$item->find_atoms(moov\trak\mdia\minf\stbl\stsd::class,1))
|
||||||
|
->flatten()
|
||||||
|
->map(fn($item)=>$item->{$key})
|
||||||
|
->join(',');
|
||||||
|
|
||||||
|
// Signatures are calculated by the sha of the MDAT atom.
|
||||||
|
case 'signature':
|
||||||
|
$atom = $this->find_atoms(mdat::class,1);
|
||||||
|
|
||||||
|
return $atom->signature;
|
||||||
|
|
||||||
|
// Creation Time is in the MOOV/MVHD atom
|
||||||
|
case 'creation_date':
|
||||||
|
case 'duration':
|
||||||
|
case 'preferred_rate':
|
||||||
|
case 'preferred_volume':
|
||||||
|
// Height/Width is in the moov/trak/tkhd attom
|
||||||
|
case 'height':
|
||||||
|
case 'width':
|
||||||
|
$atom = $this->find_atoms(moov::class,1);
|
||||||
|
|
||||||
|
return $atom->{$key};
|
||||||
|
|
||||||
|
case 'gps_altitude':
|
||||||
|
case 'gps_lat':
|
||||||
|
case 'gps_lon':
|
||||||
|
$atom = $this->find_atoms(moov::class,1)
|
||||||
|
->find_atoms(moov\meta::class,1);
|
||||||
|
|
||||||
|
return $atom->{$key};
|
||||||
|
|
||||||
|
case 'make':
|
||||||
|
case 'model':
|
||||||
|
case 'software':
|
||||||
|
return $this->find_atoms(moov\meta::class,1)->{$key};
|
||||||
|
|
||||||
|
case 'time_scale':
|
||||||
|
$atom = $this->find_atoms(moov\mvhd::class,1);
|
||||||
|
|
||||||
|
return $atom->{$key};
|
||||||
|
|
||||||
|
case 'type':
|
||||||
|
return parent::__get($key);
|
||||||
|
|
||||||
|
case 'video_codec':
|
||||||
|
return $this->getVideoAtoms()
|
||||||
|
->map(fn($item)=>$item->find_atoms(moov\trak\mdia\minf\stbl\stsd::class,1))
|
||||||
|
->flatten()
|
||||||
|
->map(fn($item)=>$item->{$key})
|
||||||
|
->join(',');
|
||||||
|
|
||||||
|
case 'video_framerate':
|
||||||
|
$atom = $this->getVideoAtoms()
|
||||||
|
->map(fn($item)=>$item->find_atoms(moov\trak\mdia\minf\stbl\stts::class,1))
|
||||||
|
->pop();
|
||||||
|
|
||||||
|
return $atom->frame_rate($this->time_scale);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new \Exception('Unknown key: '.$key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find all the audio track atoms
|
||||||
|
*
|
||||||
|
* Audio atoms are in moov/trak/mdia/minf
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function getAudioAtoms(): Collection
|
||||||
|
{
|
||||||
|
return $this->find_atoms(moov\trak\mdia\minf::class)
|
||||||
|
->filter(fn($item)=>$item->type==='soun');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find all the video track atoms
|
||||||
|
*
|
||||||
|
* Audio atoms are in moov/trak/mdia/minf
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function getVideoAtoms(): Collection
|
||||||
|
{
|
||||||
|
return $this->find_atoms(moov\trak\mdia\minf::class)
|
||||||
|
->filter(fn($item)=>$item->type==='vide');
|
||||||
|
}
|
||||||
|
}
|
144
app/Media/QuickTime/Atom.php
Normal file
144
app/Media/QuickTime/Atom.php
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media\QuickTime;
|
||||||
|
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atoms\moov\{mvhd,trak};
|
||||||
|
use App\Traits\FindQuicktimeAtoms;
|
||||||
|
|
||||||
|
abstract class Atom
|
||||||
|
{
|
||||||
|
use FindQuicktimeAtoms;
|
||||||
|
|
||||||
|
protected const record_size = 16384;
|
||||||
|
|
||||||
|
protected const BLOCK_SIZE = 4096;
|
||||||
|
|
||||||
|
protected int $offset;
|
||||||
|
protected int $size;
|
||||||
|
protected string $filename;
|
||||||
|
|
||||||
|
private mixed $fh;
|
||||||
|
private int $fp;
|
||||||
|
protected Collection $cache;
|
||||||
|
protected Collection $atoms;
|
||||||
|
|
||||||
|
public function __construct(int $offset,int $size,string $filename)
|
||||||
|
{
|
||||||
|
$this->offset = $offset;
|
||||||
|
|
||||||
|
// Quick validation
|
||||||
|
if ($size < 0)
|
||||||
|
throw new \Exception(sprintf('Atom cannot be negative. (%d)',$size));
|
||||||
|
|
||||||
|
$this->size = $size;
|
||||||
|
$this->filename = $filename;
|
||||||
|
$this->cache = collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __get(string $key): mixed
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
// Create time is in the MOOV/MVHD atom
|
||||||
|
case 'creation_date':
|
||||||
|
case 'duration':
|
||||||
|
case 'preferred_rate':
|
||||||
|
case 'preferred_volume':
|
||||||
|
$subatom = $this->find_atoms(mvhd::class,1);
|
||||||
|
|
||||||
|
return $subatom->{$key};
|
||||||
|
|
||||||
|
// Height is in the moov/trak/tkhd attom
|
||||||
|
case 'height':
|
||||||
|
// Width is in the moov/trak/tkhd attom
|
||||||
|
case 'width':
|
||||||
|
$atom = $this->find_atoms(trak::class);
|
||||||
|
|
||||||
|
return $atom->map(fn($item)=>$item->{$key})->filter()->max();
|
||||||
|
|
||||||
|
// Signatures are calculated by the sha of the MDAT atom.
|
||||||
|
case 'signature':
|
||||||
|
return $this->signature();
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new \Exception('Unknown key: '.$key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function data(): string
|
||||||
|
{
|
||||||
|
// Quick validation
|
||||||
|
if ($this->size > self::record_size)
|
||||||
|
throw new \Exception(sprintf('Refusing to read more than %d of data',self::record_size));
|
||||||
|
|
||||||
|
$data = '';
|
||||||
|
if ($this->fopen()) {
|
||||||
|
while (! is_null($read=$this->fread()))
|
||||||
|
$data .= $read;
|
||||||
|
|
||||||
|
$this->fclose();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function fclose(): bool
|
||||||
|
{
|
||||||
|
fclose($this->fh);
|
||||||
|
unset($this->fh);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the file and seek to the atom
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function fopen(): bool
|
||||||
|
{
|
||||||
|
$this->fh = fopen($this->filename,'r');
|
||||||
|
fseek($this->fh,$this->offset);
|
||||||
|
$this->fp = 0;
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the atom from the file
|
||||||
|
*
|
||||||
|
* @param int $size
|
||||||
|
* @return string|NULL
|
||||||
|
*/
|
||||||
|
protected function fread(int $size=4096): ?string
|
||||||
|
{
|
||||||
|
if ($this->fp === $this->size)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if ($this->fp+$size > $this->size)
|
||||||
|
$size = $this->size-$this->fp;
|
||||||
|
|
||||||
|
$read = fread($this->fh,$size);
|
||||||
|
$this->fp += $size;
|
||||||
|
|
||||||
|
return $read;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function fseek(int $offset): int
|
||||||
|
{
|
||||||
|
$this->fp = $offset;
|
||||||
|
|
||||||
|
return fseek($this->fh,$this->offset+$this->fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function unpack(array $unpack=[]): string
|
||||||
|
{
|
||||||
|
return collect($unpack ?: static::unpack)->map(fn($v,$k)=>$v[0].$k)->join('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function unpack_size(array $unpack=[]): int
|
||||||
|
{
|
||||||
|
return collect($unpack ?: static::unpack)->map(fn($v,$k)=>$v[1])->sum();
|
||||||
|
}
|
||||||
|
}
|
59
app/Media/QuickTime/Atoms/SubAtom.php
Normal file
59
app/Media/QuickTime/Atoms/SubAtom.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media\QuickTime\Atoms;
|
||||||
|
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Leenooks\Traits\ObjectIssetFix;
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atom;
|
||||||
|
use App\Media\QuickTime\Atoms\moov\trak\tkhd;
|
||||||
|
|
||||||
|
abstract class SubAtom extends Atom
|
||||||
|
{
|
||||||
|
use ObjectIssetFix;
|
||||||
|
|
||||||
|
protected ?string $unused_data;
|
||||||
|
|
||||||
|
protected const atom_record = [
|
||||||
|
'version'=>['c',1],
|
||||||
|
'flags'=>['a3',3],
|
||||||
|
'count'=>['N',4],
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __get(string $key): mixed
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
// Height is in the moov/trak/tkhd attom
|
||||||
|
case 'height':
|
||||||
|
// Width is in the moov/trak/tkhd attom
|
||||||
|
case 'width':
|
||||||
|
$atom = $this->find_atoms(tkhd::class,1);
|
||||||
|
|
||||||
|
return $atom->{$key};
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new \Exception('Unknown key: '.$key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unpack data into our cache
|
||||||
|
*
|
||||||
|
* @param string|null $data
|
||||||
|
* @return Collection
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
protected function cache(?string $data=NULL): Collection
|
||||||
|
{
|
||||||
|
$data = $data ?: $this->data();
|
||||||
|
|
||||||
|
if (! count($this->cache) && $this->size) {
|
||||||
|
$this->cache = collect(unpack($this->unpack(),$data));
|
||||||
|
|
||||||
|
if ($this->size > ($x=$this->unpack_size()))
|
||||||
|
$this->unused_data = substr($data,$x);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->cache;
|
||||||
|
}
|
||||||
|
}
|
23
app/Media/QuickTime/Atoms/Unknown.php
Normal file
23
app/Media/QuickTime/Atoms/Unknown.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media\QuickTime\Atoms;
|
||||||
|
|
||||||
|
// An atom we dont know how to handle
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atom;
|
||||||
|
|
||||||
|
class Unknown extends Atom
|
||||||
|
{
|
||||||
|
private string $atom;
|
||||||
|
|
||||||
|
public function __construct(int $offset,int $size,string $filename,string $atom,?string $data)
|
||||||
|
{
|
||||||
|
parent::__construct($offset,$size,$filename,$data);
|
||||||
|
|
||||||
|
$this->atom = $atom;
|
||||||
|
|
||||||
|
// For debugging
|
||||||
|
if (FALSE)
|
||||||
|
$this->debug = hex_dump($data ?: $this->data());
|
||||||
|
}
|
||||||
|
}
|
12
app/Media/QuickTime/Atoms/free.php
Normal file
12
app/Media/QuickTime/Atoms/free.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media\QuickTime\Atoms;
|
||||||
|
|
||||||
|
// Unused space available in file.
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atom;
|
||||||
|
|
||||||
|
class free extends Atom
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
34
app/Media/QuickTime/Atoms/ftyp.php
Normal file
34
app/Media/QuickTime/Atoms/ftyp.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media\QuickTime\Atoms;
|
||||||
|
|
||||||
|
// File type compatibility—identifies the file type and differentiates it from similar file types,
|
||||||
|
// such as MPEG-4 files and JPEG-2000 files.
|
||||||
|
|
||||||
|
// The file type atom has an atom type value of 'ftyp' and contains the following fields:
|
||||||
|
// Size, Type, Major brand, Minor version, Compatible brands.
|
||||||
|
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atom;
|
||||||
|
|
||||||
|
class ftyp extends Atom
|
||||||
|
{
|
||||||
|
protected const unpack = [
|
||||||
|
'major'=>['a4',1],
|
||||||
|
'minor'=>['a4',4],
|
||||||
|
'compat'=>['a4',4],
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct(int $offset,int $size,string $filename,?string $data) {
|
||||||
|
if ($size > 12)
|
||||||
|
throw new \Exception('FTYP atom larger than 12 bytes, we wont be able to handled that');
|
||||||
|
|
||||||
|
parent::__construct($offset,$size,$filename);
|
||||||
|
|
||||||
|
$this->cache['data'] = unpack($this->unpack(),$data);
|
||||||
|
|
||||||
|
if (Arr::get($this->cache,'data.compat') !== 'qt ')
|
||||||
|
throw new \Exception('This is not a QT format file');
|
||||||
|
}
|
||||||
|
}
|
42
app/Media/QuickTime/Atoms/mdat.php
Normal file
42
app/Media/QuickTime/Atoms/mdat.php
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media\QuickTime\Atoms;
|
||||||
|
|
||||||
|
// Movie sample data—media samples such as video frames and groups of audio samples.
|
||||||
|
// Usually this data can be interpreted only by using the movie resource.
|
||||||
|
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atom;
|
||||||
|
|
||||||
|
class mdat extends Atom
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Calculate the signature of the data
|
||||||
|
*
|
||||||
|
* @param string $alg
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function signature(string $alg='sha1'): string
|
||||||
|
{
|
||||||
|
if (! Arr::has($this->cache,'signature')) {
|
||||||
|
if ($this->size) {
|
||||||
|
$this->fopen();
|
||||||
|
|
||||||
|
$hash = hash_init($alg);
|
||||||
|
|
||||||
|
while (!is_null($read = $this->fread(16384)))
|
||||||
|
hash_update($hash, $read);
|
||||||
|
|
||||||
|
$this->fclose();
|
||||||
|
|
||||||
|
$this->cache['signature'] = hash_final($hash);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$this->cache['signature'] = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->cache['signature'];
|
||||||
|
}
|
||||||
|
}
|
19
app/Media/QuickTime/Atoms/moov.php
Normal file
19
app/Media/QuickTime/Atoms/moov.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media\QuickTime\Atoms;
|
||||||
|
|
||||||
|
// Movie resource metadata about the movie (number and type of tracks, location of sample data, and so on).
|
||||||
|
// Describes where the movie data can be found and how to interpret it.
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atom;
|
||||||
|
|
||||||
|
class moov extends Atom
|
||||||
|
{
|
||||||
|
private const subatom_classes = 'App\\Media\\QuickTime\\Atoms\\moov\\';
|
||||||
|
|
||||||
|
public function __construct(int $offset,int $size,string $filename,?string $data) {
|
||||||
|
parent::__construct($offset,$size,$filename);
|
||||||
|
|
||||||
|
$this->atoms = $this->get_atoms(self::subatom_classes,Unknown::class,$offset,$size,$data);
|
||||||
|
}
|
||||||
|
}
|
12
app/Media/QuickTime/Atoms/moov/free.php
Normal file
12
app/Media/QuickTime/Atoms/moov/free.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media\QuickTime\Atoms\moov;
|
||||||
|
|
||||||
|
// Unused space available in file.
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atoms\SubAtom;
|
||||||
|
|
||||||
|
class free extends SubAtom
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
52
app/Media/QuickTime/Atoms/moov/meta.php
Normal file
52
app/Media/QuickTime/Atoms/moov/meta.php
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media\QuickTime\Atoms\moov;
|
||||||
|
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atoms\moov\meta\{ilst,keys};
|
||||||
|
use App\Media\QuickTime\Atoms\{SubAtom,Unknown};
|
||||||
|
|
||||||
|
class meta extends SubAtom
|
||||||
|
{
|
||||||
|
private const subatom_classes = 'App\\Media\\QuickTime\\Atoms\\moov\\meta\\';
|
||||||
|
|
||||||
|
public function __construct(int $offset,int $size,string $filename,?string $data)
|
||||||
|
{
|
||||||
|
parent::__construct($offset,$size,$filename);
|
||||||
|
|
||||||
|
$this->atoms = $this->get_atoms(self::subatom_classes,Unknown::class,$offset,$size,$data);
|
||||||
|
|
||||||
|
$keys = $this->find_atoms(keys::class,1);
|
||||||
|
$values = $this->find_atoms(ilst::class,1);
|
||||||
|
|
||||||
|
$this->cache = $keys->cache->combine($values->cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __get(string $key): mixed
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'gps':
|
||||||
|
$m = [];
|
||||||
|
$gps = Arr::get($this->cache,'mdta.com.apple.quicktime.location.ISO6709');
|
||||||
|
preg_match('/^([+-][0-9]{2,6}(?:\.[0-9]+)?)([+-][0-9]{3,7}(?:\.[0-9]+)?)([+-][0-9]+(?:\.[0-9]+)?)?/',$gps,$m);
|
||||||
|
|
||||||
|
return ['lat'=>(float)$m[1],'lon'=>(float)$m[2],'alt'=>(float)$m[3]];
|
||||||
|
|
||||||
|
case 'gps_altitude':
|
||||||
|
return Arr::get($this->gps,'alt');
|
||||||
|
case 'gps_lat':
|
||||||
|
return Arr::get($this->gps,'lat');
|
||||||
|
case 'gps_lon':
|
||||||
|
return Arr::get($this->gps,'lon');
|
||||||
|
|
||||||
|
case 'make':
|
||||||
|
case 'model':
|
||||||
|
case 'software':
|
||||||
|
return Arr::get($this->cache,'mdta.com.apple.quicktime.'.$key);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return parent::__get($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
app/Media/QuickTime/Atoms/moov/meta/free.php
Normal file
12
app/Media/QuickTime/Atoms/moov/meta/free.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media\QuickTime\Atoms\moov\meta;
|
||||||
|
|
||||||
|
// Unused space available in file.
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atoms\SubAtom;
|
||||||
|
|
||||||
|
class free extends SubAtom
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
41
app/Media/QuickTime/Atoms/moov/meta/ilst.php
Normal file
41
app/Media/QuickTime/Atoms/moov/meta/ilst.php
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media\QuickTime\Atoms\moov\meta;
|
||||||
|
|
||||||
|
// Item LiST container atom
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atoms\SubAtom;
|
||||||
|
|
||||||
|
class ilst extends SubAtom
|
||||||
|
{
|
||||||
|
public function __construct(int $offset,int $size,string $filename,?string $data)
|
||||||
|
{
|
||||||
|
parent::__construct($offset,$size,$filename);
|
||||||
|
|
||||||
|
$ptr = 0;
|
||||||
|
while ($ptr < strlen($data)) {
|
||||||
|
$key_size = unpack('Nsize',substr($data,$ptr,4));
|
||||||
|
// Sometimes atoms are terminated with a 0
|
||||||
|
if ($key_size['size'] === 0) {
|
||||||
|
$ptr += 4;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$a = unpack(sprintf('a4name/a%ddata',$key_size['size']-8),substr($data,$ptr+4,4+$key_size['size']-8));
|
||||||
|
$ptr += $key_size['size'];
|
||||||
|
|
||||||
|
// If we didnt get the right amount of data, something is wrong.
|
||||||
|
if (strlen($a['data']) < $key_size['size']-8)
|
||||||
|
break;
|
||||||
|
|
||||||
|
$b = unpack(sprintf('Nsize/a4name/a%ddata',strlen($a['data'])-8),$a['data']);
|
||||||
|
|
||||||
|
if ($b['name'] !== 'data')
|
||||||
|
throw new \Exception('Parsing of ILST got data that wasnt expected');
|
||||||
|
$c = unpack(sprintf('a4language/a4unknown/a%ddata',strlen($b['data'])-8),$b['data']);
|
||||||
|
|
||||||
|
// heirachy, name, size, offset
|
||||||
|
$this->cache = $this->cache->push($c['data']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
app/Media/QuickTime/Atoms/moov/meta/keys.php
Normal file
26
app/Media/QuickTime/Atoms/moov/meta/keys.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media\QuickTime\Atoms\moov\meta;
|
||||||
|
|
||||||
|
// The metadata item keys atom holds a list of the metadata keys that may be present in the metadata atom.
|
||||||
|
// This list is indexed starting with 1; 0 is a reserved index value. The metadata item keys atom is a full atom with an atom type of "keys".
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atoms\SubAtom;
|
||||||
|
|
||||||
|
class keys extends SubAtom
|
||||||
|
{
|
||||||
|
public function __construct(int $offset,int $size,string $filename,?string $data)
|
||||||
|
{
|
||||||
|
parent::__construct($offset,$size,$filename);
|
||||||
|
|
||||||
|
$this->keys = collect();
|
||||||
|
$read = unpack($this->unpack(self::atom_record),substr($data,0,$ptr=$this->unpack_size(self::atom_record)));
|
||||||
|
|
||||||
|
for ($i=0; $i<$read['count']; $i++) {
|
||||||
|
$key_size = unpack('Nsize',substr($data,$ptr,4));
|
||||||
|
$keys = unpack(sprintf('a4namespace/a%dname',$key_size['size']-8),substr($data,$ptr+4,4+$key_size['size']-8));
|
||||||
|
$ptr += $key_size['size'];
|
||||||
|
$this->cache = $this->cache->push(sprintf('%s.%s',$keys['namespace'],$keys['name']));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
app/Media/QuickTime/Atoms/moov/mvhd.php
Normal file
56
app/Media/QuickTime/Atoms/moov/mvhd.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media\QuickTime\Atoms\moov;
|
||||||
|
|
||||||
|
// Specifies the characteristics of an entire QuickTime movie.
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atoms\SubAtom;
|
||||||
|
|
||||||
|
class mvhd extends SubAtom
|
||||||
|
{
|
||||||
|
protected const unpack = [
|
||||||
|
'version'=>['c',1],
|
||||||
|
'flags'=>['a3',3],
|
||||||
|
'create'=>['N',4],
|
||||||
|
'modified'=>['N',4],
|
||||||
|
'time_scale'=>['N',4],
|
||||||
|
'duration'=>['N',4],
|
||||||
|
'prate'=>['a4',4],
|
||||||
|
'pvolume'=>['a2',2],
|
||||||
|
'matrix'=>['a36',36],
|
||||||
|
'prevtime'=>['N',4],
|
||||||
|
'prevdur'=>['N',4],
|
||||||
|
'postertime'=>['N',4],
|
||||||
|
'selecttime'=>['N',4],
|
||||||
|
'selectdur'=>['N',4],
|
||||||
|
'curtime'=>['N',4],
|
||||||
|
'nexttrack'=>['N',4],
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __get($key): mixed
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'creation_date':
|
||||||
|
// We need to convert from MAC time 1904-01-01 to epoch
|
||||||
|
return Carbon::createFromTimestamp(Arr::get($this->cache(),'create')-2082844800);
|
||||||
|
|
||||||
|
case 'duration':
|
||||||
|
return ($x=$this->time_scale) ? round(Arr::get($this->cache(),'duration')/$x,3) : NULL;
|
||||||
|
|
||||||
|
case 'preferred_rate':
|
||||||
|
return fixed_point_int_to_float(Arr::get($this->cache(),'prate'));
|
||||||
|
|
||||||
|
case 'preferred_volume':
|
||||||
|
return fixed_point_int_to_float(Arr::get($this->cache(),'pvolume'));
|
||||||
|
|
||||||
|
case 'time_scale':
|
||||||
|
return round(Arr::get($this->cache(),$key));
|
||||||
|
|
||||||
|
default:
|
||||||
|
return parent::__get($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
app/Media/QuickTime/Atoms/moov/trak.php
Normal file
19
app/Media/QuickTime/Atoms/moov/trak.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media\QuickTime\Atoms\moov;
|
||||||
|
|
||||||
|
// An atom that defines a single track of a movie
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atoms\SubAtom;
|
||||||
|
use App\Media\QuickTime\Atoms\Unknown;
|
||||||
|
|
||||||
|
class trak extends SubAtom
|
||||||
|
{
|
||||||
|
private const subatom_classes = 'App\\Media\\QuickTime\\Atoms\\moov\\trak\\';
|
||||||
|
|
||||||
|
public function __construct(int $offset,int $size,string $filename,?string $data) {
|
||||||
|
parent::__construct($offset,$size,$filename);
|
||||||
|
|
||||||
|
$this->atoms = $this->get_atoms(self::subatom_classes,Unknown::class,$offset,$size,$data);
|
||||||
|
}
|
||||||
|
}
|
33
app/Media/QuickTime/Atoms/moov/trak/mdia.php
Normal file
33
app/Media/QuickTime/Atoms/moov/trak/mdia.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media\QuickTime\Atoms\moov\trak;
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atoms\moov\trak\mdia\hdlr;
|
||||||
|
use App\Media\QuickTime\Atoms\SubAtom;
|
||||||
|
use App\Media\QuickTime\Atoms\Unknown;
|
||||||
|
|
||||||
|
class mdia extends SubAtom
|
||||||
|
{
|
||||||
|
private const subatom_classes = 'App\\Media\\QuickTime\\Atoms\\moov\\trak\\mdia\\';
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct(int $offset,int $size,string $filename,?string $data)
|
||||||
|
{
|
||||||
|
parent::__construct($offset,$size,$filename);
|
||||||
|
|
||||||
|
// We'll find atoms
|
||||||
|
$this->atoms = $this->get_atoms(
|
||||||
|
self::subatom_classes,
|
||||||
|
Unknown::class,
|
||||||
|
$offset,
|
||||||
|
$size,
|
||||||
|
$data,
|
||||||
|
NULL,
|
||||||
|
fn($atom)=>($atom instanceof hdlr) ? $atom->cache['csubtype'] : NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
// For debugging
|
||||||
|
if (FALSE)
|
||||||
|
$this->debug = hex_dump($data ?: $this->data());
|
||||||
|
}
|
||||||
|
}
|
32
app/Media/QuickTime/Atoms/moov/trak/mdia/hdlr.php
Normal file
32
app/Media/QuickTime/Atoms/moov/trak/mdia/hdlr.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media\QuickTime\Atoms\moov\trak\mdia;
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atoms\SubAtom;
|
||||||
|
|
||||||
|
class hdlr extends SubAtom
|
||||||
|
{
|
||||||
|
protected const unpack = [
|
||||||
|
'version'=>['c',1],
|
||||||
|
'flags'=>['a3',3],
|
||||||
|
'ctype'=>['a4',4],
|
||||||
|
'csubtype'=>['a4',4],
|
||||||
|
'cmanufact'=>['a4',4],
|
||||||
|
'cflags'=>['a4',4],
|
||||||
|
'cmask'=>['a4',4],
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct(int $offset,int $size,string $filename,?string $data)
|
||||||
|
{
|
||||||
|
parent::__construct($offset,$size,$filename);
|
||||||
|
|
||||||
|
$this->cache = $this->cache();
|
||||||
|
$this->cache['name'] = pascal_string($this->unused_data);
|
||||||
|
|
||||||
|
$this->unused_data = (($x=strlen($this->cache['name'])+1) < strlen($this->unused_data)) ? substr($data,$x) : NULL;
|
||||||
|
|
||||||
|
// For debugging
|
||||||
|
if (FALSE)
|
||||||
|
$this->debug = hex_dump($data ?: $this->data());
|
||||||
|
}
|
||||||
|
}
|
39
app/Media/QuickTime/Atoms/moov/trak/mdia/minf.php
Normal file
39
app/Media/QuickTime/Atoms/moov/trak/mdia/minf.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media\QuickTime\Atoms\moov\trak\mdia;
|
||||||
|
|
||||||
|
use Leenooks\Traits\ObjectIssetFix;
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atoms\SubAtom;
|
||||||
|
use App\Media\QuickTime\Atoms\Unknown;
|
||||||
|
|
||||||
|
class minf extends SubAtom
|
||||||
|
{
|
||||||
|
use ObjectIssetFix;
|
||||||
|
|
||||||
|
private const subatom_classes = 'App\\Media\\QuickTime\\Atoms\\moov\\trak\\mdia\\minf\\';
|
||||||
|
|
||||||
|
protected ?string $type;
|
||||||
|
public function __construct(int $offset,int $size,string $filename,?string $data,string $arg=NULL)
|
||||||
|
{
|
||||||
|
parent::__construct($offset,$size,$filename);
|
||||||
|
$this->type = $arg;
|
||||||
|
|
||||||
|
$this->atoms = $this->get_atoms(self::subatom_classes,Unknown::class,$offset,$size,$data,$arg);
|
||||||
|
|
||||||
|
// For debugging
|
||||||
|
if (FALSE)
|
||||||
|
$this->debug = hex_dump($data ?: $this->data());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __get(string $key): mixed
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'type':
|
||||||
|
return $this->{$key};
|
||||||
|
|
||||||
|
default:
|
||||||
|
return parent::__get($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
app/Media/QuickTime/Atoms/moov/trak/mdia/minf/hdlr.php
Normal file
10
app/Media/QuickTime/Atoms/moov/trak/mdia/minf/hdlr.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media\QuickTime\Atoms\moov\trak\mdia\minf;
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atoms\moov\trak\mdia\hdlr as MdiaHdlr;
|
||||||
|
|
||||||
|
class hdlr extends MdiaHdlr
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
22
app/Media/QuickTime/Atoms/moov/trak/mdia/minf/stbl.php
Normal file
22
app/Media/QuickTime/Atoms/moov/trak/mdia/minf/stbl.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media\QuickTime\Atoms\moov\trak\mdia\minf;
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atoms\SubAtom;
|
||||||
|
use App\Media\QuickTime\Atoms\Unknown;
|
||||||
|
|
||||||
|
class stbl extends SubAtom
|
||||||
|
{
|
||||||
|
private const subatom_classes = 'App\\Media\\QuickTime\\Atoms\\moov\\trak\\mdia\\minf\\stbl\\';
|
||||||
|
|
||||||
|
public function __construct(int $offset,int $size,string $filename,?string $data,?string $arg=NULL)
|
||||||
|
{
|
||||||
|
parent::__construct($offset,$size,$filename);
|
||||||
|
|
||||||
|
$this->atoms = $this->get_atoms(self::subatom_classes,Unknown::class,$offset,$size,$data,$arg);
|
||||||
|
|
||||||
|
// For debugging
|
||||||
|
if (FALSE)
|
||||||
|
$this->debug = hex_dump($data ?: $this->data());
|
||||||
|
}
|
||||||
|
}
|
104
app/Media/QuickTime/Atoms/moov/trak/mdia/minf/stbl/stsd.php
Normal file
104
app/Media/QuickTime/Atoms/moov/trak/mdia/minf/stbl/stsd.php
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media\QuickTime\Atoms\moov\trak\mdia\minf\stbl;
|
||||||
|
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atoms\SubAtom;
|
||||||
|
|
||||||
|
class stsd extends SubAtom
|
||||||
|
{
|
||||||
|
protected const unpack = [
|
||||||
|
'size'=>['N',4],
|
||||||
|
'format'=>['a4',4],
|
||||||
|
'reserved'=>['a6',6],
|
||||||
|
'index'=>['n',2],
|
||||||
|
'encoder_version'=>['n',2],
|
||||||
|
'encoder_revision'=>['n',2],
|
||||||
|
'encoder_vendor'=>['a4',4],
|
||||||
|
];
|
||||||
|
|
||||||
|
protected const audio_record = [
|
||||||
|
'audio_channels'=>['n',2],
|
||||||
|
'audio_bit_depth'=>['n',2],
|
||||||
|
'audio_compression_id'=>['n',2],
|
||||||
|
'audio_packet_size'=>['n',2],
|
||||||
|
'audio_sample_rate'=>['a4',4],
|
||||||
|
];
|
||||||
|
|
||||||
|
protected const video_record = [
|
||||||
|
'temporal_quality'=>['N',4],
|
||||||
|
'spatial_quality'=>['N',4],
|
||||||
|
'width'=>['n',2],
|
||||||
|
'height'=>['n',2],
|
||||||
|
'resolution_x'=>['a4',4],
|
||||||
|
'resolution_y'=>['a4',4],
|
||||||
|
'data_size'=>['N',4],
|
||||||
|
'frame_count'=>['n',2],
|
||||||
|
//'codec_name'=>['a4',4], // pascal string
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct(int $offset,int $size,string $filename,?string $data,string $arg=NULL)
|
||||||
|
{
|
||||||
|
parent::__construct($offset,$size,$filename);
|
||||||
|
$this->type = $arg;
|
||||||
|
|
||||||
|
$read = unpack($this->unpack(self::atom_record),substr($data,0,$ptr=$this->unpack_size(self::atom_record)));
|
||||||
|
|
||||||
|
for ($i=0; $i<$read['count']; $i++) {
|
||||||
|
$this->cache = collect(unpack($this->unpack(),substr($data,$ptr,$x=$this->unpack_size())));
|
||||||
|
|
||||||
|
$ptr += $x;
|
||||||
|
|
||||||
|
switch ($this->type) {
|
||||||
|
case 'soun':
|
||||||
|
// Audio Track
|
||||||
|
$this->audio = unpack($this->unpack(self::audio_record),substr($data,$ptr,$x=$this->unpack_size(self::audio_record)));
|
||||||
|
$ptr += $x;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'vide':
|
||||||
|
// Video Track
|
||||||
|
$this->video = unpack($this->unpack(self::video_record),substr($data,$ptr,$x=$this->unpack_size(self::video_record)));
|
||||||
|
$ptr += $x;
|
||||||
|
|
||||||
|
// codec - pascal string
|
||||||
|
$this->video['codec'] = pascal_string(substr($data,$ptr));
|
||||||
|
|
||||||
|
$ptr += strlen($this->video['codec'])+1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->extra = substr($data,$ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __get(string $key):mixed
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'audio_channels':
|
||||||
|
return Arr::get($this->audio,$key);
|
||||||
|
|
||||||
|
case 'audio_codec':
|
||||||
|
switch ($this->cache->get('format')) {
|
||||||
|
case 'mp4a': return 'ISO/IEC 14496-3 AAC';
|
||||||
|
|
||||||
|
default:
|
||||||
|
return $this->cache->get('format');
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'audio_samplerate':
|
||||||
|
return fixed_point_int_to_float(Arr::get($this->audio,'audio_sample_rate',0));
|
||||||
|
|
||||||
|
case 'video_codec':
|
||||||
|
return Arr::get($this->video,'codec');
|
||||||
|
|
||||||
|
case 'video_framerate':
|
||||||
|
dd($this);
|
||||||
|
// return Arr::get($this->video,'codec');
|
||||||
|
|
||||||
|
default:
|
||||||
|
return parent::__get($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
app/Media/QuickTime/Atoms/moov/trak/mdia/minf/stbl/stts.php
Normal file
32
app/Media/QuickTime/Atoms/moov/trak/mdia/minf/stbl/stts.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media\QuickTime\Atoms\moov\trak\mdia\minf\stbl;
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atoms\SubAtom;
|
||||||
|
|
||||||
|
class stts extends SubAtom
|
||||||
|
{
|
||||||
|
public function __construct(int $offset,int $size,string $filename,?string $data)
|
||||||
|
{
|
||||||
|
parent::__construct($offset,$size,$filename);
|
||||||
|
|
||||||
|
$read = unpack($this->unpack(self::atom_record),substr($data,0,$ptr=$this->unpack_size(self::atom_record)));
|
||||||
|
|
||||||
|
for ($i=0; $i<$read['count']; $i++) {
|
||||||
|
$this->cache->push(unpack('Ncount/Nduration',substr($data,$ptr,8)));
|
||||||
|
$ptr += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For debugging
|
||||||
|
if (FALSE)
|
||||||
|
$this->debug = hex_dump($data ?: $this->data());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function frame_rate(float $time_scale): float
|
||||||
|
{
|
||||||
|
return $this->cache
|
||||||
|
->pluck('duration')
|
||||||
|
->map(fn($item)=>$time_scale/$item)
|
||||||
|
->max();
|
||||||
|
}
|
||||||
|
}
|
10
app/Media/QuickTime/Atoms/moov/trak/meta.php
Normal file
10
app/Media/QuickTime/Atoms/moov/trak/meta.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media\QuickTime\Atoms\moov\trak;
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atoms\moov\meta as MoovMeta;
|
||||||
|
|
||||||
|
class meta extends MoovMeta
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
55
app/Media/QuickTime/Atoms/moov/trak/tkhd.php
Normal file
55
app/Media/QuickTime/Atoms/moov/trak/tkhd.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media\QuickTime\Atoms\moov\trak;
|
||||||
|
|
||||||
|
// An atom that specifies the characteristics of a single track within a movie
|
||||||
|
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atoms\SubAtom;
|
||||||
|
|
||||||
|
class tkhd extends SubAtom
|
||||||
|
{
|
||||||
|
protected const unpack = [
|
||||||
|
'version'=>['c',1],
|
||||||
|
'flags'=>['a3',3],
|
||||||
|
'create'=>['N',4],
|
||||||
|
'modified'=>['N',4],
|
||||||
|
'trakid'=>['N',4], // The value 0 cannot be used
|
||||||
|
'reserved1'=>['a4',4],
|
||||||
|
'duration'=>['N',4],
|
||||||
|
'reserved2'=>['a8',8],
|
||||||
|
'layer'=>['n',2],
|
||||||
|
'altgroup'=>['n',2],
|
||||||
|
'volume'=>['a2',2], // 16 bit fixed point
|
||||||
|
'reserved3'=>['a2',2],
|
||||||
|
'matrix'=>['a36',36],
|
||||||
|
'twidth'=>['a4',4], // 32 bit fixed point
|
||||||
|
'theight'=>['a4',4], // 32 bit fixed point
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct(int $offset,int $size,string $filename,?string $data)
|
||||||
|
{
|
||||||
|
parent::__construct($offset,$size,$filename,$data);
|
||||||
|
|
||||||
|
$this->cache = $this->cache();
|
||||||
|
|
||||||
|
// For debugging
|
||||||
|
if (FALSE)
|
||||||
|
$this->debug = hex_dump($data ?: $this->data());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __get($key): mixed
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'height':
|
||||||
|
return fixed_point_int_to_float(Arr::get($this->cache(),'theight'));
|
||||||
|
|
||||||
|
case 'width':
|
||||||
|
return fixed_point_int_to_float(Arr::get($this->cache(),'twidth'));
|
||||||
|
|
||||||
|
default:
|
||||||
|
return parent::__get($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
app/Media/QuickTime/Atoms/skip.php
Normal file
12
app/Media/QuickTime/Atoms/skip.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media\QuickTime\Atoms;
|
||||||
|
|
||||||
|
// Unused space available in file.
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atom;
|
||||||
|
|
||||||
|
class skip extends Atom
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
13
app/Media/QuickTime/Atoms/wide.php
Normal file
13
app/Media/QuickTime/Atoms/wide.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media\QuickTime\Atoms;
|
||||||
|
|
||||||
|
// Reserved space—can be overwritten by an extended size field if the following atom exceeds 2^32 bytes,
|
||||||
|
// without displacing the contents of the following atom.
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atom;
|
||||||
|
|
||||||
|
class wide extends Atom
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
13
app/Media/Unknown.php
Normal file
13
app/Media/Unknown.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Media;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This represents a media type that we dont know how to parse
|
||||||
|
*/
|
||||||
|
class Unknown extends Base {
|
||||||
|
public function __get(string $key): mixed
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
@ -5,20 +5,43 @@ namespace App\Models\Abstracted;
|
|||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
use App\Models\{Person,Software,Tag};
|
use App\Casts\PostgresBytea;
|
||||||
|
use App\Models\{Make,Person,Software,Tag};
|
||||||
|
|
||||||
abstract class Catalog extends Model
|
abstract class Catalog extends Model
|
||||||
{
|
{
|
||||||
protected static $includeSubSecTime = FALSE;
|
protected static $includeSubSecTime = FALSE;
|
||||||
protected $dates = ['created','created_manual'];
|
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
|
'created_manual' => 'datetime',
|
||||||
'subsectime' => 'int',
|
'subsectime' => 'int',
|
||||||
|
'thumbnail' => PostgresBytea::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public const fs = 'nas';
|
||||||
|
|
||||||
|
private ?string $move_reason;
|
||||||
|
|
||||||
|
protected array $init = [];
|
||||||
|
|
||||||
|
/* STATIC */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the prefix for the file path - dependant on the object
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function dir_prefix(): string
|
||||||
|
{
|
||||||
|
return config(static::config.'.dir').'/';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RELATIONS */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* People in Multimedia Object
|
* People in Multimedia Object
|
||||||
*
|
*
|
||||||
@ -49,19 +72,19 @@ abstract class Catalog extends Model
|
|||||||
return $this->belongsToMany(Tag::class);
|
return $this->belongsToMany(Tag::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* SCOPES */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find records marked as duplicate
|
* Find records marked as duplicate
|
||||||
*
|
*
|
||||||
* @param $query
|
* @param $query
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function scopeDuplicates($query) {
|
public function scopeDuplicates($query)
|
||||||
$query->notRemove()
|
{
|
||||||
|
return $query->notRemove()
|
||||||
->where('duplicate',TRUE)
|
->where('duplicate',TRUE)
|
||||||
->where(function($q) {
|
->where(fn($q)=>$q->where('ignore_duplicate','<>',TRUE)->orWhereNull('ignore_duplicate'));
|
||||||
$q->Where('ignore_duplicate','<>',TRUE)
|
|
||||||
->orWhereNull('ignore_duplicate');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -78,40 +101,31 @@ abstract class Catalog extends Model
|
|||||||
$query->where('id','<>',$this->attributes['id']);
|
$query->where('id','<>',$this->attributes['id']);
|
||||||
|
|
||||||
// Skip ignore dups
|
// Skip ignore dups
|
||||||
$query->where(function($q) {
|
$query->where(fn($q)=>$q->whereNull('ignore_duplicate')
|
||||||
$q->whereNull('ignore_duplicate')
|
->orWhere('ignore_duplicate',FALSE));
|
||||||
->orWhere('ignore_duplicate','=',0);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Exclude those marked as remove
|
// Exclude those marked as remove
|
||||||
$query->where(function ($q) {
|
$query->where(fn($q)=>$q->where('remove','<>',TRUE));
|
||||||
$q->where('remove','<>',TRUE)
|
|
||||||
->orWhere('remove','=',NULL);
|
|
||||||
});
|
|
||||||
|
|
||||||
$query->where(function ($q) {
|
$query->where(function($q) {
|
||||||
$q->where('signature','=',$this->attributes['signature'])
|
$q->when($this->attributes['signature'],fn($q)=>$q->where('signature','=',$this->attributes['signature']))
|
||||||
->orWhere('file_signature','=',$this->attributes['file_signature'])
|
->orWhere('file_signature','=',$this->attributes['file_signature'])
|
||||||
|
|
||||||
// Where the signature is the same
|
// Where the signature is the same
|
||||||
->orWhere(function($q) {
|
->orWhere(function($q) {
|
||||||
// Or they have the same time taken with the same camera
|
// Or they have the same time taken with the same camera
|
||||||
if ($this->attributes['created'] AND $this->software_id) {
|
if ($this->attributes['created'] && $this->software_id) {
|
||||||
$q->where(function ($q) {
|
$q->where(fn($q)=>$q->where('created','=',$this->attributes['created'])
|
||||||
$q->where('created','=',$this->attributes['created'])
|
->orWhere('created_manual','=',$this->attributes['created']));
|
||||||
->orWhere('created_manual','=',$this->attributes['created']);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (static::$includeSubSecTime)
|
if (static::$includeSubSecTime)
|
||||||
$q->where('subsectime','=',Arr::get($this->attributes,'subsectime'));
|
$q->where('subsectime','=',Arr::get($this->attributes,'subsectime'));
|
||||||
|
|
||||||
$q->where('software_id','=',$this->attributes['software_id']);
|
$q->where('software_id','=',$this->attributes['software_id']);
|
||||||
|
|
||||||
} elseif ($this->attributes['created_manual'] AND $this->software_id) {
|
} elseif ($this->attributes['created_manual'] && $this->software_id) {
|
||||||
$q->where(function ($q) {
|
$q->where(fn($q)=>$q->where('created','=',$this->attributes['created_manual'])
|
||||||
$q->where('created','=',$this->attributes['created_manual'])
|
->orWhere('created_manual','=',$this->attributes['created_manual']));
|
||||||
->orWhere('created_manual','=',$this->attributes['created_manual']);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (static::$includeSubSecTime)
|
if (static::$includeSubSecTime)
|
||||||
$q->where('subsectime','=',Arr::get($this->attributes,'subsectime'));
|
$q->where('subsectime','=',Arr::get($this->attributes,'subsectime'));
|
||||||
@ -120,6 +134,8 @@ abstract class Catalog extends Model
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -129,15 +145,11 @@ abstract class Catalog extends Model
|
|||||||
*/
|
*/
|
||||||
public function scopeNotDuplicate($query)
|
public function scopeNotDuplicate($query)
|
||||||
{
|
{
|
||||||
return $query->where(function($query)
|
return $query->where(
|
||||||
{
|
fn($q)=>$q->where('duplicate','<>',TRUE)
|
||||||
$query->where('duplicate','<>',TRUE)
|
|
||||||
->orWhere('duplicate','=',NULL)
|
->orWhere('duplicate','=',NULL)
|
||||||
->orWhere(function($q) {
|
->orWhere(fn($q)=>$q->where('duplicate','=',TRUE)->where('ignore_duplicate','=',TRUE))
|
||||||
$q->where('duplicate','=',TRUE)
|
);
|
||||||
->where('ignore_duplicate','=',TRUE);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -147,11 +159,7 @@ abstract class Catalog extends Model
|
|||||||
*/
|
*/
|
||||||
public function scopeNotRemove($query)
|
public function scopeNotRemove($query)
|
||||||
{
|
{
|
||||||
return $query->where(function($query)
|
return $query->where(fn($q)=>$q->where('remove','<>',TRUE)->orWhere('remove','=',NULL));
|
||||||
{
|
|
||||||
$query->where('remove','<>',TRUE)
|
|
||||||
->orWhere('remove','=',NULL);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -161,27 +169,33 @@ abstract class Catalog extends Model
|
|||||||
*/
|
*/
|
||||||
public function scopeNotScanned($query)
|
public function scopeNotScanned($query)
|
||||||
{
|
{
|
||||||
return $query->where(function($query)
|
return $query->where(fn($q)=>$q->where('scanned','<>',TRUE)->orWhere('scanned','=',NULL));
|
||||||
{
|
|
||||||
$query->where('scanned','<>',TRUE)
|
|
||||||
->orWhere('scanned','=',NULL);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Children objects must inherit this methods
|
/* ABSTRACTS */
|
||||||
abstract public function setLocation();
|
|
||||||
abstract public function setSubSecTime();
|
abstract public function getObjectOriginal(string $property): mixed;
|
||||||
abstract public function setThumbnail();
|
|
||||||
abstract public function getHtmlImageURL();
|
/* ATTRIBUTES */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Date the multimedia was created
|
* Return the time the media was created on the device
|
||||||
|
*
|
||||||
|
* This will be (in priority order)
|
||||||
|
* + the value of created_manual (Carbon)
|
||||||
|
* + the value of created
|
||||||
|
*
|
||||||
|
* @param string|null $date
|
||||||
|
* @return Carbon|null
|
||||||
*/
|
*/
|
||||||
public function date_taken(): string
|
public function getCreatedAttribute(string $date=NULL): ?Carbon
|
||||||
{
|
{
|
||||||
return $this->created
|
$result = $this->created_manual ?: ($date ? Carbon::create($date) : NULL);
|
||||||
? $this->created->format('Y-m-d H:i:s').(static::$includeSubSecTime ? sprintf('.%03d',$this->subsectime) : '')
|
|
||||||
: 'UNKNOWN';
|
if ($result && static::$includeSubSecTime)
|
||||||
|
$result->microseconds($this->subsectime*1000);
|
||||||
|
|
||||||
|
return $result ?: $this->getObjectOriginal('creation_date');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -189,7 +203,7 @@ abstract class Catalog extends Model
|
|||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function device(): string
|
public function getDeviceAttribute(): string
|
||||||
{
|
{
|
||||||
$result = '';
|
$result = '';
|
||||||
|
|
||||||
@ -208,103 +222,6 @@ abstract class Catalog extends Model
|
|||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the date of the file
|
|
||||||
* @todo return Carbon date or NULL
|
|
||||||
*/
|
|
||||||
public function file_date($type,$format=FALSE)
|
|
||||||
{
|
|
||||||
if (! is_readable($this->file_path()))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
switch ($type)
|
|
||||||
{
|
|
||||||
case 'a': $t = fileatime($this->file_path());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'c': $t = filectime($this->file_path());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'm': $t = filemtime($this->file_path());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $format ? date('d-m-Y H:i:s',$t) : $t;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return what the filename should be.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function file_name(bool $short=TRUE): string
|
|
||||||
{
|
|
||||||
// If the date created is not set, the file name will be based on the ID of the file.
|
|
||||||
$file = sprintf('%s.%s',(is_null($this->created)
|
|
||||||
? sprintf('UNKNOWN/%07s',$this->file_path_id())
|
|
||||||
: $this->created->format('Y/m/d-His').
|
|
||||||
((! is_null($this->subsectime)) ? sprintf('_%03d',$this->subsectime) : '' ).
|
|
||||||
sprintf('-%05s',$this->id))
|
|
||||||
,$this->type()
|
|
||||||
);
|
|
||||||
|
|
||||||
return (($short OR preg_match('/^\//',$file)) ? '' : config($this->type.'.dir').DIRECTORY_SEPARATOR).$file;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the current filename
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function file_name_current(bool $short=TRUE): string
|
|
||||||
{
|
|
||||||
return (($short OR preg_match('/^\//',$this->filename)) ? '' : config($this->type.'.dir').DIRECTORY_SEPARATOR).$this->filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine the new name for the image
|
|
||||||
* @deprecated use $this->file_name()
|
|
||||||
*/
|
|
||||||
public function file_path($short=FALSE,$new=FALSE)
|
|
||||||
{
|
|
||||||
$file = $this->filename;
|
|
||||||
|
|
||||||
if ($new)
|
|
||||||
$file = $this->file_name(TRUE);
|
|
||||||
|
|
||||||
return (($short OR preg_match('/^\//',$file)) ? '' : config($this->type.'.dir').DIRECTORY_SEPARATOR).$file;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate a file path ID based on the id of the file
|
|
||||||
*/
|
|
||||||
public function file_path_id($sep=3,$depth=9): string
|
|
||||||
{
|
|
||||||
return trim(chunk_split(sprintf("%0{$depth}s",$this->id),$sep,'/'),'/');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display the file signature
|
|
||||||
*/
|
|
||||||
public function file_signature($short=FALSE): string
|
|
||||||
{
|
|
||||||
return $short ? static::stringtrim($this->file_signature) : $this->file_signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the file size
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
public function file_size()
|
|
||||||
{
|
|
||||||
return (! is_readable($this->file_path())) ? NULL : filesize($this->file_path());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCreatedAttribute()
|
|
||||||
{
|
|
||||||
return $this->created_manual ?: ($this->attributes['created'] ? $this->asDateTime($this->attributes['created']) : NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return item dimensions
|
* Return item dimensions
|
||||||
*/
|
*/
|
||||||
@ -313,26 +230,94 @@ abstract class Catalog extends Model
|
|||||||
return $this->width.'x'.$this->height;
|
return $this->width.'x'.$this->height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the file size
|
||||||
|
*
|
||||||
|
* @return int|null
|
||||||
|
*/
|
||||||
|
public function getFileSizeAttribute(): ?int
|
||||||
|
{
|
||||||
|
return (! $this->isReadable()) ? NULL : filesize($this->file_name(FALSE));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGPSAttribute(): ?string
|
||||||
|
{
|
||||||
|
return ($this->gps_lat && $this->gps_lon)
|
||||||
|
? sprintf('%s/%s',$this->gps_lat,$this->gps_lon)
|
||||||
|
: NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* METHODS */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the filename.
|
||||||
|
* If short is TRUE, it is the filename that it should be called (and can be compared to $this->filename)
|
||||||
|
* If short is FALSE, it is the true path of the actual file
|
||||||
|
*
|
||||||
|
* @param bool $short
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function file_name(bool $short=TRUE): string
|
||||||
|
{
|
||||||
|
if ($short || preg_match('#^/#',$this->filename)) {
|
||||||
|
// If the date created is not set, the file name will be based on the ID of the file.
|
||||||
|
$file = sprintf('%s.%s',
|
||||||
|
(is_null($this->created)
|
||||||
|
? sprintf('UNKNOWN/%07s',$this->file_path_id())
|
||||||
|
: $this->created->format('Y/m/d-His').
|
||||||
|
($this->subsectime ? sprintf('_%03d',$this->subsectime) : '' ).
|
||||||
|
sprintf('-%05s',$this->id)),
|
||||||
|
$this->type()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $file;
|
||||||
|
|
||||||
|
} else
|
||||||
|
return Storage::disk(self::fs)
|
||||||
|
->path($this->file_name_rel());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function file_name_rel(bool $source=TRUE): string
|
||||||
|
{
|
||||||
|
return config(static::config.'.dir').DIRECTORY_SEPARATOR.($source ? $this->filename : $this->file_name());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the new name for the image
|
||||||
|
* @deprecated use $this->file_name(FALSE) to determine the name, and file_name(TRUE) to determine the new name
|
||||||
|
*/
|
||||||
|
public function file_path($short=FALSE,$new=FALSE)
|
||||||
|
{
|
||||||
|
$file = $this->filename;
|
||||||
|
|
||||||
|
if ($new)
|
||||||
|
$file = $this->file_name(TRUE);
|
||||||
|
|
||||||
|
return (($short OR preg_match('/^\//',$file)) ? '' : config(static::config.'.dir').DIRECTORY_SEPARATOR).$file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate a file path ID based on the id of the file
|
||||||
|
*
|
||||||
|
* We use this when we cannot determine the create time of the image
|
||||||
|
*/
|
||||||
|
public function file_path_id($sep=3,$depth=9): string
|
||||||
|
{
|
||||||
|
return trim(chunk_split(sprintf("%0{$depth}s",$this->id),$sep,'/'),'/');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return HTML Checkbox for duplicate
|
* Return HTML Checkbox for duplicate
|
||||||
|
* @deprecated use a component
|
||||||
*/
|
*/
|
||||||
public function getDuplicateCheckboxAttribute()
|
public function getDuplicateCheckboxAttribute()
|
||||||
{
|
{
|
||||||
return $this->HTMLCheckbox('duplicate',$this->id,$this->duplicate);
|
return $this->HTMLCheckbox('duplicate',$this->id,$this->duplicate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the file size
|
|
||||||
*
|
|
||||||
* @return false|int|null
|
|
||||||
*/
|
|
||||||
public function getFileSizeAttribute()
|
|
||||||
{
|
|
||||||
return (! is_readable($this->file_path())) ? NULL : filesize($this->file_path());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return HTML Checkbox for flagged
|
* Return HTML Checkbox for flagged
|
||||||
|
* @deprecated use a component
|
||||||
*/
|
*/
|
||||||
public function getFlagCheckboxAttribute()
|
public function getFlagCheckboxAttribute()
|
||||||
{
|
{
|
||||||
@ -341,6 +326,7 @@ abstract class Catalog extends Model
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Return HTML Checkbox for ignore
|
* Return HTML Checkbox for ignore
|
||||||
|
* @deprecated use a component
|
||||||
*/
|
*/
|
||||||
public function getIgnoreCheckboxAttribute()
|
public function getIgnoreCheckboxAttribute()
|
||||||
{
|
{
|
||||||
@ -348,7 +334,7 @@ abstract class Catalog extends Model
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated use a component
|
||||||
*/
|
*/
|
||||||
public function getDuplicateTextAttribute()
|
public function getDuplicateTextAttribute()
|
||||||
{
|
{
|
||||||
@ -356,7 +342,7 @@ abstract class Catalog extends Model
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated use a component
|
||||||
*/
|
*/
|
||||||
public function getFlagTextAttribute()
|
public function getFlagTextAttribute()
|
||||||
{
|
{
|
||||||
@ -365,6 +351,7 @@ abstract class Catalog extends Model
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Return HTML Checkbox for remove
|
* Return HTML Checkbox for remove
|
||||||
|
* @deprecated use a component
|
||||||
*/
|
*/
|
||||||
public function getRemoveCheckboxAttribute()
|
public function getRemoveCheckboxAttribute()
|
||||||
{
|
{
|
||||||
@ -372,25 +359,143 @@ abstract class Catalog extends Model
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the object type
|
* Return an HTML checkbox
|
||||||
* @return string
|
* @deprecated use a component
|
||||||
* @deprecated Use objecttype()
|
|
||||||
*/
|
*/
|
||||||
public function getTypeAttribute(): string
|
protected function HTMLCheckbox($name,$id,$value)
|
||||||
{
|
{
|
||||||
switch(get_class($this)) {
|
return sprintf('<input type="checkbox" name="%s[%s]" value="1"%s>',$name,$id,$value ? ' checked="checked"' : '');
|
||||||
case 'App\Models\Photo': return 'photo';
|
|
||||||
case 'App\Models\Video': return 'video';
|
|
||||||
default: return 'unknown';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the GPS coordinates
|
* Set values from the media object
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public function gps(): string
|
public function init(): void
|
||||||
{
|
{
|
||||||
return ($this->gps_lat AND $this->gps_lon) ? sprintf('%s/%s',$this->gps_lat,$this->gps_lon) : 'UNKNOWN';
|
foreach ($this->init as $item) {
|
||||||
|
Log::debug(sprintf('Init item [%s]',$item));
|
||||||
|
|
||||||
|
switch ($item) {
|
||||||
|
case 'creation_date':
|
||||||
|
$this->created = $this->getObjectOriginal('creation_date');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'gps':
|
||||||
|
$this->gps_lat = $this->getObjectOriginal('gps_lat');
|
||||||
|
$this->gps_lon = $this->getObjectOriginal('gps_lon');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'heightwidth':
|
||||||
|
$this->height = $this->getObjectOriginal('height');
|
||||||
|
$this->width = $this->getObjectOriginal('width');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'signature':
|
||||||
|
$this->signature = $this->getObjectOriginal('signature');
|
||||||
|
$this->file_signature = $this->getObjectOriginal('file_signature');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'software':
|
||||||
|
$ma = NULL;
|
||||||
|
|
||||||
|
if ($x=$this->getObjectOriginal('make'))
|
||||||
|
$ma = Make::firstOrCreate([
|
||||||
|
'name'=>$x,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$mo = \App\Models\Model::firstOrCreate([
|
||||||
|
'name'=>$this->getObjectOriginal('model') ?: NULL,
|
||||||
|
'make_id'=>$ma?->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$so = Software::firstOrCreate([
|
||||||
|
'name'=>$this->getObjectOriginal('software') ?: NULL,
|
||||||
|
'model_id'=>$mo->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->software_id = $so->id;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'subsectime':
|
||||||
|
$this->subsectime = $this->getObjectOriginal($item);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new \Exception('Unknown init item: '.$item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->custom_init();
|
||||||
|
|
||||||
|
Log::debug('Init result',['dirty'=>$this->getDirty()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the file require moving
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isMoveable(): bool
|
||||||
|
{
|
||||||
|
// No change to the name
|
||||||
|
$this->move_reason = 'Filenames match already';
|
||||||
|
if ($this->filename === $this->file_name())
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
// If there is already a file in the target.
|
||||||
|
// @todo If the target file is to be deleted, we could move this file
|
||||||
|
$this->move_reason = 'Target file exists';
|
||||||
|
if (Storage::disk(self::fs)->exists($this->file_name()))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
// Test if the source is readable
|
||||||
|
$this->move_reason = 'Source is not readable';
|
||||||
|
if (! $this->isReadable())
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
// Test if the dir is writable (so we can remove the file)
|
||||||
|
$this->move_reason = 'Source parent dir not writable';
|
||||||
|
if (! $this->isParentWritable(dirname($this->file_name(FALSE))))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
// Test if the target dir is writable
|
||||||
|
// @todo The target dir may not exist yet, so we should check that a parent exists and is writable.
|
||||||
|
$this->move_reason = 'Target parent dir doesnt is not writable';
|
||||||
|
if (! $this->isParentWritable(dirname($this->file_name(FALSE))))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
// Otherwise we can move it
|
||||||
|
$this->move_reason = NULL;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isMoveableReason(): ?string
|
||||||
|
{
|
||||||
|
return $this->move_reason ?? NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the parent dir is writable
|
||||||
|
*
|
||||||
|
* @param string $dir
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isParentWritable(string $dir): bool
|
||||||
|
{
|
||||||
|
$path = Storage::disk(self::fs)->path($dir);
|
||||||
|
|
||||||
|
if (Storage::disk(self::fs)->exists($dir) && is_dir($path) && is_writable($path))
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
elseif ($path === dirname($path))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
else
|
||||||
|
return ($this->isParentWritable(dirname($dir)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -400,89 +505,19 @@ abstract class Catalog extends Model
|
|||||||
*/
|
*/
|
||||||
public function isReadable(): bool
|
public function isReadable(): bool
|
||||||
{
|
{
|
||||||
return is_readable($this->file_path());
|
return is_readable($this->file_name(FALSE));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an HTML checkbox
|
* Get the id of the next record
|
||||||
*/
|
*/
|
||||||
protected function HTMLCheckbox($name,$id,$value)
|
public function next(): ?self
|
||||||
{
|
{
|
||||||
return sprintf('<input type="checkbox" name="%s[%s]" value="1"%s>',$name,$id,$value ? ' checked="checked"' : '');
|
return static::where('id','>',$this->id)
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get ID Info link
|
|
||||||
*/
|
|
||||||
protected function HTMLLinkAttribute($id,$url)
|
|
||||||
{
|
|
||||||
return sprintf('<a href="%s" target="%s">%s</a>',url($url,$id),$id,$id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if the parent dir is writable
|
|
||||||
*
|
|
||||||
* @param $dir
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isParentWritable($dir): bool
|
|
||||||
{
|
|
||||||
if (file_exists($dir) AND is_writable($dir) AND is_dir($dir))
|
|
||||||
return TRUE;
|
|
||||||
|
|
||||||
elseif ($dir == dirname($dir) OR file_exists($dir))
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
else
|
|
||||||
return ($this->isParentWritable(dirname($dir)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if a file is moveable
|
|
||||||
*
|
|
||||||
* useID boolean Determine if the path is based on the the ID or date
|
|
||||||
* @todo Change to boolean and rename isMoveable() Log any FALSE reason.
|
|
||||||
*/
|
|
||||||
public function moveable()
|
|
||||||
{
|
|
||||||
// If the source and target are the same, we dont need to move it
|
|
||||||
if ($this->file_path() == $this->file_path(FALSE,TRUE))
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
// If there is already a file in the target.
|
|
||||||
// @todo If the target file is to be deleted, we could move this file
|
|
||||||
if (file_exists($this->file_path(FALSE,TRUE)))
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
// Test if the source is readable
|
|
||||||
if (! is_readable($this->file_path()))
|
|
||||||
return 2;
|
|
||||||
|
|
||||||
// Test if the dir is writable (so we can remove the file)
|
|
||||||
if (! $this->isParentWritable(dirname($this->file_path())))
|
|
||||||
return 3;
|
|
||||||
|
|
||||||
// Test if the target dir is writable
|
|
||||||
// @todo The target dir may not exist yet, so we should check that a parent exists and is writable.
|
|
||||||
if (! $this->isParentWritable($this->file_path(FALSE,TRUE)))
|
|
||||||
return 4;
|
|
||||||
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the id of the previous record
|
|
||||||
*/
|
|
||||||
public function next()
|
|
||||||
{
|
|
||||||
return DB::table($this->getTable())
|
|
||||||
->where('id','>',$this->id)
|
|
||||||
->orderby('id','ASC')
|
->orderby('id','ASC')
|
||||||
->first();
|
->first();
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract public function property(string $property);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return my class shortname
|
* Return my class shortname
|
||||||
*/
|
*/
|
||||||
@ -494,62 +529,21 @@ abstract class Catalog extends Model
|
|||||||
/**
|
/**
|
||||||
* Get the id of the previous record
|
* Get the id of the previous record
|
||||||
*/
|
*/
|
||||||
public function previous()
|
public function previous(): ?self
|
||||||
{
|
{
|
||||||
return DB::table($this->getTable())
|
return static::where('id','<',$this->id)
|
||||||
->where('id','<',$this->id)
|
|
||||||
->orderby('id','DESC')
|
->orderby('id','DESC')
|
||||||
->first();
|
->first();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setDateCreated()
|
|
||||||
{
|
|
||||||
$this->created = $this->property('creationdate') ?: NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setHeightWidth()
|
|
||||||
{
|
|
||||||
$this->height = $this->property('height');
|
|
||||||
$this->width = $this->property('width');
|
|
||||||
$this->orientation = $this->property('orientation');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setSignature()
|
|
||||||
{
|
|
||||||
$this->signature = $this->property('signature');
|
|
||||||
|
|
||||||
$this->file_signature = md5_file($this->file_path());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the media signature
|
* Display the media signature
|
||||||
*/
|
*/
|
||||||
public function signature($short=FALSE)
|
public function signature($short=FALSE)
|
||||||
{
|
{
|
||||||
return ($short AND $this->signature) ? static::stringtrim($this->signature) : $this->signature;
|
return ($short && $this->signature)
|
||||||
}
|
? stringtrim($this->signature)
|
||||||
|
: $this->signature;
|
||||||
/**
|
|
||||||
* Trim a string
|
|
||||||
*
|
|
||||||
* @param string $string
|
|
||||||
* @param int $chrs
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public static function stringtrim(string $string,int $chrs=6)
|
|
||||||
{
|
|
||||||
return sprintf('%s...%s',substr($string,0,$chrs),substr($string,-1*$chrs));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
* @param string $string
|
|
||||||
* @param int $chrs
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public static function signaturetrim(string $string,int $chrs=6)
|
|
||||||
{
|
|
||||||
return static::stringtrim($string,$chrs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -560,25 +554,14 @@ abstract class Catalog extends Model
|
|||||||
return $this->filename !== $this->file_name();
|
return $this->filename !== $this->file_name();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function TextTrueFalse($value): string
|
/** @deprecated is this really needed? */
|
||||||
|
private function TextTrueFalse($value): string
|
||||||
{
|
{
|
||||||
return $value ? 'TRUE' : 'FALSE';
|
return $value ? 'TRUE' : 'FALSE';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @todo Check if this is redundant
|
|
||||||
*
|
|
||||||
* @param bool $includeme
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
private function list_duplicate($includeme=FALSE)
|
|
||||||
{
|
|
||||||
return $this->list_duplicates($includeme)->pluck('id');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find duplicate images based on some attributes of the current image
|
* Find duplicate images based on some attributes of the current image
|
||||||
* @deprecate Use static::duplicates()
|
|
||||||
*/
|
*/
|
||||||
private function list_duplicates($includeme=FALSE)
|
private function list_duplicates($includeme=FALSE)
|
||||||
{
|
{
|
||||||
@ -595,7 +578,7 @@ abstract class Catalog extends Model
|
|||||||
->orWhere('remove','=',NULL);
|
->orWhere('remove','=',NULL);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Where the signature is the same
|
// Where the signalist_duplicatesture is the same
|
||||||
$o->where(function($query)
|
$o->where(function($query)
|
||||||
{
|
{
|
||||||
$query->where('signature','=',$this->signature);
|
$query->where('signature','=',$this->signature);
|
||||||
@ -607,7 +590,7 @@ abstract class Catalog extends Model
|
|||||||
{
|
{
|
||||||
$query->where('date_created','=',$this->date_created ? $this->date_created : NULL);
|
$query->where('date_created','=',$this->date_created ? $this->date_created : NULL);
|
||||||
if (Schema::hasColumn($this->getTable(),'subsectime'))
|
if (Schema::hasColumn($this->getTable(),'subsectime'))
|
||||||
$query->where('subsectime','=',$this->subsectime ? $this->subsectime : NULL);
|
$query->where('subsectime','=',$this->subsectime ?: NULL);
|
||||||
|
|
||||||
if (! is_null($this->model))
|
if (! is_null($this->model))
|
||||||
$query->where('model','=',$this->model);
|
$query->where('model','=',$this->model);
|
||||||
|
@ -2,54 +2,66 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Traits\ForwardsCalls;
|
||||||
|
use Imagick;
|
||||||
|
|
||||||
|
use App\Casts\PostgresBytea;
|
||||||
|
use App\Jobs\CatalogMove;
|
||||||
|
|
||||||
class Photo extends Abstracted\Catalog
|
class Photo extends Abstracted\Catalog
|
||||||
{
|
{
|
||||||
protected $table = 'photo';
|
use ForwardsCalls;
|
||||||
|
|
||||||
|
public const config = 'photo';
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'created'=>'datetime:Y-m-d H:i:s',
|
||||||
|
'thumbnail'=>PostgresBytea::class,
|
||||||
|
];
|
||||||
|
|
||||||
protected static $includeSubSecTime = TRUE;
|
protected static $includeSubSecTime = TRUE;
|
||||||
|
|
||||||
// Imagick Object
|
// Imagick Object
|
||||||
private $_o;
|
private ?Imagick $_o;
|
||||||
|
protected array $init = [
|
||||||
// How should the image be rotated, based on the value of orientation
|
'creation_date',
|
||||||
private $_rotate = [
|
'gps',
|
||||||
3=>180,
|
'heightwidth',
|
||||||
6=>90,
|
'signature',
|
||||||
8=>-90,
|
'software',
|
||||||
|
'subsectime',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function getIDLinkAttribute()
|
// How should the image be rotated, based on the value of orientation
|
||||||
|
private array $_rotate = [
|
||||||
|
3 => 180,
|
||||||
|
6 => 90,
|
||||||
|
8 => -90,
|
||||||
|
];
|
||||||
|
|
||||||
|
public static function boot()
|
||||||
{
|
{
|
||||||
return $this->HTMLLinkAttribute($this->id,'p/info');
|
parent::boot();
|
||||||
}
|
|
||||||
|
|
||||||
public function getHtmlImageURL(): string
|
// Any photo saved, queue it to be moved.
|
||||||
{
|
self::saved(function($item) {
|
||||||
return sprintf('<img class="p-3" src="%s">',url('p/thumbnail',$this->id));
|
if ($item->scanned && (! $item->duplicate) && (! $item->remove) && ($x=$item->shouldMove()) === TRUE) {
|
||||||
}
|
Log::info(sprintf('%s: Need to Move [%s]','Photo',$item->id.'|'.serialize($x)));
|
||||||
|
|
||||||
/**
|
CatalogMove::dispatch($item)
|
||||||
* Return the image, rotated, minus exif data
|
->onQueue('move');
|
||||||
*/
|
}
|
||||||
public function image()
|
});
|
||||||
{
|
|
||||||
if (is_null($imo = $this->o()))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
if (array_key_exists('exif',$imo->getImageProfiles()))
|
|
||||||
$imo->removeImageProfile('exif');
|
|
||||||
|
|
||||||
return $this->rotate($imo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the GPS coordinates
|
* Calculate the GPS coordinates
|
||||||
*/
|
*/
|
||||||
public static function latlon(array $coordinate,$hemisphere)
|
public static function latlon(array $coordinate,string $hemisphere): ?float
|
||||||
{
|
{
|
||||||
if (! $coordinate OR ! $hemisphere)
|
if ((! $coordinate) || (! $hemisphere))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
for ($i=0; $i<3; $i++) {
|
for ($i=0; $i<3; $i++) {
|
||||||
@ -67,29 +79,172 @@ class Photo extends Abstracted\Catalog
|
|||||||
|
|
||||||
list($degrees,$minutes,$seconds) = $coordinate;
|
list($degrees,$minutes,$seconds) = $coordinate;
|
||||||
|
|
||||||
$sign = ($hemisphere == 'W' || $hemisphere == 'S') ? -1 : 1;
|
$sign = ($hemisphere === 'W' || $hemisphere === 'S') ? -1 : 1;
|
||||||
|
|
||||||
return round($sign*($degrees+$minutes/60+$seconds/3600),($degrees>100 ? 3 : 4));
|
return round($sign*($degrees+$minutes/60+$seconds/3600),($degrees>100 ? 3 : 4));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an Imagick object or attribute
|
* Forward calls to Imagick
|
||||||
|
*
|
||||||
|
* @param $method
|
||||||
|
* @param $parameters
|
||||||
|
* @return mixed|null
|
||||||
*/
|
*/
|
||||||
protected function o($attr=NULL)
|
public function __call($method,$parameters) {
|
||||||
|
if (str_starts_with($method,'Imagick_')) {
|
||||||
|
$method = preg_replace('/^Imagick_/','',$method);
|
||||||
|
|
||||||
|
return $this->o ? $this->forwardCallTo($this->_o,$method,$parameters) : NULL;
|
||||||
|
|
||||||
|
} else
|
||||||
|
return parent::__call($method,$parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __get($key): mixed
|
||||||
{
|
{
|
||||||
if (! file_exists($this->file_path()) OR ! is_readable($this->file_path()))
|
if ($key === 'o') {
|
||||||
return NULL;
|
if (isset($this->_o))
|
||||||
|
return $this->_o;
|
||||||
|
|
||||||
if (is_null($this->_o))
|
if ((! file_exists($this->file_name(FALSE))) || (! is_readable($this->file_name(FALSE))))
|
||||||
$this->_o = new \Imagick($this->file_path());
|
return $this->_o = NULL;
|
||||||
|
|
||||||
return is_null($attr) ? $this->_o : $this->_o->getImageProperty($attr);
|
if (! isset($this->_o))
|
||||||
|
return $this->_o = new Imagick($this->file_name(FALSE));
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::__get($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ATTRIBUTES */
|
||||||
|
|
||||||
|
public function getFileSignatureAttribute(string $val=NULL): string
|
||||||
|
{
|
||||||
|
return $val ?: $this->getObjectOriginal('file_signature');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHeightAttribute(string $val=NULL): ?int
|
||||||
|
{
|
||||||
|
return $val ?: $this->getObjectOriginal('height');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOrientationAttribute(int $val=NULL): ?int
|
||||||
|
{
|
||||||
|
return $val ?: $this->getObjectOriginal('orientation');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSignatureAttribute(string $val=NULL): ?string
|
||||||
|
{
|
||||||
|
return $val ?: $this->getObjectOriginal('signature');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWidthAttribute(string $val=NULL): ?int
|
||||||
|
{
|
||||||
|
return $val ?: $this->getObjectOriginal('width');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* METHODS */
|
||||||
|
|
||||||
|
public function custom_init(): void
|
||||||
|
{
|
||||||
|
$this->orientation = $this->getObjectOriginal('orientation');
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($this->isReadable() && $this->o->thumbnailimage(150,150,true)) {
|
||||||
|
$this->o->setImageFormat('jpg');
|
||||||
|
|
||||||
|
$this->thumbnail = $this->o->getImageBlob();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::info(sprintf('Unable to create thumbnail for %s (%s)',$this->id,$e->getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->thumbnail === FALSE)
|
||||||
|
$this->thumbnail = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getObjectOriginal(string $property): mixed
|
||||||
|
{
|
||||||
|
switch ($property) {
|
||||||
|
case 'creation_date':
|
||||||
|
if ($this->Imagick_getImageProperty('exif:DateTimeOriginal') === '0000:00:00 00:00:00'
|
||||||
|
&& $this->Imagick_getImageProperty('exif:DateTime') === '0000:00:00 00:00:00')
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
$result = Carbon::create($x=
|
||||||
|
($this->Imagick_getImageProperty('exif:DateTimeOriginal') && ($this->Imagick_getImageProperty('exif:DateTimeOriginal') !== '0000:00:00 00:00:00'))
|
||||||
|
? $this->Imagick_getImageProperty('exif:DateTimeOriginal').$this->Imagick_getImageProperty('exif:OffsetTimeOriginal')
|
||||||
|
: $this->Imagick_getImageProperty('exif:DateTime').$this->Imagick_getImageProperty('exif:OffsetTime'));
|
||||||
|
|
||||||
|
return $result ?: NULL;
|
||||||
|
|
||||||
|
case 'file_signature':
|
||||||
|
return md5_file($this->file_name(FALSE));
|
||||||
|
|
||||||
|
case 'gps_lat':
|
||||||
|
return self::latlon(preg_split('/,\s?/',$this->Imagick_getImageProperty('exif:GPSLatitude')),$this->Imagick_getImageProperty('exif:GPSLatitudeRef'));
|
||||||
|
|
||||||
|
case 'gps_lon':
|
||||||
|
return self::latlon(preg_split('/,\s?/',$this->Imagick_getImageProperty('exif:GPSLongitude')),$this->Imagick_getImageProperty('exif:GPSLongitudeRef'));
|
||||||
|
|
||||||
|
case 'height':
|
||||||
|
return $this->Imagick_getImageHeight();
|
||||||
|
|
||||||
|
case 'identifier':
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
case 'make':
|
||||||
|
return $this->Imagick_getImageProperty('exif:Make');
|
||||||
|
|
||||||
|
case 'model':
|
||||||
|
return $this->Imagick_getImageProperty('exif:Model');
|
||||||
|
|
||||||
|
case 'orientation':
|
||||||
|
return $this->Imagick_getImageOrientation();
|
||||||
|
|
||||||
|
case 'signature':
|
||||||
|
return $this->Imagick_getImageSignature();
|
||||||
|
|
||||||
|
case 'software':
|
||||||
|
return $this->Imagick_getImageProperty('exif:Software');
|
||||||
|
|
||||||
|
case 'subsectime':
|
||||||
|
$this->subsectime = (int)$this->Imagick_getImageProperty('exif:SubSecTimeOriginal');
|
||||||
|
|
||||||
|
// In case of an error.
|
||||||
|
if ($this->subsectime > 32767)
|
||||||
|
$this->subsectime = 32767;
|
||||||
|
|
||||||
|
if ($this->subsectime === FALSE)
|
||||||
|
$this->subsectime = 0;
|
||||||
|
|
||||||
|
return $this->subsectime;
|
||||||
|
|
||||||
|
case 'width':
|
||||||
|
return $this->Imagick_getImageWidth();
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new \Exception('To implement: '.$property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the image, rotated
|
||||||
|
*/
|
||||||
|
public function image(): ?string
|
||||||
|
{
|
||||||
|
$imo = clone($this->o);
|
||||||
|
|
||||||
|
return $imo ? $this->rotate($imo) : NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the orientation of a photo
|
* Display the orientation of a photo
|
||||||
*/
|
*/
|
||||||
public function orientation() {
|
public function orientation(): string
|
||||||
|
{
|
||||||
switch ($this->orientation) {
|
switch ($this->orientation) {
|
||||||
case 1: return 'None!';
|
case 1: return 'None!';
|
||||||
case 3: return 'Upside Down';
|
case 3: return 'Upside Down';
|
||||||
@ -102,118 +257,36 @@ class Photo extends Abstracted\Catalog
|
|||||||
/**
|
/**
|
||||||
* Rotate the image
|
* Rotate the image
|
||||||
*/
|
*/
|
||||||
private function rotate(\Imagick $imo)
|
private function rotate(\Imagick $imo,string $format='jpg'): string
|
||||||
{
|
{
|
||||||
if (array_key_exists($this->orientation,$this->_rotate))
|
if (array_key_exists($this->orientation,$this->_rotate))
|
||||||
$imo->rotateImage(new \ImagickPixel('none'),$this->_rotate[$this->orientation]);
|
$imo->rotateImage(new \ImagickPixel('none'),$this->_rotate[$this->orientation]);
|
||||||
|
|
||||||
$imo->setImageFormat('jpg');
|
$imo->setImageFormat($format);
|
||||||
|
|
||||||
|
if (array_key_exists('exif',$imo->getImageProfiles()))
|
||||||
|
$imo->removeImageProfile('exif');
|
||||||
|
|
||||||
return $imo->getImageBlob();
|
return $imo->getImageBlob();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function property(string $property)
|
|
||||||
{
|
|
||||||
if (! $this->o())
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
switch ($property) {
|
|
||||||
case 'creationdate':
|
|
||||||
if ($this->property('exif:DateTimeOriginal') == '0000:00:00 00:00:00'
|
|
||||||
&& $this->property('exif:DateTimeOriginal') == '0000:00:00 00:00:00')
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
return strtotime(
|
|
||||||
$this->property('exif:DateTimeOriginal') && $this->property('exif:DateTimeOriginal') != '0000:00:00 00:00:00'
|
|
||||||
? $this->property('exif:DateTimeOriginal')
|
|
||||||
: $this->property('exif:DateTime'));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'height': return $this->_o->getImageHeight();
|
|
||||||
case 'orientation': return $this->_o->getImageOrientation();
|
|
||||||
case 'signature': return $this->_o->getImageSignature();
|
|
||||||
case 'width': return $this->_o->getImageWidth();
|
|
||||||
|
|
||||||
default:
|
|
||||||
return $this->_o->getImageProperty($property);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function properties()
|
|
||||||
{
|
|
||||||
return $this->o() ? $this->_o->getImageProperties() : [];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function setLocation()
|
|
||||||
{
|
|
||||||
$this->gps_lat = static::latlon(preg_split('/,\s?/',$this->property('exif:GPSLatitude')),$this->property('exif:GPSLatitudeRef'));
|
|
||||||
$this->gps_lon = static::latlon(preg_split('/,\s?/',$this->property('exif:GPSLongitude')),$this->property('exif:GPSLongitudeRef'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setMakeModel()
|
|
||||||
{
|
|
||||||
$ma = NULL;
|
|
||||||
|
|
||||||
if ($this->property('exif:Make'))
|
|
||||||
$ma = Make::firstOrCreate([
|
|
||||||
'name'=>$this->property('exif:Make'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$mo = Model::firstOrCreate([
|
|
||||||
'name'=>$this->property('exif:Model') ?: NULL,
|
|
||||||
'make_id'=>$ma ? $ma->id : NULL,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$so = Software::firstOrCreate([
|
|
||||||
'name'=>$this->property('exif:Software') ?: NULL,
|
|
||||||
'model_id'=>$mo->id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->software_id = $so->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setSubSecTime()
|
|
||||||
{
|
|
||||||
$this->subsectime = (int)$this->property('exif:SubSecTimeOriginal');
|
|
||||||
|
|
||||||
// In case of an error.
|
|
||||||
if ($this->subsectime > 32767)
|
|
||||||
$this->subsectime = 32767;
|
|
||||||
|
|
||||||
if ($this->subsectime === FALSE)
|
|
||||||
$this->subsectime = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setThumbnail()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->thumbnail = exif_thumbnail($this->file_path());
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
// @todo Couldnt get the thumbnail, so we should create one.
|
|
||||||
Log::info(sprintf('Unable to create thumbnail for %s (%s)',$this->id,$e->getMessage()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->thumbnail === FALSE)
|
|
||||||
$this->thumbnail = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the image's thumbnail
|
* Return the image's thumbnail
|
||||||
*/
|
*/
|
||||||
public function thumbnail($rotate=TRUE)
|
public function thumbnail($rotate=TRUE): ?string
|
||||||
{
|
{
|
||||||
if (! $this->thumbnail) {
|
if (! $this->thumbnail) {
|
||||||
if ($this->isReadable() AND $this->o()->thumbnailimage(200,200,true,false)) {
|
if ($this->isReadable() && $this->o->thumbnailimage(150,150,true)) {
|
||||||
$this->_o->setImageFormat('jpg');
|
$this->o->setImageFormat('jpg');
|
||||||
return $this->_o->getImageBlob();
|
|
||||||
|
return $this->o->getImageBlob();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $rotate OR ! array_key_exists($this->orientation,$this->_rotate) OR ! extension_loaded('imagick'))
|
if ((! $rotate) || (! array_key_exists($this->orientation,$this->_rotate)) || (! extension_loaded('imagick')))
|
||||||
return $this->thumbnail;
|
return $this->thumbnail;
|
||||||
|
|
||||||
$imo = new \Imagick();
|
$imo = new \Imagick();
|
||||||
@ -225,10 +298,9 @@ class Photo extends Abstracted\Catalog
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the extension of the image
|
* Return the extension of the image
|
||||||
* @todo mime-by-ext?
|
|
||||||
*/
|
*/
|
||||||
public function type($mime=FALSE)
|
public function type(bool $mime=FALSE): string
|
||||||
{
|
{
|
||||||
return strtolower($mime ? 'image/jpeg' : pathinfo($this->filename,PATHINFO_EXTENSION));
|
return strtolower($mime ? mime_content_type($this->file_name(FALSE)) : pathinfo($this->filename,PATHINFO_EXTENSION));
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,190 +2,119 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
use App\Jobs\CatalogMove;
|
||||||
|
use App\Media\{Base,Factory};
|
||||||
|
|
||||||
class Video extends Abstracted\Catalog
|
class Video extends Abstracted\Catalog
|
||||||
{
|
{
|
||||||
// ID3 Object
|
public const config = 'video';
|
||||||
private $_o = NULL;
|
|
||||||
|
|
||||||
public function getIDLinkAttribute()
|
protected $casts = [
|
||||||
|
'created'=>'datetime:Y-m-d H:i:s',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Media Object
|
||||||
|
private ?Base $_o;
|
||||||
|
protected array $init = [
|
||||||
|
'creation_date',
|
||||||
|
'gps',
|
||||||
|
'heightwidth',
|
||||||
|
'signature',
|
||||||
|
'software',
|
||||||
|
];
|
||||||
|
|
||||||
|
public static function boot()
|
||||||
{
|
{
|
||||||
return $this->HTMLLinkAttribute($this->id,'v/info');
|
parent::boot();
|
||||||
|
|
||||||
|
// Any video saved, queue it to be moved.
|
||||||
|
self::saved(function($item) {
|
||||||
|
if ($item->scanned && (! $item->duplicate) && (! $item->remove) && ($x=$item->shouldMove()) === TRUE) {
|
||||||
|
Log::info(sprintf('%s: Need to Move [%s]',__METHOD__,$item->id.'|'.serialize($x)));
|
||||||
|
|
||||||
|
CatalogMove::dispatch($item)
|
||||||
|
->onQueue('move');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getHtmlImageURL()
|
public function __get($key): mixed
|
||||||
{
|
{
|
||||||
return sprintf('<video width="320" height="240" src="%s" controls></video>',url('/v/view/'.$this->id));
|
if ($key === 'o') {
|
||||||
}
|
if (isset($this->_o))
|
||||||
|
return $this->_o;
|
||||||
|
|
||||||
/**
|
if ((! file_exists($this->file_name(FALSE))) || (! is_readable($this->file_name(FALSE))))
|
||||||
* Return an GetID3 object or attribute
|
return $this->_o = NULL;
|
||||||
*
|
|
||||||
*/
|
|
||||||
protected function o($attr=NULL)
|
|
||||||
{
|
|
||||||
if (! $this->filename OR ! file_exists($this->file_path()) OR ! is_readable($this->file_path()))
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
if (is_null($this->_o))
|
if (! isset($this->_o)) {
|
||||||
{
|
$this->_o = Factory::create($this->file_name(FALSE));
|
||||||
/*
|
|
||||||
* @todo There is a bug with getID3, where the second iteration yields different results
|
return $this->_o;
|
||||||
* this is problematic when video:scanall is run from a queue and there is more than 1 job.
|
}
|
||||||
* It also appears that only 1.9.18 gets date/gps data, a later version doesnt get gps data.
|
|
||||||
*/
|
|
||||||
$this->_o = new \getID3;
|
|
||||||
$this->_o->analyze($this->file_path());
|
|
||||||
$this->_o->getHashdata('sha1');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return is_null($attr) ? $this->_o : (array_key_exists($attr,$this->_o->info) ? $this->_o->info[$attr] : NULL);
|
return parent::__get($key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function property(string $property)
|
/* METHODS */
|
||||||
|
|
||||||
|
public function custom_init()
|
||||||
{
|
{
|
||||||
if (! $this->o())
|
$this->audio_channels = $this->getObjectOriginal('audio_channels');
|
||||||
return NULL;
|
$this->audio_codec = $this->getObjectOriginal('audio_codec');
|
||||||
|
$this->audio_samplerate = $this->getObjectOriginal('audio_samplerate');
|
||||||
|
$this->gps_altitude = $this->getObjectOriginal('gps_altitude');
|
||||||
|
$this->length = round($this->getObjectOriginal('length'),2);
|
||||||
|
$this->type = $this->getObjectOriginal('type');
|
||||||
|
$this->video_codec = $this->getObjectOriginal('video_codec');
|
||||||
|
$this->video_framerate = $this->getObjectOriginal('video_framerate');
|
||||||
|
|
||||||
|
/*
|
||||||
|
$x = new \getID3;
|
||||||
|
$x->analyze($this->file_name(FALSE));
|
||||||
|
dump(['id3'=>$x]);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getObjectOriginal(string $property): mixed
|
||||||
|
{
|
||||||
switch ($property) {
|
switch ($property) {
|
||||||
case 'creationdate':
|
case 'file_signature':
|
||||||
// Try creation_Data
|
return md5_file($this->file_name(FALSE));
|
||||||
$x = Arr::get($this->_o->info,'quicktime.comments.creation_date.0');
|
|
||||||
|
|
||||||
// Try creation_Data
|
case 'length':
|
||||||
if (is_null($x))
|
return $this->o?->duration;
|
||||||
$x = Arr::get($this->_o->info,'quicktime.comments.creationdate.0');
|
|
||||||
|
|
||||||
return $x;
|
case 'audio_channels':
|
||||||
|
case 'audio_codec':
|
||||||
case 'make': return Arr::get($this->_o->info,'quicktime.comments.make.0');
|
case 'audio_samplerate':
|
||||||
case 'model': return Arr::get($this->_o->info,'quicktime.comments.model.0');
|
case 'creation_date':
|
||||||
case 'software': return Arr::get($this->_o->info,'quicktime.comments.software.0');
|
case 'gps_altitude':
|
||||||
case 'signature': return Arr::get($this->_o->info,'sha1_data');
|
case 'gps_lat':
|
||||||
#case 'height': return $this->subatomsearch('quicktime.moov.subatoms',['trak','tkhd'],'height'); break;
|
case 'gps_lon':
|
||||||
#case 'width': return $this->subatomsearch('quicktime.moov.subatoms',['trak','tkhd'],'width'); break;
|
case 'height':
|
||||||
case 'height': return Arr::get($this->_o->info,'video.resolution_y');
|
case 'make':
|
||||||
case 'width': return Arr::get($this->_o->info,'video.resolution_x');
|
case 'model':
|
||||||
case 'length': return Arr::get($this->_o->info,'playtime_seconds');
|
case 'signature':
|
||||||
case 'type': return Arr::get($this->_o->info,'video.dataformat');
|
case 'software':
|
||||||
case 'codec': return Arr::get($this->_o->info,'audio.codec');
|
case 'type':
|
||||||
case 'audiochannels': return Arr::get($this->_o->info,'audio.channels');
|
case 'width':
|
||||||
case 'samplerate': return Arr::get($this->_o->info,'audio.sample_rate');
|
case 'video_codec':
|
||||||
case 'channelmode': return Arr::get($this->_o->info,'audio.channelmode');
|
case 'video_framerate':
|
||||||
case 'gps_lat': return Arr::get($this->_o->info,'quicktime.comments.gps_latitude.0');
|
return $this->o?->{$property};
|
||||||
case 'gps_lon': return Arr::get($this->_o->info,'quicktime.comments.gps_longitude.0');
|
|
||||||
case 'gps_altitude': return Arr::get($this->_o->info,'quicktime.comments.gps_altitude.0');
|
|
||||||
case 'identifier':
|
|
||||||
if ($x=Arr::get($this->_o->info,'quicktime.comments')) {
|
|
||||||
return Arr::get(Arr::flatten(Arr::only($x,'content.identifier')),0);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return NULL;
|
throw new \Exception('To implement: '.$property);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function properties()
|
|
||||||
{
|
|
||||||
return $this->o() ? $this->_o->info : [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigate through ID3 data to find the value.
|
* Return the extension of the video
|
||||||
*/
|
*/
|
||||||
private function subatomsearch($atom,array $paths,$key,array $data=[]) {
|
public function type($mime=FALSE): string
|
||||||
if (! $data AND is_null($data = Arr::get($this->_o->info,$atom))) {
|
|
||||||
// Didnt find it.
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($paths as $path) {
|
|
||||||
$found = FALSE;
|
|
||||||
|
|
||||||
foreach ($data as $array) {
|
|
||||||
if ($array['name'] === $path) {
|
|
||||||
$found = TRUE;
|
|
||||||
|
|
||||||
if ($path != last($paths))
|
|
||||||
$data = $array['subatoms'];
|
|
||||||
else
|
|
||||||
$data = $array;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $found)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return isset($data[$key]) ? $data[$key] : NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setDateCreated()
|
|
||||||
{
|
|
||||||
$this->created = $this->property('creationdate') ? strtotime($this->property('creationdate')) : NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setLocation()
|
|
||||||
{
|
|
||||||
$this->gps_lat = $this->property('gps_lat');
|
|
||||||
$this->gps_lon = $this->property('gps_lon');
|
|
||||||
$this->gps_altitude = $this->property('gps_altitude');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setMakeModel()
|
|
||||||
{
|
|
||||||
$ma = NULL;
|
|
||||||
|
|
||||||
if ($this->property('make'))
|
|
||||||
$ma = Make::firstOrCreate([
|
|
||||||
'name'=>$this->property('make'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$mo = Model::firstOrCreate([
|
|
||||||
'name'=>$this->property('model') ?: NULL,
|
|
||||||
'make_id'=>$ma ? $ma->id : NULL,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$so = Software::firstOrCreate([
|
|
||||||
'name'=>$this->property('software') ?: NULL,
|
|
||||||
'model_id'=>$mo->id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->software_id = $so->id;
|
|
||||||
|
|
||||||
$this->type = $this->property('type');
|
|
||||||
$this->length = round($this->property('length'),2);
|
|
||||||
$this->codec = $this->property('codec');
|
|
||||||
$this->audiochannels = $this->property('audiochannels');
|
|
||||||
$this->channelmode = $this->property('channelmode');
|
|
||||||
$this->samplerate = $this->property('samplerate');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setSignature()
|
|
||||||
{
|
|
||||||
parent::setSignature();
|
|
||||||
|
|
||||||
$this->identifier = $this->property('identifier');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setSubSecTime()
|
|
||||||
{
|
|
||||||
// NOOP
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setThumbnail()
|
|
||||||
{
|
|
||||||
// NOOP
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the extension of the image
|
|
||||||
*/
|
|
||||||
public function type($mime=FALSE)
|
|
||||||
{
|
{
|
||||||
return strtolower($mime ? File::mime_by_ext(pathinfo($this->filename,PATHINFO_EXTENSION)) : pathinfo($this->filename,PATHINFO_EXTENSION));
|
return strtolower($mime ? File::mime_by_ext(pathinfo($this->filename,PATHINFO_EXTENSION)) : pathinfo($this->filename,PATHINFO_EXTENSION));
|
||||||
}
|
}
|
||||||
|
@ -2,50 +2,34 @@
|
|||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Route;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||||
|
|
||||||
use App\Models\{Photo,Video};
|
use App\Models\{Photo,Video};
|
||||||
use App\Jobs\{PhotoMove,VideoMove};
|
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
use DispatchesJobs;
|
use DispatchesJobs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register any application services.
|
* Register any application services.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function register()
|
public function register(): void
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bootstrap any application services.
|
* Bootstrap any application services.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function boot()
|
public function boot(): void
|
||||||
{
|
{
|
||||||
// Any photo saved, queue it to be moved.
|
Route::model('po',Photo::class);
|
||||||
Photo::saved(function($photo) {
|
Route::model('vo',Video::class);
|
||||||
if ($photo->scanned AND ! $photo->duplicate AND ! $photo->remove AND ($x=$photo->moveable()) === TRUE) {
|
}
|
||||||
Log::info(sprintf('%s: Need to Move [%s]',__METHOD__,$photo->id.'|'.serialize($x)));
|
|
||||||
|
|
||||||
$this->dispatch((new PhotoMove($photo))->onQueue('move'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Any video saved, queue it to be moved.
|
|
||||||
Video::saved(function($video) {
|
|
||||||
if ($video->scanned AND ! $video->duplicate AND ! $video->remove AND ($x=$video->moveable()) === TRUE) {
|
|
||||||
Log::info(sprintf('%s: Need to Move [%s]',__METHOD__,$video->id.'|'.serialize($x)));
|
|
||||||
|
|
||||||
$this->dispatch((new VideoMove($video))->onQueue('move'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,30 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Providers;
|
|
||||||
|
|
||||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
|
||||||
use Illuminate\Support\Facades\Gate;
|
|
||||||
|
|
||||||
class AuthServiceProvider extends ServiceProvider
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The policy mappings for the application.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $policies = [
|
|
||||||
// 'App\Model' => 'App\Policies\ModelPolicy',
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register any authentication / authorization services.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function boot()
|
|
||||||
{
|
|
||||||
$this->registerPolicies();
|
|
||||||
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Providers;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Broadcast;
|
|
||||||
use Illuminate\Support\ServiceProvider;
|
|
||||||
|
|
||||||
class BroadcastServiceProvider extends ServiceProvider
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Bootstrap any application services.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function boot()
|
|
||||||
{
|
|
||||||
Broadcast::routes();
|
|
||||||
|
|
||||||
require base_path('routes/channels.php');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Providers;
|
|
||||||
|
|
||||||
use Illuminate\Auth\Events\Registered;
|
|
||||||
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
|
|
||||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
|
||||||
use Illuminate\Support\Facades\Event;
|
|
||||||
|
|
||||||
class EventServiceProvider extends ServiceProvider
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The event listener mappings for the application.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $listen = [
|
|
||||||
Registered::class => [
|
|
||||||
SendEmailVerificationNotification::class,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register any events for your application.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function boot()
|
|
||||||
{
|
|
||||||
parent::boot();
|
|
||||||
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Providers;
|
|
||||||
|
|
||||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
|
||||||
use Illuminate\Support\Facades\Route;
|
|
||||||
|
|
||||||
class RouteServiceProvider extends ServiceProvider
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This namespace is applied to your controller routes.
|
|
||||||
*
|
|
||||||
* In addition, it is set as the URL generator's root namespace.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $namespace = 'App\Http\Controllers';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define your route model bindings, pattern filters, etc.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function boot()
|
|
||||||
{
|
|
||||||
//
|
|
||||||
|
|
||||||
parent::boot();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define the routes for the application.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function map()
|
|
||||||
{
|
|
||||||
$this->mapApiRoutes();
|
|
||||||
|
|
||||||
$this->mapWebRoutes();
|
|
||||||
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define the "web" routes for the application.
|
|
||||||
*
|
|
||||||
* These routes all receive session state, CSRF protection, etc.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
protected function mapWebRoutes()
|
|
||||||
{
|
|
||||||
Route::middleware('web')
|
|
||||||
->namespace($this->namespace)
|
|
||||||
->group(base_path('routes/web.php'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define the "api" routes for the application.
|
|
||||||
*
|
|
||||||
* These routes are typically stateless.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
protected function mapApiRoutes()
|
|
||||||
{
|
|
||||||
Route::prefix('api')
|
|
||||||
->middleware('api')
|
|
||||||
->namespace($this->namespace)
|
|
||||||
->group(base_path('routes/api.php'));
|
|
||||||
}
|
|
||||||
}
|
|
104
app/Traits/FindQuicktimeAtoms.php
Normal file
104
app/Traits/FindQuicktimeAtoms.php
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Traits;
|
||||||
|
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
use App\Media\QuickTime\Atom;
|
||||||
|
|
||||||
|
trait FindQuicktimeAtoms
|
||||||
|
{
|
||||||
|
protected function get_atoms(string $class_prefix,string $unknown,int $offset,int $size,string $atom=NULL,string $passthru=NULL,\Closure $callback=NULL): Collection
|
||||||
|
{
|
||||||
|
// List of atoms
|
||||||
|
// File Type atom should proceed move, movie data, preview. free space atoms, if it exists.
|
||||||
|
// Can assume it doesnt exist if those other atoms are discovered first.
|
||||||
|
// Atoms can be present in any order
|
||||||
|
// Must contain a movie atom.
|
||||||
|
// Movie data atoms can exceed 2^32.
|
||||||
|
|
||||||
|
$rp = 0;
|
||||||
|
if (! $atom) {
|
||||||
|
$fh = fopen($this->filename,'r');
|
||||||
|
fseek($fh,$offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = collect();
|
||||||
|
|
||||||
|
while ($rp < $size) {
|
||||||
|
$read = $atom ? substr($atom,$rp,8) : fread($fh,8);
|
||||||
|
$rp += strlen($read);
|
||||||
|
|
||||||
|
$header = unpack('Nsize/a4atom',$read);
|
||||||
|
|
||||||
|
// For mdat atoms, if size = 1, the true size is in the 64 bit extended header
|
||||||
|
if (($header['atom'] === 'mdat') && ($header['size'] === 1))
|
||||||
|
throw new \Exception(sprintf('%s:! We havent handed large QT files yet.',self::LOGKEY));
|
||||||
|
|
||||||
|
// Load our class for this supplier
|
||||||
|
$class = $class_prefix.$header['atom'];
|
||||||
|
|
||||||
|
$data = $atom
|
||||||
|
? substr($atom,$rp,$header['size']-8)
|
||||||
|
: ($header['size']-8 && ($header['size']-8 <= self::BLOCK_SIZE) ? fread($fh,$header['size']-8) : NULL);
|
||||||
|
|
||||||
|
if ($header['size'] >= 8) {
|
||||||
|
$o = class_exists($class)
|
||||||
|
? new $class($offset+$rp,$header['size']-8,$this->filename,$data,$passthru)
|
||||||
|
: new $unknown($offset+$rp,$header['size']-8,$this->filename,$header['atom'],$data);
|
||||||
|
|
||||||
|
$result->push($o);
|
||||||
|
|
||||||
|
$rp += $header['size']-8;
|
||||||
|
|
||||||
|
// Only need to seek if we didnt read all the data
|
||||||
|
if ((! $atom) && ($header['size'] > self::BLOCK_SIZE))
|
||||||
|
fseek($fh,$offset+$rp);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
dd([get_class($this) => $data]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Work out if data from the last atom next to be passed onto the next one
|
||||||
|
if ($callback)
|
||||||
|
$passthru = $callback($o);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $atom) {
|
||||||
|
fclose($fh);
|
||||||
|
unset($fh);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively look through our object hierarchy of atoms for a specific atom class
|
||||||
|
*
|
||||||
|
* @param string $subatom
|
||||||
|
* @param int|NULL $expect
|
||||||
|
* @param int $depth
|
||||||
|
* @return Collection|Atom|NULL
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function find_atoms(string $subatom,?int $expect=NULL,int $depth=100): Collection|Atom|NULL
|
||||||
|
{
|
||||||
|
if (! isset($this->atoms) || ($depth < 0))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
$subatomo = $this->atoms->filter(fn($item)=>get_class($item)===$subatom);
|
||||||
|
|
||||||
|
$subatomo = $subatomo
|
||||||
|
->merge($this->atoms->map(fn($item)=>$item->find_atoms($subatom,NULL,$depth-1))
|
||||||
|
->filter(fn($item)=>$item ? $item->count() : NULL)
|
||||||
|
->flatten());
|
||||||
|
|
||||||
|
if (! $subatomo->count())
|
||||||
|
return $subatomo;
|
||||||
|
|
||||||
|
if ($expect && ($subatomo->count() !== $expect))
|
||||||
|
throw new \Exception(sprintf('! Expected %d subatoms of %s, but have %d',$expect,$subatom,$subatomo->count()));
|
||||||
|
|
||||||
|
return ($expect === 1) ? $subatomo->pop() : $subatomo;
|
||||||
|
}
|
||||||
|
}
|
@ -12,7 +12,7 @@ trait Type
|
|||||||
{
|
{
|
||||||
private function getModelType(string $type): string
|
private function getModelType(string $type): string
|
||||||
{
|
{
|
||||||
$class = 'App\Models\\'.$type;
|
$class = 'App\Models\\'.ucfirst(strtolower($type));
|
||||||
|
|
||||||
if (! class_exists($class))
|
if (! class_exists($class))
|
||||||
abort(500,sprintf('No class [%s]',$type));
|
abort(500,sprintf('No class [%s]',$type));
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class supports People.
|
|
||||||
*
|
|
||||||
* @package Photo
|
|
||||||
* @category Models
|
|
||||||
* @author Deon George
|
|
||||||
* @copyright (c) 2014 Deon George
|
|
||||||
* @license http://dev.leenooks.net/license.html
|
|
||||||
*/
|
|
||||||
class Model_People extends ORM {
|
|
||||||
public function age($x=NULL) {
|
|
||||||
return sprintf('%02s',date_diff(new DateTime(date('Y-m-d',is_null($x) ? time() : $x)),new DateTime(date('Y-m-d',$this->date_birth)))->y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
@ -1,32 +0,0 @@
|
|||||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find photos that dont match their database entries.
|
|
||||||
*
|
|
||||||
* @package Photo
|
|
||||||
* @category Tasks
|
|
||||||
* @author Deon George
|
|
||||||
* @copyright (c) 2014 Deon George
|
|
||||||
* @license http://dev.leenooks.net/license.html
|
|
||||||
*/
|
|
||||||
class Task_Photo_Stat extends Minion_Task {
|
|
||||||
protected $_options = array(
|
|
||||||
);
|
|
||||||
|
|
||||||
protected function _execute(array $params) {
|
|
||||||
$p = ORM::factory('Photo')->find_all();
|
|
||||||
|
|
||||||
foreach ($p as $po) {
|
|
||||||
if (! file_exists($po->file_path())) {
|
|
||||||
printf("ID [%s] doesnt exist (%s)?\n",$po->id,$po->file_path());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (($x=$po->io()->getImageSignature()) !== $po->signature) {
|
|
||||||
printf("ID [%s] signature doesnt match (%s) [%s != %s]?\n",$po->id,$po->file_path(),$po->signature,$x);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
50
artisan
Normal file → Executable file
50
artisan
Normal file → Executable file
@ -1,53 +1,15 @@
|
|||||||
#!/usr/bin/env php
|
#!/usr/bin/env php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Input\ArgvInput;
|
||||||
|
|
||||||
define('LARAVEL_START', microtime(true));
|
define('LARAVEL_START', microtime(true));
|
||||||
|
|
||||||
/*
|
// Register the Composer autoloader...
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Register The Auto Loader
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Composer provides a convenient, automatically generated class loader
|
|
||||||
| for our application. We just need to utilize it! We'll require it
|
|
||||||
| into the script here so that we do not have to worry about the
|
|
||||||
| loading of any our classes "manually". Feels great to relax.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
require __DIR__.'/vendor/autoload.php';
|
require __DIR__.'/vendor/autoload.php';
|
||||||
|
|
||||||
$app = require_once __DIR__.'/bootstrap/app.php';
|
// Bootstrap Laravel and handle the command...
|
||||||
|
$status = (require_once __DIR__.'/bootstrap/app.php')
|
||||||
/*
|
->handleCommand(new ArgvInput);
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Run The Artisan Application
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| When we run the console application, the current CLI command will be
|
|
||||||
| executed in this console and the response sent back to a terminal
|
|
||||||
| or another output device for the developers. Here goes nothing!
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
|
|
||||||
|
|
||||||
$status = $kernel->handle(
|
|
||||||
$input = new Symfony\Component\Console\Input\ArgvInput,
|
|
||||||
new Symfony\Component\Console\Output\ConsoleOutput
|
|
||||||
);
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Shutdown The Application
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Once Artisan has finished running, we will fire off the shutdown events
|
|
||||||
| so that any final work may be done by the application before we shut
|
|
||||||
| down the process. This is the last thing to happen to the request.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
$kernel->terminate($input, $status);
|
|
||||||
|
|
||||||
exit($status);
|
exit($status);
|
||||||
|
@ -1,55 +1,18 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/*
|
use Illuminate\Foundation\Application;
|
||||||
|--------------------------------------------------------------------------
|
use Illuminate\Foundation\Configuration\Exceptions;
|
||||||
| Create The Application
|
use Illuminate\Foundation\Configuration\Middleware;
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| The first thing we will do is create a new Laravel application instance
|
|
||||||
| which serves as the "glue" for all the components of Laravel, and is
|
|
||||||
| the IoC container for the system binding all of the various parts.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
$app = new Illuminate\Foundation\Application(
|
return Application::configure(basePath: dirname(__DIR__))
|
||||||
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
|
->withRouting(
|
||||||
);
|
web: __DIR__.'/../routes/web.php',
|
||||||
|
commands: __DIR__.'/../routes/console.php',
|
||||||
/*
|
health: '/up',
|
||||||
|--------------------------------------------------------------------------
|
)
|
||||||
| Bind Important Interfaces
|
->withMiddleware(function (Middleware $middleware) {
|
||||||
|--------------------------------------------------------------------------
|
//
|
||||||
|
|
})
|
||||||
| Next, we need to bind some important interfaces into the container so
|
->withExceptions(function (Exceptions $exceptions) {
|
||||||
| we will be able to resolve them when needed. The kernels serve the
|
//
|
||||||
| incoming requests to this application from both the web and CLI.
|
})->create();
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
$app->singleton(
|
|
||||||
Illuminate\Contracts\Http\Kernel::class,
|
|
||||||
App\Http\Kernel::class
|
|
||||||
);
|
|
||||||
|
|
||||||
$app->singleton(
|
|
||||||
Illuminate\Contracts\Console\Kernel::class,
|
|
||||||
App\Console\Kernel::class
|
|
||||||
);
|
|
||||||
|
|
||||||
$app->singleton(
|
|
||||||
Illuminate\Contracts\Debug\ExceptionHandler::class,
|
|
||||||
App\Exceptions\Handler::class
|
|
||||||
);
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Return The Application
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This script returns the application instance. The instance is given to
|
|
||||||
| the calling script so we can separate the building of the instances
|
|
||||||
| from the actual running of the application and sending responses.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
return $app;
|
|
||||||
|
5
bootstrap/providers.php
Normal file
5
bootstrap/providers.php
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
App\Providers\AppServiceProvider::class,
|
||||||
|
];
|
147
composer.json
147
composer.json
@ -1,75 +1,76 @@
|
|||||||
{
|
{
|
||||||
"name": "laravel/laravel",
|
"name": "laravel/laravel",
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"description": "The Laravel Framework.",
|
"description": "The Laravel Framework.",
|
||||||
"keywords": [
|
"keywords": ["laravel", "framework"],
|
||||||
"framework",
|
"license": "MIT",
|
||||||
"laravel"
|
"require": {
|
||||||
],
|
"php": "^8.3",
|
||||||
"license": "MIT",
|
"ext-fileinfo": "*",
|
||||||
"require": {
|
"ext-pdo": "*",
|
||||||
"php": "^7.2",
|
"ext-imagick": "*",
|
||||||
"barryvdh/laravel-debugbar": "^3.2",
|
"ext-pgsql": "*",
|
||||||
"fideloper/proxy": "^4.0",
|
"laravel/framework": "^11.0",
|
||||||
"james-heinrich/getid3": "^1.9",
|
"laravel/ui": "^4.5",
|
||||||
"laravel/framework": "^6.4",
|
"leenooks/laravel": "^11.1",
|
||||||
"laravel/socialite": "^4.2",
|
"leenooks/passkey": "^0.2"
|
||||||
"laravel/tinker": "^1.0",
|
},
|
||||||
"leenooks/laravel": "^6.0"
|
"require-dev": {
|
||||||
},
|
"barryvdh/laravel-debugbar": "^3.6",
|
||||||
"require-dev": {
|
"fakerphp/faker": "^1.23",
|
||||||
"facade/ignition": "1.11.1",
|
"laravel/tinker": "^2.9",
|
||||||
"fzaninotto/faker": "^1.4",
|
"mockery/mockery": "^1.6",
|
||||||
"mockery/mockery": "^1.0",
|
"nunomaduro/collision": "^8.0",
|
||||||
"nunomaduro/collision": "^3.0",
|
"phpunit/phpunit": "^11.0.1",
|
||||||
"phpunit/phpunit": "^8.0"
|
"spatie/laravel-ignition": "^2.4"
|
||||||
},
|
},
|
||||||
"config": {
|
"autoload": {
|
||||||
"optimize-autoloader": true,
|
"psr-4": {
|
||||||
"preferred-install": "dist",
|
"App\\": "app/",
|
||||||
"sort-packages": true
|
"Database\\Factories\\": "database/factories/",
|
||||||
},
|
"Database\\Seeders\\": "database/seeders/"
|
||||||
"extra": {
|
},
|
||||||
"laravel": {
|
"files": [
|
||||||
"dont-discover": []
|
"helpers.php"
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload-dev": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"App\\": "app/"
|
"Tests\\": "tests/"
|
||||||
},
|
}
|
||||||
"classmap": [
|
},
|
||||||
"database/seeds",
|
"repositories": {
|
||||||
"database/factories"
|
"leenooks": {
|
||||||
]
|
"type": "vcs",
|
||||||
},
|
"url": "https://gitea.dege.au/laravel/leenooks.git"
|
||||||
"autoload-dev": {
|
},
|
||||||
"psr-4": {
|
"passkey": {
|
||||||
"Tests\\": "tests/"
|
"type": "vcs",
|
||||||
}
|
"url": "https://gitea.dege.au/laravel/passkey.git"
|
||||||
},
|
}
|
||||||
"repositories": [
|
},
|
||||||
{
|
"scripts": {
|
||||||
"type": "vcs",
|
"post-autoload-dump": [
|
||||||
"url": "https://dev.leenooks.net/leenooks/laravel"
|
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||||
},
|
"@php artisan package:discover --ansi"
|
||||||
{
|
],
|
||||||
"type": "vcs",
|
"post-root-package-install": [
|
||||||
"url": "https://github.com/leenooks/laravel-theme"
|
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||||
}
|
],
|
||||||
],
|
"post-create-project-cmd": [
|
||||||
"minimum-stability": "dev",
|
"@php artisan key:generate --ansi"
|
||||||
"prefer-stable": true,
|
]
|
||||||
"scripts": {
|
},
|
||||||
"post-autoload-dump": [
|
"extra": {
|
||||||
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
"laravel": {
|
||||||
"@php artisan package:discover --ansi"
|
"dont-discover": []
|
||||||
],
|
}
|
||||||
"post-root-package-install": [
|
},
|
||||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
"config": {
|
||||||
],
|
"optimize-autoloader": true,
|
||||||
"post-create-project-cmd": [
|
"preferred-install": "dist",
|
||||||
"@php artisan key:generate --ansi"
|
"sort-packages": true
|
||||||
]
|
},
|
||||||
}
|
"minimum-stability": "dev",
|
||||||
|
"prefer-stable": true
|
||||||
}
|
}
|
||||||
|
7564
composer.lock
generated
7564
composer.lock
generated
File diff suppressed because it is too large
Load Diff
173
config/app.php
173
config/app.php
@ -7,13 +7,15 @@ return [
|
|||||||
| Application Name
|
| Application Name
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| This value is the name of your application. This value is used when the
|
| This value is the name of your application, which will be used when the
|
||||||
| framework needs to place the application's name in a notification or
|
| framework needs to place the application's name in a notification or
|
||||||
| any other location as required by the application or its packages.
|
| other UI elements where an application name needs to be displayed.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'name' => env('APP_NAME', 'Laravel'),
|
'name' => env('APP_NAME', 'Laravel'),
|
||||||
|
'name_html_long' => env('APP_NAME_HTML_LONG', '<b>Laravel</b>Application'),
|
||||||
|
'admins' => env('APP_ADMINS',[]) ? explode(';',env('APP_ADMINS')) : [],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@ -39,7 +41,7 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'debug' => env('APP_DEBUG', false),
|
'debug' => (bool) env('APP_DEBUG', false),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@ -48,26 +50,24 @@ return [
|
|||||||
|
|
|
|
||||||
| This URL is used by the console to properly generate URLs when using
|
| This URL is used by the console to properly generate URLs when using
|
||||||
| the Artisan command line tool. You should set this to the root of
|
| the Artisan command line tool. You should set this to the root of
|
||||||
| your application so that it is used when running Artisan tasks.
|
| the application so that it's available within Artisan commands.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'url' => env('APP_URL', 'http://localhost'),
|
'url' => env('APP_URL', 'http://localhost'),
|
||||||
|
|
||||||
'asset_url' => env('ASSET_URL', null),
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Application Timezone
|
| Application Timezone
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| Here you may specify the default timezone for your application, which
|
| Here you may specify the default timezone for your application, which
|
||||||
| will be used by the PHP date and date-time functions. We have gone
|
| will be used by the PHP date and date-time functions. The timezone
|
||||||
| ahead and set this to a sensible default for you out of the box.
|
| is set to "UTC" by default as it is suitable for most use cases.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'timezone' => 'Australia/Melbourne',
|
'timezone' => env('APP_TIMEZONE', 'UTC'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@ -75,165 +75,54 @@ return [
|
|||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| The application locale determines the default locale that will be used
|
| The application locale determines the default locale that will be used
|
||||||
| by the translation service provider. You are free to set this value
|
| by Laravel's translation / localization methods. This option can be
|
||||||
| to any of the locales which will be supported by the application.
|
| set to any locale for which you plan to have translation strings.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'locale' => 'en',
|
'locale' => env('APP_LOCALE', 'en'),
|
||||||
|
|
||||||
/*
|
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Application Fallback Locale
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| The fallback locale determines the locale to use when the current one
|
|
||||||
| is not available. You may change the value to correspond to any of
|
|
||||||
| the language folders that are provided through your application.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'fallback_locale' => 'en',
|
'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Faker Locale
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This locale will be used by the Faker PHP library when generating fake
|
|
||||||
| data for your database seeds. For example, this will be used to get
|
|
||||||
| localized telephone numbers, street address information and more.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'faker_locale' => 'en_US',
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Encryption Key
|
| Encryption Key
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| This key is used by the Illuminate encrypter service and should be set
|
| This key is utilized by Laravel's encryption services and should be set
|
||||||
| to a random, 32 character string, otherwise these encrypted strings
|
| to a random, 32 character string to ensure that all encrypted values
|
||||||
| will not be safe. Please do this before deploying an application!
|
| are secure. You should do this prior to deploying the application.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'key' => env('APP_KEY'),
|
|
||||||
|
|
||||||
'cipher' => 'AES-256-CBC',
|
'cipher' => 'AES-256-CBC',
|
||||||
|
|
||||||
/*
|
'key' => env('APP_KEY'),
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Autoloaded Service Providers
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| The service providers listed here will be automatically loaded on the
|
|
||||||
| request to your application. Feel free to add your own services to
|
|
||||||
| this array to grant expanded functionality to your applications.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'providers' => [
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Laravel Framework Service Providers...
|
|
||||||
*/
|
|
||||||
Illuminate\Auth\AuthServiceProvider::class,
|
|
||||||
Illuminate\Broadcasting\BroadcastServiceProvider::class,
|
|
||||||
Illuminate\Bus\BusServiceProvider::class,
|
|
||||||
Illuminate\Cache\CacheServiceProvider::class,
|
|
||||||
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
|
|
||||||
Illuminate\Cookie\CookieServiceProvider::class,
|
|
||||||
Illuminate\Database\DatabaseServiceProvider::class,
|
|
||||||
Illuminate\Encryption\EncryptionServiceProvider::class,
|
|
||||||
Illuminate\Filesystem\FilesystemServiceProvider::class,
|
|
||||||
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
|
|
||||||
Illuminate\Hashing\HashServiceProvider::class,
|
|
||||||
Illuminate\Mail\MailServiceProvider::class,
|
|
||||||
Illuminate\Notifications\NotificationServiceProvider::class,
|
|
||||||
Illuminate\Pagination\PaginationServiceProvider::class,
|
|
||||||
Illuminate\Pipeline\PipelineServiceProvider::class,
|
|
||||||
Illuminate\Queue\QueueServiceProvider::class,
|
|
||||||
Illuminate\Redis\RedisServiceProvider::class,
|
|
||||||
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
|
|
||||||
Illuminate\Session\SessionServiceProvider::class,
|
|
||||||
Illuminate\Translation\TranslationServiceProvider::class,
|
|
||||||
Illuminate\Validation\ValidationServiceProvider::class,
|
|
||||||
Illuminate\View\ViewServiceProvider::class,
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Package Service Providers...
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Application Service Providers...
|
|
||||||
*/
|
|
||||||
App\Providers\AppServiceProvider::class,
|
|
||||||
App\Providers\AuthServiceProvider::class,
|
|
||||||
// App\Providers\BroadcastServiceProvider::class,
|
|
||||||
App\Providers\EventServiceProvider::class,
|
|
||||||
App\Providers\RouteServiceProvider::class,
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Other Service Providers...
|
|
||||||
*/
|
|
||||||
Orchestra\Asset\AssetServiceProvider::class,
|
|
||||||
Collective\Html\HtmlServiceProvider::class,
|
|
||||||
//\SocialiteProviders\Manager\ServiceProvider::class,
|
|
||||||
|
|
||||||
|
'previous_keys' => [
|
||||||
|
...array_filter(
|
||||||
|
explode(',', env('APP_PREVIOUS_KEYS', ''))
|
||||||
|
),
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Class Aliases
|
| Maintenance Mode Driver
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| This array of class aliases will be registered when this application
|
| These configuration options determine the driver used to determine and
|
||||||
| is started. However, feel free to register as many as you wish as
|
| manage Laravel's "maintenance mode" status. The "cache" driver will
|
||||||
| the aliases are "lazy" loaded so they don't hinder performance.
|
| allow maintenance mode to be controlled across multiple machines.
|
||||||
|
|
|
||||||
|
| Supported drivers: "file", "cache"
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'aliases' => [
|
'maintenance' => [
|
||||||
|
'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
|
||||||
'App' => Illuminate\Support\Facades\App::class,
|
'store' => env('APP_MAINTENANCE_STORE', 'database'),
|
||||||
'Arr' => Illuminate\Support\Arr::class,
|
|
||||||
'Artisan' => Illuminate\Support\Facades\Artisan::class,
|
|
||||||
'Asset' => Orchestra\Support\Facades\Asset::class,
|
|
||||||
'Auth' => Illuminate\Support\Facades\Auth::class,
|
|
||||||
'Blade' => Illuminate\Support\Facades\Blade::class,
|
|
||||||
'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
|
|
||||||
'Bus' => Illuminate\Support\Facades\Bus::class,
|
|
||||||
'Cache' => Illuminate\Support\Facades\Cache::class,
|
|
||||||
'Config' => Illuminate\Support\Facades\Config::class,
|
|
||||||
'Cookie' => Illuminate\Support\Facades\Cookie::class,
|
|
||||||
'Crypt' => Illuminate\Support\Facades\Crypt::class,
|
|
||||||
'DB' => Illuminate\Support\Facades\DB::class,
|
|
||||||
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
|
|
||||||
'Event' => Illuminate\Support\Facades\Event::class,
|
|
||||||
'File' => Illuminate\Support\Facades\File::class,
|
|
||||||
'Gate' => Illuminate\Support\Facades\Gate::class,
|
|
||||||
'Hash' => Illuminate\Support\Facades\Hash::class,
|
|
||||||
'Lang' => Illuminate\Support\Facades\Lang::class,
|
|
||||||
'Log' => Illuminate\Support\Facades\Log::class,
|
|
||||||
'Mail' => Illuminate\Support\Facades\Mail::class,
|
|
||||||
'Notification' => Illuminate\Support\Facades\Notification::class,
|
|
||||||
'Password' => Illuminate\Support\Facades\Password::class,
|
|
||||||
'Queue' => Illuminate\Support\Facades\Queue::class,
|
|
||||||
'Redirect' => Illuminate\Support\Facades\Redirect::class,
|
|
||||||
'Redis' => Illuminate\Support\Facades\Redis::class,
|
|
||||||
'Request' => Illuminate\Support\Facades\Request::class,
|
|
||||||
'Response' => Illuminate\Support\Facades\Response::class,
|
|
||||||
'Route' => Illuminate\Support\Facades\Route::class,
|
|
||||||
'Schema' => Illuminate\Support\Facades\Schema::class,
|
|
||||||
'Session' => Illuminate\Support\Facades\Session::class,
|
|
||||||
'Storage' => Illuminate\Support\Facades\Storage::class,
|
|
||||||
'Str' => Illuminate\Support\Str::class,
|
|
||||||
'URL' => Illuminate\Support\Facades\URL::class,
|
|
||||||
'Validator' => Illuminate\Support\Facades\Validator::class,
|
|
||||||
'View' => Illuminate\Support\Facades\View::class,
|
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
211
config/auth.php
211
config/auth.php
@ -2,115 +2,128 @@
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Authentication Defaults
|
| Authentication Defaults
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| This option controls the default authentication "guard" and password
|
| This option defines the default authentication "guard" and password
|
||||||
| reset options for your application. You may change these defaults
|
| reset "broker" for your application. You may change these values
|
||||||
| as required, but they're a perfect start for most applications.
|
| as required, but they're a perfect start for most applications.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'defaults' => [
|
'defaults' => [
|
||||||
'guard' => 'web',
|
'guard' => env('AUTH_GUARD', 'web'),
|
||||||
'passwords' => 'users',
|
'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Authentication Guards
|
| Authentication Guards
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| Next, you may define every authentication guard for your application.
|
| Next, you may define every authentication guard for your application.
|
||||||
| Of course, a great default configuration has been defined for you
|
| Of course, a great default configuration has been defined for you
|
||||||
| here which uses session storage and the Eloquent user provider.
|
| which utilizes session storage plus the Eloquent user provider.
|
||||||
|
|
|
|
||||||
| All authentication drivers have a user provider. This defines how the
|
| All authentication guards have a user provider, which defines how the
|
||||||
| users are actually retrieved out of your database or other storage
|
| users are actually retrieved out of your database or other storage
|
||||||
| mechanisms used by this application to persist your user's data.
|
| system used by the application. Typically, Eloquent is utilized.
|
||||||
|
|
|
|
||||||
| Supported: "session", "token"
|
| Supported: "session"
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'guards' => [
|
'guards' => [
|
||||||
'web' => [
|
'web' => [
|
||||||
'driver' => 'session',
|
'driver' => 'session',
|
||||||
'provider' => 'users',
|
'provider' => 'users',
|
||||||
],
|
],
|
||||||
|
|
||||||
'api' => [
|
'api' => [
|
||||||
'driver' => 'token',
|
'driver' => 'passport',
|
||||||
'provider' => 'users',
|
'provider' => 'users',
|
||||||
'hash' => false,
|
],
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
],
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| User Providers
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| All authentication drivers have a user provider. This defines how the
|
|
||||||
| users are actually retrieved out of your database or other storage
|
|
||||||
| mechanisms used by this application to persist your user's data.
|
|
||||||
|
|
|
||||||
| If you have multiple user tables or models you may configure multiple
|
|
||||||
| sources which represent each model / table. These sources may then
|
|
||||||
| be assigned to any extra authentication guards you have defined.
|
|
||||||
|
|
|
||||||
| Supported: "database", "eloquent"
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'providers' => [
|
/*
|
||||||
'users' => [
|
|--------------------------------------------------------------------------
|
||||||
'driver' => 'eloquent',
|
| User Providers
|
||||||
'model' => App\User::class,
|
|--------------------------------------------------------------------------
|
||||||
],
|
|
|
||||||
|
| All authentication guards have a user provider, which defines how the
|
||||||
|
| users are actually retrieved out of your database or other storage
|
||||||
|
| system used by the application. Typically, Eloquent is utilized.
|
||||||
|
|
|
||||||
|
| If you have multiple user tables or models you may configure multiple
|
||||||
|
| providers to represent the model / table. These providers may then
|
||||||
|
| be assigned to any extra authentication guards you have defined.
|
||||||
|
|
|
||||||
|
| Supported: "database", "eloquent"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
// 'users' => [
|
'providers' => [
|
||||||
// 'driver' => 'database',
|
'users' => [
|
||||||
// 'table' => 'users',
|
'driver' => 'eloquent',
|
||||||
// ],
|
'model' => env('AUTH_MODEL', App\Models\User::class),
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
// 'users' => [
|
||||||
|--------------------------------------------------------------------------
|
// 'driver' => 'database',
|
||||||
| Resetting Passwords
|
// 'table' => 'users',
|
||||||
|--------------------------------------------------------------------------
|
// ],
|
||||||
|
|
],
|
||||||
| You may specify multiple password reset configurations if you have more
|
|
||||||
| than one user table or model in the application and you want to have
|
|
||||||
| separate password reset settings based on the specific user types.
|
|
||||||
|
|
|
||||||
| The expire time is the number of minutes that the reset token should be
|
|
||||||
| considered valid. This security feature keeps tokens short-lived so
|
|
||||||
| they have less time to be guessed. You may change this as needed.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'passwords' => [
|
/*
|
||||||
'users' => [
|
|--------------------------------------------------------------------------
|
||||||
'provider' => 'users',
|
| Resetting Passwords
|
||||||
'table' => 'password_resets',
|
|--------------------------------------------------------------------------
|
||||||
'expire' => 60,
|
|
|
||||||
],
|
| These configuration options specify the behavior of Laravel's password
|
||||||
],
|
| reset functionality, including the table utilized for token storage
|
||||||
|
| and the user provider that is invoked to actually retrieve users.
|
||||||
|
|
|
||||||
|
| The expiry time is the number of minutes that each reset token will be
|
||||||
|
| considered valid. This security feature keeps tokens short-lived so
|
||||||
|
| they have less time to be guessed. You may change this as needed.
|
||||||
|
|
|
||||||
|
| The throttle setting is the number of seconds a user must wait before
|
||||||
|
| generating more password reset tokens. This prevents the user from
|
||||||
|
| quickly generating a very large amount of password reset tokens.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
/*
|
'passwords' => [
|
||||||
|--------------------------------------------------------------------------
|
'users' => [
|
||||||
| Password Confirmation Timeout
|
'provider' => 'users',
|
||||||
|--------------------------------------------------------------------------
|
'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
|
||||||
|
|
'expire' => 60,
|
||||||
| Here you may define the amount of seconds before a password confirmation
|
'throttle' => 60,
|
||||||
| times out and the user is prompted to re-enter their password via the
|
],
|
||||||
| confirmation screen. By default, the timeout lasts for three hours.
|
],
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'password_timeout' => 10800,
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Password Confirmation Timeout
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may define the amount of seconds before a password confirmation
|
||||||
|
| window expires and users are asked to re-enter their password via the
|
||||||
|
| confirmation screen. By default, the timeout lasts for three hours.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
|
||||||
|
|
||||||
|
'social' => [
|
||||||
|
'google' => [
|
||||||
|
'name' => 'Google',
|
||||||
|
'id' => 'google',
|
||||||
|
'class' => 'btn-danger',
|
||||||
|
'icon' => 'fab fa-google',
|
||||||
|
],
|
||||||
|
],
|
||||||
];
|
];
|
@ -1,59 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Default Broadcaster
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This option controls the default broadcaster that will be used by the
|
|
||||||
| framework when an event needs to be broadcast. You may set this to
|
|
||||||
| any of the connections defined in the "connections" array below.
|
|
||||||
|
|
|
||||||
| Supported: "pusher", "redis", "log", "null"
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'default' => env('BROADCAST_DRIVER', 'null'),
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Broadcast Connections
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Here you may define all of the broadcast connections that will be used
|
|
||||||
| to broadcast events to other systems or over websockets. Samples of
|
|
||||||
| each available type of connection are provided inside this array.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'connections' => [
|
|
||||||
|
|
||||||
'pusher' => [
|
|
||||||
'driver' => 'pusher',
|
|
||||||
'key' => env('PUSHER_APP_KEY'),
|
|
||||||
'secret' => env('PUSHER_APP_SECRET'),
|
|
||||||
'app_id' => env('PUSHER_APP_ID'),
|
|
||||||
'options' => [
|
|
||||||
'cluster' => env('PUSHER_APP_CLUSTER'),
|
|
||||||
'useTLS' => true,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
'redis' => [
|
|
||||||
'driver' => 'redis',
|
|
||||||
'connection' => 'default',
|
|
||||||
],
|
|
||||||
|
|
||||||
'log' => [
|
|
||||||
'driver' => 'log',
|
|
||||||
],
|
|
||||||
|
|
||||||
'null' => [
|
|
||||||
'driver' => 'null',
|
|
||||||
],
|
|
||||||
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
@ -9,16 +9,13 @@ return [
|
|||||||
| Default Cache Store
|
| Default Cache Store
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| This option controls the default cache connection that gets used while
|
| This option controls the default cache store that will be used by the
|
||||||
| using this caching library. This connection is used when another is
|
| framework. This connection is utilized if another isn't explicitly
|
||||||
| not explicitly specified when executing a given caching function.
|
| specified when running a cache operation inside the application.
|
||||||
|
|
|
||||||
| Supported: "apc", "array", "database", "file",
|
|
||||||
| "memcached", "redis", "dynamodb"
|
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'default' => env('CACHE_DRIVER', 'file'),
|
'default' => env('CACHE_STORE', 'database'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@ -29,27 +26,30 @@ return [
|
|||||||
| well as their drivers. You may even define multiple stores for the
|
| well as their drivers. You may even define multiple stores for the
|
||||||
| same cache driver to group types of items stored in your caches.
|
| same cache driver to group types of items stored in your caches.
|
||||||
|
|
|
|
||||||
|
| Supported drivers: "array", "database", "file", "memcached",
|
||||||
|
| "redis", "dynamodb", "octane", "null"
|
||||||
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'stores' => [
|
'stores' => [
|
||||||
|
|
||||||
'apc' => [
|
|
||||||
'driver' => 'apc',
|
|
||||||
],
|
|
||||||
|
|
||||||
'array' => [
|
'array' => [
|
||||||
'driver' => 'array',
|
'driver' => 'array',
|
||||||
|
'serialize' => false,
|
||||||
],
|
],
|
||||||
|
|
||||||
'database' => [
|
'database' => [
|
||||||
'driver' => 'database',
|
'driver' => 'database',
|
||||||
'table' => 'cache',
|
'connection' => env('DB_CACHE_CONNECTION'),
|
||||||
'connection' => null,
|
'table' => env('DB_CACHE_TABLE', 'cache'),
|
||||||
|
'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
|
||||||
|
'lock_table' => env('DB_CACHE_LOCK_TABLE'),
|
||||||
],
|
],
|
||||||
|
|
||||||
'file' => [
|
'file' => [
|
||||||
'driver' => 'file',
|
'driver' => 'file',
|
||||||
'path' => storage_path('framework/cache/data'),
|
'path' => storage_path('framework/cache/data'),
|
||||||
|
'lock_path' => storage_path('framework/cache/data'),
|
||||||
],
|
],
|
||||||
|
|
||||||
'memcached' => [
|
'memcached' => [
|
||||||
@ -73,7 +73,8 @@ return [
|
|||||||
|
|
||||||
'redis' => [
|
'redis' => [
|
||||||
'driver' => 'redis',
|
'driver' => 'redis',
|
||||||
'connection' => 'cache',
|
'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
|
||||||
|
'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
|
||||||
],
|
],
|
||||||
|
|
||||||
'dynamodb' => [
|
'dynamodb' => [
|
||||||
@ -85,6 +86,10 @@ return [
|
|||||||
'endpoint' => env('DYNAMODB_ENDPOINT'),
|
'endpoint' => env('DYNAMODB_ENDPOINT'),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'octane' => [
|
||||||
|
'driver' => 'octane',
|
||||||
|
],
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -92,12 +97,12 @@ return [
|
|||||||
| Cache Key Prefix
|
| Cache Key Prefix
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| When utilizing a RAM based store such as APC or Memcached, there might
|
| When utilizing the APC, database, memcached, Redis, and DynamoDB cache
|
||||||
| be other applications utilizing the same cache. So, we'll specify a
|
| stores, there might be other applications using the same cache. For
|
||||||
| value to get prefixed to all our keys so we can avoid collisions.
|
| that reason, you may prefix every cache key to avoid collisions.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache'),
|
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
|
||||||
|
|
||||||
];
|
];
|
||||||
|
@ -10,26 +10,22 @@ return [
|
|||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| Here you may specify which of the database connections below you wish
|
| Here you may specify which of the database connections below you wish
|
||||||
| to use as your default connection for all database work. Of course
|
| to use as your default connection for database operations. This is
|
||||||
| you may use many connections at once using the Database library.
|
| the connection which will be utilized unless another connection
|
||||||
|
| is explicitly specified when you execute a query / statement.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'default' => env('DB_CONNECTION', 'mysql'),
|
'default' => env('DB_CONNECTION', 'sqlite'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Database Connections
|
| Database Connections
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| Here are each of the database connections setup for your application.
|
| Below are all of the database connections defined for your application.
|
||||||
| Of course, examples of configuring each database platform that is
|
| An example configuration is provided for each database system which
|
||||||
| supported by Laravel is shown below to make development simple.
|
| is supported by Laravel. You're free to add / remove connections.
|
||||||
|
|
|
||||||
|
|
|
||||||
| All database work in Laravel is done through the PHP PDO facilities
|
|
||||||
| so make sure you have the driver for your particular database of
|
|
||||||
| choice installed on your machine before you begin development.
|
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -37,7 +33,7 @@ return [
|
|||||||
|
|
||||||
'sqlite' => [
|
'sqlite' => [
|
||||||
'driver' => 'sqlite',
|
'driver' => 'sqlite',
|
||||||
'url' => env('DATABASE_URL'),
|
'url' => env('DB_URL'),
|
||||||
'database' => env('DB_DATABASE', database_path('database.sqlite')),
|
'database' => env('DB_DATABASE', database_path('database.sqlite')),
|
||||||
'prefix' => '',
|
'prefix' => '',
|
||||||
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
|
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
|
||||||
@ -45,15 +41,35 @@ return [
|
|||||||
|
|
||||||
'mysql' => [
|
'mysql' => [
|
||||||
'driver' => 'mysql',
|
'driver' => 'mysql',
|
||||||
'url' => env('DATABASE_URL'),
|
'url' => env('DB_URL'),
|
||||||
'host' => env('DB_HOST', '127.0.0.1'),
|
'host' => env('DB_HOST', '127.0.0.1'),
|
||||||
'port' => env('DB_PORT', '3306'),
|
'port' => env('DB_PORT', '3306'),
|
||||||
'database' => env('DB_DATABASE', 'forge'),
|
'database' => env('DB_DATABASE', 'laravel'),
|
||||||
'username' => env('DB_USERNAME', 'forge'),
|
'username' => env('DB_USERNAME', 'root'),
|
||||||
'password' => env('DB_PASSWORD', ''),
|
'password' => env('DB_PASSWORD', ''),
|
||||||
'unix_socket' => env('DB_SOCKET', ''),
|
'unix_socket' => env('DB_SOCKET', ''),
|
||||||
'charset' => 'utf8mb4',
|
'charset' => env('DB_CHARSET', 'utf8mb4'),
|
||||||
'collation' => 'utf8mb4_unicode_ci',
|
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
|
||||||
|
'prefix' => '',
|
||||||
|
'prefix_indexes' => true,
|
||||||
|
'strict' => true,
|
||||||
|
'engine' => null,
|
||||||
|
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||||
|
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
||||||
|
]) : [],
|
||||||
|
],
|
||||||
|
|
||||||
|
'mariadb' => [
|
||||||
|
'driver' => 'mariadb',
|
||||||
|
'url' => env('DB_URL'),
|
||||||
|
'host' => env('DB_HOST', '127.0.0.1'),
|
||||||
|
'port' => env('DB_PORT', '3306'),
|
||||||
|
'database' => env('DB_DATABASE', 'laravel'),
|
||||||
|
'username' => env('DB_USERNAME', 'root'),
|
||||||
|
'password' => env('DB_PASSWORD', ''),
|
||||||
|
'unix_socket' => env('DB_SOCKET', ''),
|
||||||
|
'charset' => env('DB_CHARSET', 'utf8mb4'),
|
||||||
|
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
|
||||||
'prefix' => '',
|
'prefix' => '',
|
||||||
'prefix_indexes' => true,
|
'prefix_indexes' => true,
|
||||||
'strict' => true,
|
'strict' => true,
|
||||||
@ -65,30 +81,32 @@ return [
|
|||||||
|
|
||||||
'pgsql' => [
|
'pgsql' => [
|
||||||
'driver' => 'pgsql',
|
'driver' => 'pgsql',
|
||||||
'url' => env('DATABASE_URL'),
|
'url' => env('DB_URL'),
|
||||||
'host' => env('DB_HOST', '127.0.0.1'),
|
'host' => env('DB_HOST', '127.0.0.1'),
|
||||||
'port' => env('DB_PORT', '5432'),
|
'port' => env('DB_PORT', '5432'),
|
||||||
'database' => env('DB_DATABASE', 'forge'),
|
'database' => env('DB_DATABASE', 'laravel'),
|
||||||
'username' => env('DB_USERNAME', 'forge'),
|
'username' => env('DB_USERNAME', 'root'),
|
||||||
'password' => env('DB_PASSWORD', ''),
|
'password' => env('DB_PASSWORD', ''),
|
||||||
'charset' => 'utf8',
|
'charset' => env('DB_CHARSET', 'utf8'),
|
||||||
'prefix' => '',
|
'prefix' => '',
|
||||||
'prefix_indexes' => true,
|
'prefix_indexes' => true,
|
||||||
'schema' => 'public',
|
'search_path' => env('DB_SCHEMA', 'public'),
|
||||||
'sslmode' => 'prefer',
|
'sslmode' => 'prefer',
|
||||||
],
|
],
|
||||||
|
|
||||||
'sqlsrv' => [
|
'sqlsrv' => [
|
||||||
'driver' => 'sqlsrv',
|
'driver' => 'sqlsrv',
|
||||||
'url' => env('DATABASE_URL'),
|
'url' => env('DB_URL'),
|
||||||
'host' => env('DB_HOST', 'localhost'),
|
'host' => env('DB_HOST', 'localhost'),
|
||||||
'port' => env('DB_PORT', '1433'),
|
'port' => env('DB_PORT', '1433'),
|
||||||
'database' => env('DB_DATABASE', 'forge'),
|
'database' => env('DB_DATABASE', 'laravel'),
|
||||||
'username' => env('DB_USERNAME', 'forge'),
|
'username' => env('DB_USERNAME', 'root'),
|
||||||
'password' => env('DB_PASSWORD', ''),
|
'password' => env('DB_PASSWORD', ''),
|
||||||
'charset' => 'utf8',
|
'charset' => env('DB_CHARSET', 'utf8'),
|
||||||
'prefix' => '',
|
'prefix' => '',
|
||||||
'prefix_indexes' => true,
|
'prefix_indexes' => true,
|
||||||
|
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
|
||||||
|
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
|
||||||
],
|
],
|
||||||
|
|
||||||
],
|
],
|
||||||
@ -100,11 +118,14 @@ return [
|
|||||||
|
|
|
|
||||||
| This table keeps track of all the migrations that have already run for
|
| This table keeps track of all the migrations that have already run for
|
||||||
| your application. Using this information, we can determine which of
|
| your application. Using this information, we can determine which of
|
||||||
| the migrations on disk haven't actually been run in the database.
|
| the migrations on disk haven't actually been run on the database.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'migrations' => 'migrations',
|
'migrations' => [
|
||||||
|
'table' => 'migrations',
|
||||||
|
'update_date_on_publish' => true,
|
||||||
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@ -113,7 +134,7 @@ return [
|
|||||||
|
|
|
|
||||||
| Redis is an open source, fast, and advanced key-value store that also
|
| Redis is an open source, fast, and advanced key-value store that also
|
||||||
| provides a richer body of commands than a typical key-value system
|
| provides a richer body of commands than a typical key-value system
|
||||||
| such as APC or Memcached. Laravel makes it easy to dig right in.
|
| such as Memcached. You may define your connection settings here.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -129,17 +150,19 @@ return [
|
|||||||
'default' => [
|
'default' => [
|
||||||
'url' => env('REDIS_URL'),
|
'url' => env('REDIS_URL'),
|
||||||
'host' => env('REDIS_HOST', '127.0.0.1'),
|
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||||
'password' => env('REDIS_PASSWORD', null),
|
'username' => env('REDIS_USERNAME'),
|
||||||
'port' => env('REDIS_PORT', 6379),
|
'password' => env('REDIS_PASSWORD'),
|
||||||
'database' => env('REDIS_DB', 0),
|
'port' => env('REDIS_PORT', '6379'),
|
||||||
|
'database' => env('REDIS_DB', '0'),
|
||||||
],
|
],
|
||||||
|
|
||||||
'cache' => [
|
'cache' => [
|
||||||
'url' => env('REDIS_URL'),
|
'url' => env('REDIS_URL'),
|
||||||
'host' => env('REDIS_HOST', '127.0.0.1'),
|
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||||
'password' => env('REDIS_PASSWORD', null),
|
'username' => env('REDIS_USERNAME'),
|
||||||
'port' => env('REDIS_PORT', 6379),
|
'password' => env('REDIS_PASSWORD'),
|
||||||
'database' => env('REDIS_CACHE_DB', 1),
|
'port' => env('REDIS_PORT', '6379'),
|
||||||
|
'database' => env('REDIS_CACHE_DB', '1'),
|
||||||
],
|
],
|
||||||
|
|
||||||
],
|
],
|
||||||
|
@ -2,68 +2,82 @@
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Default Filesystem Disk
|
| Default Filesystem Disk
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| Here you may specify the default filesystem disk that should be used
|
| Here you may specify the default filesystem disk that should be used
|
||||||
| by the framework. The "local" disk, as well as a variety of cloud
|
| by the framework. The "local" disk, as well as a variety of cloud
|
||||||
| based disks are available to your application. Just store away!
|
| based disks are available to your application for file storage.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'default' => env('FILESYSTEM_DRIVER', 'local'),
|
'default' => env('FILESYSTEM_DISK', 'local'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Default Cloud Filesystem Disk
|
| Filesystem Disks
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| Many applications store files both locally and in the cloud. For this
|
| Below you may configure as many filesystem disks as necessary, and you
|
||||||
| reason, you may specify a default "cloud" driver here. This driver
|
| may even configure multiple disks for the same driver. Examples for
|
||||||
| will be bound as the Cloud disk implementation in the container.
|
| most supported storage drivers are configured here for reference.
|
||||||
|
|
|
|
||||||
*/
|
| Supported drivers: "local", "ftp", "sftp", "s3"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
'cloud' => env('FILESYSTEM_CLOUD', 's3'),
|
'disks' => [
|
||||||
|
|
||||||
/*
|
'local' => [
|
||||||
|--------------------------------------------------------------------------
|
'driver' => 'local',
|
||||||
| Filesystem Disks
|
'root' => storage_path('app'),
|
||||||
|--------------------------------------------------------------------------
|
'throw' => false,
|
||||||
|
|
],
|
||||||
| Here you may configure as many filesystem "disks" as you wish, and you
|
|
||||||
| may even configure multiple disks of the same driver. Defaults have
|
|
||||||
| been setup for each driver as an example of the required options.
|
|
||||||
|
|
|
||||||
| Supported Drivers: "local", "ftp", "sftp", "s3"
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'disks' => [
|
'nas' => [
|
||||||
|
'driver' => 'local',
|
||||||
|
'root' => storage_path('nas'),
|
||||||
|
'throw' => false,
|
||||||
|
'visibility' => 'public',
|
||||||
|
],
|
||||||
|
|
||||||
'local' => [
|
'public' => [
|
||||||
'driver' => 'local',
|
'driver' => 'local',
|
||||||
'root' => storage_path('app'),
|
'root' => storage_path('app/public'),
|
||||||
],
|
'url' => env('APP_URL').'/storage',
|
||||||
|
'visibility' => 'public',
|
||||||
|
'throw' => false,
|
||||||
|
],
|
||||||
|
|
||||||
'public' => [
|
's3' => [
|
||||||
'driver' => 'local',
|
'driver' => 's3',
|
||||||
'root' => storage_path('app/public'),
|
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||||
'url' => env('APP_URL').'/storage',
|
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||||
'visibility' => 'public',
|
'region' => env('AWS_DEFAULT_REGION'),
|
||||||
],
|
'bucket' => env('AWS_BUCKET'),
|
||||||
|
'url' => env('AWS_URL'),
|
||||||
|
'endpoint' => env('AWS_ENDPOINT'),
|
||||||
|
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
|
||||||
|
'throw' => false,
|
||||||
|
],
|
||||||
|
|
||||||
's3' => [
|
],
|
||||||
'driver' => 's3',
|
|
||||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
|
||||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
|
||||||
'region' => env('AWS_DEFAULT_REGION'),
|
|
||||||
'bucket' => env('AWS_BUCKET'),
|
|
||||||
'url' => env('AWS_URL'),
|
|
||||||
],
|
|
||||||
|
|
||||||
],
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Symbolic Links
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may configure the symbolic links that will be created when the
|
||||||
|
| `storage:link` Artisan command is executed. The array keys should be
|
||||||
|
| the locations of the links and the values should be their targets.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'links' => [
|
||||||
|
public_path('storage') => storage_path('app/public'),
|
||||||
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
@ -3,98 +3,138 @@
|
|||||||
use Monolog\Handler\NullHandler;
|
use Monolog\Handler\NullHandler;
|
||||||
use Monolog\Handler\StreamHandler;
|
use Monolog\Handler\StreamHandler;
|
||||||
use Monolog\Handler\SyslogUdpHandler;
|
use Monolog\Handler\SyslogUdpHandler;
|
||||||
|
use Monolog\Processor\PsrLogMessageProcessor;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Default Log Channel
|
| Default Log Channel
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| This option defines the default log channel that gets used when writing
|
| This option defines the default log channel that is utilized to write
|
||||||
| messages to the logs. The name specified in this option should match
|
| messages to your logs. The value provided here should match one of
|
||||||
| one of the channels defined in the "channels" configuration array.
|
| the channels present in the list of "channels" configured below.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'default' => env('LOG_CHANNEL', 'stack'),
|
'default' => env('LOG_CHANNEL', 'stack'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Log Channels
|
| Deprecations Log Channel
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| Here you may configure the log channels for your application. Out of
|
| This option controls the log channel that should be used to log warnings
|
||||||
| the box, Laravel uses the Monolog PHP logging library. This gives
|
| regarding deprecated PHP and library features. This allows you to get
|
||||||
| you a variety of powerful log handlers / formatters to utilize.
|
| your application ready for upcoming major versions of dependencies.
|
||||||
|
|
|
|
||||||
| Available Drivers: "single", "daily", "slack", "syslog",
|
*/
|
||||||
| "errorlog", "monolog",
|
|
||||||
| "custom", "stack"
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'channels' => [
|
'deprecations' => [
|
||||||
'stack' => [
|
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
|
||||||
'driver' => 'stack',
|
'trace' => env('LOG_DEPRECATIONS_TRACE', false),
|
||||||
'channels' => ['daily'],
|
],
|
||||||
'ignore_exceptions' => false,
|
|
||||||
],
|
|
||||||
|
|
||||||
'single' => [
|
/*
|
||||||
'driver' => 'single',
|
|--------------------------------------------------------------------------
|
||||||
'path' => storage_path('logs/laravel.log'),
|
| Log Channels
|
||||||
'level' => 'debug',
|
|--------------------------------------------------------------------------
|
||||||
],
|
|
|
||||||
|
| Here you may configure the log channels for your application. Laravel
|
||||||
|
| utilizes the Monolog PHP logging library, which includes a variety
|
||||||
|
| of powerful log handlers and formatters that you're free to use.
|
||||||
|
|
|
||||||
|
| Available drivers: "single", "daily", "slack", "syslog",
|
||||||
|
| "errorlog", "monolog", "custom", "stack"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
'daily' => [
|
'channels' => [
|
||||||
'driver' => 'daily',
|
|
||||||
'path' => storage_path('logs/laravel.log'),
|
|
||||||
'level' => 'debug',
|
|
||||||
'days' => 14,
|
|
||||||
],
|
|
||||||
|
|
||||||
'slack' => [
|
'stack' => [
|
||||||
'driver' => 'slack',
|
'driver' => 'stack',
|
||||||
'url' => env('LOG_SLACK_WEBHOOK_URL'),
|
'channels' => explode(',', env('LOG_STACK', 'daily')),
|
||||||
'username' => 'Laravel Log',
|
'ignore_exceptions' => false,
|
||||||
'emoji' => ':boom:',
|
],
|
||||||
'level' => 'critical',
|
|
||||||
],
|
|
||||||
|
|
||||||
'papertrail' => [
|
'single' => [
|
||||||
'driver' => 'monolog',
|
'driver' => 'single',
|
||||||
'level' => 'debug',
|
'path' => storage_path('logs/laravel.log'),
|
||||||
'handler' => SyslogUdpHandler::class,
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
'handler_with' => [
|
'replace_placeholders' => true,
|
||||||
'host' => env('PAPERTRAIL_URL'),
|
],
|
||||||
'port' => env('PAPERTRAIL_PORT'),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
'stderr' => [
|
'daily' => [
|
||||||
'driver' => 'monolog',
|
'driver' => 'daily',
|
||||||
'handler' => StreamHandler::class,
|
'path' => storage_path('logs/laravel.log'),
|
||||||
'formatter' => env('LOG_STDERR_FORMATTER'),
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
'with' => [
|
'days' => env('LOG_DAILY_DAYS', 14),
|
||||||
'stream' => 'php://stderr',
|
'replace_placeholders' => true,
|
||||||
],
|
],
|
||||||
],
|
|
||||||
|
|
||||||
'syslog' => [
|
'webhook' => [
|
||||||
'driver' => 'syslog',
|
'driver' => 'daily',
|
||||||
'level' => 'debug',
|
'path' => storage_path('logs/webhook.log'),
|
||||||
],
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
|
'days' => env('LOG_DAILY_DAYS', 14),
|
||||||
|
'replace_placeholders' => true,
|
||||||
|
],
|
||||||
|
|
||||||
'errorlog' => [
|
'slack' => [
|
||||||
'driver' => 'errorlog',
|
'driver' => 'slack',
|
||||||
'level' => 'debug',
|
'url' => env('LOG_SLACK_WEBHOOK_URL'),
|
||||||
],
|
'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
|
||||||
|
'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
|
||||||
|
'level' => env('LOG_LEVEL', 'critical'),
|
||||||
|
'replace_placeholders' => true,
|
||||||
|
],
|
||||||
|
|
||||||
'null' => [
|
'papertrail' => [
|
||||||
'driver' => 'monolog',
|
'driver' => 'monolog',
|
||||||
'handler' => NullHandler::class,
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
],
|
'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
|
||||||
],
|
'handler_with' => [
|
||||||
|
'host' => env('PAPERTRAIL_URL'),
|
||||||
|
'port' => env('PAPERTRAIL_PORT'),
|
||||||
|
'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
|
||||||
|
],
|
||||||
|
'processors' => [PsrLogMessageProcessor::class],
|
||||||
|
],
|
||||||
|
|
||||||
|
'stderr' => [
|
||||||
|
'driver' => 'monolog',
|
||||||
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
|
'handler' => StreamHandler::class,
|
||||||
|
'formatter' => env('LOG_STDERR_FORMATTER'),
|
||||||
|
'with' => [
|
||||||
|
'stream' => 'php://stderr',
|
||||||
|
],
|
||||||
|
'processors' => [PsrLogMessageProcessor::class],
|
||||||
|
],
|
||||||
|
|
||||||
|
'syslog' => [
|
||||||
|
'driver' => 'syslog',
|
||||||
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
|
'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
|
||||||
|
'replace_placeholders' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
'errorlog' => [
|
||||||
|
'driver' => 'errorlog',
|
||||||
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
|
'replace_placeholders' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
'null' => [
|
||||||
|
'driver' => 'monolog',
|
||||||
|
'handler' => NullHandler::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
'emergency' => [
|
||||||
|
'path' => storage_path('logs/laravel.log'),
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
];
|
];
|
181
config/mail.php
181
config/mail.php
@ -4,54 +4,108 @@ return [
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Mail Driver
|
| Default Mailer
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| Laravel supports both SMTP and PHP's "mail" function as drivers for the
|
| This option controls the default mailer that is used to send all email
|
||||||
| sending of e-mail. You may specify which one you're using throughout
|
| messages unless another mailer is explicitly specified when sending
|
||||||
| your application here. By default, Laravel is setup for SMTP mail.
|
| the message. All additional mailers can be configured within the
|
||||||
|
|
| "mailers" array. Examples of each type of mailer are provided.
|
||||||
| Supported: "smtp", "sendmail", "mailgun", "ses",
|
|
||||||
| "postmark", "log", "array"
|
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'driver' => env('MAIL_DRIVER', 'smtp'),
|
'default' => env('MAIL_MAILER', 'log'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| SMTP Host Address
|
| Mailer Configurations
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| Here you may provide the host address of the SMTP server used by your
|
| Here you may configure all of the mailers used by your application plus
|
||||||
| applications. A default option is provided that is compatible with
|
| their respective settings. Several examples have been configured for
|
||||||
| the Mailgun mail service which will provide reliable deliveries.
|
| you and you are free to add your own as your application requires.
|
||||||
|
|
|
||||||
|
| Laravel supports a variety of mail "transport" drivers that can be used
|
||||||
|
| when delivering an email. You may specify which one you're using for
|
||||||
|
| your mailers below. You may also add additional mailers if needed.
|
||||||
|
|
|
||||||
|
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
|
||||||
|
| "postmark", "resend", "log", "array",
|
||||||
|
| "failover", "roundrobin"
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
|
'mailers' => [
|
||||||
|
|
||||||
/*
|
'smtp' => [
|
||||||
|--------------------------------------------------------------------------
|
'transport' => 'smtp',
|
||||||
| SMTP Host Port
|
'url' => env('MAIL_URL'),
|
||||||
|--------------------------------------------------------------------------
|
'host' => env('MAIL_HOST', '127.0.0.1'),
|
||||||
|
|
'port' => env('MAIL_PORT', 2525),
|
||||||
| This is the SMTP port used by your application to deliver e-mails to
|
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
|
||||||
| users of the application. Like the host we have set this value to
|
'username' => env('MAIL_USERNAME'),
|
||||||
| stay compatible with the Mailgun e-mail application by default.
|
'password' => env('MAIL_PASSWORD'),
|
||||||
|
|
'timeout' => null,
|
||||||
*/
|
'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url(env('APP_URL', 'http://localhost'), PHP_URL_HOST)),
|
||||||
|
'verify_peer' => false,
|
||||||
|
],
|
||||||
|
|
||||||
'port' => env('MAIL_PORT', 587),
|
'ses' => [
|
||||||
|
'transport' => 'ses',
|
||||||
|
],
|
||||||
|
|
||||||
|
'postmark' => [
|
||||||
|
'transport' => 'postmark',
|
||||||
|
// 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
|
||||||
|
// 'client' => [
|
||||||
|
// 'timeout' => 5,
|
||||||
|
// ],
|
||||||
|
],
|
||||||
|
|
||||||
|
'resend' => [
|
||||||
|
'transport' => 'resend',
|
||||||
|
],
|
||||||
|
|
||||||
|
'sendmail' => [
|
||||||
|
'transport' => 'sendmail',
|
||||||
|
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'log' => [
|
||||||
|
'transport' => 'log',
|
||||||
|
'channel' => env('MAIL_LOG_CHANNEL'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'array' => [
|
||||||
|
'transport' => 'array',
|
||||||
|
],
|
||||||
|
|
||||||
|
'failover' => [
|
||||||
|
'transport' => 'failover',
|
||||||
|
'mailers' => [
|
||||||
|
'smtp',
|
||||||
|
'log',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'roundrobin' => [
|
||||||
|
'transport' => 'roundrobin',
|
||||||
|
'mailers' => [
|
||||||
|
'ses',
|
||||||
|
'postmark',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Global "From" Address
|
| Global "From" Address
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| You may wish for all e-mails sent by your application to be sent from
|
| You may wish for all emails sent by your application to be sent from
|
||||||
| the same address. Here, you may specify a name and address that is
|
| the same address. Here you may specify a name and address that is
|
||||||
| used globally for all e-mails that are sent by your application.
|
| used globally for all emails that are sent by your application.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -60,77 +114,4 @@ return [
|
|||||||
'name' => env('MAIL_FROM_NAME', 'Example'),
|
'name' => env('MAIL_FROM_NAME', 'Example'),
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| E-Mail Encryption Protocol
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Here you may specify the encryption protocol that should be used when
|
|
||||||
| the application send e-mail messages. A sensible default using the
|
|
||||||
| transport layer security protocol should provide great security.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| SMTP Server Username
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| If your SMTP server requires a username for authentication, you should
|
|
||||||
| set it here. This will get used to authenticate with your server on
|
|
||||||
| connection. You may also set the "password" value below this one.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'username' => env('MAIL_USERNAME'),
|
|
||||||
|
|
||||||
'password' => env('MAIL_PASSWORD'),
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Sendmail System Path
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| When using the "sendmail" driver to send e-mails, we will need to know
|
|
||||||
| the path to where Sendmail lives on this server. A default path has
|
|
||||||
| been provided here, which will work well on most of your systems.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'sendmail' => '/usr/sbin/sendmail -bs',
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Markdown Mail Settings
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| If you are using Markdown based email rendering, you may configure your
|
|
||||||
| theme and component paths here, allowing you to customize the design
|
|
||||||
| of the emails. Or, you may simply stick with the Laravel defaults!
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'markdown' => [
|
|
||||||
'theme' => 'default',
|
|
||||||
|
|
||||||
'paths' => [
|
|
||||||
resource_path('views/vendor/mail'),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Log Channel
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| If you are using the "log" driver, you may specify the logging channel
|
|
||||||
| if you prefer to keep mail messages separate from other log entries
|
|
||||||
| for simpler reading. Otherwise, the default channel will be used.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'log_channel' => env('MAIL_LOG_CHANNEL'),
|
|
||||||
|
|
||||||
];
|
];
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'dir'=>'/photos',
|
'dir'=>'Photos',
|
||||||
'import'=>[
|
'import'=>[
|
||||||
'accepted'=>['jpg','jpeg','heic'],
|
'accepted'=>['jpg','jpeg','heic'],
|
||||||
],
|
],
|
||||||
|
@ -7,22 +7,22 @@ return [
|
|||||||
| Default Queue Connection Name
|
| Default Queue Connection Name
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| Laravel's queue API supports an assortment of back-ends via a single
|
| Laravel's queue supports a variety of backends via a single, unified
|
||||||
| API, giving you convenient access to each back-end using the same
|
| API, giving you convenient access to each backend using identical
|
||||||
| syntax for every one. Here you may define a default connection.
|
| syntax for each. The default queue connection is defined below.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'default' => env('QUEUE_CONNECTION', 'sync'),
|
'default' => env('QUEUE_CONNECTION', 'database'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Queue Connections
|
| Queue Connections
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| Here you may configure the connection information for each server that
|
| Here you may configure the connection options for every queue backend
|
||||||
| is used by your application. A default configuration has been added
|
| used by your application. An example configuration is provided for
|
||||||
| for each back-end shipped with Laravel. You are free to add more.
|
| each backend supported by Laravel. You're also free to add more.
|
||||||
|
|
|
|
||||||
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
|
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
|
||||||
|
|
|
|
||||||
@ -36,17 +36,20 @@ return [
|
|||||||
|
|
||||||
'database' => [
|
'database' => [
|
||||||
'driver' => 'database',
|
'driver' => 'database',
|
||||||
'table' => 'jobs',
|
'connection' => env('DB_QUEUE_CONNECTION'),
|
||||||
'queue' => 'default',
|
'table' => env('DB_QUEUE_TABLE', 'jobs'),
|
||||||
'retry_after' => 90,
|
'queue' => env('DB_QUEUE', 'default'),
|
||||||
|
'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90),
|
||||||
|
'after_commit' => false,
|
||||||
],
|
],
|
||||||
|
|
||||||
'beanstalkd' => [
|
'beanstalkd' => [
|
||||||
'driver' => 'beanstalkd',
|
'driver' => 'beanstalkd',
|
||||||
'host' => 'localhost',
|
'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'),
|
||||||
'queue' => 'default',
|
'queue' => env('BEANSTALKD_QUEUE', 'default'),
|
||||||
'retry_after' => 90,
|
'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90),
|
||||||
'block_for' => 0,
|
'block_for' => 0,
|
||||||
|
'after_commit' => false,
|
||||||
],
|
],
|
||||||
|
|
||||||
'sqs' => [
|
'sqs' => [
|
||||||
@ -54,34 +57,55 @@ return [
|
|||||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||||
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
|
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
|
||||||
'queue' => env('SQS_QUEUE', 'your-queue-name'),
|
'queue' => env('SQS_QUEUE', 'default'),
|
||||||
|
'suffix' => env('SQS_SUFFIX'),
|
||||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||||
|
'after_commit' => false,
|
||||||
],
|
],
|
||||||
|
|
||||||
'redis' => [
|
'redis' => [
|
||||||
'driver' => 'redis',
|
'driver' => 'redis',
|
||||||
'connection' => 'default',
|
'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
|
||||||
'queue' => env('REDIS_QUEUE', 'default'),
|
'queue' => env('REDIS_QUEUE', 'default'),
|
||||||
'retry_after' => 90,
|
'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90),
|
||||||
'block_for' => null,
|
'block_for' => null,
|
||||||
|
'after_commit' => false,
|
||||||
],
|
],
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Job Batching
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The following options configure the database and table that store job
|
||||||
|
| batching information. These options can be updated to any database
|
||||||
|
| connection and table which has been defined by your application.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'batching' => [
|
||||||
|
'database' => env('DB_CONNECTION', 'sqlite'),
|
||||||
|
'table' => 'job_batches',
|
||||||
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Failed Queue Jobs
|
| Failed Queue Jobs
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| These options configure the behavior of failed queue job logging so you
|
| These options configure the behavior of failed queue job logging so you
|
||||||
| can control which database and table are used to store the jobs that
|
| can control how and where failed jobs are stored. Laravel ships with
|
||||||
| have failed. You may change them to any database / table you wish.
|
| support for storing failed jobs in a simple file or in a database.
|
||||||
|
|
|
||||||
|
| Supported drivers: "database-uuids", "dynamodb", "file", "null"
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'failed' => [
|
'failed' => [
|
||||||
'driver' => env('QUEUE_FAILED_DRIVER', 'database'),
|
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
|
||||||
'database' => env('DB_CONNECTION', 'mysql'),
|
'database' => env('DB_CONNECTION', 'sqlite'),
|
||||||
'table' => 'failed_jobs',
|
'table' => 'failed_jobs',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -9,16 +9,16 @@ return [
|
|||||||
| Default Session Driver
|
| Default Session Driver
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| This option controls the default session "driver" that will be used on
|
| This option determines the default session driver that is utilized for
|
||||||
| requests. By default, we will use the lightweight native driver but
|
| incoming requests. Laravel supports a variety of storage options to
|
||||||
| you may specify any of the other wonderful drivers provided here.
|
| persist session data. Database storage is a great default choice.
|
||||||
|
|
|
|
||||||
| Supported: "file", "cookie", "database", "apc",
|
| Supported: "file", "cookie", "database", "apc",
|
||||||
| "memcached", "redis", "dynamodb", "array"
|
| "memcached", "redis", "dynamodb", "array"
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'driver' => env('SESSION_DRIVER', 'file'),
|
'driver' => env('SESSION_DRIVER', 'database'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@ -27,13 +27,14 @@ return [
|
|||||||
|
|
|
|
||||||
| Here you may specify the number of minutes that you wish the session
|
| Here you may specify the number of minutes that you wish the session
|
||||||
| to be allowed to remain idle before it expires. If you want them
|
| to be allowed to remain idle before it expires. If you want them
|
||||||
| to immediately expire on the browser closing, set that option.
|
| to expire immediately when the browser is closed then you may
|
||||||
|
| indicate that via the expire_on_close configuration option.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'lifetime' => env('SESSION_LIFETIME', 120),
|
'lifetime' => env('SESSION_LIFETIME', 120),
|
||||||
|
|
||||||
'expire_on_close' => false,
|
'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@ -41,21 +42,21 @@ return [
|
|||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| This option allows you to easily specify that all of your session data
|
| This option allows you to easily specify that all of your session data
|
||||||
| should be encrypted before it is stored. All encryption will be run
|
| should be encrypted before it's stored. All encryption is performed
|
||||||
| automatically by Laravel and you can use the Session like normal.
|
| automatically by Laravel and you may use the session like normal.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'encrypt' => false,
|
'encrypt' => env('SESSION_ENCRYPT', false),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Session File Location
|
| Session File Location
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| When using the native session driver, we need a location where session
|
| When utilizing the "file" session driver, the session files are placed
|
||||||
| files may be stored. A default has been set for you but a different
|
| on disk. The default storage location is defined here; however, you
|
||||||
| location may be specified. This is only needed for file sessions.
|
| are free to provide another location where they should be stored.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -72,33 +73,35 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'connection' => env('SESSION_CONNECTION', null),
|
'connection' => env('SESSION_CONNECTION'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Session Database Table
|
| Session Database Table
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| When using the "database" session driver, you may specify the table we
|
| When using the "database" session driver, you may specify the table to
|
||||||
| should use to manage the sessions. Of course, a sensible default is
|
| be used to store sessions. Of course, a sensible default is defined
|
||||||
| provided for you; however, you are free to change this as needed.
|
| for you; however, you're welcome to change this to another table.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'table' => 'sessions',
|
'table' => env('SESSION_TABLE', 'sessions'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Session Cache Store
|
| Session Cache Store
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| When using the "apc", "memcached", or "dynamodb" session drivers you may
|
| When using one of the framework's cache driven session backends, you may
|
||||||
| list a cache store that should be used for these sessions. This value
|
| define the cache store which should be used to store the session data
|
||||||
| must match with one of the application's configured cache "stores".
|
| between requests. This must match one of your defined cache stores.
|
||||||
|
|
|
||||||
|
| Affects: "apc", "dynamodb", "memcached", "redis"
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'store' => env('SESSION_STORE', null),
|
'store' => env('SESSION_STORE'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@ -118,9 +121,9 @@ return [
|
|||||||
| Session Cookie Name
|
| Session Cookie Name
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| Here you may change the name of the cookie used to identify a session
|
| Here you may change the name of the session cookie that is created by
|
||||||
| instance by ID. The name specified here will get used every time a
|
| the framework. Typically, you should not need to change this value
|
||||||
| new session cookie is created by the framework for every driver.
|
| since doing so does not grant a meaningful security improvement.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -136,24 +139,24 @@ return [
|
|||||||
|
|
|
|
||||||
| The session cookie path determines the path for which the cookie will
|
| The session cookie path determines the path for which the cookie will
|
||||||
| be regarded as available. Typically, this will be the root path of
|
| be regarded as available. Typically, this will be the root path of
|
||||||
| your application but you are free to change this when necessary.
|
| your application, but you're free to change this when necessary.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'path' => '/',
|
'path' => env('SESSION_PATH', '/'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Session Cookie Domain
|
| Session Cookie Domain
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| Here you may change the domain of the cookie used to identify a session
|
| This value determines the domain and subdomains the session cookie is
|
||||||
| in your application. This will determine which domains the cookie is
|
| available to. By default, the cookie will be available to the root
|
||||||
| available to in your application. A sensible default has been set.
|
| domain and all subdomains. Typically, this shouldn't be changed.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'domain' => env('SESSION_DOMAIN', null),
|
'domain' => env('SESSION_DOMAIN'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@ -162,11 +165,11 @@ return [
|
|||||||
|
|
|
|
||||||
| By setting this option to true, session cookies will only be sent back
|
| By setting this option to true, session cookies will only be sent back
|
||||||
| to the server if the browser has a HTTPS connection. This will keep
|
| to the server if the browser has a HTTPS connection. This will keep
|
||||||
| the cookie from being sent to you if it can not be done securely.
|
| the cookie from being sent to you when it can't be done securely.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'secure' => env('SESSION_SECURE_COOKIE', false),
|
'secure' => env('SESSION_SECURE_COOKIE'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@ -175,11 +178,11 @@ return [
|
|||||||
|
|
|
|
||||||
| Setting this value to true will prevent JavaScript from accessing the
|
| Setting this value to true will prevent JavaScript from accessing the
|
||||||
| value of the cookie and the cookie will only be accessible through
|
| value of the cookie and the cookie will only be accessible through
|
||||||
| the HTTP protocol. You are free to modify this option if needed.
|
| the HTTP protocol. It's unlikely you should disable this option.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'http_only' => true,
|
'http_only' => env('SESSION_HTTP_ONLY', true),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@ -188,12 +191,27 @@ return [
|
|||||||
|
|
|
|
||||||
| This option determines how your cookies behave when cross-site requests
|
| This option determines how your cookies behave when cross-site requests
|
||||||
| take place, and can be used to mitigate CSRF attacks. By default, we
|
| take place, and can be used to mitigate CSRF attacks. By default, we
|
||||||
| do not enable this as other CSRF protection services are in place.
|
| will set this value to "lax" to permit secure cross-site requests.
|
||||||
|
|
|
|
||||||
| Supported: "lax", "strict"
|
| See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value
|
||||||
|
|
|
||||||
|
| Supported: "lax", "strict", "none", null
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'same_site' => null,
|
'same_site' => env('SESSION_SAME_SITE', 'lax'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Partitioned Cookies
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Setting this value to true will tie the cookie to the top-level site for
|
||||||
|
| a cross-site context. Partitioned cookies are accepted by the browser
|
||||||
|
| when flagged "secure" and the Same-Site attribute is set to "none".
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'partitioned' => env('SESSION_PARTITIONED_COOKIE', false),
|
||||||
|
|
||||||
];
|
];
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'dir'=>'/videos',
|
'dir'=>'HomeMovies',
|
||||||
'import'=>[
|
'import'=>[
|
||||||
'accepted'=>['m4v','mov','mp4','avi'],
|
'accepted'=>['m4v','mov','mp4','avi'],
|
||||||
],
|
],
|
||||||
|
56
database/migrations/0010-make_model_software.php
Normal file
56
database/migrations/0010-make_model_software.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('makes', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->timestamps();
|
||||||
|
$table->string('name')->unique();
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('models', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->timestamps();
|
||||||
|
$table->string('name')->nullable();
|
||||||
|
|
||||||
|
$table->bigInteger('make_id')->unsigned()->nullable();
|
||||||
|
$table->foreign('make_id')->references('id')->on('makes');
|
||||||
|
|
||||||
|
$table->unique(['name','make_id']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('software', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->timestamps();
|
||||||
|
$table->string('name')->nullable();
|
||||||
|
|
||||||
|
$table->bigInteger('model_id')->unsigned()->nullable();
|
||||||
|
$table->foreign('model_id')->references('id')->on('models');
|
||||||
|
|
||||||
|
$table->unique(['name','model_id']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('software');
|
||||||
|
Schema::dropIfExists('models');
|
||||||
|
Schema::dropIfExists('makes');
|
||||||
|
}
|
||||||
|
};
|
59
database/migrations/0020-make_photo.php
Normal file
59
database/migrations/0020-make_photo.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('photos', function(Blueprint $table)
|
||||||
|
{
|
||||||
|
$table->id();
|
||||||
|
$table->timestamps();
|
||||||
|
$table->softDeletes();
|
||||||
|
|
||||||
|
$table->smallInteger('subsectime')->nullable();
|
||||||
|
$table->string('filename',256)->index();
|
||||||
|
$table->string('file_signature')->index()->nullable();
|
||||||
|
$table->dateTimeTz('created')->nullable();
|
||||||
|
$table->dateTimeTz('created_manual')->nullable();
|
||||||
|
$table->string('signature',64)->index()->nullable();
|
||||||
|
$table->integer('height')->nullable();
|
||||||
|
$table->integer('width')->nullable();
|
||||||
|
$table->integer('orientation')->nullable();
|
||||||
|
$table->float('gps_lat',10)->nullable();
|
||||||
|
$table->float('gps_lon',10)->nullable();
|
||||||
|
$table->binary('thumbnail')->nullable();
|
||||||
|
$table->string('identifier')->nullable();
|
||||||
|
|
||||||
|
$table->boolean('duplicate')->default(FALSE)->nullable();
|
||||||
|
$table->boolean('flag')->default(FALSE)->nullable();
|
||||||
|
$table->boolean('scanned')->default(FALSE)->nullable();
|
||||||
|
$table->boolean('ignore_duplicate')->default(FALSE)->nullable();
|
||||||
|
$table->boolean('remove')->default(FALSE)->nullable();
|
||||||
|
|
||||||
|
$table->bigInteger('model_id')->unsigned()->nullable();
|
||||||
|
$table->foreign('model_id')->references('id')->on('models');
|
||||||
|
|
||||||
|
$table->bigInteger('software_id')->unsigned()->nullable();
|
||||||
|
$table->foreign('software_id')->references('id')->on('software');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::drop('photos');
|
||||||
|
}
|
||||||
|
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user