The Archive

Reading google+ API feeds into your blog with PHP

Today I get this lovely email from google telling me all about the availability of the new Google+ Platform API. So naturally I have to check it out! For a while I have been using facebook and twitter feeds as simplistic blogs for some of my sites, but for my personal site, I would love to just use Google+.

I've created a simple example of how to setup and read your own public Google+ feeds.

Enabling API access and getting your API Key

  1. Head over to the APIs Console.
  2. Select Services
  3. Enable the Google+ API services. Without this you will not be able to access your feeds.
  4. Select API Access
  5. Under Simple API Access, grab your API Key. Hang onto this as we will need it to access any of the API services.

Getting your Google+ ID

If you don't already know where this is located, head on over to Google+, click on your image in the upper right, and click Profile. In the address bar, it will now have a link like

https://plus.google.com/106670447018211124292

Accessing your activity stream

Now that you have your ID and Key, you can go to the url of your stream. Your stream URL will look like this

https://www.googleapis.com/plus/v1/people/106670447018211124292/activities/public?key=[API Key]

Parsing this stuff in PHP

Thankfully all the responses are in JSON which makes it really easy to read the data. Each item is inside of items.

$id = '106670447018211124292';
$key = 'YOUR KEY HERE';
$feed = json_decode(file_get_contents('https://www.googleapis.com/plus/v1/people/'.$id.'/activities/public?key='.$key));

foreach ($feed->items as $item) {
  echo '<h2>'.$item->title.'</h2>'
    .'<i>'.date('F jS Y @ H:i:s',strtotime($item->published)).'</i>'
    .'<p>'.$item->object->content.'</p>'
    .'<br /><br />';
}

And now we can access our public junk! This feed contains some additional information other than just the title and content. Like if you entered a url, it will attach the url to it inside of object.

Terminal skipping while editing files over SSH in OSX Lion

So since I installed OSX Lion ive been having this crazy issue where editing a file in nano or vi would sometimes skip characters, or shuffle them around, particularly when I hit backspace. I would then save the file and open it and it would look nothing like what i entered. There is a solution!

  1. Go to Terminal > Preferences
  2. Go to Advanced
  3. Change the Declare terminal as: from xterm-256color to xterm-color

yay! I only has this issue while editing my CentOS boxes as that all i have anymore.

Monitoring site errors using Server Density and Python

I recently stumbled upon the site Server Density while upgrading all my servers and signed up for the service. It as a great pretty interface, a customizable dashboard, and pretty extensive alert capabilities. They also have a free trial so I suggest signing up. The one thing I did not like was the inability to scan for a specific string on the website to confirm if the website is still operational, like with host-tracker.com.

The good news is Server Density allows you to create python plugins to run at the update interval on any/all of your servers. This gives you free reign over your reports. Using this tutorial, I will explain how to manually create and install this and any other plugin. Or you can just download or install it from the below links.

GitHub - https://github.com/arzynik/sd-webcheck

Server Density Plugin - http://plugins.serverdensity.com/webcheck

Create your plugin

First, log into your SD account and go to Plugins. Then add a new plugin and name it WebCheck.

Add your plugin to your server

Next you will need to ssh into your server and create the directory

mkdir /usr/bin/sd-agent/plugins/

In that directory, create a new file called WebCheck.py. This will be the main and only file for the plugin. Grab the code from https://github.com/arzynik/sd-webcheck/blob/master/WebCheck.py and paste it into the file.

Configure your plugin

If this is your first plugin, you will need to add your plugin directory to your plugin config file. Open up /etc/sd-agent/config.cfg and replace the plugin_directory line with

plugin_directory: /usr/bin/sd-agent/plugins

WebCheck allows you to check the status of as many sites as you want. In order to check they you will need to enter a new config entry for each site, using the below fields:

  • name - The name and key you want to use on the SD interface. Remember that all "." will be replaced with "_" due to api limitations.
  • url -
  • find - The regex or string to search for. I typically just use an html tag but you can search for anything.
  • result - The result is the regex quantifier. Lets say you want to search from </html>, you would keep this True. Or lets say you want to search for Parse Error, you would then make this False.

Here is an example config entry

[WebCheck 1]
name: devin.la 
url: http://devin.la/
find: </html>
result: True

Restart the agent service to load the changes. Once it is restart it will begin sending data to SD

/etc/init.d/sd-agent restart

Settings up alerts

Because WebCheck only returns a boolean result, you probably wont be using it with graphs much. The alerts, however, are extremely useful. I personally recommend the iPhone push notifications, as they are uber fast.

  1. In your Server Density account, go to Alerts and click add new alert.
  2. Select your server that you installed it on, who is going to get it, and the alert type.
  3. For Check, select WebCheck.
  4. For Key, enter the name of your config entry. For me it is devin_la.
  5. For Trigger threshold, set it to Less than 1.
  6. Add it and you now know when your server is messed!

I love it!

One of my developers was working on a site and pushed some naughty changes to one of the sites I had just set up with this, and within seconds i got a push notification that it was broken. He fixed it pretty quick and it then alerted me when it was all fixed! Please share your cool plugins with me!

Importing and exporting cPanel accounts into other systems

Finally, after six years, I found an affordable host thats slightly faster than my box I built in 2005 then. goodbye cPanel -- hello vm. I curently have hosting accounts with Dreamhost, Media Temple, Linode, and Rackspace Cloud, though I wont get into that right now. For now lets focus on how to get all those cPanel accounts from my other box into one of my new CentOS vms. For these I will be using no admin panel of any kind, no webmin, VHCS, WHM, or cPanel. I simply dont need it as its just as easy to ssh in and run a command rather than loging into a web interface and clicking something.

What I need to import

Im going to need a few things imported

  • Users
  • Passwords
  • MySQL databases
  • MySQL users
  • Files
  • vhosts

The reason I will not be needing email is I have switched to Google Apps for everything. Its more reliable and faster than anything I have ever set up using a single box. If you haven't tried it yet I highly recommend it.

The import script

You can find my cPanel import script on GitHub

cPanel-import - https://github.com/arzynik/cpanel-import

Running the script

Running the script is easy and has a few options

Usage: cpanel-import.php file [options]
Ex: cpanel-import.php devin.tar.gz --ip=100.100.100.100 --forceuser
  • --username - Import as a different username.
  • --password - Overwrite the users password.
  • --ip - The IP to bind the vhost to.
  • --mysql-user - The root or super admin for mysql.
  • --mysql-pass - The root or super admin password for mysql.
  • --source - Where to find the file.
  • --dest - Where to find put the user.
  • --ignore - Comma separate list of files to ignore in homedir.
  • --httpd - Apache path.
  • --debug - Show commands.
  • --verbose - Show additional info.
  • --forceuser - If the user exists, keep going, and delete their home directory.

This will create the user, database, vhost, and pretty much set up the entire site for you, and restart apache. Easy!

Disabling Spotlight on OSX Lion

I have had so many problems with Lion, one of which is it keeps indexing spotlight. This sucks when i need something to actualy run. So ... lets disable it!

sudo mdutil -a -i off

Two days into Lion: touchpad gesture problems

One of the most common gestures i used to use is the 3 finger back and forward swipe on web pages, to navigate through my history. By default in lion, it changes this to a 2 finger gesture, but only works in safari. chrome is only set up to listen for the three finger gesture, and ONLY if you set up your system preferences to use three finger swipes instead of two. However, on my system three finger swipe was crashing every program. after a little digging it seemed it was some wierd system preference for multiclutch, when i didnt even have it installed.

the error

8 net.wonderboots.multiclutchinputmanager  0x0a9fcbba -[GestureShortcutsController executeSwipe:inDirection:] + 85
9 net.wonderboots.multiclutchinputmanager  0x0a9fc348 -[NSApplication(GESTURES) swizzledGestureEvent:] + 362

the solution

rm ~/Library/Preferences/com.wonderboots.MultiClutchBindings.plist
rm /Library/Application Support/SIMBL/Plugins/AirKeysInputManager.bundle
reboot

three finger swipe works again!

More OSX Lion issues

the other day when my computer booted up it gave me a nice NØ symbol telling me the kernel was messed up. thanks to the help of the tech guys at luma we booted up in firewire transfer mode, removed my trim support, and it all rebooted fine.

