242 lines
9.2 KiB
PHP
242 lines
9.2 KiB
PHP
<?php
|
|
|
|
namespace App\Providers;
|
|
|
|
use App\Classes\Synths\PriceSynth;
|
|
use App\Helpers\ExtensionHelper;
|
|
use App\Models\EmailLog;
|
|
use App\Models\Extension;
|
|
use App\Models\OauthClient;
|
|
use App\Models\User;
|
|
use App\Support\Passport\ScopeRegistry;
|
|
use Closure;
|
|
use Dedoc\Scramble\Scramble;
|
|
use Dedoc\Scramble\Support\Generator\OpenApi;
|
|
use Dedoc\Scramble\Support\Generator\SecurityScheme;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Queue\Events\JobFailed;
|
|
use Illuminate\Queue\Events\JobProcessed;
|
|
use Illuminate\Routing\UrlGenerator;
|
|
use Illuminate\Support\Facades\Event;
|
|
use Illuminate\Support\Facades\File;
|
|
use Illuminate\Support\Facades\Gate;
|
|
use Illuminate\Support\Facades\Queue;
|
|
use Illuminate\Support\ServiceProvider;
|
|
use Illuminate\Support\Str;
|
|
use Laravel\Passport\Passport;
|
|
use League\CommonMark\Extension\Table\TableExtension;
|
|
|
|
class AppServiceProvider extends ServiceProvider
|
|
{
|
|
/**
|
|
* Register any application services.
|
|
*/
|
|
public function register(): void
|
|
{
|
|
// Service provider for settings
|
|
$this->app->register(SettingsProvider::class);
|
|
|
|
UrlGenerator::macro('alternateHasCorrectSignature', function (Request $request, $absolute = true, Closure|array $ignoreQuery = []) {
|
|
// ensure the base path is applied to absolute url
|
|
$absoluteUrl = url($request->path()); // forceRootUrl and forceScheme will apply
|
|
$url = $absolute ? $absoluteUrl : '/' . $request->path();
|
|
|
|
$queryString = collect(explode('&', (string) $request->server->get('QUERY_STRING')))
|
|
->reject(function ($parameter) use ($ignoreQuery) {
|
|
$parameter = Str::before($parameter, '=');
|
|
|
|
if ($parameter === 'signature') {
|
|
return true;
|
|
}
|
|
|
|
if ($ignoreQuery instanceof Closure) {
|
|
return $ignoreQuery($parameter);
|
|
}
|
|
|
|
return in_array($parameter, $ignoreQuery);
|
|
})
|
|
->join('&');
|
|
|
|
$original = rtrim($url . '?' . $queryString, '?');
|
|
|
|
$keys = call_user_func($this->keyResolver);
|
|
|
|
$keys = is_array($keys) ? $keys : [$keys];
|
|
|
|
foreach ($keys as $key) {
|
|
if (
|
|
hash_equals(
|
|
hash_hmac('sha256', $original, $key),
|
|
(string) $request->query('signature', '')
|
|
)
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
UrlGenerator::macro('alternateHasValidSignature', function (Request $request, $absolute = true, array $ignoreQuery = []) {
|
|
return \URL::alternateHasCorrectSignature($request, $absolute, $ignoreQuery)
|
|
&& \URL::signatureHasNotExpired($request);
|
|
});
|
|
|
|
Request::macro('hasValidSignature', function ($absolute = true, array $ignoreQuery = []) {
|
|
return \URL::alternateHasValidSignature($this, $absolute, $ignoreQuery);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Bootstrap any application services.
|
|
*/
|
|
public function boot(): void
|
|
{
|
|
// Ensure storage symlink exists for public access
|
|
$this->ensureStorageSymlinkExists();
|
|
|
|
// Change livewire url
|
|
\Livewire\Livewire::setUpdateRoute(function ($handle) {
|
|
return \Illuminate\Support\Facades\Route::post('/paymenter/update', $handle)->middleware('web')->name('paymenter.');
|
|
});
|
|
\Livewire\Livewire::propertySynthesizer(PriceSynth::class);
|
|
|
|
Gate::define('has-permission', function (User $user, string $ability) {
|
|
return $user->hasPermission($ability);
|
|
});
|
|
|
|
Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) {
|
|
$event->extendSocialite('discord', \SocialiteProviders\Discord\Provider::class);
|
|
});
|
|
|
|
try {
|
|
foreach (
|
|
collect(Extension::where(function ($query) {
|
|
$query->where('enabled', true)->orWhere('type', 'server')->orWhere('type', 'gateway');
|
|
})->get())->unique('extension') as $extension
|
|
) {
|
|
ExtensionHelper::call($extension, 'boot', mayFail: true);
|
|
}
|
|
} catch (\Exception $e) {
|
|
// Fail silently
|
|
}
|
|
|
|
Queue::after(function (JobProcessed $event) {
|
|
if ($event->job->resolveName() === 'App\Mail\Mail') {
|
|
$payload = json_decode($event->job->getRawBody());
|
|
$data = unserialize($payload->data->command);
|
|
EmailLog::where('id', $data->mailable->email_log_id)->update([
|
|
'sent_at' => now(),
|
|
'status' => 'sent',
|
|
]);
|
|
}
|
|
});
|
|
Queue::failing(function (JobFailed $event) {
|
|
if ($event->job->resolveName() === 'App\Mail\Mail') {
|
|
$payload = json_decode($event->job->getRawBody());
|
|
$data = unserialize($payload->data->command);
|
|
EmailLog::where('id', $data->mailable->email_log_id)->update([
|
|
'status' => 'failed',
|
|
'error' => $event->exception->getMessage(),
|
|
'job_uuid' => $event->job->uuid(),
|
|
]);
|
|
}
|
|
});
|
|
|
|
Str::macro('markdown', function ($markdown) {
|
|
return Str::markdown($markdown, extensions: [
|
|
new TableExtension,
|
|
]);
|
|
});
|
|
Passport::clientModel(OauthClient::class);
|
|
Passport::ignoreRoutes();
|
|
Passport::tokensCan(ScopeRegistry::getAll());
|
|
|
|
if (class_exists(Scramble::class)) {
|
|
Scramble::configure()
|
|
->routes(function (\Illuminate\Routing\Route $route) {
|
|
return Str::startsWith($route->uri, 'api/v1/admin');
|
|
})
|
|
->withDocumentTransformers(function (OpenApi $openApi) {
|
|
$openApi->secure(
|
|
SecurityScheme::http('bearer')
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensure the storage symlink exists for public file access
|
|
*/
|
|
protected function ensureStorageSymlinkExists(): void
|
|
{
|
|
$publicStoragePath = public_path('storage');
|
|
$targetStoragePath = storage_path('app/public');
|
|
|
|
// Check if symlink doesn't exist or is broken
|
|
if (!file_exists($publicStoragePath) || !is_link($publicStoragePath)) {
|
|
// Remove broken symlink or existing file/directory if it exists
|
|
if (file_exists($publicStoragePath)) {
|
|
if (is_link($publicStoragePath)) {
|
|
@unlink($publicStoragePath);
|
|
} elseif (is_dir($publicStoragePath)) {
|
|
// If it's a directory, we can't safely remove it automatically
|
|
// Skip silently - the user is aware of this situation
|
|
return;
|
|
} else {
|
|
@unlink($publicStoragePath);
|
|
}
|
|
}
|
|
|
|
// Ensure target directory exists
|
|
if (!file_exists($targetStoragePath)) {
|
|
File::makeDirectory($targetStoragePath, 0755, true);
|
|
}
|
|
|
|
// Create the symlink using Laravel's filesystem links
|
|
try {
|
|
$links = config('filesystems.links', []);
|
|
foreach ($links as $link => $target) {
|
|
if (file_exists($link)) {
|
|
continue;
|
|
}
|
|
|
|
if (PHP_OS_FAMILY === 'Windows') {
|
|
// Windows requires administrator privileges for symlinks
|
|
// Try to create junction instead
|
|
if (function_exists('exec')) {
|
|
$command = sprintf(
|
|
'mklink /J "%s" "%s"',
|
|
str_replace('/', '\\', $link),
|
|
str_replace('/', '\\', $target)
|
|
);
|
|
@exec($command, $output, $return);
|
|
// Silently ignore if symlink creation fails on Windows
|
|
}
|
|
// Skip silently if exec is not available on Windows
|
|
} else {
|
|
// Unix/Linux - create symlink
|
|
symlink($target, $link);
|
|
}
|
|
}
|
|
} catch (\Exception $e) {
|
|
// Silently ignore symlink creation failures
|
|
}
|
|
} elseif (is_link($publicStoragePath)) {
|
|
// Check if symlink is broken (points to non-existent target)
|
|
$linkTarget = readlink($publicStoragePath);
|
|
if ($linkTarget && !file_exists($linkTarget) && $linkTarget !== $targetStoragePath) {
|
|
// Symlink is broken, recreate it
|
|
@unlink($publicStoragePath);
|
|
try {
|
|
if (PHP_OS_FAMILY !== 'Windows') {
|
|
symlink($targetStoragePath, $publicStoragePath);
|
|
}
|
|
} catch (\Exception $e) {
|
|
\Log::warning('Storage symlink recreation failed: ' . $e->getMessage() . '. Please run: php artisan storage:link');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|