Asynchronous API handling with PHP - php

I have this PHP function that logs the visitors to a .txt file (for security reasons). It uses an API to get their location. It works fine on localhost, but when it's actually live it logs empty values. I'm assuming it's because the functions runs before the API has had time to return the data. Is there a way to write something like a Javascript promise or some asynchronous function that will wait for the data to return before logging it?
This is what I currently have:
function getLocation() {
$query = #unserialize (file_get_contents('http://ip-api.com/php/'));
if ($query && $query['status'] == 'success') {
$user = $_SERVER['REMOTE_ADDR'].' - '.date("H:i:s d-m-Y").' - '.$query['country'].', '.$query['regionName'].', '.$query['city'].', '.$query['zip'];
$file = '../logs/userlog.txt';
$currentFileData = file_get_contents($file);
$currentFileData .= $user . PHP_EOL;
file_put_contents($file, $currentFileData);
}
}
What the output should be:
127.0.0.1 - 11:59:33 03-04-2020 - South Africa, Western Cape, Cape Town, 8001
What the actual output is:
127.0.0.1 - 11:59:33 03-04-2020 - , , ,
Your help will be greatly appreciated!

You are not passing the IP address as stated in the documentation after the /php/ part of the URL. It should be
$query = #unserialize (file_get_contents('http://ip-api.com/php/' . $_SERVER['REMOTE_ADDR']));

SOLVED: Thanks to #DanielProtopopov's post I actually found on the documentation that this php version of the API has been deprecated. So using #DanielProtopopov's fix I have to use the JSON API for it to work:
$query = json_decode(file_get_contents('http://ip-api.com/json/' . $_SERVER['REMOTE_ADDR']));

Related

Why do I need curl-ca-bundle.crt?

For college, I've made a dynamic news website which uses openweathermap and ipinfo to create a little weather info line in the navbar. Initially it threw an error 'failed loading cafile stream' which was solved by installing a CA certificate in xampp/Apache/bin.
I've got a vague idea what this does - something in relation to making sure the peer's server certificate is valid, but I thought this was only necessary if you're using the 'curl' library? I'm not sure where in my code I've used this, unless it's related to where I pull info from one of the URLs? Just looking for clarification on where in the code 'curl' is used, what it's doing and why exactly I need this certificate. Also as an additional point, if I were to send my files to another person, would they also have to install this .crt file to xampp/apache/bin?
$query = #unserialize (file_get_contents('http://ip-api.com/php/'));
if ($query && $query['status'] == 'success') {
foreach ($query as $data) {
$data . "<br>";
}
}
$url="https://api.openweathermap.org/data/2.5/find?q=" . $query['city'] . "," . $query['countryCode'] . "&units=imperial&type=accurate&mode=xml&APPID=MYKEYCODE";
/*Converts an XML document to an object we can pull our info from*/
$getweather = simplexml_load_file($url);
$gettemp = $getweather->list->item->temperature['value'];
$celcius = ($gettemp - 32) * 5/9;
Thank you!
The slightly modified version I tried was as follows ( using json rather than XML for simplicity ):
$appkey='xxxxxxxxxx165be29428029b';
$data=(object)#unserialize( file_get_contents('http://ip-api.com/php/') );
if( $data ){
$city=$data->city;
$countryCode=$data->countryCode;
$url=sprintf('https://api.openweathermap.org/data/2.5/find?q=%s,%s&units=imperial&type=accurate&mode=json&APPID=%s',$city,$countryCode,$appkey);
$json=json_decode( file_get_contents( $url ) ) ?: false;
if( $json ){
$temp=$json->list[0]->main->temp;
$celcius=( ( $temp - 32 ) * 5/9 );
printf("<pre>City: %s\nCountry: %s\nTemperature: %sF (%sC)</pre>",$city,$countryCode,$temp,$celcius);
}
}
Which yielded:
City: Sheffield
Country: GB
Temperature: 42.17F (5.65C)
Which is a little odd as I have never been to Sheffield and it's nowhere near where I live but I know my ISP routes traffic all over the reekin so geo-location etc never works. That aside there were no errors and no requirement for a valid cacert
good luck

Return in function not working