last night however, i didn't change anything, rebooted because my battery died, and was once again greeted with Ø.

now i've had alot of issues with lion so far, and only a few things positive, what was i realized when about to boot into my bootcamp partition, was that lion installs a recovery partition! this pretty much made my night. it allows you to access the internet, open terminal and modify files, AND reinstall lion. i had no idea what to change in terminal, and the internets provided me with no help. so i reinstalled, and 30 minutes later i was back and happy!

Embed video on Facebook Graph and wall posts

The Facebook Open Graph protocol is a simple and great way to add meta data to your existing webpages. It basically turns your external pages into a Facebook page! It's important to display the right information on a per page basis, whether that be a description, image, video, or song. By adding additional information to your headers, you can change the title, url, share image, and even add media like videos and music. Open graph optimization is an important skill to understand, regardless of your Facebook implementation.

Adding Open Graph

If you have not already read about how to add the Facebook Open Graph API to your page, check out my entry on Using Facebook's Graph API to optimize your Likes. It will get you started and explain how and why we add certain tags.

Using Flowplayer

Flowplayer is a great way to play locally hosted content, without the limitations of youtube or vimeo.

Below is the default modern skin config which is nearly transparent, and plays a h264 video hosted wherever you want, with a posterframe:

"playlist":[
  "http://lumapictures.com/assets/images/thumb/650x254/reel/montage.jpg",
  {
     "url":"http://lumapictures.com/assets/reel/luma/Luma_Reel_Montage_050611_650.mp4",
     "autoPlay":true,
     "autoBuffering":true
  }
],
"screen":{
  "height":"100pct",
  "top":0
},
"plugins":{
  "controls":{
     "borderRadius":"0px",
     "buttonOffColor":"rgba(130,130,130,1)",
     "timeColor":"#ffffff",
     "bufferGradient":"none",
     "sliderColor":"#000000",
     "zIndex":1,
     "backgroundColor":"rgba(0, 0, 0, 0)",
     "scrubberHeightRatio":0.6,
     "volumeSliderGradient":"none",
     "tooltipTextColor":"#ffffff",
     "spacing":{
        "time":6,
        "volume":8,
        "all":2
     },
     "sliderGradient":"none",
     "timeBorderRadius":20,
     "timeBgHeightRatio":0.8,
     "volumeSliderHeightRatio":0.6,
     "progressGradient":"none",
     "height":26,
     "volumeColor":"#4599ff",
     "tooltips":{
        "marginBottom":5,
        "buttons":false
     },
     "timeSeparator":" ",
     "name":"controls",
     "volumeBarHeightRatio":0.2,
     "opacity":1,
     "timeFontSize":12,
     "left":"50pct",
     "tooltipColor":"rgba(0, 0, 0, 0)",
     "bufferColor":"#a3a3a3",
     "border":"0px",
     "volumeSliderColor":"#ffffff",
     "buttonColor":"#ffffff",
     "durationColor":"#b8d9ff",
     "autoHide":{
        "enabled":true,
        "hideDelay":500,
        "mouseOutDelay":500,
        "hideStyle":"fade",
        "hideDuration":400,
        "fullscreenOnly":true
     },
     "backgroundGradient":"none",
     "width":"100pct",
     "sliderBorder":"1px solid rgba(128, 128, 128, 0.7)",
     "display":"block",
     "buttonOverColor":"#ffffff",
     "url":"flowplayer.controls-3.2.5.swf",
     "timeBorder":"0px solid rgba(0, 0, 0, 0.3)",
     "progressColor":"#4599ff",
     "timeBgColor":"rgb(0, 0, 0, 0)",
     "scrubberBarHeightRatio":0.2,
     "bottom":0,
     "builtIn":false,
     "volumeBorder":"1px solid rgba(128, 128, 128, 0.7)",
     "margins":[
        2,
        12,
        2,
        12
     ]
  }
}

There are many configuration options. They even have an online player setup. While this will not work directly for our purposes, we can use this to copy the the config object and feed it into our own Facebook config.

If you need a tool to easily read the config object, visit http://jsonformatter.curiousconcept.com/. This will put it in a human readable format.

Using Flowplayer In Facebook

Including the following in the HEAD of your html will make your page accessible on Open Graft with an video player right on Facebook and your wall posts.

<meta property="og:title" content="Luma Pictures">
<meta property="og:type" content="company">
<meta property="og:url" content="http://lumapictures.com/">
<meta property="og:site_name" content="Luma Pictures">
<meta property="og:image" content="http://lumapictures.com/assets/images/logo_share.gif">
<meta property="fb:app_id" content="1234">
<meta property="og:description" content="A full service visual effects facility located in Venice, California...">
<meta property="og:video" content='http://releases.flowplayer.org/swf/flowplayer-3.2.7.swf?config={"playlist":["http://lumapictures.com/assets/images/thumb/650x254/reel/montage.jpg",{"url":"http://lumapictures.com/assets/reel/luma/Luma_Reel_Montage_050611_650.mp4","autoPlay":true,"autoBuffering":true}],"screen":{"height":"100pct","top":0},"plugins":{"controls":{"borderRadius":"0px","buttonOffColor":"rgba(130,130,130,1)","timeColor":"#ffffff","bufferGradient":"none","sliderColor":"#000000","zIndex":1,"backgroundColor":"rgba(0,0,0,0)","scrubberHeightRatio":0.6,"volumeSliderGradient":"none","tooltipTextColor":"#ffffff","spacing":{"time":6,"volume":8,"all":2},"sliderGradient":"none","timeBorderRadius":20,"timeBgHeightRatio":0.8,"volumeSliderHeightRatio":0.6,"progressGradient":"none","height":26,"volumeColor":"#4599ff","tooltips":{"marginBottom":5,"buttons":false},"timeSeparator":"","name":"controls","volumeBarHeightRatio":0.2,"opacity":1,"timeFontSize":12,"left":"50pct","tooltipColor":"rgba(0,0,0,0)","bufferColor":"#a3a3a3","border":"0px","volumeSliderColor":"#ffffff","buttonColor":"#ffffff","durationColor":"#b8d9ff","autoHide":{"enabled":true,"hideDelay":500,"mouseOutDelay":500,"hideStyle":"fade","hideDuration":400,"fullscreenOnly":true},"backgroundGradient":"none","width":"100pct","sliderBorder":"1pxsolidrgba(128,128,128,0.7)","display":"block","buttonOverColor":"#ffffff","url":"flowplayer.controls-3.2.5.swf","timeBorder":"0pxsolidrgba(0,0,0,0.3)","progressColor":"#4599ff","timeBgColor":"rgb(0,0,0,0)","scrubberBarHeightRatio":0.2,"bottom":0,"builtIn":false,"volumeBorder":"1pxsolidrgba(128,128,128,0.7)","margins":[2,12,2,12]}}}'>
<meta property="og:video:height" content="254">
<meta property="og:video:width" content="650">
<meta property="og:video:type" content="application/x-shockwave-flash">

If you use URL Linter, you will be able to see the swf link at the bottom of the page. Using the like button from the page will show the video.

Using Facebook's Graph API to optimize your Likes

The Facebook Open Graph protocol is not just a great way to access data remotely on Facebook, but also a really easy way to add meta data to your existing webpages. By adding additional information to your headers, you can change the title, url, share image, and even add media like videos and music. Open graph optimization is an important skill to understand, regardless of your Facebook implementation.

Register Your App (website)

http://developers.facebook.com/setup/ Registering your website will give you a unite App ID that you will be able to use for Insights data, as well as linking all your graph data to. You can find your apps ID and Secret key at any later time at http://www.facebook.com/developers/ if you decide later that you need the secret key. The secret key is used for some more advanced api calls and functionality.

URL Linter

http://developers.facebook.com/tools/lint/ Perhaps the most useful you will want to know about is URL Linter. It will parse your current site, and give you information on how it is seen on the graph. As you add additional meta data you can come back here to keep checking that it is working correctly. Every time you get the new information from the linter, it will recache your site with Facebook.

Open Graph Implementation

You'll find that the documentation on Open Graph is really well done, as long as you know where you are looking.

Open Graph documentation: http://developers.facebook.com/docs/opengraph/

