146
common/Core/Install/CheckSiteHealth.php
Executable file
146
common/Core/Install/CheckSiteHealth.php
Executable file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Core\Install;
|
||||
|
||||
class CheckSiteHealth
|
||||
{
|
||||
public function execute(): array
|
||||
{
|
||||
return $this->performChecks();
|
||||
}
|
||||
|
||||
protected function performChecks(): array
|
||||
{
|
||||
$minPhpVersion = $this->getMinimumPhpversion();
|
||||
$results = collect([
|
||||
'server' => [
|
||||
'items' => [
|
||||
'PHP Version' => [
|
||||
'passes' => version_compare(
|
||||
PHP_VERSION,
|
||||
$minPhpVersion,
|
||||
'>',
|
||||
),
|
||||
'errorMessage' => "You need at least $minPhpVersion PHP version.",
|
||||
],
|
||||
],
|
||||
],
|
||||
'extensions' => [
|
||||
'items' => [
|
||||
'PDO' => [
|
||||
'passes' => defined('PDO::ATTR_DRIVER_NAME'),
|
||||
'errorMessage' =>
|
||||
'PHP PDO extension needs to be enabled.',
|
||||
],
|
||||
'XML' => [
|
||||
'passes' => extension_loaded('xml'),
|
||||
'errorMessage' =>
|
||||
'PHP XML extension needs to be enabled.',
|
||||
],
|
||||
'Mbstring' => [
|
||||
'passes' => extension_loaded('mbstring'),
|
||||
'errorMessage' =>
|
||||
'PHP mbstring extension needs to be enabled.',
|
||||
],
|
||||
'Fileinfo' => [
|
||||
'passes' => extension_loaded('fileinfo'),
|
||||
'errorMessage' =>
|
||||
'PHP fileinfo extension needs to be enabled.',
|
||||
],
|
||||
'OpenSSL' => [
|
||||
'passes' => extension_loaded('openssl'),
|
||||
'errorMessage' =>
|
||||
'PHP openssl extension needs to be enabled.',
|
||||
],
|
||||
'GD' => [
|
||||
'passes' => extension_loaded('gd'),
|
||||
'errorMessage' =>
|
||||
'PHP GD extension needs to be enabled.',
|
||||
],
|
||||
'Curl' => [
|
||||
'passes' => extension_loaded('curl'),
|
||||
'errorMessage' =>
|
||||
'PHP curl extension needs to be enabled.',
|
||||
],
|
||||
'Zip' => [
|
||||
'passes' => class_exists('ZipArchive'),
|
||||
'errorMessage' =>
|
||||
'PHP ZipArchive extension needs to be installed.',
|
||||
],
|
||||
'fpassthru' => [
|
||||
'passes' => function_exists('fpassthru'),
|
||||
'errorMessage' =>
|
||||
'"fpassthru" PHP function needs to be enabled.',
|
||||
],
|
||||
],
|
||||
],
|
||||
'filesystem' => $this->checkFilesystemPermissions(),
|
||||
])->toArray();
|
||||
|
||||
$someFailed = false;
|
||||
foreach ($results as $groupName => $group) {
|
||||
$results[$groupName]['allPassed'] = collect($group['items'])->every(
|
||||
'passes',
|
||||
);
|
||||
if (!$results[$groupName]['allPassed']) {
|
||||
$someFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'results' => $results,
|
||||
'allPassed' => !$someFailed,
|
||||
];
|
||||
}
|
||||
|
||||
protected function checkFilesystemPermissions(): array
|
||||
{
|
||||
$basePath = base_path();
|
||||
return [
|
||||
'items' => collect([
|
||||
'.htaccess',
|
||||
'public/.htaccess',
|
||||
config('common.site.installed') ? '.env' : 'env.example',
|
||||
'storage',
|
||||
'storage/app',
|
||||
'storage/logs',
|
||||
'storage/framework',
|
||||
'public/storage',
|
||||
])
|
||||
->map(function ($directory) use ($basePath) {
|
||||
$path = rtrim("$basePath/$directory", '/');
|
||||
if (is_file($path)) {
|
||||
if (!is_writable($path)) {
|
||||
@chmod($path, 0664);
|
||||
}
|
||||
return [
|
||||
'path' => $path,
|
||||
'passes' =>
|
||||
is_writable($path) && filesize($path) > 0,
|
||||
'errorMessage' => "Make sure <strong>$path</strong> has been uploaded properly and is writable (0664 permission).",
|
||||
];
|
||||
} else {
|
||||
if (!is_writable($path)) {
|
||||
@chmod($path, 0775);
|
||||
}
|
||||
return [
|
||||
'path' => $path,
|
||||
'passes' => is_writable($path),
|
||||
'errorMessage' => "Make <strong>$path</strong> writable by giving it 0775 or 0777 permissions via file manager.",
|
||||
];
|
||||
}
|
||||
})
|
||||
->toArray(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getMinimumPhpversion(): string
|
||||
{
|
||||
$composer = json_decode(
|
||||
file_get_contents(base_path('composer.json')),
|
||||
true,
|
||||
);
|
||||
preg_match('/(\d+\.\d+\.\d+)/', $composer['require']['php'], $matches);
|
||||
return $matches[1] ?? '8.1';
|
||||
}
|
||||
}
|
||||
201
common/Core/Install/InstallController.php
Executable file
201
common/Core/Install/InstallController.php
Executable file
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Core\Install;
|
||||
|
||||
use App\Models\User;
|
||||
use Common\Admin\Appearance\GenerateFavicon;
|
||||
use Common\Auth\Permissions\Permission;
|
||||
use Common\Database\MigrateAndSeed;
|
||||
use Common\Settings\DotEnvEditor;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class InstallController
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
if (config('common.site.installed')) {
|
||||
abort(404);
|
||||
}
|
||||
}
|
||||
|
||||
public function introductionStep()
|
||||
{
|
||||
$this->createHtaccessFiles();
|
||||
return view('common::install/introduction');
|
||||
}
|
||||
|
||||
public function requirementsStep()
|
||||
{
|
||||
$data = (new CheckSiteHealth())->execute();
|
||||
return view('common::install/requirements')->with($data);
|
||||
}
|
||||
|
||||
public function databaseStep()
|
||||
{
|
||||
$credentials = config('database.connections.mysql');
|
||||
|
||||
return view('common::install/database', [
|
||||
'host' => $credentials['host'],
|
||||
'database' => $credentials['database'],
|
||||
'username' => $credentials['username'],
|
||||
'password' => $credentials['password'],
|
||||
'port' => $credentials['port'],
|
||||
'prefix' => $credentials['prefix'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function insertAndValidateDatabaseCredentials()
|
||||
{
|
||||
$data = request()->validate([
|
||||
'host' => 'required|string',
|
||||
'database' => 'required|string',
|
||||
'username' => 'required|string',
|
||||
'password' => 'nullable|string',
|
||||
'port' => 'nullable|int',
|
||||
'prefix' => 'nullable|string|max:10',
|
||||
]);
|
||||
|
||||
$this->getEnvWriter()->write(
|
||||
collect($data)->mapWithKeys(
|
||||
fn($value, $key) => ['DB_' . strtoupper($key) => $value],
|
||||
),
|
||||
);
|
||||
config()->set(
|
||||
'database.connections.mysql',
|
||||
array_merge(config('database.connections.mysql'), $data),
|
||||
);
|
||||
|
||||
try {
|
||||
$tables = DB::select('SHOW TABLES');
|
||||
if (count($tables) > 0) {
|
||||
return back()->withErrors([
|
||||
'database' =>
|
||||
'Database is not empty. Please provide an empty database or delete all tables from the current one.',
|
||||
]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return back()->withErrors([
|
||||
'database' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
if (!file_exists(base_path('.env'))) {
|
||||
rename(base_path('env.example'), base_path('.env'));
|
||||
}
|
||||
|
||||
return redirect('install/admin');
|
||||
}
|
||||
|
||||
public function adminStep()
|
||||
{
|
||||
return view('common::install/admin');
|
||||
}
|
||||
|
||||
public function validateAdminCredentials()
|
||||
{
|
||||
$data = request()->validate([
|
||||
'email' => 'required|email',
|
||||
'password' => 'required|string|min:4|confirmed',
|
||||
]);
|
||||
|
||||
return redirect('install/finalize')->with('adminCredentials', $data);
|
||||
}
|
||||
|
||||
public function finalizeStep()
|
||||
{
|
||||
if (!session()->has('adminCredentials')) {
|
||||
return redirect('install/admin');
|
||||
}
|
||||
|
||||
// app key
|
||||
$appKey = 'base64:' . base64_encode(random_bytes(32));
|
||||
$this->getEnvWriter()->write(['APP_KEY' => $appKey]);
|
||||
|
||||
// clear cache
|
||||
Cache::flush();
|
||||
Schema::defaultStringLength(191);
|
||||
|
||||
// migrate/seed/admin account
|
||||
(new MigrateAndSeed())->execute(function () {
|
||||
$credentials = session('adminCredentials');
|
||||
$user = app(User::class)->firstOrNew([
|
||||
'email' => $credentials['email'],
|
||||
]);
|
||||
$user->password = $credentials['password'];
|
||||
$user->email_verified_at = now();
|
||||
$user->save();
|
||||
$adminPermission = app(Permission::class)->firstOrCreate(
|
||||
['name' => 'admin'],
|
||||
[
|
||||
'name' => 'admin',
|
||||
'group' => 'admin',
|
||||
'display_name' => 'Super Admin',
|
||||
'description' => 'Give all permissions to user.',
|
||||
],
|
||||
);
|
||||
$user->permissions()->syncWithoutDetaching($adminPermission->id);
|
||||
Auth::login($user);
|
||||
});
|
||||
|
||||
$appUrl = $this->getFinalSiteUrl();
|
||||
|
||||
// finalize
|
||||
$this->getEnvWriter()->write([
|
||||
'app_url' => $appUrl,
|
||||
'app_env' => 'production',
|
||||
'app_debug' => false,
|
||||
'cache_driver' => 'file',
|
||||
'installed' => true,
|
||||
]);
|
||||
|
||||
// move default favicons
|
||||
File::copyDirectory(
|
||||
base_path('assets/favicons'),
|
||||
public_path(GenerateFavicon::FAVICON_DIR),
|
||||
);
|
||||
|
||||
Cache::flush();
|
||||
|
||||
return view('common::install/finalize')->with([
|
||||
'url' => $appUrl,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getFinalSiteUrl(): string
|
||||
{
|
||||
// config('app.url') will already be updated by "AppUrl" class at this point,
|
||||
// we just need to trim "public" in case .htaccess file was not created for some reason
|
||||
$url = config('app.url');
|
||||
$url = rtrim($url, 'public');
|
||||
return rtrim($url, '/');
|
||||
}
|
||||
|
||||
protected function createHtaccessFiles(): void
|
||||
{
|
||||
$rootHtaccess = base_path('.htaccess');
|
||||
$rootHtaccessStub = base_path('htaccess.example');
|
||||
$publicHtaccess = public_path('.htaccess');
|
||||
$publicHtaccessStub = base_path('public/htaccess.example');
|
||||
|
||||
if (!file_exists($rootHtaccess)) {
|
||||
$contents = file_get_contents($rootHtaccessStub);
|
||||
file_put_contents($rootHtaccess, $contents);
|
||||
}
|
||||
|
||||
if (!file_exists($publicHtaccess)) {
|
||||
$contents = file_get_contents($publicHtaccessStub);
|
||||
file_put_contents($publicHtaccess, $contents);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getEnvWriter(): DotEnvEditor
|
||||
{
|
||||
return new DotEnvEditor(
|
||||
file_exists(base_path('.env')) ? '.env' : 'env.example',
|
||||
);
|
||||
}
|
||||
}
|
||||
22
common/Core/Install/RedirectIfNotInstalledMiddleware.php
Executable file
22
common/Core/Install/RedirectIfNotInstalledMiddleware.php
Executable file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Core\Install;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class RedirectIfNotInstalledMiddleware
|
||||
{
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
if (
|
||||
!config('common.site.installed') &&
|
||||
!Str::contains($request->path(), 'install')
|
||||
) {
|
||||
return redirect()->route('install');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
61
common/Core/Install/UpdateActions.php
Executable file
61
common/Core/Install/UpdateActions.php
Executable file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Core\Install;
|
||||
|
||||
use Common\Database\MigrateAndSeed;
|
||||
use Common\Settings\DotEnvEditor;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class UpdateActions
|
||||
{
|
||||
public function execute(): void
|
||||
{
|
||||
@ini_set('memory_limit', '-1');
|
||||
@set_time_limit(0);
|
||||
|
||||
//fix "index is too long" issue on MariaDB and older mysql versions
|
||||
Schema::defaultStringLength(191);
|
||||
|
||||
app(MigrateAndSeed::class)->execute();
|
||||
|
||||
if (
|
||||
file_exists(base_path('env.example')) &&
|
||||
file_exists(base_path('.env'))
|
||||
) {
|
||||
$envExampleValues = (new DotEnvEditor('env.example'))->load(
|
||||
'env.example',
|
||||
);
|
||||
$currentEnvValues = (new DotEnvEditor())->load();
|
||||
$envValuesToWrite = array_diff_key(
|
||||
$envExampleValues,
|
||||
$currentEnvValues,
|
||||
);
|
||||
$envValuesToWrite['app_version'] = $envExampleValues['app_version'];
|
||||
$envValuesToWrite['installed'] = true;
|
||||
|
||||
// mark mail as setup if app was installed before this setting was added.
|
||||
if (!isset($currentEnvValues['mail_setup'])) {
|
||||
$envValuesToWrite['mail_setup'] = true;
|
||||
}
|
||||
|
||||
if (
|
||||
isset($currentEnvValues['scout_driver']) &&
|
||||
$currentEnvValues['scout_driver'] === 'mysql-like'
|
||||
) {
|
||||
$envValuesToWrite['scout_driver'] = 'mysql';
|
||||
}
|
||||
|
||||
(new DotEnvEditor())->write($envValuesToWrite);
|
||||
}
|
||||
|
||||
Cache::flush();
|
||||
|
||||
// clear cached views
|
||||
$path = config('view.compiled');
|
||||
foreach (File::glob("{$path}/*") as $view) {
|
||||
File::delete($view);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
common/Core/Install/UpdateActionsCommand.php
Executable file
19
common/Core/Install/UpdateActionsCommand.php
Executable file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Core\Install;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class UpdateActionsCommand extends Command
|
||||
{
|
||||
protected $signature = 'update:run';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
(new UpdateActions())->execute();
|
||||
|
||||
$this->info('Update complete');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
43
common/Core/Install/UpdateController.php
Executable file
43
common/Core/Install/UpdateController.php
Executable file
@@ -0,0 +1,43 @@
|
||||
<?php namespace Common\Core\Install;
|
||||
|
||||
use Common\Core\BaseController;
|
||||
use Common\Settings\DotEnvEditor;
|
||||
use Exception;
|
||||
|
||||
class UpdateController extends BaseController
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
if (
|
||||
!config('common.site.disable_update_auth') &&
|
||||
version_compare(
|
||||
config('common.site.version'),
|
||||
$this->getAppVersion(),
|
||||
) === 0
|
||||
) {
|
||||
$this->middleware('isAdmin');
|
||||
}
|
||||
}
|
||||
|
||||
public function show()
|
||||
{
|
||||
$data = (new CheckSiteHealth())->execute();
|
||||
return view('common::install/update')->with($data);
|
||||
}
|
||||
|
||||
public function performUpdate()
|
||||
{
|
||||
(new UpdateActions())->execute();
|
||||
|
||||
return view('common::install/update-complete');
|
||||
}
|
||||
|
||||
private function getAppVersion(): string
|
||||
{
|
||||
try {
|
||||
return (new DotEnvEditor('env.example'))->load()['app_version'];
|
||||
} catch (Exception $e) {
|
||||
return config('common.site.version');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user