Microsoft Translate Text Stopped Working - PHP - php

I've been using this library to translate text using Microsoft cognitive translator.
PHP Microsoft Translate package
I have an Azure account and I think my endpoints and keys are still valid. I had not changed anything in the interim, although I have the "free" package.
The code I had actually translate multiple languages and worked fine before. For some reason it STOPPED working in the recently. I edited the post to set:
$host = "https://api.cognitive.microsofttranslator.com", thank you (typo).
It actually does work if I set $text to something simple like:
$text = 'Guten Morgen! Mit Corona zusammenraufen.Milde gesagt: Es sind keine besonders beruhigenden Nachrichten.';
returns English:
{"text":"Good morning! To put it mildly, it's not particularly reassuring news.","to":"en"}
If I slightly lengthen the string to:
$text = 'Guten Morgen! Mit Corona zusammenraufen. Milde gesagt: Es sind keine besonders beruhigenden Nachrichten. Milde gesagt: Es sind keine besonders beruhigenden Nachrichten.';
I get a:
failed to open stream: HTTP request failed! HTTP/1.1 400 Bad Request
This worked previously with much longer strings for input. I have a "free" plan with Azure. Is it possible that there is now a limit to string length with my Azure account ?
The code that I have is. There is some extra stuff for putting the translations into a database.
$key = 'MicrosoftKEY';
$host = "https://api.cognitive.microsofttranslator.com";
$path = "/translate?api-version=3.0";
$languages = $data['languagearray']; // array of languages to translate to.
$params = '&from=' . $data["from"] ; // language to translate from
foreach ($languages as $language) {
$params .= "&to=" . $language;
}
$params .= "&textType=html";
$text = $data["text"]; // text to translate
if (!function_exists('com_create_guid')) {
function com_create_guid() {
return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
mt_rand( 0, 0xffff ),
mt_rand( 0, 0x0fff ) | 0x4000,
mt_rand( 0, 0x3fff ) | 0x8000,
mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
);
}
}
// function to return the translated text, seem below, $json = jsonp_decode($result, true)[0]["translations"];
function Translate ($host, $path, $key, $params, $content) {
$headers = "Content-type: application/json\r\n" .
"Content-length: " . strlen($content) . "\r\n" .
"Ocp-Apim-Subscription-Key: $key\r\n" .
"X-ClientTraceId: " . com_create_guid() . "\r\n";
// NOTE: Use the key 'http' even if you are making an HTTPS request. See:
// http://php.net/manual/en/function.stream-context-create.php
$options = array (
'http' => array (
'header' => $headers,
'method' => 'POST',
'content' => $content
)
);
$context = stream_context_create ($options);
$result = file_get_contents ($host . $path . $params, false, $context);
echo $result;
return $result;
}
$requestBody = array (
array (
'Text' => $text,
),
);
function jsonp_decode($jsonp, $assoc = false) { // PHP 5.3 adds depth as third parameter to json_decode
if($jsonp[0] !== '[' && $jsonp[0] !== '{') { // we have JSONP
$jsonp = substr($jsonp, strpos($jsonp, '('));
}
return json_decode(trim($jsonp,'();'), $assoc);
}
$content = json_encode($requestBody);
$result = Translate ($host, $path, $key, $params, $content);
// Note: We convert result, which is JSON, to and from an object so we can pretty-print it.
// We want to avoid escaping any Unicode characters that result contains. See:
// http://php.net/manual/en/function.json-encode.php
$json = jsonp_decode($result, true)[0]["translations"];
$conn = DatabaseFactory::getFactory()->getConnection();
foreach ($json as $language) {
$query = 'UPDATE kronen_translations SET translated_text = ? WHERE language_code = ?';
$parameters = [$language['text'], $language['to']];
$stmt = $conn->prepare($query);
$stmt->execute($parameters);
echo $language['to'] . '<br>';
echo $language['text'] . '<br>';
}