To start, you will want to add some meta properties into the HEAD of your html document. The required properties are

  • og:title - the title of your object. Bear in mind this will be unchangeble after it receives 50 likes.
  • og:type - the type of the object. like movie, company, article, website, all types
  • og:image - the image that people will see. it must be 50x50 and only PNG, GIF, or JPG. you can add multiple images to allow the user to select when sharing.
  • og:url - the canonical url for the object. this will most likely be the current page url.

Other than the required tags, there are a couple that you should probably add.

  • og:site_name - a human readable version of your site url
  • fb:admins or fb:app_id - I suggest you enter your app id here instead of using your user id. You can get the app id after registering your site.
  • og:description - the description of the object that will display on posts.

Here is a basic example of how I made Luma Pictures look on non media rich pages:

<meta property="og:title" content="Luma Pictures">
<meta property="og:type" content="company">
<meta property="og:url" content="http://lumapictures.com/">
<meta property="og:site_name" content="Luma Pictures">
<meta property="og:image" content="http://lumapictures.com/assets/images/logo_share.gif">
<meta property="fb:app_id" content="1234">
<meta property="og:description" content="A full service visual effects facility located in Venice, California...">

**Adding the like button

The easiest way to add a like button is to use the pre-made Facebook plugin.

Facebook Social Plugins / Like Button: http://developers.facebook.com/docs/reference/plugins/like/

The plugin generator is pretty self explanatory. I recommend using the xfmbl version, as it allows the user to add a comment to their wall. When including XFMBL with any page, you will need both the fb-root object, and to include the script source like this:

<div id="fb-root"></div>
<script src="http://connect.facebook.net/en_US/all.js#appId=1234&amp;xfbml=1"></script>

You can add this anywhere in your document, but I reccomend adding the script in the head, and the div in the body. Next you need to add the like button:

<like href="lumapictures.com" send="false" layout="button_count" width="400" show_faces="false" font="trebuchet ms"></like>

If you did everything right you should now have a like button! Check the URL Linter to see if all your tags are picked up, and if they are, go ahead and like your own page!

Keep in mind that if you are running locally, the like button may not show up.

Base64 encoding and embedding images in CSS

One problem I always have with mobile development is making images load faster. Having lots of images can make the page extremely slow, and with the iphone 4 using double rez images, its even slower.

Theres a couple steps to take to optimize the images.

First I recommend using no image tags, and only using the url() property in CSS to display your images.

.img-social-facebook {
  background: url(/assets/images/gurgo/social-icon-facebook.png) no-repeat;
  background-size: 100%;
  width: 32px;
  height: 32px;
}

Second, encode all your url entries using base64 encode. This allows each image to be inside your main css.

.img-social-facebook {
  background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABBCAYAAABhNaJ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NUVBQ0RBQ0YxQjY0MTFFMDk0Q0RDQThBQ0E4QkZCQjAiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NUVBQ0RBRDAxQjY0MTFFMDk0Q0RDQThBQ0E4QkZCQjAiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1RUFDREFDRDFCNjQxMUUwOTRDRENBOEFDQThCRkJCMCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1RUFDREFDRTFCNjQxMUUwOTRDRENBOEFDQThCRkJCMCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Puxf/AUAAAioSURBVHja3JxdaCPXFcf/M3M10kiyPm3Z3mgdr+31bjbtZrchFBooDqVks4Vt0pY+9CW7Dyl9al/yUALJQ1sIlFJC6EuhYBeSl1La5KXQBhpDCQTCtl1C6s2yTpTNru21HUuyLMmar9t7RzP6yq40Wo3HHxdkW9Kdmfs795z/PefOYIFSinu1J7/3yrPs1/NEInMSIQkcwmboekE39EX25x/f+8sv3rpXH6HTAE8+9/Ic+zUfjkQnw9EY5EAQh7mpWg2VnW1Uyjs59vbKe3/95eJ9DcDgL5OAPJ9KZ5CIhvHo1DimH84gGY8cSvh8sYzlz9bx0SerKOxUsPXFOnRN5UZY+JIBOLwSjs7HY0l8dSaLJx47gaPUPrj2KT68eRvF7TyqlZ2GESwDcLcnJPBuangMTzwyiZnJUeug4nYVt1YLKJZrhxI6HgliYjyBeEyx3t/M3cUHSzlsba5B17WneDgQu+98IjmC2WwGx0ZT+CJfwUfLd7F1SMGdtlnaxfJaESlmiEenRy222VIF1w0Dmxsr86zLCemda8azSij8k2Q8gcdOPYwCm/WrH69ip6YdGfevqjpWNkqIhgLIpGNY3ShCVWuJ37/5j2tS9tQ3X43Gk6cnRochSgQffroB3aQ4as1gob5eKCOqyDzuUazWmBaUQ4RIEot/GZGwguU7W9ANE0e16Qa1GMdTEXBmi10QxQQXwqpqMrfXnaXhSBqACoLFyFk5M2cnHFUSRWyVqqxHHZweVRew+TgrZ+bvSJ1WwC4TPScnOKIOAOYAVtu1BF6wZprwnwaLey4SHPwgsZ/IRHH+zCjLS5JIpRRXx/z8t//q6gDcBobNzGmtPMBk3xgHSPmPJRRcnJvCzHSqOXiXU0N7uC+1f5h2P0sDzAPk9+emh3Hp6RkobM2mbFLCSgihoIxAINBXnPdqpm0MAsv16YFw/fNTafzw0mnrbw6eirNqlMH3pXOu9ZBaxiLOG3OfQ+DM8QR+wOC5UA2n04ixavRBmlsOJ1SIWTfEvnpAOCDh+9+ZZfACy9dHEAqFHnyl62NF5OxEAG24w361C9+YQDgsIzs2GHw/GmAlQnwVsPrbS+B+NCUg4vFzY0gm4oPD96PlNjOhoLY70LZkwTfVP5m2wIeTcW+SvR5B4BioHvaOCLYc5rcnTGYTSMSinp3PrZY7xFYqvJ8SMMuSnURsqGe/Qr6CV3/3TyytFLwrC5xUeD9FMJl0N/tv/Pkqlu7kPayLaDMVridC/htgOBpk6u8ux7+e2/R0jI08YD8rwLhCIBPJVd/cZmkPqmOnGOpQDuqTNfhlJEI8KXLclcPClzJG0nly6mNOIEkSIkrItbG8WCQdI7SEgG2dlmXELw8QIPQds4NeURTajUrq2TOFaH9oHJDK8EFz/K74lHMKLWdsrALNDJCimRUO2k5mwrjyo7MDn6dQKuNXP/u6q74vvfZ+Vw1ocNqIhFKnMPDY3M7OS4/UjLpJ3fop12nXCGjOv1MLOEe0ioNXQSCRAE5OTvSo33vfh1BCwZ7nafK/31VIhbZih9a3xFpF0MucgIvcUDg0sLhJQu/zuBq70MHZuiUGYQ/ygH3IMKlLCzS2xKg9ULFjp8Qr5aY+p5jdxs6HIrZNTuPGCPtCaF8ePPMA33Ns2vU7sVUDWqtBocVKHkaAO5X3OL3uKoKd1aAz341VAF4awH8P6GqATk5HBNFiGZ4tmR4mAn6LYLexC635jj3TpK6NLDbEpmW8UsFrn+fx9Atv3Pf7s9kkfv3KRVfnuvDjNwefDlZ5O5wC2qrBlsLEx3sE/VSeHhWDDc6WVJjaZSI81wB3ImkOHNv9rA9CSwzYGyKCdaNQ2Ie47Sft9mZMTQ0w7UAgBhUh0WYe0HqPwI8V2+0q4cWYaAcnZ2chYMI0NOaK9eeD+N+mrvtiAINf1yWXqQ/+2J4p0jZOzk4MdmIBNeha/aFIXa2ylz8G0FTFtQbwcQ3adBbxuibb56uBsxPD0Nn6uMveOAbYhapqPhmg5joEVA8MEESAcdarSkNjzMwbSDr7EIYnZnFqZsr6IrxVRVk1fDHAdFp2LW1feerCwNeLyBKO288a6eOfYPPWjeYyKMLnu6JOzuFzqty6J2gtgxqL92qxjNp22fq4XNhBqeaPBlRJyLUBSvntwS8YJKjZW0Cc2dRYCJj8ETmm+tQwbA3QoGn+hIDBrmO6FEFNG3xSdIE2OOvMZnNP0FkfRRzdJrbte9ipMP+gxmZdcIohX5+Q8Ldc5mwOZ81e6axnhCqaam08AvBdCv00AGdzODkzsXaIKC3wdKy6W60/ohaRfTeAm5cXjbNxRs7KmTm7aKiVRW6V9XzBum+WiSsIiIJf9FaO7+Y1aONMnI2jcVbOzNlFQ1PfFvUKcnc3rM2CABFxOjPkpwu4ew3YOBNn44wWK2O22P/999cWzEo+V1NVfJz73NozS8dCODseg+yDJ+x1CHAGzsKZOBtn5KycmbNbO0L6buWKHNx+98YKSxdDQUw+NIYR5i7JaBB3C1VslGoosYRJ9XiHd6/uGyhspsNEwshQEKMJBUSqL+65O2u4sbKGgLoNlTHb+4T1ATx+8cXLUmx0XheDeCQ7jjPTk3s++5mEjEtz4676/uGtzwa61v+Wc1i6vQqZqtCKa1eu/u03C409Qd74B1975kVmvsT80m3g1vomZo6N4dhICtFIeE8MIPaRdT1INO6UK0zwirh+6w6qzO1lrQStXGjAt3mA085/+6dzgqzMC0pykspN8GSYrQ6S5KkBjqdDeO5bWVd9X//TTfdlNkt385V6+Swxw3HBo5VCTmNu/593Xl9sywQ7D7Y7nGCGuMwM8V2BBOeYiRJFNchmzFsDDAkK8gV3K85Wfr0PcWE5Pt/foGaBGuqiult9m3Et3KvrfR/Rsg9YaPGMc6yM8PT/CGiEw0+565tf6efUBTb+/7rp+H8BBgDWSfs1k+ZuiQAAAABJRU5ErkJggg==) no-repeat;
  background-size: 100%;
  width: 32px;
  height: 32px;
}

