From 365034d6110d3dbafe2a4c51cad5333f6f6791f2 Mon Sep 17 00:00:00 2001 From: Andy Heathershaw Date: Sun, 26 Apr 2020 15:08:26 +0100 Subject: [PATCH 1/8] Added a new Bootstrapper experience to download the vendors directly from Gitea instead of having to download Composer. #146 --- app/Services/GiteaService.php | 66 ++- config/app.php | 2 +- public/b2_test.php | 143 ------ public/bootstrap/composer.json | 26 + public/bootstrap/helpers.php | 12 + public/bootstrap/images/completed.svg | 1 + public/bootstrap/images/loading.svg | 7 + public/bootstrap/images/waiting.svg | 1 + public/bootstrap/index.php | 25 + public/bootstrap/src/Bootstrapper.php | 136 ++++++ public/bootstrap/temp/.gitignore | 0 public/bootstrap/vendor/autoload.php | 7 + .../bootstrap/vendor/composer/ClassLoader.php | 445 ++++++++++++++++++ public/bootstrap/vendor/composer/LICENSE | 21 + .../vendor/composer/autoload_classmap.php | 9 + .../vendor/composer/autoload_namespaces.php | 9 + .../vendor/composer/autoload_psr4.php | 11 + .../vendor/composer/autoload_real.php | 52 ++ .../vendor/composer/autoload_static.php | 36 ++ public/bootstrap/views/index.php | 57 +++ public/index.php | 2 +- public/install.php | 256 ---------- public/update.php | 255 ---------- resources/js/bootstrapper.js | 71 +++ resources/sass/bootstrapper.scss | 33 ++ 25 files changed, 1020 insertions(+), 663 deletions(-) delete mode 100644 public/b2_test.php create mode 100644 public/bootstrap/composer.json create mode 100644 public/bootstrap/helpers.php create mode 100644 public/bootstrap/images/completed.svg create mode 100644 public/bootstrap/images/loading.svg create mode 100644 public/bootstrap/images/waiting.svg create mode 100644 public/bootstrap/index.php create mode 100644 public/bootstrap/src/Bootstrapper.php create mode 100644 public/bootstrap/temp/.gitignore create mode 100644 public/bootstrap/vendor/autoload.php create mode 100644 public/bootstrap/vendor/composer/ClassLoader.php create mode 100644 public/bootstrap/vendor/composer/LICENSE create mode 100644 public/bootstrap/vendor/composer/autoload_classmap.php create mode 100644 public/bootstrap/vendor/composer/autoload_namespaces.php create mode 100644 public/bootstrap/vendor/composer/autoload_psr4.php create mode 100644 public/bootstrap/vendor/composer/autoload_real.php create mode 100644 public/bootstrap/vendor/composer/autoload_static.php create mode 100644 public/bootstrap/views/index.php delete mode 100644 public/install.php delete mode 100644 public/update.php create mode 100644 resources/js/bootstrapper.js create mode 100644 resources/sass/bootstrapper.scss diff --git a/app/Services/GiteaService.php b/app/Services/GiteaService.php index e37b7da..bd40125 100644 --- a/app/Services/GiteaService.php +++ b/app/Services/GiteaService.php @@ -6,11 +6,34 @@ class GiteaService { private $cacheFile = null; private $config = []; + private $currentVersionNumber; - public function __construct() + public function __construct(array $config = null, $currentVersionNumber = null) { - $this->config = config('services.gitea'); - $this->cacheFile = storage_path('app/gitea_cache.txt'); + // 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'); + } } public function checkForLatestRelease() @@ -26,7 +49,7 @@ class GiteaService { // Lookup and store the version information $statusCode = -1; - $result = $this->getLatestReleaseFromGitea($statusCode); + $result = $this->getReleasesFromGitea($statusCode); if ($statusCode == 200) { @@ -51,6 +74,31 @@ 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); @@ -75,10 +123,10 @@ class GiteaService return json_decode(file_get_contents($this->cacheFile)); } - private function getLatestReleaseFromGitea(&$statusCode) + private function getReleasesFromGitea(&$statusCode) { $httpHeaders = [ - sprintf('User-Agent: aheathershaw/blue-twilight (v%s)', config('app.version')) + sprintf('User-Agent: aheathershaw/blue-twilight (v%s)', $this->currentVersionNumber) ]; if (isset($this->config['api_key']) && !empty($this->config['api_key'])) @@ -106,7 +154,11 @@ class GiteaService private function setCacheData($data) { - file_put_contents($this->cacheFile, json_encode(get_object_vars($data))); + if (!is_null($this->cacheFile)) + { + file_put_contents($this->cacheFile, json_encode(get_object_vars($data))); + } + return $data; } } \ No newline at end of file diff --git a/config/app.php b/config/app.php index 1f0f69e..62c7e52 100644 --- a/config/app.php +++ b/config/app.php @@ -2,7 +2,7 @@ return [ // Version number of Blue Twilight - 'version' => '2.2.0-beta.2', + 'version' => '2.1.2', /* |-------------------------------------------------------------------------- diff --git a/public/b2_test.php b/public/b2_test.php deleted file mode 100644 index a741604..0000000 --- a/public/b2_test.php +++ /dev/null @@ -1,143 +0,0 @@ -' . $uri . '

