Simple PHP caching

There are many tools around that will help cache your website to increase speed and performance, such as eAccelerator and APC. However these usually require installation on your web server, thus rendering them useless for people who use shared hosting and don’t have access to the server.

Using some simple PHP code we can quite effectively cache a webpage, useful for sites that are database driven and have a large amount of queries or high volumes of traffic.

First we need to setup the folder where the cached files are stored, making sure it has read/write permissions (CHMOD to 0777 – see your FTP client documentation on how to do this).

Next create a file called cache.php which contains the following code:

<?php
// Number of seconds a page should remain cached for
$cache_expires = 3600;

// Path to the cache folder
$cache_folder = "/home/usr/www/cache/";

// Checks whether the page has been cached or not
function is_cached($file) {
 global $cache_folder, $cache_expires;
 $cachefile = $cache_folder . $file;
 $cachefile_created = (file_exists($cachefile)) ? @filemtime($cachefile) : 0;
 return ((time() - $cache_expires) < $cachefile_created);
}

// Reads from a cached file
function read_cache($file) {
 global $cache_folder;
 $cachefile = $cache_folder . $file;
 return file_get_contents($cachefile);
}

// Writes to a cached file
function write_cache($file, $out) {
 global $cache_folder;
 $cachefile = $cache_folder . $file;
 $fp = fopen($cachefile, 'w');
 fwrite($fp, $out);
 fclose($fp);
}

// Let's begin, first work out the cached filename
$cache_file = md5($_SERVER['REQUEST_URI']) . ".html";

// Check if it has already been cached and not expired
// If true then we output the cached file contents and finish
if (is_cached($cache_file)) {
 echo read_cache($cache_file);
 exit();
}

// Ok so the page needs to be cached
// Turn on output buffering
ob_start();

Now in your page you would include cache.php on the first line before you do anything else. This is because if the page is cached we don’t want it to do anything else other than output the cache to the page. If you are still including other files or connecting to databases before checking for the cache you are putting additional load on the server, making this exercise pointless.

Finally create a file called cache_footer.php which contains the following code:

<?php
// Grab the uncached page contents
$cache_contents = ob_get_contents();

// Save it to the cache for next time
write_cache($cache_file, $cache_contents);
?>

You would include this file on the last line of your page, which saves the page in the cache if required.

Putting it altogether a typical page would look like this:

<?php
// Load the cache process
include("cache.php");

// Connect to database
include("config.php");
mysql_connect($db_host, $db_username, $db_password) or die(mysql_error());
mysql_select_db($db_name) or die(mysql_error());
?>
<html>
<body>
<h1>Articles</h1>
<ul>
<?php
// Some query
$q = mysql_query("SELECT * FROM articles ORDER BY id");
while ($r = mysql_fetch_array($q)) {
  echo '<li><a href="view_article.php?id='.$r['id'].'">'.$r['title'].'</a></li>';
}
?>
</ul>
</body>
</html>
<?php
// Save the cache
include("cache_footer.php");
?>

GoLinks – PHP/MySQL Links Directory Script

GoLinksHere at XEWeb we are cracking open a bottle of champagne as we announce the new version of GoLinks.

GoLinks is a simple yet powerful and efficient links directory script with many features including unlimited categories, link submission, search engine friendly URL’s, multiple themes and language support with completely customizable templates.

Take a look at http://www.golinks-script.com to download, order and view a demo online.

A simple and lightweight Javascript calendar

This Javascript code will display a calendar in a table, useful for date picking…

// these are labels for the days of the week
cal_days_labels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];

// these are human-readable month name labels, in order
cal_months_labels = ['January', 'February', 'March', 'April',
                     'May', 'June', 'July', 'August', 'September',
                     'October', 'November', 'December'];

// these are the days of the week for each month, in order
cal_days_in_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

// this is the current date
var cal_current_date = new Date();

function Calendar(month, year) {
  this.month = (isNaN(month) || month == null) ? cal_current_date.getMonth() : month;
  this.year  = (isNaN(year) || year == null) ? cal_current_date.getFullYear() : year;
}

Calendar.prototype.generateHTML = function(){

  // get first day of month
  var firstDay = new Date(this.year, this.month, 1);
  var startingDay = firstDay.getDay();
 
  // find number of days in month
  var monthLength = cal_days_in_month[this.month];
 
  // compensate for leap year
  if (this.month == 1) { // February only!
    if((this.year % 4 == 0 && this.year % 100 != 0) || this.year % 400 == 0){
      monthLength = 29;
    }
  }

  // get todays date
  var cal_today_date = new Date();
  checkDay = (cal_today_date.getMonth() == this.month && cal_today_date.getFullYear() == this.year) ? true : false;
 
  // do the header
  var monthName = cal_months_labels[this.month]
  var html = '<table class="calendar-table" cellpadding="4" cellspacing="0">';
  html += '<tr><th><a href="javascript:cal.prevMonth()">&#171;</a></th><th colspan="5">';
  html +=  monthName + "&nbsp;" + this.year;
  html += '</th><th><a href="javascript:cal.nextMonth()">&#187;</a></th></tr>';
  html += '<tr class="calendar-header">';
  for(var i = 0; i <= 6; i++ ){
    html += '<td class="calendar-header-day">';
    html += cal_days_labels[i];
    html += '</td>';
  }
  html += '</tr><tr>';

  // fill in the days
  var day = 1;
  // this loop is for is weeks (rows)
  for (var i = 0; i < 9; i++) {
    // this loop is for weekdays (cells)
    for (var j = 0; j <= 6; j++) {
      html += '<td class="calendar-day">';
      if (day <= monthLength && (i > 0 || j >= startingDay)) {
        ts = this.generateTimestamp(day);
        if (checkDay == true && day == cal_today_date.getDate())
          html += '<a class="today" href="javascript:void(0)">' + day + '</a>';
        else
          html += '<a href="javascript:void(0)">' + day + '</a>';
        day++;
      }
      html += '</td>';
    }
    // stop making rows if we've run out of days
    if (day > monthLength) {
      break;
    } else {
      html += '</tr><tr>';
    }
  }
  html += '</tr></table>';
  document.getElementById('calendar-holder').innerHTML = html;
}