The problem with this is encoding all the images into a css file can be hella time consuming. The solution: write a script to do it for you!

./cssbuild.php gurgo.src.css gurgo.css

Thats all you need to convert a css any time you modify it. This script will read though the css for whatever regex you want, encode the images, and output it to the second file. Source:

#!/usr/bin/php
<?php

$file = dirname(__FILE__).'/../public_html/assets/css/'.$argv[1];

$lines = file($file);
foreach ($lines as $key => $line) {

  if (preg_match('/url\(\/assets\/images.*\)/i',$line)) {
    $css = preg_replace('/^.*url\(\/assets\/images\/(.*)\).*$/','\\1',$line);
    $file = new Caffeine_View_Helper_ImageBase64(dirname(__FILE__).'/../public_html/assets/images/'.trim($css));
    $lines[$key] = preg_replace('/url\((.*)\)/','url('.$file.')',$lines[$key]);
  }
}

$writefile = dirname(__FILE__).'/../public_html/assets/css/'.$argv[2];
file_put_contents($writefile, $lines);
echo "done\n";

class Caffeine_View_Helper_ImageBase64 {
  public function __construct($image) {
    $this->_file = $image;
    $fp = fopen($this->_file,'rb', 0);
    if (!$fp) throw new Exception('Could not open '.$this->_file.' for reading');
    $picture = fread($fp,filesize($this->_file));
    fclose($fp);
    $this->_encoded = base64_encode($picture);
  }

  public function output() {
    return 'data:'.mime_content_type($this->_file).';base64,'.$this->_encoded;
  }

  public function __toString() {
    return $this->output();
  }
}

Reasons NOT to use this method: If you have a page that has images only used on a single page, I instead recommend using a base64 encoded img tag on that page. If that image is going to be reused, using a normal image is most likely your best bet.

Facebook's fql.multiquery for faster load times

While building my new blog's comment system I decided to just be lazy and use some other api. For this I decided to use the facebook API. I had previously built a comment system using a modified PHP API, but this time I had no reason to need to run it through my own backend.

Adding the comment box is extremely simply and only needs to use the fb:comments XFBML. it looks a little like this.

<fb:comments
  url="http://devin.co/blog/ID"
  xid="devin-blog-ID"
  numposts="100"
  width="650">
</fb:comments>

By specifying a url we can make sure that the likes for the page stay the same even if the title or url of the page changes slightly.

Now that I can receive comments and likes, I needed to get a summary of this info for multiple blog entries on the main blog page. I could have easily added a like button or looped through all the blog entries and queried them with fql.query, but I wanted to get all the info all at once.

My html markup looks something like this

<div class="blog-entry" xid="devin-blog-ID">
  <div class="blog-info-item blog-info-comment">
    <span class="blog-info-text">0</span>
  </div>
  <div class="blog-info-item blog-info-like">
    <span class="blog-info-text">0</span>
  </div>
</div>

Next I want to loop through all of the blog elements and build a multiquery for fql. Then loop through all the results and update all the blog entries with their counts.

q = '';
// loop through each blog element
$('.blog-entry').each(function(i) {
  // add the comments count query
  q += 
    (q ? ',' : '') + 
    '"blog-query-' + (i*2+1) + 
    '": "select count from comments_info where xid=\'' + $(this).attr('xid') + '\' and app_id=' + FB._apiKey + '"';
  // add the like count query
  q += 
    ',"blog-query-' + (i*2+2) + 
    '": "select like_count from link_stat where url=\'http://' + window.location.hostname + '/blog/' +
    $(this).attr('xid').replace(/^.*-([\d]+)$/,'$1') + '\'"';
});
q = '{' + q + '}';

// send the api request
FB.api(
  {
    method: 'fql.multiquery',
    queries: q
  },
  function(r) {
    $('.blog-entry').each(function(i) {
      for (x in r) {
        if (r[x].name == 'blog-query-'+(i*2+1)) {
          // assign the count of the comments
          $(this)
            .find('.blog-info-comment .blog-info-text')
            .html(r[x].fql_result_set.length ? r[x].fql_result_set[0].count : '0');
        } else if (r[x].name == 'blog-query-'+(i*2+2)) {
          // assign the count of the likes
          $(this)
            .find('.blog-info-like .blog-info-text')
            .html(r[x].fql_result_set.length ? r[x].fql_result_set[0].like_count : '0');
        }
      }
    });
  }
);

Bulding an archive of images dynamicly

Recently I had a need to gather a group of images, add a watermark, and zip them up so a user could download them. Instead of create all of these zip files each time anything is changed, I instead wrote a class to do this all for me.

This class requires Cana_Zip and Cana_Thumb

class myZip extends Cana_Zip {
  public function __construct($params = array()) {
    parent::__construct($params);
  }
  public function create($params = array()) {
    // here i queried our database to get a list of files.
    // you could also loop through a directory
    $files = array();

    // create the thumb object
    $thumb = new Cana_Thumb(array(
      'path'       => 'images/',
      'cache'     => 'imagecache/',
      'watermarkSrc'    => 'images/watermark.png',
      'watermark'    => true,
      'format'    => 'jpg'
    ));

    $zipedFiles = array();
    foreach ($files as $file) {
      // write the thumb
      $out = $thumb->writeThumb($file->image);
      // add the cached image path to the zip
      $zipedFiles[$file->image] = $out['file'];
    }

    // write out the zip file
    $out = $this->create($zipedFiles, array(
      'name' => 'myzip.zip',
      'destination' => 'zip/'
    ));
  }
}

Once our class is set up, we now need to use it.

// create the zip
$zip = new myZip(array(
  'destination' => './zip'
));
// give it to the user
header('Location: /zip/myzip.zip');

e338 & ghettogloss

Loic needed a simple separate webpage to display his print for a ghettogloss event. This entire page is controlled by a single easily editable xml file that he manages over FTP. Example:

<image>
    <title>Brandon Mouche, Festa do Espirito Santo.</title>
    <size>4 colors silkscreen</size>
    <description>4 colors silkscreen (gold, copper, light blue & Sienna)</description>
    <framed>unframed</framed>
    <price>$220</price>
    <file>BrandonMouche-festa.jpg</file>
    <file>BrandomMouche001.jpeg</file>
    <file>BrandomMouche004.jpeg</file>
    <file>BrandomMouche01.jpeg</file>
    <file>BrandonMouche05.jpeg</file>
    <sold>no</sold>
