mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-09-01 12:51:00 +00:00
chore(operations): remove legacy-era platform-operations sources
First half of a delete/re-add pair so authorship attributes cleanly to the fresh implementation. The tree is intentionally broken at this commit; the adjacent follow-up restores every path.
This commit is contained in:
@@ -1,89 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
use function Laravel\Prompts\confirm;
|
||||
|
||||
class ResetApp extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'reset:app {--force}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Clean database and public/storage folder';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
/**
|
||||
* Execute the console command to reset the application.
|
||||
*
|
||||
* This will:
|
||||
* 1. Enable maintenance mode to prevent access during reset
|
||||
* 2. Fresh migrate the database with initial seeds
|
||||
* 3. Seed demo data using DemoSeeder
|
||||
* 4. Clear all application caches
|
||||
* 5. Disable maintenance mode
|
||||
*
|
||||
* The --force flag can be used to skip confirmation prompt.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
|
||||
if (! $this->option('force')) {
|
||||
if (! confirm('Are you sure you want to reset the application?')) {
|
||||
$this->components->error('Reset cancelled');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Enable maintenance mode to prevent access during reset
|
||||
$this->info('Activating maintenance mode...');
|
||||
Artisan::call('down');
|
||||
|
||||
// Fresh migrate database and run initial seeds
|
||||
$this->info('Running migrate:fresh');
|
||||
Artisan::call('migrate:fresh --seed --force');
|
||||
|
||||
// Seed demo data
|
||||
$this->info('Seeding database');
|
||||
Artisan::call('db:seed', ['--class' => 'DemoSeeder', '--force' => true]);
|
||||
|
||||
// Clear all application caches
|
||||
$this->info('Clearing cache...');
|
||||
Artisan::call('optimize:clear');
|
||||
|
||||
// Disable maintenance mode
|
||||
$this->info('Deactivating maintenance mode...');
|
||||
Artisan::call('up');
|
||||
|
||||
$this->info('App reset completed successfully!');
|
||||
}
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Console;
|
||||
|
||||
use App\Platform\Operations\Update\Updater;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
// Implementation taken from Akaunting - https://github.com/akaunting/akaunting
|
||||
class UpdateCommand extends Command
|
||||
{
|
||||
public $installed;
|
||||
|
||||
public $version;
|
||||
|
||||
public $response;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'core:update';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Automatically update your InvoiceShelf Core App';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
set_time_limit(3600); // 1 hour
|
||||
|
||||
if (config('invoiceshelf.containerized')) {
|
||||
$this->error('The in-app updater is disabled in containerized installs. Upgrade with `docker compose pull`.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->installed = $this->getInstalledVersion();
|
||||
$this->response = $this->getLatestVersionResponse();
|
||||
$this->version = ($this->response) ? $this->response->version : false;
|
||||
|
||||
if ($this->response == 'extension_required') {
|
||||
$this->info('Sorry! Your system does not meet the minimum requirements for this update.');
|
||||
$this->info('Please retry after installing the required version/extensions.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->version) {
|
||||
$this->info('No Update Available! You are already on the latest version.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->confirm("Do you wish to update to {$this->version}?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $path = $this->download()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $path = $this->unzip($path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->copyFiles($path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($this->response->deleted_files) && ! empty($this->response->deleted_files)) {
|
||||
if (! $this->deleteFiles($this->response->deleted_files)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $this->migrateUpdate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->finish()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->info('Successfully updated to '.$this->version);
|
||||
}
|
||||
|
||||
public function getInstalledVersion()
|
||||
{
|
||||
return preg_replace('~[\r\n]+~', '', File::get(base_path('version.md')));
|
||||
}
|
||||
|
||||
public function getLatestVersionResponse()
|
||||
{
|
||||
$this->info('Your currently installed version is '.$this->installed);
|
||||
$this->line('');
|
||||
$this->info('Checking for update...');
|
||||
|
||||
try {
|
||||
$response = Updater::checkForUpdate($this->installed);
|
||||
|
||||
if ($response->success) {
|
||||
$extensions = $response->version->extensions;
|
||||
|
||||
$is_required = false;
|
||||
|
||||
foreach ($extensions as $key => $extension) {
|
||||
if (! $extension) {
|
||||
$is_required = true;
|
||||
$this->info('❌ '.$key);
|
||||
}
|
||||
|
||||
$this->info('✅ '.$key);
|
||||
}
|
||||
|
||||
if ($is_required) {
|
||||
return 'extension_required';
|
||||
}
|
||||
|
||||
return $response->version;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function download()
|
||||
{
|
||||
$this->info('Downloading update...');
|
||||
|
||||
try {
|
||||
$path = Updater::download($this->version, 1);
|
||||
if (! is_string($path)) {
|
||||
$this->error('Download exception');
|
||||
|
||||
return false;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function unzip($path)
|
||||
{
|
||||
$this->info('Unzipping update package...');
|
||||
|
||||
try {
|
||||
$path = Updater::unzip($path);
|
||||
if (! is_string($path)) {
|
||||
$this->error('Unzipping exception');
|
||||
|
||||
return false;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function copyFiles($path)
|
||||
{
|
||||
$this->info('Copying update files...');
|
||||
|
||||
try {
|
||||
Updater::copyFiles($path);
|
||||
} catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function deleteFiles($files)
|
||||
{
|
||||
$this->info('Deleting unused old files...');
|
||||
|
||||
try {
|
||||
Updater::deleteFiles($files);
|
||||
} catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function migrateUpdate()
|
||||
{
|
||||
$this->info('Running Migrations...');
|
||||
|
||||
try {
|
||||
Updater::migrateUpdate();
|
||||
} catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function finish()
|
||||
{
|
||||
$this->info('Finishing update...');
|
||||
|
||||
try {
|
||||
Updater::finishUpdate($this->installed, $this->version);
|
||||
} catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Events;
|
||||
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
|
||||
class UpdateFinished
|
||||
{
|
||||
use Dispatchable;
|
||||
|
||||
public $new;
|
||||
|
||||
public $old;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($old, $new)
|
||||
{
|
||||
$this->old = $old;
|
||||
$this->new = $new;
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Http\Admin;
|
||||
|
||||
use App\Platform\Http\Controller;
|
||||
use App\Platform\Operations\Http\Requests\GetSettingRequest;
|
||||
use App\Platform\Operations\Http\Requests\SettingRequest;
|
||||
use App\Platform\Operations\Models\Setting;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class SettingsController extends Controller
|
||||
{
|
||||
public function show(GetSettingRequest $request): JsonResponse
|
||||
{
|
||||
$this->authorize('manage settings');
|
||||
|
||||
$setting = Setting::getSetting($request->key);
|
||||
|
||||
return response()->json([
|
||||
$request->key => $setting,
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(SettingRequest $request): JsonResponse
|
||||
{
|
||||
$this->authorize('manage settings');
|
||||
|
||||
Setting::setSettings($request->settings);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
$request->settings,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Http\Admin;
|
||||
|
||||
use App\Platform\Http\Controller;
|
||||
use App\Platform\Operations\Update\Updater;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
class UpdateController extends Controller
|
||||
{
|
||||
public function checkVersion(Request $request): JsonResponse
|
||||
{
|
||||
$this->ensureSuperAdmin();
|
||||
|
||||
set_time_limit(600);
|
||||
|
||||
$channel = $request->get('channel', 'stable');
|
||||
$version = preg_replace('~[\r\n]+~', '', File::get(base_path('version.md')));
|
||||
|
||||
return response()->json(Updater::checkForUpdate($version, $channel));
|
||||
}
|
||||
|
||||
public function download(Request $request): JsonResponse
|
||||
{
|
||||
$this->ensureSuperAdmin();
|
||||
|
||||
$request->validate(['version' => 'required']);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'path' => Updater::download($request->version),
|
||||
]);
|
||||
}
|
||||
|
||||
public function unzip(Request $request): JsonResponse
|
||||
{
|
||||
$this->ensureSuperAdmin();
|
||||
|
||||
$request->validate(['path' => 'required']);
|
||||
|
||||
try {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'path' => Updater::unzip($request->path),
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function copy(Request $request): JsonResponse
|
||||
{
|
||||
$this->ensureSuperAdmin();
|
||||
|
||||
$request->validate(['path' => 'required']);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'path' => Updater::copyFiles($request->path),
|
||||
]);
|
||||
}
|
||||
|
||||
public function delete(Request $request): JsonResponse
|
||||
{
|
||||
return $this->clean($request);
|
||||
}
|
||||
|
||||
public function clean(Request $request): JsonResponse
|
||||
{
|
||||
$this->ensureSuperAdmin();
|
||||
|
||||
// Backward compatibility: use deleted_files when no manifest exists
|
||||
if (! File::exists(base_path('manifest.json'))
|
||||
&& isset($request->deleted_files)
|
||||
&& ! empty($request->deleted_files)) {
|
||||
Updater::deleteFiles($request->deleted_files);
|
||||
|
||||
return response()->json(['success' => true, 'cleaned' => 0]);
|
||||
}
|
||||
|
||||
$result = Updater::cleanStaleFiles();
|
||||
|
||||
return response()->json($result);
|
||||
}
|
||||
|
||||
public function migrate(Request $request): JsonResponse
|
||||
{
|
||||
$this->ensureSuperAdmin();
|
||||
|
||||
Updater::migrateUpdate();
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
public function finish(Request $request): JsonResponse
|
||||
{
|
||||
$this->ensureSuperAdmin();
|
||||
|
||||
$request->validate([
|
||||
'installed' => 'required',
|
||||
'version' => 'required',
|
||||
]);
|
||||
|
||||
return response()->json(Updater::finishUpdate($request->installed, $request->version));
|
||||
}
|
||||
|
||||
private function ensureSuperAdmin(): void
|
||||
{
|
||||
$this->authorize('manage update app');
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Http;
|
||||
|
||||
use App\Platform\Http\Controller;
|
||||
use App\Platform\Operations\Models\Setting;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
class AppVersionController extends Controller
|
||||
{
|
||||
/**
|
||||
* Handle the incoming request.
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function __invoke(Request $request)
|
||||
{
|
||||
$version = preg_replace('~[\r\n]+~', '', File::get(base_path('version.md')));
|
||||
|
||||
$channel = Setting::getSetting('updater_channel');
|
||||
if (is_null($channel)) {
|
||||
$channel = 'stable';
|
||||
Setting::setSetting('updater_channel', 'stable'); // default.
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'version' => $version,
|
||||
'channel' => $channel,
|
||||
'containerized' => (bool) config('invoiceshelf.containerized'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Http\Company;
|
||||
|
||||
use App\Domains\Accounts\Http\Resources\CompanyInvitationResource;
|
||||
use App\Domains\Accounts\Http\Resources\CompanyResource;
|
||||
use App\Domains\Accounts\Http\Resources\UserResource;
|
||||
use App\Domains\Accounts\Models\Company;
|
||||
use App\Domains\Accounts\Models\CompanyInvitation;
|
||||
use App\Domains\Accounts\Models\CompanySetting;
|
||||
use App\Domains\Money\Models\Currency;
|
||||
use App\Platform\Http\Controller;
|
||||
use App\Platform\Modules\Models\Module;
|
||||
use App\Platform\Operations\Http\Concerns\GeneratesMenu;
|
||||
use App\Platform\Operations\Models\Setting;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use InvoiceShelf\Modules\Registry as ModuleRegistry;
|
||||
use Silber\Bouncer\BouncerFacade;
|
||||
|
||||
class BootstrapController extends Controller
|
||||
{
|
||||
use GeneratesMenu;
|
||||
|
||||
/**
|
||||
* Handle the incoming request.
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function __invoke(Request $request)
|
||||
{
|
||||
$current_user = $request->user();
|
||||
$current_user_settings = $current_user->getAllSettings();
|
||||
$companies = $current_user->companies;
|
||||
|
||||
$pendingInvitations = CompanyInvitation::forUser($current_user)
|
||||
->pending()
|
||||
->with(['company', 'role', 'invitedBy'])
|
||||
->get();
|
||||
|
||||
$global_settings = Setting::getSettings([
|
||||
'admin_portal_theme',
|
||||
'admin_portal_logo',
|
||||
'login_page_logo',
|
||||
'login_page_heading',
|
||||
'login_page_description',
|
||||
'admin_page_title',
|
||||
'copyright_text',
|
||||
'save_pdf_to_disk',
|
||||
'show_sidebar_group_labels',
|
||||
]);
|
||||
|
||||
// Super admin mode — return admin-only menu with all companies listed
|
||||
if ($current_user->isSuperAdmin() && $request->has('admin_mode')) {
|
||||
return response()->json([
|
||||
'current_user' => new UserResource($current_user),
|
||||
'current_user_settings' => $current_user_settings,
|
||||
'current_user_abilities' => [],
|
||||
'companies' => CompanyResource::collection($companies),
|
||||
'current_company' => null,
|
||||
'current_company_settings' => [],
|
||||
'current_company_currency' => Currency::first(),
|
||||
'config' => config('invoiceshelf'),
|
||||
'global_settings' => $global_settings,
|
||||
'main_menu' => $this->generateMenu('admin_menu', $current_user),
|
||||
'setting_menu' => [],
|
||||
'modules' => [],
|
||||
'admin_mode' => true,
|
||||
'pending_invitations' => CompanyInvitationResource::collection($pendingInvitations),
|
||||
]);
|
||||
}
|
||||
|
||||
// User has no companies — return minimal bootstrap
|
||||
if ($companies->isEmpty()) {
|
||||
return response()->json([
|
||||
'current_user' => new UserResource($current_user),
|
||||
'current_user_settings' => $current_user_settings,
|
||||
'current_user_abilities' => [],
|
||||
'companies' => [],
|
||||
'current_company' => null,
|
||||
'current_company_settings' => [],
|
||||
'current_company_currency' => Currency::first(),
|
||||
'config' => config('invoiceshelf'),
|
||||
'global_settings' => $global_settings,
|
||||
'main_menu' => [],
|
||||
'setting_menu' => [],
|
||||
'modules' => [],
|
||||
'pending_invitations' => CompanyInvitationResource::collection($pendingInvitations),
|
||||
]);
|
||||
}
|
||||
|
||||
$main_menu = $this->generateMenu('main_menu', $current_user);
|
||||
$setting_menu = $this->generateMenu('setting_menu', $current_user);
|
||||
|
||||
// Merge module-registered menu items into the main menu so they
|
||||
// participate in the unified group + priority ordering.
|
||||
foreach (ModuleRegistry::allMenu() as $slug => $item) {
|
||||
$main_menu[] = [
|
||||
'title' => __($item['title']),
|
||||
'link' => $item['link'],
|
||||
'icon' => $item['icon'],
|
||||
'name' => 'module-'.$slug,
|
||||
'group' => $item['group'] ?? 'modules',
|
||||
'group_label' => $item['group_label'] ?? 'navigation.modules',
|
||||
'priority' => $item['priority'] ?? 100,
|
||||
];
|
||||
}
|
||||
|
||||
$current_company = Company::find($request->header('company'));
|
||||
|
||||
if ((! $current_company) || ($current_company && ! $current_user->hasCompany($current_company->id))) {
|
||||
$current_company = $current_user->companies()->first();
|
||||
}
|
||||
|
||||
$current_company_settings = CompanySetting::getAllSettings($current_company->id);
|
||||
|
||||
$current_company_currency = $current_company_settings->has('currency')
|
||||
? Currency::find($current_company_settings->get('currency'))
|
||||
: Currency::first();
|
||||
|
||||
BouncerFacade::refreshFor($current_user);
|
||||
|
||||
return response()->json([
|
||||
'current_user' => new UserResource($current_user),
|
||||
'current_user_settings' => $current_user_settings,
|
||||
'current_user_abilities' => $current_user->getAbilities(),
|
||||
'companies' => CompanyResource::collection($companies),
|
||||
'current_company' => new CompanyResource($current_company),
|
||||
'current_company_settings' => $current_company_settings,
|
||||
'current_company_currency' => $current_company_currency,
|
||||
'config' => config('invoiceshelf'),
|
||||
'global_settings' => $global_settings,
|
||||
'main_menu' => $main_menu,
|
||||
'setting_menu' => $setting_menu,
|
||||
'modules' => Module::where('enabled', true)->pluck('name'),
|
||||
'user_menu' => collect(ModuleRegistry::allUserMenu())
|
||||
->map(fn (array $item, string $slug) => [
|
||||
...$item,
|
||||
'title' => __($item['title']),
|
||||
'name' => 'module-'.$slug,
|
||||
])
|
||||
->sortBy('priority')
|
||||
->values()
|
||||
->all(),
|
||||
'pending_invitations' => CompanyInvitationResource::collection($pendingInvitations),
|
||||
]);
|
||||
}
|
||||
|
||||
public function currentCompany(Request $request)
|
||||
{
|
||||
$company = Company::find($request->header('company'));
|
||||
|
||||
return new CompanyResource($company);
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Http\Company;
|
||||
|
||||
use App\Platform\Http\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use InvoiceShelf\Modules\Registry;
|
||||
|
||||
class ConfigController extends Controller
|
||||
{
|
||||
/**
|
||||
* Handle the incoming request.
|
||||
*/
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
if ($request->key === 'exchange_rate_drivers') {
|
||||
return response()->json([
|
||||
'exchange_rate_drivers' => $this->exchangeRateDrivers(),
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
$request->key => config('invoiceshelf.'.$request->key),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the exchange rate driver list from the module Registry.
|
||||
*
|
||||
* Returns enriched objects (with label, website, and config_fields) so the
|
||||
* frontend can render driver-specific configuration forms without hardcoding
|
||||
* any per-driver UI.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
protected function exchangeRateDrivers(): array
|
||||
{
|
||||
return collect(Registry::allDrivers('exchange_rate'))
|
||||
->map(fn (array $meta, string $name) => [
|
||||
'value' => $name,
|
||||
'label' => $meta['label'] ?? $name,
|
||||
'website' => $meta['website'] ?? '',
|
||||
'config_fields' => $meta['config_fields'] ?? [],
|
||||
])
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Http\Concerns;
|
||||
|
||||
trait GeneratesMenu
|
||||
{
|
||||
public function generateMenu($key, $user)
|
||||
{
|
||||
$new_items = [];
|
||||
|
||||
$menu = \Menu::get($key);
|
||||
$items = $menu ? $menu->items->toArray() : [];
|
||||
|
||||
foreach ($items as $data) {
|
||||
if ($user->checkAccess($data)) {
|
||||
$new_items[] = [
|
||||
'title' => $data->title,
|
||||
'link' => $data->link->path['url'],
|
||||
'icon' => $data->data['icon'],
|
||||
'name' => $data->data['name'],
|
||||
'group' => $data->data['group'],
|
||||
'group_label' => $data->data['group_label'] ?? '',
|
||||
'priority' => $data->data['priority'] ?? 100,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $new_items;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class CronJobMiddleware
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if ($request->header('x-authorization-token') && $request->header('x-authorization-token') == config('services.cron_job.auth_token')) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
return response()->json(['unauthorized'], 401);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class GetSettingRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'key' => [
|
||||
'required',
|
||||
'string',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class SettingRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'settings' => [
|
||||
'required',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Http\Webhooks;
|
||||
|
||||
use App\Platform\Http\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
class CronJobController extends Controller
|
||||
{
|
||||
/**
|
||||
* Handle the incoming request.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function __invoke(Request $request)
|
||||
{
|
||||
Artisan::call('schedule:run');
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
}
|
||||
@@ -1,314 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Installation\Application;
|
||||
|
||||
use App\Platform\Operations\Installation\Http\Requests\DatabaseEnvironmentRequest;
|
||||
use App\Platform\Operations\Installation\Http\Requests\DomainEnvironmentRequest;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class EnvironmentManager
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $envPath;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $delimiter = "\n";
|
||||
|
||||
/**
|
||||
* Set the .env and .env.example paths.
|
||||
*/
|
||||
public function __construct($path = null)
|
||||
{
|
||||
$this->envPath = base_path('.env');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the .env contents
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
private function getEnvContents()
|
||||
{
|
||||
return file_get_contents($this->envPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates .env file - inspired by Akaunting
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function updateEnv(array $data)
|
||||
{
|
||||
if (empty($data) || ! is_array($data) || ! is_file($this->envPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$env = $this->getEnvContents();
|
||||
|
||||
$env = explode($this->delimiter, $env);
|
||||
|
||||
foreach ($data as $data_key => $data_value) {
|
||||
$updated = false;
|
||||
|
||||
foreach ($env as $env_key => $env_value) {
|
||||
$entry = explode('=', $env_value, 2);
|
||||
|
||||
// Check if new or old key
|
||||
if ($entry[0] == $data_key) {
|
||||
$env[$env_key] = sprintf('%s=%s', $data_key, $this->encode($data_value));
|
||||
$updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Lets create if not available
|
||||
if (! $updated) {
|
||||
$env[] = $data_key.'='.$this->encode($data_value);
|
||||
}
|
||||
}
|
||||
|
||||
$env = implode($this->delimiter, $env);
|
||||
|
||||
file_put_contents(base_path('.env'), $env);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes value for .env
|
||||
*
|
||||
* @return mixed|string
|
||||
*/
|
||||
private function encode($str)
|
||||
{
|
||||
// Convert to string if not already
|
||||
$str = (string) $str;
|
||||
|
||||
// If the value is already properly quoted, return as is
|
||||
if (strlen($str) >= 2 && $str[0] === '"' && $str[strlen($str) - 1] === '"') {
|
||||
return $str;
|
||||
}
|
||||
|
||||
// Check if the value contains characters that need quoting
|
||||
// Using a character class regex to properly match special characters
|
||||
$specialChars = '\^\'£$%&*()}{@#~?><,|=\-_+¬!';
|
||||
$needsQuoting = (
|
||||
strpos($str, ' ') !== false ||
|
||||
preg_match('/['.preg_quote($specialChars, '/').']/', $str)
|
||||
);
|
||||
|
||||
if ($needsQuoting) {
|
||||
// Escape any existing double quotes in the string
|
||||
$str = str_replace('"', '\\"', $str);
|
||||
$str = '"'.$str.'"';
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the database content to the .env file.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function saveDatabaseVariables(DatabaseEnvironmentRequest $request)
|
||||
{
|
||||
$appUrl = $request->get('app_url');
|
||||
if ($appUrl !== config('app.url')) {
|
||||
config(['app.url' => $appUrl]);
|
||||
}
|
||||
[$sanctumDomain, $sessionDomain] = $this->getDomains(
|
||||
$request->getHttpHost()
|
||||
);
|
||||
$dbEnv = [
|
||||
'APP_URL' => $appUrl,
|
||||
'APP_LOCALE' => $request->get('app_locale'),
|
||||
'DB_CONNECTION' => $request->get('database_connection'),
|
||||
'SESSION_DOMAIN' => $sessionDomain,
|
||||
];
|
||||
if ($sanctumDomain !== null) {
|
||||
$dbEnv['SANCTUM_STATEFUL_DOMAINS'] = $sanctumDomain;
|
||||
}
|
||||
if ($dbEnv['DB_CONNECTION'] != 'sqlite') {
|
||||
if ($request->has('database_username') && $request->has('database_password')) {
|
||||
$dbEnv['DB_HOST'] = $request->get('database_hostname');
|
||||
$dbEnv['DB_PORT'] = $request->get('database_port');
|
||||
$dbEnv['DB_DATABASE'] = $request->get('database_name');
|
||||
$dbEnv['DB_USERNAME'] = $request->get('database_username');
|
||||
$dbEnv['DB_PASSWORD'] = $request->get('database_password');
|
||||
}
|
||||
} else {
|
||||
// Laravel 11 requires SQLite at least v3.35.0
|
||||
// https://laravel.com/docs/11.x/database#introduction
|
||||
if (extension_loaded('sqlite3') && class_exists('\SQLite3') && method_exists('\SQLite3', 'version')) {
|
||||
$version = \SQLite3::version();
|
||||
if (! empty($version['versionString']) && version_compare($version['versionString'], '3.35.0', '<')) {
|
||||
return [
|
||||
'error_message' => sprintf('The minimum SQLite version is %s. Your current SQLite version is %s which is not supported. Please upgrade SQLite and retry.', '3.35.0', $version['versionString']),
|
||||
];
|
||||
}
|
||||
} else {
|
||||
return [
|
||||
'error_message' => sprintf('SQLite3 is not present. Please install SQLite >=%s and retry.', '3.35.0'),
|
||||
];
|
||||
}
|
||||
$dbEnv['DB_DATABASE'] = $request->get('database_name');
|
||||
$sqlitePath = $this->resolveSqliteDatabasePath($dbEnv['DB_DATABASE']);
|
||||
// Create empty SQLite database if it doesn't exist. Ensure the
|
||||
// parent directory exists first so user-supplied absolute paths
|
||||
// (e.g. /var/data/foo.sqlite) work even when the directory hasn't
|
||||
// been pre-created.
|
||||
if (! file_exists($sqlitePath)) {
|
||||
$parentDir = dirname($sqlitePath);
|
||||
if (! is_dir($parentDir)) {
|
||||
mkdir($parentDir, 0755, true);
|
||||
}
|
||||
copy(database_path('stubs/sqlite.empty.db'), $sqlitePath);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$this->checkDatabaseConnection($request);
|
||||
if ($request->get('database_overwrite')) {
|
||||
Artisan::call('db:wipe --force');
|
||||
}
|
||||
if (\Schema::hasTable('users')) {
|
||||
return [
|
||||
'error' => 'database_should_be_empty',
|
||||
];
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'error_message' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
$this->updateEnv($dbEnv);
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'error' => 'database_variables_save_error',
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => 'database_variables_save_successfully',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns PDO object if all ok.
|
||||
*
|
||||
* @return \Closure|\PDO
|
||||
*/
|
||||
private function checkDatabaseConnection(DatabaseEnvironmentRequest $request)
|
||||
{
|
||||
$connection = $request->get('database_connection');
|
||||
|
||||
$settings = config("database.connections.$connection");
|
||||
|
||||
$connectionArray = array_merge($settings, [
|
||||
'driver' => $connection,
|
||||
'database' => $connection === 'sqlite'
|
||||
? $this->resolveSqliteDatabasePath($request->get('database_name'))
|
||||
: $request->get('database_name'),
|
||||
]);
|
||||
|
||||
if ($connection !== 'sqlite' && $request->has('database_username') && $request->has('database_password')) {
|
||||
$connectionArray = array_merge($connectionArray, [
|
||||
'username' => $request->get('database_username'),
|
||||
'password' => $request->get('database_password'),
|
||||
'host' => $request->get('database_hostname'),
|
||||
'port' => $request->get('database_port'),
|
||||
]);
|
||||
}
|
||||
|
||||
config([
|
||||
'database' => [
|
||||
'migrations' => 'migrations',
|
||||
'default' => $connection,
|
||||
'connections' => [$connection => $connectionArray],
|
||||
],
|
||||
]);
|
||||
|
||||
DB::purge($connection);
|
||||
|
||||
return DB::connection($connection)->getPdo();
|
||||
}
|
||||
|
||||
private function resolveSqliteDatabasePath(?string $databasePath): string
|
||||
{
|
||||
$databasePath = trim((string) $databasePath);
|
||||
|
||||
if ($databasePath === '') {
|
||||
return storage_path('app/database.sqlite');
|
||||
}
|
||||
|
||||
if ($this->isAbsolutePath($databasePath)) {
|
||||
return $databasePath;
|
||||
}
|
||||
|
||||
return base_path($databasePath);
|
||||
}
|
||||
|
||||
private function isAbsolutePath(string $path): bool
|
||||
{
|
||||
return str_starts_with($path, DIRECTORY_SEPARATOR)
|
||||
|| preg_match('/^[A-Za-z]:[\\\\\\/]/', $path) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save sanctum stateful domain to the .env file.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function saveDomainVariables(DomainEnvironmentRequest $request)
|
||||
{
|
||||
try {
|
||||
[$sanctumDomain, $sessionDomain] = $this->getDomains(
|
||||
$request->get('app_domain')
|
||||
);
|
||||
$domainEnv = [
|
||||
'SESSION_DOMAIN' => $sessionDomain,
|
||||
];
|
||||
if ($sanctumDomain !== null) {
|
||||
$domainEnv['SANCTUM_STATEFUL_DOMAINS'] = $sanctumDomain;
|
||||
}
|
||||
$this->updateEnv($domainEnv);
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'error' => 'domain_verification_failed',
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => 'domain_variable_save_successfully',
|
||||
];
|
||||
}
|
||||
|
||||
private function getDomains(string $requestDomain): array
|
||||
{
|
||||
$appUrl = config('app.url');
|
||||
|
||||
$port = parse_url($appUrl, PHP_URL_PORT);
|
||||
$currentDomain = parse_url($appUrl, PHP_URL_HOST).(
|
||||
$port ? ':'.$port : ''
|
||||
);
|
||||
|
||||
$requestHost = parse_url($requestDomain, PHP_URL_HOST) ?: $requestDomain;
|
||||
|
||||
$isSame = $currentDomain === $requestDomain;
|
||||
|
||||
return [
|
||||
$isSame && env('SANCTUM_STATEFUL_DOMAINS', false) === false ?
|
||||
null : $requestDomain,
|
||||
$isSame && env('SESSION_DOMAIN', false) === null ?
|
||||
null : $requestHost,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Installation\Application;
|
||||
|
||||
class FilePermissionChecker
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $results = [];
|
||||
|
||||
/**
|
||||
* Set the result array permissions and errors.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->results['permissions'] = [];
|
||||
|
||||
$this->results['errors'] = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for the folders permissions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function check(array $folders)
|
||||
{
|
||||
foreach ($folders as $folder => $permission) {
|
||||
if (! ($this->getPermission($folder) >= $permission)) {
|
||||
$this->addFileAndSetErrors($folder, $permission, false);
|
||||
} else {
|
||||
$this->addFile($folder, $permission, true);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a folder permission.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getPermission($folder)
|
||||
{
|
||||
return substr(sprintf('%o', fileperms(base_path($folder))), -4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the file to the list of results.
|
||||
*/
|
||||
private function addFile($folder, $permission, $isSet)
|
||||
{
|
||||
array_push($this->results['permissions'], [
|
||||
'folder' => $folder,
|
||||
'permission' => $permission,
|
||||
'isSet' => $isSet,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the file and set the errors.
|
||||
*/
|
||||
private function addFileAndSetErrors($folder, $permission, $isSet)
|
||||
{
|
||||
$this->addFile($folder, $permission, $isSet);
|
||||
|
||||
$this->results['errors'] = true;
|
||||
}
|
||||
}
|
||||
@@ -1,235 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Installation\Application;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use PDO;
|
||||
use SQLite3;
|
||||
|
||||
class RequirementsChecker
|
||||
{
|
||||
/**
|
||||
* Minimum PHP Version Supported (Override is in installer.php config file).
|
||||
*
|
||||
* @var _minPhpVersion
|
||||
*/
|
||||
private $_minPhpVersion = '7.0.0';
|
||||
|
||||
/**
|
||||
* Check for the server requirements.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function check(array $requirements)
|
||||
{
|
||||
$results = [];
|
||||
|
||||
foreach ($requirements as $type => $requirement) {
|
||||
switch ($type) {
|
||||
// check php requirements
|
||||
case 'php':
|
||||
foreach ($requirements[$type] as $requirement) {
|
||||
$results['requirements'][$type][$requirement] = true;
|
||||
|
||||
if (! extension_loaded($requirement)) {
|
||||
$results['requirements'][$type][$requirement] = false;
|
||||
|
||||
$results['errors'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
// check apache requirements
|
||||
case 'apache':
|
||||
foreach ($requirements[$type] as $requirement) {
|
||||
// if function doesn't exist we can't check apache modules
|
||||
if (function_exists('apache_get_modules')) {
|
||||
$results['requirements'][$type][$requirement] = true;
|
||||
|
||||
if (! in_array($requirement, apache_get_modules())) {
|
||||
$results['requirements'][$type][$requirement] = false;
|
||||
|
||||
$results['errors'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check PHP version requirement.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function checkPHPVersion(?string $minPhpVersion = null)
|
||||
{
|
||||
$minVersionPhp = $minPhpVersion;
|
||||
$currentPhpVersion = $this->getPhpVersionInfo();
|
||||
$supported = false;
|
||||
|
||||
if ($minPhpVersion == null) {
|
||||
$minVersionPhp = $this->getMinPhpVersion();
|
||||
}
|
||||
|
||||
if (version_compare($currentPhpVersion['version'], $minVersionPhp) >= 0) {
|
||||
$supported = true;
|
||||
}
|
||||
|
||||
$phpStatus = [
|
||||
'full' => $currentPhpVersion['full'],
|
||||
'current' => $currentPhpVersion['version'],
|
||||
'minimum' => $minVersionPhp,
|
||||
'supported' => $supported,
|
||||
];
|
||||
|
||||
return $phpStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current Php version information.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function getPhpVersionInfo()
|
||||
{
|
||||
$currentVersionFull = PHP_VERSION;
|
||||
preg_match("#^\d+(\.\d+)*#", $currentVersionFull, $filtered);
|
||||
$currentVersion = $filtered[0];
|
||||
|
||||
return [
|
||||
'full' => $currentVersionFull,
|
||||
'version' => $currentVersion,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get minimum PHP version ID.
|
||||
*
|
||||
* @return string _minPhpVersion
|
||||
*/
|
||||
protected function getMinPhpVersion()
|
||||
{
|
||||
return $this->_minPhpVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check PHP version requirement.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function checkMysqlVersion($conn)
|
||||
{
|
||||
$version_info = $conn->getAttribute(PDO::ATTR_SERVER_VERSION);
|
||||
|
||||
$isMariaDb = Str::contains($version_info, 'MariaDB');
|
||||
|
||||
$minVersionMysql = $isMariaDb ? config('invoiceshelf.min_mariadb_version') : config('invoiceshelf.min_mysql_version');
|
||||
|
||||
$currentMysqlVersion = $this->getMysqlVersionInfo($conn);
|
||||
|
||||
$supported = false;
|
||||
|
||||
if (version_compare($currentMysqlVersion, $minVersionMysql) >= 0) {
|
||||
$supported = true;
|
||||
}
|
||||
|
||||
$phpStatus = [
|
||||
'current' => $currentMysqlVersion,
|
||||
'minimum' => $minVersionMysql,
|
||||
'supported' => $supported,
|
||||
];
|
||||
|
||||
return $phpStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current Mysql version information.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function getMysqlVersionInfo($pdo)
|
||||
{
|
||||
$version = $pdo->query('select version()')->fetchColumn();
|
||||
|
||||
preg_match("/^[0-9\.]+/", $version, $match);
|
||||
|
||||
return $match[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check Sqlite version requirement.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function checkSqliteVersion(?string $minSqliteVersion = null)
|
||||
{
|
||||
$minVersionSqlite = $minSqliteVersion;
|
||||
$currentSqliteVersion = $this->getSqliteVersionInfo();
|
||||
$supported = false;
|
||||
|
||||
if (version_compare($currentSqliteVersion, $minVersionSqlite) >= 0) {
|
||||
$supported = true;
|
||||
}
|
||||
|
||||
$phpStatus = [
|
||||
'current' => $currentSqliteVersion,
|
||||
'minimum' => $minVersionSqlite,
|
||||
'supported' => $supported,
|
||||
];
|
||||
|
||||
return $phpStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current Sqlite version information.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function getSqliteVersionInfo()
|
||||
{
|
||||
$currentVersion = SQLite3::version();
|
||||
|
||||
return $currentVersion['versionString'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check Pgsql version requirement.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function checkPgsqlVersion($conn, ?string $minPgsqlVersion = null)
|
||||
{
|
||||
$minVersionPgsql = $minPgsqlVersion;
|
||||
$currentPgsqlVersion = $this->getPgsqlVersionInfo($conn);
|
||||
$supported = false;
|
||||
|
||||
if (version_compare($currentPgsqlVersion, $minVersionPgsql) >= 0) {
|
||||
$supported = true;
|
||||
}
|
||||
|
||||
$phpStatus = [
|
||||
'current' => $currentPgsqlVersion,
|
||||
'minimum' => $minVersionPgsql,
|
||||
'supported' => $supported,
|
||||
];
|
||||
|
||||
return $phpStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current Pgsql version information.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function getPgsqlVersionInfo($conn)
|
||||
{
|
||||
$currentVersion = pg_version($conn);
|
||||
|
||||
return $currentVersion['server'];
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Installation\Http\Controllers;
|
||||
|
||||
use App\Platform\Http\Controller;
|
||||
use App\Platform\Operations\Installation\Application\EnvironmentManager;
|
||||
use App\Platform\Operations\Installation\Http\Requests\DomainEnvironmentRequest;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
class AppDomainController extends Controller
|
||||
{
|
||||
public function __invoke(DomainEnvironmentRequest $request)
|
||||
{
|
||||
Artisan::call('optimize:clear');
|
||||
|
||||
$environmentManager = new EnvironmentManager;
|
||||
|
||||
$results = $environmentManager->saveDomainVariables($request);
|
||||
|
||||
if (in_array('error', $results)) {
|
||||
return response()->json($results);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
-108
@@ -1,108 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Installation\Http\Controllers;
|
||||
|
||||
use App\Platform\Http\Controller;
|
||||
use App\Platform\Operations\Installation\Application\EnvironmentManager;
|
||||
use App\Platform\Operations\Installation\Application\InstallationState;
|
||||
use App\Platform\Operations\Installation\Http\Requests\DatabaseEnvironmentRequest;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
class DatabaseConfigurationController extends Controller
|
||||
{
|
||||
/**
|
||||
* @var EnvironmentManager
|
||||
*/
|
||||
protected $EnvironmentManager;
|
||||
|
||||
public function __construct(EnvironmentManager $environmentManager)
|
||||
{
|
||||
$this->environmentManager = $environmentManager;
|
||||
}
|
||||
|
||||
public function saveDatabaseEnvironment(DatabaseEnvironmentRequest $request)
|
||||
{
|
||||
Artisan::call('config:clear');
|
||||
Artisan::call('cache:clear');
|
||||
|
||||
$results = $this->environmentManager->saveDatabaseVariables($request);
|
||||
|
||||
if (array_key_exists('success', $results)) {
|
||||
// Automatically regenerating the key is disabled to prevent complications in the wizard process.
|
||||
// This can cause issues with the CSRF token, resulting in "Token Mismatch" or "Invalid CSRF Token" errors.
|
||||
// It is recommended that the user manually generates the key before running the wizard to ensure application security and stability.
|
||||
// Artisan::call('key:generate --force');
|
||||
Artisan::call('optimize:clear');
|
||||
Artisan::call('config:clear');
|
||||
Artisan::call('cache:clear');
|
||||
Artisan::call('storage:link');
|
||||
Artisan::call('migrate --seed --force');
|
||||
// Set version.
|
||||
InstallationState::setCurrentVersion();
|
||||
}
|
||||
|
||||
return response()->json($results);
|
||||
}
|
||||
|
||||
public function getDatabaseEnvironment(Request $request)
|
||||
{
|
||||
$databaseData = [];
|
||||
$connection = $request->connection ?? config('database.default');
|
||||
|
||||
switch ($connection) {
|
||||
case 'sqlite':
|
||||
$databaseData = [
|
||||
'database_connection' => 'sqlite',
|
||||
'database_name' => config('database.connections.sqlite.database') ?: 'storage/app/database.sqlite',
|
||||
];
|
||||
|
||||
break;
|
||||
|
||||
case 'pgsql':
|
||||
$databaseData = [
|
||||
'database_connection' => 'pgsql',
|
||||
'database_host' => '127.0.0.1',
|
||||
'database_port' => 5432,
|
||||
];
|
||||
|
||||
break;
|
||||
|
||||
case 'mysql':
|
||||
$databaseData = [
|
||||
'database_connection' => 'mysql',
|
||||
'database_host' => '127.0.0.1',
|
||||
'database_port' => 3306,
|
||||
];
|
||||
|
||||
break;
|
||||
|
||||
case 'mariadb':
|
||||
$databaseData = [
|
||||
'database_connection' => 'mariadb',
|
||||
'database_host' => '127.0.0.1',
|
||||
'database_port' => 3306,
|
||||
];
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
// Never return an empty config: the wizard picks its form from
|
||||
// database_connection, so an unrecognised driver used to render
|
||||
// a blank step with no way forward. Echo it back with the
|
||||
// server defaults instead.
|
||||
$databaseData = [
|
||||
'database_connection' => $connection,
|
||||
'database_host' => '127.0.0.1',
|
||||
'database_port' => 3306,
|
||||
];
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'config' => $databaseData,
|
||||
'success' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Installation\Http\Controllers;
|
||||
|
||||
use App\Platform\Http\Controller;
|
||||
use App\Platform\Operations\Installation\Application\FilePermissionChecker;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class FilePermissionsController extends Controller
|
||||
{
|
||||
/**
|
||||
* @var PermissionsChecker
|
||||
*/
|
||||
protected $permissions;
|
||||
|
||||
/**
|
||||
* @param PermissionsChecker $checker
|
||||
*/
|
||||
public function __construct(FilePermissionChecker $checker)
|
||||
{
|
||||
$this->permissions = $checker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the permissions check page.
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function permissions()
|
||||
{
|
||||
$permissions = $this->permissions->check(
|
||||
config('installer.permissions')
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'permissions' => $permissions,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Installation\Http\Controllers;
|
||||
|
||||
use App\Platform\Http\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class FinishController extends Controller
|
||||
{
|
||||
/**
|
||||
* Handle the incoming request.
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function __invoke(Request $request)
|
||||
{
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Installation\Http\Controllers;
|
||||
|
||||
use App\Domains\Accounts\Models\User;
|
||||
use App\Platform\Http\Controller;
|
||||
use App\Platform\Operations\Installation\Authentication\InstallWizardAuth;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class LoginController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
$user = User::where('role', 'super admin')->first();
|
||||
if (! $user) {
|
||||
return response()->json([
|
||||
'message' => 'Super admin user not found.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$company = $user->companies()->first();
|
||||
if (! $company) {
|
||||
return response()->json([
|
||||
'message' => 'Super admin company not found.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
Auth::guard('web')->logout();
|
||||
if ($request->hasSession()) {
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
}
|
||||
|
||||
$user->tokens()->where('name', InstallWizardAuth::TOKEN_NAME)->delete();
|
||||
$token = $user->createToken(
|
||||
InstallWizardAuth::TOKEN_NAME,
|
||||
[InstallWizardAuth::TOKEN_ABILITY],
|
||||
)->plainTextToken;
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'type' => 'Bearer',
|
||||
'token' => $token,
|
||||
'user' => $user,
|
||||
'company' => $company,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Installation\Http\Controllers;
|
||||
|
||||
use App\Platform\Http\Controller;
|
||||
use App\Platform\Operations\Installation\Application\InstallationState;
|
||||
use App\Platform\Operations\Models\Setting;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class OnboardingWizardController extends Controller
|
||||
{
|
||||
/**
|
||||
* Handle the incoming request.
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function getStep(Request $request)
|
||||
{
|
||||
if (! InstallationState::isDbCreated()) {
|
||||
return response()->json([
|
||||
'profile_complete' => 0,
|
||||
'profile_language' => 'en',
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'profile_complete' => Setting::getSetting('profile_complete'),
|
||||
'profile_language' => Setting::getSetting('profile_language'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateStep(Request $request)
|
||||
{
|
||||
$setting = Setting::getSetting('profile_complete');
|
||||
|
||||
if ($setting === 'COMPLETED') {
|
||||
return response()->json([
|
||||
'profile_complete' => $setting,
|
||||
]);
|
||||
}
|
||||
|
||||
Setting::setSetting('profile_complete', $request->profile_complete);
|
||||
|
||||
return response()->json([
|
||||
'profile_complete' => Setting::getSetting('profile_complete'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function saveLanguage(Request $request)
|
||||
{
|
||||
Setting::setSetting('profile_language', $request->profile_language);
|
||||
|
||||
return response()->json([
|
||||
'profile_language' => Setting::getSetting('profile_language'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Installation\Http\Controllers;
|
||||
|
||||
use App\Platform\Http\Controller;
|
||||
use App\Platform\Operations\Installation\Application\RequirementsChecker;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class RequirementsController extends Controller
|
||||
{
|
||||
/**
|
||||
* @var RequirementsChecker
|
||||
*/
|
||||
protected $requirements;
|
||||
|
||||
public function __construct(RequirementsChecker $checker)
|
||||
{
|
||||
$this->requirements = $checker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the requirements page.
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function requirements()
|
||||
{
|
||||
$phpSupportInfo = $this->requirements->checkPHPVersion(
|
||||
config('installer.core.minPhpVersion')
|
||||
);
|
||||
|
||||
$requirements = $this->requirements->check(
|
||||
config('installer.requirements')
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'phpSupportInfo' => $phpSupportInfo,
|
||||
'requirements' => $requirements,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Installation\Http\Middleware;
|
||||
|
||||
use App\Platform\Operations\Installation\Application\InstallationState;
|
||||
use App\Platform\Operations\Models\Setting;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class EnsureInstalled
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
try {
|
||||
if (! InstallationState::isDbCreated() || Setting::getSetting('profile_complete') !== 'COMPLETED') {
|
||||
return redirect('/installation');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return redirect('/installation');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Installation\Http\Middleware;
|
||||
|
||||
use App\Platform\Operations\Installation\Application\InstallationState;
|
||||
use App\Platform\Operations\Models\Setting;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class RedirectIfInstalled
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (InstallationState::isDbCreated()) {
|
||||
try {
|
||||
if (Setting::getSetting('profile_complete') === 'COMPLETED') {
|
||||
return redirect('login');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Settings table may not exist yet during installation
|
||||
}
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Installation\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class DatabaseEnvironmentRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
switch ($this->get('database_connection')) {
|
||||
case 'sqlite':
|
||||
return [
|
||||
'app_url' => [
|
||||
'required',
|
||||
'url',
|
||||
],
|
||||
'database_connection' => [
|
||||
'required',
|
||||
'string',
|
||||
],
|
||||
'database_name' => [
|
||||
'required',
|
||||
'string',
|
||||
],
|
||||
'database_overwrite' => [
|
||||
'nullable',
|
||||
'boolean',
|
||||
],
|
||||
];
|
||||
|
||||
break;
|
||||
default:
|
||||
return [
|
||||
'app_url' => [
|
||||
'required',
|
||||
'url',
|
||||
],
|
||||
'database_connection' => [
|
||||
'required',
|
||||
'string',
|
||||
],
|
||||
'database_hostname' => [
|
||||
'required',
|
||||
'string',
|
||||
],
|
||||
'database_port' => [
|
||||
'required',
|
||||
'numeric',
|
||||
],
|
||||
'database_name' => [
|
||||
'required',
|
||||
'string',
|
||||
],
|
||||
'database_username' => [
|
||||
'required',
|
||||
'string',
|
||||
],
|
||||
'database_overwrite' => [
|
||||
'nullable',
|
||||
'boolean',
|
||||
],
|
||||
];
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Installation\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class DomainEnvironmentRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'app_domain' => [
|
||||
'required',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class Setting extends Model
|
||||
{
|
||||
protected $table = 'settings';
|
||||
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = ['option', 'value'];
|
||||
|
||||
/**
|
||||
* Create or update a single application setting by key.
|
||||
*/
|
||||
public static function setSetting(string $key, mixed $setting): void
|
||||
{
|
||||
$old = self::whereOption($key)->first();
|
||||
|
||||
if ($old) {
|
||||
$old->value = $setting;
|
||||
$old->save();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$set = new Setting;
|
||||
$set->option = $key;
|
||||
$set->value = $setting;
|
||||
$set->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk create or update application settings from a key-value array.
|
||||
*/
|
||||
public static function setSettings(array $settings): void
|
||||
{
|
||||
foreach ($settings as $key => $value) {
|
||||
self::updateOrCreate(
|
||||
[
|
||||
'option' => $key,
|
||||
],
|
||||
[
|
||||
'option' => $key,
|
||||
'value' => $value,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a single setting value by key, or null if not found.
|
||||
*/
|
||||
public static function getSetting(string $key): mixed
|
||||
{
|
||||
$setting = static::whereOption($key)->first();
|
||||
|
||||
if ($setting) {
|
||||
return $setting->value;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve multiple settings as a key-value collection.
|
||||
*/
|
||||
public static function getSettings(array $settings): Collection
|
||||
{
|
||||
return static::whereIn('option', $settings)
|
||||
->get()->mapWithKeys(function ($item) {
|
||||
return [$item['option'] => $item['value']];
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Update;
|
||||
|
||||
use App\Platform\Operations\Models\Setting;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
|
||||
// Implementation taken from Akaunting - https://github.com/akaunting/akaunting
|
||||
trait CallsReleaseServer
|
||||
{
|
||||
protected static function getRemote($url, $data = [], $token = null)
|
||||
{
|
||||
$client = new Client(['verify' => true, 'base_uri' => config('invoiceshelf.base_url').'/']);
|
||||
|
||||
$headers['headers'] = [
|
||||
'Accept' => 'application/json',
|
||||
'Referer' => url('/'),
|
||||
'Authorization' => "Bearer {$token}",
|
||||
'invoiceshelf' => Setting::getSetting('version'),
|
||||
];
|
||||
|
||||
$data['http_errors'] = false;
|
||||
|
||||
$data = array_merge($data, $headers);
|
||||
|
||||
try {
|
||||
$result = $client->get($url, $data);
|
||||
} catch (GuzzleException $e) {
|
||||
$result = null;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -1,225 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Platform\Operations\Update;
|
||||
|
||||
use App\Platform\Operations\Events\UpdateFinished;
|
||||
use App\Platform\Operations\Models\Setting;
|
||||
use Artisan;
|
||||
use File;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use ZipArchive;
|
||||
|
||||
// Implementation taken from Akaunting - https://github.com/akaunting/akaunting
|
||||
class Updater
|
||||
{
|
||||
use CallsReleaseServer;
|
||||
|
||||
public static function checkForUpdate($installed_version, $updater_channel = 'stable')
|
||||
{
|
||||
$data = null;
|
||||
$url = sprintf('releases/update-check/%s?channel=%s', $installed_version, $updater_channel);
|
||||
|
||||
$response = static::getRemote($url, ['timeout' => 100, 'track_redirects' => true]);
|
||||
|
||||
$data = (object) ['success' => false, 'release' => null];
|
||||
if ($response && ($response->getStatusCode() == 200)) {
|
||||
$data = $response->getBody()->getContents();
|
||||
$data = json_decode($data);
|
||||
}
|
||||
|
||||
if ($data->success && $data->release && property_exists($data->release, 'extensions')) {
|
||||
$extensions = [];
|
||||
foreach ($data->release->extensions as $extension) {
|
||||
$extensions[$extension] = phpversion($extension) !== false;
|
||||
}
|
||||
$extensions['php'.'('.$data->release->min_php_version.')'] = version_compare(phpversion(), $data->release->min_php_version, '>=');
|
||||
$data->release->extensions = $extensions;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public static function download($new_version, $is_cmd = 0)
|
||||
{
|
||||
$data = null;
|
||||
$path = null;
|
||||
|
||||
$url = 'releases/download/'.$new_version.'.zip';
|
||||
$response = static::getRemote($url, ['timeout' => 100, 'track_redirects' => true]);
|
||||
|
||||
// Exception
|
||||
if ($response instanceof RequestException) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'Download Exception',
|
||||
'data' => [
|
||||
'path' => $path,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if ($response && ($response->getStatusCode() == 200)) {
|
||||
$data = $response->getBody()->getContents();
|
||||
}
|
||||
|
||||
// Create temp directory
|
||||
$temp_dir = storage_path('app/temp-'.md5(mt_rand()));
|
||||
|
||||
if (! File::isDirectory($temp_dir)) {
|
||||
File::makeDirectory($temp_dir);
|
||||
}
|
||||
|
||||
$zip_file_path = $temp_dir.'/upload.zip';
|
||||
|
||||
// Add content to the Zip file
|
||||
$uploaded = is_int(file_put_contents($zip_file_path, $data)) ? true : false;
|
||||
|
||||
if (! $uploaded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $zip_file_path;
|
||||
}
|
||||
|
||||
public static function unzip($zip_file_path)
|
||||
{
|
||||
if (! file_exists($zip_file_path)) {
|
||||
throw new \Exception('Zip file not found');
|
||||
}
|
||||
|
||||
$temp_extract_dir = storage_path('app/temp2-'.md5(mt_rand()));
|
||||
|
||||
if (! File::isDirectory($temp_extract_dir)) {
|
||||
File::makeDirectory($temp_extract_dir);
|
||||
}
|
||||
// Unzip the file
|
||||
$zip = new ZipArchive;
|
||||
|
||||
if ($zip->open($zip_file_path)) {
|
||||
$zip->extractTo($temp_extract_dir);
|
||||
}
|
||||
|
||||
$zip->close();
|
||||
|
||||
// Delete zip file
|
||||
File::delete($zip_file_path);
|
||||
|
||||
return $temp_extract_dir;
|
||||
}
|
||||
|
||||
public static function copyFiles($temp_extract_dir)
|
||||
{
|
||||
if (! File::copyDirectory($temp_extract_dir.'/InvoiceShelf', base_path())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Delete temp directory
|
||||
File::deleteDirectory($temp_extract_dir);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function deleteFiles($json)
|
||||
{
|
||||
$files = json_decode($json);
|
||||
|
||||
foreach ($files as $file) {
|
||||
File::delete(base_path($file));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function cleanStaleFiles(): array
|
||||
{
|
||||
$manifestPath = base_path('manifest.json');
|
||||
|
||||
if (! File::exists($manifestPath)) {
|
||||
return ['success' => true, 'cleaned' => 0];
|
||||
}
|
||||
|
||||
$manifest = json_decode(File::get($manifestPath), true);
|
||||
|
||||
if (! is_array($manifest)) {
|
||||
return ['success' => false, 'error' => 'Invalid manifest'];
|
||||
}
|
||||
|
||||
$manifestLookup = array_flip($manifest);
|
||||
$protectedPaths = config('invoiceshelf.update_protected_paths', []);
|
||||
$cleaned = 0;
|
||||
|
||||
$iterator = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator(base_path(), \RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
\RecursiveIteratorIterator::CHILD_FIRST
|
||||
);
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
$relativePath = substr($file->getPathname(), strlen(base_path()) + 1);
|
||||
|
||||
if (static::isProtectedPath($relativePath, $protectedPaths)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($file->isFile() && ! isset($manifestLookup[$relativePath])) {
|
||||
File::delete($file->getPathname());
|
||||
$cleaned++;
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: remove empty directories
|
||||
$dirIterator = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator(base_path(), \RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
\RecursiveIteratorIterator::CHILD_FIRST
|
||||
);
|
||||
|
||||
foreach ($dirIterator as $item) {
|
||||
if (! $item->isDir()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$relativePath = substr($item->getPathname(), strlen(base_path()) + 1);
|
||||
|
||||
if (static::isProtectedPath($relativePath, $protectedPaths)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$entries = scandir($item->getPathname());
|
||||
|
||||
if (count($entries) <= 2) {
|
||||
@rmdir($item->getPathname());
|
||||
}
|
||||
}
|
||||
|
||||
return ['success' => true, 'cleaned' => $cleaned];
|
||||
}
|
||||
|
||||
private static function isProtectedPath(string $relativePath, array $protectedPaths): bool
|
||||
{
|
||||
foreach ($protectedPaths as $protected) {
|
||||
if ($relativePath === $protected || str_starts_with($relativePath, $protected.'/')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function migrateUpdate()
|
||||
{
|
||||
Artisan::call('migrate --force');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function finishUpdate($installed, $version)
|
||||
{
|
||||
Setting::setSetting('version', $version);
|
||||
event(new UpdateFinished($installed, $version));
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'error' => false,
|
||||
'data' => [],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Accounts\Models\CompanySetting;
|
||||
use App\Domains\Metadata\Models\CustomField;
|
||||
use App\Domains\Money\Models\Currency;
|
||||
use App\Platform\Operations\Installation\Application\InstallationState;
|
||||
use App\Platform\Operations\Models\Setting;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Get company setting
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function get_company_setting($key, $company_id)
|
||||
{
|
||||
if (! InstallationState::isDbCreated()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return CompanySetting::getSetting($key, $company_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get app setting
|
||||
*
|
||||
* @param $company_id
|
||||
* @return string
|
||||
*/
|
||||
function get_app_setting($key)
|
||||
{
|
||||
if (! InstallationState::isDbCreated()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Setting::getSetting($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get page title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function get_page_title($company_id)
|
||||
{
|
||||
if (! InstallationState::isDbCreated()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$routeName = Route::currentRouteName();
|
||||
|
||||
$defaultPageTitle = 'InvoiceShelf - Self Hosted Invoicing Platform';
|
||||
|
||||
if ($routeName === 'customer.dashboard') {
|
||||
$pageTitle = CompanySetting::getSetting('customer_portal_page_title', $company_id);
|
||||
|
||||
return $pageTitle ? $pageTitle : $defaultPageTitle;
|
||||
}
|
||||
|
||||
$pageTitle = Setting::getSetting('admin_page_title');
|
||||
|
||||
return $pageTitle ? $pageTitle : $defaultPageTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Active Path
|
||||
*
|
||||
* @param string $active
|
||||
* @return string
|
||||
*/
|
||||
function set_active($path, $active = 'active')
|
||||
{
|
||||
return call_user_func_array('Request::is', (array) $path) ? $active : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
function is_url($path)
|
||||
{
|
||||
return call_user_func_array('Request::is', (array) $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function getCustomFieldValueKey(string $type)
|
||||
{
|
||||
switch ($type) {
|
||||
case 'Input':
|
||||
return 'string_answer';
|
||||
|
||||
case 'TextArea':
|
||||
return 'string_answer';
|
||||
|
||||
case 'Phone':
|
||||
return 'number_answer';
|
||||
|
||||
case 'Url':
|
||||
return 'string_answer';
|
||||
|
||||
case 'Number':
|
||||
return 'number_answer';
|
||||
|
||||
case 'Dropdown':
|
||||
return 'string_answer';
|
||||
|
||||
case 'Switch':
|
||||
return 'boolean_answer';
|
||||
|
||||
case 'Date':
|
||||
return 'date_answer';
|
||||
|
||||
case 'Time':
|
||||
return 'time_answer';
|
||||
|
||||
case 'DateTime':
|
||||
return 'date_time_answer';
|
||||
|
||||
default:
|
||||
return 'string_answer';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an amount given in cents as currency markup for PDF templates.
|
||||
*
|
||||
* The magnitude is formatted first and a single minus sign is prefixed to the
|
||||
* whole assembled string, so a negative amount reads "-$24,738.00" rather than
|
||||
* "$-24,738.00" and the symbol stays glued to the digits in both symbol
|
||||
* positions. The symbol is wrapped in a DejaVu Sans span so it renders even
|
||||
* when the active font has no glyph for it.
|
||||
*
|
||||
* @param int|float|string|null $money Amount in cents.
|
||||
* @param Currency|null $currency Defaults to the company currency setting.
|
||||
* @return string
|
||||
*/
|
||||
function format_money_pdf($money, $currency = null)
|
||||
{
|
||||
$money = $money / 100;
|
||||
|
||||
if (! $currency) {
|
||||
$currency = Currency::findOrFail(CompanySetting::getSetting('currency', 1));
|
||||
}
|
||||
|
||||
$format_money = number_format(
|
||||
abs($money),
|
||||
$currency->precision,
|
||||
$currency->decimal_separator,
|
||||
$currency->thousand_separator
|
||||
);
|
||||
|
||||
$symbol = '<span style="font-family: DejaVu Sans;">'.$currency->symbol.'</span>';
|
||||
|
||||
$currency_with_symbol = $currency->swap_currency_symbol
|
||||
? $format_money.$symbol
|
||||
: $symbol.$format_money;
|
||||
|
||||
// The sign is decided on the formatted digits, not on the raw input, so an
|
||||
// amount that rounds away at the currency's precision (a stray cent on a
|
||||
// zero-precision currency) renders as zero instead of "-0".
|
||||
$is_negative = $money < 0 && preg_match('/[1-9]/', $format_money) === 1;
|
||||
|
||||
return $is_negative ? '-'.$currency_with_symbol : $currency_with_symbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $string
|
||||
* @return string
|
||||
*/
|
||||
function clean_slug($model, $title, $id = 0)
|
||||
{
|
||||
// Normalize the title
|
||||
$slug = Str::upper('CUSTOM_'.$model.'_'.Str::slug($title, '_'));
|
||||
|
||||
// Get any that could possibly be related.
|
||||
// This cuts the queries down by doing it once.
|
||||
$allSlugs = getRelatedSlugs($model, $slug, $id);
|
||||
|
||||
// If we haven't used it before then we are all good.
|
||||
if (! $allSlugs->contains('slug', $slug)) {
|
||||
return $slug;
|
||||
}
|
||||
|
||||
// Just append numbers like a savage until we find not used.
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
$newSlug = $slug.'_'.$i;
|
||||
if (! $allSlugs->contains('slug', $newSlug)) {
|
||||
return $newSlug;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception('Can not create a unique slug');
|
||||
}
|
||||
|
||||
function getRelatedSlugs($type, $slug, $id = 0)
|
||||
{
|
||||
return CustomField::select('slug')->where('slug', 'like', $slug.'%')
|
||||
->where('model_type', $type)
|
||||
->where('id', '<>', $id)
|
||||
->get();
|
||||
}
|
||||
|
||||
function respondJson($error, $message)
|
||||
{
|
||||
return response()->json([
|
||||
'error' => $error,
|
||||
'message' => $message,
|
||||
], 422);
|
||||
}
|
||||
Reference in New Issue
Block a user