first commit
Some checks failed
Build / run (push) Has been cancelled

This commit is contained in:
maher
2025-10-29 11:42:25 +01:00
commit 703f50a09d
4595 changed files with 385164 additions and 0 deletions

View 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';
}
}

View 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',
);
}
}

View 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);
}
}

View 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);
}
}
}

View 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;
}
}

View 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');
}
}
}