-1

I try to use the following API to make automatic call with audio files : https://contact-everyone.orange-business.com/api/docs/guides/index.html?shell#cr-er-une-diffusion-vocale

The 'linux cURL' code is the following one (on the right in the previous link) :

    curl -X POST https://[SERVER_URL]/api/v1.2/groups/[id_group]/diffusion-requests \
-H 'Authorization: Bearer [Access-Token]' \
-H 'Content-Type: multipart/form-data' \
-F audio-intro=@/path/to/myintro.wav \
-F audio-body=@/path/to/mybody.wav \
-F audio-outro=@/path/to/myoutro.wav \
-F 'diffusion={
           "name":"diffusion vocale via API REST",
           "contactIds":["id_contact_1", "id_contact_2", ...],
           "mailingListIds":["id_mailing_list_1","id_mailing_list_2", ...],
           "excludedContactIds":[],
           "msisdns":["0612327745"],
           "landlines":["0522331155"],
           "voiceParam":{
              "locale": "fr_FR"
           }
        };type=application/json'

I search to do the same, but in PHP. I tried a lot of things like this :

    $diffusion_params = '"diffusion"={
           "name":"diffusion vocale via API REST",
           "contactIds":[],
           "mailingListIds":[],
           "excludedContactIds":[],
           "msisdns":["0612345678"],
           "landlines":[],
           "voiceParam":{
              "locale": "fr_FR"
           }
        };type=application/json' ;

    $audio_intro_param = '"audio-intro"="@/path/to/file/sound.wav"';
    $audio_body_param = '"audio-body"="@/path/to/file/sound.wav"';
    $audio_outro_param = '"audio-outro"="@/path/to/file/sound.wav"';

    $post_field_params = array($audio_intro_param, $audio_body_param, $audio_outro_param, $diffusion_params);
    $ch = curl_init();

    curl_setopt( $ch,CURLOPT_URL, 'https://'.$URL.'/api/v1.2/groups/'.$group_id.'/diffusion-requests');
    curl_setopt( $ch,CURLOPT_POST, true );
    curl_setopt( $ch,CURLOPT_HTTPHEADER, array("Authorization: Bearer ".$token, "Content-Type: multipart/form-data") );
    curl_setopt( $ch,CURLOPT_RETURNTRANSFER, true );
    curl_setopt( $ch,CURLOPT_SSL_VERIFYPEER, false );
    curl_setopt( $ch,CURLOPT_POSTFIELDS, $post_field_params );

    $result = curl_exec($ch);// Retourne un résultat de la forme suivante : 

    curl_close($ch);

My problem concerns all '-F' options. How to 'convert' it in PHP ?

[UPDATE] The problem was an internal routing error in Orange network.

I use this service in php using a "shell_exec($cmd)", where the $cmd is a raw bash curl command, and it works fine.

JeremDem
  • 51
  • 1
  • 11

2 Answers2

0

the -X POST roughly translates to CURLOPT_POST=>1 (actually the exact translation would be with CURLOPT_CUSTOMREQUEST, but don't use that, use CURLOPT_POST instead.)

the https://[SERVER_URL]/api/v1.2/groups/[id_group]/diffusion-requests translates to CURLOPT_URL => 'https://[SERVER_URL]/api/v1.2/groups/[id_group]/diffusion-requests'

the

-H 'Authorization: Bearer [Access-Token]' \

translates to

CURLOPT_HTTPHEADER=>array('Authorization: Bearer [Access-Token]')

as for -H 'Content-Type: multipart/form-data' - don't add that header manually at all, curl will do it for you. (if you add it manually, you may mess up the boundary string, the full header looks something like Content-Type: multipart/form-data; boundary=------------------------82442bc797f0 )

-F audio-intro=@/path/to/myintro.wav \
-F audio-body=@/path/to/mybody.wav \
-F audio-outro=@/path/to/myoutro.wav \

translates to

CURLOPT_POSTFIELDS=>array(
"audio-intro"=>new CURLFile("/path/to/myintro.wav"),
"audio-body"=> new CURLFile("/path/to/mybody.wav"),
"audio-outro"=>new CURLFile("/path/to/myoutro.wav"),
)

but the next 1,

-F 'diffusion={
           "name":"diffusion vocale via API REST",
           "contactIds":["id_contact_1", "id_contact_2", ...],
           "mailingListIds":["id_mailing_list_1","id_mailing_list_2", ...],
           "excludedContactIds":[],
           "msisdns":["0612327745"],
           "landlines":["0522331155"],
           "voiceParam":{
              "locale": "fr_FR"
           }
        };type=application/json'

