diff --git a/app/Platform/Operations/Console/ResetApp.php b/app/Platform/Operations/Console/ResetApp.php deleted file mode 100644 index bd587da6..00000000 --- a/app/Platform/Operations/Console/ResetApp.php +++ /dev/null @@ -1,89 +0,0 @@ -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!'); - } -} diff --git a/app/Platform/Operations/Console/UpdateCommand.php b/app/Platform/Operations/Console/UpdateCommand.php deleted file mode 100644 index 7ba2872d..00000000 --- a/app/Platform/Operations/Console/UpdateCommand.php +++ /dev/null @@ -1,247 +0,0 @@ -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; - } -} diff --git a/app/Platform/Operations/Events/UpdateFinished.php b/app/Platform/Operations/Events/UpdateFinished.php deleted file mode 100644 index a9d1c614..00000000 --- a/app/Platform/Operations/Events/UpdateFinished.php +++ /dev/null @@ -1,25 +0,0 @@ -old = $old; - $this->new = $new; - } -} diff --git a/app/Platform/Operations/Http/Admin/SettingsController.php b/app/Platform/Operations/Http/Admin/SettingsController.php deleted file mode 100644 index 854e47f2..00000000 --- a/app/Platform/Operations/Http/Admin/SettingsController.php +++ /dev/null @@ -1,35 +0,0 @@ -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, - ]); - } -} diff --git a/app/Platform/Operations/Http/Admin/UpdateController.php b/app/Platform/Operations/Http/Admin/UpdateController.php deleted file mode 100644 index cd67ac2f..00000000 --- a/app/Platform/Operations/Http/Admin/UpdateController.php +++ /dev/null @@ -1,116 +0,0 @@ -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'); - } -} diff --git a/app/Platform/Operations/Http/AppVersionController.php b/app/Platform/Operations/Http/AppVersionController.php deleted file mode 100644 index cab20a74..00000000 --- a/app/Platform/Operations/Http/AppVersionController.php +++ /dev/null @@ -1,34 +0,0 @@ -json([ - 'version' => $version, - 'channel' => $channel, - 'containerized' => (bool) config('invoiceshelf.containerized'), - ]); - } -} diff --git a/app/Platform/Operations/Http/Company/BootstrapController.php b/app/Platform/Operations/Http/Company/BootstrapController.php deleted file mode 100644 index 2ca63675..00000000 --- a/app/Platform/Operations/Http/Company/BootstrapController.php +++ /dev/null @@ -1,155 +0,0 @@ -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); - } -} diff --git a/app/Platform/Operations/Http/Company/ConfigController.php b/app/Platform/Operations/Http/Company/ConfigController.php deleted file mode 100644 index 02fe83b5..00000000 --- a/app/Platform/Operations/Http/Company/ConfigController.php +++ /dev/null @@ -1,49 +0,0 @@ -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> - */ - 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(); - } -} diff --git a/app/Platform/Operations/Http/Concerns/GeneratesMenu.php b/app/Platform/Operations/Http/Concerns/GeneratesMenu.php deleted file mode 100644 index ba106e73..00000000 --- a/app/Platform/Operations/Http/Concerns/GeneratesMenu.php +++ /dev/null @@ -1,30 +0,0 @@ -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; - } -} diff --git a/app/Platform/Operations/Http/Middleware/CronJobMiddleware.php b/app/Platform/Operations/Http/Middleware/CronJobMiddleware.php deleted file mode 100644 index 6ba0ffee..00000000 --- a/app/Platform/Operations/Http/Middleware/CronJobMiddleware.php +++ /dev/null @@ -1,24 +0,0 @@ -header('x-authorization-token') && $request->header('x-authorization-token') == config('services.cron_job.auth_token')) { - return $next($request); - } - - return response()->json(['unauthorized'], 401); - } -} diff --git a/app/Platform/Operations/Http/Requests/GetSettingRequest.php b/app/Platform/Operations/Http/Requests/GetSettingRequest.php deleted file mode 100644 index f157de35..00000000 --- a/app/Platform/Operations/Http/Requests/GetSettingRequest.php +++ /dev/null @@ -1,29 +0,0 @@ - [ - 'required', - 'string', - ], - ]; - } -} diff --git a/app/Platform/Operations/Http/Requests/SettingRequest.php b/app/Platform/Operations/Http/Requests/SettingRequest.php deleted file mode 100644 index 02cb268f..00000000 --- a/app/Platform/Operations/Http/Requests/SettingRequest.php +++ /dev/null @@ -1,28 +0,0 @@ - [ - 'required', - ], - ]; - } -} diff --git a/app/Platform/Operations/Http/Webhooks/CronJobController.php b/app/Platform/Operations/Http/Webhooks/CronJobController.php deleted file mode 100644 index 45ee2f4c..00000000 --- a/app/Platform/Operations/Http/Webhooks/CronJobController.php +++ /dev/null @@ -1,23 +0,0 @@ -json(['success' => true]); - } -} diff --git a/app/Platform/Operations/Installation/Application/EnvironmentManager.php b/app/Platform/Operations/Installation/Application/EnvironmentManager.php deleted file mode 100644 index 35dfbabd..00000000 --- a/app/Platform/Operations/Installation/Application/EnvironmentManager.php +++ /dev/null @@ -1,314 +0,0 @@ -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, - ]; - } -} diff --git a/app/Platform/Operations/Installation/Application/FilePermissionChecker.php b/app/Platform/Operations/Installation/Application/FilePermissionChecker.php deleted file mode 100644 index 68e5a444..00000000 --- a/app/Platform/Operations/Installation/Application/FilePermissionChecker.php +++ /dev/null @@ -1,73 +0,0 @@ -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; - } -} diff --git a/app/Platform/Operations/Installation/Application/RequirementsChecker.php b/app/Platform/Operations/Installation/Application/RequirementsChecker.php deleted file mode 100644 index b36f1a14..00000000 --- a/app/Platform/Operations/Installation/Application/RequirementsChecker.php +++ /dev/null @@ -1,235 +0,0 @@ - $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']; - } -} diff --git a/app/Platform/Operations/Installation/Http/Controllers/AppDomainController.php b/app/Platform/Operations/Installation/Http/Controllers/AppDomainController.php deleted file mode 100644 index 64847915..00000000 --- a/app/Platform/Operations/Installation/Http/Controllers/AppDomainController.php +++ /dev/null @@ -1,28 +0,0 @@ -saveDomainVariables($request); - - if (in_array('error', $results)) { - return response()->json($results); - } - - return response()->json([ - 'success' => true, - ]); - } -} diff --git a/app/Platform/Operations/Installation/Http/Controllers/DatabaseConfigurationController.php b/app/Platform/Operations/Installation/Http/Controllers/DatabaseConfigurationController.php deleted file mode 100644 index 57e75651..00000000 --- a/app/Platform/Operations/Installation/Http/Controllers/DatabaseConfigurationController.php +++ /dev/null @@ -1,108 +0,0 @@ -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, - ]); - } -} diff --git a/app/Platform/Operations/Installation/Http/Controllers/FilePermissionsController.php b/app/Platform/Operations/Installation/Http/Controllers/FilePermissionsController.php deleted file mode 100644 index 008458e6..00000000 --- a/app/Platform/Operations/Installation/Http/Controllers/FilePermissionsController.php +++ /dev/null @@ -1,39 +0,0 @@ -permissions = $checker; - } - - /** - * Display the permissions check page. - * - * @return JsonResponse - */ - public function permissions() - { - $permissions = $this->permissions->check( - config('installer.permissions') - ); - - return response()->json([ - 'permissions' => $permissions, - ]); - } -} diff --git a/app/Platform/Operations/Installation/Http/Controllers/FinishController.php b/app/Platform/Operations/Installation/Http/Controllers/FinishController.php deleted file mode 100644 index ae8e8612..00000000 --- a/app/Platform/Operations/Installation/Http/Controllers/FinishController.php +++ /dev/null @@ -1,20 +0,0 @@ -json(['success' => true]); - } -} diff --git a/app/Platform/Operations/Installation/Http/Controllers/LoginController.php b/app/Platform/Operations/Installation/Http/Controllers/LoginController.php deleted file mode 100644 index c0906a7e..00000000 --- a/app/Platform/Operations/Installation/Http/Controllers/LoginController.php +++ /dev/null @@ -1,50 +0,0 @@ -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, - ]); - } -} diff --git a/app/Platform/Operations/Installation/Http/Controllers/OnboardingWizardController.php b/app/Platform/Operations/Installation/Http/Controllers/OnboardingWizardController.php deleted file mode 100644 index 1182cf89..00000000 --- a/app/Platform/Operations/Installation/Http/Controllers/OnboardingWizardController.php +++ /dev/null @@ -1,58 +0,0 @@ -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'), - ]); - } -} diff --git a/app/Platform/Operations/Installation/Http/Controllers/RequirementsController.php b/app/Platform/Operations/Installation/Http/Controllers/RequirementsController.php deleted file mode 100644 index ba9ede34..00000000 --- a/app/Platform/Operations/Installation/Http/Controllers/RequirementsController.php +++ /dev/null @@ -1,41 +0,0 @@ -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, - ]); - } -} diff --git a/app/Platform/Operations/Installation/Http/Middleware/EnsureInstalled.php b/app/Platform/Operations/Installation/Http/Middleware/EnsureInstalled.php deleted file mode 100644 index b6747f32..00000000 --- a/app/Platform/Operations/Installation/Http/Middleware/EnsureInstalled.php +++ /dev/null @@ -1,30 +0,0 @@ -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; - - } - } -} diff --git a/app/Platform/Operations/Installation/Http/Requests/DomainEnvironmentRequest.php b/app/Platform/Operations/Installation/Http/Requests/DomainEnvironmentRequest.php deleted file mode 100644 index cc892b03..00000000 --- a/app/Platform/Operations/Installation/Http/Requests/DomainEnvironmentRequest.php +++ /dev/null @@ -1,28 +0,0 @@ - [ - 'required', - ], - ]; - } -} diff --git a/app/Platform/Operations/Models/Setting.php b/app/Platform/Operations/Models/Setting.php deleted file mode 100644 index 22377952..00000000 --- a/app/Platform/Operations/Models/Setting.php +++ /dev/null @@ -1,79 +0,0 @@ -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']]; - }); - } -} diff --git a/app/Platform/Operations/Update/CallsReleaseServer.php b/app/Platform/Operations/Update/CallsReleaseServer.php deleted file mode 100644 index 887bfb76..00000000 --- a/app/Platform/Operations/Update/CallsReleaseServer.php +++ /dev/null @@ -1,35 +0,0 @@ - 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; - } -} diff --git a/app/Platform/Operations/Update/Updater.php b/app/Platform/Operations/Update/Updater.php deleted file mode 100644 index 71082187..00000000 --- a/app/Platform/Operations/Update/Updater.php +++ /dev/null @@ -1,225 +0,0 @@ - 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' => [], - ]; - } -} diff --git a/app/Platform/Operations/helpers.php b/app/Platform/Operations/helpers.php deleted file mode 100644 index c3b4a107..00000000 --- a/app/Platform/Operations/helpers.php +++ /dev/null @@ -1,210 +0,0 @@ -precision, - $currency->decimal_separator, - $currency->thousand_separator - ); - - $symbol = ''.$currency->symbol.''; - - $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); -}