Created
July 24, 2025 21:16
-
-
Save dexit/33f0b7ee0b0b7a13b41a5d559cf5ec3a to your computer and use it in GitHub Desktop.
process_opportunity.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| // process_opportunity.php (or your chosen script name for this specific workflow) | |
| ini_set('display_errors', 1); | |
| ini_set('display_startup_errors', 1); | |
| error_reporting(E_ALL); // Use E_ALL for comprehensive error reporting | |
| // Adjust paths as necessary based on your project structure | |
| require_once __DIR__ . '/../vendor/autoload.php'; | |
| require_once './ApiClient/BaseApiClient.php'; | |
| require_once './ApiClient/DynamicsApiClient.php'; | |
| require_once './ApiClient/NfceApiClient.php'; // Assuming this is the NCFE client | |
| require_once './Handlers/LoggingHandler.php'; | |
| require_once './Handlers/DatabaseHandler.php'; | |
| require_once './Handlers/WorkflowLogger.php'; // NEW: Include the WorkflowLogger | |
| use Dotenv\Dotenv; | |
| use Handlers\LoggingHandler; | |
| use Handlers\DatabaseHandler; | |
| use Handlers\WorkflowLogger; // Use the WorkflowLogger class | |
| use ApiClient\DynamicsApiClient; | |
| use ApiClient\NfceApiClient; | |
| use Exception; // Ensure Exception class is imported | |
| // Set content type for JSON response | |
| header('Content-Type: application/json'); | |
| // --- Helper Functions for Request Processing and Filtering --- | |
| /** | |
| * Extracts all HTTP headers from the $_SERVER superglobal. | |
| * | |
| * @return array | |
| */ | |
| function get_all_request_headers(): array | |
| { | |
| $headers = []; | |
| foreach ($_SERVER as $name => $value) { | |
| if (str_starts_with($name, 'HTTP_')) { | |
| $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; | |
| } | |
| } | |
| return $headers; | |
| } | |
| /** | |
| * Reads the raw request body and attempts to decode it as JSON. | |
| * | |
| * @return array|null | |
| */ | |
| function get_request_body_json(): ?array | |
| { | |
| $rawBody = file_get_contents('php://input'); | |
| if (empty($rawBody)) { | |
| return null; | |
| } | |
| $jsonBody = json_decode($rawBody, true); | |
| return json_last_error() === JSON_ERROR_NONE ? $jsonBody : null; | |
| } | |
| /** | |
| * Evaluates a JSONPath-like string against a data array/object. | |
| * Supports dot notation and [*] for array iteration. | |
| * Returns an array of all matching values. | |
| * | |
| * @param array|object $data The data to search within. | |
| * @param string $path The JSONPath-like string (e.g., "user.address.street", "items[*].id"). | |
| * @return array An array of all values found at the specified path. | |
| */ | |
| function evaluate_path($data, string $path): array | |
| { | |
| if (!is_array($data) && !is_object($data)) { | |
| return []; | |
| } | |
| $segments = explode('.', $path); | |
| $currentValues = [$data]; | |
| $results = []; | |
| foreach ($segments as $segment) { | |
| $nextValues = []; | |
| foreach ($currentValues as $currentValue) { | |
| if (!is_array($currentValue) && !is_object($currentValue)) { | |
| continue; | |
| } | |
| if ($segment === '[*]') { | |
| if (is_array($currentValue)) { | |
| foreach ($currentValue as $item) { | |
| $nextValues[] = $item; | |
| } | |
| } | |
| } elseif (is_array($currentValue) && isset($currentValue[$segment])) { | |
| $nextValues[] = $currentValue[$segment]; | |
| } elseif (is_object($currentValue) && isset($currentValue->$segment)) { | |
| $nextValues[] = $currentValue->$segment; | |
| } | |
| } | |
| $currentValues = $nextValues; | |
| } | |
| // Filter out non-scalar values if the path leads to a final value | |
| foreach ($currentValues as $value) { | |
| if (is_scalar($value) || is_null($value)) { | |
| $results[] = $value; | |
| } | |
| } | |
| return $results; | |
| } | |
| /** | |
| * Performs wildcard matching using fnmatch. | |
| * | |
| * @param string $pattern The pattern with wildcards (*, ?). | |
| * @param string $subject The string to match against. | |
| * @return bool | |
| */ | |
| function match_wildcard(string $pattern, string $subject): bool | |
| { | |
| return fnmatch($pattern, $subject); | |
| } | |
| /** | |
| * Evaluates a single condition based on operator and target value. | |
| * | |
| * @param mixed $value The actual value from the request data. | |
| * @param string $operator The comparison operator (e.g., 'equals', 'contains', 'starts_with', 'ends_with', 'matches_wildcard'). | |
| * @param mixed $targetValue The value to compare against. | |
| * @return bool | |
| */ | |
| function evaluate_condition($value, string $operator, $targetValue): bool | |
| { | |
| // Ensure values are strings for string operations | |
| $value = (string)$value; | |
| $targetValue = (string)$targetValue; | |
| switch ($operator) { | |
| case 'equals': | |
| return $value === $targetValue; | |
| case 'contains': | |
| return str_contains($value, $targetValue); | |
| case 'starts_with': | |
| return str_starts_with($value, $targetValue); | |
| case 'ends_with': | |
| return str_ends_with($value, $targetValue); | |
| case 'matches_wildcard': | |
| return match_wildcard($targetValue, $value); | |
| default: | |
| return false; // Unknown operator | |
| } | |
| } | |
| /** | |
| * Evaluates a set of conditions against request data. | |
| * | |
| * @param array $requestData Contains 'query_params', 'headers', 'body'. | |
| * @param array $rulesConfig The configuration array for filtering rules. | |
| * Example: | |
| * [ | |
| * "conditions" => [ | |
| * ["source" => "query_param", "key" => "action", "operator" => "equals", "value" => "processUser"], | |
| * ["source" => "header", "key" => "User-Agent", "operator" => "matches_wildcard", "value" => "Mozilla*"], | |
| * ["source" => "body", "path" => "user.status", "operator" => "equals", "value" => "active"] | |
| * ], | |
| * "logic" => "AND" // or "OR" | |
| * ] | |
| * @return bool True if all (AND) or any (OR) conditions are met. | |
| */ | |
| function evaluate_request_conditions(array $requestData, array $rulesConfig): bool | |
| { | |
| $conditions = $rulesConfig['conditions'] ?? []; | |
| $logic = strtoupper($rulesConfig['logic'] ?? 'AND'); // Default to AND | |
| if (empty($conditions)) { | |
| return true; // No conditions means always pass | |
| } | |
| $overallResult = ($logic === 'AND'); // Start with true for AND, false for OR | |
| foreach ($conditions as $condition) { | |
| $source = $condition['source'] ?? null; | |
| $key = $condition['key'] ?? null; // For query_param, header | |
| $path = $condition['path'] ?? null; // For body | |
| $operator = $condition['operator'] ?? null; | |
| $targetValue = $condition['value'] ?? null; | |
| $valuesToEvaluate = []; | |
| switch ($source) { | |
| case 'query_param': | |
| if (isset($requestData['query_params'][$key])) { | |
| $valuesToEvaluate[] = $requestData['query_params'][$key]; | |
| } | |
| break; | |
| case 'header': | |
| // Headers are case-insensitive, but $_SERVER keys are normalized. | |
| // Use the normalized key from get_all_request_headers() | |
| $normalizedKey = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key)))); | |
| if (isset($requestData['headers'][$normalizedKey])) { | |
| $valuesToEvaluate[] = $requestData['headers'][$normalizedKey]; | |
| } | |
| break; | |
| case 'body': | |
| if ($requestData['body_json'] && ($path !== null)) { // Use body_json here | |
| $valuesToEvaluate = evaluate_path($requestData['body_json'], $path); | |
| } | |
| break; | |
| default: | |
| // Invalid source, this condition fails | |
| $conditionMet = false; | |
| break; | |
| } | |
| $conditionMet = false; | |
| foreach ($valuesToEvaluate as $value) { | |
| if (evaluate_condition($value, $operator, $targetValue)) { | |
| $conditionMet = true; | |
| break; // Found at least one match for this condition | |
| } | |
| } | |
| if ($logic === 'AND') { | |
| $overallResult = $overallResult && $conditionMet; | |
| if (!$overallResult) { | |
| return false; // Short-circuit for AND if any condition fails | |
| } | |
| } elseif ($logic === 'OR') { | |
| $overallResult = $overallResult || $conditionMet; | |
| if ($overallResult) { | |
| return true; // Short-circuit for OR if any condition passes | |
| } | |
| } | |
| } | |
| return $overallResult; | |
| } | |
| /** | |
| * Validates an HMAC signature. | |
| * | |
| * @param string $payload The raw request body. | |
| * @param string $signature The signature provided in the header. | |
| * @param string $secret The HMAC secret key. | |
| * @param string $algo The hashing algorithm (e.g., 'sha256', 'sha1'). | |
| * @param string $prefix Optional prefix to remove from the signature (e.g., 'sha256='). | |
| * @return bool True if the signature is valid, false otherwise. | |
| */ | |
| function validate_hmac_signature(string $payload, string $signature, string $secret, string $algo, string $prefix = ''): bool | |
| { | |
| if ($prefix && str_starts_with($signature, $prefix)) { | |
| $signature = substr($signature, strlen($prefix)); | |
| } | |
| $expectedSignature = hash_hmac($algo, $payload, $secret, false); // raw_output = false for hex string | |
| // Use hash_equals for timing attack safe comparison | |
| return hash_equals($expectedSignature, $signature); | |
| } | |
| /** | |
| * Performs webhook HMAC validation based on configured rules. | |
| * | |
| * @param array $requestData Contains 'raw_body', 'headers'. | |
| * @param array $hmacConfigs Array of HMAC configurations. | |
| * Example: | |
| * [ | |
| * "hubspot" => [ | |
| * "enabled" => true, | |
| * "header" => "X-HubSpot-Signature", | |
| * "secret" => "YOUR_HUBSPOT_SECRET", // Direct secret value from DB config | |
| * "algo" => "sha256", | |
| * "prefix" => "sha256=" | |
| * ], | |
| * "twilio" => [ | |
| * "enabled" => false, | |
| * "header" => "X-Twilio-Signature", | |
| * "secret" => "YOUR_TWILIO_SECRET", // Direct secret value from DB config | |
| * "algo" => "sha1" | |
| * ] | |
| * ] | |
| * @param LoggingHandler $logger For logging validation attempts and failures. | |
| * @param string $workflowId For logging context. | |
| * @return bool True if validation is not enabled or passes for an enabled type, false otherwise. | |
| */ | |
| function validate_webhook_request(array $requestData, array $hmacConfigs, LoggingHandler $logger, string $workflowId): bool | |
| { | |
| $rawBody = $requestData['raw_body'] ?? ''; | |
| $headers = $requestData['headers'] ?? []; | |
| foreach ($hmacConfigs as $type => $config) { | |
| if (!($config['enabled'] ?? false)) { | |
| continue; // Skip if not enabled | |
| } | |
| $headerName = $config['header'] ?? null; | |
| $secret = $config['secret'] ?? null; // Now directly from DB config | |
| $algo = $config['algo'] ?? null; | |
| $prefix = $config['prefix'] ?? ''; | |
| if (!$headerName || !$secret || !$algo) { | |
| $logger->logError("Workflow {$workflowId}: HMAC config for '{$type}' is incomplete (missing header, secret, or algo). Skipping validation."); | |
| continue; | |
| } | |
| $signature = $headers[$headerName] ?? null; | |
| if (!$signature) { | |
| $logger->logError("Workflow {$workflowId}: HMAC signature header '{$headerName}' missing for '{$type}' validation."); | |
| return false; // Signature required but not found | |
| } | |
| if (validate_hmac_signature($rawBody, $signature, $secret, $algo, $prefix)) { | |
| $logger->log("Workflow {$workflowId}: HMAC validation successful for '{$type}'."); | |
| return true; // Validation passed for this type | |
| } else { | |
| $logger->logError("Workflow {$workflowId}: HMAC validation FAILED for '{$type}'. Signature: '{$signature}', Payload: '{$rawBody}'."); | |
| return false; // Validation failed for this type | |
| } | |
| } | |
| // If no HMAC validation types are enabled, or loop completes without returning false, | |
| // it means validation is either not required or passed. | |
| $logger->log("Workflow {$workflowId}: No enabled HMAC validation types or all passed."); | |
| return true; | |
| } | |
| /** | |
| * Sets up all necessary database tables for logging and configuration. | |
| * This function is called early to ensure tables exist before logging/config operations. | |
| * | |
| * @param PDO $pdo The PDO connection (from WorkflowLogger's own connection). | |
| * @param LoggingHandler $logger The logger instance. | |
| */ | |
| function setup_database_tables(PDO $pdo, LoggingHandler $logger): void | |
| { | |
| $tables = [ | |
| "workflow_logs" => " | |
| CREATE TABLE IF NOT EXISTS workflow_logs ( | |
| id INT AUTO_INCREMENT PRIMARY KEY, | |
| workflow_id VARCHAR(255) NOT NULL, | |
| status VARCHAR(50) NOT NULL, | |
| message TEXT NOT NULL, | |
| context JSON, | |
| logged_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP | |
| ) | |
| ", | |
| "api_logs" => " | |
| CREATE TABLE IF NOT EXISTS api_logs ( | |
| id INT AUTO_INCREMENT PRIMARY KEY, | |
| operation TEXT NOT NULL, | |
| request_data JSON, | |
| response_data JSON, | |
| logged_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP | |
| ) | |
| ", | |
| "error_logs" => " | |
| CREATE TABLE IF NOT EXISTS error_logs ( | |
| id INT AUTO_INCREMENT PRIMARY KEY, | |
| message TEXT NOT NULL, | |
| context JSON, | |
| logged_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP | |
| ) | |
| ", | |
| "webhook_logs" => " | |
| CREATE TABLE IF NOT EXISTS webhook_logs ( | |
| id INT AUTO_INCREMENT PRIMARY KEY, | |
| event_type VARCHAR(255) NOT NULL, | |
| event_id VARCHAR(255) NOT NULL, | |
| signature VARCHAR(255) NOT NULL, | |
| timestamp TIMESTAMP NOT NULL, | |
| body TEXT NOT NULL | |
| ) | |
| ", | |
| "webhook_events" => " | |
| CREATE TABLE IF NOT EXISTS webhook_events ( | |
| id INT AUTO_INCREMENT PRIMARY KEY, | |
| object_id INT NOT NULL, | |
| property_name VARCHAR(255), | |
| property_value VARCHAR(255), | |
| change_source VARCHAR(255), | |
| event_id INT NOT NULL, | |
| subscription_id INT NOT NULL, | |
| portal_id INT NOT NULL, | |
| app_id INT NOT NULL, | |
| occurred_at TIMESTAMP NOT NULL, | |
| event_type VARCHAR(255) NOT NULL, | |
| attempt_number INT NOT NULL | |
| ) | |
| ", | |
| "data_triggers" => " | |
| CREATE TABLE IF NOT EXISTS data_triggers ( | |
| trigger_id INT AUTO_INCREMENT PRIMARY KEY, | |
| trigger_category VARCHAR(255), | |
| trigger_propname VARCHAR(255) NOT NULL, | |
| trigger_propvalue VARCHAR(255) NOT NULL, | |
| trigger_process TINYINT(1) NOT NULL, | |
| trigger_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | |
| trigger_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | |
| ) | |
| ", | |
| "data_trigger_logs" => " | |
| CREATE TABLE IF NOT EXISTS data_trigger_logs ( | |
| id INT AUTO_INCREMENT PRIMARY KEY, | |
| trigger_id INT, | |
| trigger_category VARCHAR(255), | |
| trigger_propname VARCHAR(255), | |
| trigger_propvalue VARCHAR(255), | |
| trigger_wasprocessed BOOLEAN, | |
| trigger_webhook_event_id VARCHAR(255), | |
| trigger_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | |
| FOREIGN KEY (trigger_id) REFERENCES data_triggers(trigger_id) | |
| ) | |
| ", | |
| "execution_times" => " | |
| CREATE TABLE IF NOT EXISTS execution_times ( | |
| id INT AUTO_INCREMENT PRIMARY KEY, | |
| operation VARCHAR(255) NOT NULL, | |
| round_time_ms INT NOT NULL | |
| ) | |
| ", | |
| "warning_logs" => " | |
| CREATE TABLE IF NOT EXISTS warning_logs ( | |
| id INT AUTO_INCREMENT PRIMARY KEY, | |
| message TEXT NOT NULL, | |
| context TEXT, | |
| log_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP | |
| ) | |
| ", | |
| "tokens" => " | |
| CREATE TABLE IF NOT EXISTS tokens ( | |
| id INT AUTO_INCREMENT PRIMARY KEY, | |
| username VARCHAR(255) NOT NULL UNIQUE, | |
| token TEXT NOT NULL, | |
| expiry INT NOT NULL | |
| ) | |
| ", | |
| "email_templates" => " | |
| CREATE TABLE IF NOT EXISTS email_templates ( | |
| id INT AUTO_INCREMENT PRIMARY KEY, | |
| name VARCHAR(255) NOT NULL UNIQUE, | |
| subject VARCHAR(255) NOT NULL, | |
| body TEXT NOT NULL | |
| ) | |
| ", | |
| "app_configurations" => " | |
| CREATE TABLE IF NOT EXISTS app_configurations ( | |
| id INT AUTO_INCREMENT PRIMARY KEY, | |
| config_name VARCHAR(255) NOT NULL UNIQUE, | |
| config_value JSON NOT NULL, | |
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | |
| updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | |
| ) | |
| " | |
| ]; | |
| foreach ($tables as $tableName => $createQuery) { | |
| try { | |
| $pdo->exec($createQuery); | |
| $logger->log("Table '{$tableName}' ensured to exist."); | |
| } catch (PDOException $e) { | |
| $logger->logError("Failed to create table '{$tableName}': " . $e->getMessage()); | |
| // Critical error, but don't die, just log and try next table | |
| } | |
| } | |
| } | |
| // --- Main Script Execution --- | |
| // Generate a unique workflow ID for this request at the very beginning | |
| $workflowId = uniqid('workflow_'); | |
| $oppRef = $_GET['opp'] ?? 'N/A'; // Initialize early for consistent logging | |
| // Initialize logger, databaseHandler, and workflowLogger | |
| $logger = null; | |
| $databaseHandler = null; | |
| $workflowLogger = null; | |
| try { | |
| // Load environment variables | |
| $dotenv = Dotenv::createImmutable(__DIR__ . '/../'); // Adjust path if .env is elsewhere | |
| $dotenv->load(); | |
| // Configure file-based logging | |
| $logConfig = [ | |
| 'file' => $_ENV['LOG_FILE'] ?? __DIR__ . '/../logs/app.log', | |
| 'json_file' => $_ENV['LOG_JSON_FILE'] ?? __DIR__ . '/../logs/app.json', | |
| 'json_logging_enabled' => filter_var($_ENV['JSON_LOGGING_ENABLED'] ?? 'false', FILTER_VALIDATE_BOOLEAN), | |
| ]; | |
| $logger = new LoggingHandler($logConfig); | |
| // Configure database connection details | |
| $dbConfig = [ | |
| 'host' => $_ENV['DB_HOST'] ?? 'localhost', | |
| 'dbname' => $_ENV['DB_NAME'] ?? 'api_integration', | |
| 'user' => $_ENV['DB_USER'] ?? 'root', | |
| 'password' => $_ENV['DB_PASSWORD'] ?? '', | |
| ]; | |
| // Instantiate DatabaseHandler (your original, unchanged one) | |
| // Note: Your DatabaseHandler's constructor does not accept a logger. | |
| // If its connection fails, it will `die()`. | |
| $databaseHandler = null; | |
| if (filter_var($_ENV['DB_LOGGING_ENABLED'] ?? 'false', FILTER_VALIDATE_BOOLEAN)) { | |
| try { | |
| $databaseHandler = new DatabaseHandler($dbConfig); | |
| } catch (Exception $e) { | |
| // This catch block might not be reached if DatabaseHandler uses die() | |
| $logger->logError("Workflow {$workflowId}: Failed to initialize DatabaseHandler: " . $e->getMessage()); | |
| $databaseHandler = null; // Ensure it's null if construction failed | |
| } | |
| } | |
| // Instantiate WorkflowLogger, passing the DatabaseHandler AND dbConfig to it. | |
| // WorkflowLogger will now create its OWN PDO connection and manage config fetching. | |
| if ($databaseHandler) { // Only instantiate workflow logger if DatabaseHandler was successfully initialized | |
| $workflowLogger = new WorkflowLogger($databaseHandler, $dbConfig); // Pass dbConfig for its own PDO | |
| // Ensure all tables are set up using WorkflowLogger's PDO | |
| if ($workflowLogger->pdo) { // Check if WorkflowLogger's PDO connected successfully | |
| setup_database_tables($workflowLogger->pdo, $logger); | |
| } else { | |
| $logger->logError("Workflow {$workflowId}: WorkflowLogger's PDO connection failed, cannot setup tables."); | |
| } | |
| } | |
| // --- Capture full incoming request details --- | |
| $requestHeaders = get_all_request_headers(); | |
| $rawRequestBody = file_get_contents('php://input'); | |
| $requestBodyJson = json_decode($rawRequestBody, true); | |
| $requestQueryParams = $_GET; | |
| $fullRequestData = [ | |
| 'method' => $_SERVER['REQUEST_METHOD'], | |
| 'uri' => $_SERVER['REQUEST_URI'], | |
| 'query_params' => $requestQueryParams, | |
| 'headers' => $requestHeaders, | |
| 'body_json' => $requestBodyJson, // Parsed JSON body | |
| 'raw_body' => $rawRequestBody // Raw body string | |
| ]; | |
| // Log incoming request details using WorkflowLogger | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'incoming', 'Received HTTP request for opportunity processing.', $fullRequestData); | |
| } | |
| $logger->log("Workflow {$workflowId}: Incoming request received.", ['query_params' => $requestQueryParams, 'headers_count' => count($requestHeaders), 'body_present' => ($requestBodyJson !== null)]); | |
| // --- Fetch Configurations from Database or use Defaults --- | |
| $filteringRules = []; | |
| $hmacValidationConfigs = []; | |
| $ncfeAssignmentRules = []; | |
| if ($workflowLogger) { | |
| $dbFilteringRules = $workflowLogger->getConfiguration('filtering_rules'); | |
| if ($dbFilteringRules) { | |
| $filteringRules = $dbFilteringRules; | |
| $logger->log("Workflow {$workflowId}: Loaded filtering rules from DB."); | |
| } else { | |
| $logger->logWarning("Workflow {$workflowId}: Filtering rules not found in DB. Using default/example rules."); | |
| // Default/Example Filtering Rules (if not found in DB) | |
| $filteringRules = [ | |
| "conditions" => [ | |
| [ | |
| "source" => "query_param", | |
| "key" => "action", | |
| "operator" => "equals", | |
| "value" => "process_opportunity" | |
| ], | |
| [ | |
| "source" => "header", | |
| "key" => "Content-Type", | |
| "operator" => "contains", | |
| "value" => "application/json" | |
| ] | |
| ], | |
| "logic" => "AND" | |
| ]; | |
| // Optionally, store this default to DB for future use | |
| $workflowLogger->setConfiguration('filtering_rules', $filteringRules); | |
| } | |
| $dbHmacConfigs = $workflowLogger->getConfiguration('hmac_configs'); | |
| if ($dbHmacConfigs) { | |
| $hmacValidationConfigs = $dbHmacConfigs; | |
| $logger->log("Workflow {$workflowId}: Loaded HMAC configurations from DB."); | |
| } else { | |
| $logger->logWarning("Workflow {$workflowId}: HMAC configurations not found in DB. Using default/example configs."); | |
| // Default/Example HMAC Configurations (if not found in DB) | |
| // IMPORTANT: Replace 'YOUR_HUBSPOT_SECRET' and 'YOUR_TWILIO_SECRET' with actual secrets | |
| // These should ideally be managed securely, not hardcoded even as defaults. | |
| $hmacValidationConfigs = [ | |
| "hubspot" => [ | |
| "enabled" => false, // Set to true to enable | |
| "header" => "X-HubSpot-Signature", | |
| "secret" => $_ENV['HUBSPOT_WEBHOOK_SECRET'] ?? 'YOUR_HUBSPOT_SECRET', // Fallback to .env or hardcoded | |
| "algo" => "sha256", | |
| "prefix" => "sha256=" | |
| ], | |
| "twilio" => [ | |
| "enabled" => false, // Set to true to enable | |
| "header" => "X-Twilio-Signature", | |
| "secret" => $_ENV['TWILIO_AUTH_TOKEN'] ?? 'YOUR_TWILIO_SECRET', // Fallback to .env or hardcoded | |
| "algo" => "sha1" | |
| ] | |
| ]; | |
| // Optionally, store this default to DB for future use | |
| $workflowLogger->setConfiguration('hmac_configs', $hmacValidationConfigs); | |
| } | |
| $dbNcfeAssignmentRules = $workflowLogger->getConfiguration('ncfe_assignment_rules'); | |
| if ($dbNcfeAssignmentRules) { | |
| $ncfeAssignmentRules = $dbNcfeAssignmentRules; | |
| $logger->log("Workflow {$workflowId}: Loaded NCFE assignment rules from DB."); | |
| } else { | |
| $logger->logWarning("Workflow {$workflowId}: NCFE assignment rules not found in DB. Using default/example rules."); | |
| // Default/Example NCFE Assignment Rules (if not found in DB) | |
| $ncfeAssignmentRules = [ | |
| "default_assignments" => [ | |
| "units" => [], | |
| "modules" => [], | |
| "diags" => [] | |
| ], | |
| "funding_stream_rules" => [ | |
| "Adult Education Budget" => [ | |
| "units" => [ | |
| ["id" => 971, "diags" => [["diag" => 185, "progress" => 1], ["diag" => 184, "progress" => 1], ["diag" => 183, "progress" => 1]]] | |
| ], | |
| "modules" => [ | |
| ["id" => 568], | |
| ["id" => 570] | |
| ], | |
| "diags" => [] | |
| ], | |
| "Apprenticeship Levy" => [ | |
| "units" => [ | |
| ["id" => 211, "diags" => [["diag" => 199, "progress" => 1]]] | |
| ], | |
| "modules" => [ | |
| ["id" => 586] | |
| ], | |
| "diags" => [] | |
| ] | |
| ], | |
| "known_ncfe_items" => [ | |
| "units" => [ | |
| {"id": 568, "name": "Reform English Resources (FS)"}, | |
| {"id": 570, "name": "Reform Maths Resources (FS)"}, | |
| {"id": 971, "name": "Reform Assessments"}, | |
| {"id": 211, "name": "Digital Functional Skills"}, | |
| {"id": 975, "name": "Essential Digital Skills (Unit)"} | |
| ], | |
| "modules" => [ | |
| {"id": 183, "name": "Reform maths"}, | |
| {"id": 184, "name": "Reform english"}, | |
| {"id": 199, "name": "Essential Digital Skills Assessment"}, | |
| {"id": 586, "name": "Essential Digital Skills Resources"} | |
| ], | |
| "diagnostics" => [ | |
| {"id": 15, "name": "FS English Diagnostic"}, | |
| {"id": 16, "name": "FS Maths Diagnostic"}, | |
| {"id": 18, "name": "FS ICT Diagnostic"}, | |
| {"id": 23, "name": "Learning Styles Assessment"}, | |
| {"id": 60, "name": "GCSE English Diagnostic"}, | |
| {"id": 61, "name": "GCSE Maths Diagnostic"}, | |
| {"id": 70, "name": "Digital Skills Diagnostic"}, | |
| {"id": 183, "name": "Reform Maths Diag"}, | |
| {"id": 184, "name": "Reform English Diag"}, | |
| {"id": 185, "name": "Reform ICT Diag"}, | |
| {"id": 199, "name": "Essential Digital Skills Diag"} | |
| ] | |
| ] | |
| ]; | |
| // Optionally, store this default to DB for future use | |
| $workflowLogger->setConfiguration('ncfe_assignment_rules', $ncfeAssignmentRules); | |
| } | |
| } else { | |
| $logger->logError("Workflow {$workflowId}: WorkflowLogger not initialized. Cannot fetch DB configurations. Using hardcoded defaults."); | |
| // Fallback to hardcoded defaults if WorkflowLogger itself failed to initialize | |
| $filteringRules = [ | |
| "conditions" => [ | |
| ["source" => "query_param", "key" => "action", "operator" => "equals", "value" => "process_opportunity"] | |
| ], | |
| "logic" => "AND" | |
| ]; | |
| $hmacValidationConfigs = [ | |
| "hubspot" => ["enabled" => false, "header" => "X-HubSpot-Signature", "secret" => 'FALLBACK_HUBSPOT_SECRET', "algo" => "sha256", "prefix" => "sha256="], | |
| "twilio" => ["enabled" => false, "header" => "X-Twilio-Signature", "secret" => 'FALLBACK_TWILIO_SECRET', "algo" => "sha1"] | |
| ]; | |
| $ncfeAssignmentRules = [ | |
| "default_assignments" => ["units" => [], "modules" => [], "diags" => []], | |
| "funding_stream_rules" => [], | |
| "known_ncfe_items" => [] | |
| ]; | |
| } | |
| // --- End Fetch Configurations --- | |
| // --- HMAC Validation --- | |
| $isHmacValid = validate_webhook_request($fullRequestData, $hmacValidationConfigs, $logger, $workflowId); | |
| if (!$isHmacValid) { | |
| $errorMessage = "HMAC signature validation failed. Processing halted."; | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'failed', $errorMessage, ['reason' => 'hmac_validation_failed']); | |
| } | |
| $logger->logError("Workflow {$workflowId}: " . $errorMessage); | |
| http_response_code(401); // Unauthorized | |
| echo json_encode(['status' => 'error', 'message' => $errorMessage]); | |
| exit; | |
| } | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'processing', "HMAC signature validated successfully."); | |
| } | |
| $logger->log("Workflow {$workflowId}: HMAC signature validated successfully."); | |
| // --- End HMAC Validation --- | |
| // --- Conditional Filtering based on Request Data --- | |
| $requestMeetsConditions = evaluate_request_conditions($fullRequestData, $filteringRules); | |
| if (!$requestMeetsConditions) { | |
| $errorMessage = "Request did not meet defined filtering conditions. Processing halted."; | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'failed', $errorMessage, ['filtering_rules' => $filteringRules]); | |
| } | |
| $logger->logError("Workflow {$workflowId}: " . $errorMessage); | |
| http_response_code(403); // Forbidden | |
| echo json_encode(['status' => 'error', 'message' => $errorMessage]); | |
| exit; | |
| } | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'processing', "Request met filtering conditions. Proceeding."); | |
| } | |
| $logger->log("Workflow {$workflowId}: Request met filtering conditions. Proceeding."); | |
| // --- End Conditional Filtering --- | |
| // Initialize API Clients (pass the original DatabaseHandler to them) | |
| $dynamicsClientApi = new DynamicsApiClient($logger, $databaseHandler); | |
| $nfceApiClient = new NfceApiClient($logger, $databaseHandler); | |
| // 1. Receive HTTP Request with a query parameter &opp=$_GET["opp"] | |
| $oppRef = $_GET['opp'] ?? null; | |
| if (!$oppRef) { | |
| $errorMessage = 'Missing opportunity reference (opp) parameter in request.'; | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'failed', $errorMessage, ['reason' => 'missing_parameter']); | |
| } | |
| $logger->logError("Workflow {$workflowId}: " . $errorMessage); | |
| echo json_encode(['status' => 'error', 'message' => $errorMessage]); | |
| exit; | |
| } | |
| // 2. Lookup Dynamics Opportunity by the Opportunity reference (active_opportunityref) | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'processing', "Attempting to get Dynamics Opportunity ID.", ['opportunity_reference' => $oppRef]); | |
| } | |
| $logger->log("Workflow {$workflowId}: Attempting to get Dynamics Opportunity ID for OppRef: {$oppRef}"); | |
| $opportunityId = $dynamicsClientApi->getOpportunityIdByOppRef($oppRef); | |
| if (!$opportunityId) { | |
| $errorMessage = "Dynamics Opportunity not found for OppRef: {$oppRef}."; | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'failed', $errorMessage, ['opportunity_reference' => $oppRef]); | |
| } | |
| $logger->logError("Workflow {$workflowId}: " . $errorMessage); | |
| echo json_encode(['status' => 'error', 'message' => $errorMessage]); | |
| exit; | |
| } | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'processing', "Found Dynamics Opportunity ID.", ['opportunity_id' => $opportunityId]); | |
| } | |
| $logger->log("Workflow {$workflowId}: Found Dynamics Opportunity ID: {$opportunityId} for OppRef: {$oppRef}"); | |
| // 3. Select all interesting columns from the opportunity | |
| $selectColumns = '$select=active_ncfeuser,active_ncfeassessments,active_ncfemodules,active_ncferoles,active_ncfegroups,active_courseinfo,active_opportunityref,active_skillsforwardaccountcreated,active_iduserinstitution,active_iduser,active_leadreferenceid,firstname,lastname,emailaddress1,active_fundingstream,address1_postalcode,mobilephone,telephone1,birthdate'; | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'processing', "Retrieving Dynamics Opportunity details.", ['opportunity_id' => $opportunityId]); | |
| } | |
| $logger->log("Workflow {$workflowId}: Attempting to get Dynamics Opportunity details for ID: {$opportunityId}"); | |
| $opportunityDetails = $dynamicsClientApi->getOpportunityById($opportunityId, $selectColumns); | |
| if (!$opportunityDetails) { | |
| $errorMessage = "Failed to retrieve Dynamics Opportunity details for ID: {$opportunityId}."; | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'failed', $errorMessage, ['opportunity_id' => $opportunityId]); | |
| } | |
| $logger->logError("Workflow {$workflowId}: " . $errorMessage); | |
| echo json_encode(['status' => 'error', 'message' => $errorMessage]); | |
| exit; | |
| } | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'processing', "Successfully retrieved Dynamics Opportunity details.", ['details_retrieved' => true, 'opportunity_ref' => $opportunityDetails['active_opportunityref'] ?? 'N/A', 'lead_ref' => $opportunityDetails['active_leadreferenceid'] ?? 'N/A']); | |
| } | |
| $logger->log("Workflow {$workflowId}: Successfully retrieved Dynamics Opportunity details for ID: {$opportunityId}"); | |
| // Extract relevant data for NCFE user registration | |
| $firstName = $opportunityDetails['firstname'] ?? 'N/A'; | |
| $lastName = $opportunityDetails['lastname'] ?? 'N/A'; | |
| $email = $opportunityDetails['emailaddress1'] ?? null; | |
| $studentRef = $opportunityDetails['active_leadreferenceid'] ?? $oppRef; // Use lead ref or opp ref as student ref | |
| $phone1 = $opportunityDetails['mobilephone'] ?? $opportunityDetails['telephone1'] ?? null; | |
| $dob = isset($opportunityDetails['birthdate']) ? date('Y-m-d', strtotime($opportunityDetails['birthdate'])) : null; | |
| if (!$email) { | |
| $errorMessage = "Email address not found in Dynamics Opportunity for ID: {$opportunityId}. Cannot register NCFE user."; | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'failed', $errorMessage, ['opportunity_id' => $opportunityId]); | |
| } | |
| $logger->logError("Workflow {$workflowId}: " . $errorMessage); | |
| echo json_encode(['status' => 'error', 'message' => $errorMessage]); | |
| exit; | |
| } | |
| // Generate a simple username and password | |
| $username = strtolower(str_replace(' ', '', $firstName)) . '.' . strtolower(str_replace(' ', '', $lastName)) . '_' . uniqid(); | |
| $password = bin2hex(random_bytes(8)); // 16 character hex string | |
| $registerUserData = [ | |
| 'firstName' => $firstName, | |
| 'lastName' => $lastName, | |
| 'email' => $email, | |
| 'username' => $username, | |
| 'password' => [ | |
| 'password' => $password, | |
| 'format' => 'plain' | |
| ], | |
| 'studentRef' => $studentRef, | |
| 'sendWelcomeEmail' => false, // Set to true if you want NCFE to send welcome email | |
| 'phone1' => $phone1, | |
| 'dob' => $dob, | |
| ]; | |
| // 4. Initialize NCFE API Client (already done at the top) | |
| // 5. Register a new user in NCFE | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'processing', "Attempting NCFE user registration.", ['email' => $email, 'registration_data_preview' => ['firstName' => $firstName, 'lastName' => $lastName, 'username' => $username]]); | |
| } | |
| $logger->log("Workflow {$workflowId}: Attempting to register NCFE user with email: {$email}"); | |
| $registerUserResponse = $nfceApiClient->registerUser($registerUserData); | |
| if (!$registerUserResponse || ($registerUserResponse['code'] !== 200 && !isset($registerUserResponse['messages']))) { | |
| $errorMessage = "Failed to register NCFE user for email: {$email}."; | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'failed', $errorMessage, ['email' => $email, 'ncfe_response' => $registerUserResponse]); | |
| } | |
| $logger->logError("Workflow {$workflowId}: " . $errorMessage . " Response: " . json_encode($registerUserResponse)); | |
| echo json_encode(['status' => 'error', 'message' => $errorMessage]); | |
| exit; | |
| } | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'processing', "NCFE user registration response received.", ['ncfe_response_code' => $registerUserResponse['code'], 'ncfe_messages' => $registerUserResponse['messages'] ?? 'N/A']); | |
| } | |
| $logger->log("Workflow {$workflowId}: NCFE user registration response: " . json_encode($registerUserResponse)); | |
| // 6. Extract auto-assigned units/diagnostics | |
| $autoAssignedUnits = []; | |
| $autoAssignedDiags = []; | |
| if (isset($registerUserResponse['messages']) && is_array($registerUserResponse['messages'])) { | |
| foreach ($registerUserResponse['messages'] as $message) { | |
| if (str_starts_with($message, 'User assigned to unit:')) { | |
| if (preg_match('/unit: (\d+)/', $message, $matches)) { | |
| $autoAssignedUnits[] = (int)$matches[1]; | |
| } | |
| if (preg_match('/diagnostics: ([\d, ]+)/', $message, $matches)) { | |
| $diags = array_map('intval', explode(',', $matches[1])); | |
| $autoAssignedDiags = array_merge($autoAssignedDiags, $diags); | |
| } | |
| } | |
| } | |
| } | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'processing', "Extracted auto-assigned NCFE items.", ['units' => $autoAssignedUnits, 'diagnostics' => $autoAssignedDiags]); | |
| } | |
| $logger->log("Workflow {$workflowId}: Auto-assigned NCFE units: " . json_encode($autoAssignedUnits) . ", diagnostics: " . json_encode($autoAssignedDiags)); | |
| // 7. Lookup NCFE user details (idUser, idUserInstitution) | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'processing', "Retrieving NCFE user details (idUser, idUserInstitution).", ['email' => $email]); | |
| } | |
| $logger->log("Workflow {$workflowId}: Attempting to get NCFE user details for email: {$email}"); | |
| $ncfeUserDetailsResponse = $nfceApiClient->getUserDetail(['email' => $email]); | |
| $idUser = null; | |
| $idUserInstitution = null; | |
| $ncfeUserGroups = []; // Placeholder for groups if retrieved | |
| $ncfeUserRoles = ''; // Placeholder for role if retrieved | |
| if ($ncfeUserDetailsResponse && isset($ncfeUserDetailsResponse['data'][0])) { | |
| $idUser = $ncfeUserDetailsResponse['data'][0]['idUser'] ?? null; | |
| $idUserInstitution = $ncfeUserDetailsResponse['data'][0]['idUserInstitution'] ?? null; | |
| $ncfeUserGroups = $ncfeUserDetailsResponse['data'][0]['groups'] ?? []; | |
| $ncfeUserRoles = $ncfeUserDetailsResponse['data'][0]['roleTitle'] ?? ''; | |
| } | |
| if (!$idUser || !$idUserInstitution) { | |
| $errorMessage = "Failed to retrieve idUser or idUserInstitution for NCFE user with email: {$email}."; | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'failed', $errorMessage, ['email' => $email, 'ncfe_response' => $ncfeUserDetailsResponse]); | |
| } | |
| $logger->logError("Workflow {$workflowId}: " . $errorMessage . " Response: " . json_encode($ncfeUserDetailsResponse)); | |
| echo json_encode(['status' => 'error', 'message' => $errorMessage]); | |
| exit; | |
| } | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'processing', "Retrieved NCFE idUser and idUserInstitution.", ['idUser' => $idUser, 'idUserInstitution' => $idUserInstitution, 'groups' => $ncfeUserGroups, 'role' => $ncfeUserRoles]); | |
| } | |
| $logger->log("Workflow {$workflowId}: Retrieved NCFE idUser: {$idUser}, idUserInstitution: {$idUserInstitution}"); | |
| // 8. Conditionally assign additional NCFE modules/units/diagnostics based on DB config | |
| $fundingStream = $opportunityDetails['active_fundingstream'] ?? null; | |
| $unitsToAssign = $ncfeAssignmentRules['default_assignments']['units'] ?? []; | |
| $modulesToAssign = $ncfeAssignmentRules['default_assignments']['modules'] ?? []; | |
| $diagsToAssign = $ncfeAssignmentRules['default_assignments']['diags'] ?? []; | |
| if ($fundingStream && isset($ncfeAssignmentRules['funding_stream_rules'][$fundingStream])) { | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'processing', "Applying funding stream specific assignments for '{$fundingStream}'."); | |
| } | |
| $logger->log("Workflow {$workflowId}: Funding stream '{$fundingStream}' detected. Applying specific NCFE assignments."); | |
| $streamRules = $ncfeAssignmentRules['funding_stream_rules'][$fundingStream]; | |
| $unitsToAssign = array_merge($unitsToAssign, $streamRules['units'] ?? []); | |
| $modulesToAssign = array_merge($modulesToAssign, $streamRules['modules'] ?? []); | |
| $diagsToAssign = array_merge($diagsToAssign, $streamRules['diags'] ?? []); | |
| } else { | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'processing', "No specific funding stream rules found for '{$fundingStream}'. Using default assignments."); | |
| } | |
| $logger->log("Workflow {$workflowId}: No specific funding stream rules found for '{$fundingStream}'. Using default assignments."); | |
| } | |
| $ncfeAssignedModules = []; | |
| $ncfeAssignedUnits = []; | |
| $ncfeAssignedDiags = []; | |
| if (!empty($unitsToAssign) || !empty($modulesToAssign) || !empty($diagsToAssign)) { | |
| $assignUnitsModulesData = []; | |
| if (!empty($unitsToAssign)) { | |
| $assignUnitsModulesData['units'] = $unitsToAssign; | |
| } | |
| if (!empty($modulesToAssign)) { | |
| $assignUnitsModulesData['modules'] = $modulesToAssign; | |
| } | |
| // Note: NCFE API's assignUnitsModules expects diags *within* units. | |
| // If $diagsToAssign contains standalone diags, you might need to map them to a generic unit or handle separately. | |
| // For now, assuming diags are either auto-assigned or part of units. | |
| // If $diagsToAssign are standalone, you might need a separate API call or a different structure. | |
| // For this example, I'll merge standalone diags into the first unit if available, or log a warning. | |
| if (!empty($diagsToAssign)) { | |
| if (!empty($assignUnitsModulesData['units'])) { | |
| // Merge standalone diags into the first unit's diags | |
| $existingDiags = $assignUnitsModulesData['units'][0]['diags'] ?? []; | |
| foreach ($diagsToAssign as $diag) { | |
| $existingDiags[] = $diag; | |
| } | |
| $assignUnitsModulesData['units'][0]['diags'] = $existingDiags; | |
| } else { | |
| $logger->logWarning("Workflow {$workflowId}: Standalone diagnostics found but no units to attach them to for assignment. Skipping standalone diags assignment."); | |
| } | |
| } | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'processing', "Attempting to assign additional NCFE units/modules.", ['idUser' => $idUser, 'assignment_data' => $assignUnitsModulesData]); | |
| } | |
| $logger->log("Workflow {$workflowId}: Attempting to assign additional NCFE units/modules for idUser: {$idUser}. Data: " . json_encode($assignUnitsModulesData)); | |
| $assignResponse = $nfceApiClient->assignUnitsModules(['idUser' => $idUser], $assignUnitsModulesData); | |
| if ($assignResponse && ($assignResponse['code'] === 1 || $assignResponse['code'] === 200)) { | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'processing', "Successfully assigned additional NCFE units/modules.", ['ncfe_response' => $assignResponse]); | |
| } | |
| $logger->log("Workflow {$workflowId}: Successfully assigned additional NCFE units/modules. Response: " . json_encode($assignResponse)); | |
| if (isset($assignResponse['messages']['unit']['Success'])) { | |
| preg_match('/\[([\d,]+)\]/', $assignResponse['messages']['unit']['Success'], $matches); | |
| if (isset($matches[1])) { | |
| $ncfeAssignedUnits = array_merge($ncfeAssignedUnits, array_map('intval', explode(',', $matches[1]))); | |
| } | |
| } | |
| if (isset($assignResponse['messages']['unitdiags']['Success'])) { | |
| preg_match('/\[([\d,]+)\]/', $assignResponse['messages']['unitdiags']['Success'], $matches); | |
| if (isset($matches[1])) { | |
| $ncfeAssignedDiags = array_merge($ncfeAssignedDiags, array_map('intval', explode(',', $matches[1]))); | |
| } | |
| } | |
| if (isset($assignResponse['messages']['modules']['Success'])) { | |
| preg_match('/\[([\d,]+)\]/', $assignResponse['messages']['modules']['Success'], $matches); | |
| if (isset($matches[1])) { | |
| $ncfeAssignedModules = array_merge($ncfeAssignedModules, array_map('intval', explode(',', $matches[1]))); | |
| } | |
| } | |
| } else { | |
| $errorMessage = "Failed to assign additional NCFE units/modules for idUser: {$idUser}."; | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'processing_error', $errorMessage, ['idUser' => $idUser, 'ncfe_response' => $assignResponse]); | |
| } | |
| $logger->logError("Workflow {$workflowId}: " . $errorMessage . " Response: " . json_encode($assignResponse)); | |
| } | |
| } else { | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'processing', "No additional NCFE units/modules to assign based on funding stream.", ['funding_stream' => $fundingStream]); | |
| } | |
| $logger->log("Workflow {$workflowId}: No additional NCFE units/modules to assign based on funding stream: {$fundingStream}"); | |
| } | |
| // Combine auto-assigned and conditionally assigned items for Dynamics update | |
| $finalAssignedUnits = array_unique(array_merge($autoAssignedUnits, $ncfeAssignedUnits)); | |
| $finalAssignedDiags = array_unique(array_merge($autoAssignedDiags, $ncfeAssignedDiags)); | |
| $finalAssignedModules = array_unique($ncfeAssignedModules); | |
| // 9. Update the Dynamics about success | |
| $dynamicsUpdateData = [ | |
| 'active_ncfeuser' => true, | |
| 'active_iduser' => (string)$idUser, | |
| 'active_iduserinstitution' => (string)$idUserInstitution, | |
| 'active_skillsforwardaccountcreated' => true, | |
| 'active_ncfegroups' => json_encode($ncfeUserGroups), // Store actual groups if retrieved/assigned | |
| 'active_ncferoles' => $ncfeUserRoles, // Store role if retrieved/assigned | |
| 'active_ncfeunits' => json_encode($finalAssignedUnits), // Assuming a field for units | |
| 'active_ncfeassessments' => json_encode($finalAssignedDiags), // Using 'active_ncfeassessments' for diagnostics | |
| 'active_ncfemodules' => json_encode($finalAssignedModules), | |
| 'active_ncfeusername' => $username // Assuming a field for NCFE username | |
| ]; | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'processing', "Attempting to update Dynamics Opportunity with NCFE details.", ['opportunity_id' => $opportunityId, 'update_data_keys' => array_keys($dynamicsUpdateData)]); | |
| } | |
| $logger->log("Workflow {$workflowId}: Attempting to patch Dynamics Opportunity ID: {$opportunityId} with NCFE details. Data: " . json_encode($dynamicsUpdateData)); | |
| $patchSuccess = $dynamicsClientApi->patchOpportunityById($opportunityId, $dynamicsUpdateData); | |
| if (!$patchSuccess) { | |
| $errorMessage = "Failed to update Dynamics Opportunity ID: {$opportunityId} with NCFE details."; | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'failed', $errorMessage, ['opportunity_id' => $opportunityId, 'update_data' => $dynamicsUpdateData]); | |
| } | |
| $logger->logError("Workflow {$workflowId}: " . $errorMessage); | |
| echo json_encode(['status' => 'error', 'message' => $errorMessage]); | |
| exit; | |
| } | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'success', "Successfully updated Dynamics Opportunity with NCFE details.", ['opportunity_id' => $opportunityId]); | |
| } | |
| $logger->log("Workflow {$workflowId}: Successfully updated Dynamics Opportunity ID: {$opportunityId} with NCFE details."); | |
| // Final success response for the entire workflow | |
| $successResponse = [ | |
| 'status' => 'success', | |
| 'message' => 'Workflow completed successfully.', | |
| 'workflow_id' => $workflowId, | |
| 'opportunity_id' => $opportunityId, | |
| 'dynamics_opp_ref' => $opportunityDetails['active_opportunityref'] ?? 'N/A', | |
| 'dynamics_lead_ref' => $opportunityDetails['active_leadreferenceid'] ?? 'N/A', | |
| 'ncfe_user_id' => $idUser, | |
| 'ncfe_user_institution_id' => $idUserInstitution, | |
| 'ncfe_username' => $username, | |
| 'assigned_units' => $finalAssignedUnits, | |
| 'assigned_diagnostics' => $finalAssignedDiags, | |
| 'assigned_modules' => $finalAssignedModules, | |
| 'ncfe_groups' => $ncfeUserGroups, | |
| 'ncfe_role' => $ncfeUserRoles | |
| ]; | |
| echo json_encode($successResponse); | |
| } catch (Exception $e) { | |
| // Catch any unexpected exceptions during the workflow | |
| $errorMessage = 'An unexpected error occurred during workflow execution: ' . $e->getMessage(); | |
| if ($workflowLogger) { | |
| $workflowLogger->logActivity($workflowId, 'failed', $errorMessage, ['exception' => $e->getMessage(), 'trace' => $e->getTraceAsString()]); | |
| } | |
| // Log to file as well | |
| if ($logger) { | |
| $logger->logError("Workflow {$workflowId}: " . $errorMessage . " Stack: " . $e->getTraceAsString()); | |
| } else { | |
| // Fallback if logger itself failed to initialize | |
| error_log("CRITICAL ERROR: Workflow {$workflowId}: " . $errorMessage . " Stack: " . $e->getTraceAsString()); | |
| } | |
| http_response_code(500); // Internal Server Error | |
| echo json_encode(['status' => 'error', 'message' => $errorMessage]); | |
| exit; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment