[ 'tables' => ['ivr_details', 'ivr_entries'], 'files' => ['/var/lib/asterisk/sounds/'], 'file_filter' => 'custom' // Only export custom directory ], 'queues' => [ 'tables' => ['queues_config', 'queues_details'], 'files' => [] ], 'moh' => [ 'tables' => ['music'], 'files' => ['/var/lib/asterisk/moh/'], 'file_filter' => 'custom' // Only export custom MOH categories ], 'recordings' => [ 'tables' => ['recordings'], 'files' => ['/var/lib/asterisk/sounds/'], 'file_filter' => 'custom' // Only export custom recordings ], 'announcements' => [ 'tables' => ['announcement'], 'files' => [], // Files are handled through recordings relationship 'dependencies' => ['recordings'] // Depends on recordings for audio 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' => [] ] ]; private $defaultSoundDirs = [ 'en', 'es', 'fr', 'de', 'it', 'ja', 'pt', 'ru', 'sv', 'zh', 'en_AU', 'en_GB', 'en_NZ', 'es_MX', 'fr_CA', 'pt_BR', 'digits', 'letters', 'phonetic', 'silence', 'priv-callerintros' ]; public function handle() { $componentsToExport = $this->option('components') ? explode(',', $this->option('components')) : array_keys($this->components); // Add dependencies $componentsToExport = $this->addDependencies($componentsToExport); $outputDir = $this->option('output') ?: storage_path('app'); $timestamp = date('Y_m_d_H_i_s'); $exportPath = storage_path('app/pbx_export_temp_' . $timestamp); $zipFile = $outputDir . '/pbx_export_' . $timestamp . '.zip'; // Validate components $validComponents = array_keys($this->components); $invalidComponents = array_diff($componentsToExport, $validComponents); if (!empty($invalidComponents)) { $this->error('Invalid components: ' . implode(', ', $invalidComponents)); $this->info('Valid components: ' . implode(', ', $validComponents)); return 1; } // Create export directory if (!file_exists($exportPath)) { mkdir($exportPath, 0755, true); } $this->info('Starting PBX components export...'); $this->info('Components to export: ' . implode(', ', $componentsToExport)); $this->info('Export directory: ' . $exportPath); $bar = $this->output->createProgressBar(count($componentsToExport)); $bar->start(); foreach ($componentsToExport as $component) { $this->exportComponent($component, $exportPath); $bar->advance(); } $bar->finish(); $this->newLine(); // Create metadata file $this->createMetadataFile($exportPath, $componentsToExport); // Create zip file $this->info('Creating zip file...'); $this->createZipFile($exportPath, $zipFile); // Cleanup $this->info('Cleaning up temporary files...'); $this->removeDirectory($exportPath); $this->info("Export completed successfully!"); $this->info("Zip file created: {$zipFile}"); $this->info("File size: " . $this->formatBytes(filesize($zipFile))); return 0; } private function addDependencies($components) { $allComponents = $components; foreach ($components as $component) { if (isset($this->components[$component]['dependencies'])) { foreach ($this->components[$component]['dependencies'] as $dependency) { if (!in_array($dependency, $allComponents)) { $allComponents[] = $dependency; $this->info("Added dependency: {$dependency} (required by {$component})"); } } } } return array_unique($allComponents); } private function exportComponent($component, $exportPath) { $config = $this->components[$component]; $componentPath = $exportPath . '/' . $component; if (!file_exists($componentPath)) { mkdir($componentPath, 0755, true); } // Export database tables foreach ($config['tables'] as $table) { $this->exportTable($table, $componentPath); } // Special handling for announcements - export related recordings if ($component === 'announcements') { $this->exportAnnouncementRecordings($componentPath); } // Export associated files foreach ($config['files'] as $filePath) { if (is_dir($filePath)) { $filter = $config['file_filter'] ?? null; $this->exportDirectory($filePath, $componentPath, $filter); } } } private function exportAnnouncementRecordings($exportPath) { try { // Get all recording IDs used by announcements $announcementRecordings = DB::connection() ->table('asterisk.announcement') ->whereNotNull('recording_id') ->pluck('recording_id') ->unique() ->toArray(); if (empty($announcementRecordings)) { $this->info(" No announcement recordings to export"); return; } // Export the recording metadata $recordings = DB::connection() ->table('asterisk.recordings') ->whereIn('id', $announcementRecordings) ->get(); if ($recordings->count() > 0) { $filename = $exportPath . '/announcement_recordings.json'; file_put_contents($filename, json_encode($recordings, JSON_PRETTY_PRINT)); $this->info(" Exported announcement recordings: {$recordings->count()} files"); // Export the actual audio files for these recordings $this->exportRecordingFiles($recordings, $exportPath); } } catch (\Exception $e) { $this->error(" Failed to export announcement recordings: " . $e->getMessage()); } } private function exportRecordingFiles($recordings, $exportPath) { $soundsPath = '/var/lib/asterisk/sounds/'; $filesPath = $exportPath . '/sounds'; if (!is_dir($filesPath)) { mkdir($filesPath, 0755, true); } $exportedFiles = 0; foreach ($recordings as $recording) { $filename = $recording->filename; // Check if it's a custom recording if (strpos($filename, 'custom/') === 0) { $sourceFile = $soundsPath . $filename; if (file_exists($sourceFile)) { $targetFile = $filesPath . '/' . $filename; $targetDir = dirname($targetFile); if (!is_dir($targetDir)) { mkdir($targetDir, 0755, true); } copy($sourceFile, $targetFile); $exportedFiles++; } } } if ($exportedFiles > 0) { $this->info(" Exported announcement audio files: {$exportedFiles} files"); } } private function exportTable($table, $exportPath) { try { $data = DB::connection()->table('asterisk.' . $table)->get(); if ($data->count() > 0) { $filename = $exportPath . '/' . $table . '.json'; file_put_contents($filename, json_encode($data, JSON_PRETTY_PRINT)); $this->info(" Exported table: {$table} ({$data->count()} records)"); } else { $this->warn(" Table {$table} is empty, skipping..."); } } catch (\Exception $e) { $this->error(" Failed to export table {$table}: " . $e->getMessage()); } } private function exportDirectory($sourceDir, $exportPath, $filter = null) { $targetDir = $exportPath . '/' . basename($sourceDir); if (!is_dir($targetDir)) { mkdir($targetDir, 0755, true); } $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($sourceDir, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY ); $fileCount = 0; foreach ($iterator as $file) { if ($file->isFile()) { $relativePath = $iterator->getSubPathName(); // Apply filter if specified if ($filter && !$this->shouldIncludeFile($relativePath, $filter)) { continue; } $targetFile = $targetDir . '/' . $relativePath; $targetFileDir = dirname($targetFile); if (!is_dir($targetFileDir)) { mkdir($targetFileDir, 0755, true); } copy($file->getPathname(), $targetFile); $fileCount++; } } if ($fileCount > 0) { $this->info(" Exported directory: {$sourceDir} ({$fileCount} files, filter: " . ($filter ?: 'none') . ")"); } else { $this->warn(" No files to export from: {$sourceDir} (filter: " . ($filter ?: 'none') . ")"); } } private function shouldIncludeFile($relativePath, $filter) { // For 'custom' filter, only include files in custom/ directory or */custom/ subdirectories if ($filter === 'custom') { // Include files directly in custom/ directory if (strpos($relativePath, 'custom/') === 0) { return true; } // Include files in language-specific custom directories (e.g., en/custom/, ar/custom/, etc.) if (preg_match('#^[^/]+/custom/#', $relativePath)) { return true; } return false; } // For other filters, implement as needed return true; } private function createMetadataFile($exportPath, $components) { $metadata = [ 'export_date' => date('Y-m-d H:i:s'), 'export_server' => gethostname(), 'freepbx_version' => $this->getFreepbxVersion(), 'asterisk_version' => $this->getAsteriskVersion(), 'components' => $components, 'tables_exported' => $this->getAllTables($components), 'total_records' => $this->getTotalRecords($components), 'php_version' => PHP_VERSION, 'laravel_version' => app()->version(), 'export_filters' => $this->getExportFilters($components) ]; file_put_contents( $exportPath . '/metadata.json', json_encode($metadata, JSON_PRETTY_PRINT) ); $this->info('Metadata file created'); } private function createZipFile($sourcePath, $zipFile) { $zip = new ZipArchive(); if ($zip->open($zipFile, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) { throw new \Exception("Cannot create zip file: {$zipFile}"); } $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($sourcePath, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY ); $fileCount = 0; foreach ($iterator as $file) { $zip->addFile($file->getPathname(), $iterator->getSubPathName()); $fileCount++; } $zip->close(); $this->info("Zip file created with {$fileCount} files"); } 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); } private function getFreepbxVersion() { try { $version = DB::connection()->table('asterisk.modules') ->where('rawname', 'framework') ->value('version'); return $version ?: 'Unknown'; } catch (\Exception $e) { return 'Unknown'; } } private function getAsteriskVersion() { try { $output = shell_exec('asterisk -V 2>/dev/null'); return trim($output) ?: 'Unknown'; } catch (\Exception $e) { return 'Unknown'; } } private function getAllTables($components) { $tables = []; foreach ($components as $component) { if (isset($this->components[$component]['tables'])) { $tables = array_merge($tables, $this->components[$component]['tables']); } } return array_unique($tables); } private function getTotalRecords($components) { $total = 0; $tables = $this->getAllTables($components); foreach ($tables as $table) { try { $count = DB::connection()->table('asterisk.' . $table)->count(); $total += $count; } catch (\Exception $e) { // Skip tables that can't be accessed } } return $total; } private function getExportFilters($components) { $filters = []; foreach ($components as $component) { if (isset($this->components[$component]['file_filter'])) { $filters[$component] = $this->components[$component]['file_filter']; } } return $filters; } private function formatBytes($bytes) { $units = ['B', 'KB', 'MB', 'GB']; $i = 0; while ($bytes >= 1024 && $i < count($units) - 1) { $bytes /= 1024; $i++; } return round($bytes, 2) . ' ' . $units[$i]; } }