</image>

osFileManager on Github

osFileManager is a PHP Filemanager origionaly written in 2003, and with little improvements since. Includes User CP, Admin CP, and many basic file creation/modifying tools. Alows user defined themes as well. File Functions include: List, Open, View, Edit, Create, Upload, Rename and Move. User Functions include: Change password, and Change color scheme. Admin Functions include: New user, Edit user, Delete user.

https://github.com/arzynik/osFileManager

Custom photo gallery with phpFlickr

phpFlickr is a PHP library for use with flickr's powerful API. Recently I had a need for a very simple photo gallery with a minimal budget, so we decided to go with hosting all the images on flickr and accessing them remotely.

The biggest problem with this idea was what we wanted to do with the photos. Flickr allows you a decent range of size options to display. None of these sizes where exactly what we wanted. In order to resize these server side we needed to first download the images, and resize them. This is where caching came into play. By creating a wrapper for phpFlickr and caching the images and requests, we can dramatically speed up the time it takes to get the data from flickr, and to process the images.

In this example we will be using phpFlickr 2.3.1.

First lets make a very basic local cache class. You can easily modify this class to work with a database rather than flat file.

class myCache {
  public function __construct($params) {
    // the directory to store the cache files
    if (isset($params['dir'])) {
      $this->dir = $params['dir'];
    }
    // expiration duration
    if (isset($params['expire'])) {
      $this->expire = $params['expire'];
    }
  }

  public function cached($fileName, $expire = null) {
    // get the valid state of the file
    if (file_exists($this->dir.sha1($fileName).'.cache') && filemtime($this->dir.sha1($fileName).'.cache') expire)) {
      return true;
    } else {
      return false;
    }
  }

  public function read($fileName)  {
    return unserialize(file_get_contents($this->dir.sha1($fileName).'.cache'));
  }

  public function write($fileName, $file)  {
    return file_put_contents($this->dir.sha1($fileName).'.cache', serialize($file));
  }
}

Next we to create an object that extends the flickr library. We will create the cache object instance for reference inside our flickr object.

class myFlickr extends phpFlickr {
  public function __construct($params = array()) {
    $this->cache = new myCache(array(
      'dir' => './cache_dir'
    ));
    if (isset($params['flickrId']) {
      $this->flickrId = $params['flickrId'];
    }
    if (isset($params['apiKey']) {
      $this-> apiKey = $params['apiKey'];
    }
    // use the api key generated from your flickr account
    parent::__construct($this->apiKey);
  }
}

Now for the caching. We need to access the photosets_getList and photosets_getPhotos methods of the flickr library and cache their output.

public function sets() {
  // check the cache status
  $file = 'flickr-portfolio-all-data';
  if ($this->cache->cached($file)) {
    $setpics = $this->cache->read($file);
  } else {

    $setpics = array();
    // get a list of photo sets from a specific user id
    $sets = $this->photosets_getList($this->flickrId);
    foreach ($sets['photoset'] as $set) {
      // get the photos in each set
      $pics = $this->photosets_getPhotos($set['id']);
      $setpics[$set['id']] = $set;
      $setpics[$set['id']]['pics'] = $pics['photoset']['photo'];
    }

    // write the cache
    $this->cache->write($file, $setpics);
  }

  return $setpics;
}

Now that we are all set up, lets use it! For this example we will only display a specific photoset.

// set up our object
$flickr = new myFlickr(array(
  'flickrId' => '48882192@N03',
  'apiKey' => 'my_api_key'
));
// get all the sets
$sets = $flickr->sets();
// loop through only a specific set
foreach ($sets['72157624072392212'] as $set) {
  print_r($set);
}

Safari Developer Tools

Developing in safari has always been somewhat of a pain. But the developer menu makes things a little easier.

  • Close Safari
  • Open Terminal
  • Enter the following into Terminal and press enter

defaults write com.apple.Safari IncludeDebugMenu 1

  • Open Safari and you now have a fancy develop menu!

QuickControl: a Mootools Quicktime controller

QuickControl was made to replace flash for quicktime video playback on webpages. The QuickControl library requires the Class, Options, Events, and Slider libraries from Mootools.

Creating a new controller is as easy as adding the code below to your domready function.

new QuickControl('http://movies.apple.com/movies/sony_pictures/district9/d9-fte_h.640.mov', {
  id: 'QuickControlVid',
  width: 640,
  height: 340,
  style: 'QuickControlCustom',
  container: 'QuickControl',
  attributes: {
    autoplay: 'true',
    enablejavascript: 'false'
  }
});

Take a look at the full library on github: https://github.com/arzynik/QuickControl

Luma Pictures

Luma Pictures is the company's website and portfolio of works. Their works include True Grit, Thor, Green Hornet, Harry Potter, Wolverine, Percy Jackson, Underworld, and much more.

I did not design the site but I did build it. It is a simple Mootools and Caffeine implementation. This is just one of the projects I have worked on while working for Luma.








Working with CSS Sprites & PHP

A CSS sprite is a single image that contains many images images that you want to display on the page. By using the background position properties of an element we can select a specific image from a single one. The reason we do this is to load multiple images without having to make multiple HTTP requests. Thus speeding up the performance of our page.

Take a look at the CSSsprite library over on github:

https://github.com/arzynik/CSSsprite

First lets make an array of our images. Each image array needs a file and can have an array of style attributes.

$images['devin']['file'] = 'devinsmith.logo.png';
$images['devin']['style']['margin'] = '0 auto';

Next we'll create our sprites with the CssSpriteMap class

$sprite = new CssSpriteMap();
$sprite->setImages($images)
  ->setInPath('./')
  ->setForceRefresh(false)
  ->setCssPrefix('csssprite')
  ->setOutPath('./cache/')
  ->setOutUrl('cache/')
  ->create();

Now that we have our object all set up we can now use it in a simple webpage.

<style><?=$sprite->getCss()?></style>
<div class="<?=$sprite->getImg('devin')->getCssPrefix()?>devin"></div>

PHP PayPal Mobile Checkout Library

This PHP library is a basic implementation for the paypal mobile checkout NVP interface. For simplicities sake I am going to create a single controller for the checkout that will check to see if we have a token or not, and then process it accordingly.

We will require the Cana_Paypal libraries for this. Go ahead and grab them over on github:

https://github.com/arzynik/cana-paypal

First lets set up our object. You will need to create an api user/pass/sig on paypals site, or with the sandbox.

$paypal = new Cana_Paypal_Mobile;

$paypal->setApiVersion('3.0')
  ->setApiUrl('https://mobile.paypal.com/wc?t=')
  ->setApiUser('api_user')
  ->setApiPass('api_pass')
  ->setApiSignature('api_sig');

We next need to call the SetMobileCheckout method on the api. In this example we will switch between Set and Do checkout based on if there is a token. You can see how this all fits together by downloading the library and example at the bottom of the page.

if (empty($_GET['token'])) {
  // SetMobileCheckout
} else {
  // DoMobileCheckout
}

SetMobileCheckout is what we do first, then send the user off to paypal.

$response = $paypal->setAmt('5.00')
  ->setCurrencycode('USD')
  ->setDesc('Test Item')
  ->setReturnurl('http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'])
  ->setCancelurl('http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'])
  ->setCheckout($optionalParams ? optionalParams : null);

if ($paypal->getResponseStatus()) {
  // forward user off to paypal
  header('Location: '.$paypal->getApiUrl().urldecode($paypal->getToken()));
} else {
  // error
  print_r($response);
  exit;
}

When they come back, they will have a token. We take that token and authenticate it back to paypal to finish the transaction.

$response = $paypal->setToken($_GET['token'])
  ->doCheckoutPayment();

if ($paypal->getResponseStatus()) {
  // success!
  print_r($response);
  exit;
} else {
  // error
  print_r($response);
  exit;
}

PHP Short Tags

Short tags aren't going anywhere, so why not use them! Short tags are best used in template files in frameworks like Zend Framework.

These are short tags

<? $items = array('apple','pie','bacon'); ?>
<? foreach ($items as $item) : ?>
  <? if ($item == 'bacon') : ?>
    <b><?=$item?></b>
  <? else : ?>
    <?=$item?>
  <? endif ; ?>
<?endforeach ; ?>

