I'm new developer, I need to connect XML API Hotel, In the documentation is mentioned about Startup guide, first we need to add customer login and user, there're two steps at first, but I don't know how to use them, and how to connect Main XML URL into PHP using the password login info like below:
General Request / Request Format:
<customer>
<username>username</username>
<password>md5(password)</password>
<id>company code</id>
<source>1</source>
<request command="getallcities">
<return>
<filters>
<countryCode></countryCode>
<countryName></countryName>
</filters>
<fields>
<field>countryName</field>
<field>countryCode</field>
</fields>
</return>
</request>
</customer>
General Response / Response Format:
<result command=" " date="" ip="">
<request>
<error>
<class></class>
<code>error code</code>
<details></details>
<extraDetails></extraDetails>
<postXml></postXml>
</error>
<successful>FALSE</successful>
</request>
</result>
General Request XSD
getallcities.xsd
I highly appreciate of someone will guide me how to use them and how to connect PHP into Main XML Url.
I highly appreciate your help in this issue.
Thanks in advance
Okay, so, you didn't give very much to work with, but I'm going to try and help you solve this.
First off, let's use a PHP package to simplify converting an array into valid XML. It will save you time down the road. I recommend Spatie's Array to Xml package.
If you're familiar with composer, go ahead and install that. If you've never used composer, go ahead and download src/ArrayToXml.php and put it in your project. (I highly recommend you use composer if you don't, I'm just providing some extra instructions since I don't know your level of experience.)
Now, I'm going to assume you got the Array To Xml package set up. Let's go ahead and structure an array before we convert it to xml.
// Structure the payload as an array and pass in any data you need dynamically
$payload = [
'username' => 'username',
'password' => 'md5(password)',
'id' => 'company code',
'source' => '1',
'request' => [
'return' => [
'filters' => [
'countryCode' => [],
'countryName' => [],
],
'fields' => [
'field' => [
'countryName',
'countryCode',
],
],
],
'#attributes' => [
'command' => 'getallcities',
],
],
];
// Convert the array to xml with a root of 'customer'
$xml = \Spatie\ArrayToXml\ArrayToXml::convert($payload, 'customer');
From there, we're going to initiate a curl call to the endpoint with your payload.
// Set the url you're making an api call to
$endpoint = 'https://yoursite.com/your-endpoint';
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => $endpoint,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => $xml,
CURLOPT_HTTPHEADER => [
"Content-Type: application/xml"
]
]);
$response = curl_exec($curl);
$error = curl_error($curl);
curl_close($curl);
if ($error) {
echo "cURL Error #:" . $error;
} else {
echo $response;
}
Now you'll want to parse the response xml back into an array so you can process it. I created a package to help you do this. You can find it here: https://github.com/mtownsend5512/xml-to-array
The basic premise of installing it is the same. You'll do $response = \Mtownsend\XmlToArray\XmlToArray::convert($response);
See how that works.
Related
I am having a little trouble with the SOAP API I need to consume. It requires authorization, so I log in with the following.
Basically, I need to log in with the following, I don't get a "Login Required", which is fine.
$client = new \SoapClient('localwsdlfile.wsdl', array(
'local_cert' => 'localcert.pem',
'passphrase' => 'passphrase',
'location' => 'https://wsmurl/login/'
));
$response = $client->Get(array(
"AccessKey" => "accesskey",
"ProductID" => "SOMEPRODUCT",
"Scope" => "SOMESCOPE",
"Parameters" => array('Param' => array('_' => 'DATATOLOOKUP', 'id'=>'MOREDATATOLOOKUP'))
));
print_r($response);
So, basically, in the client, I need to uncomment the line
'location' => 'https://wsmurl/login/'
If I don't, I get this login error. The server I am trying to consume has a cache, so I no longer need to send this login for future operations for the next 10 minutes.
So, now, I have logged in, I can go the following, with success.
$client = new \SoapClient('localwsdlfile.wsdl', array(
'local_cert' => 'localcert.pem',
'passphrase' => 'passphrase',
// 'location' => 'https://wsmurl/login/' // Uncomment to login
));
$response = $client->Get(array(
"AccessKey" => "accesskey",
"ProductID" => "SOMEPRODUCT",
"Scope" => "SOMESCOPE",
"Parameters" => array('Param' => array('_' => 'DATATOLOOKUP', 'id'=>'MOREDATATOLOOKUP'))
));
print_r($response);
If I keep 'location' => 'https://wsmurl/login/' uncommented, I get errors about not sending a valid WSDL.
So in summary:-
I am consuming this soap API in Laravel, using PHP's SoapClinet per the above code.
My understanding is that the soap service I am trying to consume required login first.
After quite a lot of testing, I managed to get to the point there were no errors by including the line 'location' => 'https://wsmurl/login/' as shown above in the call to SoapClient.
This seems to be required the first time I call this soap service I guess it is logging me in. If I don't include it, I get the error
"SoapFault
Login Required"
So, if I include the line 'location' => 'https://wsmurl/login/', it appears to log me in, but I now get the error
"SoapFault
looks like we got no XML document"
If I keep the line, I continue to get this error
"SoapFault
looks like we got no XML document"
Removing the line then gives me the expected SOAP response and all is ok.
I have tried catching for errors using try{} ... catch{} looking for exceptions/errors etc, but it isn't catching the "Login Required" or "Looks like we got no XML document" as faults.
So, I guess, is there a better way to detect this condition so I can either send or not without the line or is there a better way to login to the service (note, it just uses the cert and passphrase).
Thanks in advance, I hope the little re-write above makes it clearer.
Because the location for the login and localwsdlfile.wsdl is different, I could not do it with one call. So we created a function using curl to login and if login is successful it will proceed to the soap client. Thanks to Brian from freelancer for his assistance here.
$client = new SoapClient('wsdl/VocusSchemas/localwsdlfile.wsdl', array(
'trace' => 1,
'exception' => true
));
try {
$response = $client->Get(array(
// "AccessKey" => "MADAITABNSAATOT1",
"AccessKey" => "accesskey",
"ProductID" => "SOMEPRODUCT",
"Scope" => "SOMESCOPE",
"Parameters" => array('Param' => array('_' => 'DATATOLOOKUP', 'id' => 'MOREDATATOLOOKUP'))
));
SOLUTION: I had malformed my JSON data for the payload body. The "ttl" => 30 was in the incorrect array() method. This probably won't help anyone in the future, moving the ttl key/value pair made this work correctly as seen below.
$data = array(
"statement" => array(
"actor" => array(
"mbox" => "mailto:test#example.com"
),
),
"ttl" => 30
);
I have checked numerous other StackOverflow questions and cannot find a solution that works. I should note that I am testing this using a local XAMPP server running on port 8080. Not sure if that matters. I have been able to get this working using Postman, but translating it to PHP has been problematic. Am I missing something? I am not all that familiar with PHP, but need this for work.
EDIT: Some more information about what the API is expecting. It's a fairly simple API that requires a JSON body, a Basic Authorization header, and a Content-Type: application/json.
Here is the JSON body I am using in Postman. This is a direct copy/paste from Postman, which is successfully communicating with the API:
{
"statement": {
"actor": {
"mbox": "mailto:test#example.com"
}
},
"ttl": 30
}
Is there a syntax error in my below PHP code for this? Again, I am learning PHP on the fly so I'm unsure if I am properly constructing a JSON payload using the array() method in PHP.
My code below has the $https_user,$https_password, and $url domain changed for obvious security reasons. In my actual PHP code, I have the same credentials and domain used in Postman.
The $randomSessionID serves no real purpose other than an identification number for future requests. Has no affect on the API response failing or succeeding.
<?php
$https_user = 'username';
$https_password = 'password';
$randomSessionID = floor((mt_rand() / mt_getrandmax()) * 10000000);
$url = 'https://www.example.com/session/' . $randomSessionID . '/launch';
$json = json_encode(array(
"statement" => array(
"actor" => array(
"mbox" => "mailto:test#example.com"
),"ttl" => 30
)
));
$options = array(
'http' => array(
'method' => 'POST',
'header' => 'Content-Type: application/json\r\n'.
"Authorization: Basic ".base64_encode("$https_user:$https_password")."\r\n",
'content' => $json
)
);
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
if ($result === FALSE) { /* Handle error */ }
?>
SOLUTION: I had malformed my JSON data for the payload body. The "ttl" => 30 was in the incorrect array() method. This probably won't help anyone in the future, but moving the ttl key/value pair made this work correctly as seen below.
$data = array(
"statement" => array(
"actor" => array(
"mbox" => "mailto:test#example.com"
),
),
"ttl" => 30
);
I'm trying to import a TMX file via the Microsoft Custom Translator API in PHP. Unfortunately, I keep running into the following error:
"DocumentDetails must follow type IEnumerable[ImportDocumentRequestDetails]."
I've managed to make other (though only GET) requests to the API successfully, so it's specifically this request I'm having trouble figuring out.
So far, I've tried various permutations of the request, mostly by trial and error. I've tried replicating the request by uploading the same file in the portal, which succeeds without problems, but I've not been able to replicate this in PHP (7.3).
I've also tried to reverse-engineer the C# API samples on GitHub. Unfortunately, my C# knowledge isn't that sharp and I'm sure there are nuances I'm missing. I have noticed the sample uses a 'Language' string, whereas the portal seems to use a 'LanguageCode', as well as other inconsistencies which haven't made solving this much easier.
A stripped-down version of my code, with only the relevant parts (one can assume a valid access token and local filepath to a valid .tmx) is the following:
Class CustomTranslator {
private $curl;
private $aAccessToken; // valid, working token
// Set up connection and login with initial user
public function __construct() {
$this->curl = curl_init();
$aOptions = array(
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 60
);
curl_setopt_array($this->curl, $aOptions);
}
function ImportParallelDocument($strFilePath) {
$aRequestContent = [
'Files' => new CURLFile($strFilePath, mime_content_type($strFilePath), basename($strFilePath)),
'DocumentDetails' => [
'DocumentName' => basename($strFilePath),
'DocumentType' => 'training',
'IsParallel' => true,
'FileDetails' => [
'Name' => $strFilePath,
'Language' => 'Dutch',
'Type' => pathinfo($strFilePath, PATHINFO_EXTENSION),
'OverwriteIfExists' => true
]
]
];
return $this->Request("v1.0/documents/import?workspaceId=".CUSTOMTRANSLATOR_WORKSPACEID, $aRequestContent, 'POST');
}
// Prototype request function
private function Request($strRequest, $aData = array(), $strMethod = 'GET') {
$strRequest = CUSTOMTRANSLATOR_API_URL.$strRequest;
// Reset between requests
curl_setopt($this->curl, CURLOPT_POST, false);
curl_setopt($this->curl, CURLOPT_HTTPHEADER, ['Authorization: Bearer '.$this->aAccessToken['access_token']]);
if(isset($aData['authorization'])) $aData['authorization'] = $this->aAccessToken['access_token'];
if ($strMethod == 'GET') {
$strRequest .= "?".http_build_query($aData);
}
else {
curl_setopt($this->curl, CURLOPT_POST, true);
curl_setopt($this->curl, CURLOPT_HTTPHEADER, ['Authorization: Bearer '.$this->aAccessToken['access_token'],
'X-HTTP-Method-Override: '.$strMethod]);
curl_setopt($this->curl, CURLOPT_POSTFIELDS, $aData);
}
curl_setopt($this->curl, CURLOPT_URL, $strRequest);
$strResponse = curl_exec($this->curl);
// Return the JSON array if it can be decoded, otherwise the actual curl response
return json_decode($strResponse, true)?:$strResponse;
}
}
As stated, when I try to upload a file using the above code, the exact error I'm receiving is {"message":"DocumentDetails must follow type IEnumerable[ImportDocumentRequestDetails].","display":false}, unfortunately without further specification of what's missing or incorrect. I'm hoping to achieve a successful file import of a TMX file which does successfully import via the portal itself, which I understand implements the same API.
I expect I'm simply missing something, or doing something not quite right, so any help would be appreciated!
With help from a colleague, figured out a will-do-for-now workaround, by simply providing the equivalent JSON of the call as it's used in the portal:
$aRequestContent = [
'Files' => new CURLFile($strFilePath, mime_content_type($strFilePath), basename($strFilePath)),
'DocumentDetails' =>
'[{ "DocumentName": "",
"DocumentType": "training",
"FileDetails": [{
"Name": "'.basename($strFilePath).'",
"LanguageCode": "en",
"OverwriteIfExists": true }]
}]'
];
It's not the best solution, but for the purposes of a preview API, it'll work for now (until it inevitably ends up in production, sigh).
Examining the JSON a little closer led me to discover the nested array structure (presumably for multi-file uploads), which wasn't inherently obvious to me at first. However, the following array structure is sufficient for requests to be processed:
$aDocumentDetails = [[ // Note the nested array here, for index numbering
'DocumentName' => '',
'DocumentType' => ucfirst($strType),
'FileDetails' => [[ // As well as here
'Name' => basename($strFilePath),
'LanguageCode' => $strLanguageCode,
'OverwriteIfExists' => $bOverwrite
]]
]];
$aRequestContent = [
'Files' => new CURLFile($strFilePath, mime_content_type($strFilePath), basename($strFilePath)),
'DocumentDetails' => json_encode($aDocumentDetails)
];
In short, the API expects DocumentDetails (and FileDetails) in indexed sub-arrays:
[0 => [ 'DocumentName' => ...,
'FileDetails' => [0 => ['Name' => ...]
];
Understanding this tidbit helped me tremendously.
I wrote a simple PHP script that retrieves an item from eBay. I'm using PHP and SOAP. I can get the items with other calls just fine. However, I need to use the findItemsAdvanced call to get items from a seller, but I haven't been able to make it work.
Here's what I have so far:
function call_ebay_id_api($itemID){
$location = 'http://svcs.ebay.com/services/search/FindingService/v1?SECURITY-APPNAME=****8&OPERATION-NAME=findItemsAdvanced&itemFilter(0).name=Seller&itemFilter(0).value=****';
$clientebay = new SoapClient(
"http://developer.ebay.com/webservices/Finding/latest/FindingService.wsdl",
array(
"exceptions" => 0,
"trace" => 1,
"location"=> $location
)
);
$requestebay = array("itemFilter" => array("name" => "Seller", "value" => "****"));
$xmlresultsebay = $clientebay->__soapCall('findItemsAdvanced', array($requestebay));
echo "REQUEST HEADERS:\n<pre>" . htmlentities($clientebay->__getLastRequestHeaders()) . "</pre>\n";
echo "REQUEST:\n<pre>" . htmlentities($clientebay->__getLastRequest()) . "</pre>\n";
echo "RESPONSE:\n<pre>" . htmlentities($clientebay->__getLastResponse()) . "</pre>\n";
echo 'd';
return $xmlresultsebay;
and the XML request from this is:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://www.ebay.com/marketplace/search/v1/services">
<SOAP-ENV:Body>
<ns1:findItemsAdvancedRequest>
<itemFilter>
<name>Seller</name>
<value>*seller name here*</value>
</itemFilter>
</ns1:findItemsAdvancedRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
which looks like the example they have on the API's page
However, the response that I got back is an error. It says I need to specify keywords or category ID. When I do that, I get success, but the itemFilter isn't applied.
Can anyone help me here?
Mkay so I got it working...I guess?
So I thought the problem was that eBay's itemFilter requires an ItemFilter type (notice the capital I in the ItemFilter, btw), instead of an array. This led me to create classes with the same names as the types required for the request call. So I created objects called ItemFilter, Name, Value, etc. However, it still didn't work.
I almost gave up and start over maybe with cURL or something...or even Cajun voodoo since nothing else worked, so hey, why not magic?
After I went to the cemetery of Madam Marie Laveau with a piece of alligator skin and some boudin links to put on her grave, I got really hungry and started thinking about the boudin. "Hmm...delicious rice and pig offal inside the pig intestine wrapper...hold on," I thought to myself "wrapper? I should check if the variable is wrapped correctly." So after I got home (with some more boudin from the local market), I checked the wrapper and it was correct. But then I noticed that the namespace for the itemFilter was missing. So I started messing around with PHP SoapVar...and it worked! "Finally, a dim spark in this god forsaken web-dev abyss," so I thought.
So basically instead of:
$requestebay = array("itemFilter" => array("name" => "Seller", "value" => "****"));
I should've put:
$itemFilters = array(
"name" => new SoapVar("Seller", XSD_STRING, NULL, NULL, NULL, 'http://www.ebay.com/marketplace/search/v1/services'),
"value" => new SoapVar("****", XSD_STRING, NULL, NULL, NULL, 'http://www.ebay.com/marketplace/search/v1/services')
);
$requestebay = array(
"itemFilter" => new SoapVar($itemFilters, SOAP_ENC_OBJECT, NULL, NULL, NULL, 'http://www.ebay.com/marketplace/search/v1/services')
);
...and BAM! that solved my problem. I guess the lesson to be learned here is that voodoo is a whole lot better than eBay's documentation, since with voodoo I don't have to dig very deep to find things for my next project.
Instead of building a Soapvar for every variable
create a subclass of \SoapClient
namespace whatever;
class EbaySoapClient extends \SoapClient{
public function __doRequest($request, $location, $action, $version, $one_way = 0) {
$request = str_replace(['ns1:', ':ns1'], '', $request);
$doreq = parent::__doRequest($request, $location, $action, $version, $one_way);
$this->__last_request = $request;
return $doreq;
}
}
example call:
$wsdl = "http://developer.ebay.com/webservices/Finding/latest/FindingService.wsdl";
$endpointprod = "http://svcs.ebay.com/services/search/FindingService/v1";
$endpointbox ="http://svcs.sandbox.ebay.com/services/search/FindingService/v1";
$sandboxApi = "YourSandboxApiKey";
$prodApi = "YourProdApiKey";
$headers = stream_context_create(['http' =>
[
'method' => 'POST',
'header' => implode(PHP_EOL, [
'X-EBAY-SOA-SERVICE-NAME: FindingService',
'X-EBAY-SOA-OPERATION-NAME: findCompletedItems',
'X-EBAY-SOA-SERVICE-VERSION: 1.0.0',
'X-EBAY-SOA-GLOBAL-ID: EBAY-US',
'X-EBAY-SOA-SECURITY-APPNAME: ' . $sandboxApi,
'X-EBAY-SOA-REQUEST-DATA-FORMAT: XML',
'X-EBAY-SOA-MESSAGE-PROTOCOL: SOAP12',
'Content-Type: text/xml;charset=utf-8',])
]
]
);
$options = [
'trace' => true,
'cache' => WSDL_CACHE_NONE,
'exceptions' => true,
'soap_version' => SOAP_1_2,
'location' => $endpointbox,
'stream_context' => $headers
];
$soap = new \whatever\EbaySoapClient($wsdl, $options);
$array = [
'keywords' => 'clock',
'itemFilter' => [
['name' => 'Condition', 'value' => 3000],
['name' => 'FreeShippingOnly', 'value' => 1]
]
];
$response = $soap->findCompletedItems($array);
echo $soap->__getLastRequest();
var_dump($response);
not the most eloquent but works for me, you can scrap the namespace if you want just put it up there for illustrative purposes. It sets the default namespace.
Sorry, I can only post 2 hyperlinks so I'm going to have to remove the http : //
Background
I'm, trying to convert the code here: https://github.com/RusticiSoftware/TinCan_Prototypes/blob/92969623efebe2588fdbf723dd9f33165694970c/ClientPrototypes/StatementIssuer/StatementIssuer.java
into PHP, specifically the makeRequest function. This code posts data to a Tin Can Compliant Learner Record Store.
The current version of my PHP code is here:
tincanapi.co.uk/wiki/tincanapi.co.uk:MediaWikiTinCan
The specification for the Tin Can API which everything should conform to is here:
scorm.com/wp-content/assets/tincandocs/TinCanAPI.pdf
There is also a working java script function that Posts data in the right format here (see the XHR_request function I think):
https://github.com/RusticiSoftware/TinCan_Prototypes/blob/92969623efebe2588fdbf723dd9f33165694970c/ClientPrototypes/GolfExample_TCAPI/scripts/TCDriver.js
I don't have access to the code or server that I'm posting to, but the end result should be an output here: beta.projecttincan.com/ClientPrototypes/ReportSample/index.html
Problem
I'm trying to use Curl to POST the data as JSON in PHP. Curl is returning 'false' but no error and is not posting the data.
On the recommendation of other questions on this site, I've tried adding 'json=' to the start of the POSTFIELDS, but since the Java and JavaScript versions does have this, I'm not sure this is right.
Can anybody suggest either how I might fix this or how I might get useful errors out of curl? My backup is to output the relevant JavaScript to the user's browser, but surely PHP should be able to do this server side?
Very grateful for any help.
Andrew
At least one thing is wrong: you should not be using rawurlencode on your Authorization header value.
Consider using php streams and json_encode() and json_decode() instead. The following code works.
function fopen_request_json($data, $url)
{
$streamopt = array(
'ssl' => array(
'verify-peer' => false,
),
'http' => array(
'method' => 'POST',
'ignore_errors' => true,
'header' => array(
'Authorization: Basic VGVzdFVzZXI6cGFzc3dvcmQ=',
'Content-Type: application/json',
'Accept: application/json, */*; q=0.01',
),
'content' => json_encode($data),
),
);
$context = stream_context_create($streamopt);
$stream = fopen($url, 'rb', false, $context);
$ret = stream_get_contents($stream);
$meta = stream_get_meta_data($stream);
if ($ret) {
$ret = json_decode($ret);
}
return array($ret, $meta);
}
function make_request()
{
$url = 'https://cloud.scorm.com/ScormEngineInterface/TCAPI/public/statements';
$statements = array(
array(
'actor' => array(
'name' => array('Example Name'),
'mbox' => array('mailto:example#example.com'),
'objectType' => 'Person',
),
'verb' => 'experienced',
'object' => array(
'objectType' => 'Activity',
'id'=> 'http://www.thincanapi.co.uk/wiki/index.php?Main_Page',
'definition' => array(
'name' => array('en-US'=>'TinCanAPI.co.uk-tincanapi.co.uk'),
'description' => array('en-US'=> 'TinCanAPI.co.uk-tincanapi.co.uk'),
),
),
),
);
return fopen_request_json($statements, $url);
}
list($resp, $meta) = make_request();
var_export($resp); // Returned headers, including errors, are in $meta
We've now released an open source library specifically for PHP, it uses a similar method as the accepted answer but rounds out the rest of the library as well. See:
http://rusticisoftware.github.io/TinCanPHP/
https://github.com/RusticiSoftware/TinCanPHP