extension; if (empty($extension)) { Log::error("UserAsteriskGroup: User {$userEntity->id} has no extension assigned"); return false; } // Get queue ID from group $queueId = $groupEntity->queue_id ?? $groupEntity->id; // Pre-operation: Check if user is on pause $pauseStatusBefore = $this->isUserOnPause($extension, $queueId); Log::debug("UserAsteriskGroup: Extension {$extension} pause status before adding to queue {$queueId}: " . ($pauseStatusBefore ? 'Paused' : 'Active')); // Add user to queue as FreePBX dynamic extension using Asterisk database // Command: asterisk -rx "database put QPENALTY {queue_id}/agents/{extension} 0" $command = sprintf('database put QPENALTY %s/agents/%s 0', $queueId, $extension); $result = $this->executeAsteriskCommand($command); if (!$result) { Log::error("UserAsteriskGroup: Failed to add extension {$extension} to queue {$queueId}"); return false; } Log::info("UserAsteriskGroup: Added extension {$extension} to queue {$queueId} as dynamic extension"); // Post-operation: Check pause status $pauseStatusAfter = $this->isUserOnPause($extension, $queueId); Log::debug("UserAsteriskGroup: Extension {$extension} pause status after adding to queue {$queueId}: " . ($pauseStatusAfter ? 'Paused' : 'Active')); return true; } catch (\Exception $e) { Log::error("UserAsteriskGroup: Error adding user to queue - " . $e->getMessage()); return false; } } /** * Remove a user from a queue (group) from FreePBX dynamic extensions * * This method removes a user from the specified queue by: * 1. Checking if user is paused before removal * 2. Using asterisk database del command to remove from QPENALTY family * 3. Verifying removal was successful * * @param int|User $user User ID or User instance * @param int|Group $group Group ID or Group instance * @return bool Success status */ public function RemoveUserFromQueue($user, $group) { try { // Normalize inputs $userEntity = $user instanceof User ? $user : User::find($user); $groupEntity = $group instanceof Group ? $group : Group::find($group); if (!$userEntity) { Log::error("UserAsteriskGroup: Invalid user"); return false; } if (!$groupEntity) { Log::error("UserAsteriskGroup: Invalid group"); return false; } // Get user's extension $extension = $userEntity->extension; if (empty($extension)) { Log::error("UserAsteriskGroup: User {$userEntity->id} has no extension assigned"); return false; } // Get queue ID from group $queueId = $groupEntity->queue_id ?? $groupEntity->id; // Pre-operation: Check if user is on pause $pauseStatusBefore = $this->isUserOnPause($extension, $queueId); Log::debug("UserAsteriskGroup: Extension {$extension} pause status before removing from queue {$queueId}: " . ($pauseStatusBefore ? 'Paused' : 'Active')); // Remove user from queue using Asterisk database del command // Command: asterisk -rx "database del QPENALTY {queue_id}/agents/{extension}" $command = sprintf('database del QPENALTY %s/agents/%s', $queueId, $extension); $result = $this->executeAsteriskCommand($command); if (!$result) { Log::error("UserAsteriskGroup: Failed to remove extension {$extension} from queue {$queueId}"); return false; } Log::info("UserAsteriskGroup: Removed extension {$extension} from queue {$queueId}"); // Post-operation: Verify removal by checking if still exists $verifyCommand = sprintf('database show QPENALTY/%s/agents/%s', $queueId, $extension); $verifyOutput = $this->executeAsteriskCommand($verifyCommand, true); // If the key still exists, the removal failed if (strpos($verifyOutput, "/QPENALTY/{$queueId}/agents/{$extension}") !== false) { Log::error("UserAsteriskGroup: Failed to verify removal of extension {$extension} from queue {$queueId}"); return false; } return true; } catch (\Exception $e) { Log::error("UserAsteriskGroup: Error removing user from queue - " . $e->getMessage()); return false; } } /** * Synchronize queue users with the given user list * * This method ensures the queue membership matches exactly the provided user list by: * 1. Getting current members from Asterisk QPENALTY database * 2. Identifying extensions to add and remove * 3. Performing batch operations using database commands * 4. Checking pause status for all affected users * * @param int|Group $group Group ID or Group instance * @param array $userIds Array of user IDs that should be in the queue * @return bool Success status */ public function SyncQueueUsers($group, array $userIds) { try { // Normalize group input $groupEntity = $group instanceof Group ? $group : Group::find($group); if (!$groupEntity) { Log::error("UserAsteriskGroup: Invalid group"); return false; } // Get queue ID from group $queueId = $groupEntity->queue_id ?? $groupEntity->id; // Get current members from Asterisk database $currentExtensions = $this->getCurrentQueueMembers($queueId); // Get extensions for the desired user list $desiredExtensions = []; // $extensionToUserMap = []; foreach ($userIds as $userId) { $user = $userId; $desiredExtensions[] = $userId; // $extensionToUserMap[$userId] = $userId; } // Determine which extensions to add and remove $extensionsToAdd = array_diff($desiredExtensions, $currentExtensions); $extensionsToRemove = array_diff($currentExtensions, $desiredExtensions); Log::info("UserAsteriskGroup: Syncing queue {$queueId} - Adding: " . count($extensionsToAdd) . " extensions, Removing: " . count($extensionsToRemove) . " extensions"); // Pre-operation: Check pause status for all affected extensions $affectedExtensions = array_merge($extensionsToAdd, $extensionsToRemove); foreach ($affectedExtensions as $extension) { $pauseStatus = $this->isUserOnPause($extension, $queueId); Log::debug("UserAsteriskGroup: Extension {$extension} pause status before sync: " . ($pauseStatus ? 'Paused' : 'Active')); } // Remove extensions that should not be in the queue foreach ($extensionsToRemove as $extension) { $command = sprintf('database del QPENALTY %s/agents/%s', $queueId, $extension); $result = $this->executeAsteriskCommand($command); if ($result) { Log::debug("UserAsteriskGroup: Removed extension {$extension} from queue {$queueId} during sync"); } else { Log::warning("UserAsteriskGroup: Failed to remove extension {$extension} from queue {$queueId} during sync"); } } // Add extensions that should be in the queue foreach ($extensionsToAdd as $extension) { $command = sprintf('database put QPENALTY %s/agents/%s 0', $queueId, $extension); $result = $this->executeAsteriskCommand($command); if ($result) { Log::debug("UserAsteriskGroup: Added extension {$extension} to queue {$queueId} during sync"); } else { Log::warning("UserAsteriskGroup: Failed to add extension {$extension} to queue {$queueId} during sync"); } } // Post-operation: Check pause status for all affected extensions foreach ($affectedExtensions as $extension) { $pauseStatus = $this->isUserOnPause($extension, $queueId); Log::debug("UserAsteriskGroup: Extension {$extension} pause status after sync: " . ($pauseStatus ? 'Paused' : 'Active')); } Log::info("UserAsteriskGroup: Successfully synced queue {$queueId}"); return true; } catch (\Exception $e) { Log::error("UserAsteriskGroup: Error syncing queue users - " . $e->getMessage()); return false; } } /** * Add a user to a queue in realtime using Asterisk CLI * * This method immediately adds a member to the queue without database persistence. * The member will be added to the running queue but will be lost on Asterisk reload. * Command: queue add member Local/{extension}@from-queue/n to {queue_number} * * @param string|int $extension User's extension number * @param string|int $queueNumber Queue number/ID * @return bool Success status */ public function RealtimeAddUserToQueue($extension, $queueNumber) { try { // Build the queue add member command // Format: queue add member Local/{extension}@from-queue/n to {queue_number} $memberInterface = sprintf('Local/%s@from-queue/n', $extension); $command = sprintf('queue add member %s to %s', $memberInterface, $queueNumber); // Execute the command $result = $this->executeAsteriskCommand($command); if (!$result) { Log::error("UserAsteriskGroup: Failed to add extension {$extension} to queue {$queueNumber} in realtime"); return false; } Log::info("UserAsteriskGroup: Successfully added extension {$extension} to queue {$queueNumber} in realtime"); return true; } catch (\Exception $e) { Log::error("UserAsteriskGroup: Error in realtime add user to queue - " . $e->getMessage()); return false; } } /** * Remove a user from a queue in realtime using Asterisk CLI * * This method immediately removes a member from the queue without database persistence. * The member will be removed from the running queue but may return on Asterisk reload. * Command: queue remove member Local/{extension}@from-queue/n from {queue_number} * * @param string|int $extension User's extension number * @param string|int $queueNumber Queue number/ID * @return bool Success status */ public function RealtimeRemoveUserFromQueue($extension, $queueNumber) { try { // Check if user is on pause before removal $isPaused = $this->isUserOnPause($extension, $queueNumber); if ($isPaused) { Log::info("UserAsteriskGroup: Extension {$extension} is on pause before removal from queue {$queueNumber}, unpausing first"); // Unpause the user globally before removing $this->UnpauseUser($extension); } // Build the queue remove member command // Format: queue remove member Local/{extension}@from-queue/n from {queue_number} $memberInterface = sprintf('Local/%s@from-queue/n', $extension); $command = sprintf('queue remove member %s from %s', $memberInterface, $queueNumber); // Execute the command $result = $this->executeAsteriskCommand($command); if (!$result) { Log::error("UserAsteriskGroup: Failed to remove extension {$extension} from queue {$queueNumber} in realtime"); return false; } Log::info("UserAsteriskGroup: Successfully removed extension {$extension} from queue {$queueNumber} in realtime"); return true; } catch (\Exception $e) { Log::error("UserAsteriskGroup: Error in realtime remove user from queue - " . $e->getMessage()); return false; } } /** * Pause a user globally across all queues * * This method pauses a user from all queues they are a member of using a single * QueuePause command without specifying a specific queue. * Command: queue pause member Local/{extension}@from-queue/n reason {reason} * * @param string|int $extension User's extension number * @param string $reason Optional reason for pause (default: 'Break') * @return bool Success status */ public function PauseUser($extension, $queueNumber, $reason = 'Break') { try { $process = new Process([ 'asterisk', '-x', "queue pause member Local/$extension@from-queue/n queue $queueNumber reason '$reason'" ]); $process->run(); // activity() // ->inLog($name) // ->performedOn($user)->log($command); // if($command == 'unpause'){ // $this->updateLoginEvent($user); // } else{ // $this->createLoginEvent($user); // } \Log::info("UserAsteriskGroup: Successfully paused extension {$extension} globally for queue {$queueNumber} with reason '{$reason}'"); } catch (\Exception $e) { Log::error("UserAsteriskGroup: Error pausing user globally - " . $e->getMessage()); return false; } } /** * Unpause a user globally across all queues * * This method unpauses a user from all queues they are a member of using a single * QueueUnpause command without specifying a specific queue. * Command: queue unpause member Local/{extension}@from-queue/n * * @param string|int $extension User's extension number * @return bool Success status */ public function UnpauseUser($extension) { try { // Build the global queue unpause command // Format: queue unpause member Local/{extension}@from-queue/n $memberInterface = sprintf('Local/%s@from-queue/n', $extension); $command = sprintf('queue unpause member %s', $memberInterface); // Execute the command $result = $this->executeAsteriskCommand($command); if (!$result) { Log::error("UserAsteriskGroup: Failed to unpause extension {$extension} globally"); return false; } Log::info("UserAsteriskGroup: Successfully unpaused extension {$extension} globally"); return true; } catch (\Exception $e) { Log::error("UserAsteriskGroup: Error unpausing user globally - " . $e->getMessage()); return false; } } /** * Check if a user is on pause in a specific queue * * This private method queries the Asterisk CLI to determine * if a user (by extension) is currently paused in the queue. * * @param string $extension User's extension number * @param string $queueId Queue ID * @return bool True if user is paused, false otherwise */ public function isUserOnPause($extension, $queueId) { try { // Check the MySQL activity_log table for a 'pause' event for this user // within the last 2 hours. The activity_log uses 'subject_type' and // 'subject_id' to identify the user (subject_type = Modules\\Platform\\User\\Entities\\User). // $cutoff = Carbon::now()->subHours(2)->format('Y-m-d H:i:s'); $exists = DB::table('activity_log') ->where('subject_type', 'Modules\\Platform\\User\\Entities\\User') ->where('subject_id', $extension) ->where('description', '!=', 'Unreachable') ->orderByDesc('id') ->first(); if ($exists && $exists->description === 'pause') { return $exists->log_name; Log::debug("UserAsteriskGroup: Found recent 'pause' activity for extension {$extension} (queue {$queueId})"); } else { return false; Log::debug("UserAsteriskGroup: No recent 'pause' activity for extension {$extension} (queue {$queueId})"); } } catch (\Exception $e) { Log::error("UserAsteriskGroup: Error checking pause status from DB - " . $e->getMessage()); return false; } } /** * Get current queue members from Asterisk database * * Queries the QPENALTY database family to get all agents for a queue * * @param string $queueId Queue ID * @return array Array of extension numbers */ private function getCurrentQueueMembers($queueId) { try { // Query Asterisk database for queue members // Command: asterisk -rx "database show QPENALTY/{queueId}/agents" $command = sprintf('database show QPENALTY/%s/agents', $queueId); $output = $this->executeAsteriskCommand($command, true); $members = []; if (empty($output)) { Log::debug("UserAsteriskGroup: No members found in queue {$queueId}"); return $members; } // Parse output to extract extensions // Expected format: /QPENALTY/{queueId}/agents/{extension} : 0 $lines = explode("\n", $output); foreach ($lines as $line) { // Match the pattern to extract extension number if (preg_match("/\/QPENALTY\/{$queueId}\/agents\/(\d+)/", $line, $matches)) { $members[] = $matches[1]; } } Log::debug("UserAsteriskGroup: Found " . count($members) . " members in queue {$queueId}"); return $members; } catch (\Exception $e) { Log::error("UserAsteriskGroup: Error getting queue members - " . $e->getMessage()); return []; } } /** * Execute an Asterisk CLI command * * @param string $command The Asterisk command to execute (without "asterisk -rx") * @param bool $returnOutput Whether to return the command output (default: false) * @return bool|string Success status or output string if $returnOutput is true */ private function executeAsteriskCommand($command, $returnOutput = false) { try { // Build the full command $fullCommand = sprintf('asterisk -rx "%s"', $command); \Log::debug($fullCommand); // Execute the command $output = []; $returnVar = 0; exec($fullCommand, $output, $returnVar); if ($returnOutput) { return implode("\n", $output); } // Return success status return $returnVar === 0; } catch (\Exception $e) { Log::error("UserAsteriskGroup: Error executing Asterisk command '{$command}' - " . $e->getMessage()); return $returnOutput ? '' : false; } } }