Enable in php.ini

short_open_tag = On

Enable in .htaccess

php_flag short_open_tags on

Hooray for less typing!

iPhone

Purchased on release day cheaper than list price. Buahahah. As you can see i couldnt help but to play with it before the pix.

Dublin Breakfast

So adam ford of rbzdesign sent me some dublin dr. pepper in the mail and i got it when i woke up today. For some reason they do not ship glass to california so he sent it instead.

First, the bottles are fucking small 8oz. Its like fucking squeezits. I think I got like 4 sips out of my first bottle. These things are SO sweet. They are made from pure cane sugar. The taste difference is more than just more sweet. Its a totally different wonderful flavor. I am sure having it in the glass bottle helped bringg out its real flavor and not the flavor of aluminum I love so much.

Thanks Adam! I own you one! Best breakfast EVER.

One Laptop Per Child board

I just got a board print from one laptop per child. Pretty sweet looking. Small as hell. Once i get it plugged in and load up fedora on it I will post some more (lies).

For developers, if you want one of these get on their wiki and find the developers section. You can obtain one for free. However you might be required to send it back when you are done.

Some AJAX basics

The concept of AJAX is simple, data transport is not reliant on the time the data is sent out to be received. What does this allow you to do? Well it lets you query your php, asp, perl, py, or whatever pages and get data without having the user refresh. This can be extremely useful.

There are countless libraries that do ajax for you, and personally I believe you should be using at least one of them. But if you for some reason don't want to or cant, here is a very basic breakdown of how to build an AJAX request without any libraries.

First we need a page to grab the html or xml or whatever. In our case it will be a string but I will explain that later.

function loadHTML(url, dest_var){
  dest_v = dest_var;
  if (window.XMLHttpRequest){
    req = new XMLHttpRequest();
    req.onreadystatechange = processStateChange;
    req.open("GET", url, true);
    req.send(null);
  } else if (window.ActiveXObject) {
    req = new ActiveXObject("Microsoft.XMLHTTP");
    if (req) {
      req.onreadystatechange = processStateChange;
      req.open("GET", url, true);
      req.send();
    }
  }
}

This function will accept "url" and load the page remotely. Remember that you can not access pages on different domains. So if you are on www.google.com you can not open the url google.com or mail.google.com. Only www.google.com. There is, however, a way to get arround this. read more.

The variable "dest_var" is the function we will execute afterwards. Next we need a function so figure out if the page has been loaded.

function processStateChange() {
  if (req.readyState == 4) {
    if (req.status == 200) {
      response = req.responseText;
      eval(dest_v + '(response);');
    }
  }
}

All this function does is says "If the page is done loading then execute the function and send it the response we got".

So what would the response function look like? Very very basic.

function responseReturn(ret) {
  var ret_lines = ret.split(';');
  alert("Your first item was: " + ret_lines[0]);
}

This takes the response we got. With this we assumed that we where getting the response back in a format that separates values by ";". No this is not good programing but it is fast dirty and doesn't require your users to download half a MB of javascript files. When passing back your response from the server, pass back an array separate by your character like this "valuea;valueb;valuec". This keeps the javascript from eating up all your cpu parsing stupid things on old computers.

Thats great by how about a way to call the function? Here.

function faxJax() {
  loadHTML("http://jmp2.net/?a=make&url=www.google.com","responseReturn");
}

Yay now i gave you an example in the completely wrong order.

Finished Product

function faxJax() {
  loadHTML("http://jmp2.net/?a=make&url=www.google.com","responseReturn");
  return false;
}
function responseReturn(ret) {
  var ret_lines = ret.split(';');
  alert("Your first item was: " + ret_lines[0]);
}
function processStateChange() {
  if (req.readyState == 4) {
    if (req.status == 200) {
      response = req.responseText;
      eval(dest_v + '(response);');
    }
  }
}
function loadHTML(url, dest_var) {
  dest_v = dest_var;
  if (window.XMLHttpRequest) {
    req = new XMLHttpRequest();
    req.onreadystatechange = processStateChange;
    req.open("GET", url, true);
    req.send(null);
  } else if (window.ActiveXObject) {
    req = new ActiveXObject("Microsoft.XMLHTTP");
    if (req) {
      req.onreadystatechange = processStateChange;
      req.open("GET", url, true);
      req.send();
    }
  }
}

Cheers!

Using linux and opera for dynamic web screenshots

I recently had a need to take screenshots of webpages dynamically. What I came up with was a simple PHP cron and que that calls a very basic shell script.

#!/bin/bash
export DISPLAY=":1"
/usr/bin/opera -remote "raise()"
/usr/bin/opera -remote "openURL($1)" & 
/bin/sleep $6
/usr/bin/import -window root -display :1 -resize $3x$4 -quality $5 "$2"

I then call this script with a command in php

screenshot.sh www.url.com /tmp/tempfile.jpg 1024x768 75 15

This will take a load www.url.com for 15 seconds and save a 1024x768px screenshot to /tmp/tempfile.jpg

PHP Power Browse for PHP5

PHP Power Browse is a complex file browsing script which allows the user to see basic file stats and view source of allows file types. It also includes simple configurable security on specified files and directory, as well as customizable colors and feel. All images are built into the script and only one file is needed!

Installation:

  1. Copy the 'browse.php' to your web server.
  2. Modify the config in the top of 'browse.php' to meet your needs.
  3. Run the script with 'http://server.com/browse.php'

Single file visitor tracking using PHP

Recently I have been asked to create a simple and free visitor tracker for a website. Normally I would track this with apache access logs, or using PHP to add the visitors to the database. But with this situation they have no access to PHP or the access logs.

In this example ill be using the PHP GD libraries so make sure those are installed unless you plan on using imagemagick.

/*****************************************************************/
/*                                                               */
/* Hydrus stat-track (lite)                                      */
/*  Lite version only includes a counter script                  */
/* Created by Devin Smith, July 26th 2003                        */
/* php@arzynik.com                                               */
/* http://hydrus.sourceforge.net                                 */
/*                                                               */
/*****************************************************************/


/* Database Config */
/*  This MUST be filled in correctly */
$sqlserver = "localhost";
$sqluser = "root";
$sqlpass = "";
$sqldb = "hydrus";
$sqlpref = "hydrus_";

/* Default counter values */
/*  These setings can be changed by calling the script with the vars in the url */
/*  ex: 'http://domain.com/script.php?b=000000&f=FFFFFF&z=3&s=1' */
$bgcolor = "000000"; /* Background color of counter (000000 - FFFFFF)*/
$fgcolor = "FFFFFF"; /* Text colot of counter (000000 - FFFFFF)*/
$size = 5;           /* Size of test font (1 - 5) */
$show = 1;           /* Display counter (0 or 1)*/

/* Do not modify below this line unless you know what you are doing */
$dbh=mysql_connect ($sqlserver, $sqluser, $sqlpass) or die ('I cannot connect to the database because: ' . mysql_error());
mysql_select_db ($sqldb);

if ($b) $bgcolor = $b;
if ($f) $fgcolor = $f;
if ($z) $size = $z;
if ($s) $show = $s;

if (getenv(HTTP_X_FORWARDED_FOR)) $ip = getenv(HTTP_X_FORWARDED_FOR);
else if (getenv(HTTP_CLIENT_IP)) $ip = getenv(HTTP_CLIENT_IP);
else $ip = getenv(REMOTE_ADDR);

$httref = getenv(HTTP_REFERER);
$http = "http://".$HTTP_HOST.$REQUEST_URI;
$date = date("YmdHis");