I am subscribing to data from a MQTT broker with phpMQTT. I have successfully set up a pub / sub routine based on their basic implementation. I can echo the information just fine inside the procmsg() function.
However, I need to take the data I receive and use it for running a few database operations and such. I can't seem to get access to the topic or msg received outside of the procmsg() function. Using return as below seems to yield nothing.
<?php
function procmsg($topic, $msg){
$value = $msg * 10;
return $value;
}
echo procmsg($topic, $msg);
echo $value;
?>
Obviously I am doing something wrong - but how do I get at the values so I can use them outside the procmsg()? Thanks a lot.
I dont know about that lib, but in that code
https://github.com/bluerhinos/phpMQTT/blob/master/phpMQTT.php ,
its possible see how works.
in :
$topics['edafdff398fb22847a2f98a15ca3186e/#'] = array("qos"=>0, "function"=>"procmsg");
you are telling it that topic "edafdff398fb22847a2f98a15ca3186e/#" will have Quality of Service (qos) = 0, and an "event" called 'procmsg'.
That's why you later wrote this
function procmsg($topic,$msg){ ... }
so in the while($mqtt->proc()) this function will check everytime if has a new message (line 332 calls a message function and then that make a call to procmsg of Source Code)
thats are the reason why you cannot call in your code to procmsg
in other words maybe inside the procmsg you can call the functions to process message ej :
function procmsg($topic,$msg){
$value = $msg * 10;
doStuffWithDataAndDatabase($value);
}
Note that you can change the name of the function simply ej :
$topics['edafdff398fb22847a2f98a15ca3186e/#'] = array("qos"=>0, "function"=>"onMessage");
and then :
function onMessage($topic,$msg){
$value = $msg * 10;
doStuffWithDataAndDatabase($value);
}
Sorry for my english, hope this help !

file_get_contents() returns extra underscore on angular.callbacks