Calendar.prototype.generateTimestamp = function(day) {
  month = this.month + 1;
  return "'" + day + "','" + month + "','" + this.year + "'";
}

Calendar.prototype.prevMonth = function() {
  if (this.month == 0) {
    this.month = 12;
    this.year = (this.year - 1);
  }
  this.month = (this.month - 1);
  this.generateHTML();
}

Calendar.prototype.nextMonth = function() {
  if (this.month == 11) {
    this.month = -1;
    this.year = (this.year + 1);
  }
  this.month = (this.month + 1);
  this.generateHTML();
}

To display the calendar for example…

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
<head>
    <title>Select date</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <link rel="stylesheet" type="text/css" href="css/calendar.css" />
    <script type="text/javascript" src="scripts/calendar.js"></script>
</head>
<body>
<div id="calendar-holder">
<script type="text/javascript">
// <![CDATA[
 var cal = new Calendar();
 cal.generateHTML();
// ]]>
</script>
</div>
</body>
</html>

Convert plain text to clickable links in PHP

A useful function if you want to convert a link in plain text into a clickable link…

function clickable_links($text) {
  $text = eregi_replace('(((f|ht){1}tp://)[-a-zA-Z0-9@:%_+.~#?&//=]+)', '<a target="_blank" href="\1">\1</a>', $text);
  $text = eregi_replace('([[:space:]()[{}])(www.[-a-zA-Z0-9@:%_+.~#?&//=]+)', '\1<a target="_blank" href="http://\2">\2</a>', $text);
  return $text;
}

How to setup one time download of your files securely

How to setup one time download of your files securelyIf you are selling a digital product and want to offer it for download but are concerned that people will share the URL to the file, you could use a solution like ClickBank. However the advantage of setting up one time downloads from your server instead is that you have more control over what you are selling and you don’t have to pay any fee’s.

Let us assume that you have setup and integrated a payment solution (such as PayPal using the IPN, post coming soon on this) and you have a database setup that holds the payment information.

CREATE TABLE `transactions` (
`id` INT( 5 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`transaction_id` VARCHAR( 50 ) NOT NULL ,
`file_downloaded` TINYINT( 1 ) NOT NULL
) ;

Now we will create download.php which will be the link sent to the user after they have paid to download your files.

To keep it secure you would include the transaction ID (which would be unique to each payment) as part of the query string, so the link would be:

http://www.site.com/download.php?transaction_id=XX

Now you do need the files to be uploaded somewhere on your web server, however the location will never be revealed. The best way is to put them in a folder outside of the web root so that they cant be accessed via HTTP. However if this is not possible then put them in a folder name that cannot be guessed (use a random password generator if you don’t have any ideas).

<?php
// Path to the files to be downloaded
$file = '/home/usr/files/blah.zip';

// Transaction ID
$transaction_id = $_GET['transaction_id'];

// Connect to database
mysql_connect('localhost', 'user', 'pass');
mysql_select_db('dbname');

// Lookup the transaction ID in the database
$query = sprintf("SELECT * FROM transactions WHERE transaction_id='%s'",
mysql_real_escape_string($transaction_id));
$query = mysql_query($query);
$row = mysql_fetch_array($query);

// Valid transaction ID?
if (!$row) {
  die('Invalid transaction ID!');
}

// File already been downloaded?
elseif ($row['file_downloaded'] == 1) {
  die('File has already been downloaded, please contact us if you have any problems');
}

else {

  // It's a valid transaction, update the database so that we know the file has been downloaded for next time
  $query = sprintf("UPDATE transactions SET file_downloaded = 1 WHERE transaction_id='%s'",
  mysql_real_escape_string($transaction_id));
  $query = mysql_query($query);

  // Now force the file to be downloaded
  header('Content-Description: File Transfer');
  header('Content-Type: application/octet-stream');
  header('Content-Disposition: attachment; filename='.basename($file));
  header('Content-Transfer-Encoding: binary');
  header('Expires: 0');
  header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
  header('Pragma: public');
  header('Content-Length: ' . filesize($file));
  ob_clean();
  flush();
  readfile($file);
  exit();

}
?>