/* Browser detect */
if((ereg("Nav", $_SERVER["HTTP_USER_AGENT"])) || (ereg("Gold", $_SERVER["HTTP_USER_AGENT"])) || (ereg("X11", $_SERVER["HTTP_USER_AGENT"])) || (ereg("Mozilla", $_SERVER["HTTP_USER_AGENT"])) || (ereg("Netscape", $_SERVER["HTTP_USER_AGENT"])) AND (!ereg("MSIE", $_SERVER["HTTP_USER_AGENT"]) AND (!ereg("Konqueror", $_SERVER["HTTP_USER_AGENT"])))) $browser = "Netscape";
elseif(ereg("MSIE", $_SERVER["HTTP_USER_AGENT"])) $browser = "MSIE";
elseif(ereg("Lynx", $_SERVER["HTTP_USER_AGENT"])) $browser = "Lynx";
elseif(ereg("Opera", $_SERVER["HTTP_USER_AGENT"])) $browser = "Opera";
elseif(ereg("WebTV", $_SERVER["HTTP_USER_AGENT"])) $browser = "WebTV";
elseif(ereg("Konqueror", $_SERVER["HTTP_USER_AGENT"])) $browser = "Konqueror";
elseif((eregi("bot", $_SERVER["HTTP_USER_AGENT"])) || (ereg("Google", $_SERVER["HTTP_USER_AGENT"])) || (ereg("Slurp", $_SERVER["HTTP_USER_AGENT"])) || (ereg("Scooter", $_SERVER["HTTP_USER_AGENT"])) || (eregi("Spider", $_SERVER["HTTP_USER_AGENT"])) || (eregi("Infoseek", $_SERVER["HTTP_USER_AGENT"]))) $browser = "Bot";
else $browser = "Other";

/* OS detect */
if(ereg("Win", $_SERVER["HTTP_USER_AGENT"])) $os = "Windows";
elseif((ereg("Mac", $_SERVER["HTTP_USER_AGENT"])) || (ereg("PPC", $_SERVER["HTTP_USER_AGENT"]))) $os = "Mac";
elseif(ereg("Linux", $_SERVER["HTTP_USER_AGENT"])) $os = "Linux";
elseif(ereg("FreeBSD", $_SERVER["HTTP_USER_AGENT"])) $os = "FreeBSD";
elseif(ereg("SunOS", $_SERVER["HTTP_USER_AGENT"])) $os = "SunOS";
elseif(ereg("IRIX", $_SERVER["HTTP_USER_AGENT"])) $os = "IRIX";
elseif(ereg("BeOS", $_SERVER["HTTP_USER_AGENT"])) $os = "BeOS";
elseif(ereg("OS/2", $_SERVER["HTTP_USER_AGENT"])) $os = "OS/2";
elseif(ereg("AIX", $_SERVER["HTTP_USER_AGENT"])) $os = "AIX";
else $os = "Other";

$msql = mysql_query("INSERT INTO ".$sqlpref."stats VALUES ('','$http','$httref','$ip','$os','$browser','$date')")or die(mysql_error());

Header("Content-type: image/jpeg");
if ($show) {
  $viewcount = mysql_num_rows(mysql_query("SELECT id FROM ".$sqlpref."stats WHERE http='$http'"));
  if ($size >= 5) $sizewidth = 9;
  elseif ($size >= 4) $sizewidth = 8;
  elseif ($size >= 3) $sizewidth = 7;
  elseif ($size >= 2) $sizewidth = 6;
  else $sizewidth = 5;
  $imwidth = strlen($viewcount)*$sizewidth;
  if ($size >= 4) $imheight = 16;
  elseif ($size >= 2) $imheight = 14;
  else $imheight = 8;
  $im = imagecreate ($imwidth, $imheight);
  $bgcolor = eregi_replace("#","",$bgcolor);
  $fgcolor = eregi_replace("#","",$fgcolor);
  $bgr = hexdec($bgcolor{0}.$bgcolor{1});
  $bgg = hexdec($bgcolor{2}.$bgcolor{3});
  $bgb = hexdec($bgcolor{4}.$bgcolor{5});
  $fgr = hexdec($fgcolor{0}.$fgcolor{1});
  $fgg = hexdec($fgcolor{2}.$fgcolor{3});
  $fgb = hexdec($fgcolor{4}.$fgcolor{5});
  $background_color = imagecolorallocate ($im, $bgr, $bgg, $bgb);
  $text_color = imagecolorallocate ($im, $fgr, $fgg, $fgb);
  imagestring ($im, $size, 0, 0,  $viewcount, $text_color);
  Imagejpeg($im);
  ImageDestroy($im);
}

Once you have the tracking data you can do whatever you want with it in terms of reports. Take a look at Hydrus Stat Tracker for some simple graphing examples.

Building thumbnails dynamicly in PHP

One problem Ive always hated was creating lots of thumbnails for images. So instead I created a PHP wrapper for the command line imagemagick tools.

This script requires Cana_Thumb which you can get on github:

https://github.com/arzynik/cana/blob/master/lib/Cana/Thumb.php

class Cana_Thumb {
  private $_path;
  private $_cache;
  private $_watermark;
  private $_img;
  private $_im;


  public function __construct($params = array()) {
    $this->setOptions($params);

    if (file_exists('/usr/local/bin/convert')) {
      $this->_im = '/usr/local/bin/convert';
    } elseif(file_exists('/usr/bin/convert')) {
      $this->_im = '/usr/bin/convert';
    } elseif(file_exists('/opt/local/bin/convert')) {
      $this->_im = '/opt/local/bin/convert';
    } else {
      throw new Exception('Could not find imagemagick');
    }

    if ($this->_im && isset($params['img'])) {
      $this->writeThumb($params['img']);
    }
  }
}

Removing tripod.co.uk's banner ads

EU tripod may allow you to host PHP for free, but it also puts ads on the page. They way they do this is by executing additional code once the page has completed. However, if the page ends in a parse error, or any fatal error, it will not be able to execute. So the solution is simple. At the bottom of your code, or footer php, add the following.

<div style="display: none;">
<? no_more_ads();

because the function no_more_ads() does not exist, the ads will not appear! It will however add an error message to the bottom of your page, which is what the hidden div is for.

WoW Stat

WoW Stat is a simple World of Warcraft server monitor written in C#.

WoW Stat will read the server xml from blizzards servers and provide a status in both the application and the systray. If your server goes offline, you can set WoW Stat to alert you in the sys tray or open wow for you when it is back online.

WoW Stat is 100% free

http://wow-stat.net

Hammerin Hero

Hammerin Hero is a PSP game based on the old arcade game Hammerin Harry. You basically go around and kill everyone corporate.

While working with Atlus USA I was tasked with making multiple gameplay clip trailers.

Please Note: I did not make the website.

http://atlus.com/hammerinhero

Trackmania

Trackmania DS is a portable version of the popular Trackmania game. It features customizable race tracks and multiple track and car types.

While working with Atlus USA I was tasked with making multiple gameplay clip trailers.

Please Note: I did not make the website.

http://atlus.com/trackmania

Ys

Ys is a dungeon RPG port of an old game over to the Nintendo DS.

While working with Atlus USA I was tasked with making multiple gameplay clip trailers.

Please Note: I did not make the website.

http://atlus.com/ys

Steal Princess

Steal princess is a unique style of action puzzle game.

While working with Atlus USA I was tasked with making multiple gameplay clip trailers.

Please Note: I did not make the website.

http://atlus.com/stealprincess

Dokapon Journey

Dokapon Journey is a remake of the old Dokapon game ported over to the Nintendo DS.

While working with Atlus USA I was tasked with creating multiple gameplay clip trailers.

Please note: I did not make the website.

http://atlus.com/dokaponjourney

Dark Spire

Dark spire is a new version of the old Dark Spire game for the Nintendo DS. It includes a "Retro" mode where you can play the game in ghetto old wireframe looking graphics.

While working at Atlus USA I was tasked with making multiple gameplay clip trailers

Please note: I did not make the website.

http://atlus.com/darkspire

Crimson Gem Saga

Crimson Gem Saga is an RPG video game for the PSP.

While working for Atlus USA I was tasked with making a quick gameplay clip trailer for use on their website. Of all the games I worked on while at Atlus, this is my favorite!

Please note: I did not make the website

http://atlus.com/cgs

Ionic Video Chat

Last weekend I decided to clone what I imaginged would be a somewhat complicated app: WhatsApp. Particularly the video I was worried about. Currently, Safari, including mobile Safari does not support WebRTC as a means of communication for video. But the super heroes over at eface2face created this amazing cordova-plugin-iosrtc which adds WebRTC to cordova/ionic iOS apps! Along with iosrtc, it also includes cordova-plugin-crosswalk-webview for WebRTC inside of Android.

Included with the package is a Node.js, socket.io, and MongoDB backend. All communication for chat happens using socket.io. For video, each peer connects with eachother after sending messages through socket.io to eachother.

It also includes some pretty perfect chat sounds from Chase Kenway.