'; - - $server_output = curl_exec($session); // Let's do this! - - if (curl_getinfo($session, CURLINFO_HTTP_CODE) != 200) - { - echo '

' . $server_output . '

'; - } - else - { - echo '

' . (strlen($server_output) . ' bytes received') . '

'; // 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 '

' . $uri . '

'; - - $server_output = curl_exec($session); // Let's do this! - - if (curl_getinfo($session, CURLINFO_HTTP_CODE) != 200) - { - echo '

' . $server_output . '

'; - } - else - { - echo '

' . (strlen($server_output) . ' bytes received') . '

'; // 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 '

' . $uri . '

'; - - $server_output = curl_exec($session); // Let's do this! - - if (curl_getinfo($session, CURLINFO_HTTP_CODE) != 200) - { - echo '

' . $server_output . '

'; - } - else - { - echo '

' . (strlen($server_output) . ' bytes received') . '

'; // 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 '

' . $uri . '

'; - - $server_output = curl_exec($session); // Let's do this! - - if (curl_getinfo($session, CURLINFO_HTTP_CODE) != 200) - { - echo '

' . $server_output . '

'; - } - else - { - echo '

' . (strlen($server_output) . ' bytes received') . '

'; // Tell me about the rabbits, George! - } - - curl_close ($session); // Clean up -} - -?> -

b2_authorize_account

- - -

b2_download_file_by_name

-downloadUrl, $authorize_account_result->authorizationToken); ?> - -

b2_download_file_by_id

-downloadUrl, $authorize_account_result->authorizationToken); ?> diff --git a/public/bootstrap/composer.json b/public/bootstrap/composer.json new file mode 100644 index 0000000..27c2f40 --- /dev/null +++ b/public/bootstrap/composer.json @@ -0,0 +1,26 @@ +{ + "name": "aheathershaw/blue-twilight-bootstrapper", + "description": "Bootstrapper component 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/", + "AppBootstrap\\": "src/" + } + } +} \ No newline at end of file diff --git a/public/bootstrap/helpers.php b/public/bootstrap/helpers.php new file mode 100644 index 0000000..867f9fc --- /dev/null +++ b/public/bootstrap/helpers.php @@ -0,0 +1,12 @@ += strlen($stringToFind) && + substr(strtolower($stringToCheck), strlen($stringToCheck) - strlen($stringToFind), strlen($stringToFind)) == strtolower($stringToFind); +} + +function starts_with($stringToCheck, $stringToFind) +{ + return strlen($stringToCheck) >= strlen($stringToFind) && + substr(strtolower($stringToCheck), 0, strlen($stringToFind)) == strtolower($stringToFind); +} \ No newline at end of file diff --git a/public/bootstrap/images/completed.svg b/public/bootstrap/images/completed.svg new file mode 100644 index 0000000..517ce74 --- /dev/null +++ b/public/bootstrap/images/completed.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/bootstrap/images/loading.svg b/public/bootstrap/images/loading.svg new file mode 100644 index 0000000..c0f8953 --- /dev/null +++ b/public/bootstrap/images/loading.svg @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/public/bootstrap/images/waiting.svg b/public/bootstrap/images/waiting.svg new file mode 100644 index 0000000..f93f32a --- /dev/null +++ b/public/bootstrap/images/waiting.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/bootstrap/index.php b/public/bootstrap/index.php new file mode 100644 index 0000000..da5781b --- /dev/null +++ b/public/bootstrap/index.php @@ -0,0 +1,25 @@ +handleRequest(); +} +catch (\Exception $ex) +{ + echo sprintf('ERROR: %s', $ex); +} \ No newline at end of file diff --git a/public/bootstrap/src/Bootstrapper.php b/public/bootstrap/src/Bootstrapper.php new file mode 100644 index 0000000..36f9e7e --- /dev/null +++ b/public/bootstrap/src/Bootstrapper.php @@ -0,0 +1,136 @@ +rootDir = dirname(__DIR__); + $this->configDir = sprintf('%s/config', dirname(dirname($this->rootDir))); + $this->tempDir = sprintf('%s/temp', $this->rootDir); + $this->viewsDir = sprintf('%s/views', $this->rootDir); + } + + public function handleRequest() + { + if (!isset($_GET['act'])) + { + $this->view('index', ['appName' => 'Blue Twilight Bootstrapper']); + } + else + { + switch (trim($_GET['act'])) + { + case 'download': + $this->download(); + return; + + default: + throw new \Exception(sprintf('ERROR: Action \'%s\' was not recognised.', $_GET['act'])); + } + } + } + + private function download() + { + $appConfig = require_once sprintf('%s/app.php', $this->configDir); + $servicesConfig = require_once sprintf('%s/services.php', $this->configDir); + + $versionNumber = sprintf('v%s', $appConfig['version']); + + $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 \'%\'', $versionNumber)); + } + else if (!isset($releaseInfo->assets)) + { + throw new \Exception(sprintf('No assets found in Gitea for Blue Twilight version \'%\'', $versionNumber)); + } + + $vendorsPrefix = 'vendors'; + $vendorsSuffix = '.tar.gz'; + $selectedAsset = null; + foreach ($releaseInfo->assets as $asset) + { + /* + Ignore anything that is not "vendors.tar.gz" were the 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('No vendors.tar.gz found in Gitea for Blue Twilight version \'%\'', $versionNumber); + } + + $targetFileName = sprintf('%s/vendors.tar.gz', $this->tempDir); + + $this->downloadFile($selectedAsset->browser_download_url, $targetFileName); + var_dump('done'); + } + + 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 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; + } +} \ No newline at end of file diff --git a/public/bootstrap/temp/.gitignore b/public/bootstrap/temp/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/public/bootstrap/vendor/autoload.php b/public/bootstrap/vendor/autoload.php new file mode 100644 index 0000000..400d928 --- /dev/null +++ b/public/bootstrap/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * 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 + * @author Jordi Boggiano + * @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; +} diff --git a/public/bootstrap/vendor/composer/LICENSE b/public/bootstrap/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/public/bootstrap/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +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. + diff --git a/public/bootstrap/vendor/composer/autoload_classmap.php b/public/bootstrap/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..7a91153 --- /dev/null +++ b/public/bootstrap/vendor/composer/autoload_classmap.php @@ -0,0 +1,9 @@ + array($baseDir . '/../../app'), + 'AppBootstrap\\' => array($baseDir . '/src'), +); diff --git a/public/bootstrap/vendor/composer/autoload_real.php b/public/bootstrap/vendor/composer/autoload_real.php new file mode 100644 index 0000000..60669ec --- /dev/null +++ b/public/bootstrap/vendor/composer/autoload_real.php @@ -0,0 +1,52 @@ += 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\ComposerStaticInitae2939a73e74219a5e53fe33357ebb1a::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; + } +} diff --git a/public/bootstrap/vendor/composer/autoload_static.php b/public/bootstrap/vendor/composer/autoload_static.php new file mode 100644 index 0000000..23bfb55 --- /dev/null +++ b/public/bootstrap/vendor/composer/autoload_static.php @@ -0,0 +1,36 @@ + + array ( + 'App\\' => 4, + 'AppBootstrap\\' => 13, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'App\\' => + array ( + 0 => __DIR__ . '/../..' . '/../../app', + ), + 'AppBootstrap\\' => + array ( + 0 => __DIR__ . '/../..' . '/src', + ), + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInitae2939a73e74219a5e53fe33357ebb1a::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInitae2939a73e74219a5e53fe33357ebb1a::$prefixDirsPsr4; + + }, null, ClassLoader::class); + } +} diff --git a/public/bootstrap/views/index.php b/public/bootstrap/views/index.php new file mode 100644 index 0000000..641ab83 --- /dev/null +++ b/public/bootstrap/views/index.php @@ -0,0 +1,57 @@ + + + + + + + <?php echo $appName; ?> + + + +
+
+
+
+
+

+
+
+

Welcome to Blue Twilight - the self-hosted PHP photo gallery.

+ +
+

We need to download and install a few more files before you get started.

+

Click the "Let's Go" button below to begin.

+

+
+
+
    +
  • +
    + + + +
    + +
  • +
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/public/index.php b/public/index.php index 82fa173..7708cc8 100644 --- a/public/index.php +++ b/public/index.php @@ -10,7 +10,7 @@ /* Added by Andy - check to see if Composer/vendors are installed */ if (!file_exists(__DIR__.'/../vendor/autoload.php')) { - header('Location: install.php'); + header('Location: bootstrap/'); exit(); } /* End Added by Andy */ diff --git a/public/install.php b/public/install.php deleted file mode 100644 index 0e6de00..0000000 --- a/public/install.php +++ /dev/null @@ -1,256 +0,0 @@ -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(); - } -?> - - - Blue Twilight Setup - - -

