[ 'tables' => ['ivr_details', 'ivr_entries'], 'files' => ['/var/lib/asterisk/sounds/'], 'file_filter' => 'custom' ], 'queues' => [ 'tables' => ['queues_config', 'queues_details'], 'files' => [] ], 'moh' => [ 'tables' => ['music'], 'files' => ['/var/lib/asterisk/moh/'], 'file_filter' => 'custom' ], 'recordings' => [ 'tables' => ['recordings'], 'files' => ['/var/lib/asterisk/sounds/'], 'file_filter' => 'custom' ], 'announcements' => [ 'tables' => ['announcement'], 'files' => [] ], 'inbound_routes' => [ 'tables' => ['incoming'], 'files' => [] ], 'time_conditions' => [ 'tables' => ['timeconditions'], 'files' => [] ], 'time_groups' => [ 'tables' => ['timegroups_details', 'timegroups_groups'], 'files' => [] ], 'outbound_routes' => [ 'tables' => ['outbound_routes'], 'files' => [] ], 'trunks' => [ 'tables' => ['trunks'], 'files' => [] ] ]; public function handle() { $zipFile = $this->option('file'); if (!$zipFile || !file_exists($zipFile)) { $this->error('Please specify a valid zip file path using --file option'); return 1; } $this->info('Starting PBX components import...'); $this->info("Importing from: {$zipFile}"); // Create temporary directory for extraction $tempDir = storage_path('app/pbx_import_temp_' . time()); if (!file_exists($tempDir)) { mkdir($tempDir, 0755, true); } try { // Extract zip file $this->extractZipFile($zipFile, $tempDir); // Read metadata $metadata = $this->readMetadata($tempDir); // Determine which components to import $componentsToImport = $this->option('components') ? explode(',', $this->option('components')) : $metadata['components']; $this->info('Components to import: ' . implode(', ', $componentsToImport)); // Validate components $validComponents = array_keys($this->components); $invalidComponents = array_diff($componentsToImport, $validComponents); if (!empty($invalidComponents)) { $this->error('Invalid components: ' . implode(', ', $invalidComponents)); $this->info('Valid components: ' . implode(', ', $validComponents)); return 1; } // Import components $bar = $this->output->createProgressBar(count($componentsToImport)); $bar->start(); foreach ($componentsToImport as $component) { $this->importComponent($component, $tempDir); $bar->advance(); } $bar->finish(); $this->newLine(); $this->info('Import completed successfully!'); } catch (\Exception $e) { $this->error('Import failed: ' . $e->getMessage()); return 1; } finally { // Cleanup $this->removeDirectory($tempDir); } return 0; } private function extractZipFile($zipFile, $tempDir) { $this->info('Extracting zip file...'); $zip = new ZipArchive(); if ($zip->open($zipFile) !== TRUE) { throw new \Exception("Cannot open zip file: {$zipFile}"); } $zip->extractTo($tempDir); $zip->close(); $this->info('Zip file extracted successfully'); } private function readMetadata($tempDir) { $metadataFile = $tempDir . '/metadata.json'; if (!file_exists($metadataFile)) { throw new \Exception('Metadata file not found in zip archive'); } $metadata = json_decode(file_get_contents($metadataFile), true); if (!$metadata) { throw new \Exception('Invalid metadata file'); } $this->info('Metadata loaded:'); $this->info(" Export date: {$metadata['export_date']}"); $this->info(" Source server: {$metadata['export_server']}"); $this->info(" FreePBX version: {$metadata['freepbx_version']}"); $this->info(" Components: " . count($metadata['components'])); $this->info(" Total records: {$metadata['total_records']}"); return $metadata; } private function importComponent($component, $tempDir) { $componentDir = $tempDir . '/' . $component; if (!is_dir($componentDir)) { $this->warn("Component directory not found: {$component}"); return; } $config = $this->components[$component]; // Import database tables foreach ($config['tables'] as $table) { $this->importTable($table, $componentDir); } // Import associated files foreach ($config['files'] as $filePath) { if (is_dir($tempDir . '/' . basename($filePath))) { $this->importDirectory($filePath, $tempDir, $config['file_filter'] ?? null); } } // Special handling for announcements if ($component === 'announcements') { $this->importAnnouncementRecordings($tempDir); } } private function importTable($table, $componentDir) { $jsonFile = $componentDir . '/' . $table . '.json'; if (!file_exists($jsonFile)) { $this->warn("Table file not found: {$table}.json"); return; } try { $data = json_decode(file_get_contents($jsonFile), true); if (empty($data)) { $this->warn("Table {$table} is empty, skipping..."); return; } // Clear existing data (optional - could be made configurable) DB::connection()->table('asterisk.' . $table)->truncate(); // Insert new data DB::connection()->table('asterisk.' . $table)->insert($data); $this->info(" Imported table: {$table} (" . count($data) . " records)"); } catch (\Exception $e) { $this->error(" Failed to import table {$table}: " . $e->getMessage()); } } private function importDirectory($targetDir, $tempDir, $filter = null) { $sourceDir = $tempDir . '/' . basename($targetDir); if (!is_dir($sourceDir)) { $this->warn("Source directory not found: {$sourceDir}"); return; } $this->copyDirectory($sourceDir, $targetDir, $filter); $this->info(" Imported directory: {$targetDir}"); } private function copyDirectory($source, $dest, $filter = null) { if (!is_dir($dest)) { mkdir($dest, 0755, true); } $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST ); foreach ($iterator as $item) { $relativePath = $iterator->getSubPathName(); // Apply filter if specified if ($filter && !$this->shouldIncludeFile($relativePath, $filter)) { continue; } $targetPath = $dest . '/' . $relativePath; if ($item->isDir()) { if (!is_dir($targetPath)) { mkdir($targetPath, 0755, true); } } else { copy($item->getPathname(), $targetPath); } } } private function importAnnouncementRecordings($tempDir) { $announcementRecordingsFile = $tempDir . '/announcements/announcement_recordings.json'; if (!file_exists($announcementRecordingsFile)) { return; // No announcement recordings to import } try { $recordings = json_decode(file_get_contents($announcementRecordingsFile), true); if (!empty($recordings)) { // Import recording metadata DB::connection()->table('asterisk.recordings')->insert($recordings); // Import associated audio files $this->importRecordingFiles($recordings, $tempDir); $this->info(" Imported announcement recordings: " . count($recordings) . " files"); } } catch (\Exception $e) { $this->error(" Failed to import announcement recordings: " . $e->getMessage()); } } private function importRecordingFiles($recordings, $tempDir) { $soundsPath = '/var/lib/asterisk/sounds/'; $filesPath = $tempDir . '/announcements/sounds'; if (!is_dir($filesPath)) { return; // No audio files to import } foreach ($recordings as $recording) { $filename = $recording['filename']; if (strpos($filename, 'custom/') === 0) { $sourceFile = $filesPath . '/' . $filename; $targetFile = $soundsPath . $filename; if (file_exists($sourceFile)) { $targetDir = dirname($targetFile); if (!is_dir($targetDir)) { mkdir($targetDir, 0755, true); } copy($sourceFile, $targetFile); } } } } private function shouldIncludeFile($relativePath, $filter) { if ($filter === 'custom') { return strpos($relativePath, 'custom/') === 0; } return true; } private function removeDirectory($dir) { if (!is_dir($dir)) { return; } $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::CHILD_FIRST ); foreach ($iterator as $file) { if ($file->isDir()) { rmdir($file->getPathname()); } else { unlink($file->getPathname()); } } rmdir($dir); } }