is problematic, php's curl_ api wrapper does not support adding headers to individual parameters of multipart/form-data requests, but if you're lucky, you can make due without the Content-Type header, so except that header, it translates to

/*...,*/
"diffusion"=>json_encode(array(
"name"=>"diffusion vocale via API REST",
"contactIds"=>array("id_contact_1", "id_contact_2", ...),
"mailingListIds"=>array("id_mailing_list_1","id_mailing_list_2", ...),
"excludedContactIds"=>array(),
"msisdns"=>array(0=>array("0612327745")),
"landlines"=>array("0522331155"),
"voiceParam"=>array("locale"=>"fr_FR")
)
));

so in short:

curl_setopt_array ( $ch, array (
        CURLOPT_URL => 'https://[SERVER_URL]/api/v1.2/groups/[id_group]/diffusion-requests',
        CURLOPT_HTTPHEADER => array (
                'Authorization: Bearer [Access-Token]' 
        ),
        CURLOPT_POSTFIELDS => array (
                "audio-intro" => new CURLFile ( "/path/to/myintro.wav" ),
                "audio-body" => new CURLFile ( "/path/to/mybody.wav" ),
                "audio-outro" => new CURLFile ( "/path/to/myoutro.wav" ),
                "diffusion" => json_encode ( array (
                        "name" => "diffusion vocale via API REST",
                        "contactIds" => array (
                                "id_contact_1",
                                "id_contact_2",
                                (...) 
                        ),
                        "mailingListIds" => array (
                                "id_mailing_list_1",
                                "id_mailing_list_2",
                                (...) 
                        ),
                        "excludedContactIds" => array (),
                        "msisdns" => array (
                                0 => array (
                                        "0612327745" 
                                ) 
                        ),
                        "landlines" => array (
                                "0522331155" 
                        ),
                        "voiceParam" => array (
                                "locale" => "fr_FR" 
                        ) 
                ) ) 
        ) 

) );

edit: if you absolutely must have the header, then you cannot use PHP's curl_ api's multipart/form-data generator, you must roll your own, see https://bugs.php.net/bug.php?id=76847 - here is a fairly untested example:

class CURLMultiPart {
    /** @var string[] $headers */
    public $headers;
    /** @var string $value */
    public $value;
    /**
     *
     * @param string $value
     * @param string[] $headers
     */
    function __construct(array $headers, string $value) {
        // todo: verify that all $headers are strings.
        $this->headers = $headers;
        $this->value = $value;
    }
}
/**
 *
 * @param curl_resource $ch
 * @param string[] $additional_headers
 * @param array $post_data
 * @throws \InvalidArgumentException
 */
function shitty_multipart_form_data_generator($ch, array $additional_headers = [], array $post_data) {
    $bon = '------------------------' . bin2hex ( random_bytes ( 8 ) );
    $global_header = 'Content-Type: multipart/form-data; boundary=' . $bon;
    $body = '';
    foreach ( $post_data as $post_name => $post_value ) {
        $body .= "$bon\r\n";
        if (is_string ( $post_value )) {
            $body .= "Content-Disposition: form-data; name=\"$post_name\"\r\n";
            $body .= "\r\n$post_value\r\n";
        } elseif (is_a ( $post_value, 'CURLMultiPart', false )) {
            /** @var CURLMultiPart $post_value */
            $has_content_disposition = false;
            foreach ( $post_value->headers as $header ) {
                if (0 === stripos ( $header, 'Content-Disposition' )) {
                    $has_content_disposition = true;
                    break;
                }
            }
            if (! $has_content_disposition) {
                $body .= "Content-Disposition: form-data; name=\"$post_name\"\r\n";
            }
            foreach ( $post_value->headers as $header ) {
                $body .= "$header\r\n";
            }
            $body .= "\r\n{$post_value->value}\r\n";
        } elseif (is_a ( $post_value, 'CURLFile' )) {
            /** @var CURLFile $post_value */
            // Content-Disposition: form-data; name="file"; filename="myPostName"
            // Content-Type: myMime
            $body .= "Content-Disposition: form-data; name=\"$post_name\"; filename=\"" . $post_value->getPostFilename () . "\"\r\n";
            $body .= "Content-Type: " . $post_value->getMimeType () . "\r\n\r\n";
            $body .= file_get_contents ( $post_value->getFilename () );
            $body .= "\r\n";
        } else {
            // error, invalid argument.
            ob_start ();
            var_dump ( [ 
                    $post_name => $post_value 
            ] );
            $debug = ob_get_clean ();
            throw new \InvalidArgumentException ( "every member of \$post_data must be either a string, CURLMultiPart, or CURLFile - but contains something else: " . $debug );
        }
        // unreachable
    }
    $body .= "{$bon}--\r\n";
    // var_dump ( $body );
    $additional_headers [] = $global_header;
    curl_setopt_array ( $ch, array (
            CURLOPT_POSTFIELDS => $body,
            CURLOPT_HTTPHEADER => $additional_headers 
    ) );
}