Blue Twilight Setup

-

We need to download a few things - namely Composer and related packages - before we can kick off the Blue Twilight installer.

-

We can do this for you - simply click the button below.

-

This can take a few minutes so please be patient, and only click the button once!

-
- -
- -
-

Got Composer?

- -

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:

-

Please note: "composer.phar" may actually be "composer" on your system.

-
cd baseDirectory; ?>
/path/to/composer.phar install
- - - -

Installing Blue Twilight Setup Files

- -
    -%s...%s", $step[0], PHP_EOL); - $result = call_user_func([$this, $step[1]]); - - if (!$result) - { - $successful = false; - break; - } - } - - if ($successful) - { - header('Location: install/check'); - exit(); - } - -?> -
- 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("%s.%s", $message, PHP_EOL); - } - - private function echoOK($message = '') - { - echo "OK"; - - if (strlen($message) > 0) - { - echo sprintf('... %s', $message); - } - - echo '' . 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 '
'; - $this->echoError('You will need to install the vendor libraries manually - see this page for more details'); - 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(); \ No newline at end of file diff --git a/public/update.php b/public/update.php deleted file mode 100644 index afcbd4c..0000000 --- a/public/update.php +++ /dev/null @@ -1,255 +0,0 @@ -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(); - } - ?> - - - Blue Twilight Update - - -

Blue Twilight Update

-

This update routine ensures your Blue Twilight Composer packages are up-to-date.

-

To get started, simply click the button below.

-

This can take a few minutes so please be patient, and only click the button once!

-
- -
- -
-

Got Composer?

- -

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:

-

Please note: "composer.phar" may actually be "composer" on your system.

-
-cd baseDirectory; ?>
-
-php artisan clear-compiled
-php artisan cache:clear
-php artisan config:clear
-php artisan view:clear
-/path/to/composer.phar install
-        
- - - -

Updating Blue Twilight Composer packages

- -
    - %s...%s", $step[0], PHP_EOL); - $result = call_user_func([$this, $step[1]]); - - if (!$result) - { - $successful = false; - break; - } - } - - if ($successful) - { - header('Location: admin'); - exit(); - } - - ?> -
- echoError('allow_url_fopen is disabled so we cannot use Composer'); - echo '
'; - $this->echoError('You will need to install the vendor libraries manually - see this page for more details'); - 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("%s.%s", $message, PHP_EOL); - } - - private function echoOK($message = '') - { - echo "OK"; - - if (strlen($message) > 0) - { - echo sprintf('... %s', $message); - } - - echo '' . 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(); \ No newline at end of file diff --git a/resources/js/bootstrapper.js b/resources/js/bootstrapper.js new file mode 100644 index 0000000..6a39dba --- /dev/null +++ b/resources/js/bootstrapper.js @@ -0,0 +1,71 @@ +/** + * This model is used by the system bootstrapper in public/bootstrap. + * @constructor + */ +function BootstrapperViewModel() { + this.el = '#bootstrapper'; + this.data = { + isCompleted: false, + isRunning: false, + operations: [] + } + this.methods = { + bootstrap() + { + this.operations.push({ + 'isCompleted': false, + 'isRunning': false, + 'name': 'Removing any previous versions', + 'url': '?act=removePrevious' + }); + + this.operations.push({ + 'isCompleted': false, + 'isRunning': false, + 'name': 'Downloading new files', + 'url': '?act=download' + }); + + this.operations.push({ + 'isCompleted': false, + 'isRunning': false, + 'name': 'Extracting new files', + 'url': '?act=extract' + }); + + this.operations.push({ + 'isCompleted': false, + 'isRunning': false, + 'name': 'Cleaning up', + 'url': '?act=cleanup' + }); + + this.isRunning = true; + + this.runOperation(this.operations[0], 0); + }, + runOperation(operation, index) + { + var self = this; + operation.isRunning = true; + + $.post(operation.url) + .done(function(result) + { + operation.isRunning = false; + operation.isCompleted = true; + + index++; + if (index < self.operations.length) + { + self.runOperation(self.operations[index], index); + } + else + { + //self.isRunning = false; + self.isCompleted = true; + } + }) + } + } +} \ No newline at end of file diff --git a/resources/sass/bootstrapper.scss b/resources/sass/bootstrapper.scss new file mode 100644 index 0000000..d0ff4d4 --- /dev/null +++ b/resources/sass/bootstrapper.scss @@ -0,0 +1,33 @@ +#bootstrapper +{ + .operation + { + .status + { + display: inline-block; + height: 32px; + width: 32px; + + i, + img + { + height: 100%; + width: 100%; + } + + span + { + display: inline-block; + height: 100%; + vertical-align: middle; + } + } + } + + ul + { + list-style-type: none; + margin: 0; + padding: 0; + } +} \ No newline at end of file From e06b227147356fe564fb3ede75ab0929b9fb6d80 Mon Sep 17 00:00:00 2001 From: Andy Heathershaw Date: Sun, 26 Apr 2020 21:53:24 +0100 Subject: [PATCH 2/8] Completed the first interation of the new Bootstrapper experience. Downloads and extracts vendors from Gitea and configures the encryption key. Still need to get upgrades implemented. #146 --- .env.example | 6 +- Gruntfile.js | 30 +++++ config/app.php | 2 +- package.json | 2 +- public/bootstrap/src/Bootstrapper.php | 162 ++++++++++++++++++++++++-- public/bootstrap/views/index.php | 12 +- public/css/blue-twilight.css | 23 ++++ public/css/blue-twilight.min.css | 6 + public/js/blue-twilight.js | 116 ++++++++++++++++-- public/js/blue-twilight.min.js | 2 + public/js/blue-twilight.min.js.map | 1 + resources/js/bootstrapper.js | 8 +- resources/js/external_services.js | 4 +- resources/sass/bootstrapper.scss | 2 + 14 files changed, 342 insertions(+), 34 deletions(-) create mode 100644 public/css/blue-twilight.min.css create mode 100644 public/js/blue-twilight.min.js create mode 100644 public/js/blue-twilight.min.js.map diff --git a/.env.example b/.env.example index 3ba91c8..7b22e46 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,7 @@ -APP_ENV=local +APP_ENV=production APP_KEY= -APP_DEBUG=true -APP_LOG_LEVEL=debug +APP_DEBUG=false +APP_LOG_LEVEL=warning APP_URL=http://localhost DB_CONNECTION=mysql diff --git a/Gruntfile.js b/Gruntfile.js index f2117c8..df50c8a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -14,7 +14,9 @@ module.exports = function(grunt) 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'); @@ -97,6 +99,23 @@ module.exports = function(grunt) 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'] + } + } } }); @@ -121,7 +140,18 @@ module.exports = function(grunt) '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']); }; \ No newline at end of file diff --git a/config/app.php b/config/app.php index 62c7e52..763a831 100644 --- a/config/app.php +++ b/config/app.php @@ -2,7 +2,7 @@ return [ // Version number of Blue Twilight - 'version' => '2.1.2', + 'version' => '2.2.0-beta.1', /* |-------------------------------------------------------------------------- diff --git a/package.json b/package.json index ca23a02..ed35856 100644 --- a/package.json +++ b/package.json @@ -12,4 +12,4 @@ "grunt-exec": "^3.0.0", "node-sass": "^4.13.0" } -} \ No newline at end of file +} diff --git a/public/bootstrap/src/Bootstrapper.php b/public/bootstrap/src/Bootstrapper.php index 36f9e7e..19283a1 100644 --- a/public/bootstrap/src/Bootstrapper.php +++ b/public/bootstrap/src/Bootstrapper.php @@ -3,6 +3,7 @@ namespace AppBootstrap; use App\Services\GiteaService; +use Illuminate\Support\Facades\Artisan; /** * This class handles the downloading and extracting of the vendors directory. @@ -13,17 +14,54 @@ use App\Services\GiteaService; */ class Bootstrapper { + /** + * Path to /public/bootstrap + * @var string + */ + private $bootstrapDir; + + /** + * Path to /app/config + * @var string + */ private $configDir; + + /** + * Path to / - the app's root + * @var string + */ private $rootDir; + + /** + * Path to /public/bootstrap/temp + * @var string + */ private $tempDir; + + /** + * Path to /vendor + * @var string + */ + private $vendorDir; + + /** + * Path to /public/bootstrap/views + * @var string + */ private $viewsDir; public function __construct() { - $this->rootDir = dirname(__DIR__); - $this->configDir = sprintf('%s/config', dirname(dirname($this->rootDir))); - $this->tempDir = sprintf('%s/temp', $this->rootDir); - $this->viewsDir = sprintf('%s/views', $this->rootDir); + $this->bootstrapDir = dirname(__DIR__); + $this->rootDir = dirname(dirname($this->bootstrapDir)); + + $this->configDir = sprintf('%s/config', dirname(dirname($this->bootstrapDir))); + $this->tempDir = sprintf('%s/temp', $this->bootstrapDir); + $this->vendorDir = sprintf('%s/vendor', $this->rootDir); + $this->viewsDir = sprintf('%s/views', $this->bootstrapDir); + + chdir($this->rootDir); + putenv('HOME=' . $this->rootDir); } public function handleRequest() @@ -40,13 +78,21 @@ class Bootstrapper $this->download(); return; + case 'extract': + $this->extract(); + return; + + case 'finalise': + $this->finalise(); + return; + default: - throw new \Exception(sprintf('ERROR: Action \'%s\' was not recognised.', $_GET['act'])); + throw new \Exception(sprintf('Action \'%s\' was not recognised.', $_GET['act'])); } } } - private function download() + protected function download() { $appConfig = require_once sprintf('%s/app.php', $this->configDir); $servicesConfig = require_once sprintf('%s/services.php', $this->configDir); @@ -58,16 +104,17 @@ class Bootstrapper if (is_null($releaseInfo)) { - throw new \Exception(sprintf('No release info found in Gitea for Blue Twilight version \'%\'', $versionNumber)); + 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 \'%\'', $versionNumber)); + 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) { /* @@ -87,13 +134,68 @@ class Bootstrapper if (is_null($selectedAsset)) { - throw new \Exception('No vendors.tar.gz found in Gitea for Blue Twilight version \'%\'', $versionNumber); + throw new \Exception(sprintf('No vendors.tar.gz found in Gitea for Blue Twilight version \'%s\'', $versionNumber)); } - $targetFileName = sprintf('%s/vendors.tar.gz', $this->tempDir); + $targetFileName = $this->getVendorsTempFileName(); $this->downloadFile($selectedAsset->browser_download_url, $targetFileName); - var_dump('done'); + $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->json([ + 'result' => true, + 'testFile' => $vendorsTestFile + ]); + } + else + { + throw new \Exception('The extraction failed'); + } + } + + protected function finalise() + { + $result = [ + 'envFileCreated' => false + ]; + + $result['envFileCreated'] = $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) @@ -120,6 +222,44 @@ class Bootstrapper @fclose($tempFilename); } + 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 json($data) + { + echo json_encode($data); + } + private function view($name, array $viewData = []) { $viewFile = sprintf('%s/%s.php', $this->viewsDir, $name); diff --git a/public/bootstrap/views/index.php b/public/bootstrap/views/index.php index 641ab83..58b3dce 100644 --- a/public/bootstrap/views/index.php +++ b/public/bootstrap/views/index.php @@ -3,7 +3,7 @@ - + <?php echo $appName; ?> + +
+

Welcome to Blue Twilight - the self-hosted PHP photo gallery.

+

Your application/PHP environment have been checked and the results are below. Please correct any failed items before continuing.

+
-
-
-

+
+
+ Blue Twilight will automatically download the required third-party libraries when you + click Continue.
-
-

Welcome to Blue Twilight - the self-hosted PHP photo gallery.

-
-

We need to download and install a few more files before you get started.

-

Click the "Let's Go" button below to begin.

-

+ $items): ?> +
+
+

