Compare commits
8 Commits
Author | SHA1 | Date |
---|---|---|
Andy Heathershaw | b5d9b9c6cf | |
Andy Heathershaw | 6692c530e4 | |
Andy Heathershaw | 344c78755d | |
Andy Heathershaw | cefff7ef4b | |
Andy Heathershaw | c078210da8 | |
Andy Heathershaw | 5d84a67085 | |
Andy Heathershaw | 9e777af0bf | |
Andy Heathershaw | 41908d4428 |
|
@ -1,7 +1,7 @@
|
|||
APP_ENV=production
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=false
|
||||
APP_LOG_LEVEL=warning
|
||||
APP_DEBUG=true
|
||||
APP_LOG_LEVEL=debug
|
||||
APP_URL=http://localhost
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
|
|
157
Gruntfile.js
|
@ -1,157 +0,0 @@
|
|||
/*
|
||||
This Gruntfile downloads the necessary CSS and JS includes (Bootstrap, etc.) and creates a combined file ready for
|
||||
deployment with the application.
|
||||
|
||||
Available tasks:
|
||||
|
||||
- build-debug: Builds the minified CSS and JS files
|
||||
*/
|
||||
|
||||
module.exports = function(grunt)
|
||||
{
|
||||
var download_url = 'https://cdn.andysh.uk/';
|
||||
|
||||
const sass = require('node-sass');
|
||||
|
||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||
grunt.loadNpmTasks('grunt-contrib-cssmin');
|
||||
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-curl');
|
||||
grunt.loadNpmTasks('grunt-dart-sass');
|
||||
grunt.loadNpmTasks('grunt-exec');
|
||||
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
clean: {
|
||||
build_css: [
|
||||
// Clean the build folder - downloaded files
|
||||
'build/css',
|
||||
// Clean the resources folder - compiled SASS files
|
||||
'resources/css'
|
||||
],
|
||||
build_js: [
|
||||
'build/js',
|
||||
],
|
||||
output: [
|
||||
'public/css',
|
||||
'public/js'
|
||||
]
|
||||
},
|
||||
concat: {
|
||||
// Concatenate all third-party stylesheets into blue-twilight.css
|
||||
bt_css: {
|
||||
src: [
|
||||
'build/css/*.css',
|
||||
'resources/css/*.css'
|
||||
],
|
||||
dest: 'public/css/blue-twilight.css'
|
||||
},
|
||||
// Concatenate all third-party and application Javascripts into blue-twilight.js
|
||||
bt_js: {
|
||||
src: [
|
||||
'build/js/*.js',
|
||||
'resources/js/*.js',
|
||||
],
|
||||
dest: 'public/js/blue-twilight.js'
|
||||
},
|
||||
},
|
||||
curl: {
|
||||
/* These elements are sorted according to the order we desire them to be loaded when concatenated */
|
||||
/** CSS **/
|
||||
bootstrap_css: {
|
||||
src: download_url + 'bootstrap/4.4.1/css/bootstrap.css',
|
||||
dest: 'build/css/01-bootstrap.css'
|
||||
},
|
||||
|
||||
/** JS **/
|
||||
jquery: {
|
||||
src: download_url + 'jquery/3.4.1/jquery.js',
|
||||
dest: 'build/js/01-jquery.js'
|
||||
},
|
||||
bootstrap_js: {
|
||||
src: download_url + 'bootstrap/4.4.1/js/bootstrap.bundle.js',
|
||||
dest: 'build/js/02-bootstrap.js'
|
||||
},
|
||||
bootbox_js: {
|
||||
src: download_url + 'bootbox/5.3.3/bootbox.all.js',
|
||||
dest: 'build/js/03-bootbox.js'
|
||||
},
|
||||
font_awesome_js: {
|
||||
src: download_url + 'font-awesome/5.12.0/js/all.js',
|
||||
dest: 'build/js/04-fontawesome.js'
|
||||
},
|
||||
vuejs: {
|
||||
src: download_url + 'vuejs/2.6.11/vue.js',
|
||||
dest: 'build/js/05-vuejs.js'
|
||||
},
|
||||
},
|
||||
'dart-sass': {
|
||||
bt_sass: {
|
||||
options: {
|
||||
sourceMap: false
|
||||
},
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: 'resources/sass/',
|
||||
src: ['*.scss'],
|
||||
dest: 'resources/css/',
|
||||
ext: '.css'
|
||||
}]
|
||||
},
|
||||
},
|
||||
cssmin: {
|
||||
bt_css: {
|
||||
files: {
|
||||
'public/css/blue-twilight.min.css': ['public/css/blue-twilight.css']
|
||||
}
|
||||
}
|
||||
},
|
||||
uglify: {
|
||||
bt_js: {
|
||||
options: {
|
||||
sourceMap: true
|
||||
},
|
||||
files: {
|
||||
'public/js/blue-twilight.min.js': ['public/js/blue-twilight.js']
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Register our tasks
|
||||
grunt.registerTask('build-css-debug', [
|
||||
// Download third-party stylesheets
|
||||
'curl:bootstrap_css',
|
||||
// SASS our own CSS
|
||||
'dart-sass:bt_sass',
|
||||
// Create our blue-twilight.css
|
||||
'concat:bt_css'
|
||||
]);
|
||||
|
||||
grunt.registerTask('build-js-debug', [
|
||||
// Download third-party Javascripts
|
||||
'curl:jquery',
|
||||
'curl:bootstrap_js',
|
||||
'curl:bootbox_js',
|
||||
'curl:font_awesome_js',
|
||||
'curl:vuejs',
|
||||
// Create our blue-twilight.js
|
||||
'concat:bt_js'
|
||||
]);
|
||||
|
||||
grunt.registerTask('build-css-release', [
|
||||
'build-css-debug',
|
||||
'cssmin:bt_css'
|
||||
]);
|
||||
|
||||
grunt.registerTask('build-js-release', [
|
||||
'build-js-debug',
|
||||
'uglify:bt_js'
|
||||
]);
|
||||
|
||||
// Shortcut tasks for the ones above
|
||||
grunt.registerTask('clean-all', ['clean:build_css', 'clean:build_js', 'clean:output']);
|
||||
grunt.registerTask('build-debug', ['clean-all', 'build-css-debug', 'build-js-debug']);
|
||||
grunt.registerTask('build-release', ['clean-all', 'build-css-release', 'build-js-release']);
|
||||
};
|
|
@ -42,11 +42,6 @@ abstract class AlbumSourceBase
|
|||
return self::$albumSourceCache[$fullClassName];
|
||||
}
|
||||
|
||||
public function getConfiguration()
|
||||
{
|
||||
return $this->configuration;
|
||||
}
|
||||
|
||||
public function setAlbum(Album $album)
|
||||
{
|
||||
$this->album = $album;
|
||||
|
|
|
@ -4,10 +4,9 @@ namespace App\AlbumSources;
|
|||
|
||||
use App\Helpers\MiscHelper;
|
||||
use App\Photo;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use GuzzleHttp\Psr7\Stream;
|
||||
use Guzzle\Http\EntityBody;
|
||||
use Guzzle\Http\Exception\ClientErrorResponseException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use function GuzzleHttp\Psr7\stream_for;
|
||||
|
||||
class AmazonS3Source extends AlbumSourceBase implements IAlbumSource, IAnalysisQueueSource
|
||||
{
|
||||
|
@ -38,7 +37,7 @@ class AmazonS3Source extends AlbumSourceBase implements IAlbumSource, IAnalysisQ
|
|||
'Key' => $fileToDelete
|
||||
]);
|
||||
}
|
||||
catch (GuzzleException $ex)
|
||||
catch (ClientErrorResponseException $ex)
|
||||
{
|
||||
// Don't worry if the file no longer exists
|
||||
Log::warning('Failed deleting image from S3.', ['error' => $ex->getMessage(), 'path' => $fileToDelete]);
|
||||
|
@ -62,7 +61,7 @@ class AmazonS3Source extends AlbumSourceBase implements IAlbumSource, IAnalysisQ
|
|||
'Key' => $this->getPathToPhoto($photo, $thumbnail)
|
||||
]);
|
||||
}
|
||||
catch (GuzzleException $ex)
|
||||
catch (ClientErrorResponseException $ex)
|
||||
{
|
||||
// Don't worry if the file no longer exists
|
||||
Log::warning('Failed deleting image from S3.', ['error' => $ex->getMessage(), 'path' => $photoPath]);
|
||||
|
@ -92,7 +91,7 @@ class AmazonS3Source extends AlbumSourceBase implements IAlbumSource, IAnalysisQ
|
|||
* Fetches the contents of a thumbnail for a photo.
|
||||
* @param Photo $photo Photo to fetch the thumbnail for.
|
||||
* @param string $thumbnail Thumbnail to fetch (or null to fetch the original.)
|
||||
* @return Stream
|
||||
* @return EntityBody
|
||||
*/
|
||||
public function fetchPhotoContent(Photo $photo, $thumbnail = null)
|
||||
{
|
||||
|
@ -106,7 +105,7 @@ class AmazonS3Source extends AlbumSourceBase implements IAlbumSource, IAnalysisQ
|
|||
'SaveAs' => $tempFile
|
||||
]);
|
||||
|
||||
return stream_for(fopen($tempFile, 'r+'));
|
||||
return EntityBody::factory(fopen($tempFile, 'r+'));
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -131,19 +130,7 @@ class AmazonS3Source extends AlbumSourceBase implements IAlbumSource, IAnalysisQ
|
|||
*/
|
||||
public function getUrlToPhoto(Photo $photo, $thumbnail = null)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
|
||||
if ($this->configuration->s3_signed_urls)
|
||||
{
|
||||
$cmd = $client->getCommand('GetObject', [
|
||||
'Bucket' => $this->configuration->container_name,
|
||||
'Key' => $this->getPathToPhoto($photo, $thumbnail)
|
||||
]);
|
||||
|
||||
return (string)$client->createPresignedRequest($cmd, '+5 minutes')->getUri();
|
||||
}
|
||||
|
||||
return $client->getObjectUrl($this->configuration->container_name, $this->getPathToPhoto($photo, $thumbnail));
|
||||
return $this->getClient()->getObjectUrl($this->configuration->container_name, $this->getPathToPhoto($photo, $thumbnail));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -157,11 +144,7 @@ class AmazonS3Source extends AlbumSourceBase implements IAlbumSource, IAnalysisQ
|
|||
{
|
||||
$photoPath = $this->getPathToPhoto($photo, $thumbnail);
|
||||
|
||||
$uploadAcl = $this->configuration->s3_signed_urls
|
||||
? 'private'
|
||||
: 'public-read';
|
||||
|
||||
$this->getClient()->upload($this->configuration->container_name, $photoPath, fopen($tempFilename, 'r+'), $uploadAcl);
|
||||
$this->getClient()->upload($this->configuration->container_name, $photoPath, fopen($tempFilename, 'r+'), 'public-read');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,9 +6,8 @@ use App\BackblazeB2FileIdCache;
|
|||
use App\Photo;
|
||||
use App\Services\BackblazeB2Service;
|
||||
use App\Storage;
|
||||
use GuzzleHttp\Psr7\Stream;
|
||||
use Guzzle\Http\EntityBody;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use function GuzzleHttp\Psr7\stream_for;
|
||||
|
||||
class BackblazeB2Source extends AlbumSourceBase implements IAlbumSource
|
||||
{
|
||||
|
@ -68,7 +67,7 @@ class BackblazeB2Source extends AlbumSourceBase implements IAlbumSource
|
|||
* Fetches the contents of a thumbnail for a photo.
|
||||
* @param Photo $photo Photo to fetch the thumbnail for.
|
||||
* @param string $thumbnail Thumbnail to fetch (or null to fetch the original.)
|
||||
* @return Stream
|
||||
* @return EntityBody
|
||||
*/
|
||||
public function fetchPhotoContent(Photo $photo, $thumbnail = null)
|
||||
{
|
||||
|
@ -79,10 +78,12 @@ class BackblazeB2Source extends AlbumSourceBase implements IAlbumSource
|
|||
$b2Cache = $this->getB2FileFromCache($pathOnStorage);
|
||||
if (is_null($b2Cache))
|
||||
{
|
||||
return stream_for('');
|
||||
return EntityBody::fromString('');
|
||||
}
|
||||
|
||||
return stream_for($this->getClient()->downloadFile($b2Cache->b2_file_id));
|
||||
return EntityBody::fromString(
|
||||
$this->getClient()->downloadFile($b2Cache->b2_file_id)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,137 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\AlbumSources;
|
||||
|
||||
use App\Photo;
|
||||
use App\Services\DropboxService;
|
||||
use GuzzleHttp\Psr7\Stream;
|
||||
use function GuzzleHttp\Psr7\stream_for;
|
||||
|
||||
class DropboxSource extends AlbumSourceBase implements IAlbumSource
|
||||
{
|
||||
/**
|
||||
* @var DropboxService
|
||||
*/
|
||||
private $dropboxClient;
|
||||
|
||||
/**
|
||||
* Deletes an entire album's media contents.
|
||||
* @return void
|
||||
*/
|
||||
public function deleteAlbumContents()
|
||||
{
|
||||
try
|
||||
{
|
||||
$albumPathOnStorage = sprintf('/%s', $this->album->url_alias);
|
||||
|
||||
$this->getClient()->deleteFile($albumPathOnStorage);
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
// Don't worry too much if the delete fails - the file may have been removed on Dropbox itself
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a thumbnail file for a photo.
|
||||
* @param Photo $photo Photo to delete the thumbnail from.
|
||||
* @param string $thumbnail Thumbnail to delete (or null to delete the original.)
|
||||
* @return void
|
||||
*/
|
||||
public function deleteThumbnail(Photo $photo, $thumbnail = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
$pathOnStorage = $this->getPathToPhoto($photo, $thumbnail);
|
||||
|
||||
$this->getClient()->deleteFile($pathOnStorage);
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
// Don't worry too much if the delete fails - the file may have been removed on Dropbox itself
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the contents of a thumbnail for a photo.
|
||||
* @param Photo $photo Photo to fetch the thumbnail for.
|
||||
* @param string $thumbnail Thumbnail to fetch (or null to fetch the original.)
|
||||
* @return Stream
|
||||
*/
|
||||
public function fetchPhotoContent(Photo $photo, $thumbnail = null)
|
||||
{
|
||||
$pathOnStorage = $this->getPathToPhoto($photo, $thumbnail);
|
||||
|
||||
return stream_for($this->getClient()->downloadFile($pathOnStorage));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of this album source.
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'global.album_sources.dropbox';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the absolute URL to the given photo file.
|
||||
* @param Photo $photo Photo to get the URL to.
|
||||
* @param string $thumbnail Thumbnail to get the image to.
|
||||
* @return string
|
||||
*/
|
||||
public function getUrlToPhoto(Photo $photo, $thumbnail = null)
|
||||
{
|
||||
$photoUrl = route('downloadPhoto', [
|
||||
'albumUrlAlias' => $this->album->url_path,
|
||||
'photoFilename' => $photo->storage_file_name
|
||||
]);
|
||||
|
||||
if (!is_null($thumbnail))
|
||||
{
|
||||
$photoUrl .= sprintf('?t=%s', urlencode($thumbnail));
|
||||
}
|
||||
|
||||
return $photoUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a generated thumbnail to its permanent location.
|
||||
* @param Photo $photo Photo the image relates to.
|
||||
* @param string $tempFilename Filename containing the image.
|
||||
* @param string $thumbnail Name of the thumbnail (or null for the original.)
|
||||
* @return mixed
|
||||
*/
|
||||
public function saveThumbnail(Photo $photo, $tempFilename, $thumbnail = null)
|
||||
{
|
||||
$pathOnStorage = $this->getPathToPhoto($photo, $thumbnail);
|
||||
|
||||
$this->getClient()->uploadFile($tempFilename, $pathOnStorage);
|
||||
}
|
||||
|
||||
private function getClient()
|
||||
{
|
||||
if (is_null($this->dropboxClient))
|
||||
{
|
||||
$this->dropboxClient = new DropboxService();
|
||||
$this->dropboxClient->setAccessToken(decrypt($this->configuration->access_token));
|
||||
}
|
||||
|
||||
return $this->dropboxClient;
|
||||
}
|
||||
|
||||
private function getOriginalsFolder()
|
||||
{
|
||||
return '_originals';
|
||||
}
|
||||
|
||||
private function getPathToPhoto(Photo $photo, $thumbnail = null)
|
||||
{
|
||||
return sprintf(
|
||||
'/%s/%s/%s',
|
||||
$this->album->url_alias,
|
||||
is_null($thumbnail) ? $this->getOriginalsFolder() : $thumbnail,
|
||||
$photo->storage_file_name
|
||||
);
|
||||
}
|
||||
}
|
|
@ -5,7 +5,8 @@ namespace App\AlbumSources;
|
|||
use App\Album;
|
||||
use App\Photo;
|
||||
use App\Storage;
|
||||
use GuzzleHttp\Psr7\Stream;
|
||||
use Guzzle\Http\EntityBody;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
|
||||
interface IAlbumSource
|
||||
{
|
||||
|
@ -27,16 +28,10 @@ interface IAlbumSource
|
|||
* Fetches the contents of a thumbnail for a photo.
|
||||
* @param Photo $photo Photo to fetch the thumbnail for.
|
||||
* @param string $thumbnail Thumbnail to fetch (or null to fetch the original.)
|
||||
* @return Stream
|
||||
* @return EntityBody
|
||||
*/
|
||||
function fetchPhotoContent(Photo $photo, $thumbnail = null);
|
||||
|
||||
/**
|
||||
* Gets the configuration of the source.
|
||||
* @return mixed
|
||||
*/
|
||||
function getConfiguration();
|
||||
|
||||
/**
|
||||
* Gets the name of this album source.
|
||||
* @return string
|
||||
|
|
|
@ -5,9 +5,8 @@ namespace App\AlbumSources;
|
|||
use App\Helpers\FileHelper;
|
||||
use App\Helpers\MiscHelper;
|
||||
use App\Photo;
|
||||
use GuzzleHttp\Psr7\Stream;
|
||||
use Guzzle\Http\EntityBody;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
use function GuzzleHttp\Psr7\stream_for;
|
||||
|
||||
/**
|
||||
* Driver for managing files on the local filesystem.
|
||||
|
@ -38,7 +37,7 @@ class LocalFilesystemSource extends AlbumSourceBase implements IAlbumSource, IAn
|
|||
* Fetches the contents of a thumbnail for a photo.
|
||||
* @param Photo $photo Photo to fetch the thumbnail for.
|
||||
* @param string $thumbnail Thumbnail to fetch (or null to fetch the original.)
|
||||
* @return Stream
|
||||
* @return EntityBody
|
||||
*/
|
||||
public function fetchPhotoContent(Photo $photo, $thumbnail = null)
|
||||
{
|
||||
|
@ -51,7 +50,7 @@ class LocalFilesystemSource extends AlbumSourceBase implements IAlbumSource, IAn
|
|||
'r+'
|
||||
);
|
||||
|
||||
return stream_for($fh);
|
||||
return EntityBody::factory($fh);
|
||||
}
|
||||
|
||||
public function getName()
|
||||
|
|
|
@ -3,16 +3,12 @@
|
|||
namespace App\AlbumSources;
|
||||
|
||||
use App\Photo;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
use GuzzleHttp\Psr7\Stream;
|
||||
use Guzzle\Http\EntityBody;
|
||||
use Guzzle\Http\Exception\ClientErrorResponseException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use OpenStack\Common\Error\BadResponseError;
|
||||
use OpenStack\Common\Transport\Utils as TransportUtils;
|
||||
use OpenStack\Identity\v2\Service as IdentityV2Service;
|
||||
use OpenStack\OpenStack;
|
||||
use function GuzzleHttp\Psr7\stream_for;
|
||||
use OpenCloud\ObjectStore\Exception\ObjectNotFoundException;
|
||||
use OpenCloud\OpenStack;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
|
||||
/**
|
||||
* Driver for managing files on an OpenStack Keystone+Swift compatible platform.
|
||||
|
@ -42,14 +38,9 @@ class OpenStackSource extends AlbumSourceBase implements IAlbumSource
|
|||
|
||||
try
|
||||
{
|
||||
$this->getContainer()->getObject($photoPath)->delete();
|
||||
$this->getContainer()->deleteObject($photoPath);
|
||||
}
|
||||
catch (GuzzleException $ex)
|
||||
{
|
||||
// Don't worry if the file no longer exists
|
||||
Log::warning('Failed deleting image from OpenStack.', ['error' => $ex->getMessage(), 'path' => $photoPath]);
|
||||
}
|
||||
catch (BadResponseError $ex)
|
||||
catch (ClientErrorResponseException $ex)
|
||||
{
|
||||
// Don't worry if the file no longer exists
|
||||
Log::warning('Failed deleting image from OpenStack.', ['error' => $ex->getMessage(), 'path' => $photoPath]);
|
||||
|
@ -60,13 +51,13 @@ class OpenStackSource extends AlbumSourceBase implements IAlbumSource
|
|||
* Fetches the contents of a thumbnail for a photo.
|
||||
* @param Photo $photo Photo to fetch the thumbnail for.
|
||||
* @param string $thumbnail Thumbnail to fetch (or null to fetch the original.)
|
||||
* @return Stream
|
||||
* @return EntityBody
|
||||
*/
|
||||
public function fetchPhotoContent(Photo $photo, $thumbnail = null)
|
||||
{
|
||||
$object = $this->getContainer()->getObject($this->getPathToPhoto($photo, $thumbnail));
|
||||
|
||||
return stream_for($object->download());
|
||||
return $object->getContent();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -122,33 +113,17 @@ class OpenStackSource extends AlbumSourceBase implements IAlbumSource
|
|||
{
|
||||
$photoPath = $this->getPathToPhoto($photo, $thumbnail);
|
||||
|
||||
$createOptions = [
|
||||
'name' => $photoPath,
|
||||
'content' => file_get_contents($tempFilename)
|
||||
];
|
||||
|
||||
$this->getContainer()->createObject($createOptions);
|
||||
$container = $this->getContainer();
|
||||
$container->uploadObject($photoPath, fopen($tempFilename, 'r+'));
|
||||
}
|
||||
|
||||
protected function getClient()
|
||||
{
|
||||
$authURL = $this->configuration->auth_url;
|
||||
|
||||
$options = [
|
||||
'authUrl' => $authURL,
|
||||
return new OpenStack($this->configuration->auth_url, [
|
||||
'username' => $this->configuration->username,
|
||||
'password' => decrypt($this->configuration->password),
|
||||
'tenantName' => $this->configuration->tenant_name,
|
||||
'region' => $this->configuration->service_region,
|
||||
'identityService' => IdentityV2Service::factory(
|
||||
new Client([
|
||||
'base_uri' => TransportUtils::normalizeUrl($authURL),
|
||||
'handler' => HandlerStack::create(),
|
||||
])
|
||||
)
|
||||
];
|
||||
|
||||
return new OpenStack($options);
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getContainer()
|
||||
|
@ -158,14 +133,11 @@ class OpenStackSource extends AlbumSourceBase implements IAlbumSource
|
|||
|
||||
protected function getStorageService()
|
||||
{
|
||||
return $this->getClient()->objectStoreV1([
|
||||
'catalogName' => $this->getStorageServiceCatalogName()
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getStorageServiceCatalogName()
|
||||
{
|
||||
return $this->configuration->service_name;
|
||||
return $this->getClient()->objectStoreService(
|
||||
$this->configuration->service_name,
|
||||
$this->configuration->service_region,
|
||||
'publicURL'
|
||||
);
|
||||
}
|
||||
|
||||
protected function getOriginalsFolder()
|
||||
|
|
|
@ -3,15 +3,26 @@
|
|||
namespace App\AlbumSources;
|
||||
|
||||
use App\Photo;
|
||||
use App\Services\Rackspace\Identity\v2\Service as RackspaceIdentityV2Service;
|
||||
use App\Services\Rackspace\ObjectStoreCdn\v1\Models\Container;
|
||||
use App\Services\Rackspace\Rackspace;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
use OpenStack\Common\Transport\Utils as TransportUtils;
|
||||
use App\Storage;
|
||||
use OpenCloud\Rackspace;
|
||||
|
||||
class RackspaceSource extends OpenStackSource
|
||||
{
|
||||
protected function getClient()
|
||||
{
|
||||
$endpoint = Rackspace::US_IDENTITY_ENDPOINT;
|
||||
|
||||
if ($this->configuration->service_region == 'LON')
|
||||
{
|
||||
$endpoint = Rackspace::UK_IDENTITY_ENDPOINT;
|
||||
}
|
||||
|
||||
return new Rackspace($endpoint, [
|
||||
'username' => $this->configuration->username,
|
||||
'apiKey' => decrypt($this->configuration->password)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of this album source.
|
||||
* @return string
|
||||
|
@ -30,14 +41,12 @@ class RackspaceSource extends OpenStackSource
|
|||
public function getUrlToPhoto(Photo $photo, $thumbnail = null)
|
||||
{
|
||||
$isCdnEnabled = false;
|
||||
$cdnService = $this->getCdnService();
|
||||
|
||||
$cdnService = $this->getStorageService()->getCdnService();
|
||||
$thisCdnContainer = null;
|
||||
|
||||
/** @var Container $cdnContainer */
|
||||
foreach ($cdnService->listContainers() as $cdnContainer)
|
||||
{
|
||||
if ($cdnContainer->cdn_enabled && strtolower($cdnContainer->name) == strtolower($this->configuration->container_name))
|
||||
if ($cdnContainer->name == $this->configuration->container_name)
|
||||
{
|
||||
$isCdnEnabled = true;
|
||||
$thisCdnContainer = $cdnContainer;
|
||||
|
@ -46,47 +55,9 @@ class RackspaceSource extends OpenStackSource
|
|||
|
||||
if ($isCdnEnabled)
|
||||
{
|
||||
return sprintf('%s/%s', $thisCdnContainer->cdn_ssl_uri, $this->getPathToPhoto($photo, $thumbnail));
|
||||
return sprintf('%s/%s', $thisCdnContainer->getCdnSslUri(), $this->getPathToPhoto($photo, $thumbnail));
|
||||
}
|
||||
|
||||
return parent::getPathToPhoto($photo, $thumbnail);
|
||||
}
|
||||
|
||||
protected function getCdnService()
|
||||
{
|
||||
return $this->getClient()->objectStoreCdnV1();
|
||||
}
|
||||
|
||||
protected function getClient()
|
||||
{
|
||||
$authURL = config('services.rackspace.authentication_url');
|
||||
|
||||
// Uncomment the commented out lines below and in the $options array to get a 'storage/logs/openstack.log' file
|
||||
// with passed HTTP traffic
|
||||
//$logger = new Logger('MyLog');
|
||||
//$logger->pushHandler(new StreamHandler(__DIR__ . '/../../storage/logs/openstack.log'), Logger::DEBUG);
|
||||
|
||||
$options = [
|
||||
'authUrl' => $authURL,
|
||||
'username' => $this->configuration->username,
|
||||
'apiKey' => decrypt($this->configuration->password),
|
||||
'region' => $this->configuration->service_region,
|
||||
'identityService' => RackspaceIdentityV2Service::factory(
|
||||
new Client([
|
||||
'base_uri' => TransportUtils::normalizeUrl($authURL),
|
||||
'handler' => HandlerStack::create(),
|
||||
])
|
||||
),
|
||||
//'debugLog' => true,
|
||||
//'logger' => $logger,
|
||||
//'messageFormatter' => new MessageFormatter('{req_body} - {res_body}')
|
||||
];
|
||||
|
||||
return new Rackspace($options);
|
||||
}
|
||||
|
||||
protected function getStorageServiceCatalogName()
|
||||
{
|
||||
return 'cloudFiles';
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
class DropboxRetryException extends \Exception
|
||||
{
|
||||
private $innerException;
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getInnerException()
|
||||
{
|
||||
return $this->innerException;
|
||||
}
|
||||
|
||||
public function __construct($httpCode, \Exception $innerException)
|
||||
{
|
||||
parent::__construct('Dropbox requested to retry the request');
|
||||
|
||||
$this->innerException = $innerException;
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ExternalService extends Model
|
||||
{
|
||||
public const DROPBOX = 'dropbox';
|
||||
public const FACEBOOK = 'facebook';
|
||||
public const GOOGLE = 'google';
|
||||
public const TWITTER = 'twitter';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = ['name', 'service_type', 'app_id', 'app_secret'];
|
||||
|
||||
/**
|
||||
* Gets all possible service configurations for the given service type.
|
||||
* @param $serviceType
|
||||
* @return ExternalService[]
|
||||
*/
|
||||
public static function getForService($serviceType)
|
||||
{
|
||||
return ExternalService::where('service_type', $serviceType)->get();
|
||||
}
|
||||
|
||||
public function isDropbox()
|
||||
{
|
||||
// This logic must be mirrored in external_services.js
|
||||
return $this->service_type == self::DROPBOX;
|
||||
}
|
||||
|
||||
public function isFacebook()
|
||||
{
|
||||
// This logic must be mirrored in external_services.js
|
||||
return $this->service_type == self::FACEBOOK;
|
||||
}
|
||||
|
||||
public function isGoogle()
|
||||
{
|
||||
// This logic must be mirrored in external_services.js
|
||||
return $this->service_type == self::GOOGLE;
|
||||
}
|
||||
|
||||
public function isTwitter()
|
||||
{
|
||||
// This logic must be mirrored in external_services.js
|
||||
return $this->service_type == self::TWITTER;
|
||||
}
|
||||
}
|
|
@ -4,7 +4,6 @@ namespace App\Helpers;
|
|||
|
||||
use App\AlbumSources\AmazonS3Source;
|
||||
use App\AlbumSources\BackblazeB2Source;
|
||||
use App\AlbumSources\DropboxSource;
|
||||
use App\AlbumSources\IAlbumSource;
|
||||
use App\AlbumSources\LocalFilesystemSource;
|
||||
use App\AlbumSources\OpenStackSource;
|
||||
|
@ -49,7 +48,6 @@ class ConfigHelper
|
|||
LocalFilesystemSource::class,
|
||||
AmazonS3Source::class,
|
||||
BackblazeB2Source::class,
|
||||
DropboxSource::class,
|
||||
OpenStackSource::class,
|
||||
RackspaceSource::class
|
||||
];
|
||||
|
@ -113,8 +111,10 @@ class ConfigHelper
|
|||
'date_format' => $this->allowedDateFormats()[0],
|
||||
'default_album_view' => $this->allowedAlbumViews()[0],
|
||||
'enable_visitor_hits' => false,
|
||||
'facebook_external_service_id' => 0,
|
||||
'google_external_service_id' => 0,
|
||||
'facebook_app_id' => '',
|
||||
'facebook_app_secret' => '',
|
||||
'google_app_id' => '',
|
||||
'google_app_secret' => '',
|
||||
'hotlink_protection' => false,
|
||||
'items_per_page' => 12,
|
||||
'items_per_page_admin' => 10,
|
||||
|
@ -149,7 +149,8 @@ class ConfigHelper
|
|||
'social_user_feeds' => false,
|
||||
'social_user_profiles' => false,
|
||||
'theme' => 'default',
|
||||
'twitter_external_service_id' => 0
|
||||
'twitter_app_id' => '',
|
||||
'twitter_app_secret' => '',
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -216,29 +217,11 @@ class ConfigHelper
|
|||
!empty($this->get('rabbitmq_vhost'));
|
||||
}
|
||||
|
||||
public function isLoginWithFacebookEnabled()
|
||||
{
|
||||
return boolval($this->get('social_facebook_login')) &&
|
||||
intval($this->get('facebook_external_service_id')) > 0;
|
||||
}
|
||||
|
||||
public function isLoginWithGoogleEnabled()
|
||||
{
|
||||
return boolval($this->get('social_google_login')) &&
|
||||
intval($this->get('google_external_service_id')) > 0;
|
||||
}
|
||||
|
||||
public function isLoginWithTwitterEnabled()
|
||||
{
|
||||
return boolval($this->get('social_twitter_login')) &&
|
||||
intval($this->get('twitter_external_service_id')) > 0;
|
||||
}
|
||||
|
||||
public function isSocialMediaLoginEnabled()
|
||||
{
|
||||
return $this->isLoginWithFacebookEnabled() ||
|
||||
$this->isLoginWithGoogleEnabled() ||
|
||||
$this->isLoginWithTwitterEnabled();
|
||||
return $this->get('social_facebook_login') ||
|
||||
$this->get('social_twitter_login') ||
|
||||
$this->get('social_google_login');
|
||||
}
|
||||
|
||||
private function loadCache()
|
||||
|
|
|
@ -111,9 +111,9 @@ class MiscHelper
|
|||
return sprintf('%s/.env', dirname(dirname(__DIR__)));
|
||||
}
|
||||
|
||||
public static function getEnvironmentSetting($settingName, $envFile = null)
|
||||
public static function getEnvironmentSetting($settingName)
|
||||
{
|
||||
$envFile = $envFile ?? MiscHelper::getEnvironmentFilePath();
|
||||
$envFile = MiscHelper::getEnvironmentFilePath();
|
||||
|
||||
if (!file_exists($envFile))
|
||||
{
|
||||
|
|
|
@ -11,6 +11,7 @@ use App\Facade\Theme;
|
|||
use App\Facade\UserConfig;
|
||||
use App\Group;
|
||||
use App\Helpers\DbHelper;
|
||||
use App\Helpers\FileHelper;
|
||||
use App\Helpers\MiscHelper;
|
||||
use App\Helpers\PermissionsHelper;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
@ -73,7 +74,7 @@ class AlbumController extends Controller
|
|||
|
||||
if (count($photos) == 0)
|
||||
{
|
||||
return redirect(route('albums.show', ['album' => $album->id]));
|
||||
return redirect(route('albums.show', ['id' => $album->id]));
|
||||
}
|
||||
|
||||
return Theme::render('admin.analyse_album', ['album' => $album, 'photos' => $photos, 'queue_token' => $queue_token]);
|
||||
|
@ -170,7 +171,7 @@ class AlbumController extends Controller
|
|||
|
||||
$redirect->delete();
|
||||
$request->session()->flash('success', trans('admin.delete_redirect_success_message'));
|
||||
return redirect(route('albums.show', ['album' => $id, 'tab' => 'redirects']));
|
||||
return redirect(route('albums.show', ['id' => $id, 'tab' => 'redirects']));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -705,7 +706,7 @@ class AlbumController extends Controller
|
|||
$helper = new PermissionsHelper();
|
||||
$helper->rebuildCache();
|
||||
|
||||
return redirect(route('albums.show', ['album' => $album->id]));
|
||||
return redirect(route('albums.show', ['id' => $album->id]));
|
||||
}
|
||||
|
||||
public function storeRedirect(Requests\StoreAlbumRedirectRequest $request, $id)
|
||||
|
@ -720,7 +721,7 @@ class AlbumController extends Controller
|
|||
$redirect->save();
|
||||
|
||||
$request->session()->flash('success', trans('admin.create_redirect_success_message'));
|
||||
return redirect(route('albums.show', ['album' => $id, 'tab' => 'redirects']));
|
||||
return redirect(route('albums.show', ['id' => $id, 'tab' => 'redirects']));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -782,7 +783,7 @@ class AlbumController extends Controller
|
|||
|
||||
$request->session()->flash('success', trans('admin.album_saved_successfully', ['name' => $album->name]));
|
||||
|
||||
return redirect(route('albums.show', ['album' => $id]));
|
||||
return redirect(route('albums.show', ['id' => $id]));
|
||||
}
|
||||
|
||||
private function createActivityRecord(Album $album, $type, $activityDateTime = null)
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Album;
|
||||
use App\ExternalService;
|
||||
use App\Facade\Theme;
|
||||
use App\Facade\UserConfig;
|
||||
use App\Group;
|
||||
|
@ -261,8 +260,10 @@ class DefaultController extends Controller
|
|||
'analysis_queue_storage_location',
|
||||
'app_name',
|
||||
'date_format',
|
||||
'facebook_external_service_id',
|
||||
'google_external_service_id',
|
||||
'facebook_app_id',
|
||||
'facebook_app_secret',
|
||||
'google_app_id',
|
||||
'google_app_secret',
|
||||
'photo_comments_allowed_html',
|
||||
'photo_comments_thread_depth',
|
||||
'rabbitmq_server',
|
||||
|
@ -278,7 +279,8 @@ class DefaultController extends Controller
|
|||
'smtp_username',
|
||||
'smtp_password',
|
||||
'theme',
|
||||
'twitter_external_service_id',
|
||||
'twitter_app_id',
|
||||
'twitter_app_secret',
|
||||
'recaptcha_site_key',
|
||||
'recaptcha_secret_key',
|
||||
'analytics_code'
|
||||
|
@ -372,30 +374,12 @@ class DefaultController extends Controller
|
|||
// Storage sources for the Image Processing tab
|
||||
$storageSources = AnalysisQueueHelper::getCompatibleStorages();
|
||||
|
||||
// External services
|
||||
$externalServices = ExternalService::all();
|
||||
$facebookServices = $externalServices->filter(function (ExternalService $item)
|
||||
{
|
||||
return $item->service_type == ExternalService::FACEBOOK;
|
||||
});
|
||||
$googleServices = $externalServices->filter(function (ExternalService $item)
|
||||
{
|
||||
return $item->service_type == ExternalService::GOOGLE;
|
||||
});
|
||||
$twitterServices = $externalServices->filter(function (ExternalService $item)
|
||||
{
|
||||
return $item->service_type == ExternalService::TWITTER;
|
||||
});
|
||||
|
||||
return Theme::render('admin.settings', [
|
||||
'config' => $config,
|
||||
'date_formats' => $dateFormatsLookup,
|
||||
'facebookServices' => $facebookServices,
|
||||
'googleServices' => $googleServices,
|
||||
'storage_sources' => $storageSources,
|
||||
'success' => $request->session()->get('success'),
|
||||
'theme_names' => $themeNamesLookup,
|
||||
'twitterServices' => $twitterServices
|
||||
'theme_names' => $themeNamesLookup
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -134,7 +134,7 @@ class PhotoCommentController extends Controller
|
|||
if (count($commentIDs) == 1)
|
||||
{
|
||||
// Single comment selected - redirect to the single delete page
|
||||
return redirect(route('comments.delete', ['comment' => $commentIDs[0]]));
|
||||
return redirect(route('comments.delete', ['id' => $commentIDs[0]]));
|
||||
}
|
||||
|
||||
// Show the view to confirm the delete
|
||||
|
@ -148,7 +148,7 @@ class PhotoCommentController extends Controller
|
|||
if (count($commentIDs) == 1)
|
||||
{
|
||||
// Single comment selected - redirect to the single approve page
|
||||
return redirect(route('comments.approve', ['comment' => $commentIDs[0]]));
|
||||
return redirect(route('comments.approve', ['id' => $commentIDs[0]]));
|
||||
}
|
||||
|
||||
// Show the view to confirm the approval
|
||||
|
@ -162,7 +162,7 @@ class PhotoCommentController extends Controller
|
|||
if (count($commentIDs) == 1)
|
||||
{
|
||||
// Single comment selected - redirect to the single reject page
|
||||
return redirect(route('comments.reject', ['comment' => $commentIDs[0]]));
|
||||
return redirect(route('comments.reject', ['id' => $commentIDs[0]]));
|
||||
}
|
||||
|
||||
// Show the view to confirm the rejection
|
||||
|
|
|
@ -162,7 +162,7 @@ class PhotoController extends Controller
|
|||
$request->session()->flash('success', trans('admin.delete_photo_successful_message', ['name' => $photo->name]));
|
||||
}
|
||||
|
||||
public function flip(Request $request, $photoId, $horizontal, $vertical)
|
||||
public function flip($photoId, $horizontal, $vertical)
|
||||
{
|
||||
$this->authorizeAccessToAdminPanel();
|
||||
|
||||
|
@ -176,8 +176,6 @@ class PhotoController extends Controller
|
|||
|
||||
// Log an activity record for the user's feed
|
||||
$this->createActivityRecord($photo, 'photo.edited');
|
||||
|
||||
return $photo->thumbnailUrl($request->get('t', 'admin-preview'));
|
||||
}
|
||||
|
||||
public function move(Request $request, $photoId)
|
||||
|
@ -243,13 +241,13 @@ class PhotoController extends Controller
|
|||
return response()->json($result);
|
||||
}
|
||||
|
||||
public function regenerateThumbnails(Request $request, $photoId)
|
||||
public function regenerateThumbnails($photoId)
|
||||
{
|
||||
$this->authorizeAccessToAdminPanel();
|
||||
|
||||
$photo = $this->loadPhoto($photoId, 'change-metadata');
|
||||
|
||||
$result = ['is_successful' => false, 'message' => '', 'thumbnail_url' => ''];
|
||||
$result = ['is_successful' => false, 'message' => ''];
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -257,7 +255,6 @@ class PhotoController extends Controller
|
|||
$photoService->regenerateThumbnails();
|
||||
|
||||
$result['is_successful'] = true;
|
||||
$result['thumbnail_url'] = $photo->thumbnailUrl($request->get('t', 'admin-preview'));
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
|
@ -268,7 +265,7 @@ class PhotoController extends Controller
|
|||
return response()->json($result);
|
||||
}
|
||||
|
||||
public function rotate(Request $request, $photoId, $angle)
|
||||
public function rotate($photoId, $angle)
|
||||
{
|
||||
$this->authorizeAccessToAdminPanel();
|
||||
|
||||
|
@ -276,7 +273,7 @@ class PhotoController extends Controller
|
|||
|
||||
if ($angle != 90 && $angle != 180 && $angle != 270)
|
||||
{
|
||||
App::abort(400);
|
||||
App::aport(400);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -285,8 +282,6 @@ class PhotoController extends Controller
|
|||
|
||||
// Log an activity record for the user's feed
|
||||
$this->createActivityRecord($photo, 'photo.edited');
|
||||
|
||||
return $photo->thumbnailUrl($request->get('t', 'admin-preview'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -397,7 +392,7 @@ class PhotoController extends Controller
|
|||
else
|
||||
{
|
||||
return redirect(route('albums.analyse', [
|
||||
'album' => $album->id,
|
||||
'id' => $album->id,
|
||||
'queue_token' => $queueUid
|
||||
]));
|
||||
}
|
||||
|
@ -413,7 +408,7 @@ class PhotoController extends Controller
|
|||
if (is_null($request->files->get('archive')))
|
||||
{
|
||||
$request->session()->flash('error', trans('admin.upload_bulk_no_file'));
|
||||
return redirect(route('albums.show', ['album' => $album->id]));
|
||||
return redirect(route('albums.show', ['id' => $album->id]));
|
||||
}
|
||||
|
||||
$archiveFile = UploadedFile::createFromBase($request->files->get('archive'));
|
||||
|
@ -421,7 +416,7 @@ class PhotoController extends Controller
|
|||
{
|
||||
Log::error('Bulk image upload failed.', ['error' => $archiveFile->getError(), 'reason' => $archiveFile->getErrorMessage()]);
|
||||
$request->session()->flash('error', $archiveFile->getErrorMessage());
|
||||
return redirect(route('albums.show', ['album' => $album->id]));
|
||||
return redirect(route('albums.show', ['id' => $album->id]));
|
||||
}
|
||||
|
||||
// Create the folder to hold the analysis results if not already present
|
||||
|
@ -451,7 +446,7 @@ class PhotoController extends Controller
|
|||
|
||||
default:
|
||||
$request->session()->flash('error', sprintf('The file type "%s" is not supported for bulk uploads.', $mimeType));
|
||||
return redirect(route('albums.show', ['album' => $album->id]));
|
||||
return redirect(route('albums.show', ['id' => $album->id]));
|
||||
}
|
||||
|
||||
$di = new \RecursiveDirectoryIterator($temporaryFolder, \RecursiveDirectoryIterator::SKIP_DOTS);
|
||||
|
@ -528,7 +523,7 @@ class PhotoController extends Controller
|
|||
}
|
||||
|
||||
return redirect(route('albums.analyse', [
|
||||
'album' => $album->id,
|
||||
'id' => $album->id,
|
||||
'queue_token' => $queueUid
|
||||
]));
|
||||
}
|
||||
|
@ -591,7 +586,7 @@ class PhotoController extends Controller
|
|||
)
|
||||
);
|
||||
|
||||
return redirect(route('albums.show', array('album' => $albumId, 'page' => $request->get('page', 1))));
|
||||
return redirect(route('albums.show', array('id' => $albumId, 'page' => $request->get('page', 1))));
|
||||
}
|
||||
|
||||
private function applyBulkActions(Request $request, Album $album)
|
||||
|
|
|
@ -1,355 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Configuration;
|
||||
use App\ExternalService;
|
||||
use App\Facade\Theme;
|
||||
use App\Facade\UserConfig;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreServiceRequest;
|
||||
use App\Services\DropboxService;
|
||||
use App\Storage;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\View;
|
||||
|
||||
class ServiceController extends Controller
|
||||
{
|
||||
/**
|
||||
* List of fields that must be encrypted before being saved.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $fieldsToEncrypt;
|
||||
|
||||
/**
|
||||
* List of fields that depend on the service_type being configured.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $serviceTypeDependentFields;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth');
|
||||
View::share('is_admin', true);
|
||||
|
||||
$this->serviceTypeDependentFields = ['app_id', 'app_secret'];
|
||||
$this->fieldsToEncrypt = ['app_id', 'app_secret'];
|
||||
}
|
||||
|
||||
public function authoriseDropbox(Request $request)
|
||||
{
|
||||
$this->authorizeAccessToAdminPanel('admin:manage-storage');
|
||||
|
||||
if (!$request->has('state') && !$request->has('code'))
|
||||
{
|
||||
// TODO flash an error
|
||||
return redirect('storages.index');
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$storageID = decrypt($request->get('state'));
|
||||
|
||||
$storage = Storage::where('id', intval($storageID))->first();
|
||||
if (is_null($storage))
|
||||
{
|
||||
// TODO flash an error
|
||||
return redirect('storages.index');
|
||||
}
|
||||
|
||||
if (is_null($storage->externalService))
|
||||
{
|
||||
// TODO flash an error
|
||||
return redirect('storages.index');
|
||||
}
|
||||
|
||||
switch ($storage->externalService->service_type)
|
||||
{
|
||||
case ExternalService::DROPBOX:
|
||||
$dropbox = new DropboxService();
|
||||
$dropbox->handleAuthenticationResponse($request, $storage);
|
||||
// TODO flash a success message
|
||||
return redirect(route('storage.index'));
|
||||
|
||||
default:
|
||||
// TODO flash an error
|
||||
return redirect('storages.index');
|
||||
}
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
// TODO flash an error
|
||||
return redirect('storages.index');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function create(Request $request)
|
||||
{
|
||||
$this->authorizeAccessToAdminPanel('admin:manage-services');
|
||||
|
||||
$serviceTypes = $this->serviceTypeList();
|
||||
$selectedServiceType = old('service_type', $request->get('service_type'));
|
||||
|
||||
if (!array_key_exists($selectedServiceType, $serviceTypes))
|
||||
{
|
||||
$selectedServiceType = '';
|
||||
}
|
||||
|
||||
$returnTo = old('return_to', $request->get('return_to'));
|
||||
if (!array_key_exists($returnTo, $this->validReturnLocations()))
|
||||
{
|
||||
$returnTo = '';
|
||||
}
|
||||
|
||||
return Theme::render('admin.create_service', [
|
||||
'callbackUrls' => $this->callbackList(),
|
||||
'returnTo' => $returnTo,
|
||||
'selectedServiceType' => $selectedServiceType,
|
||||
'service' => new ExternalService(),
|
||||
'serviceTypes' => $serviceTypes
|
||||
]);
|
||||
}
|
||||
|
||||
public function delete(Request $request, $id)
|
||||
{
|
||||
$this->authorizeAccessToAdminPanel('admin:manage-users');
|
||||
|
||||
$service = ExternalService::where('id', intval($id))->first();
|
||||
if (is_null($service))
|
||||
{
|
||||
App::abort(404);
|
||||
}
|
||||
|
||||
if ($this->isServiceInUse($service))
|
||||
{
|
||||
$request->session()->flash('warning', trans('admin.cannot_delete_service_in_use'));
|
||||
return redirect(route('services.index'));
|
||||
}
|
||||
|
||||
return Theme::render('admin.delete_service', ['service' => $service]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy(Request $request, $id)
|
||||
{
|
||||
$this->authorizeAccessToAdminPanel('admin:manage-services');
|
||||
|
||||
$service = ExternalService::where('id', intval($id))->first();
|
||||
if (is_null($service))
|
||||
{
|
||||
App::abort(404);
|
||||
}
|
||||
|
||||
if ($this->isServiceInUse($service))
|
||||
{
|
||||
$request->session()->flash('warning', trans('admin.cannot_delete_service_in_use'));
|
||||
return redirect(route('services.index'));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$service->delete();
|
||||
$request->session()->flash('success', trans('admin.service_deletion_successful', [
|
||||
'name' => $service->name
|
||||
]));
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
$request->session()->flash('error', trans('admin.service_deletion_failed', [
|
||||
'error_message' => $ex->getMessage(),
|
||||
'name' => $service->name
|
||||
]));
|
||||
}
|
||||
|
||||
return redirect(route('services.index'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function edit(Request $request, $id)
|
||||
{
|
||||
$this->authorizeAccessToAdminPanel('admin:manage-services');
|
||||
|
||||
$service = ExternalService::where('id', intval($id))->first();
|
||||
if (is_null($service))
|
||||
{
|
||||
App::abort(404);
|
||||
}
|
||||
|
||||
// Decrypt the fields that are stored as encrypted in the DB
|
||||
foreach ($this->fieldsToEncrypt as $field)
|
||||
{
|
||||
if (!empty($service->$field))
|
||||
{
|
||||
$service->$field = decrypt($service->$field);
|
||||
}
|
||||
}
|
||||
|
||||
return Theme::render('admin.edit_service', [
|
||||
'callbackUrls' => $this->callbackList(),
|
||||
'service' => $service,
|
||||
'serviceTypes' => $this->serviceTypeList()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorizeAccessToAdminPanel('admin:manage-services');
|
||||
|
||||
$services = ExternalService::orderBy('name')
|
||||
->paginate(UserConfig::get('items_per_page'));
|
||||
|
||||
return Theme::render('admin.list_services', [
|
||||
'error' => $request->session()->get('error'),
|
||||
'services' => $services,
|
||||
'success' => $request->session()->get('success'),
|
||||
'warning' => $request->session()->get('warning')
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(StoreServiceRequest $request)
|
||||
{
|
||||
$this->authorizeAccessToAdminPanel('admin:manage-services');
|
||||
|
||||
$service = new ExternalService($request->only(['name', 'service_type']));
|
||||
|
||||
foreach ($this->serviceTypeDependentFields as $field)
|
||||
{
|
||||
if ($request->has($field))
|
||||
{
|
||||
$service->$field = in_array($field, $this->fieldsToEncrypt)
|
||||
? encrypt($request->get($field))
|
||||
: $request->get($field);
|
||||
}
|
||||
}
|
||||
|
||||
$service->save();
|
||||
|
||||
$returnToLocations = $this->validReturnLocations();
|
||||
$returnTo = $request->get('return_to');
|
||||
|
||||
if (array_key_exists($returnTo, $returnToLocations))
|
||||
{
|
||||
return redirect($returnToLocations[$returnTo]);
|
||||
}
|
||||
|
||||
return redirect(route('services.index'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(StoreServiceRequest $request, $id)
|
||||
{
|
||||
$this->authorizeAccessToAdminPanel('admin:manage-services');
|
||||
|
||||
$service = ExternalService::where('id', intval($id))->first();
|
||||
if (is_null($service))
|
||||
{
|
||||
App::abort(404);
|
||||
}
|
||||
|
||||
$service->fill($request->only(['name', 'service_type']));
|
||||
|
||||
foreach ($this->serviceTypeDependentFields as $field)
|
||||
{
|
||||
if ($request->has($field))
|
||||
{
|
||||
$service->$field = in_array($field, $this->fieldsToEncrypt)
|
||||
? encrypt($request->get($field))
|
||||
: $request->get($field);
|
||||
}
|
||||
}
|
||||
|
||||
$service->save();
|
||||
|
||||
return redirect(route('services.index'));
|
||||
}
|
||||
|
||||
private function callbackList()
|
||||
{
|
||||
$dropboxService = new DropboxService();
|
||||
|
||||
return [
|
||||
ExternalService::DROPBOX => $dropboxService->callbackUrl(),
|
||||
ExternalService::FACEBOOK => route('login_callback.facebook'),
|
||||
ExternalService::GOOGLE => route('login_callback.google'),
|
||||
ExternalService::TWITTER => route('login_callback.twitter')
|
||||
];
|
||||
}
|
||||
|
||||
private function isServiceInUse(ExternalService $service)
|
||||
{
|
||||
switch ($service->service_type)
|
||||
{
|
||||
case ExternalService::FACEBOOK:
|
||||
// Cannot delete Facebook service if it's set as the login provider
|
||||
$facebookConfig = Configuration::where('key', 'facebook_external_service_id')->first();
|
||||
return !is_null($facebookConfig) && intval($facebookConfig->value) == $service->id;
|
||||
|
||||
case ExternalService::GOOGLE:
|
||||
// Cannot delete Google service if it's set as the login provider
|
||||
$googleConfig = Configuration::where('key', 'google_external_service_id')->first();
|
||||
return !is_null($googleConfig) && intval($googleConfig->value) == $service->id;
|
||||
|
||||
case ExternalService::DROPBOX:
|
||||
return Storage::where('external_service_id', $service->id)->count() > 0;
|
||||
|
||||
case ExternalService::TWITTER:
|
||||
// Cannot delete Twitter service if it's set as the login provider
|
||||
$twitterConfig = Configuration::where('key', 'twitter_external_service_id')->first();
|
||||
return !is_null($twitterConfig) && intval($twitterConfig->value) == $service->id;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function serviceTypeList()
|
||||
{
|
||||
return [
|
||||
ExternalService::DROPBOX => trans(sprintf('services.%s', ExternalService::DROPBOX)),
|
||||
ExternalService::FACEBOOK => trans(sprintf('services.%s', ExternalService::FACEBOOK)),
|
||||
ExternalService::GOOGLE => trans(sprintf('services.%s', ExternalService::GOOGLE)),
|
||||
ExternalService::TWITTER => trans(sprintf('services.%s', ExternalService::TWITTER))
|
||||
];
|
||||
}
|
||||
|
||||
private function validReturnLocations()
|
||||
{
|
||||
return [
|
||||
'settings' => route('admin.settings')
|
||||
];
|
||||
}
|
||||
}
|
|
@ -2,12 +2,10 @@
|
|||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\ExternalService;
|
||||
use App\Facade\Theme;
|
||||
use App\Facade\UserConfig;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests;
|
||||
use App\Services\DropboxService;
|
||||
use App\Storage;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
@ -25,39 +23,7 @@ class StorageController extends Controller
|
|||
$this->middleware('auth');
|
||||
View::share('is_admin', true);
|
||||
|
||||
$this->encryptedFields = ['password', 'access_key', 'secret_key', 'access_token'];
|
||||
}
|
||||
|
||||
public function authoriseService(Request $request, $id)
|
||||
{
|
||||
$this->authorizeAccessToAdminPanel('admin:manage-storage');
|
||||
|
||||
$storage = Storage::where('id', intval($id))->first();
|
||||
if (is_null($storage))
|
||||
{
|
||||
return redirect(route('storages.index'));
|
||||
}
|
||||
|
||||
$externalServiceType = $this->getExternalServiceType($storage);
|
||||
|
||||
if (is_null($externalServiceType))
|
||||
{
|
||||
$request->session()->flash('error', trans('admin.storage_no_external_service_support'));
|
||||
return redirect(route('storages.index'));
|
||||
}
|
||||
|
||||
$serviceTypeName = trans(sprintf('services.%s', $externalServiceType));
|
||||
|
||||
switch ($externalServiceType)
|
||||
{
|
||||
case ExternalService::DROPBOX:
|
||||
$dropbox = new DropboxService();
|
||||
return redirect($dropbox->authoriseUrl($storage));
|
||||
|
||||
default:
|
||||
$request->session()->flash('error', trans('admin.storage_external_service_no_authorisation', ['service_name' => $serviceTypeName]));
|
||||
return redirect(route('storages.index'));
|
||||
}
|
||||
$this->encryptedFields = ['password', 'access_key', 'secret_key'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -89,15 +55,12 @@ class StorageController extends Controller
|
|||
$this->authorizeAccessToAdminPanel('admin:manage-storage');
|
||||
|
||||
$filesystemDefaultLocation = sprintf('%s/storage/app/albums', dirname(dirname(dirname(dirname(__DIR__)))));
|
||||
$storage = new Storage();
|
||||
$storage->s3_signed_urls = true;
|
||||
|
||||
return Theme::render('admin.create_storage', [
|
||||
'album_sources' => UserConfig::albumSources(),
|
||||
'dropbox_services' => ExternalService::getForService(ExternalService::DROPBOX),
|
||||
'filesystem_default_location' => $filesystemDefaultLocation,
|
||||
'info' => $request->session()->get('info'),
|
||||
'storage' => $storage
|
||||
'storage' => new Storage()
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -126,13 +89,11 @@ class StorageController extends Controller
|
|||
'cdn_url',
|
||||
'access_key',
|
||||
'secret_key',
|
||||
'b2_bucket_type',
|
||||
'external_service_id'
|
||||
'b2_bucket_type'
|
||||
]));
|
||||
$storage->is_active = true;
|
||||
$storage->is_default = (strtolower($request->get('is_default')) == 'on');
|
||||
$storage->is_internal = false;
|
||||
$storage->s3_signed_urls = (strtolower($request->get('s3_signed_urls')) == 'on');
|
||||
|
||||
if ($storage->source != 'LocalFilesystemSource' && isset($storage->location))
|
||||
{
|
||||
|
@ -154,17 +115,6 @@ class StorageController extends Controller
|
|||
$this->unsetIsDefaultFromOthers($storage);
|
||||
}
|
||||
|
||||
$externalServiceType = $this->getExternalServiceType($storage);
|
||||
|
||||
if (!is_null($externalServiceType))
|
||||
{
|
||||
switch ($externalServiceType)
|
||||
{
|
||||
case ExternalService::DROPBOX:
|
||||
return redirect(route('storage.authoriseService', ['storage' => $storage->id]));
|
||||
}
|
||||
}
|
||||
|
||||
return redirect(route('storage.index'));
|
||||
}
|
||||
|
||||
|
@ -237,10 +187,7 @@ class StorageController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
return Theme::render('admin.edit_storage', [
|
||||
'dropbox_services' => ExternalService::getForService(ExternalService::DROPBOX),
|
||||
'storage' => $storage
|
||||
]);
|
||||
return Theme::render('admin.edit_storage', ['storage' => $storage]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -272,12 +219,10 @@ class StorageController extends Controller
|
|||
'cdn_url',
|
||||
'access_key',
|
||||
'secret_key',
|
||||
'b2_bucket_type',
|
||||
'external_service_id'
|
||||
'b2_bucket_type'
|
||||
]));
|
||||
$storage->is_active = (strtolower($request->get('is_active')) == 'on');
|
||||
$storage->is_default = (strtolower($request->get('is_default')) == 'on');
|
||||
$storage->s3_signed_urls = (strtolower($request->get('s3_signed_urls')) == 'on');
|
||||
|
||||
if ($storage->is_default && !$storage->is_active)
|
||||
{
|
||||
|
@ -341,16 +286,6 @@ class StorageController extends Controller
|
|||
return redirect(route('storage.index'));
|
||||
}
|
||||
|
||||
private function getExternalServiceType(Storage $storage)
|
||||
{
|
||||
if (!is_null($storage->externalService))
|
||||
{
|
||||
return $storage->externalService->service_type;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function setIsDefaultForFirstStorage()
|
||||
{
|
||||
$count = Storage::where('is_default', true)->count();
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\ExternalService;
|
||||
use App\Facade\Theme;
|
||||
use App\Facade\UserConfig;
|
||||
use App\Helpers\MiscHelper;
|
||||
|
@ -153,12 +152,7 @@ class LoginController extends Controller
|
|||
*/
|
||||
public function redirectToFacebook()
|
||||
{
|
||||
$socialite = $this->setSocialiteConfigForFacebook();
|
||||
if (is_null($socialite))
|
||||
{
|
||||
return redirect(route('login'));
|
||||
}
|
||||
|
||||
$socialite = $this->setSocialiteConfigs();
|
||||
return $socialite->driver('facebook')->redirect();
|
||||
}
|
||||
|
||||
|
@ -169,12 +163,7 @@ class LoginController extends Controller
|
|||
*/
|
||||
public function redirectToGoogle()
|
||||
{
|
||||
$socialite = $this->setSocialiteConfigForGoogle();
|
||||
if (is_null($socialite))
|
||||
{
|
||||
return redirect(route('login'));
|
||||
}
|
||||
|
||||
$socialite = $this->setSocialiteConfigs();
|
||||
return $socialite->driver('google')->redirect();
|
||||
}
|
||||
|
||||
|
@ -185,12 +174,7 @@ class LoginController extends Controller
|
|||
*/
|
||||
public function redirectToTwitter()
|
||||
{
|
||||
$socialite = $this->setSocialiteConfigForTwitter();
|
||||
if (is_null($socialite))
|
||||
{
|
||||
return redirect(route('login'));
|
||||
}
|
||||
|
||||
$socialite = $this->setSocialiteConfigs();
|
||||
return $socialite->driver('twitter')->redirect();
|
||||
}
|
||||
|
||||
|
@ -201,12 +185,7 @@ class LoginController extends Controller
|
|||
*/
|
||||
public function handleFacebookCallback(Request $request)
|
||||
{
|
||||
$socialite = $this->setSocialiteConfigForFacebook();
|
||||
if (is_null($socialite))
|
||||
{
|
||||
return redirect(route('login'));
|
||||
}
|
||||
|
||||
$socialite = $this->setSocialiteConfigs();
|
||||
$facebookUser = $socialite->driver('facebook')->user();
|
||||
|
||||
return $this->processSocialMediaLogin($request, 'facebook_id', $facebookUser);
|
||||
|
@ -219,12 +198,7 @@ class LoginController extends Controller
|
|||
*/
|
||||
public function handleGoogleCallback(Request $request)
|
||||
{
|
||||
$socialite = $this->setSocialiteConfigForGoogle();
|
||||
if (is_null($socialite))
|
||||
{
|
||||
return redirect(route('login'));
|
||||
}
|
||||
|
||||
$socialite = $this->setSocialiteConfigs();
|
||||
$googleUser = $socialite->driver('google')->user();
|
||||
|
||||
return $this->processSocialMediaLogin($request, 'google_id', $googleUser);
|
||||
|
@ -237,30 +211,12 @@ class LoginController extends Controller
|
|||
*/
|
||||
public function handleTwitterCallback(Request $request)
|
||||
{
|
||||
$socialite = $this->setSocialiteConfigForTwitter();
|
||||
if (is_null($socialite))
|
||||
{
|
||||
return redirect(route('login'));
|
||||
}
|
||||
|
||||
$socialite = $this->setSocialiteConfigs();
|
||||
$twitterUser = $socialite->driver('twitter')->user();
|
||||
|
||||
return $this->processSocialMediaLogin($request, 'twitter_id', $twitterUser);
|
||||
}
|
||||
|
||||
private function getSocialMediaConfig($socialMediaEnabledField, $socialMediaExternalServiceIdField)
|
||||
{
|
||||
if (boolval(UserConfig::get($socialMediaEnabledField)))
|
||||
{
|
||||
$externalServiceID = intval(UserConfig::get($socialMediaExternalServiceIdField));
|
||||
$externalService = ExternalService::where('id', $externalServiceID)->first();
|
||||
|
||||
return $externalService;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function processSocialMediaLogin(Request $request, $socialMediaIdField, $socialMediaUser)
|
||||
{
|
||||
$userBySocialMediaId = User::where($socialMediaIdField, $socialMediaUser->getId())->first();
|
||||
|
@ -304,81 +260,38 @@ class LoginController extends Controller
|
|||
return redirect(route('auth.register_sso'));
|
||||
}
|
||||
|
||||
private function setSocialiteConfigForFacebook()
|
||||
private function setSocialiteConfigs()
|
||||
{
|
||||
$facebookConfig = $this->getSocialMediaConfig(
|
||||
'social_facebook_login',
|
||||
'facebook_external_service_id'
|
||||
);
|
||||
|
||||
if (is_null($facebookConfig))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Force Socialite to use our config from the database instead of hard-coded in config/services.php
|
||||
$socialite = app()->make(\Laravel\Socialite\Contracts\Factory::class);
|
||||
$socialite->extend(
|
||||
'facebook',
|
||||
function ($app) use ($socialite, $facebookConfig) {
|
||||
function ($app) use ($socialite) {
|
||||
$config = [
|
||||
'client_id' => trim(decrypt($facebookConfig->app_id)),
|
||||
'client_secret' => trim(decrypt($facebookConfig->app_secret)),
|
||||
'client_id' => trim(UserConfig::get('facebook_app_id')),
|
||||
'client_secret' => trim(decrypt(UserConfig::get('facebook_app_secret'))),
|
||||
'redirect' => route('login_callback.facebook')
|
||||
];
|
||||
return $socialite->buildProvider(FacebookProvider::class, $config);
|
||||
}
|
||||
);
|
||||
|
||||
return $socialite;
|
||||
}
|
||||
|
||||
private function setSocialiteConfigForGoogle()
|
||||
{
|
||||
$googleConfig = $this->getSocialMediaConfig(
|
||||
'social_google_login',
|
||||
'google_external_service_id'
|
||||
);
|
||||
|
||||
if (is_null($googleConfig))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
$socialite = app()->make(\Laravel\Socialite\Contracts\Factory::class);
|
||||
$socialite->extend(
|
||||
'google',
|
||||
function ($app) use ($socialite, $googleConfig) {
|
||||
function ($app) use ($socialite) {
|
||||
$config = [
|
||||
'client_id' => trim(decrypt($googleConfig->app_id)),
|
||||
'client_secret' => trim(decrypt($googleConfig->app_secret)),
|
||||
'client_id' => trim(UserConfig::get('google_app_id')),
|
||||
'client_secret' => trim(decrypt(UserConfig::get('google_app_secret'))),
|
||||
'redirect' => route('login_callback.google')
|
||||
];
|
||||
return $socialite->buildProvider(GoogleProvider::class, $config);
|
||||
}
|
||||
);
|
||||
|
||||
return $socialite;
|
||||
}
|
||||
|
||||
private function setSocialiteConfigForTwitter()
|
||||
{
|
||||
$twitterConfig = $this->getSocialMediaConfig(
|
||||
'social_twitter_login',
|
||||
'twitter_external_service_id'
|
||||
);
|
||||
|
||||
if (is_null($twitterConfig))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
$socialite = app()->make(\Laravel\Socialite\Contracts\Factory::class);
|
||||
$socialite->extend(
|
||||
'twitter',
|
||||
function ($app) use ($socialite, $twitterConfig) {
|
||||
function ($app) use ($socialite) {
|
||||
$config = [
|
||||
'identifier' => trim(decrypt($twitterConfig->app_id)),
|
||||
'secret' => trim(decrypt($twitterConfig->app_secret)),
|
||||
'identifier' => trim(UserConfig::get('twitter_app_id')),
|
||||
'secret' => trim(decrypt(UserConfig::get('twitter_app_secret'))),
|
||||
'callback_uri' => route('login_callback.twitter')
|
||||
];
|
||||
return new TwitterProvider($app['request'], new TwitterServer($config));
|
||||
|
|
|
@ -2,11 +2,14 @@
|
|||
|
||||
namespace App\Http\Controllers\Gallery;
|
||||
|
||||
use App\Album;
|
||||
use App\AlbumRedirect;
|
||||
use App\Facade\Theme;
|
||||
use App\Facade\UserConfig;
|
||||
use App\Helpers\ConfigHelper;
|
||||
use App\Helpers\DbHelper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests;
|
||||
use App\VisitorHit;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
@ -31,7 +34,7 @@ class AlbumController extends Controller
|
|||
}
|
||||
|
||||
$album = DbHelper::getAlbumById($redirect->album_id);
|
||||
return redirect($album->url(), 301);
|
||||
return redirect($album->url());
|
||||
}
|
||||
|
||||
$this->authorizeForUser($this->getUser(), 'view', $album);
|
||||
|
|
|
@ -6,15 +6,17 @@ use App\Album;
|
|||
use App\Facade\Theme;
|
||||
use App\Facade\UserConfig;
|
||||
use App\Helpers\DbHelper;
|
||||
use App\Helpers\MiscHelper;
|
||||
use app\Http\Controllers\Admin\AlbumController;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Middleware\VerifyCsrfToken;
|
||||
use App\Photo;
|
||||
use App\VisitorHit;
|
||||
use GuzzleHttp\Psr7\Stream;
|
||||
use Guzzle\Http\Mimetypes;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use function GuzzleHttp\Psr7\mimetype_from_extension;
|
||||
|
||||
class PhotoController extends Controller
|
||||
{
|
||||
|
@ -69,9 +71,8 @@ class PhotoController extends Controller
|
|||
});
|
||||
}
|
||||
|
||||
/** @var Stream $photoStream */
|
||||
$photoStream = $album->getAlbumSource()->fetchPhotoContent($photo, $thumbnail);
|
||||
$mimeType = mimetype_from_extension(pathinfo($photo->storage_file_name, PATHINFO_EXTENSION));
|
||||
$mimeType = Mimetypes::getInstance()->fromFilename($photo->storage_file_name);
|
||||
|
||||
return response()->stream(
|
||||
function() use ($photoStream)
|
||||
|
@ -80,7 +81,7 @@ class PhotoController extends Controller
|
|||
},
|
||||
200,
|
||||
[
|
||||
'Content-Length' => strlen($photoStream->getContents()),
|
||||
'Content-Length' => $photoStream->getContentLength(),
|
||||
'Content-Type' => $mimeType
|
||||
]
|
||||
);
|
||||
|
|
|
@ -19,10 +19,10 @@ class InstallController extends Controller
|
|||
public function administrator(StoreUserRequest $request)
|
||||
{
|
||||
// Validate we're at the required stage
|
||||
$stage = 2;
|
||||
$stage = 3;
|
||||
if (intval($request->session()->get('install_stage')) < $stage)
|
||||
{
|
||||
return redirect(route('install.database'));
|
||||
return redirect(route('install.check'));
|
||||
}
|
||||
|
||||
// If we already have an admin account, this step can be skipped
|
||||
|
@ -52,10 +52,71 @@ class InstallController extends Controller
|
|||
]);
|
||||
}
|
||||
|
||||
public function database(Request $request)
|
||||
public function check(Request $request)
|
||||
{
|
||||
// This is the first installation step therefore it doesn't need to verify the stage
|
||||
|
||||
if ($request->getMethod() == 'POST')
|
||||
{
|
||||
$request->session()->put('install_stage', 2);
|
||||
return redirect(route('install.database'));
|
||||
}
|
||||
|
||||
$canContinue = true;
|
||||
$runtimeMinimum = '7.0.0'; // this minimum is imposed by Laravel 5.5
|
||||
$runtimeVersion = phpversion();
|
||||
$phpIsValid = version_compare($runtimeVersion, $runtimeMinimum) >= 0;
|
||||
|
||||
if (!$phpIsValid)
|
||||
{
|
||||
$canContinue = false;
|
||||
}
|
||||
|
||||
$requiredModules = [
|
||||
'curl' => 'installer.php_modules.curl',
|
||||
'pdo_mysql' => 'installer.php_modules.mysql',
|
||||
'gd' => 'installer.php_modules.gd'
|
||||
];
|
||||
$availableModules = [];
|
||||
|
||||
foreach ($requiredModules as $key => $langString)
|
||||
{
|
||||
$availableModules[$key] = extension_loaded($key);
|
||||
if (!$availableModules[$key])
|
||||
{
|
||||
$canContinue = false;
|
||||
}
|
||||
}
|
||||
|
||||
$uploadLimit = MiscHelper::convertToBytes(ini_get('upload_max_filesize'));
|
||||
$postMaxSize = MiscHelper::convertToBytes(ini_get('post_max_size'));
|
||||
|
||||
$recommendedMinimum = 4 * 1024 * 1024;
|
||||
|
||||
return view('install.check', [
|
||||
'available_modules' => $availableModules,
|
||||
'can_continue' => $canContinue,
|
||||
'php_is_valid' => $phpIsValid,
|
||||
'php_version_current' => $runtimeVersion,
|
||||
'php_version_required' => $runtimeMinimum,
|
||||
'post_max_size' => ($postMaxSize / 1024 / 1024),
|
||||
'post_max_size_warning' => $postMaxSize < $recommendedMinimum,
|
||||
'recommended_minimum_upload' => ($recommendedMinimum / 1024 / 1024),
|
||||
'upload_limit' => ($uploadLimit / 1024 / 1024),
|
||||
'upload_limit_warning' => $uploadLimit < $recommendedMinimum,
|
||||
'required_modules' => $requiredModules
|
||||
]);
|
||||
}
|
||||
|
||||
public function database(Request $request)
|
||||
{
|
||||
// Validate we're at the required stage
|
||||
$stage = 2;
|
||||
if (intval($request->session()->get('install_stage')) < $stage)
|
||||
{
|
||||
return redirect(route('install.check'));
|
||||
}
|
||||
|
||||
if ($request->method() == 'POST')
|
||||
{
|
||||
$baseDirectory = dirname(dirname(dirname(__DIR__)));
|
||||
|
@ -101,7 +162,7 @@ class InstallController extends Controller
|
|||
// Default settings
|
||||
$this->setConfigurationForNewSystems();
|
||||
|
||||
$request->session()->put('install_stage', 2);
|
||||
$request->session()->put('install_stage', 3);
|
||||
return redirect(route('install.administrator'));
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
|
|
|
@ -14,6 +14,9 @@ use Illuminate\Support\Facades\Log;
|
|||
|
||||
class AppInstallation
|
||||
{
|
||||
private $baseDirectory;
|
||||
private $environmentFilePath;
|
||||
|
||||
/**
|
||||
* The application instance.
|
||||
*
|
||||
|
@ -30,6 +33,8 @@ class AppInstallation
|
|||
public function __construct(Application $app)
|
||||
{
|
||||
$this->app = $app;
|
||||
$this->baseDirectory = dirname(dirname(dirname(__DIR__)));
|
||||
$this->environmentFilePath = sprintf('%s/.env', $this->baseDirectory);
|
||||
}
|
||||
|
||||
public function handle(Request $request, Closure $next)
|
||||
|
@ -46,14 +51,6 @@ class AppInstallation
|
|||
// See if the successful flag has been written to the .env file
|
||||
$isAppInstalled = MiscHelper::getEnvironmentSetting('APP_INSTALLED');
|
||||
|
||||
// See if the vendors are out-of-date
|
||||
if ($this->isVendorUpdateRequired())
|
||||
{
|
||||
return $isAppInstalled
|
||||
? redirect('/update')
|
||||
: redirect('/install');
|
||||
}
|
||||
|
||||
if ($request->is('install/*'))
|
||||
{
|
||||
// Already in the installer
|
||||
|
@ -69,40 +66,26 @@ class AppInstallation
|
|||
if ($isAppInstalled)
|
||||
{
|
||||
// See if an update is necessary
|
||||
if ($this->updateDatabaseIfRequired())
|
||||
{
|
||||
return redirect($request->fullUrl());
|
||||
}
|
||||
$this->updateDatabaseIfRequired();
|
||||
|
||||
// App is configured, continue on
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
return redirect(route('install.database'));
|
||||
return redirect(route('install.check'));
|
||||
}
|
||||
|
||||
private function generateAppKey()
|
||||
{
|
||||
// Generate an application key and store to the .env file
|
||||
if (!file_exists(MiscHelper::getEnvironmentFilePath()))
|
||||
if (!file_exists($this->environmentFilePath))
|
||||
{
|
||||
$key = MiscHelper::randomString(32);
|
||||
file_put_contents(MiscHelper::getEnvironmentFilePath(), sprintf('APP_KEY=%s', $key) . PHP_EOL);
|
||||
file_put_contents($this->environmentFilePath, sprintf('APP_KEY=%s', $key) . PHP_EOL);
|
||||
app('config')->set(['app' => ['key' => $key]]);
|
||||
}
|
||||
}
|
||||
|
||||
private function isVendorUpdateRequired()
|
||||
{
|
||||
$vendorsVersionFilename = $this->app->basePath('vendor/version.txt');
|
||||
if (!file_exists($vendorsVersionFilename))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return trim(file_get_contents($vendorsVersionFilename)) != trim(config('app.version'));
|
||||
}
|
||||
|
||||
private function updateDatabaseIfRequired()
|
||||
{
|
||||
$versionNumber = UserConfig::getOrCreateModel('app_version');
|
||||
|
@ -155,10 +138,6 @@ class AppInstallation
|
|||
// Rebuild the permissions cache
|
||||
$helper = new PermissionsHelper();
|
||||
$helper->rebuildCache();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\ExternalService;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreServiceRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
$result = [];
|
||||
|
||||
switch ($this->method())
|
||||
{
|
||||
case 'POST':
|
||||
$result = [
|
||||
'name' => 'required|unique:external_services|max:255',
|
||||
'service_type' => 'required|max:255',
|
||||
];
|
||||
|
||||
switch ($this->get('service_type'))
|
||||
{
|
||||
case ExternalService::DROPBOX:
|
||||
case ExternalService::FACEBOOK:
|
||||
case ExternalService::GOOGLE:
|
||||
case ExternalService::TWITTER:
|
||||
// Standard OAuth services
|
||||
$result['app_id'] = 'sometimes|required';
|
||||
$result['app_secret'] = 'sometimes|required';
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'PATCH':
|
||||
case 'PUT':
|
||||
$serviceId = intval($this->segment(3));
|
||||
$service = ExternalService::find($serviceId);
|
||||
$result = [
|
||||
'name' => 'required|max:255|unique:external_services,name,' . $serviceId
|
||||
];
|
||||
|
||||
switch ($service->service_type)
|
||||
{
|
||||
case ExternalService::DROPBOX:
|
||||
case ExternalService::FACEBOOK:
|
||||
case ExternalService::GOOGLE:
|
||||
case ExternalService::TWITTER:
|
||||
// Standard OAuth services
|
||||
$result['app_id'] = 'sometimes|required';
|
||||
$result['app_secret'] = 'sometimes|required';
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
|
@ -65,16 +65,6 @@ class StoreStorageRequest extends FormRequest
|
|||
$result['service_region'] = 'sometimes|required';
|
||||
$result['container_name'] = 'sometimes|required';
|
||||
break;
|
||||
|
||||
case 'BackblazeB2Source':
|
||||
$result['access_key'] = 'sometimes|required';
|
||||
$result['secret_key'] = 'sometimes|required';
|
||||
$result['container_name'] = 'sometimes|required';
|
||||
break;
|
||||
|
||||
case 'DropboxSource':
|
||||
$result['external_service_id'] = 'sometimes|required';
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -113,16 +103,6 @@ class StoreStorageRequest extends FormRequest
|
|||
$result['service_region'] = 'sometimes|required';
|
||||
$result['container_name'] = 'sometimes|required';
|
||||
break;
|
||||
|
||||
case 'BackblazeB2Source':
|
||||
$result['access_key'] = 'sometimes|required';
|
||||
$result['secret_key'] = 'sometimes|required';
|
||||
$result['container_name'] = 'sometimes|required';
|
||||
break;
|
||||
|
||||
case 'DropboxSource':
|
||||
$result['external_service_id'] = 'sometimes|required';
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace App;
|
||||
|
||||
use App\AlbumSources\IAlbumSource;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
|
@ -111,14 +110,9 @@ class Photo extends Model
|
|||
|
||||
public function thumbnailUrl($thumbnailName = null, $cacheBust = true)
|
||||
{
|
||||
/** @var IAlbumSource $source */
|
||||
$source = $this->album->getAlbumSource();
|
||||
$sourceConfiguration = $source->getConfiguration();
|
||||
$url = $this->album->getAlbumSource()->getUrlToPhoto($this, $thumbnailName);
|
||||
|
||||
$url = $source->getUrlToPhoto($this, $thumbnailName);
|
||||
|
||||
// Cache busting doesn't work with S3 signed URLs
|
||||
if ($cacheBust && !$sourceConfiguration->s3_signed_urls)
|
||||
if ($cacheBust)
|
||||
{
|
||||
// Append the timestamp of the last update to avoid browser caching
|
||||
$theDate = is_null($this->updated_at) ? $this->created_at : $this->updated_at;
|
||||
|
|
|
@ -11,8 +11,9 @@ use App\Policies\AlbumPolicy;
|
|||
use App\Policies\PhotoPolicy;
|
||||
use App\Policies\UserPolicy;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
use function GuzzleHttp\Psr7\mimetype_from_extension;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
|
||||
class AuthServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
@ -65,10 +66,6 @@ class AuthServiceProvider extends ServiceProvider
|
|||
{
|
||||
return $this->userHasAdminPermission($user, 'manage-labels');
|
||||
});
|
||||
Gate::define('admin:manage-services', function ($user)
|
||||
{
|
||||
return $this->userHasAdminPermission($user, 'manage-services');
|
||||
});
|
||||
Gate::define('admin:manage-storage', function ($user)
|
||||
{
|
||||
return $this->userHasAdminPermission($user, 'manage-storage');
|
||||
|
|
|
@ -1,269 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Exceptions\DropboxRetryException;
|
||||
use App\Storage;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class DropboxService
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $accessToken;
|
||||
|
||||
/**
|
||||
* Configuration related to the Backblaze B2 service.
|
||||
* @var \Illuminate\Config\Repository|mixed
|
||||
*/
|
||||
private $config;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->config = config('services.dropbox');
|
||||
}
|
||||
|
||||
public function authoriseUrl(Storage $storage)
|
||||
{
|
||||
$service = $storage->externalService;
|
||||
$redirectUrl = $this->callbackUrl();
|
||||
|
||||
return sprintf(
|
||||
'%s?client_id=%s&response_type=code&redirect_uri=%s&state=%s',
|
||||
$this->config['authorise_url'],
|
||||
urlencode(decrypt($service->app_id)),
|
||||
urlencode($redirectUrl),
|
||||
urlencode(encrypt($storage->id))
|
||||
);
|
||||
}
|
||||
|
||||
public function callbackUrl()
|
||||
{
|
||||
return route('services.authoriseDropbox');
|
||||
}
|
||||
|
||||
public function deleteFile($pathOnStorage)
|
||||
{
|
||||
$dropboxData = ['path' => $pathOnStorage];
|
||||
|
||||
$deleteResult = $this->sendRequest(
|
||||
$this->config['delete_url'],
|
||||
'POST',
|
||||
$dropboxData,
|
||||
[
|
||||
'http_headers' => [
|
||||
'Content-Type: application/json'
|
||||
],
|
||||
'post_body_is_json' => true
|
||||
]
|
||||
);
|
||||
|
||||
Log::debug('DropboxService - response to deleteFile.', ['response' => $deleteResult, 'path' => $pathOnStorage]);
|
||||
}
|
||||
|
||||
public function downloadFile($pathOnStorage)
|
||||
{
|
||||
$dropboxArgs = ['path' => $pathOnStorage];
|
||||
|
||||
return $this->sendRequest(
|
||||
$this->config['download_url'],
|
||||
'POST',
|
||||
null,
|
||||
[
|
||||
'http_headers' => [
|
||||
sprintf('Dropbox-API-Arg: %s', json_encode($dropboxArgs)),
|
||||
'Content-Type: application/octet-stream'
|
||||
],
|
||||
'post_body_is_json' => false,
|
||||
'response_body_is_json' => false
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function handleAuthenticationResponse(Request $request, Storage $storage)
|
||||
{
|
||||
$authorisationCode = $request->query('code');
|
||||
|
||||
$storage->access_token = encrypt($this->convertAuthorisationCodeToToken($authorisationCode, $storage));
|
||||
$storage->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*/
|
||||
public function setAccessToken(string $accessToken)
|
||||
{
|
||||
$this->accessToken = $accessToken;
|
||||
}
|
||||
|
||||
public function uploadFile($pathToFileToUpload, $pathOnStorage)
|
||||
{
|
||||
$dropboxArgs = [
|
||||
'path' => $pathOnStorage,
|
||||
'mode' => 'overwrite',
|
||||
'mute' => true
|
||||
];
|
||||
|
||||
$shouldRetry = true;
|
||||
while ($shouldRetry)
|
||||
{
|
||||
try
|
||||
{
|
||||
$uploadResult = $this->sendRequest(
|
||||
$this->config['upload_url'],
|
||||
'POST',
|
||||
file_get_contents($pathToFileToUpload),
|
||||
[
|
||||
'http_headers' => [
|
||||
sprintf('Dropbox-API-Arg: %s', json_encode($dropboxArgs)),
|
||||
'Content-Type: application/octet-stream'
|
||||
],
|
||||
'post_body_is_json' => false
|
||||
]
|
||||
);
|
||||
|
||||
$shouldRetry = false;
|
||||
Log::debug('DropboxService - response to uploadFile.', ['response' => $uploadResult, 'path' => $pathOnStorage]);
|
||||
}
|
||||
catch (DropboxRetryException $dre)
|
||||
{
|
||||
// Retry - leave shouldRetry as true
|
||||
Log::debug('DropboxService - Dropbox reported a lock/rate limit and requested to retry');
|
||||
sleep(2);
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
$shouldRetry = false;
|
||||
Log::debug('DropboxService - exception in uploadFile.', ['exception' => $ex->getMessage()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function convertAuthorisationCodeToToken($authorisationCode, Storage $storage)
|
||||
{
|
||||
$service = $storage->externalService;
|
||||
$credentials = sprintf('%s:%s', decrypt($service->app_id), decrypt($service->app_secret));
|
||||
$redirectUrl = $this->callbackUrl();
|
||||
|
||||
$httpHeaders = [
|
||||
'Accept: application/json',
|
||||
sprintf('Authorization: Basic %s', base64_encode($credentials))
|
||||
];
|
||||
|
||||
$ch = curl_init($this->config['token_url']);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeaders);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, [
|
||||
'code' => $authorisationCode,
|
||||
'grant_type' => 'authorization_code',
|
||||
'redirect_uri' => $redirectUrl
|
||||
]);
|
||||
|
||||
$response = json_decode(curl_exec($ch));
|
||||
if (is_null($response) || $response === false)
|
||||
{
|
||||
throw new \Exception('Unable to read the response from Dropbox');
|
||||
}
|
||||
else if (isset($response->error_description))
|
||||
{
|
||||
throw new \Exception(sprintf('Error from Dropbox: %s', $response->error_description));
|
||||
}
|
||||
|
||||
return $response->access_token;
|
||||
}
|
||||
|
||||
private function getBasicHttpClient($url, $method = 'GET', array $httpHeaders = [])
|
||||
{
|
||||
$httpHeaders = array_merge(
|
||||
[
|
||||
'Accept: application/json',
|
||||
sprintf('Authorization: Bearer %s', $this->accessToken)
|
||||
],
|
||||
$httpHeaders
|
||||
);
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeaders);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
switch (strtoupper($method))
|
||||
{
|
||||
case 'GET':
|
||||
curl_setopt($ch, CURLOPT_HTTPGET, true);
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
break;
|
||||
}
|
||||
|
||||
return $ch;
|
||||
}
|
||||
|
||||
private function sendRequest($url, $method = 'GET', $postData = null, array $postOptions = [])
|
||||
{
|
||||
$postOptions = array_merge(
|
||||
[
|
||||
'http_headers' => [],
|
||||
'post_body_is_json' => true,
|
||||
'response_body_is_json' => true
|
||||
],
|
||||
$postOptions
|
||||
);
|
||||
$httpHeaders = $postOptions['http_headers'];
|
||||
|
||||
$ch = $this->getBasicHttpClient($url, $method, $httpHeaders);
|
||||
|
||||
Log::info(sprintf('DropboxService - %s: %s', strtoupper($method), $url));
|
||||
Log::debug('DropboxService - HTTP headers:', $httpHeaders);
|
||||
|
||||
if (!is_null($postData))
|
||||
{
|
||||
if ($postOptions['post_body_is_json'])
|
||||
{
|
||||
// Only log a post body if we have one and it's in JSON format (i.e. not a file upload)
|
||||
Log::debug('DropboxService - Body: ', $postData);
|
||||
$postData = json_encode($postData);
|
||||
}
|
||||
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
|
||||
}
|
||||
|
||||
$result = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
|
||||
Log::info(sprintf('DropboxService - Received HTTP code %d', $httpCode));
|
||||
|
||||
// Only log a result if we have one and it's in JSON format (i.e. not a file download)
|
||||
if (!is_null($result) && $result !== false && $postOptions['response_body_is_json'])
|
||||
{
|
||||
Log::debug($result);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if ($httpCode != 200 && $httpCode != 304)
|
||||
{
|
||||
if ($httpCode == 429)
|
||||
{
|
||||
throw new DropboxRetryException($httpCode, new \Exception(sprintf('Exception from Dropbox: %s', $result)));
|
||||
}
|
||||
|
||||
throw new \Exception(sprintf('Exception from Dropbox: %s', $result));
|
||||
}
|
||||
|
||||
return $postOptions['response_body_is_json']
|
||||
? json_decode($result)
|
||||
: $result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
curl_close($ch);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,34 +6,11 @@ class GiteaService
|
|||
{
|
||||
private $cacheFile = null;
|
||||
private $config = [];
|
||||
private $currentVersionNumber;
|
||||
|
||||
public function __construct(array $config = null, $currentVersionNumber = null)
|
||||
public function __construct()
|
||||
{
|
||||
// This class is used in the Bootstrapper to fetch release information, therefore
|
||||
// we need to check if the Laravel helper functions are loaded before we use them
|
||||
if (is_null($config) && function_exists('config'))
|
||||
{
|
||||
$this->config = config('services.gitea');
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
if (is_null($currentVersionNumber) && function_exists('config'))
|
||||
{
|
||||
$this->currentVersionNumber = config('app.version');
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->currentVersionNumber = $currentVersionNumber;
|
||||
}
|
||||
|
||||
if (function_exists('storage_path'))
|
||||
{
|
||||
$this->cacheFile = storage_path('app/gitea_cache.txt');
|
||||
}
|
||||
$this->config = config('services.gitea');
|
||||
$this->cacheFile = storage_path('app/gitea_cache.txt');
|
||||
}
|
||||
|
||||
public function checkForLatestRelease()
|
||||
|
@ -49,7 +26,7 @@ class GiteaService
|
|||
{
|
||||
// Lookup and store the version information
|
||||
$statusCode = -1;
|
||||
$result = $this->getReleasesFromGitea($statusCode);
|
||||
$result = $this->getLatestReleaseFromGitea($statusCode);
|
||||
|
||||
if ($statusCode == 200)
|
||||
{
|
||||
|
@ -74,31 +51,6 @@ class GiteaService
|
|||
return $cacheData;
|
||||
}
|
||||
|
||||
public function getSpecificRelease($versionNumber)
|
||||
{
|
||||
$cacheData = null;
|
||||
|
||||
// Lookup and store the version information
|
||||
$statusCode = -1;
|
||||
$result = $this->getReleasesFromGitea($statusCode);
|
||||
|
||||
if ($statusCode == 200)
|
||||
{
|
||||
$releases = json_decode($result[1]);
|
||||
|
||||
$foundRelease = null;
|
||||
foreach ($releases as $release)
|
||||
{
|
||||
if (version_compare($release->tag_name, $versionNumber) === 0)
|
||||
{
|
||||
return $release;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function doesCacheExist()
|
||||
{
|
||||
$exists = file_exists($this->cacheFile);
|
||||
|
@ -123,10 +75,10 @@ class GiteaService
|
|||
return json_decode(file_get_contents($this->cacheFile));
|
||||
}
|
||||
|
||||
private function getReleasesFromGitea(&$statusCode)
|
||||
private function getLatestReleaseFromGitea(&$statusCode)
|
||||
{
|
||||
$httpHeaders = [
|
||||
sprintf('User-Agent: aheathershaw/blue-twilight (v%s)', $this->currentVersionNumber)
|
||||
sprintf('User-Agent: aheathershaw/blue-twilight (v%s)', config('app.version'))
|
||||
];
|
||||
|
||||
if (isset($this->config['api_key']) && !empty($this->config['api_key']))
|
||||
|
@ -154,11 +106,7 @@ class GiteaService
|
|||
|
||||
private function setCacheData($data)
|
||||
{
|
||||
if (!is_null($this->cacheFile))
|
||||
{
|
||||
file_put_contents($this->cacheFile, json_encode(get_object_vars($data)));
|
||||
}
|
||||
|
||||
file_put_contents($this->cacheFile, json_encode(get_object_vars($data)));
|
||||
return $data;
|
||||
}
|
||||
}
|
|
@ -6,10 +6,12 @@ use App\Album;
|
|||
use App\AlbumSources\IAlbumSource;
|
||||
use App\AlbumSources\IAnalysisQueueSource;
|
||||
use App\Helpers\AnalysisQueueHelper;
|
||||
use App\Helpers\FileHelper;
|
||||
use App\Helpers\ImageHelper;
|
||||
use App\Helpers\MiscHelper;
|
||||
use App\Helpers\ThemeHelper;
|
||||
use App\Photo;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
|
||||
class PhotoService
|
||||
{
|
||||
|
@ -205,7 +207,7 @@ class PhotoService
|
|||
|
||||
$stream = $this->albumSource->fetchPhotoContent($this->photo);
|
||||
$stream->rewind();
|
||||
while (!$stream->eof())
|
||||
while (!$stream->feof())
|
||||
{
|
||||
fwrite($photoHandle, $stream->read(4096));
|
||||
}
|
||||
|
@ -324,7 +326,7 @@ class PhotoService
|
|||
|
||||
$stream = $this->albumSource->fetchPhotoContent($this->photo);
|
||||
$stream->rewind();
|
||||
while (!$stream->eof())
|
||||
while (!$stream->feof())
|
||||
{
|
||||
fwrite($photoHandle, $stream->read(4096));
|
||||
}
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Rackspace\Identity\v2;
|
||||
|
||||
use OpenStack\Common\Api\ApiInterface;
|
||||
|
||||
class Api implements ApiInterface
|
||||
{
|
||||
public function postTokenWithApiKey(): array
|
||||
{
|
||||
return [
|
||||
'method' => 'POST',
|
||||
'path' => 'tokens',
|
||||
'params' => [
|
||||
'username' => [
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'path' => 'auth.RAX-KSKEY:apiKeyCredentials',
|
||||
],
|
||||
'apiKey' => [
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'path' => 'auth.RAX-KSKEY:apiKeyCredentials',
|
||||
],
|
||||
'tenantId' => [
|
||||
'type' => 'string',
|
||||
'path' => 'auth',
|
||||
],
|
||||
'tenantName' => [
|
||||
'type' => 'string',
|
||||
'path' => 'auth',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Rackspace\Identity\v2;
|
||||
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use OpenStack\Common\Auth\IdentityService;
|
||||
use OpenStack\Common\Service\AbstractService;
|
||||
use OpenStack\Identity\v2\Models\Catalog;
|
||||
use OpenStack\Identity\v2\Models\Token;
|
||||
|
||||
/**
|
||||
* Represents the Rackspace Identity v2 service.
|
||||
*
|
||||
* @property Api $api
|
||||
*/
|
||||
class Service extends AbstractService implements IdentityService
|
||||
{
|
||||
public static function factory(ClientInterface $client): self
|
||||
{
|
||||
return new static($client, new Api());
|
||||
}
|
||||
|
||||
public function authenticate(array $options = []): array
|
||||
{
|
||||
$definition = $this->api->postTokenWithApiKey();
|
||||
|
||||
$response = $this->execute($definition, array_intersect_key($options, $definition['params']));
|
||||
|
||||
$token = $this->model(Token::class, $response);
|
||||
|
||||
$serviceUrl = $this->model(Catalog::class, $response)->getServiceUrl(
|
||||
$options['catalogName'],
|
||||
$options['catalogType'],
|
||||
$options['region'],
|
||||
$options['urlType']
|
||||
);
|
||||
|
||||
return [$token, $serviceUrl];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new authentication token.
|
||||
*
|
||||
* @param array $options {@see \OpenStack\Identity\v2\Api::postToken}
|
||||
*
|
||||
* @return Token
|
||||
*/
|
||||
public function generateToken(array $options = []): Token
|
||||
{
|
||||
$response = $this->execute($this->api->postTokenWithApiKey(), $options);
|
||||
|
||||
return $this->model(Token::class, $response);
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Rackspace\ObjectStoreCdn\v1;
|
||||
|
||||
use OpenStack\Common\Api\AbstractApi;
|
||||
|
||||
class Api extends AbstractApi
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->params = new Params();
|
||||
}
|
||||
|
||||
public function getAccount(): array
|
||||
{
|
||||
return [
|
||||
'method' => 'GET',
|
||||
'path' => '',
|
||||
'params' => [
|
||||
'format' => $this->params->format()
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Rackspace\ObjectStoreCdn\v1\Models;
|
||||
|
||||
use OpenStack\Common\Resource\OperatorResource;
|
||||
|
||||
class Container extends OperatorResource
|
||||
{
|
||||
/** @var bool */
|
||||
public $cdn_enabled;
|
||||
|
||||
/** @var string */
|
||||
public $cdn_ssl_uri;
|
||||
|
||||
/** @var string */
|
||||
public $name;
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Rackspace\ObjectStoreCdn\v1;
|
||||
|
||||
use OpenStack\Common\Api\AbstractParams;
|
||||
|
||||
class Params extends AbstractParams
|
||||
{
|
||||
public function format(): array
|
||||
{
|
||||
return [
|
||||
'location' => self::QUERY,
|
||||
'type' => self::STRING_TYPE,
|
||||
'description' => 'Defines the format of the collection. Will always default to `json`.',
|
||||
];
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Rackspace\ObjectStoreCdn\v1;
|
||||
|
||||
use App\Services\Rackspace\ObjectStoreCdn\v1\Models\Container;
|
||||
use OpenStack\Common\Service\AbstractService;
|
||||
|
||||
/**
|
||||
* Represents the Rackspace Cloud Files CDN v1 service.
|
||||
*
|
||||
* @property Api $api
|
||||
*/
|
||||
class Service extends AbstractService
|
||||
{
|
||||
/**
|
||||
* Retrieves a collection of CDN-enabled container resources in a generator format.
|
||||
*
|
||||
* @param array $options {@see Api::getAccount()}
|
||||
* @param callable|null $mapFn allows a function to be mapped over each element in the collection
|
||||
*/
|
||||
public function listContainers(array $options = [], callable $mapFn = null): \Generator
|
||||
{
|
||||
$options = array_merge($options, ['format' => 'json']);
|
||||
|
||||
return $this->model(Container::class)->enumerate($this->api->getAccount(), $options, $mapFn);
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Rackspace;
|
||||
|
||||
use App\Services\Rackspace\ObjectStoreCdn\v1\Service as ObjectStoreCdnService;
|
||||
use OpenStack\Common\Service\Builder;
|
||||
use OpenStack\OpenStack;
|
||||
|
||||
class Rackspace extends OpenStack
|
||||
{
|
||||
/** @var Builder */
|
||||
private $rsBuilder;
|
||||
|
||||
public function __construct(array $options = [], Builder $builder = null)
|
||||
{
|
||||
parent::__construct($options, $builder);
|
||||
|
||||
$this->rsBuilder = $builder ?: new Builder($options, 'App\Services\Rackspace');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Object Store (Rackspace CDN) v1 service.
|
||||
*
|
||||
* @param array $options options that will be used in configuring the service
|
||||
*/
|
||||
public function objectStoreCdnV1(array $options = []): ObjectStoreCdnService
|
||||
{
|
||||
$defaults = ['catalogName' => 'cloudFilesCDN', 'catalogType' => 'rax:object-cdn'];
|
||||
|
||||
return $this->rsBuilder->createService('ObjectStoreCdn\\v1', array_merge($defaults, $options));
|
||||
}
|
||||
}
|
|
@ -31,17 +31,11 @@ class Storage extends Model
|
|||
'cdn_url',
|
||||
'access_key',
|
||||
'secret_key',
|
||||
'b2_bucket_type',
|
||||
'external_service_id'
|
||||
'b2_bucket_type'
|
||||
];
|
||||
|
||||
public function albums()
|
||||
{
|
||||
return $this->hasMany(Album::class);
|
||||
}
|
||||
|
||||
public function externalService()
|
||||
{
|
||||
return $this->belongsTo(ExternalService::class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,60 +1,62 @@
|
|||
{
|
||||
"name": "aheathershaw/blue-twilight",
|
||||
"name": "pandy06269/blue-twilight",
|
||||
"description": "Blue Twilight - self-hosted photo gallery software.",
|
||||
"keywords": ["blue", "twilight", "photo", "photograph", "portfolio", "gallery", "self-hosted"],
|
||||
"license": "MIT",
|
||||
"type": "project",
|
||||
"require": {
|
||||
"php": ">=7.2.0",
|
||||
"php": ">=7.0.0",
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*",
|
||||
"aws/aws-sdk-php": "^3.134",
|
||||
"laravel/framework": "5.5.*",
|
||||
"rackspace/php-opencloud": "^1.16",
|
||||
"doctrine/dbal": "^2.5",
|
||||
"guzzlehttp/guzzle": "^6.5",
|
||||
"laravel/framework": "^6.0",
|
||||
"laravel/socialite": "^4.3",
|
||||
"php-amqplib/php-amqplib": "^2.9",
|
||||
"php-opencloud/openstack": "^3.0"
|
||||
"aws/aws-sdk-php": "^3.19",
|
||||
"laravel/socialite": "^3.0",
|
||||
"php-amqplib/php-amqplib": "^2.9"
|
||||
},
|
||||
"require-dev": {
|
||||
"facade/ignition": "^1.4",
|
||||
"fzaninotto/faker": "^1.9.1",
|
||||
"mockery/mockery": "^1.0",
|
||||
"phpunit/phpunit": "^8.0"
|
||||
"filp/whoops": "~2.0",
|
||||
"fzaninotto/faker": "~1.4",
|
||||
"mockery/mockery": "0.9.*",
|
||||
"phpunit/phpunit": "~6.0",
|
||||
"symfony/css-selector": "3.1.*",
|
||||
"symfony/dom-crawler": "3.1.*"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"database/data_migrations",
|
||||
"database/seeds",
|
||||
"database/factories"
|
||||
"database"
|
||||
],
|
||||
"psr-4": {
|
||||
"App\\": "app/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Tests\\": "tests/"
|
||||
}
|
||||
"classmap": [
|
||||
]
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"scripts": {
|
||||
"post-autoload-dump": [
|
||||
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||
"@php artisan package:discover --ansi"
|
||||
],
|
||||
"post-root-package-install": [
|
||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||
"php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||
],
|
||||
"post-create-project-cmd": [
|
||||
"@php artisan key:generate --ansi"
|
||||
"php artisan key:generate"
|
||||
],
|
||||
"post-install-cmd": [
|
||||
"Illuminate\\Foundation\\ComposerScripts::postInstall",
|
||||
"php artisan optimize"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"Illuminate\\Foundation\\ComposerScripts::postUpdate",
|
||||
"php artisan optimize"
|
||||
],
|
||||
"post-autoload-dump": [
|
||||
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||
"@php artisan package:discover"
|
||||
]
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"preferred-install": "dist",
|
||||
"discard-changes": true,
|
||||
"sort-packages": true
|
||||
"discard-changes": true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
return [
|
||||
// Version number of Blue Twilight
|
||||
'version' => '2.2.0-beta.2',
|
||||
'version' => '2.2.0-beta.1',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Hash Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default hash driver that will be used to hash
|
||||
| passwords for your application. By default, the bcrypt algorithm is
|
||||
| used; however, you remain free to modify this option if you wish.
|
||||
|
|
||||
| Supported: "bcrypt", "argon"
|
||||
|
|
||||
*/
|
||||
|
||||
'driver' => 'bcrypt',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Bcrypt Options
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the configuration options that should be used when
|
||||
| passwords are hashed using the Bcrypt algorithm. This will allow you
|
||||
| to control the amount of time it takes to hash the given password.
|
||||
|
|
||||
*/
|
||||
|
||||
'bcrypt' => [
|
||||
'rounds' => env('BCRYPT_ROUNDS', 10),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Argon Options
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the configuration options that should be used when
|
||||
| passwords are hashed using the Argon algorithm. These will allow you
|
||||
| to control the amount of time it takes to hash the given password.
|
||||
|
|
||||
*/
|
||||
|
||||
'argon' => [
|
||||
'memory' => 1024,
|
||||
'threads' => 2,
|
||||
'time' => 2,
|
||||
],
|
||||
|
||||
];
|
|
@ -1,81 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Monolog\Handler\StreamHandler;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Log Channel
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option defines the default log channel that gets used when writing
|
||||
| messages to the logs. The name specified in this option should match
|
||||
| one of the channels defined in the "channels" configuration array.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('LOG_CHANNEL', 'stack'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Log Channels
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the log channels for your application. Out of
|
||||
| the box, Laravel uses the Monolog PHP logging library. This gives
|
||||
| you a variety of powerful log handlers / formatters to utilize.
|
||||
|
|
||||
| Available Drivers: "single", "daily", "slack", "syslog",
|
||||
| "errorlog", "monolog",
|
||||
| "custom", "stack"
|
||||
|
|
||||
*/
|
||||
|
||||
'channels' => [
|
||||
'stack' => [
|
||||
'driver' => 'stack',
|
||||
'channels' => ['single'],
|
||||
],
|
||||
|
||||
'single' => [
|
||||
'driver' => 'single',
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
'level' => 'debug',
|
||||
],
|
||||
|
||||
'daily' => [
|
||||
'driver' => 'daily',
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
'level' => 'debug',
|
||||
'days' => 7,
|
||||
],
|
||||
|
||||
'slack' => [
|
||||
'driver' => 'slack',
|
||||
'url' => env('LOG_SLACK_WEBHOOK_URL'),
|
||||
'username' => 'Laravel Log',
|
||||
'emoji' => ':boom:',
|
||||
'level' => 'critical',
|
||||
],
|
||||
|
||||
'stderr' => [
|
||||
'driver' => 'monolog',
|
||||
'handler' => StreamHandler::class,
|
||||
'with' => [
|
||||
'stream' => 'php://stderr',
|
||||
],
|
||||
],
|
||||
|
||||
'syslog' => [
|
||||
'driver' => 'syslog',
|
||||
'level' => 'debug',
|
||||
],
|
||||
|
||||
'errorlog' => [
|
||||
'driver' => 'errorlog',
|
||||
'level' => 'debug',
|
||||
],
|
||||
],
|
||||
|
||||
];
|
|
@ -19,24 +19,12 @@ return [
|
|||
'download_token_lifetime' => 300
|
||||
],
|
||||
|
||||
'dropbox' => [
|
||||
'authorise_url' => 'https://www.dropbox.com/oauth2/authorize',
|
||||
'delete_url' => 'https://api.dropboxapi.com/2/files/delete_v2',
|
||||
'download_url' => 'https://content.dropboxapi.com/2/files/download',
|
||||
'token_url' => 'https://api.dropbox.com/oauth2/token',
|
||||
'upload_url' => 'https://content.dropboxapi.com/2/files/upload'
|
||||
],
|
||||
|
||||
'gitea' => [
|
||||
'api_url' => env('GITEA_API_URL', 'https://apps.andysh.uk/api/v1'),
|
||||
'api_url' => 'https://apps.andysh.uk/api/v1',
|
||||
'cache_time_seconds' => 3600,
|
||||
'releases_url' => env('GITEA_RELEASES_URL', 'https://apps.andysh.uk/%s/%s/releases'),
|
||||
'repo_name' => env('GITEA_REPO_NAME', 'blue-twilight'),
|
||||
'repo_owner' => env('GITEA_REPO_OWNER', 'aheathershaw')
|
||||
],
|
||||
|
||||
'rackspace' => [
|
||||
'authentication_url' => 'https://identity.api.rackspacecloud.com/v2.0'
|
||||
'releases_url' => 'https://apps.andysh.uk/%s/%s/releases',
|
||||
'repo_name' => 'blue-twilight',
|
||||
'repo_owner' => 'aheathershaw'
|
||||
],
|
||||
|
||||
'recaptcha' => [
|
||||
|
|
|
@ -1,112 +0,0 @@
|
|||
<?php
|
||||
|
||||
use App\Configuration;
|
||||
use App\DataMigration;
|
||||
use App\ExternalService;
|
||||
use App\Facade\UserConfig;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class DataMigrationV2_2_0_beta_2 extends DataMigration
|
||||
{
|
||||
public function getVersion()
|
||||
{
|
||||
return '2.2.0-beta.2';
|
||||
}
|
||||
|
||||
public function run($currentVersion)
|
||||
{
|
||||
DB::transaction(function()
|
||||
{
|
||||
$this->moveFacebookSettingsToService();
|
||||
$this->moveGoogleSettingsToService();
|
||||
$this->moveTwitterSettingsToService();
|
||||
});
|
||||
}
|
||||
|
||||
private function moveFacebookSettingsToService()
|
||||
{
|
||||
/** @var Configuration $facebookAppID */
|
||||
$facebookAppID = Configuration::where(['key' => 'facebook_app_id'])->first();
|
||||
|
||||
/** @var Configuration $facebookAppID */
|
||||
$facebookAppSecret = Configuration::where(['key' => 'facebook_app_secret'])->first();
|
||||
|
||||
if (is_null($facebookAppID) || is_null($facebookAppSecret))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$externalService = ExternalService::create([
|
||||
'service_type' => ExternalService::FACEBOOK,
|
||||
'name' => 'Facebook (migrated from settings)',
|
||||
'app_id' => encrypt($facebookAppID->value), // app ID needs to be encrypted now
|
||||
'app_secret' => $facebookAppSecret->value // secret is already encrypted
|
||||
]);
|
||||
|
||||
/** @var ExternalService $facebookExternalServiceConfig */
|
||||
$facebookExternalServiceConfig = UserConfig::getOrCreateModel('facebook_external_service_id');
|
||||
$facebookExternalServiceConfig->value = $externalService->id;
|
||||
$facebookExternalServiceConfig->save();
|
||||
|
||||
$facebookAppID->delete();
|
||||
$facebookAppSecret->delete();
|
||||
}
|
||||
|
||||
private function moveGoogleSettingsToService()
|
||||
{
|
||||
/** @var Configuration $googleAppID */
|
||||
$googleAppID = Configuration::where(['key' => 'google_app_id'])->first();
|
||||
|
||||
/** @var Configuration $facebookAppID */
|
||||
$googleAppSecret = Configuration::where(['key' => 'google_app_secret'])->first();
|
||||
|
||||
if (is_null($googleAppID) || is_null($googleAppSecret))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$externalService = ExternalService::create([
|
||||
'service_type' => ExternalService::GOOGLE,
|
||||
'name' => 'Google (migrated from settings)',
|
||||
'app_id' => encrypt($googleAppID->value), // app ID needs to be encrypted now
|
||||
'app_secret' => $googleAppSecret->value // secret is already encrypted
|
||||
]);
|
||||
|
||||
/** @var ExternalService $googleExternalServiceConfig */
|
||||
$googleExternalServiceConfig = UserConfig::getOrCreateModel('google_external_service_id');
|
||||
$googleExternalServiceConfig->value = $externalService->id;
|
||||
$googleExternalServiceConfig->save();
|
||||
|
||||
$googleAppID->delete();
|
||||
$googleAppSecret->delete();
|
||||
}
|
||||
|
||||
private function moveTwitterSettingsToService()
|
||||
{
|
||||
/** @var Configuration $twitterAppID */
|
||||
$twitterAppID = Configuration::where(['key' => 'twitter_app_id'])->first();
|
||||
|
||||
/** @var Configuration $facebookAppID */
|
||||
$twitterAppSecret = Configuration::where(['key' => 'twitter_app_secret'])->first();
|
||||
|
||||
if (is_null($twitterAppID) || is_null($twitterAppSecret))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$externalService = ExternalService::create([
|
||||
'service_type' => ExternalService::TWITTER,
|
||||
'name' => 'Twitter (migrated from settings)',
|
||||
'app_id' => encrypt($twitterAppID->value), // app ID needs to be encrypted now
|
||||
'app_secret' => $twitterAppSecret->value // secret is already encrypted
|
||||
]);
|
||||
|
||||
/** @var ExternalService $twitterExternalServiceConfig */
|
||||
$twitterExternalServiceConfig = UserConfig::getOrCreateModel('twitter_external_service_id');
|
||||
$twitterExternalServiceConfig->value = $externalService->id;
|
||||
$twitterExternalServiceConfig->save();
|
||||
|
||||
$twitterAppID->delete();
|
||||
$twitterAppSecret->delete();
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddStorageAccessTokenColumn extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('storages', function (Blueprint $table) {
|
||||
$table->text('access_token')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('storages', function (Blueprint $table) {
|
||||
$table->dropColumn('access_token');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddS3SignedUrlsColumnToStoragesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('storages', function (Blueprint $table) {
|
||||
$table->boolean('s3_signed_urls')->default(false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('storages', function (Blueprint $table) {
|
||||
$table->dropColumn('s3_signed_urls');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateExternalServicesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('external_services', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->string('service_type', 50);
|
||||
$table->string('name');
|
||||
$table->text('app_id')->nullable();
|
||||
$table->text('app_secret')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('external_services');
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddServiceToStoragesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('storages', function (Blueprint $table) {
|
||||
$table->unsignedInteger('external_service_id')->nullable();
|
||||
|
||||
$table->foreign('external_service_id')
|
||||
->references('id')
|
||||
->on('external_services');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('storages', function (Blueprint $table) {
|
||||
$table->dropForeign('storages_external_service_id_foreign');
|
||||
$table->dropColumn('external_service_id');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -80,14 +80,6 @@ class PermissionsSeeder extends Seeder
|
|||
'is_default' => false,
|
||||
'sort_order' => 0
|
||||
]);
|
||||
|
||||
// admin:manage-services = controls if external servies can be managed
|
||||
DatabaseSeeder::createOrUpdate('permissions', [
|
||||
'section' => 'admin',
|
||||
'description' => 'manage-services',
|
||||
'is_default' => false,
|
||||
'sort_order' => 0
|
||||
]);
|
||||
}
|
||||
|
||||
private function seedAlbumPermissions()
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace AppInstaller;
|
||||
|
||||
use App\Helpers\MiscHelper;
|
||||
|
||||
class AppRequirements
|
||||
{
|
||||
const STATUS_OK = 0;
|
||||
const STATUS_WARNING = 1;
|
||||
const STATUS_NOT_MET = 2;
|
||||
|
||||
public static function hasCurlLibrary()
|
||||
{
|
||||
return self::isModuleLoaded('curl') ? self::STATUS_OK : self::STATUS_NOT_MET;
|
||||
}
|
||||
|
||||
public static function hasGdLibrary()
|
||||
{
|
||||
return self::isModuleLoaded('gd') ? self::STATUS_OK : self::STATUS_NOT_MET;
|
||||
}
|
||||
|
||||
public static function hasMySqlClientLibrary()
|
||||
{
|
||||
return self::isModuleLoaded('pdo_mysql') ? self::STATUS_OK : self::STATUS_NOT_MET;
|
||||
}
|
||||
|
||||
public static function maxPostRequestSize(&$status)
|
||||
{
|
||||
$bytes = self::getPhpIniValueAsBytes('post_max_size');
|
||||
$recommendedMinimum = 4 * 1024 * 1024;
|
||||
|
||||
if ($bytes < $recommendedMinimum)
|
||||
{
|
||||
return self::STATUS_WARNING;
|
||||
}
|
||||
|
||||
$status = sprintf('%0.2f MB', $bytes / 1024 / 1024);
|
||||
return self::STATUS_OK;
|
||||
}
|
||||
|
||||
public static function maxUploadSize(&$status)
|
||||
{
|
||||
$bytes = self::getPhpIniValueAsBytes('upload_max_filesize');
|
||||
$recommendedMinimum = 4 * 1024 * 1024;
|
||||
|
||||
if ($bytes < $recommendedMinimum)
|
||||
{
|
||||
return self::STATUS_WARNING;
|
||||
}
|
||||
|
||||
$status = sprintf('%0.2f MB', $bytes / 1024 / 1024);
|
||||
return self::STATUS_OK;
|
||||
}
|
||||
|
||||
public static function php72OrLater(&$status)
|
||||
{
|
||||
$status = phpversion();
|
||||
return version_compare(phpversion(), '7.4.5', '>=') ? self::STATUS_OK : self::STATUS_NOT_MET;
|
||||
}
|
||||
|
||||
private static function getPhpIniValueAsBytes($settingName)
|
||||
{
|
||||
return MiscHelper::convertToBytes(ini_get($settingName));
|
||||
}
|
||||
|
||||
private static function isModuleLoaded($name)
|
||||
{
|
||||
return extension_loaded($name);
|
||||
}
|
||||
}
|
|
@ -1,394 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace AppInstaller;
|
||||
|
||||
use App\Services\GiteaService;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
/**
|
||||
* This class handles the downloading and extracting of the vendors directory.
|
||||
* Because Laravel and other vendors are not yet available, it uses "raw" PHP and the odd few classes within Blue
|
||||
* Twilight, such as GiteaService.
|
||||
*
|
||||
* @package AppInstaller
|
||||
*/
|
||||
class Installer
|
||||
{
|
||||
/**
|
||||
* Path to /app/config
|
||||
* @var string
|
||||
*/
|
||||
private $configDir;
|
||||
|
||||
/**
|
||||
* Path to /installer
|
||||
* @var string
|
||||
*/
|
||||
private $installerDir;
|
||||
|
||||
/**
|
||||
* True if we're upgrading Blue Twilight, false if it's a new install
|
||||
* @var bool
|
||||
*/
|
||||
private $isUpgrade;
|
||||
|
||||
/**
|
||||
* Path to / - the app's root
|
||||
* @var string
|
||||
*/
|
||||
private $rootDir;
|
||||
|
||||
/**
|
||||
* Path to /installer/temp
|
||||
* @var string
|
||||
*/
|
||||
private $tempDir;
|
||||
|
||||
/**
|
||||
* Path to /vendor
|
||||
* @var string
|
||||
*/
|
||||
private $vendorDir;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $versionNumber;
|
||||
|
||||
/**
|
||||
* Path to /installer/views
|
||||
* @var string
|
||||
*/
|
||||
private $viewsDir;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->installerDir = __DIR__;
|
||||
$this->rootDir = dirname($this->installerDir);
|
||||
|
||||
$this->configDir = sprintf('%s/config', $this->rootDir);
|
||||
$this->tempDir = sprintf('%s/temp', $this->installerDir);
|
||||
$this->vendorDir = sprintf('%s/vendor', $this->rootDir);
|
||||
$this->viewsDir = sprintf('%s/views', $this->installerDir);
|
||||
|
||||
$appConfig = require_once sprintf('%s/app.php', $this->configDir);
|
||||
$this->versionNumber = $appConfig['version'];
|
||||
}
|
||||
|
||||
public function handleRequest()
|
||||
{
|
||||
if (!isset($_GET['act']))
|
||||
{
|
||||
$this->checkInstallation();
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (trim($_GET['act']))
|
||||
{
|
||||
case 'download':
|
||||
$this->download();
|
||||
return;
|
||||
|
||||
case 'extract':
|
||||
$this->extract();
|
||||
return;
|
||||
|
||||
case 'finalise':
|
||||
$this->finalise();
|
||||
return;
|
||||
|
||||
default:
|
||||
throw new \Exception(sprintf('Action \'%s\' was not recognised.', $_GET['act']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $isUpgrade
|
||||
*/
|
||||
public function setIsUpgrade(bool $isUpgrade): void
|
||||
{
|
||||
$this->isUpgrade = $isUpgrade;
|
||||
}
|
||||
|
||||
protected function checkInstallation()
|
||||
{
|
||||
$requirements = [
|
||||
'core:php72OrLater',
|
||||
'php:hasCurlLibrary',
|
||||
'php:hasMySqlClientLibrary',
|
||||
'php:hasGdLibrary',
|
||||
'config:maxUploadSize',
|
||||
'config:maxPostRequestSize'
|
||||
];
|
||||
$requirementsGrouped = [];
|
||||
$canInstall = true;
|
||||
|
||||
foreach ($requirements as $requirement)
|
||||
{
|
||||
$requirementSplit = explode(':', $requirement);
|
||||
$groupName = $requirementSplit[0];
|
||||
$functionName = $requirementSplit[1];
|
||||
|
||||
$status = null;
|
||||
$result = call_user_func_array([AppRequirements::class, $functionName], [&$status]);
|
||||
|
||||
if ($result == AppRequirements::STATUS_NOT_MET)
|
||||
{
|
||||
$canInstall = false;
|
||||
}
|
||||
|
||||
if (!array_key_exists($groupName, $requirementsGrouped))
|
||||
{
|
||||
$requirementsGrouped[$groupName] = [];
|
||||
}
|
||||
|
||||
if (!array_key_exists($functionName, $requirementsGrouped[$groupName]))
|
||||
{
|
||||
$requirementsGrouped[$groupName][$functionName] = [
|
||||
'result' => $result,
|
||||
'status' => $status
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$requirementGroupNames = [
|
||||
'config' => 'PHP configuration',
|
||||
'core' => 'Core requirements',
|
||||
'php' => 'Required PHP modules'
|
||||
];
|
||||
|
||||
$requirementNames = [
|
||||
'hasCurlLibrary' => 'cURL Web Requests Library',
|
||||
'hasGdLibrary' => 'GD Graphics Procesisng Library',
|
||||
'hasMySqlClientLibrary' => 'MySQL PDO Data Access Library',
|
||||
'maxPostRequestSize' => 'Maximum POST request size (recommended: 4 MB)',
|
||||
'maxUploadSize' => 'Maximum file size allowed to upload (recommended: 4 MB)',
|
||||
'php72OrLater' => 'Requires PHP 7.2.0 minimum',
|
||||
];
|
||||
|
||||
$this->view('index', [
|
||||
'appName' => 'Blue Twilight Installer',
|
||||
'canInstall' => $canInstall,
|
||||
'isUpgrade' => $this->isUpgrade,
|
||||
'requirementGroupNames' => $requirementGroupNames,
|
||||
'requirementNames' => $requirementNames,
|
||||
'statusNotMet' => AppRequirements::STATUS_NOT_MET,
|
||||
'statusOK' => AppRequirements::STATUS_OK,
|
||||
'statusWarning' => AppRequirements::STATUS_WARNING,
|
||||
'systemRequirements' => $requirementsGrouped
|
||||
]);
|
||||
}
|
||||
|
||||
protected function download()
|
||||
{
|
||||
$servicesConfig = require_once sprintf('%s/services.php', $this->configDir);
|
||||
|
||||
$versionNumber = sprintf('v%s', $this->versionNumber);
|
||||
|
||||
$gitea = new GiteaService($servicesConfig['gitea'], $versionNumber);
|
||||
$releaseInfo = $gitea->getSpecificRelease($versionNumber);
|
||||
|
||||
if (is_null($releaseInfo))
|
||||
{
|
||||
throw new \Exception(sprintf('No release info found in Gitea for Blue Twilight version \'%s\'', $versionNumber));
|
||||
}
|
||||
else if (!isset($releaseInfo->assets))
|
||||
{
|
||||
throw new \Exception(sprintf('No assets found in Gitea for Blue Twilight version \'%s\'', $versionNumber));
|
||||
}
|
||||
|
||||
$vendorsPrefix = 'vendors';
|
||||
$vendorsSuffix = '.tar.gz';
|
||||
$selectedAsset = null;
|
||||
|
||||
foreach ($releaseInfo->assets as $asset)
|
||||
{
|
||||
/*
|
||||
Ignore anything that is not "vendors<something>.tar.gz" were the <something> is also optional - e.g.
|
||||
vendors_2.1.2.tar.gz
|
||||
vendors.tar.gz
|
||||
but NOT 2.1.2_vendors.zip
|
||||
*/
|
||||
if (!starts_with($asset->name, $vendorsPrefix) || !ends_with($asset->name, $vendorsSuffix))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$selectedAsset = $asset;
|
||||
break;
|
||||
}
|
||||
|
||||
if (is_null($selectedAsset))
|
||||
{
|
||||
throw new \Exception(sprintf('No vendors.tar.gz found in Gitea for Blue Twilight version \'%s\'', $versionNumber));
|
||||
}
|
||||
|
||||
$targetFileName = $this->getVendorsTempFileName();
|
||||
|
||||
$this->downloadFile($selectedAsset->browser_download_url, $targetFileName);
|
||||
$this->json([
|
||||
'result' => true,
|
||||
'fileName' => $targetFileName
|
||||
]);
|
||||
}
|
||||
|
||||
protected function extract()
|
||||
{
|
||||
$targetFileName = $this->getVendorsTempFileName();
|
||||
if (!file_exists($targetFileName) || !is_readable($targetFileName))
|
||||
{
|
||||
throw new \Exception(sprintf('The file \'%s\' does not exist or is not readable', $targetFileName));
|
||||
}
|
||||
|
||||
$phar = new \PharData($targetFileName);
|
||||
$phar->extractTo($this->rootDir, null, true);
|
||||
|
||||
// We should always have a vendor/autoload.php
|
||||
$vendorsTestFile = $this->getVendorsAutoloadFileName();
|
||||
|
||||
if (file_exists($vendorsTestFile))
|
||||
{
|
||||
$this->writeVersionFile();
|
||||
|
||||
$this->json([
|
||||
'result' => true,
|
||||
'testFile' => $vendorsTestFile
|
||||
]);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \Exception('The extraction failed');
|
||||
}
|
||||
}
|
||||
|
||||
protected function finalise()
|
||||
{
|
||||
$result = [
|
||||
'cacheFilesRemoved' => 0,
|
||||
'envFileCreated' => false
|
||||
];
|
||||
|
||||
$result['cacheFilesRemoved'] = $this->clearCacheIfExists();
|
||||
$result['envFileCreated'] = !$this->isUpgrade && $this->createEnvFileIfNotExist();
|
||||
|
||||
require sprintf('%s/bootstrap/autoload.php', $this->rootDir);
|
||||
|
||||
$app = require_once sprintf('%s/bootstrap/app.php', $this->rootDir);
|
||||
|
||||
$kernel = $app->make(\Illuminate\Contracts\Console\Kernel::class);
|
||||
$kernel->bootstrap();
|
||||
|
||||
if ($result['envFileCreated'])
|
||||
{
|
||||
Artisan::call('key:generate');
|
||||
}
|
||||
|
||||
$kernel->terminate(null, null);
|
||||
|
||||
$this->json($result);
|
||||
}
|
||||
|
||||
private function downloadFile($sourceURL, $targetFilename)
|
||||
{
|
||||
$urlHandle = @fopen($sourceURL, 'r');
|
||||
$tempFilename = @fopen($targetFilename, 'w');
|
||||
|
||||
if ($urlHandle === false)
|
||||
{
|
||||
throw new \Exception(sprintf('Failed downloading the file from %s', $sourceURL));
|
||||
}
|
||||
else if ($tempFilename === false)
|
||||
{
|
||||
throw new \Exception(sprintf('Failed opening the file \'%s\' for writing', $targetFilename));
|
||||
}
|
||||
|
||||
while (!feof($urlHandle))
|
||||
{
|
||||
$buffer = fread($urlHandle, 8192);
|
||||
fwrite($tempFilename, $buffer);
|
||||
}
|
||||
|
||||
@fclose($urlHandle);
|
||||
@fclose($tempFilename);
|
||||
}
|
||||
|
||||
private function clearCacheIfExists()
|
||||
{
|
||||
$filesDeleted = 0;
|
||||
$cacheDir = sprintf('%s/bootstrap/cache', $this->rootDir);
|
||||
|
||||
$di = new \DirectoryIterator($cacheDir);
|
||||
foreach ($di as $fileInfo)
|
||||
{
|
||||
if (@unlink($fileInfo->getRealPath()))
|
||||
{
|
||||
$filesDeleted++;
|
||||
}
|
||||
}
|
||||
|
||||
return $filesDeleted;
|
||||
}
|
||||
|
||||
private function createEnvFileIfNotExist()
|
||||
{
|
||||
$envFile = $this->getEnvFileName();
|
||||
|
||||
if (!file_exists($envFile))
|
||||
{
|
||||
copy($this->getEnvExampleFileName(), $envFile);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function getEnvExampleFileName()
|
||||
{
|
||||
return sprintf('%s/.env.example', $this->rootDir);
|
||||
}
|
||||
|
||||
private function getEnvFileName()
|
||||
{
|
||||
return sprintf('%s/.env', $this->rootDir);
|
||||
}
|
||||
|
||||
private function getVendorsAutoloadFileName()
|
||||
{
|
||||
return sprintf('%s/autoload.php', $this->vendorDir);
|
||||
}
|
||||
|
||||
private function getVendorsTempFileName()
|
||||
{
|
||||
return sprintf('%s/vendors.tar.gz', $this->tempDir);
|
||||
}
|
||||
|
||||
private function getVendorsVersionFileName()
|
||||
{
|
||||
return sprintf('%s/version.txt', $this->vendorDir);
|
||||
}
|
||||
|
||||
private function json($data)
|
||||
{
|
||||
echo json_encode($data);
|
||||
}
|
||||
|
||||
private function view($name, array $viewData = [])
|
||||
{
|
||||
$viewFile = sprintf('%s/%s.php', $this->viewsDir, $name);
|
||||
if (!file_exists($viewFile) || !is_readable($viewFile))
|
||||
{
|
||||
throw new \Exception(sprintf('ERROR: View file \'%s\' does not exist.', $viewFile));
|
||||
}
|
||||
|
||||
// Provide keys as variables - e.g. $viewData['something'] becomes accessible via $something
|
||||
extract($viewData);
|
||||
|
||||
require_once $viewFile;
|
||||
}
|
||||
|
||||
private function writeVersionFile()
|
||||
{
|
||||
file_put_contents($this->getVendorsVersionFileName(), $this->versionNumber . PHP_EOL);
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
"name": "aheathershaw/blue-twilight-installer",
|
||||
"description": "Installer for Blue Twilight - self-hosted photo gallery software.",
|
||||
"keywords": [
|
||||
"blue",
|
||||
"twilight",
|
||||
"photo",
|
||||
"photograph",
|
||||
"portfolio",
|
||||
"gallery",
|
||||
"self-hosted"
|
||||
],
|
||||
"license": "MIT",
|
||||
"type": "project",
|
||||
"require": {
|
||||
"php": ">=7.2.0",
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "../app/",
|
||||
"AppInstaller\\": "./"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
<?php
|
||||
|
||||
function ends_with($stringToCheck, $stringToFind)
|
||||
{
|
||||
return strlen($stringToCheck) >= strlen($stringToFind) &&
|
||||
substr(strtolower($stringToCheck), strlen($stringToCheck) - strlen($stringToFind), strlen($stringToFind)) == strtolower($stringToFind);
|
||||
}
|
||||
|
||||
/**
|
||||
* A crude implementation of a .env reader to allow the installer to have overriden values from .env.install.
|
||||
* @param $key
|
||||
* @param null $default
|
||||
*/
|
||||
function env($key, $default = null)
|
||||
{
|
||||
$envFilePath = sprintf('%s/.env.install', dirname(__DIR__));
|
||||
if (!file_exists($envFilePath))
|
||||
{
|
||||
return $default;
|
||||
}
|
||||
|
||||
return \App\Helpers\MiscHelper::getEnvironmentSetting($key, $envFilePath) ?? $default;
|
||||
}
|
||||
|
||||
function starts_with($stringToCheck, $stringToFind)
|
||||
{
|
||||
return strlen($stringToCheck) >= strlen($stringToFind) &&
|
||||
substr(strtolower($stringToCheck), 0, strlen($stringToFind)) == strtolower($stringToFind);
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInitae1de26c658d13c195b98449ea1bf6a1::getLoader();
|
|
@ -1,445 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
/**
|
||||
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
|
||||
*
|
||||
* $loader = new \Composer\Autoload\ClassLoader();
|
||||
*
|
||||
* // register classes with namespaces
|
||||
* $loader->add('Symfony\Component', __DIR__.'/component');
|
||||
* $loader->add('Symfony', __DIR__.'/framework');
|
||||
*
|
||||
* // activate the autoloader
|
||||
* $loader->register();
|
||||
*
|
||||
* // to enable searching the include path (eg. for PEAR packages)
|
||||
* $loader->setUseIncludePath(true);
|
||||
*
|
||||
* In this example, if you try to use a class in the Symfony\Component
|
||||
* namespace or one of its children (Symfony\Component\Console for instance),
|
||||
* the autoloader will first look for the class under the component/
|
||||
* directory, and it will then fallback to the framework/ directory if not
|
||||
* found before giving up.
|
||||
*
|
||||
* This class is loosely based on the Symfony UniversalClassLoader.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @see http://www.php-fig.org/psr/psr-0/
|
||||
* @see http://www.php-fig.org/psr/psr-4/
|
||||
*/
|
||||
class ClassLoader
|
||||
{
|
||||
// PSR-4
|
||||
private $prefixLengthsPsr4 = array();
|
||||
private $prefixDirsPsr4 = array();
|
||||
private $fallbackDirsPsr4 = array();
|
||||
|
||||
// PSR-0
|
||||
private $prefixesPsr0 = array();
|
||||
private $fallbackDirsPsr0 = array();
|
||||
|
||||
private $useIncludePath = false;
|
||||
private $classMap = array();
|
||||
private $classMapAuthoritative = false;
|
||||
private $missingClasses = array();
|
||||
private $apcuPrefix;
|
||||
|
||||
public function getPrefixes()
|
||||
{
|
||||
if (!empty($this->prefixesPsr0)) {
|
||||
return call_user_func_array('array_merge', $this->prefixesPsr0);
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getPrefixesPsr4()
|
||||
{
|
||||
return $this->prefixDirsPsr4;
|
||||
}
|
||||
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirsPsr0;
|
||||
}
|
||||
|
||||
public function getFallbackDirsPsr4()
|
||||
{
|
||||
return $this->fallbackDirsPsr4;
|
||||
}
|
||||
|
||||
public function getClassMap()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $classMap Class to filename map
|
||||
*/
|
||||
public function addClassMap(array $classMap)
|
||||
{
|
||||
if ($this->classMap) {
|
||||
$this->classMap = array_merge($this->classMap, $classMap);
|
||||
} else {
|
||||
$this->classMap = $classMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix, either
|
||||
* appending or prepending to the ones previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param array|string $paths The PSR-0 root directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*/
|
||||
public function add($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if (!$prefix) {
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirsPsr0
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$this->fallbackDirsPsr0,
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$first = $prefix[0];
|
||||
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
||||
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
|
||||
|
||||
return;
|
||||
}
|
||||
if ($prepend) {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixesPsr0[$first][$prefix]
|
||||
);
|
||||
} else {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$this->prefixesPsr0[$first][$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace, either
|
||||
* appending or prepending to the ones previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-4 base directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function addPsr4($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if (!$prefix) {
|
||||
// Register directories for the root namespace.
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirsPsr4
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$this->fallbackDirsPsr4,
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
||||
// Register directories for a new namespace.
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
} elseif ($prepend) {
|
||||
// Prepend directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixDirsPsr4[$prefix]
|
||||
);
|
||||
} else {
|
||||
// Append directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$this->prefixDirsPsr4[$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix,
|
||||
* replacing any others previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param array|string $paths The PSR-0 base directories
|
||||
*/
|
||||
public function set($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr0 = (array) $paths;
|
||||
} else {
|
||||
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace,
|
||||
* replacing any others previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-4 base directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setPsr4($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr4 = (array) $paths;
|
||||
} else {
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on searching the include path for class files.
|
||||
*
|
||||
* @param bool $useIncludePath
|
||||
*/
|
||||
public function setUseIncludePath($useIncludePath)
|
||||
{
|
||||
$this->useIncludePath = $useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to check if the autoloader uses the include path to check
|
||||
* for classes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getUseIncludePath()
|
||||
{
|
||||
return $this->useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off searching the prefix and fallback directories for classes
|
||||
* that have not been registered with the class map.
|
||||
*
|
||||
* @param bool $classMapAuthoritative
|
||||
*/
|
||||
public function setClassMapAuthoritative($classMapAuthoritative)
|
||||
{
|
||||
$this->classMapAuthoritative = $classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should class lookup fail if not found in the current class map?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isClassMapAuthoritative()
|
||||
{
|
||||
return $this->classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
|
||||
*
|
||||
* @param string|null $apcuPrefix
|
||||
*/
|
||||
public function setApcuPrefix($apcuPrefix)
|
||||
{
|
||||
$this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The APCu prefix in use, or null if APCu caching is not enabled.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getApcuPrefix()
|
||||
{
|
||||
return $this->apcuPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this instance as an autoloader.
|
||||
*
|
||||
* @param bool $prepend Whether to prepend the autoloader or not
|
||||
*/
|
||||
public function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this instance as an autoloader.
|
||||
*/
|
||||
public function unregister()
|
||||
{
|
||||
spl_autoload_unregister(array($this, 'loadClass'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given class or interface.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
* @return bool|null True if loaded, null otherwise
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
includeFile($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the path to the file where the class is defined.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return string|false The path if found, false otherwise
|
||||
*/
|
||||
public function findFile($class)
|
||||
{
|
||||
// class map lookup
|
||||
if (isset($this->classMap[$class])) {
|
||||
return $this->classMap[$class];
|
||||
}
|
||||
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
||||
return false;
|
||||
}
|
||||
if (null !== $this->apcuPrefix) {
|
||||
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
|
||||
if ($hit) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
$file = $this->findFileWithExtension($class, '.php');
|
||||
|
||||
// Search for Hack files if we are running on HHVM
|
||||
if (false === $file && defined('HHVM_VERSION')) {
|
||||
$file = $this->findFileWithExtension($class, '.hh');
|
||||
}
|
||||
|
||||
if (null !== $this->apcuPrefix) {
|
||||
apcu_add($this->apcuPrefix.$class, $file);
|
||||
}
|
||||
|
||||
if (false === $file) {
|
||||
// Remember that this class does not exist.
|
||||
$this->missingClasses[$class] = true;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
private function findFileWithExtension($class, $ext)
|
||||
{
|
||||
// PSR-4 lookup
|
||||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
||||
|
||||
$first = $class[0];
|
||||
if (isset($this->prefixLengthsPsr4[$first])) {
|
||||
$subPath = $class;
|
||||
while (false !== $lastPos = strrpos($subPath, '\\')) {
|
||||
$subPath = substr($subPath, 0, $lastPos);
|
||||
$search = $subPath.'\\';
|
||||
if (isset($this->prefixDirsPsr4[$search])) {
|
||||
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
|
||||
foreach ($this->prefixDirsPsr4[$search] as $dir) {
|
||||
if (file_exists($file = $dir . $pathEnd)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-4 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr4 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 lookup
|
||||
if (false !== $pos = strrpos($class, '\\')) {
|
||||
// namespaced class name
|
||||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
||||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
||||
} else {
|
||||
// PEAR-like class name
|
||||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
||||
}
|
||||
|
||||
if (isset($this->prefixesPsr0[$first])) {
|
||||
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
|
||||
if (0 === strpos($class, $prefix)) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr0 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 include paths.
|
||||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope isolated include.
|
||||
*
|
||||
* Prevents access to $this/self from included files.
|
||||
*/
|
||||
function includeFile($file)
|
||||
{
|
||||
include $file;
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
|
||||
Copyright (c) Nils Adermann, Jordi Boggiano
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<?php
|
||||
|
||||
// autoload_classmap.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
|
@ -1,9 +0,0 @@
|
|||
<?php
|
||||
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
|
@ -1,11 +0,0 @@
|
|||
<?php
|
||||
|
||||
// autoload_psr4.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'App\\' => array($baseDir . '/../app'),
|
||||
'AppInstaller\\' => array($baseDir . '/'),
|
||||
);
|
|
@ -1,52 +0,0 @@
|
|||
<?php
|
||||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInitae1de26c658d13c195b98449ea1bf6a1
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInitae1de26c658d13c195b98449ea1bf6a1', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInitae1de26c658d13c195b98449ea1bf6a1', 'loadClassLoader'));
|
||||
|
||||
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
|
||||
if ($useStaticLoader) {
|
||||
require_once __DIR__ . '/autoload_static.php';
|
||||
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInitae1de26c658d13c195b98449ea1bf6a1::getInitializer($loader));
|
||||
} else {
|
||||
$map = require __DIR__ . '/autoload_namespaces.php';
|
||||
foreach ($map as $namespace => $path) {
|
||||
$loader->set($namespace, $path);
|
||||
}
|
||||
|
||||
$map = require __DIR__ . '/autoload_psr4.php';
|
||||
foreach ($map as $namespace => $path) {
|
||||
$loader->setPsr4($namespace, $path);
|
||||
}
|
||||
|
||||
$classMap = require __DIR__ . '/autoload_classmap.php';
|
||||
if ($classMap) {
|
||||
$loader->addClassMap($classMap);
|
||||
}
|
||||
}
|
||||
|
||||
$loader->register(true);
|
||||
|
||||
return $loader;
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
<?php
|
||||
|
||||
// autoload_static.php @generated by Composer
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInitae1de26c658d13c195b98449ea1bf6a1
|
||||
{
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'A' =>
|
||||
array (
|
||||
'App\\' => 4,
|
||||
'AppInstaller\\' => 13,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'App\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/../..' . '/../app',
|
||||
),
|
||||
'AppInstaller\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/../..' . '/',
|
||||
),
|
||||
);
|
||||
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
{
|
||||
return \Closure::bind(function () use ($loader) {
|
||||
$loader->prefixLengthsPsr4 = ComposerStaticInitae1de26c658d13c195b98449ea1bf6a1::$prefixLengthsPsr4;
|
||||
$loader->prefixDirsPsr4 = ComposerStaticInitae1de26c658d13c195b98449ea1bf6a1::$prefixDirsPsr4;
|
||||
|
||||
}, null, ClassLoader::class);
|
||||
}
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<link rel="stylesheet" href="../css/blue-twilight.min.css">
|
||||
<link rel="stylesheet" href="../themes/default/theme.css">
|
||||
<title><?php echo $appName; ?></title>
|
||||
<style type="text/css">
|
||||
* {
|
||||
font-family: "Raleway", sans-serif;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar bg-primary navbar-dark">
|
||||
<a class="navbar-brand" href="" style="color: #fff;"><i class="fa fa-fw fa-image"></i> Blue Twilight - Install</a>
|
||||
<div class="collapse navbar-collapse" id="navbar-content">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container" id="bootstrapper">
|
||||
<h3>Welcome to Blue Twilight - the self-hosted PHP photo gallery.</h3>
|
||||
<p>Your application/PHP environment have been checked and the results are below. Please correct any failed items before continuing.</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8 offset-md-2">
|
||||
<div class="mt-4" v-if="!isRunning">
|
||||
<div class="alert alert-info">
|
||||
Blue Twilight will automatically download the required third-party libraries when you
|
||||
click Continue.
|
||||
</div>
|
||||
|
||||
<?php foreach ($systemRequirements as $groupName => $items): ?>
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<p class="m-0"><b><?php echo $requirementGroupNames[$groupName]; ?></b></p>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<table class="table mb-0">
|
||||
<tbody>
|
||||
<?php foreach ($items as $itemName => $result): ?>
|
||||
<tr>
|
||||
<td style="width: 75%;"><?php echo $requirementNames[$itemName]; ?></td>
|
||||
<td style="width: 25%;">
|
||||
<?php if ($result['result'] == $statusOK): ?>
|
||||
<i class="fas fa-check text-success mr-2"></i>
|
||||
<?php elseif ($result['result'] == $statusNotMet): ?>
|
||||
<i class="fas fa-times text-danger mr-2"></i>
|
||||
<?php endif; ?>
|
||||
<?php echo $result['status'] ?? ''; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php if ($canInstall): ?>
|
||||
<p class="mb-0 mt-4 text-right"><button class="btn btn-success" @click.prevent="bootstrap"><i class="fas fa-check"></i> Continue</button></p>
|
||||
<?php else: ?>
|
||||
<div class="alert alert-danger">
|
||||
Blue Twilight cannot be installed until the issues identified above are rectified.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="mt-5" v-else>
|
||||
<ul v-cloak>
|
||||
<li class="operation mb-3" v-for="operation in operations">
|
||||
<div class="status mr-1">
|
||||
<img src="images/waiting.svg" v-if="!operation.isRunning && !operation.isCompleted"></img>
|
||||
<img src="images/loading.svg" v-if="operation.isRunning && !operation.isCompleted"/>
|
||||
<img src="images/completed.svg" v-if="!operation.isRunning && operation.isCompleted"></img>
|
||||
</div>
|
||||
<span v-text="operation.name"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="../js/blue-twilight.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(function()
|
||||
{
|
||||
var vm = new BootstrapperViewModel();
|
||||
var app = new Vue(vm);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
32
package.json
|
@ -1,15 +1,25 @@
|
|||
{
|
||||
"name": "blue-twilight",
|
||||
"version": "2.2.0-beta.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"prod": "gulp --production",
|
||||
"dev": "gulp watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt": "^1.0.4",
|
||||
"grunt-contrib-clean": "^2.0.0",
|
||||
"grunt-contrib-concat": "^1.0.1",
|
||||
"grunt-contrib-cssmin": "^3.0.0",
|
||||
"grunt-contrib-uglify": "^4.0.1",
|
||||
"grunt-curl": "^2.5.1",
|
||||
"grunt-dart-sass": "^1.1.3",
|
||||
"grunt-exec": "^3.0.0",
|
||||
"node-sass": "^4.13.0"
|
||||
"bootstrap-sass": "^3.3.7",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-copy": "^1.0.0",
|
||||
"gulp-help": "^1.6.1",
|
||||
"gulp-rename": "^1.2.2",
|
||||
"gulp-uglify-es": "^0.1.3",
|
||||
"uglify-js": "^3.0.28",
|
||||
"gulp-uglifycss": "^1.0.8",
|
||||
"jquery": "^3.1.0",
|
||||
"laravel-elixir": "^6.0.0-14",
|
||||
"laravel-elixir-vue-2": "^0.3.0",
|
||||
"laravel-elixir-webpack-official": "^1.0.2",
|
||||
"lodash": "^4.16.2",
|
||||
"vue": "^2.0.1",
|
||||
"vue-resource": "^1.0.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
<?php
|
||||
|
||||
function b2_authorize_account()
|
||||
{
|
||||
$application_key_id = "0023254ec9bda08000000000a"; // Obtained from your B2 account page
|
||||
$application_key = "K002eARNPUlxdj1XaVJbwEYPMz0c7e8"; // Obtained from your B2 account page
|
||||
$credentials = base64_encode($application_key_id . ":" . $application_key);
|
||||
$url = "https://api.backblazeb2.com/b2api/v2/b2_authorize_account";
|
||||
|
||||
$session = curl_init($url);
|
||||
|
||||
// Add headers
|
||||
$headers = array();
|
||||
$headers[] = "Accept: application/json";
|
||||
$headers[] = "Authorization: Basic " . $credentials;
|
||||
curl_setopt($session, CURLOPT_HTTPHEADER, $headers); // Add headers
|
||||
|
||||
curl_setopt($session, CURLOPT_HTTPGET, true); // HTTP GET
|
||||
curl_setopt($session, CURLOPT_RETURNTRANSFER, true); // Receive server response
|
||||
$server_output = curl_exec($session);
|
||||
curl_close ($session);
|
||||
echo ($server_output);
|
||||
|
||||
return json_decode($server_output);
|
||||
}
|
||||
|
||||
function b2_download_file_by_id($download_url, $auth_token)
|
||||
{
|
||||
//$download_url = ""; // From b2_authorize_account call
|
||||
$file_id = "4_z731245f41efc196b6dda0018_f116729ca6de74b38_d20190910_m132847_c002_v0001127_t0021"; // The ID of the file you want to download
|
||||
$uri = $download_url . "/b2api/v2/b2_download_file_by_id?fileId=" . $file_id;
|
||||
|
||||
$session = curl_init($uri);
|
||||
|
||||
curl_setopt($session, CURLOPT_HTTPGET, true); // HTTP GET
|
||||
curl_setopt($session, CURLOPT_RETURNTRANSFER, true); // Receive server response
|
||||
|
||||
echo '<p>' . $uri . '</p>';
|
||||
|
||||
$server_output = curl_exec($session); // Let's do this!
|
||||
|
||||
if (curl_getinfo($session, CURLINFO_HTTP_CODE) != 200)
|
||||
{
|
||||
echo '<p>' . $server_output . '</p>';
|
||||
}
|
||||
else
|
||||
{
|
||||
echo '<p>' . (strlen($server_output) . ' bytes received') . '</p>'; // Tell me about the rabbits, George!
|
||||
}
|
||||
|
||||
curl_close ($session); // Clean up
|
||||
|
||||
//$download_url = ""; // From b2_authorize_account call
|
||||
$file_id = "4_z731245f41efc196b6dda0018_f116729ca6de74b38_d20190910_m132847_c002_v0001127_t0021"; // The ID of the file you want to download
|
||||
$uri = $download_url . "/b2api/v2/b2_download_file_by_id?fileId=" . $file_id . '&Authorization=' . $auth_token;
|
||||
|
||||
$session = curl_init($uri);
|
||||
|
||||
curl_setopt($session, CURLOPT_HTTPGET, true); // HTTP GET
|
||||
curl_setopt($session, CURLOPT_RETURNTRANSFER, true); // Receive server response
|
||||
|
||||
echo '<p>' . $uri . '</p>';
|
||||
|
||||
$server_output = curl_exec($session); // Let's do this!
|
||||
|
||||
if (curl_getinfo($session, CURLINFO_HTTP_CODE) != 200)
|
||||
{
|
||||
echo '<p>' . $server_output . '</p>';
|
||||
}
|
||||
else
|
||||
{
|
||||
echo '<p>' . (strlen($server_output) . ' bytes received') . '</p>'; // Tell me about the rabbits, George!
|
||||
}
|
||||
|
||||
curl_close ($session); // Clean up
|
||||
}
|
||||
|
||||
function b2_download_file_by_name($download_url, $auth_token)
|
||||
{
|
||||
//$download_url = ""; // From b2_authorize_account call
|
||||
$bucket_name = "andysh-bt-test"; // The NAME of the bucket you want to download from
|
||||
$file_name = "B2-Test-Album/preview/7tgoy55do1vjv180ytlp.jpeg"; // The name of the file you want to download
|
||||
$uri = $download_url . "/file/" . $bucket_name . "/" . $file_name;
|
||||
|
||||
$session = curl_init($uri);
|
||||
|
||||
curl_setopt($session, CURLOPT_HTTPGET, true); // HTTP GET
|
||||
curl_setopt($session, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($session, CURLOPT_RETURNTRANSFER, true); // Receive server response
|
||||
|
||||
echo '<p>' . $uri . '</p>';
|
||||
|
||||
$server_output = curl_exec($session); // Let's do this!
|
||||
|
||||
if (curl_getinfo($session, CURLINFO_HTTP_CODE) != 200)
|
||||
{
|
||||
echo '<p>' . $server_output . '</p>';
|
||||
}
|
||||
else
|
||||
{
|
||||
echo '<p>' . (strlen($server_output) . ' bytes received') . '</p>'; // Tell me about the rabbits, George!
|
||||
}
|
||||
|
||||
curl_close ($session); // Clean up
|
||||
|
||||
// You will need to use the account authorization token if your bucket's type is allPrivate.
|
||||
|
||||
//$download_url = ""; // From b2_authorize_account call
|
||||
$bucket_name = "andysh-bt-test"; // The NAME of the bucket you want to download from
|
||||
$file_name = "B2-Test-Album/preview/7tgoy55do1vjv180ytlp.jpeg"; // The name of the file you want to download
|
||||
//$auth_token = ""; // From b2_authorize_account call
|
||||
$uri = $download_url . "/file/" . $bucket_name . "/" . $file_name . '?Authorization=' . $auth_token;
|
||||
|
||||
$session = curl_init($uri);
|
||||
|
||||
curl_setopt($session, CURLOPT_HTTPGET, true); // HTTP POST
|
||||
curl_setopt($session, CURLOPT_RETURNTRANSFER, true); // Receive server response
|
||||
|
||||
echo '<p>' . $uri . '</p>';
|
||||
|
||||
$server_output = curl_exec($session); // Let's do this!
|
||||
|
||||
if (curl_getinfo($session, CURLINFO_HTTP_CODE) != 200)
|
||||
{
|
||||
echo '<p>' . $server_output . '</p>';
|
||||
}
|
||||
else
|
||||
{
|
||||
echo '<p>' . (strlen($server_output) . ' bytes received') . '</p>'; // Tell me about the rabbits, George!
|
||||
}
|
||||
|
||||
curl_close ($session); // Clean up
|
||||
}
|
||||
|
||||
?>
|
||||
<h2>b2_authorize_account</h2>
|
||||
<?php $authorize_account_result = b2_authorize_account(); ?>
|
||||
|
||||
<h2>b2_download_file_by_name</h2>
|
||||
<?php b2_download_file_by_name($authorize_account_result->downloadUrl, $authorize_account_result->authorizationToken); ?>
|
||||
|
||||
<h2>b2_download_file_by_id</h2>
|
||||
<?php b2_download_file_by_id($authorize_account_result->downloadUrl, $authorize_account_result->authorizationToken); ?>
|
|
@ -10,9 +10,7 @@
|
|||
/* Added by Andy - check to see if Composer/vendors are installed */
|
||||
if (!file_exists(__DIR__.'/../vendor/autoload.php'))
|
||||
{
|
||||
$currentUrl = $_SERVER['PHP_SELF']; // e.g. /some/directory/index.php
|
||||
$bootstrapUrl = sprintf('%sinstall', dirname($currentUrl));
|
||||
header(sprintf('Location: %s', $bootstrapUrl));
|
||||
header('Location: install.php');
|
||||
exit();
|
||||
}
|
||||
/* End Added by Andy */
|
||||
|
|
|
@ -0,0 +1,256 @@
|
|||
<?php
|
||||
|
||||
namespace BtwInstaller;
|
||||
|
||||
class BlueTwilightInstaller
|
||||
{
|
||||
private $baseDirectory;
|
||||
private $composerSignature;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->baseDirectory = dirname(__DIR__);
|
||||
chdir($this->baseDirectory);
|
||||
putenv('HOME=' . $this->baseDirectory);
|
||||
|
||||
// Display errors so installer never gets a WSOD!
|
||||
ini_set('display_errors', true);
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
if (strtoupper($_SERVER['REQUEST_METHOD']) == 'POST')
|
||||
{
|
||||
// Handle post
|
||||
$this->runInstall();
|
||||
exit();
|
||||
}
|
||||
?>
|
||||
<html>
|
||||
<head>
|
||||
<title>Blue Twilight Setup</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Blue Twilight Setup</h1>
|
||||
<p>We need to download a few things - namely <a href="http://getcomposer.org" target="_blank">Composer</a> and related packages - before we can kick off the Blue Twilight installer.</p>
|
||||
<p>We can do this for you - simply click the button below.</p>
|
||||
<p style="font-weight: bold; color: #ff0000;">This can take a few minutes so please be patient, and only click the button once!</p>
|
||||
<form method="post">
|
||||
<button type="submit">Install Composer and dependencies for me</button>
|
||||
</form>
|
||||
|
||||
<hr/>
|
||||
<h2>Got Composer?</h2>
|
||||
|
||||
<p>If you already have Composer installed, however, you may want to use that instead. Just run the below commands on your server, changing the path to Composer as appropriate:</p>
|
||||
<p><em>Please note: "composer.phar" may actually be "composer" on your system.</em></p>
|
||||
<pre>cd <?php echo $this->baseDirectory; ?><br/>/path/to/composer.phar install</pre>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
}
|
||||
|
||||
private function runInstall()
|
||||
{
|
||||
|
||||
?>
|
||||
<h1>Installing Blue Twilight Setup Files</h1>
|
||||
|
||||
<ul>
|
||||
<?php
|
||||
$steps = [
|
||||
['Checking PHP modules', 'checkPhpModules'],
|
||||
['Fetching Composer signature', 'fetchComposerSignature'],
|
||||
['Installing Composer', 'installComposer'],
|
||||
['Installing dependencies using Composer', 'runComposer'],
|
||||
['Generating application key', 'generateAppKey']
|
||||
];
|
||||
|
||||
$successful = true;
|
||||
foreach ($steps as $step)
|
||||
{
|
||||
echo sprintf("<li>%s...</li>%s", $step[0], PHP_EOL);
|
||||
$result = call_user_func([$this, $step[1]]);
|
||||
|
||||
if (!$result)
|
||||
{
|
||||
$successful = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($successful)
|
||||
{
|
||||
header('Location: install/check');
|
||||
exit();
|
||||
}
|
||||
|
||||
?>
|
||||
</ul>
|
||||
<?php
|
||||
}
|
||||
|
||||
private function checkPhpModules()
|
||||
{
|
||||
$requiredModules = [
|
||||
'simplexml',
|
||||
'curl',
|
||||
'mbstring',
|
||||
'dom'
|
||||
];
|
||||
$invalidModules = [];
|
||||
|
||||
foreach ($requiredModules as $module)
|
||||
{
|
||||
if (!extension_loaded($module))
|
||||
{
|
||||
$invalidModules[] = $module;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($invalidModules) > 0)
|
||||
{
|
||||
$this->echoError(sprintf('The following PHP modules are missing and need to be installed to continue: %s', join(', ', $invalidModules)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function echoError($message)
|
||||
{
|
||||
echo sprintf("<span style=\"color: #ff0000;\">%s.</span>%s", $message, PHP_EOL);
|
||||
}
|
||||
|
||||
private function echoOK($message = '')
|
||||
{
|
||||
echo "<span style=\"color: #008800;\">OK";
|
||||
|
||||
if (strlen($message) > 0)
|
||||
{
|
||||
echo sprintf('... %s', $message);
|
||||
}
|
||||
|
||||
echo '</span>' . PHP_EOL;
|
||||
}
|
||||
|
||||
private function fetchComposerSignature()
|
||||
{
|
||||
if (!boolval(ini_get('allow_url_fopen')))
|
||||
{
|
||||
$this->echoError('allow_url_fopen is disabled so we cannot use Composer');
|
||||
echo '<br/>';
|
||||
$this->echoError('You will need to install the vendor libraries manually - <a href="https://github.com/pandy06269/blue-twilight/wiki/Install-Vendor-libraries-manually" target="_blank">see this page for more details</a>');
|
||||
return false;
|
||||
}
|
||||
|
||||
$signatureUrl = 'https://composer.github.io/installer.sig';
|
||||
$this->composerSignature = trim(file_get_contents($signatureUrl));
|
||||
if (strlen($this->composerSignature) == 0)
|
||||
{
|
||||
$this->echoError(sprintf("Failed downloading the Composer signature from %s", $signatureUrl));
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->echoOK($this->composerSignature);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function generateAppKey()
|
||||
{
|
||||
if (!file_exists('.env') && file_exists('.env.example'))
|
||||
{
|
||||
copy('.env.example', '.env');
|
||||
}
|
||||
|
||||
ob_start();
|
||||
system('touch .env', $rc);
|
||||
$result = ob_get_clean();
|
||||
echo nl2br($result);
|
||||
|
||||
ob_start();
|
||||
system('php artisan key:generate', $rc);
|
||||
$result = ob_get_clean();
|
||||
echo nl2br($result);
|
||||
|
||||
if ($rc != 0)
|
||||
{
|
||||
$this->echoError('Failed to generate application key');
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->echoOK();
|
||||
return true;
|
||||
}
|
||||
|
||||
private function installComposer()
|
||||
{
|
||||
$rc = -1;
|
||||
|
||||
ob_start();
|
||||
system('php -r "copy(\'https://getcomposer.org/installer\', \'composer-setup.php\');"', $rc);
|
||||
$result = ob_get_clean();
|
||||
echo nl2br($result);
|
||||
|
||||
if ($rc != 0)
|
||||
{
|
||||
$this->echoError('Failed to fetch Composer');
|
||||
return false;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
system(sprintf('php -r "if (hash_file(\'SHA384\', \'composer-setup.php\') === \'%s\') { echo \'Installer verified\'; } else { echo \'Installer corrupt\'; unlink(\'composer-setup.php\'); } echo PHP_EOL;"', $this->composerSignature), $rc);
|
||||
$result = ob_get_clean();
|
||||
echo nl2br($result);
|
||||
if ($rc != 0)
|
||||
{
|
||||
$this->echoError('Composer verification failed');
|
||||
return false;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
system('php composer-setup.php', $rc);
|
||||
$result = ob_get_clean();
|
||||
echo nl2br($result);
|
||||
if ($rc != 0)
|
||||
{
|
||||
$this->echoError('Failed to install Composer');
|
||||
return false;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
system('php -r "unlink(\'composer-setup.php\');"', $rc);
|
||||
$result = ob_get_clean();
|
||||
echo nl2br($result);
|
||||
if ($rc != 0)
|
||||
{
|
||||
$this->echoError('Failed to remove Composer setup file');
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->echoOK();
|
||||
return true;
|
||||
}
|
||||
|
||||
private function runComposer()
|
||||
{
|
||||
ob_start();
|
||||
system('php composer.phar install', $rc);
|
||||
$result = ob_get_clean();
|
||||
echo nl2br($result);
|
||||
if ($rc != 0)
|
||||
{
|
||||
$this->echoError('Installing Composer packages failed');
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->echoOK();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$installer = new BlueTwilightInstaller();
|
||||
$installer->run();
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="100px" height="100px"><path fill="#43A047" d="M40.6 12.1L17 35.7 7.4 26.1 4.6 29 17 41.3 43.4 14.9z"/></svg>
|
Before Width: | Height: | Size: 175 B |
|
@ -1,7 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="margin: auto; background: none; display: block; shape-rendering: auto;"
|
||||
width="200px" height="200px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
|
||||
<path d="M10 50A40 40 0 0 0 90 50A40 42 0 0 1 10 50" fill="#1d3f72" stroke="none" transform="rotate(222.794 50 51)">
|
||||
<animateTransform attributeName="transform" type="rotate" dur="1s" repeatCount="indefinite" keyTimes="0;1" values="0 50 51;360 50 51"></animateTransform>
|
||||
</path>
|
||||
<!-- [ldio] generated by https://loading.io/ --></svg>
|
Before Width: | Height: | Size: 581 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="100px" height="100px"><path fill="#00acc1" d="M44,24c0,11.044-8.956,20-20,20S4,35.044,4,24S12.956,4,24,4S44,12.956,44,24z"/><path fill="#eee" d="M40,24c0,8.838-7.162,16-16,16S8,32.838,8,24S15.163,8,24,8S40,15.163,40,24z"/><path d="M23 11H25V24H23z"/><path d="M26.082 22.654H28.419V31.846H26.082z" transform="rotate(-45.001 27.25 27.25)"/><path d="M27,24c0,1.657-1.344,3-3,3c-1.657,0-3-1.343-3-3s1.343-3,3-3C25.656,21,27,22.343,27,24"/><path fill="#00acc1" d="M25,24c0,0.551-0.448,1-1,1s-1-0.449-1-1c0-0.553,0.448-1,1-1S25,23.447,25,24"/></svg>
|
Before Width: | Height: | Size: 610 B |
|
@ -1,16 +0,0 @@
|
|||
<?php
|
||||
ini_set('display_errors', 'on');
|
||||
|
||||
$installerDir = sprintf('%s/installer', dirname(dirname(__DIR__)));
|
||||
require_once sprintf('%s/vendor/autoload.php', $installerDir);
|
||||
require_once sprintf('%s/helpers.php', $installerDir);
|
||||
|
||||
try
|
||||
{
|
||||
$installer = new \AppInstaller\Installer();
|
||||
$installer->handleRequest();
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
echo sprintf('ERROR: %s', $ex);
|
||||
}
|
Before Width: | Height: | Size: 6.5 KiB |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1024 1024"><defs><linearGradient id="a" x1="50.31%" x2="50%" y1="74.74%" y2="0%"><stop offset="0%" stop-color="#FFE98A"/><stop offset="67.7%" stop-color="#B63E59"/><stop offset="100%" stop-color="#68126F"/></linearGradient><circle id="c" cx="603" cy="682" r="93"/><filter id="b" width="203.2%" height="203.2%" x="-51.6%" y="-51.6%" filterUnits="objectBoundingBox"><feOffset in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation="32"/><feColorMatrix in="shadowBlurOuter1" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/></filter><linearGradient id="d" x1="49.48%" x2="49.87%" y1="11.66%" y2="77.75%"><stop offset="0%" stop-color="#F7EAB9"/><stop offset="100%" stop-color="#E5765E"/></linearGradient><linearGradient id="e" x1="91.59%" x2="66.97%" y1="5.89%" y2="100%"><stop offset="0%" stop-color="#A22A50"/><stop offset="100%" stop-color="#EE7566"/></linearGradient><linearGradient id="f" x1="49.48%" x2="49.61%" y1="11.66%" y2="98.34%"><stop offset="0%" stop-color="#F7EAB9"/><stop offset="100%" stop-color="#E5765E"/></linearGradient><linearGradient id="g" x1="78.5%" x2="36.4%" y1="106.76%" y2="26.41%"><stop offset="0%" stop-color="#A22A50"/><stop offset="100%" stop-color="#EE7566"/></linearGradient></defs><g fill="none" fill-rule="evenodd"><rect width="1024" height="1024" fill="url(#a)"/><use fill="black" filter="url(#b)" xlink:href="#c"/><use fill="#FFF6CB" xlink:href="#c"/><g fill="#FFFFFF" opacity=".3" transform="translate(14 23)"><circle cx="203" cy="255" r="3" fill-opacity=".4"/><circle cx="82" cy="234" r="2"/><circle cx="22" cy="264" r="2" opacity=".4"/><circle cx="113" cy="65" r="3"/><circle cx="202" cy="2" r="2"/><circle cx="2" cy="114" r="2"/><circle cx="152" cy="144" r="2"/><circle cx="362" cy="224" r="2"/><circle cx="453" cy="65" r="3" opacity=".4"/><circle cx="513" cy="255" r="3"/><circle cx="593" cy="115" r="3"/><circle cx="803" cy="5" r="3" opacity=".4"/><circle cx="502" cy="134" r="2"/><circle cx="832" cy="204" r="2"/><circle cx="752" cy="114" r="2"/><circle cx="933" cy="255" r="3" opacity=".4"/><circle cx="703" cy="225" r="3"/><circle cx="903" cy="55" r="3"/><circle cx="982" cy="144" r="2"/><circle cx="632" cy="14" r="2"/></g><g transform="translate(0 550)"><path fill="#8E2C15" d="M259 5.47c0 5.33 3.33 9.5 10 12.5s9.67 9.16 9 18.5h1c.67-6.31 1-11.8 1-16.47 8.67 0 13.33-1.33 14-4 .67 4.98 1.67 8.3 3 9.97 1.33 1.66 2 5.16 2 10.5h1c0-5.65.33-9.64 1-11.97 1-3.5 4-10.03-1-14.53S295 7 290 3c-5-4-10-3-13 2s-5 7-9 7-5-3.53-5-5.53c0-2 2-5-1.5-5s-7.5 0-7.5 2c0 1.33 1.67 2 5 2z"/><path fill="url(#d)" d="M1024 390H0V105.08C77.3 71.4 155.26 35 297.4 35c250 0 250.76 125.25 500 125 84.03-.08 160.02-18.2 226.6-40.93V390z"/><path fill="url(#d)" d="M1024 442H0V271.82c137.51-15.4 203.1-50.49 356.67-60.1C555.24 199.3 606.71 86.59 856.74 86.59c72.78 0 124.44 10.62 167.26 25.68V442z"/><path fill="url(#e)" d="M1024 112.21V412H856.91c99.31-86.5 112.63-140.75 39.97-162.78C710.24 192.64 795.12 86.58 856.9 86.58c72.7 0 124.3 10.6 167.09 25.63z"/><path fill="url(#e)" d="M1024 285.32V412H857c99.31-86.6 112.63-140.94 39.97-163L1024 285.32z"/><path fill="url(#f)" d="M0 474V223.93C67.12 190.69 129.55 155 263 155c250 0 331.46 162.6 530 175 107.42 6.71 163-26.77 231-58.92V474H0z"/><path fill="url(#e)" d="M353.02 474H0V223.93C67.12 190.69 129.55 155 263 155c71.14 0 151.5 12.76 151.5 70.5 0 54.5-45.5 79.72-112.5 109-82.26 35.95-54.57 111.68 51.02 139.5z"/><path fill="url(#g)" d="M353.02 474H0v-14.8l302-124.7c-82.26 35.95-54.57 111.68 51.02 139.5z"/></g><g fill="#FFFFFF" opacity=".2" transform="translate(288 523)"><circle cx="250" cy="110" r="110"/><circle cx="420" cy="78" r="60"/><circle cx="70" cy="220" r="70"/></g><g fill="#FFFFFF" fill-rule="nonzero" opacity=".08" transform="translate(135 316)"><path d="M10 80.22a14.2 14.2 0 0 1 20 0 14.2 14.2 0 0 0 20 0l20-19.86a42.58 42.58 0 0 1 60 0l15 14.9a21.3 21.3 0 0 0 30 0 21.3 21.3 0 0 1 30 0l.9.9A47.69 47.69 0 0 1 220 110H0v-5.76c0-9.02 3.6-17.67 10-24.02zm559.1-66.11l5.9-5.86c11.07-11 28.93-11 40 0l10 9.94a14.19 14.19 0 0 0 20 0 14.19 14.19 0 0 1 20 0 16.36 16.36 0 0 0 21.3 1.5l8.7-6.47a33.47 33.47 0 0 1 40 0l4.06 3.03A39.6 39.6 0 0 1 755 48H555a47.77 47.77 0 0 1 14.1-33.89z"/></g></g></svg>
|
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 5.4 KiB |
|
@ -0,0 +1,255 @@
|
|||
<?php
|
||||
|
||||
namespace BtwInstaller;
|
||||
|
||||
class BlueTwilightUpdater
|
||||
{
|
||||
private $baseDirectory;
|
||||
private $composerSignature;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->baseDirectory = dirname(__DIR__);
|
||||
chdir($this->baseDirectory);
|
||||
putenv('HOME=' . $this->baseDirectory);
|
||||
|
||||
// Display errors so installer never gets a WSOD!
|
||||
ini_set('display_errors', true);
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
if (strtoupper($_SERVER['REQUEST_METHOD']) == 'POST')
|
||||
{
|
||||
// Handle post
|
||||
$this->runUpdate();
|
||||
exit();
|
||||
}
|
||||
?>
|
||||
<html>
|
||||
<head>
|
||||
<title>Blue Twilight Update</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Blue Twilight Update</h1>
|
||||
<p>This update routine ensures your Blue Twilight Composer packages are up-to-date.</p>
|
||||
<p>To get started, simply click the button below.</p>
|
||||
<p style="font-weight: bold; color: #ff0000;">This can take a few minutes so please be patient, and only click the button once!</p>
|
||||
<form method="post">
|
||||
<button type="submit">Update Composer and dependencies for me</button>
|
||||
</form>
|
||||
|
||||
<hr/>
|
||||
<h2>Got Composer?</h2>
|
||||
|
||||
<p>If you already have Composer installed, however, you may want to use that instead. Just run the below commands on your server, changing the path to Composer as appropriate:</p>
|
||||
<p><em>Please note: "composer.phar" may actually be "composer" on your system.</em></p>
|
||||
<pre>
|
||||
cd <?php echo $this->baseDirectory; ?>
|
||||
|
||||
php artisan clear-compiled
|
||||
php artisan cache:clear
|
||||
php artisan config:clear
|
||||
php artisan view:clear
|
||||
/path/to/composer.phar install
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
}
|
||||
|
||||
private function runUpdate()
|
||||
{
|
||||
|
||||
?>
|
||||
<h1>Updating Blue Twilight Composer packages</h1>
|
||||
|
||||
<ul>
|
||||
<?php
|
||||
$steps = [
|
||||
['Removing compiled cache', 'removeCompiledCached'],
|
||||
['Fetching Composer signature', 'fetchComposerSignature'],
|
||||
['Installing Composer', 'installComposer'],
|
||||
['Updating dependencies using Composer', 'runComposer']
|
||||
];
|
||||
|
||||
$successful = true;
|
||||
foreach ($steps as $step)
|
||||
{
|
||||
echo sprintf("<li>%s...</li>%s", $step[0], PHP_EOL);
|
||||
$result = call_user_func([$this, $step[1]]);
|
||||
|
||||
if (!$result)
|
||||
{
|
||||
$successful = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($successful)
|
||||
{
|
||||
header('Location: admin');
|
||||
exit();
|
||||
}
|
||||
|
||||
?>
|
||||
</ul>
|
||||
<?php
|
||||
}
|
||||
|
||||
private function fetchComposerSignature()
|
||||
{
|
||||
if (!boolval(ini_get('allow_url_fopen')))
|
||||
{
|
||||
$this->echoError('allow_url_fopen is disabled so we cannot use Composer');
|
||||
echo '<br/>';
|
||||
$this->echoError('You will need to install the vendor libraries manually - <a href="https://github.com/pandy06269/blue-twilight/wiki/Install-Vendor-libraries-manually" target="_blank">see this page for more details</a>');
|
||||
return false;
|
||||
}
|
||||
|
||||
$signatureUrl = 'https://composer.github.io/installer.sig';
|
||||
$this->composerSignature = trim(file_get_contents($signatureUrl));
|
||||
if (strlen($this->composerSignature) == 0)
|
||||
{
|
||||
$this->echoError(sprintf("Failed downloading the Composer signature from %s", $signatureUrl));
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->echoOK($this->composerSignature);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function removeCompiledCached()
|
||||
{
|
||||
ob_start();
|
||||
system('php artisan clear-compiled', $rc);
|
||||
$result = ob_get_clean();
|
||||
echo nl2br($result);
|
||||
if ($rc != 0)
|
||||
{
|
||||
$this->echoError('clear-compiled command failed');
|
||||
return false;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
system('php artisan cache:clear', $rc);
|
||||
$result = ob_get_clean();
|
||||
echo nl2br($result);
|
||||
if ($rc != 0)
|
||||
{
|
||||
$this->echoError('cache:clear command failed');
|
||||
return false;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
system('php artisan config:clear', $rc);
|
||||
$result = ob_get_clean();
|
||||
echo nl2br($result);
|
||||
if ($rc != 0)
|
||||
{
|
||||
$this->echoError('config:clear command failed');
|
||||
return false;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
system('php artisan view:clear', $rc);
|
||||
$result = ob_get_clean();
|
||||
echo nl2br($result);
|
||||
if ($rc != 0)
|
||||
{
|
||||
$this->echoError('view:clear command failed');
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->echoOK();
|
||||
return true;
|
||||
}
|
||||
|
||||
private function echoError($message)
|
||||
{
|
||||
echo sprintf("<span style=\"color: #ff0000;\">%s.</span>%s", $message, PHP_EOL);
|
||||
}
|
||||
|
||||
private function echoOK($message = '')
|
||||
{
|
||||
echo "<span style=\"color: #008800;\">OK";
|
||||
|
||||
if (strlen($message) > 0)
|
||||
{
|
||||
echo sprintf('... %s', $message);
|
||||
}
|
||||
|
||||
echo '</span>' . PHP_EOL;
|
||||
}
|
||||
|
||||
private function installComposer()
|
||||
{
|
||||
$rc = -1;
|
||||
|
||||
ob_start();
|
||||
system('php -r "copy(\'https://getcomposer.org/installer\', \'composer-setup.php\');"', $rc);
|
||||
$result = ob_get_clean();
|
||||
echo nl2br($result);
|
||||
|
||||
if ($rc != 0)
|
||||
{
|
||||
$this->echoError('Failed to fetch Composer');
|
||||
return false;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
system(sprintf('php -r "if (hash_file(\'SHA384\', \'composer-setup.php\') === \'%s\') { echo \'Installer verified\'; } else { echo \'Installer corrupt\'; unlink(\'composer-setup.php\'); } echo PHP_EOL;"', $this->composerSignature), $rc);
|
||||
$result = ob_get_clean();
|
||||
echo nl2br($result);
|
||||
if ($rc != 0)
|
||||
{
|
||||
$this->echoError('Composer verification failed');
|
||||
return false;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
system('php composer-setup.php', $rc);
|
||||
$result = ob_get_clean();
|
||||
echo nl2br($result);
|
||||
if ($rc != 0)
|
||||
{
|
||||
$this->echoError('Failed to install Composer');
|
||||
return false;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
system('php -r "unlink(\'composer-setup.php\');"', $rc);
|
||||
$result = ob_get_clean();
|
||||
echo nl2br($result);
|
||||
if ($rc != 0)
|
||||
{
|
||||
$this->echoError('Failed to remove Composer setup file');
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->echoOK();
|
||||
return true;
|
||||
}
|
||||
|
||||
private function runComposer()
|
||||
{
|
||||
ob_start();
|
||||
system('php composer.phar --no-interaction install', $rc);
|
||||
$result = ob_get_clean();
|
||||
echo nl2br($result);
|
||||
if ($rc != 0)
|
||||
{
|
||||
$this->echoError('Updating Composer packages failed');
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->echoOK();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$installer = new BlueTwilightUpdater();
|
||||
$installer->run();
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="100px" height="100px"><path fill="#43A047" d="M40.6 12.1L17 35.7 7.4 26.1 4.6 29 17 41.3 43.4 14.9z"/></svg>
|
Before Width: | Height: | Size: 175 B |
|
@ -1,7 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="margin: auto; background: none; display: block; shape-rendering: auto;"
|
||||
width="200px" height="200px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
|
||||
<path d="M10 50A40 40 0 0 0 90 50A40 42 0 0 1 10 50" fill="#1d3f72" stroke="none" transform="rotate(222.794 50 51)">
|
||||
<animateTransform attributeName="transform" type="rotate" dur="1s" repeatCount="indefinite" keyTimes="0;1" values="0 50 51;360 50 51"></animateTransform>
|
||||
</path>
|
||||
<!-- [ldio] generated by https://loading.io/ --></svg>
|
Before Width: | Height: | Size: 581 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="100px" height="100px"><path fill="#00acc1" d="M44,24c0,11.044-8.956,20-20,20S4,35.044,4,24S12.956,4,24,4S44,12.956,44,24z"/><path fill="#eee" d="M40,24c0,8.838-7.162,16-16,16S8,32.838,8,24S15.163,8,24,8S40,15.163,40,24z"/><path d="M23 11H25V24H23z"/><path d="M26.082 22.654H28.419V31.846H26.082z" transform="rotate(-45.001 27.25 27.25)"/><path d="M27,24c0,1.657-1.344,3-3,3c-1.657,0-3-1.343-3-3s1.343-3,3-3C25.656,21,27,22.343,27,24"/><path fill="#00acc1" d="M25,24c0,0.551-0.448,1-1,1s-1-0.449-1-1c0-0.553,0.448-1,1-1S25,23.447,25,24"/></svg>
|
Before Width: | Height: | Size: 610 B |
|
@ -1,17 +0,0 @@
|
|||
<?php
|
||||
ini_set('display_errors', 'on');
|
||||
|
||||
$installerDir = sprintf('%s/installer', dirname(dirname(__DIR__)));
|
||||
require_once sprintf('%s/vendor/autoload.php', $installerDir);
|
||||
require_once sprintf('%s/helpers.php', $installerDir);
|
||||
|
||||
try
|
||||
{
|
||||
$installer = new \AppInstaller\Installer();
|
||||
$installer->setIsUpgrade(true);
|
||||
$installer->handleRequest();
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
echo sprintf('ERROR: %s', $ex);
|
||||
}
|
|
@ -6,6 +6,10 @@ It takes advantage of modern frameworks (Laravel, Bootstrap 4, VueJS) as well as
|
|||
|
||||
You can see Blue Twilight in action on my own photo gallery - the reason I wrote Blue Twilight - at: [photos.andysh.uk](https://photos.andysh.uk)
|
||||
|
||||
## Blue Twilight Cloud
|
||||
|
||||
If you want your own dedicated, private instance of Blue Twilight without the hassle of managing servers, hosting and updates - check out [Blue Twilight Cloud](https://showmy.photos).
|
||||
|
||||
## Demo System
|
||||
|
||||
See Blue Twilight in action using the demo system. Full details are [available here](https://showmy.photos/demo/).
|
||||
|
@ -20,7 +24,7 @@ The link to the demo system is: https://demo.showmy.photos. Login with:
|
|||
* [Blue Twilight website](https://showmy.photos/)
|
||||
* [User Manual](https://showmy.photos/user-guide/)
|
||||
* [Installation Guide](https://showmy.photos/user-guide/installation/)
|
||||
* [Issues/Tasks](https://projects.waggybytes.com/aheathershaw/blue-twilight/issues)
|
||||
* [Issues/Tasks](https://apps.andysh.uk/aheathershaw/blue-twilight/issues)
|
||||
* [Roadmap](https://apps.andysh.uk/aheathershaw/blue-twilight/milestones)
|
||||
|
||||
## Need Help?
|
||||
|
|
|
@ -0,0 +1,331 @@
|
|||
/*!
|
||||
* Bootstrap Reboot v4.1.2 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2018 The Bootstrap Authors
|
||||
* Copyright 2011-2018 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||
*/
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
line-height: 1.15;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-ms-overflow-style: scrollbar;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
@-ms-viewport {
|
||||
width: device-width;
|
||||
}
|
||||
|
||||
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #212529;
|
||||
text-align: left;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
[tabindex="-1"]:focus {
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
hr {
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title],
|
||||
abbr[data-original-title] {
|
||||
text-decoration: underline;
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: .5rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
background-color: transparent;
|
||||
-webkit-text-decoration-skip: objects;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #0056b3;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]) {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]):focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
-ms-overflow-style: scrollbar;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
color: #6c757d;
|
||||
text-align: left;
|
||||
caption-side: bottom;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus {
|
||||
outline: 1px dotted;
|
||||
outline: 5px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
button,
|
||||
html [type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
input[type="radio"],
|
||||
input[type="checkbox"] {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input[type="date"],
|
||||
input[type="time"],
|
||||
input[type="datetime-local"],
|
||||
input[type="month"] {
|
||||
-webkit-appearance: listbox;
|
||||
}
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: .5rem;
|
||||
font-size: 1.5rem;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type="search"] {
|
||||
outline-offset: -2px;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
[type="search"]::-webkit-search-cancel-button,
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
|
@ -0,0 +1,8 @@
|
|||
/*!
|
||||
* Bootstrap Reboot v4.1.2 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2018 The Bootstrap Authors
|
||||
* Copyright 2011-2018 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}@-ms-viewport{width:device-width}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent;-webkit-text-decoration-skip:objects}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg:not(:root){overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
|
||||
/*# sourceMappingURL=bootstrap-reboot.min.css.map */
|