checkRequirements(); return Inertia::render('Installer/Requirements', compact('requirements')); } public function permissions() { $permissions = $this->checkPermissions(); return Inertia::render('Installer/Permissions', compact('permissions')); } public function environment() { $timezones = \DateTimeZone::listIdentifiers(); return Inertia::render('Installer/Environment', compact('timezones')); } public function environmentStore(Request $request) { try { $validator = Validator::make($request->all(), [ 'app_name' => 'required|string|max:255', 'app_url' => 'required|url', 'app_timezone' => 'required|string|timezone', 'db_connection' => 'required|string', 'db_host' => 'required_unless:db_connection,sqlite|string', 'db_port' => 'required_unless:db_connection,sqlite|numeric', 'db_database' => 'required|string', 'db_username' => 'required_unless:db_connection,sqlite|string', 'db_password' => 'nullable|string', ], [ 'app_name.required' => __('Application name is required.'), 'app_name.string' => __('Application name must be a valid string.'), 'app_name.max' => __('Application name must not exceed 255 characters.'), 'app_url.required' => __('Application URL is required.'), 'app_url.url' => __('Please enter a valid application URL.'), 'app_timezone.required' => __('Timezone is required.'), 'app_timezone.timezone' => __('Please select a valid timezone.'), 'db_connection.required' => __('Database connection type is required.'), 'db_connection.string' => __('Database connection must be a valid string.'), 'db_host.required_unless' => __('Database host is required for non-SQLite connections.'), 'db_host.string' => __('Database host must be a valid string.'), 'db_port.required_unless' => __('Database port is required for non-SQLite connections.'), 'db_port.numeric' => __('Database port must be a valid number.'), 'db_database.required' => __('Database name is required.'), 'db_database.string' => __('Database name must be a valid string.'), 'db_username.required_unless' => __('Database username is required for non-SQLite connections.'), 'db_username.string' => __('Database username must be a valid string.'), 'db_password.string' => __('Database password must be a valid string.'), ]); if ($validator->fails()) { return back()->withErrors($validator)->withInput(); } $data = $validator->validated(); // Test database connection before saving if ($data['db_connection'] !== 'sqlite') { $this->testDatabaseConnection($data); } $this->createEnvFile($data); return redirect('/install/database'); } catch (\Exception $e) { return back()->withErrors(['database' => $e->getMessage()])->withInput(); } } public function database() { return Inertia::render('Installer/Database'); } public function databaseStore() { try { // Test database connection first DB::connection()->getPdo(); // Handle foreign key constraints based on database type $dbType = config('database.default'); if ($dbType === 'mysql') { DB::statement('SET FOREIGN_KEY_CHECKS=0'); } // Drop all tables if they exist Artisan::call('migrate:fresh', ['--force' => true]); if ($dbType === 'mysql') { DB::statement('SET FOREIGN_KEY_CHECKS=1'); } Artisan::call('db:seed', ['--force' => true]); return redirect('/install/addons'); } catch (\Exception $e) { return back()->withErrors(['database' => 'Database connection failed. Please check your database credentials.']); } } public function addons() { $modules = $this->getAllAvailableModules(); return Inertia::render('Installer/Addons', compact('modules')); } public function addonsStore(Request $request) { try { $moduleName = $request->input('module'); if ($moduleName) { $this->enableModule($moduleName); $nextModule = $this->getNextModule($moduleName); if ($nextModule) { return response()->json([ 'success' => true, 'next_module' => $nextModule, 'message' => "Module {$moduleName} installed successfully" ]); } else { return response()->json([ 'success' => true, 'completed' => true, 'message' => 'All modules installed successfully' ]); } } return response()->json(['success' => false, 'message' => 'No module specified']); } catch (\Exception $e) { return response()->json(['success' => false, 'message' => $e->getMessage()]); } } public function final() { $this->createInstalledFile(); $credentials = [ 'admin' => [ 'email' => 'superadmin@example.com', 'password' => '1234' ], 'company' => [ 'email' => 'company@example.com', 'password' => '1234' ] ]; return Inertia::render('Installer/Final', compact('credentials')); } private function checkRequirements() { return [ 'php' => [ 'name' => 'PHP Version (>= 8.3)', 'check' => version_compare(PHP_VERSION, '8.3.0', '>='), 'current' => PHP_VERSION ], 'extensions' => [ 'openssl' => [ 'name' => 'OpenSSL Extension', 'check' => extension_loaded('openssl') ], 'pdo' => [ 'name' => 'PDO Extension', 'check' => extension_loaded('pdo') ], 'mbstring' => [ 'name' => 'Mbstring Extension', 'check' => extension_loaded('mbstring') ], 'tokenizer' => [ 'name' => 'Tokenizer Extension', 'check' => extension_loaded('tokenizer') ], 'xml' => [ 'name' => 'XML Extension', 'check' => extension_loaded('xml') ], 'ctype' => [ 'name' => 'Ctype Extension', 'check' => extension_loaded('ctype') ], 'json' => [ 'name' => 'JSON Extension', 'check' => extension_loaded('json') ], 'curl' => [ 'name' => 'cURL Extension', 'check' => extension_loaded('curl') ], 'zip' => [ 'name' => 'Zip Extension', 'check' => extension_loaded('zip') ] ] ]; } private function checkPermissions() { $paths = [ 'storage/app' => storage_path('app'), 'storage/framework' => storage_path('framework'), 'storage/logs' => storage_path('logs'), 'bootstrap/cache' => base_path('bootstrap/cache'), ]; $permissions = []; foreach ($paths as $name => $path) { $permissions[$name] = [ 'name' => $name, 'path' => $path, 'check' => is_writable($path) ]; } return $permissions; } private function createEnvFile($data) { $envContent = "APP_NAME=\"{$data['app_name']}\"\n"; $envContent .= "APP_ENV=production\n"; $envContent .= "APP_KEY=" . config('app.key') . "\n"; $envContent .= "APP_DEBUG=false\n"; $envContent .= "APP_TIMEZONE={$data['app_timezone']}\n"; $envContent .= "APP_URL={$data['app_url']}\n\n"; $envContent .= "APP_LOCALE=en\n"; $envContent .= "APP_FALLBACK_LOCALE=en\n"; $envContent .= "APP_FAKER_LOCALE=en_US\n\n"; $envContent .= "LOG_CHANNEL=stack\n"; $envContent .= "LOG_LEVEL=error\n\n"; $envContent .= "DB_CONNECTION={$data['db_connection']}\n"; if ($data['db_connection'] !== 'sqlite') { $envContent .= "DB_HOST={$data['db_host']}\n"; $envContent .= "DB_PORT={$data['db_port']}\n"; $envContent .= "DB_USERNAME={$data['db_username']}\n"; $envContent .= "DB_PASSWORD={$data['db_password']}\n"; } $envContent .= "DB_DATABASE={$data['db_database']}\n\n"; $envContent .= "SESSION_DRIVER=file\n"; $envContent .= "SESSION_LIFETIME=120\n"; $envContent .= "CACHE_DRIVER=file\n"; $envContent .= "CACHE_STORE=file\n"; $envContent .= "QUEUE_CONNECTION=database\n\n"; $envContent .= "MAIL_MAILER=log\n"; $envContent .= "MAIL_FROM_ADDRESS=\"noreply@example.com\"\n"; $envContent .= "MAIL_FROM_NAME=\"\${APP_NAME}\"\n\n"; $envContent .= "VITE_APP_NAME=\"\${APP_NAME}\"\n"; File::put(base_path('.env'), $envContent); // Clear config cache and reload environment try { Artisan::call('config:clear'); Artisan::call('route:clear'); } catch (\Exception $e) { // Ignore cache clear errors during installation } } private function testDatabaseConnection($data) { try { $pdo = new \PDO( $data['db_connection'] . ':host=' . $data['db_host'] . ';port=' . $data['db_port'] . ';dbname=' . $data['db_database'], $data['db_username'], $data['db_password'], [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION] ); $pdo = null; } catch (\PDOException $e) { throw new \Exception('Database connection failed: ' . $e->getMessage()); } } private function getAllAvailableModules() { $modules = []; $packagesPath = base_path('packages/workdo'); if (!File::exists($packagesPath)) { return $modules; } $directories = File::directories($packagesPath); foreach ($directories as $directory) { $moduleName = basename($directory); $moduleJsonPath = "{$directory}/module.json"; if (File::exists($moduleJsonPath)) { $moduleData = json_decode(File::get($moduleJsonPath), true); if ($moduleData) { $modules[] = [ 'name' => $moduleData['name'], 'alias' => $moduleData['alias'], 'description' => $moduleData['description'] ?? '', 'priority' => $moduleData['priority'] ?? 10, ]; } } } usort($modules, function ($a, $b) { return $a['priority'] - $b['priority']; }); return $modules; } private function getNextModule($currentModule) { $modules = $this->getAllAvailableModules(); $currentIndex = array_search($currentModule, array_column($modules, 'name')); if ($currentIndex !== false && isset($modules[$currentIndex + 1])) { return $modules[$currentIndex + 1]; } return null; } private function enableModule($moduleName) { // Validate module name to prevent path traversal if (!preg_match('/^[a-zA-Z0-9_-]+$/', $moduleName)) { throw new \Exception('Invalid module name'); } $addon = AddOn::where('module', $moduleName)->first(); if (empty($addon)) { $filePath = base_path('packages/workdo/' . $moduleName . '/module.json'); if (!file_exists($filePath)) { throw new \Exception('Module configuration not found'); } $jsonContent = file_get_contents($filePath); $data = json_decode($jsonContent, true); if (!$data) { throw new \Exception('Invalid module configuration'); } Artisan::call('migrate --path=/packages/workdo/' . $moduleName . '/src/Database/Migrations --force'); Artisan::call('package:seed ' . $moduleName); $addon = new AddOn; $addon->module = $data['name']; $addon->name = $data['alias']; $addon->monthly_price = $data['monthly_price'] ?? 0; $addon->yearly_price = $data['yearly_price'] ?? 0; $addon->package_name = $data['package_name'] ?? null; $addon->for_admin = $data['for_admin'] ?? false; $addon->priority = $data['priority'] ?? 0; $addon->is_enable = 1; $addon->save(); } else { Artisan::call('migrate --path=/packages/workdo/' . $moduleName . '/src/Database/Migrations --force'); Artisan::call('package:seed ' . $moduleName); $addon->is_enable = 1; $addon->save(); } } private function createInstalledFile() { File::put(storage_path('installed'), 'install ' . date('Y-m-d H:i:s')); } }