-
-
    -
  • -
    - - - -
    - -
  • -
+
+ + + $result): ?> + + + + + + +
+ + + + + + +
+ + + +

+ +
+ Blue Twilight cannot be installed until the issues identified above are rectified. +
+ +
+
+
    +
  • +
    + + + +
    + +
  • +
diff --git a/public/install/index.php b/public/install/index.php index a3fbeb2..9df9553 100644 --- a/public/install/index.php +++ b/public/install/index.php @@ -5,16 +5,6 @@ $installerDir = sprintf('%s/installer', dirname(dirname(__DIR__))); require_once sprintf('%s/vendor/autoload.php', $installerDir); require_once sprintf('%s/helpers.php', $installerDir); -if (!function_exists('env')) -{ - // Define a dummy env() function that always returns the default value so we can load the config/app.php file - // to get the current version number - function env($key, $default = null) - { - return $default; - } -} - try { $installer = new \AppInstaller\Installer(); diff --git a/public/update/images/completed.svg b/public/update/images/completed.svg new file mode 100644 index 0000000..517ce74 --- /dev/null +++ b/public/update/images/completed.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/update/images/loading.svg b/public/update/images/loading.svg new file mode 100644 index 0000000..c0f8953 --- /dev/null +++ b/public/update/images/loading.svg @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/public/update/images/waiting.svg b/public/update/images/waiting.svg new file mode 100644 index 0000000..f93f32a --- /dev/null +++ b/public/update/images/waiting.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/update/index.php b/public/update/index.php new file mode 100644 index 0000000..f9b7ce8 --- /dev/null +++ b/public/update/index.php @@ -0,0 +1,17 @@ +setIsUpgrade(true); + $installer->handleRequest(); +} +catch (\Exception $ex) +{ + echo sprintf('ERROR: %s', $ex); +} \ No newline at end of file diff --git a/resources/views/install/navbar.blade.php b/resources/views/install/navbar.blade.php index cb21d84..216162b 100644 --- a/resources/views/install/navbar.blade.php +++ b/resources/views/install/navbar.blade.php @@ -1,5 +1,5 @@
@@ -546,35 +547,36 @@ @lang('admin.settings.social_twitter') -
- - -
+ @if (count($twitterServices) == 0) +
+

@lang('admin.settings.social_twitter_no_services')

+

@lang('admin.settings.social_add_external_services_link')

+
+ @else +
+ + +
-
- - +
+ + - @if ($errors->has('twitter_app_id')) -
- {{ $errors->first('twitter_app_id') }} -
- @endif -
- -
- - - - @if ($errors->has('twitter_app_secret')) -
- {{ $errors->first('twitter_app_secret') }} -
- @endif -
+ @if ($errors->has('twitter_external_service_id')) +
+ {{ $errors->first('twitter_external_service_id') }} +
+ @endif +
+ @endif
@@ -593,32 +595,33 @@ @lang('admin.settings.social_google') -
- - -
+ @if (count($googleServices) == 0) +
+

@lang('admin.settings.social_google_no_services')

+

@lang('admin.settings.social_add_external_services_link')

+
+ @else +
+ + +
+ @endif
- - + + - @if ($errors->has('google_app_id')) + @if ($errors->has('google_external_service_id'))
- {{ $errors->first('google_app_id') }} -
- @endif -
- -
- - - - @if ($errors->has('google_app_secret')) -
- {{ $errors->first('google_app_secret') }} + {{ $errors->first('google_external_service_id') }}
@endif
diff --git a/resources/views/themes/base/partials/admin_services_dropbox_options.blade.php b/resources/views/themes/base/partials/admin_services_dropbox_options.blade.php deleted file mode 100644 index b48c00f..0000000 --- a/resources/views/themes/base/partials/admin_services_dropbox_options.blade.php +++ /dev/null @@ -1,31 +0,0 @@ -
-
-
- - - - @if ($errors->has('app_id')) -
- {{ $errors->first('app_id') }} -
- @endif -
-
-
-
- - - - @if ($errors->has('app_secret')) -
- {{ $errors->first('app_secret') }} -
- @endif -
-
-
- -
-

@lang('admin.service_callback_intro', ['name' => trans(sprintf('services.%s', \App\ExternalService::DROPBOX))])

-

{{ $callbackUrls[\App\ExternalService::DROPBOX] }}

-
\ No newline at end of file diff --git a/resources/views/themes/base/partials/admin_services_oauth_options.blade.php b/resources/views/themes/base/partials/admin_services_oauth_options.blade.php index ac2db65..e5b76cc 100644 --- a/resources/views/themes/base/partials/admin_services_oauth_options.blade.php +++ b/resources/views/themes/base/partials/admin_services_oauth_options.blade.php @@ -23,4 +23,11 @@ @endif
-
\ No newline at end of file + + +@if (isset($callbackUrls[$oauthService])) +
+

@lang('admin.service_callback_intro', ['name' => trans(sprintf('services.%s', $oauthService))])

+

{{ $callbackUrls[$oauthService] }}

+
+@endif \ No newline at end of file diff --git a/resources/views/themes/base/partials/social_login_providers.blade.php b/resources/views/themes/base/partials/social_login_providers.blade.php index 13b9377..f327bae 100644 --- a/resources/views/themes/base/partials/social_login_providers.blade.php +++ b/resources/views/themes/base/partials/social_login_providers.blade.php @@ -1,11 +1,11 @@

@if (UserConfig::get('social_facebook_login')) - + @endif @if (UserConfig::get('social_twitter_login')) - + @endif @if (UserConfig::get('social_google_login')) - + @endif

\ No newline at end of file From 3655c28c73d86e1937c953ccbc3313696155a800 Mon Sep 17 00:00:00 2001 From: Andy Heathershaw Date: Thu, 30 Apr 2020 08:28:19 +0100 Subject: [PATCH 7/8] Facebook, Google and Twitter SSO app credentials are now migrated to the new services section when running under v2.2.0-beta.2. Providers no longer appear on the login/register page unless they are enabled AND a service has been selected. Added a link to amend services in the settings section. closes #152 --- app/ExternalService.php | 2 +- app/Helpers/ConfigHelper.php | 30 ++++- app/Http/Middleware/AppInstallation.php | 9 +- .../DataMigrationV2_2_0_beta_2.php | 112 ++++++++++++++++++ resources/lang/en/forms.php | 1 + .../themes/base/admin/settings.blade.php | 9 +- .../partials/social_login_providers.blade.php | 6 +- 7 files changed, 155 insertions(+), 14 deletions(-) create mode 100644 database/data_migrations/DataMigrationV2_2_0_beta_2.php diff --git a/app/ExternalService.php b/app/ExternalService.php index d4a1a93..9a908ed 100644 --- a/app/ExternalService.php +++ b/app/ExternalService.php @@ -16,7 +16,7 @@ class ExternalService extends Model * * @var array */ - protected $fillable = ['name', 'service_type']; + protected $fillable = ['name', 'service_type', 'app_id', 'app_secret']; /** * Gets all possible service configurations for the given service type. diff --git a/app/Helpers/ConfigHelper.php b/app/Helpers/ConfigHelper.php index eb53d1b..80bc340 100644 --- a/app/Helpers/ConfigHelper.php +++ b/app/Helpers/ConfigHelper.php @@ -113,8 +113,8 @@ class ConfigHelper 'date_format' => $this->allowedDateFormats()[0], 'default_album_view' => $this->allowedAlbumViews()[0], 'enable_visitor_hits' => false, - 'facebook_external_service_id' => null, - 'google_external_service_id' => null, + 'facebook_external_service_id' => 0, + 'google_external_service_id' => 0, 'hotlink_protection' => false, 'items_per_page' => 12, 'items_per_page_admin' => 10, @@ -149,7 +149,7 @@ class ConfigHelper 'social_user_feeds' => false, 'social_user_profiles' => false, 'theme' => 'default', - 'twitter_external_service_id' => null + 'twitter_external_service_id' => 0 ); } @@ -216,11 +216,29 @@ 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->get('social_facebook_login') || - $this->get('social_twitter_login') || - $this->get('social_google_login'); + return $this->isLoginWithFacebookEnabled() || + $this->isLoginWithGoogleEnabled() || + $this->isLoginWithTwitterEnabled(); } private function loadCache() diff --git a/app/Http/Middleware/AppInstallation.php b/app/Http/Middleware/AppInstallation.php index b711d99..d90361f 100644 --- a/app/Http/Middleware/AppInstallation.php +++ b/app/Http/Middleware/AppInstallation.php @@ -69,7 +69,10 @@ class AppInstallation if ($isAppInstalled) { // See if an update is necessary - $this->updateDatabaseIfRequired(); + if ($this->updateDatabaseIfRequired()) + { + return redirect($request->fullUrl()); + } // App is configured, continue on return $next($request); @@ -152,6 +155,10 @@ class AppInstallation // Rebuild the permissions cache $helper = new PermissionsHelper(); $helper->rebuildCache(); + + return true; } + + return false; } } \ No newline at end of file diff --git a/database/data_migrations/DataMigrationV2_2_0_beta_2.php b/database/data_migrations/DataMigrationV2_2_0_beta_2.php new file mode 100644 index 0000000..1f01120 --- /dev/null +++ b/database/data_migrations/DataMigrationV2_2_0_beta_2.php @@ -0,0 +1,112 @@ +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(); + } +} \ No newline at end of file diff --git a/resources/lang/en/forms.php b/resources/lang/en/forms.php index 994b770..64f1e62 100644 --- a/resources/lang/en/forms.php +++ b/resources/lang/en/forms.php @@ -82,6 +82,7 @@ return [ 'settings_moderate_known_users_help' => 'If this option is enabled, comments posted by logged-in users must be moderated before being displayed.', 'settings_restrict_originals_download' => 'Restrict access to original images', 'settings_restrict_originals_download_help' => 'With this option enabled, only the photo\'s owner can download the original high-resolution images.', + 'settings_social_external_services_edit_link' => 'Add or change these details in Services', 'settings_social_facebook_external_service' => 'Facebook service:', 'settings_social_facebook_login' => 'Allow login/registration with a Facebook account.', 'settings_social_facebook_login_help' => 'With this option enabled, users can register (if enabled) and login with their Facebook account.', diff --git a/resources/views/themes/base/admin/settings.blade.php b/resources/views/themes/base/admin/settings.blade.php index 5aa83f2..85ed4dd 100644 --- a/resources/views/themes/base/admin/settings.blade.php +++ b/resources/views/themes/base/admin/settings.blade.php @@ -516,11 +516,12 @@
+ @lang('forms.settings_social_external_services_edit_link') @if ($errors->has('facebook_external_service_id'))
@@ -564,11 +565,12 @@
+ @lang('forms.settings_social_external_services_edit_link') @if ($errors->has('twitter_external_service_id'))
@@ -613,11 +615,12 @@
+ @lang('forms.settings_social_external_services_edit_link') @if ($errors->has('google_external_service_id'))
diff --git a/resources/views/themes/base/partials/social_login_providers.blade.php b/resources/views/themes/base/partials/social_login_providers.blade.php index f327bae..bf73c49 100644 --- a/resources/views/themes/base/partials/social_login_providers.blade.php +++ b/resources/views/themes/base/partials/social_login_providers.blade.php @@ -1,11 +1,11 @@

- @if (UserConfig::get('social_facebook_login')) + @if (UserConfig::isLoginWithFacebookEnabled()) @endif - @if (UserConfig::get('social_twitter_login')) + @if (UserConfig::isLoginWithTwitterEnabled()) @endif - @if (UserConfig::get('social_google_login')) + @if (UserConfig::isLoginWithGoogleEnabled()) @endif

\ No newline at end of file From 8f9a38649487f1f5835578a8db9487196452586f Mon Sep 17 00:00:00 2001 From: Andy Heathershaw Date: Thu, 30 Apr 2020 08:38:37 +0100 Subject: [PATCH 8/8] Prevent deleting service definitions when they are in use throughout the system. Closes #153 --- .../Controllers/Admin/ServiceController.php | 25 +++++++++++++++++-- resources/lang/en/admin.php | 2 +- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/Admin/ServiceController.php b/app/Http/Controllers/Admin/ServiceController.php index 464878c..32e3e0f 100644 --- a/app/Http/Controllers/Admin/ServiceController.php +++ b/app/Http/Controllers/Admin/ServiceController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Admin; +use App\Configuration; use App\ExternalService; use App\Facade\Theme; use App\Facade\UserConfig; @@ -311,8 +312,28 @@ class ServiceController extends Controller private function isServiceInUse(ExternalService $service) { - // TODO check if the service is in use anywhere else and prevent it being deleted if so - return false; + 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() diff --git a/resources/lang/en/admin.php b/resources/lang/en/admin.php index 65a714a..2e5e9e1 100644 --- a/resources/lang/en/admin.php +++ b/resources/lang/en/admin.php @@ -72,7 +72,7 @@ return [ 'bulk_photos_changed' => ':number photo was updated successfully.|:number photos were updated successfully.', 'bulk_photos_changed_queued' => 'Your requested change has been queued. :number photo will be updated shortly.|Your requested change has been queued. :number photos will be updated shortly.', 'cannot_delete_own_user_account' => 'It is not possible to delete your own user account. Please ask another administrator to delete it for you.', - 'cannot_delete_service_in_use' => 'This service is still in use and cannot be deleted. Please remove any references to it in your configuration and try again.', + 'cannot_delete_service_in_use' => 'This service is still in use and cannot be deleted. Please remove any references to it in your configuration or storages and try again.', 'cannot_remove_own_admin' => 'You cannot remove your own administrator permissions. Please ask another administrator to remove the administrator permissions for you.', 'change_album_message' => 'Please select the album to move the photo(s) to:', 'change_album_title' => 'Move photo(s) to another album',