I did a little bit more investigation and eventually opened up a ticket with Microsoft Cognitive Services. The script as it is / was was working fine a few months ago, and I was actually translating a couple of paragraphs into about 40 different languages (almost all that they support) using a bundled call single call and I would get a response back having all of the translations. This was working fine before.
However, there is a character limits for API requests for the Translation Services:
See: Request limits for Translator Text
"Each translate request is limited to 5,000 characters, across all the target languages you are translating to. For example, sending a translate request of 1,500 characters to translate to 3 different languages results in a request size of 1,500x3 = 4,500 characters, which satisfies the request limit. You're charged per character, not by the number of requests. It's recommended to send shorter requests."
With 40 different languages and a couple of paragraphs having a few hundred characters I was over the limit. After trading e-mails with MS support it turns out that the API request limit was not being enforced for part of last year and recently due to a bug on their end, and they fixed it recently apparently.
Just for reference, the old code was something like the following, with the query string having about 40 or so &to=lang& . . . .
$languages = $data['languagearray'];
$params = '&from=' . $data["from"] ;
foreach ($languages as $language) {
$params .= "&to=" . $language;
}
$params .= "&textType=html";
$text = $data["text"];
if (!function_exists('com_create_guid')) {
function com_create_guid() {
return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
mt_rand( 0, 0xffff ),
mt_rand( 0, 0x0fff ) | 0x4000,
mt_rand( 0, 0x3fff ) | 0x8000,
mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
);
}
}
function Translate ($host, $path, $key, $params, $content) {
$headers = "Content-type: application/json\r\n" .
"Content-length: " . strlen($content) . "\r\n" .
"Ocp-Apim-Subscription-Key: $key\r\n" .
"X-ClientTraceId: " . com_create_guid() . "\r\n";
// NOTE: Use the key 'http' even if you are making an HTTPS request. See:
// http://php.net/manual/en/function.stream-context-create.php
$options = array (
'http' => array (
'header' => $headers,
'method' => 'POST',
'content' => $content
)
);
$context = stream_context_create ($options);
$result = file_get_contents ($host . $path . $params, false, $context);
echo $result;
return $result;
}
$requestBody = array (
array (
'Text' => $text,
),
);
function jsonp_decode($jsonp, $assoc = false) { // PHP 5.3 adds depth as third parameter to json_decode
if($jsonp[0] !== '[' && $jsonp[0] !== '{') { // we have JSONP
$jsonp = substr($jsonp, strpos($jsonp, '('));
}
return json_decode(trim($jsonp,'();'), $assoc);
}
$content = json_encode($requestBody);
$result = Translate ($host, $path, $key, $params, $content);
// Note: We convert result, which is JSON, to and from an object so we can pretty-print it.
// We want to avoid escaping any Unicode characters that result contains. See:
// http://php.net/manual/en/function.json-encode.php
$json = jsonp_decode($result, true)[0]["translations"];
$conn = DatabaseFactory::getFactory()->getConnection();
foreach ($json as $language) {
$query = 'UPDATE kronen_translations SET translated_text = ? WHERE language_code = ?';
$parameters = [$language['text'], $language['to']];
$stmt = $conn->prepare($query);
$stmt->execute($parameters);
echo $language['to'] . '<br>';
echo $language['text'] . '<br>';
}
You are allowed to make bundled calls like that, and then decode the results that you get back.
The solution was to make a series of calls (one at a time in this case), one for each language and then process the results in series. The new code is something like this. There might be a better way, but at least this way I can process reasonably sized chunks of text. It just takes a bit of time to run the script for 40 or so languages, although still only my 30 seconds, depending upon the size of the text.
function Translate ($host, $path, $key, $params, $content) {
$headers = "Content-type: application/json\r\n" .
"Content-length: " . strlen($content) . "\r\n" .
"Ocp-Apim-Subscription-Key: $key\r\n" .
"X-ClientTraceId: " . com_create_guid() . "\r\n";
// NOTE: Use the key 'http' even if you are making an HTTPS request. See:
// http://php.net/manual/en/function.stream-context-create.php
$options = array (
'http' => array (
'header' => $headers,
'method' => 'POST',
'content' => $content
)
);
$context = stream_context_create ($options);
$result = file_get_contents ($host . $path . $params, false, $context);
echo $result;
return $result;
}
function jsonp_decode($jsonp, $assoc = false) { // PHP 5.3 adds depth as third parameter to json_decode
if($jsonp[0] !== '[' && $jsonp[0] !== '{') { // we have JSONP
$jsonp = substr($jsonp, strpos($jsonp, '('));
}
return json_decode(trim($jsonp,'();'), $assoc);
}
$key = 'xxxxx';
$host = "https://api.cognitive.microsofttranslator.com";
$path = "/translate?api-version=3.0";
$languages = $data['languagearray'];
// print_r($languages);
$conn = DatabaseFactory::getFactory()->getConnection();
$text = $data["text"];
foreach ($languages as $language) {
$params = '&from=' . $data["from"] ;
$params .= "&to=" . $language;
$params .= "&textType=html";
$text = $data["text"];
$requestBody = array (
array (
'Text' => $text,
),
);
$content = json_encode($requestBody);
$result = Translate ($host, $path, $key, $params, $content);
$json = jsonp_decode($result, true)[0]["translations"];
foreach ($json as $language) {
$query = 'UPDATE kronen_translations SET translated_text = ? WHERE language_code = ?';
$parameters = [$language['text'], $language['to']];
$stmt = $conn->prepare($query);
$stmt->execute($parameters);
echo $language['to'] . '<br>';
echo $language['text'] . '<br>';
}

Related

PHP sentiment score of descriptions from CSV file

I am trying to get sentiment scores of random product descriptions from a CSV file, I'm facing a problem with what I think is the API response time, not sure if I'm traversing through the CSV using the API incorrectly / un-efficiently but it is taking a long time to get results for all the 300+ entries in the CSV and whenever I want to push new changes to my codebase I need to wait for the API to re-evaluate the entries every time, here is my code I made for loading in the CSV file and for getting the sentiment scores
<?php
set_time_limit(500); // extended timeout due to slow / overwhelmed API response
function extract_file($csv) { // CSV to array function
$file = fopen($csv, 'r');
while (!feof($file)) {
$lines[] = fgetcsv($file, 1000, ',');
}
fclose($file);
return $lines;
}
$the_file = 'dataset.csv';
$csv_data = extract_file($the_file);
$response_array = []; // array container to hold returned sentiment values from among prduct descriptions
for($x = 1; $x < count($csv_data) - 1; $x++) { // loop through all descriptions
echo $x; // show iteration
$api_text = $csv_data[$x][1];
$api_text = str_replace('&', ' and ', $api_text); // removing escape sequence characters, '&' breaks the api :)
$api_text = str_replace(" ", "%20", $api_text); // serializing string
$text = 'text=';
$text .=$api_text; // serializing string further for the API
//echo 'current text1: ', $api_text;
$curl = curl_init(); // API request init
curl_setopt_array($curl, [
CURLOPT_URL => "https://text-sentiment.p.rapidapi.com/analyze",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => $text,
CURLOPT_HTTPHEADER => [
"X-RapidAPI-Host: text-sentiment.p.rapidapi.com",
"X-RapidAPI-Key: <snip>",
"content-type: application/x-www-form-urlencoded"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}
$json = json_decode($response, true); // convert response to JSON format
if(isset($json["pos"]) == false) { // catching response error 100, makes array faulty otherwise
continue;
}
else {
array_push($response_array, array($x, "+" => $json["pos"], "-" => $json["neg"])); // appends array with sentiment values at current index
}
}
echo "<br>";
echo "<br> results: ";
echo "<p>";
for ($y = 0; $y < count($response_array); $y++){ // prints out all the sentiment values
echo "<br>";
echo print_r($response_array[$y]);
echo "<br>";
}
echo "</p>";
echo "<br>the most negative description: ";
$max_neg = array_keys($response_array, max(array_column($response_array, '-')));
//$max_neg = max(array_column($response_array, '-'));
echo print_r($csv_data[$max_neg[0]]);
echo "<br>the most positive description: ";
$max_pos = array_keys($response_array, max(array_column($response_array, '+')));
echo print_r($csv_data[$max_pos[0]]);
?>
What this code snippet aims to do is find the most negative and most positive sentiment among the description column in the csv and print them out according to their index, I'm only interested in finding descriptions with the highest amount of positive and negative sentiment word number not the percentage of the overall sentiment
The file can be found in this git repo
Thanks for any suggestions
This can be achieved by creating a cache file.
This solution creates a file cache.json that contains the results from the API, using the product name as the key for each entry.
On subsequent calls, it will use the cache value if it exists.
set_time_limit(500);
function file_put_json($file, $data)
{
$json = json_encode($data, JSON_PRETTY_PRINT);
file_put_contents($file, $json);
}
function file_get_json($file, $as_array=false)
{
return json_decode(file_get_contents($file), $as_array);
}
function file_get_csv($file, $header_row=true)
{
$handle = fopen($file, 'r');
if ($header_row === true)
$header = fgetcsv($handle);
$array = [];
while ($row = fgetcsv($handle)) {
if ($header_row === true) {
$array[] = array_combine($header, array_map('trim', $row));
} else {
$array[] = array_map('trim', $row);
}
}
fclose($handle);
return $array;
}
function call_sentiment_api($input)
{
$text = 'text=' . $input;
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://text-sentiment.p.rapidapi.com/analyze",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => $text,
CURLOPT_HTTPHEADER => [
"X-RapidAPI-Host: text-sentiment.p.rapidapi.com",
"X-RapidAPI-Key: <snip>",
"content-type: application/x-www-form-urlencoded"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
throw new Exception("cURL Error #:" . $err);
}
return $response;
}
$csv_data = file_get_csv('dataset.csv');
if (file_exists('cache.json')) {
$cache_data = file_get_json('cache.json', true);
} else {
$cache_data = [];
}
$cache_names = array_keys($cache_data);
$output = [];
foreach ($csv_data as $csv) {
$product_name = $csv['name'];
echo $product_name . '...';
if (in_array($product_name, $cache_names)) {
echo 'CACHED...' . PHP_EOL;
continue;
}
$description = urlencode(str_replace('&', ' and ', $csv['description']));
$response = call_sentiment_api($description);
echo 'API...' . PHP_EOL;
$json = json_decode($response, true);
$cache_data[$product_name] = $json;
}
file_put_json('cache.json', $cache_data);
echo 'SAVE CACHE!' . PHP_EOL . PHP_EOL;
$highest_pos = 0;
$highest_neg = 0;
$pos = [];
$neg = [];
foreach ($cache_data as $name => $cache) {
if (!isset($cache['pos']) || !isset($cache['neg'])) {
continue;
}
if ($cache['pos'] > $highest_pos) {
$pos = [$name => $cache];
$highest_pos = $cache['pos'];
}
if ($cache['pos'] === $highest_pos) {
$pos[$name] = $cache;
}
if ($cache['neg'] > $highest_neg) {
$neg = [$name => $cache];
$highest_neg = $cache['neg'];
}
if ($cache['neg'] === $highest_neg) {
$neg[$name] = $cache;
}
}
echo "Most Positive Sentiment: " . $highest_pos . PHP_EOL;
foreach ($pos as $name => $pos_) {
echo "\t" . $name . PHP_EOL;
}
echo PHP_EOL;
echo "Most Negative Sentiment: " . $highest_neg . PHP_EOL;
foreach ($neg as $name => $neg_) {
echo "\t" . $name . PHP_EOL;
}
Results in:
Most Positive Sentiment: 4
X-Grip Lifting Straps - GymBeam
Beta Carotene - GymBeam
Chelated Magnesium - GymBeam
Creatine Crea7in - GymBeam
L-carnitine 1000 mg - GymBeam - 20 tabs
Resistance Band Set - GymBeam
Most Negative Sentiment: 2
Calorie free Ketchup sauce 320 ml - GymBeam
ReHydrate Hypotonic Drink 1000 ml - GymBeam
Vitamin E 60 caps - GymBeam
Vitamin B-Complex 120 tab - GymBeam
Zero Syrup Hazelnut Choco 350 ml - GymBeam
Bio Psyllium - GymBeam
Zero calorie Vanilla Syrup - GymBeam
You need to know where the time is going.
Start with identifying where the time goes in the curl request.
My guess is the API response time.
If that's the case I have a solution. Meanwhile I will get the "multi-tasking" code code I use to do simultaneous curl requests.
curl has the timing you need. It looks like this:
'total_time' => 0.029867,
'namelookup_time' => 0.000864,
'connect_time' => 0.001659,
'pretransfer_time' => 0.00988,
'size_upload' => 0.0,
'size_download' => 8300.0,
'speed_download' => 277898.0,
'speed_upload' => 0.0,
Just add a couple of lines of code
$response = curl_exec($curl);
$info = var_export(curl_getinfo($curl),true);
file_put_contents('timing.txt',$info,FILE_APPEND);
Running simultaneous curl sockets.
Put your curl in curl.php
$text = $_GET['text'];
curl_setopt_array($curl, [
CURLOPT_URL => "https://text-sentiment.p.rapidapi.com/analyze",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => $text,
CURLOPT_HTTPHEADER => [
"X-RapidAPI-Host: text-sentiment.p.rapidapi.com",
"X-RapidAPI-Key: <snip>",
"content-type: application/x-www-form-urlencoded"
],
]);
This code goes in your CSV loop to create all the URL query fields to pass to curl.php (e.g. http://127.0.0.1/curl.php?text=$text)
$query = urlencode($text);
$urls[] = array('host' => "127.0.0.1",'path' => "/curl.php?text=$query
Then process all the URLs.
foreach($urls as $path){
$host = $path['host'];
$path = $path['path'];
$http = "GET $path HTTP/1.0\r\nHost: $host\r\n\r\n";
$stream = stream_socket_client("$host:80", $errno,$errstr, 120,STREAM_CLIENT_ASYNC_CONNECT|STREAM_CLIENT_CONNECT);
if ($stream) {
$sockets[] = $stream; // supports multiple sockets
fwrite($stream, $http);
}
else {
$err .= "$id Failed<br>\n";
}
}
Then Monitor the sockets and retrieve the response from each socket.
Then close the socket until you have them all.
while (count($sockets)) {
$read = $sockets;
stream_select($read, $write = NULL, $except = NULL, $timeout);
if (count($read)) {
foreach ($read as $r) {
$id = array_search($r, $sockets);
$data = fread($r, $buffer_size);
if (strlen($data) == 0) {
// echo "$id Closed: " . date('h:i:s') . "\n\n\n";
$closed[$id] = microtime(true);
fclose($r);
unset($sockets[$id]);
}
else {
$results[$id] .= $data;
}
}
}
else {
// echo 'Timeout: ' . date('h:i:s') . "\n\n\n";
break;
}
}
Then all your results are in $results[].

Limiting and Ordering Values in Array

Using some code from NHTSA's API, my own and ideas from this site, wrapping it into a function, it is working just fine but would not run on my live server.
On the live server, it was giving an error which I finally solved to the code using an array shortcut not supported by my live server's PHP version:
Parse error: syntax error, unexpected '[', expecting ')' in /home/pchome/public_html/verify/functions/sitefunctions.php on line 9
which is this line:
$postdata = http_build_query(["data" => $VINS, "format" => "JSON"]);
Changed to this it works and also changed similar code in several other places in the same manner:
$postdata = http_build_query(array("data" => $VINS, "format" => "JSON"));
Occasionally (but not always) I may want to pass multiple VINs to it as a semicolon-separated list. This format is not changeable so what is needed to give this functionality? (Sample VINs: 3GNDA13D76S000000;5XYKT3A12CG000000
// Uses NHTSA API to decode VIN(s)
function decodeVINS($VINS) {
if ($VINS) :
$return = "";
$postdata = http_build_query(array("data" => $VINS, "format" => "JSON"));
$stream_options = array(
'http' => array(
'header' => "Content-Type: application/x-www-form-urlencoded\r\n".
"Content-Length: ".strlen($postdata)."\r\n",
'method' => "POST",
'content' => $postdata
)
);
$context = stream_context_create($stream_options);
$apiURL = "https://vpic.nhtsa.dot.gov/api/vehicles/DecodeVINValuesBatch/";
$fp = #fopen($apiURL, 'rb', FALSE, $context);
$results = array_column(json_decode(#stream_get_contents($fp),TRUE), '0');
$results = $results[0];
$output = "<blockquote>\n";
$output .= "<div><strong>VIN: {$results['VIN']}</strong></div>\n";
$output .= "<div><strong>ErrorCode: {$results['ErrorCode']}</strong></div>\n";
if ($results['AdditionalErrorText']) :
$output .= "<div><strong>AdditionalErrorText: {$results['AdditionalErrorText']}</strong></div>\n";
endif;
foreach ($results as $key => $val) :
if ($val && $key != "VIN" && $key != "ErrorCode" && $key != "AdditionalErrorText") :
$output .= "<div>$key: $val</div>";
endif;
endforeach;
$output .= "</blockquote>\n\n";
else :
$output = "Enter VINs above separated by line breaks";
endif;
return $output;
}
. . . and it is outputting something like this:
VIN: JB7FJ43S5KJ000911
ErrorCode: 0 - VIN decoded clean. Check Digit (9th position) is correct
BodyClass: Sport Utility Vehicle (SUV)/Multi Purpose Vehicle (MPV)
DisplacementCC: 3000
DisplacementCI: 183.0712322841
DisplacementL: 3
DriveType: 4WD/4-Wheel Drive/4x4
EngineConfiguration: V-Shaped
EngineCylinders: 6
FuelTypePrimary: Gasoline
GVWR: Class 1C: 4,001 - 5,000 lb (1,814 - 2,268 kg)
Make: DODGE
Manufacturer: MITSUBISHI MOTORS CORPORATION (MMC)
ManufacturerId: 1052
Model: Raider
ModelYear: 1989
PlantCity: Nagoya
PlantCompanyName: Nagoya #3
PlantCountry: Japan
VehicleType: TRUCK
Working with JSON instead of CSV, in my opinion, is going to be much easier/direct/stable.
I have added a parameter ($fields) to the custom function call which will dictate how to isolate and sort your data.
I have also modified the first parameter ($VINs), to be passed as an array instead of a semicolon delimited string. This I hope simplifies your processing -- if it doesn't you are welcome to fallback to your original string format and remove my implode(";",$VINs) call.
Code: (Demo)
function searchByVINs ($VINs,$fields) {
// test multi-VIN batching via textarea at bottom of https://vpic.nhtsa.dot.gov/api/
$stream_options_content = http_build_query(["data" => implode(";", $VINS), "format" => "JSON"]);
$stream_options = [
'http' => [
'header' => "Content-Type: application/x-www-form-urlencoded\r\n".
"Content-Length: ".strlen($postdata)."\r\n",
'method' => "POST",
'content' => $postdata
]
];
$context = stream_context_create($stream_options);
$apiURL = "https://vpic.nhtsa.dot.gov/api/vehicles/DecodeVINValuesBatch/";
if (!$fp = #fopen($apiURL, "rb", FALSE, $context)) {
return ["success" => false, "response" => "Unable to open stream"];
}
if (!$response = stream_get_contents($fp),true)) {
return ["success" => false, "response" => "Unable to receive streamed data"];
}
if(($data = #json_decode($response,true)) === null && json_last_error()!==JSON_ERROR_NONE){
return ["success" => false, "response" => "Unable to parse streamed data"];
}
if (!isset($data["Message"]) || $data["Message"] != "Results returned successfully") {
return ["success" => false, "response" => "Received unsuccessful dataset"];
}
$return = [];
$keys = array_flip($fields);
foreach ($data["Results"] as $dataset) {
$isolated = array_intersect_key($dataset,$keys); // only retain the elements with keys that match $fields values
$sorted = array_replace($keys,$isolated); // order the dataset by order of elements in $fields
$return[] = $sorted;
}
return ["success" => true, "response" => $return];
}
$VINs = ["3GNDA13D76S000000", "5XYKT3A12CG000000"];
$fields = ["VIN", "ModelYear", "Make", "FuelTypePrimary", "DriveType", "BodyClass"];
$response = searchByVINs($VINs,$fields);
if (!$response["success"]) {
echo "Oops, the api call failed. {$response["response"]}";
} else {
foreach ($response["response"] as $item){
echo "<div>";
foreach ($item as $key => $value) {
echo "<div>$key: $value</div>";
}
echo "</div>";
}
}
Output (from mocked demo)
<div>
<div>VIN: 3GNDA13D76S000000</div>
<div>ModelYear: 2006</div>
<div>Make: CHEVROLET</div>
<div>FuelTypePrimary: Gasoline</div>
<div>DriveType: </div>
<div>BodyClass: Wagon</div>
</div>
<div>
<div>VIN: 5XYKT3A12CG000000</div>
<div>ModelYear: 2012</div>
<div>Make: KIA</div>
<div>FuelTypePrimary: Gasoline</div>
<div>DriveType: 4x2</div>
<div>BodyClass: Wagon</div>
</div>
All working now so here is the final version! As needed, shows only rows with values and can handle multiple VINs in one submission. The function is called from a simple form that has a textarea for entering the VINs, along with a Submit button.
// Uses NHTSA API to decode VIN(s)
function decodeVINS($VINS) {
// sample VINs 3GNDA13D76S000000;5XYKT3A12CG000000
if ($VINS) :
$postdata = http_build_query(array("data" => $VINS, "format" => "JSON"));
$stream_options = array(
'http' => array(
'header' => "Content-Type: application/x-www-form-urlencoded\r\n".
"Content-Length: ".strlen($postdata)."\r\n",
'method' => "POST",
'content' => $postdata
)
);
$context = stream_context_create($stream_options);
$apiURL = "https://vpic.nhtsa.dot.gov/api/vehicles/DecodeVINValuesBatch/";
$fp = #fopen($apiURL, 'rb', FALSE, $context);
$returnValue = json_decode(#stream_get_contents($fp),TRUE);
if(!isset($returnValue['Results'])):
echo "Invalid return data or no return data. Exiting";
return FALSE;
endif;
$results = $returnValue['Results'];
if(!is_array($results)):
$results = array($results);
endif;
$output = '';
foreach($results as $result):
$output .= "<blockquote>\n";
$output .= "<div><strong>VIN: {$result['VIN']}</strong></div>\n";
$output .= "<div><strong>ErrorCode: {$result['ErrorCode']}</strong></div>\n";
if ($result['AdditionalErrorText']) :
$output .= "<div><strong>AdditionalErrorText: {$result['AdditionalErrorText']}</strong></div>\n";
endif;
foreach ($result as $key => $val) :
if ($val && $key != "VIN" && $key != "ErrorCode" && $key != "AdditionalErrorText") :
$output .= "<div>$key: $val</div>";
endif;
endforeach;
$output .= "</blockquote>\n\n";
endforeach;
else :
$output = "Enter VINs above separated by line breaks";
endif;
return $output;
}

GET & DELETE using file_get_contents() very slow

I use the file_get_contents() from server M to get the response from server X. The result was success but it take too long.
$url = "http://10.20.30.40";
$opts = array('http' =>
array(
'method' => 'GET',
'header' => 'Connection: close\r\n'
)
);
$context = stream_context_create($opts);
$result = file_get_contents($url, false, $context);
$result = json_decode($result);
$response = parse_http_response_header($http_response_header);
print_r($result);
print_r($response);
/////// below is just function to parse the response ///////
function parse_http_response_header(array $headers)
{
$responses = array();
$buffer = NULL;
foreach ($headers as $header)
{
if ('HTTP/' === substr($header, 0, 5))
{
// add buffer on top of all responses
if ($buffer) array_unshift($responses, $buffer);
$buffer = array();
list($version, $code, $phrase) = explode(' ', $header, 3) + array('', FALSE, '');
$buffer['status'] = array(
'line' => $header,
'version' => $version,
'code' => (int) $code,
'phrase' => $phrase
);
$fields = &$buffer['fields'];
$fields = array();
continue;
}
list($name, $value) = explode(': ', $header, 2) + array('', '');
// header-names are case insensitive
$name = strtoupper($name);
// values of multiple fields with the same name are normalized into
// a comma separated list (HTTP/1.0+1.1)
if (isset($fields[$name]))
{
$value = $fields[$name].','.$value;
}
$fields[$name] = $value;
}
unset($fields); // remove reference
array_unshift($responses, $buffer);
return $responses;
}
Is there any suggestion or function option to get the response (the content and the response code) faster?
(NOTE: I am not allowed to install cURL, so please gimme other option)

linkedin API using raw PHP

I want to get the profile info after getting the token
<?php
session_start ();
if (!$_SESSION['linkedin_access_token'])
{
echo 'Grant access first';
exit ();
}
## step 0
define ('LINKEDIN_KEY', 'xxxxxx');
define ('LINKEDIN_SECRET', 'xxxxxxx');
function urlencode_oauth ($str)
{
return str_replace ('+', ' ', str_replace ('%7E', '~', rawurlencode ($str)));
}
$links = array (
'request_token' => 'https://api.linkedin.com/uas/oauth/requestToken',
'authorize' => 'https://www.linkedin.com/uas/oauth/authorize',
'access_token' => 'https://api.linkedin.com/uas/oauth/accessToken',
);
## step 2
$params = array (
'oauth_consumer_key' => LINKEDIN_KEY,
'oauth_nonce' => sha1 (microtime ()),
'oauth_signature_method' => 'HMAC-SHA1',
'oauth_timestamp' => time (),
'oauth_token' => $_SESSION ['linkedin_access_token'],
'oauth_version' => '1.0'
);
## step 3
// sort parameters according to ascending order of key
// sort parameters according to ascending order of key
ksort ($params);
// prepare URL-encoded query string
$q = array ();
foreach ($params as $key => $value)
{
$q [] = urlencode_oauth ($key) . '=' . urlencode_oauth ($value);
}
$q = implode ('&', $q);
// generate the base string for signature
$parts = array (
'POST',
urlencode_oauth ('https://api.linkedin.com/v1/people/~'),
urlencode_oauth ($q)
);
$base_string = implode ('&', $parts);
## step 4
$key = urlencode_oauth (LINKEDIN_SECRET) . '&' . urlencode_oauth ($_SESSION ['linkedin_access_token_secret']);
$signature = base64_encode (hash_hmac ('sha1', $base_string, $key, true));
## step 5
$params ['oauth_signature'] = $signature;
$str = array ();
foreach ($params as $key => $value)
{
$str [] = $key . '="' . urlencode_oauth ($value) . '"';
}
$str = implode(', ', $str);
$headers = array (
'POST /v1/people/~ HTTP/1.1',
'Host: api.linkedin.com',
'Authorization: OAuth ' . $str,
'Content-Type: text/xml;charset=UTF-8',
'Content-Length: 0',
'Connection: close'
);
## step 6
$fp = fsockopen ("ssl://api.linkedin.com", 443, $errno, $errstr, 30);
if (!$fp)
{
echo 'Unable to connect to LinkedIn';
exit();
}
$out = implode ("\r\n", $headers) . "\r\n\r\n";
fputs ($fp, $out);
// getting LinkedIn server response
$res = '';
while (!feof ($fp)) $res .= fgets ($fp, 4096);
fclose ($fp);
echo '<pre>';
echo $res . "\n\n";
echo $_SESSION ['linkedin_access_token'] . "\n" . $_SESSION ['linkedin_access_token_secret'];
?>
What's wrong with it? it shows me
HTTP/1.1 405 Method Not Allowed
Server: Apache-Coyote/1.1
x-li-request-id: H8M76QXW5O
Date: Tue, 04 Sep 2012 12:09:21 GMT
Vary: *
x-li-format: xml
Content-Type: text/xml;charset=UTF-8
Content-Length: 262
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<error>
<status>405</status>
<timestamp>1346760561727</timestamp>
<request-id>H8M76QXW5O</request-id>
<error-code>0</error-code>
<message>Unsupported POST target {/v1/people/~}</message>
</error>
xxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxx
define('LINKEDIN_KEY', 'YOUR_KEY');
define('LINKEDIN_SECRET', 'YOUR SECRET');
function urlencode_oauth($str) {
return str_replace('+',' ',str_replace('%7E','~',rawurlencode($str)));
}
$links = array(
'request_token'=>'https://api.linkedin.com/uas/oauth/requestToken',
'authorize'=>'https://www.linkedin.com/uas/oauth/authorize',
'access_token'=>'https://api.linkedin.com/uas/oauth/accessToken'
);
$params = array(
'oauth_callback'=>"YOUR CALLBACK URL",
'oauth_consumer_key'=>LINKEDIN_KEY,
'oauth_nonce'=>sha1(microtime()),
'oauth_signature_method'=>'HMAC-SHA1',
'oauth_timestamp'=>time(),
'oauth_version'=>'1.0'
);
// sort parameters according to ascending order of key
ksort($params);
// prepare URL-encoded query string
$q = array();
foreach ($params as $key=>$value) {
$q[] = urlencode_oauth($key).'='.urlencode_oauth($value);
}
$q = implode('&',$q);
// generate the base string for signature
$parts = array(
'POST',
urlencode_oauth($links['request_token']),
urlencode_oauth($q)
);
$base_string = implode('&',$parts);
$key = urlencode_oauth(LINKEDIN_SECRET) . '&';
$signature = base64_encode(hash_hmac('sha1',$base_string,$key,true));
$params['oauth_signature'] = $signature;
$str = array();
foreach ($params as $key=>$value) {
$str[] = $key . '="'.urlencode_oauth($value).'"';
}
$str = implode(', ',$str);
$headers = array(
'POST /uas/oauth/requestToken HTTP/1.1',
'Host: api.linkedin.com',
'Authorization: OAuth '.$str,
'Content-Type: text/xml;charset=UTF-8',
'Content-Length: 0',
'Connection: close'
);
$fp = fsockopen("ssl://api.linkedin.com",443,$errno,$errstr,30);
if (!$fp) { echo 'Unable to connect to LinkedIn'; exit(); }
$out = implode("\r\n",$headers) . "\r\n\r\n";
fputs($fp,$out);
// getting LinkedIn server response
$res = '';
while (!feof($fp)) $res .= fgets($fp,4096);
fclose($fp);
$parts = explode("\n\n",str_replace("\r",'',$res));
$res_headers = explode("\n",$parts[0]);
if ($res_headers[0] != 'HTTP/1.1 200 OK') {
echo 'Error getting OAuth token and secret.'; exit();
}
parse_str($parts[1],$data);
if (empty($data['oauth_token'])) {
echo 'Failed to get LinkedIn request token.'; exit();
}
Replace three things 1. 'YOUR_KEY' 2. 'YOUR_SECRET' and 3.'oauth_callback'.
Thanks,
Anand
As the documentation indicates, the Profile API does not support the POST method. Try using GET instead to retrieve profile data.

custom images using Yahoo Weather feed

I'm retrieving data from Yahoo Weather currently and able to display the elements I need so far. I'm stumped on how to use the Condition Codes (i think i need to turn the number into an integer) to have a string such that I can display my own custom icons. As the weather in the region I am tracking is fairly mild I want to assign only a small number of icons but have each cover a string of Condition Codes. So far this is my code that is working–
function bm_getWeather ($code = '', $temp = 'c') {
$file = 'http://weather.yahooapis.com/forecastrss?w=' . $code . '&u=' . $temp;
$request = new WP_Http;
$result = $request->request($file);
if (isset($result->errors)) {
return FALSE;
}
$data = $result['body'];
$output = array (
'temperature' => bm_getWeatherProperties('temp', $data),
'weather_code' => bm_getWeatherProperties('code', $data),
'class' => bm_getWeatherProperties('code', $data),
'weather' => bm_getWeatherProperties('text', $data),
);
return $output;
}
function weather_icon() {
$data = bm_getWeather($code = '', $temp = 'c');
// Error is here
$nums = (int)$data['weather_code']; // Needs to be cast as an integer
$severe = array(12, 13, 14, 16, 19);
$rain = array(3200);
// Therefore the maths is wrong
switch($nums) {
case (in_array($nums, $severe)):
$cat = 'severe';
break;
case (in_array($nums,$rain)):
$cat = 'snow';
break;
default:
$cat = 'happy';
break;
}
return $cat;
}
function bm_getWeatherProperties ($needle, $data) {
$regex = '<yweather:condition.*' . $needle . '="(.*?)".*/>';
preg_match($regex, $data, $matches);
return (string)$matches[1];
}
Any help would be much appreciated as my php skills are less than adequate right now.

Categories