I built a PHP file with the sole purpose of hiding the API keys for Google Search, but part of the file_get_contents() always echo angular.callbacks._0_({ instead of angular.callbacks._0({
This small change makes the rest of the response worthless as Angular throws Uncaught TypeError: angular.callbacks._0_ is not a function. Although the workaround does works flawlessly, I would like to know if someone found the root of this issue or a better solution that is strictly PHP (no curl or any other package.)
search.php
<?php // Created by Deovandski on 2/14/2016
header('Content-type: application/json');
# Setup Base URL and array for Parameters
$host = 'https://www.googleapis.com/customsearch/v1?';
$queries = array();
$queries['cx'] = "XXX";// CSE KEY
$queries['key'] = "XXX"; // API KEY
# Setup possible incoming params
if (isset($_GET['search_term'])) $queries['q'] = $_GET['search_term'];
if (isset($_GET['result_count'])) $queries['result_count'] = $_GET['result_count'];
if (isset($_GET['callback'])) $queries['callback'] = $_GET['callback'];
# Build query and Final URL
$queriesURL = http_build_query($queries) . "\n";
$finalURL = $host.$queriesURL;
echo $finalURL;
/* echo $finalURL output (I only edited the keys out):
https://www.googleapis.com/customsearch/v1?cx=XXX&key=XXX&q=Hatsune+Miku&result_count=10&callback=angular.callbacks._0
*/
// Setup Response
$response = file_get_contents($finalURL);
// workaround
$fixedResponse = str_replace("angular.callbacks._0_", "angular.callbacks._0", $response);
echo $fixedResponse;
?>
This is part of a correct Google API response:
// API callback
angular.callbacks._0({
"kind": "customsearch#search",
"url": {
"type": "application/json",
"template": "https://www.googleapis.com/customsearch/v1?q={searchTerms}&num={count?}&start={startIndex?}&lr={language?}&safe={safe?}&cx={cx?}&cref={cref?}&sort={sort?}&filter={filter?}&gl={gl?}&cr={cr?}&googlehost={googleHost?}&c2coff={disableCnTwTranslation?}&hq={hq?}&hl={hl?}&siteSearch={siteSearch?}&siteSearchFilter={siteSearchFilter?}&exactTerms={exactTerms?}&excludeTerms={excludeTerms?}&linkSite={linkSite?}&orTerms={orTerms?}&relatedSite={relatedSite?}&dateRestrict={dateRestrict?}&lowRange={lowRange?}&highRange={highRange?}&searchType={searchType}&fileType={fileType?}&rights={rights?}&imgSize={imgSize?}&imgType={imgType?}&imgColorType={imgColorType?}&imgDominantColor={imgDominantColor?}&alt=json"
},
I put up a live version of this issue that can be seen on my FTP server. The PHP file can be viewed through this link (AngularJS parameters included on it).
The problem is the escape sequences \n. Which is passed as part of the request. And which is interpreted as space and as part of the callback function name and replaced by the side of the API to underline.
To understand just try this option and look at the result:
$queriesURL = http_build_query($queries) . "\n" . "after";
So just take away a newline.

How to invoke the demo url using VinceG php-first-data-api

I am trying to integrate First Data e4 Gateway using PHP. I downloaded the VinceG/php-first-data-api PHP First Data Service API class. The code comes with some examples.
I have my Terminal ID (API_LOGIN) and Password (32 character string).
What confuses me is that when I use one of the examples, I don't know how to tell the class that I want to use the demo url, not the production url.
The class comes with two constants:
const LIVE_API_URL = 'https://api.globalgatewaye4.firstdata.com/transaction/';
const TEST_API_URL = 'https://api.demo.globalgatewaye4.firstdata.com/transaction/';
In the First Data console, when I generated my password, it said to use the v12 api, /transaction/v12, so I changed the protected $apiVersion = 'v12';
All I want to do is write my first development transaction using First Data e4. I have yet to get any kind of response. Obviously I need a lot of hand holding to get started.
When I set up a website to use BalancedPayments, they have a support forum that's pretty good, and I was able to get that running fairly quickly. First Data has a lot of documentation, but for some reason not much of it has good PHP examples.
My hope is that some expert has already mastered the VinceG/php-first-data-api, and can help me write one script that works.
Here's the pre-auth code I'm using, that invokes the FirstData class:
// Pre Auth Transaction Type
define("API_LOGIN", "B123456-01");
define("API_KEY", "xxxxxxxxxxyyyyyyyyyyyyzzzzzzzzzz");
$data = array();
$data['type'] = "00";
$data['number'] = "4111111111111111";
$data['name'] = "Cyrus Vance";
$data['exp'] = "0618";
$data['amount'] = "100.00";
$data['zip'] = "33333";
$data['cvv'] = "123";
$data['address'] = "1111 OCEAN BLVD MIAMI FL";
$orderId = "0001";
require_once("FirstData.php");
$firstData = new FirstData(API_LOGIN, API_KEY, true);
// Charge
$firstData->setTransactionType(FirstData::TRAN_PREAUTH);
$firstData->setCreditCardType($data['type'])
->setCreditCardNumber($data['number'])
->setCreditCardName($data['name'])
->setCreditCardExpiration($data['exp'])
->setAmount($data['amount'])
->setReferenceNumber($orderId);
if($data['zip']) {
$firstData->setCreditCardZipCode($data['zip']);
}
if($data['cvv']) {
$firstData->setCreditCardVerification($data['cvv']);
}
if($data['address']) {
$firstData->setCreditCardAddress($data['address']);
}
$firstData->process();
// Check
if($firstData->isError()) {
echo "!!!";
// there was an error
} else {
echo "###";
// transaction passed
}
My number one problem was that I had not created (applied for, with instant approval) a
demo account on First Data. I didn't realize this was a separate thing on First Data. On Balanced Payments, for instance, you have one account, and you can run your script on a test url with test values.
From the Administration panel, click "Terminals", then your Gateway number on the ECOMM row (will look something like AH1234-03), then you have to click "Generate" on password save it to your personal notes), then click UPDATE.
Now replace your parameter values in your test scripts. I use a variable assignment block that looks something like this:
define("API_LOGIN", "AH1234-05"); //fake
define("API_KEY", "44p7797xxx790098z1z2n6f270ys1z0x"); //fake
$data = array();
$data['type'] = "03";
$data['number'] = "4111111111111111";
$data['name'] = "Cyrus Vancce";
$data['exp'] = "0618";
$data['amount'] = "100.00";
$data['zip'] = "33320";
$data['cvv'] = "123";
$data['address'] = "1234 N OCEAN BLVD MIAMI BEACH FL";
$orderId = "0001";
require_once("FirstData.php");
$firstData = new FirstData(API_LOGIN, API_KEY, true);
at the end of the VinceG test scripts, I output my gateway response with a print_r, like this:
$firstData->process();
// Check
if($firstData->isError()) {
echo "!!!";
// there was an error
} else {
echo "###";
// transaction passed
}
echo "<pre>";
print_r($firstData);

How to strip subdomains to get valid root domain using PHP?

Ok, here's what I'm looking for: from a list of links, I'm stripping everything but the domains. The result is a mixed list of domains and domain-names which represent subdomains.
stackoverflow.com
security.stackexchange.com
whoknows.test.co.uk
you.get.it.by.now.dont.you.com
What I want to do is to trim all list entries down to their VALID (=only existing) root domains like this:
stackoverflow.com
security.stackexchange.com
test.co.uk
-fail-
Currently I explode each line into an array and work my list from back to front, using curl to check each potential root domain for it's existance... as soon as curl throws back a HTTP code >= 200 and < 400, I regard the root domain to be found. When the end of each potential domain lookup is done and no valid domain has been found at all, the domain is considered to be non-existant.
input: stackoverflow.com
test: stackoverflow.com - succeeds and is the root domain
result: stackoverflow.com - valid root domain
input: whoknows.test.co.uk
test: co.uk - fails
test: test.co.uk - succeeds (theoretically) and is the root domain
result: test.co.uk - valid root domain
input: you.get.it.by.now.dont.you.com
test: you.com - fails
test: dont.you.com - fails
test: now.dont.you.com - fails
test: by.now.dont.you.com - fails
test: it.by.now.dont.you.com - fails
test: get.it.by.now.dont.you.com - fails
test: you.get.it.by.now.dont.you.com - fails
result: you.get.it.by.now.dont.you.com - invalid domain
Is there any alternative way to do this? I would like to stop heating up my webserver's CPU with 2 to X (=near to unlimited) curl look-ups for every domain on my 100.000+ list. Also, all these lookups take a bunch of time. Maybe - so I hope - there is a quicker solution to do this.
The catch? It has to work with PHP.
There are a bunch of shortcuts to acheive what you need.
For example, you already know that .co.uk and .com are TLDs, so checking these you can obviously skip.
The problem is with all the other crazy TLDs.
I suggest you take a look at the source for ruby-domain-name
They have done a lot of work using RFCs and known data, to try and process it the right way.
So...
I've been fiddling around with this for a long time now, looking for the potential bottlenecks and after a few days of back and forth I discovered that it's actually CURL (that's waiting for the individual servers to respond with a HTTP code) that's making things slower than needed.
In the end, I opted in for a different "gethostbyname" function that takes IP6 into account to solve my problem(s).
function my_gethostbyname($host, $try_a = FALSE)
{
$dns = gethostbynamel6($host, $try_a);
if ($dns == FALSE)
{
return FALSE;
}
else
{
return $dns[0];
}
}
function gethostbynamel6($host, $try_a = FALSE)
{
$dns = array();
$dns6 = #dns_get_record($host, DNS_AAAA);
if($dns6!== FALSE)
{
$dns = array_merge($dns, $dns6);
}
if ($try_a == TRUE)
{
$dns4 = #dns_get_record($host, DNS_A);
if($dns4!== FALSE)
{
$dns = array_merge($dns, $dns4);
}
}
else
{
$dns = $dns6;
}
$ip6 = array();
$ip4 = array();
foreach ($dns as $record)
{
if ($record["type"] == "A")
{
$ip4[] = $record["ip"];
}
if ($record["type"] == "AAAA")
{
$ip6[] = $record["ipv6"];
}
}
if (count($ip6) < 1)
{
if ($try_a == TRUE)
{
if (count($ip4) < 1)
{
return FALSE;
}
else
{
return $ip4;
}
}
else
{
return FALSE;
}
}
else
{
return $ip6;
}
}
As soon as the first domain-part actually resolves to an IP, (a) the domain exists and (b) is the root domain.
This spares me major time and the trick of this is that you're only as slow as your DNS resolution and some microsecs. The curl option I used before took around 3 seconds per call (sometimes up to the full timeout range I had set to 20 secs), depending on the target server's response time - if any.
The path I now chose is easy to understand: I end up with a list of resolving domains and - when needed - I can check those using curl "on demand" or using one or more CRON jobs "on interval".
I know that it's kind of a workaround, but splitting the problem into two tasks (1 = pre-check for root domain, 2 = check if domain returns expected HTTP code) makes the whole thing faster than trying to do the complete job at once using curl.
What I've learned from this...
When checking domains, try to resolve them first so you can spare yourself the timeout burden that comes with curl.
Curl is great for many tasks, but it's not the smartest way to try to do everything with it.
When you think you can't solve a problem more than you've tried to do, split the problem in two or more parts and check again. Chances are big that you'll discover a whole new world of options to enhance what you've got.
I hope this spares someone the burden of fiddling around with an alike problem for weeks. ;)
class DomainUtils {
function __construct(){
//only these super domains
$this->superDomains = array(
'.com',
'.gov',
'.org',
'.co.uk',
'.info',
'.co',
'.net',
'.me',
'.tv',
'.mobi'
);
}
//get super domain
public function getMainDomain($domain = null){
$domainChunks = explode('.', str_ireplace($this->superDomains, '', $domain));
if(sizeof($domainChunks) == 0){
return false;
}
foreach($domainChunks as $key => $domainChunk){
if($key < sizeof($domainChunks) - 1){
$domain = str_ireplace($domainChunk . '.', '', $domain);
}
}
return $domain;
}
}

Categories