I decided to release this on the Ionic Marketplace, and you can find it here

Video and Text Chat full app

Tipsy v0.10.3

New Tipsy, new sample code.

With the latest release of Tipsy we get performance improvments, PostgreSQL support, and a bunch of easily deployable examples, including Hello World, Blog Example, and Beer Squirrel.

Take a look at all examples at Examples.

Tipsy Benchmark

Tipsy gets benchmarked against other popular PHP frameworks. Acording to these tests, Tipsy is the fastest extension-free framework (both phalcon and ice require compiled PHP C extensions to work).

PHP Framework Benchmark

Pulpo Loco

Another Tipsy example complete! Pulpo Loco is a simple self hosted MySQL/PostgreSQL URL shortener using PHP and AngularJS.

Have a look over at pulpolo.co or visit its source over at GitHub.

For more Tipsy examples check out the Examples page.

Tipsy featured on homepage of coveralls.io

Woohoo!

Lee of Coveralls just added Tipsy to the front page. Scroll down to find it in the PHP section.

Thanks Lee!

Single page GitHub wiki generator

GitHub has a great markdown powered wiki editor that is good for some wikis but not always great for technical documentation. Today I finished up Github Wiki Single Page. This project allows you to enter any GitHub wiki that contains a sidebar, select a Bootswatch theme, and generate a single page version of it. This works great for searching through docs.

Have a look at the example using Tipy's Wiki.

Gamma Wray

Another website for Loic Zimmermann, this time showcasing his short film.

Most of the design was done by Loic himself. I just threw it together to do things.

Have a look over at gamma-wray.com

Male Princess

Mike drew a pretty princess on our whiteboard while we were working on Mail Princess. So we just had to.

Come check out the Male Princess.

A fresh new look

Just about one every year in January I redesign or redevelop my website with whatever tech suits my fancy. Today I release a new design, similiar to my previous one, but much cleaner, with lots of responsiveness for large displays.

This version is powerd by a lightweight MEAN (MongoDB, Express, Angular, Node.js) stack.

Typically when I redo my site I am focused on the backend or whatever framework I just came out with and how to improve it. This time I just focused on the desk and content presentation.

This also marks a move off of tumblr which was nice while it lasted. This time I built my own admin editing tools that allow me to upload to GitHub Issues for image hosting, along with a simple Markdown editor using Codemirror.

If you are reading this, you are already probably looking at the design, so...thanks!

Read the full post

I fucking love AngularJS. I fucking love Cana.

About a year ago I started a personal project called droplet that is basically a shared photo library social network (for people who have friends). It was my test to learn AngularJS. I fucking hated it. The documentation was absolutely awful, wrong at least half of the time. Or the docs reflect some crazy version that never even made it to main repo. It was a complete fucking mess, and that is what I expected.

The fact that it was a fuckshow actually made it MORE fun to learn. So I built this silly little app in 2 days, and decided I had enough knowledge to switch our entire company, Crunchbutton, away from my scratch built JS framework to Angular. I was wrong. But not in a bad way.

Over the course of the next couple days, i converted what was basically jQuery and a backbone (for templates ONLY) MVC framework and turned it into Angular...for the most part. I got seriously tired of all the broken shit, or people claiming this piece of code works, or whatever, and it just -- not -- working. So I had my developer partner, Daniel, do all the hard stuff! And hard stuff he did. He converted all of my half finished code into pretty angular directives and fixed all the junk I broke. He was my savior.

A couple weeks go by, and we are ready for live. It was awesome, but in reality at the time it was nothing more than a framework change, and a messy one at that. It took us both a while to finish cleaning things up and angularify everything. Soon after, we pushed our app to the iOS store. Yay! But thats not what this post is about.

The app has been in the store for just over a month, and since then I have COMPLETELY redone the UI and design. I spent countless drunk hours designing all the new stuff without touching any code, trying to make sure every pixel was perfect. Well...its fucking perfect.

So let me get to the point:

Without changing more than a dozen lines of code (adding directives), I have built an entire new ui ON TOP of our existing theme, that functions different due to how easy it is to bind functionality to Angular within HTML and HTML that changes.

That about sums it up. So let me explain quickly how and why things are so easy.

We use Cana. Cana is my PHP framework. Hand crafted. Aged for 12 years. This is my sipping whiskey.

Cana has the ability to configure sites, or apps, with a config array to change the include paths in a cascading manner that allows us to create sub templates for ONLY files that have changed compare to the parent. Heres what I mean:

  • active themes configuration is ["sexy","old"]
  • old theme has 15 files, including home.phtml
  • sexy theme only has layout.phtml and home.phtml
  • when the request is made to the server, it cascades downwards automatically to include all of the templates needed
  • this works for views, controllers, and models
  • did i mention there phtml so we can pre process them? :p


Now, when a request is made to the server for a page, it bundles all view files required for angular in the initial request by parsing the layout templates tags.

So in the end, what we have is a system that allows me to edit ONLY the html files I need to create a whole new theme, with new functionality thanks to the power of AngularJS. This is something I did not expect when we first switched.

Being able to develop this theme on the same code line as our primary code is absolutely amazing. It saves us time with branching/merging, and lets us test our code out in the wild where we never would.

Thanks AngularJS. Thanks Cana.

Curious on how I build the views? Take a look at crunchbutton.com (or if you are feeling adventurous dev.crunchr.co) or shoot me an email from my ME page.

Disable smart quotes in OSX Mavericks

Windows and Word users have always driven me crazy when they complain about crazy characters in their posts or pages...and the cause is almost always smart quotes or dashes. With the release of OSX Mavericks, we too now have the misfortune of smart quotes. Smart quotes are those little angled quotes that point to your content. Like:

“Smart double quotes.”          "Dumb double quotes."
‘Smart single quotes’           'Dumb single quotes'

If you are pasting things back and forth between people and someone has crazy encoding settings, or isn't using UTF-8, those characters can get all crazy, and not show up properly. Especially when pasting them straight into a database.

So, lets disable them!

To do so in OSX Mavericks:

  1. Open System Preferences from Applications or Launchpad or wherever.
  2. Go to Keyboard
  3. Go to the Text tab
  4. Uncheck Use smart quotes and dashes.

If you are using TextEdit, you will also need to:

  1. Open TextEdit
  2. In the top menu, go to TextEdit > Preferences
  3. Uncheck both Smart quotes and Smart dashes

Enjoy!

WoW Stat 2

WoW Stat is a World of Warcraft server monitor utility. All you have to do is set the server you wish to monitor. If your server goes down, WoW Stat will automatically notify you when it is back up, and even relaunch WoW for you.

After a couple years of letting this program sit and get a few hundred thousand downloads, I decided to redo the entire thing in Titanium with Javascript. I now have this released for both Windows and OSX, both open source and free. You can find the downloads over at the website.

wow-stat.net

I've moved to tumblr!

After nearly 18 years of building and managing my own website using nothing but a text editor and scratch code, I've decided to build on top of an existing platform for once.

Tumblrs custom themes are ridiculously simple to modify so that each page can display each type of data nice and pretty.

Maybe now I will write posts more often than once every year...

Learn to build an application using Angular.js

How awesome would it be if there was a short online class on how to build an application using Angular? O wait there already is. Check it out.

http://campus.codeschool.com/courses/shaping-up-with-angular-js/intro

The release of Tipsy (MVW PHP)

Over the last decade I have modified and released several different PHP frameworks, with each version changing as my style changes. This new framework, created almost from scratch for some of my newer, smaller sites, focuses on speed of development, with the idea of making REST based web apps super easy to build.

This new version has been named Tipsy, because, obviously, I made it...and everything else...while drinking.

Tipsy is an MVW (Model View Whatever) framework, following the examples of AngularJS.

Heres a quick example of how easy, yet how powerful and lightweight, Tipsy is:

index.php

require_once 'Tipsy.php';
use Tipsy\Tipsy;

$tipsy = new Tipsy;

$tipsy->router()
    ->when('hello', function($Scope, $View) {
        $Scope->user = 'Devin';
        $View->display('hello');
    })
    ->otherwise(function() {
        echo '404';
    });

$tipsy->start();

hello.phtml

<h1>Welcome, =$user?>!</h1>

Take a look at the source, examples, and documentation, over at Tipsy's Github Repo.