with this, your curl arguments would translate to, in short:

curl_setopt_array ( $ch, array (
        CURLOPT_URL => 'https://[SERVER_URL]/api/v1.2/groups/[id_group]/diffusion-requests',
        CURLOPT_POST => 1 
) );
shitty_multipart_form_data_generator ( $ch, array (
        'Authorization: Bearer [Access-Token]' 
), array (
        "audio-intro" => new CURLFile ( "/path/to/myintro.wav" ),
        "audio-body" => new CURLFile ( "/path/to/mybody.wav" ),
        "audio-outro" => new CURLFile ( "/path/to/myoutro.wav" ),
        "diffusion" => new CURLMultiPart ( array (
                'Content-Type: application/json' 
        ), json_encode ( array (
                "name" => "diffusion vocale via API REST",
                "contactIds" => array (
                        "id_contact_1",
                        "id_contact_2" 
                    // (...)
                ),
                "mailingListIds" => array (
                        "id_mailing_list_1",
                        "id_mailing_list_2" 
                    // (...)
                ),
                "excludedContactIds" => array (),
                "msisdns" => array (
                        0 => array (
                                "0612327745" 
                        ) 
                ),
                "landlines" => array (
                        "0522331155" 
                ),
                "voiceParam" => array (
                        "locale" => "fr_FR" 
                ) 
        ) ) ) 
) );
hanshenrik
  • 19,904
  • 4
  • 43
  • 89
  • Thanks for your response. But that does not work, I do not receive the call. :( The API error message is the following one (not usefull) : "{"logId":"180907W687754048","message":""}". – JeremDem Sep 07 '18 at 06:32
  • @JeremDem ok, the Content-Type header for the individual argument is required then. i didn't want to do his, but i *know* how to fix it, i just hoped i didn't have to.... check back in an hour – hanshenrik Sep 07 '18 at 07:57
  • Hi @hanshenrik and thanks for your help ! You function name is aptly named. ^^ With your updated code I get the following error message : [{"code":"NotNull","message":"A field is missing or blank, ensure that all mandatory fields are specified."}]. – JeremDem Sep 10 '18 at 07:53
  • @JeremDem redirect both the curl request and the php-libcurl request to http://dumpinput.ratma.net , and save both in 2 files, then run them both in a diff tool, what are their differences? – hanshenrik Sep 10 '18 at 09:36
  • Thank you @hanshenrik. By following your instructions, I obtain the following differences – JeremDem Sep 11 '18 at 12:52
  • I write my response in a new response for formatting. – JeremDem Sep 11 '18 at 13:02
0

Thank you @hanshenrik. By following your instructions, I obtain the following differences :

- header difference (content type) :
¤ curl = application/json ;
¤ php-curl = multipart/form-data

- content difference :
¤ curl = Content-Disposition: form-data; name="audio-body"; filename="Test.wav"
¤ php-curl = Content-Disposition: form-data; name="audio-body"; filename=""

- content type :
¤ curl = Content-Type: application/octet-stream
¤ php-curl = Content-Type:

- there is the same problem for 'audio-outro' attribute : no file and no content type.

- and at the end, the msisdns value are not of the same type (easy to correct, already tried) :
¤ curl = "msisdns":["0612327745"]
¤ php-curl = "msisdns":[["0612345678"]]

If I correct the msisdns to be a one dim array, then I get the following error by calling the API (the files are missing): [{"code":"NotNull","message":"A field is missing or blank, ensure that all mandatory fields are specified."}]

The problem seems to come from the way files are added. Any suggestion ?

[EDIT] @hanshenrik : I used the following PHP code to get these results :

function sendCallFromFile($URL, $token, $group_id, $file_path, $my_mobile_phone_number)
{
$ch = curl_init();

    curl_setopt_array ( $ch, array (
            CURLOPT_URL => 'https://'.$URL.'/api/v1.2/groups/'.$group_id.'/diffusion-requests',
            CURLOPT_POST => 1 
    ) );

    echo (file_exists($file_path)) ? "The file exists.\n" : "ERROR : The file does not exist !!!\n"; // print "The file exists"

    shitty_multipart_form_data_generator ( $ch, array (
            'Authorization: Bearer '.$token 
    ), array (
            "audio-intro" => new CURLFile ( $file_path ),
            "audio-body" => new CURLFile ( $file_path ),
            "audio-outro" => new CURLFile ( $file_path ),
            "diffusion" => new CURLMultiPart ( array (
                    'Content-Type: application/json' 
            ), json_encode ( array (
                    "name" => "diffusion vocale via API REST",
                    "contactIds" => array (                 ),
                    "mailingListIds" => array (),
                    "excludedContactIds" => array (),
                    "msisdns" => array (
                                $my_mobile_phone_number
                    ),
                    "landlines" => array (/*"0412345678"*/),
                    "voiceParam" => array (
                            "locale" => "fr_FR" 
                    ) 
            ) ) ) 
    ) );


    $result = curl_exec($ch);// Retourne un résultat de la forme suivante : 

    curl_close($ch);

    return $result;
}


class CURLMultiPart {
    /** @var string[] $headers */
    public $headers;
    /** @var string $value */
    public $value;
    /**
     *
     * @param string $value
     * @param string[] $headers
     */
    function __construct(array $headers, string $value) {
        // todo: verify that all $headers are strings.
        $this->headers = $headers;
        $this->value = $value;
    }
}
/**
 *
 * @param curl_resource $ch
 * @param string[] $additional_headers
 * @param array $post_data
 * @throws \InvalidArgumentException
 */
function shitty_multipart_form_data_generator($ch, array $additional_headers = [], array $post_data) {
    $bon = '------------------------' . bin2hex ( random_bytes ( 8 ) );
    $global_header = 'Content-Type: multipart/form-data; boundary=' . $bon;
    $body = '';
    foreach ( $post_data as $post_name => $post_value ) {
        $body .= "$bon\r\n";
        if (is_string ( $post_value )) {
            $body .= "Content-Disposition: form-data; name=\"$post_name\"\r\n";
            $body .= "\r\n$post_value\r\n";
        } elseif (is_a ( $post_value, 'CURLMultiPart', false )) {
            /** @var CURLMultiPart $post_value */
            $has_content_disposition = false;
            foreach ( $post_value->headers as $header ) {
                if (0 === stripos ( $header, 'Content-Disposition' )) {
                    $has_content_disposition = true;
                    break;
                }
            }
            if (! $has_content_disposition) {
                $body .= "Content-Disposition: form-data; name=\"$post_name\"\r\n";
            }
            foreach ( $post_value->headers as $header ) {
                $body .= "$header\r\n";
            }
            $body .= "\r\n{$post_value->value}\r\n";
        } elseif (is_a ( $post_value, 'CURLFile' )) {
            /** @var CURLFile $post_value */
            // Content-Disposition: form-data; name="file"; filename="myPostName"
            // Content-Type: myMime
            $body .= "Content-Disposition: form-data; name=\"$post_name\"; filename=\"" . $post_value->getPostFilename () . "\"\r\n";
            $body .= "Content-Type: " . $post_value->getMimeType () . "\r\n\r\n";
            $body .= file_get_contents ( $post_value->getFilename () );
            $body .= "\r\n";
        } else {
            // error, invalid argument.
            ob_start ();
            var_dump ( [ 
                    $post_name => $post_value 
            ] );
            $debug = ob_get_clean ();
            throw new \InvalidArgumentException ( "every member of \$post_data must be either a string, CURLMultiPart, or CURLFile - but contains something else: " . $debug );
        }
        // unreachable
    }
    $body .= "{$bon}--\r\n";
    // var_dump ( $body );
    $additional_headers [] = $global_header;
    curl_setopt_array ( $ch, array (
            CURLOPT_POSTFIELDS => $body,
            CURLOPT_HTTPHEADER => $additional_headers 
    ) );
}
JeremDem
  • 51
  • 1
  • 11
  • the first difference you noticed here suggests that you tried to put `multipart/form-data` as a global header in php using CURLOPT_HTTPHEADER - show me what code you used in php – hanshenrik Sep 15 '18 at 20:14
  • the second difference suggests that either there is a bug in my CURLFile parser, or you gave an empty string to the `$postname` argument of CURLFile::__construct – hanshenrik Sep 15 '18 at 20:16
  • the third difference (msisdns) suggests the code should be `"msisdns"=>array("0612327745")` instead of `"msisdns"=>array(0=>array("0612327745"))` – hanshenrik Sep 15 '18 at 20:18