Posts Tagged ‘PHP’

Twitter Timeline Feed with PHP & JSON

April 14th, 2013

I have had my share of users needing help fixing broken implementations with twitter on their own website, such as pulling from an incorrect url, or using the incorrect twitter API format. Most of the time they are pulling the XML response, but twitter offers a much nicer format that works rather well with PHP; JSON.

Quick Example
A pull from your twitter timeline is as simple as:

<?php
 
$json = file_get_contents("https://api.twitter.com/1/statuses/user_timeline/karlblessing.json", TRUE);
 
$twitter_feed = json_decode($json, true);
 
// check for errors
if(isset($twitter_feed['errors'])) {
    foreach($twitter_feed['errors'] as $error) {
        echo "(".$error['code'].") ".$error['message']."<br>";
    }
} else {
    // Loop thru and spit out the text of each tweet returned
    foreach($twitter_feed as $tweet) {
        echo $tweet['text']."<br>";
    }
?>

I do not use Twitter often, so course my own feeds may be scarce.

Example utilizing an array

If you wish to have a bit more control over how the content is interpreted. For this we can utilize an array so the data may be used elsewhere on a page.

<?php
/* 
Configuration Array
 
explanation of each option can be seen here : https://dev.twitter.com/docs/api/1/get/statuses/user_timeline
 
user = the screen_name of the twitter user you wish to query
count = the "maximum" number of items to be returned
retweet = true or false to include retweets in the response
entities = true or false
exclude_replies = true or false to exclude replies
contributor_details = true or false
trim_user = true or false to trim extra user details
 
*/
 
$twitter = array(
	"user" => "karlblessing",
	"count" => "4",
	"retweet" => "true",
	"entities" => "true",
	"exclude_replies" => "true",
	"contributor_details" => "false",
	"trim_user" => "false"
);
 
// a small function to convert "created at" time to [blank] minutes/hours/days ago
 
function relativeTime($time)
{
    $delta = strtotime('+2 hours') - strtotime($time);
    if ($delta < 2 * MINUTE) {
        return "1 min ago";
    }
    if ($delta < 45 * MINUTE) {
        return floor($delta / MINUTE) . " min ago";
    }
    if ($delta < 90 * MINUTE) {
        return "1 hour ago";
    }
    if ($delta < 24 * HOUR) {
        return floor($delta / HOUR) . " hours ago";
    }
    if ($delta < 48 * HOUR) {
        return "yesterday";
    }
    if ($delta < 30 * DAY) {
        return floor($delta / DAY) . " days ago";
    }
    if ($delta < 12 * MONTH) {
        $months = floor($delta / DAY / 30);
        return $months <= 1 ? "1 month ago" : $months . " months ago";
    } else {
        $years = floor($delta / DAY / 365);
        return $years <= 1 ? "1 year ago" : $years . " years ago";
    }
}
 
// prepare the array
 
$twitter_feed = array();
 
// form the API url for the request
 
$api_url = "https://api.twitter.com/1/statuses/user_timeline/".$twitter['user'].
	".json?include_entities=".$twitter['entities'].
	"&include_rts=".$twitter['retweet'].
	"&exclude_replies=".$twitter['exclude_replies'].
	"&contributor_details=".$twitter['contributor_details'].
	"&trim_user=".$twitter['trim_user'].
	"&count=".$twitter['count'];
 
// obtain the results 
 
$json = file_get_contents($api_url, true);
 
// decode the json response as a PHP array
 
$decode = json_decode($json, true);
 
//check for error during the last decode
if(json_last_error != JSON_ERROR_NONE) {
	// http://www.php.net/manual/en/function.json-last-error.php
	$twitter_feed[] = array('error' => "Unable to decode response");
} elseif(isset($decode['errors'])) {
	// just grabbing the first error listed
	$twitter_feed[] = array('error' => $decode['errors'][0]['message']);
} else {
	// if no decode or twitter response errors then proceed.
 
	foreach($decode as $tweet) {
		// If you are including retweets, you may want to check the status
		// as the main text is truncated as opposed to the original tweet
 
		// If you used the trim_user option, the retweeted user screen name will not be avaialble
 
		if (isset($tweet['retweeted_status'])) {
			$tweet_text = "RT @{$tweet['retweeted_status']['user']['screen_name']}: 
			{$tweet['retweeted_status']['text']}";
		} else {
			$tweet_text = $tweet['text'];
		}
 
		$twitter_feed[] = array(
			'text' => $tweet_text, 
			'created_at' => relativeTime($tweet['created_at']),
			'link' => "http://twitter.com/".$twitter['user']."/status/".$tweet['id']
		);
 
		unset($tweet_text);
	}
}
 
unset($decode, $json, $tweet);
?>
 
<?php
 
// in a later portion of your code or page you can break down the array like so:
 
foreach($twitter_feed as $tweet) {
	echo "<a href=\"{$tweet['link']}\" target=\"_blank\">{$tweet['text']}</a><br>{$tweet['created_at']}<br><br>";
}
 
?>

The above would list out a maximum of 4 tweets to the screen as hyperlinks to the status ID, followed by the tweet’s creation date in a format such as “4 days ago”.

If there are a lot of retweets, and you did not include retweets in the response, there is a possibility that your response will be blank. This is because the count is a literal maximum which is not factored after the data has been filtered. So when not including retweets you will want to set your count high enough so that some normal tweets may be returned and then simply limit the number shown via the code.

The option for screen_name from the retweeted status will not be available if you use the user_trim option, so if you wish to use this information, be sure to set it to false.

Additional Information

Most of the information regarding user timelines via the Twitter API can be found at Their Documentation.

text, created_at, id are not the only options you can use. If you were to print_r() the decoded json response you will see all the extra information you may use.

Such as entities which will break down all the hashtags, users_mentioned and urls that were in the tweet. Or you can get the tweet information regarding location (geo, cordinates), or tweet source (such as iphone, application, etc).

Here’s an example of the JSON decoded into a PHP array (using print_r to display) for just one of my own tweets.

[1] => Array
        (
            [created_at] => Tue Oct 30 03:34:11 +0000 2012
            [id] => 263121594531061760
            [id_str] => 263121594531061760
            [text] => My Dropbox Referral Link. I'd be surprised if people didn't have an account yet. Super-handy if you have a smartphone http://t.co/OQ8IDYBc
            [source] => <a href="http://www.dropbox.com" rel="nofollow">Dropbox </a>
            [truncated] => 
            [in_reply_to_status_id] => 
            [in_reply_to_status_id_str] => 
            [in_reply_to_user_id] => 
            [in_reply_to_user_id_str] => 
            [in_reply_to_screen_name] => 
            [user] => Array
                (
                    [id] => 106994601
                    [id_str] => 106994601
                    [name] => Karl Blessing
                    [screen_name] => KarlBlessing
                    [location] => Grand Rapids, Mi
                    [url] => http://kbeezie.com
                    [description] => Webdeveloper
                    [protected] => 
                    [followers_count] => 40
                    [friends_count] => 52
                    [listed_count] => 1
                    [created_at] => Thu Jan 21 08:30:25 +0000 2010
                    [favourites_count] => 0
                    [utc_offset] => -18000
                    [time_zone] => Eastern Time (US & Canada)
                    [geo_enabled] => 1
                    [verified] => 
                    [statuses_count] => 95
                    [lang] => en
                    [contributors_enabled] => 
                    [is_translator] => 
                    [profile_background_color] => FFFFFF
                    [profile_background_image_url] => http://a0.twimg.com/profile_background_images/68539068/python-logo.png
                    [profile_background_image_url_https] => https://si0.twimg.com/profile_background_images/68539068/python-logo.png
                    [profile_background_tile] => 
                    [profile_image_url] => http://a0.twimg.com/profile_images/2549311960/ooc473od4tv58hlffxvy_normal.jpeg
                    [profile_image_url_https] => https://si0.twimg.com/profile_images/2549311960/ooc473od4tv58hlffxvy_normal.jpeg
                    [profile_link_color] => 0F6FFF
                    [profile_sidebar_border_color] => FFFFFF
                    [profile_sidebar_fill_color] => FFFFFF
                    [profile_text_color] => 333333
                    [profile_use_background_image] => 
                    [default_profile] => 
                    [default_profile_image] => 
                    [following] => 
                    [follow_request_sent] => 
                    [notifications] => 
                )
 
            [geo] => 
            [coordinates] => 
            [place] => 
            [contributors] => 
            [retweet_count] => 0
            [favorite_count] => 0
            [entities] => Array
                (
                    [hashtags] => Array
                        (
                        )
 
                    [urls] => Array
                        (
                            [0] => Array
                                (
                                    [url] => http://t.co/OQ8IDYBc
                                    [expanded_url] => http://db.tt/fJi2Poc
                                    [display_url] => db.tt/fJi2Poc
                                    [indices] => Array
                                        (
                                            [0] => 118
                                            [1] => 138
                                        )
 
                                )
 
                        )
 
                    [user_mentions] => Array
                        (
                        )
 
                )
 
            [favorited] => 
            [retweeted] => 
            [possibly_sensitive] => 
            [lang] => en
        )

If the above were a part of $twitter_feed and we wanted to pull the first URL used in the tweet.

<?php
echo $twitter_feed[1][entities][urls][0][expanded_url];
// would output : http://db.tt/fJi2Poc
?>

And there you have it.

Securing Nginx and PHP

December 16th, 2011

Disclaimer
This write up is intended for single-user systems where you are the only user expected to log in via shell/terminal/sftp (or at least people you actually trust). This collection of tips does not cover situations where you may have multiple users home folders or a shared hosting situation utilizing nginx and php-fpm. Generally speaking if you have to read this post for security tips you probably should not be allowing access to any other user but yourself in the first place.

If you do not care to read this whole write up, just remember one thing: `777` is not a magical quick-fix; it’s an open invitation to having your system compromised. (Script-Kiddies… can you say jackpot?)

User/Groups

Generally speaking your server, which will most likely be a VPS running some fashion of linux will already have a web service user and group. This will sometimes be www, www-data, http, apache, (or even nginx if a package manager installed it for you). You can run the following command to get a list of users that are on your system.

cat /etc/passwd

Both Nginx and PHP-FPM should run as a web service, on a Debian squeeze this would be www-data:www-data, or on FreeBSD www:www.

If your server was set up with root being the main user you should create an unprivileged user for handling your web files. This will also make it easier to handle permissions when uploading your web files via SFTP. For example the following command on a debian system would create a user named kbeezie which has www-data as the primary group.

useradd -g 33 -m kbeezie

Group ID #33 is the id for www-data on Debian Squeeze (you can verify with id www-data). You may have to su into the new user and change the password (or usermod to change). This will also create a home folder in /home/kbeezie/ by default. You can log in via SFTP to this user and create a www folder if you wish. You’ll notice that the files will be owned by kbeezie:www-data, which will allow Nginx and PHP to read from, but also gives you group-level control over how the web services may treat those files.

This is ideal since you’re not giving nginx or php-fpm too much control over the user’s files and they can be controlled with the group flag. You could also create the user with it’s own group such as kbeezie:kbeezie and just change the group of the web files to www-data where appropriate.

SSH Options

It is usually advisable to disable Root access via /etc/ssh/sshd_config with the following line:

PermitRootLogin no

However make sure you can log in with your new unprivileged user via SSH, and also make sure that you can `su` into root permission. On a FreeBSD system only a user belonging to the wheel group can su into root, and only a user listed in the sudoers file can use the sudo command. However on Debian/etc the user could have www-data as it’s own group and still be able to su/sudo as long as the root password is valid. Your password should be at least 12 characters long and contain digits, symbols and at least one capital letter. Do not use the same password for root and your web user.

Once you’ve verified that you’re able to obtain root status with the new user you can safely disable root log in via sshd_config and restart the ssh deaemon.

You should also change your default SSH port, which is 22. While a port scanner could probably find the new SSH port it is usually best practice not to use the default port for any type of secure access. Like before make sure you can log into the new port (you can configure sshd_config to listen to both 22 and another port to test this out).

SSH – PubKey Authentication

If you are on OSX or another unix/linux operating system, like I am, setting up pub key authentication is fairly painless. Logged in as your web user on the server you can run the following command:

ssh-keygen

The above by default will ask for a passphrase for your private key as well as a destination to save both the id_rsa and id_rsa.pub files (which will normally be ~/.ssh/). You can then copy your own user’s public key to an authorized_key file with the following command.

cp ~/.ssh/id_rsa.pub ~/.ssh/authorized_keys

Then on your own computer you can run the ssh-keygen command as well, copy your own computer’s public key from the id_rsa.pub file and add it as another line to your server’s authorized_keys file.

If you have `PubkeyAuthentication yes` listed in your sshd_config file with the authorized key path being that of .ssh in your home folder the server should allow you to log in without prompting you for a password. Just remember that if you chose not to use a passphrase for your private key then it is possible for anyone who obtains your id_rsa* files to log into your server without being prompted for a password.

You can even turn off password authentication completely and rely solely on public key authentication by setting `PasswordAuthentication no` in your sshd_config file. However keep in mind, unless you have another means of getting into your server you might get locked out if you lose your public/private key or access to the machine you use to log in (also not every SFTP or Terminal application supports pub key authentication).

I actually have public key authentication set up with my iPad using iSSH for quick server access on the go (and you do not need a jailbroken iPad for this).

On the next page are some Nginx and PHP specific configurations to hardening your installation.

Paypal IPN with PhP

August 19th, 2009

If you’re a freelance coder you most likely have a PayPal account. One of the most useful feature provided by PayPal for anyone looking to automate their ordering process is the Instant Payment Notification. This guide will show you how to utilize IPN with a PayPal ‘Buy Now’ button and PHP. Additional tips to further secure your ordering process are also discussed.

What is IPN?

In a nutshell IPN is PayPal method of instantly notifying your server of a payment. This can either be setup globally for all transactions on your account, or can be provided for a specific button or subscription.

Merchant Services

The IPN Process:

  1. Paypal sends details of the transaction to your server at the provided url.
  2. Your script compiles the information provided and sends it to Paypal’s verification server.
  3. Paypal’s server will either verify the information as valid, or will reject it as invalid.
  4. If considered a valid transaction, process the information as needed, otherwise discard and ignore.

Creating a Payment Button

Once logged into PayPal you should direct your attention to the Merchant Service.

The “Buy Now Button” will be sufficient for a one-time-purchase of an electronic or tangible product. The button can be setup as a simple buy now button for a single purchase with no options, or can be setup with drop down for product choices or other options. Either way there is two key settings you will want to use when creating your button.

Secure Merchant ID

Select Secure Merchant ID as means of identifying the account as opposed to an email address. This will not only help prevent automated spam attempts but will also help prevent fake transactions (more on that later).

IPN LocationYou will also need to add the following line to the advanced option. The url will be the destination notified in the event of a payment or related transaction. Normally you don’t want to call it something as obvious as ipn.php in the root of your site, rather bury the script in a folder and give it some other name such as txn1.php.



Tracking OptionIf you have multiple options that you will want to verify in your IPN script you can get PayPal to send you the specific Item ID by turning on the tracking option for the button:

Once you have created your button and generated the code to be used on the site you will want to setup your IPN script to save the necessary transactions.