diff --git a/ProcessMaker/Traits/MakeHttpRequests.php b/ProcessMaker/Traits/MakeHttpRequests.php index f38ed211e2..bcff6a8efa 100644 --- a/ProcessMaker/Traits/MakeHttpRequests.php +++ b/ProcessMaker/Traits/MakeHttpRequests.php @@ -499,18 +499,27 @@ private function addQueryStringsParamsToUrl($endpoint, array $config, array $dat $parsedUrl = $this->parseUrl($url); $query = []; parse_str($parsedUrl['query'] ?? '', $query); - if (array_key_exists('params', $endpoint)) { + $hasEndpointParams = array_key_exists('params', $endpoint) && is_array($endpoint['params']); + if ($hasEndpointParams) { foreach ($endpoint['params'] as $param) { + if (!array_key_exists('key', $param)) { + continue; + } + $key = $this->evalMustache($param['key'], $data); // Get value from outbound configuration, if not defined get the default value - $value = $params[$key] ?? $this->evalMustache($param['value'], $data); - if ($value !== '' || $param['required']) { + $value = $params[$key] ?? $this->evalMustache($param['value'] ?? '', $data); + if ($value !== '' || ($param['required'] ?? false)) { $query[$key] = $value; } } - } else { + } + + // Preserve legacy behavior for configured endpoint params, but when params are + // missing/empty allow dynamic PARAM values (e.g. pmql from screen select-list). + if (!$hasEndpointParams || empty($endpoint['params'])) { foreach ($params as $key => $value) { - if ($value !== '') { + if ($value !== '' && !array_key_exists($key, $query)) { $query[$key] = $value; } } diff --git a/tests/Feature/MakeHttpRequestTest.php b/tests/Feature/MakeHttpRequestTest.php index ef9bde7af8..1893b4262a 100644 --- a/tests/Feature/MakeHttpRequestTest.php +++ b/tests/Feature/MakeHttpRequestTest.php @@ -128,6 +128,70 @@ public function testRequestConstructionWithoutCommonParams() $this->assertEquals('json', $bodyType); } + public function testRequestConstructionAddsDynamicParamsWhenEndpointParamsAreEmpty() + { + $testStub = new class { + use MakeHttpRequests; + }; + $testStub->endpoints = json_decode('{"records":{"url":"https://example.test/api/1.0/collections/4/records","body":"","view":false,"method":"GET","params":[],"headers":[],"purpose":"records","body_type":"raw","outboundConfig":[]}}', true); + $testStub->credentials = ['verify_certificate' => true]; + $testStub->authtype = 'NONE'; + + $endpointConfig = [ + 'dataSource' => 1, + 'endpoint' => 'records', + 'outboundConfig' => [ + ['value' => '{{pmqlValue}}', 'type' => 'PARAM', 'key' => 'pmql', 'format' => 'mustache'], + ], + ]; + $requestData = [ + 'pmqlValue' => 'data.form_input_1=Lorem', + ]; + + $request = $this->callMethod( + $testStub, + 'prepareRequestWithOutboundConfig', + [$requestData, &$endpointConfig] + ); + [$method, $url] = array_values($request); + + $this->assertEquals('GET', $method); + parse_str(parse_url($url, PHP_URL_QUERY), $query); + $this->assertArrayHasKey('pmql', $query); + $this->assertEquals('data.form_input_1=Lorem', $query['pmql']); + } + + public function testRequestConstructionWithEmptyEndpointParamsDoesNotOverwriteExistingQueryParams() + { + $testStub = new class { + use MakeHttpRequests; + }; + $testStub->endpoints = json_decode('{"records":{"url":"https://example.test/api/1.0/collections/4/records?pmql=data.form_input_1%3D%22Original%22","body":"","view":false,"method":"GET","params":[],"headers":[],"purpose":"records","body_type":"raw","outboundConfig":[]}}', true); + $testStub->credentials = ['verify_certificate' => true]; + $testStub->authtype = 'NONE'; + + $endpointConfig = [ + 'dataSource' => 1, + 'endpoint' => 'records', + 'outboundConfig' => [ + ['value' => '{{pmqlValue}}', 'type' => 'PARAM', 'key' => 'pmql', 'format' => 'mustache'], + ], + ]; + $requestData = [ + 'pmqlValue' => 'data.form_input_1=Lorem', + ]; + + $request = $this->callMethod( + $testStub, + 'prepareRequestWithOutboundConfig', + [$requestData, &$endpointConfig] + ); + [, $url] = array_values($request); + + parse_str(parse_url($url, PHP_URL_QUERY), $query); + $this->assertEquals('data.form_input_1="Original"', html_entity_decode($query['pmql'])); + } + /** * Verifies that different Guzzle Http Responses are mapped correctly calling the function responseWithHeaderData */