Passing parameters and arguments to a cron job script

Filed in cpanel | cron | PHP Leave a comment

To pass arguments or parameters to your cron job scripts use the following syntax,

php -q /home/username/scripts/cron.php name1=value1 name2=value2 >/dev/null 2>&1

In this example the script in question is a php script, hence I am invoking the php interpreter by starting the line with php. Passing arguments is done by having a space in between each name=value pair. Finally, to avoid any email output from this cron job add

>/dev/null 2>&1

to the end of your line to dump the output.

, , ,

Difference between dates using php, accurate and improved

Filed in Date and Time | PHP Leave a comment

Calculating the difference between 2 dates using php whilst at first glance might appear very simple, getting the output of the difference into a useable format is not always the easiest of tasks. There are various posts and solutions available that claim to provide the accurate solutions that most of us are looking for, but sadly fall far short of expectations.

I recently came across a very simple solution detailed on the following site, PHP: Calculate Real Differences Between Two Dates or Timestamps. One of the best features about the code is that it does not rely on new PHP features such as the new DateTime object, DateTime::diff or its alias date_diff.

I made some slight alterations to the code to provide me with more raw information that I can use in other areas of the application this is used in. I have detailed the modified code in full below.

function dateDiff($time1, $time2, $precision = 6) {
    // If not numeric then convert texts to unix timestamps
    if (!is_int($time1)) {
      $time1 = strtotime($time1);
    }
    if (!is_int($time2)) {
      $time2 = strtotime($time2);
    }

    // Set up intervals and diffs arrays
    $intervals = array('year','month','day','hour','minute','second');
    $diffs = array();

    // If time1 is bigger than time2
    // Then swap time1 and time2
    if ($time1 > $time2) {
      $ttime = $time1;
      $time1 = $time2;
      $time2 = $ttime;
      $diffs['diff'] = ' ago';
      $diffs['sign'] = '-';
    }

    // Loop thru all intervals
    foreach ($intervals as $interval) {
      // Set default diff to 0
      $diffs[$interval] = 0;
      // Create temp time from time1 and interval
      $ttime = strtotime("+1 " . $interval, $time1);
      // Loop until temp time is smaller than time2
      while ($time2 >= $ttime) {
	$time1 = $ttime;
	$diffs[$interval]++;
	// Create new temp time from time1 and interval
	$ttime = strtotime("+1 " . $interval, $time1);
      }
    }

    $count = 0;
    $times = array();
    // Loop thru all diffs
    foreach ($diffs as $interval => $value) {
      // Break if we have needed precission
      if ($count >= $precision) {
	break;
      }
      // Add value and interval
      // if value is bigger than 0
      if ($value > 0) {
	// Add s if value is not 1
	if ($value != 1) {
	  $interval .= "s";
	}
	// Add value and interval to times array
	$times[] = $value . " " . $interval;
	$count++;
      }
    }

    // -- \\ ALTERED SECTION // -- \\
    // Return string with times
    $diffs['text'] = implode(', ',$times);
    return $diffs;
    // -- \\ END OF ALTERED SECTION // -- \\
    // original return statement
    /*
    return implode(", ", $times);
    */
  }

Call the script with;

$start_time = 1331656789; // Tue, 13 Mar 2012 16:39:49
$finish_time = 1344470399; // Thu, 09 Aug 2012 00:59:59 

$diff = dateDiff((int)$start_time, (int)$finish_time);

Note that I have cast the type of variable as an integer, if you don’t do this and the variable you are passing in is cast as text or other types, the function will return some pretty strange results.

Based The contents of $diff should return as follows;

Array
(
    [year] => 0
    [month] => 4
    [day] => 26
    [hour] => 8
    [minute] => 20
    [second] => 10
    1 => 4 months, 26 days, 8 hours, 20 minutes, 10 seconds
)

As per the original documentation for this code, you can use other time and date formats as input;

dateDiff("2010-01-26", "2004-01-26");
dateDiff("2006-04-12 12:30:00", "1987-04-12 12:30:01");
dateDiff("now", "now +2 months");
dateDiff("now", "now -6 year -2 months -10 days");
dateDiff("2009-01-26", "2004-01-26 15:38:11");

I am grateful to the original author of this code for the headaches it saved me from and with a little modification fitted my needs perfectly. Hopefully this code and its minor alteration will be useful to someone else.

, , ,

css variable length buttons with mouseover effect

Filed in css | design: icons | forms | Javascript Leave a comment

This short article details how to create your own graphical buttons that can be controlled by css and can extend to any length required. they use images as for their background and incorporate a mouseover effect.
First off, let’s look at some examples;

So you can see that you can create any length of button you want and they can be created with any element you wish, not just obvious ones like buttons or links. With a little tweaking of CSS you can create buttons anywhere. Add in a little javascript and you can add actions to any element that you have converted to look like a button.

In order to allow the button background to stretch to fill any length of button, you need to create a button image that should exceed the width of your page. The mouse over effect is created using the psuedo css class, :hover. This shifts the background position by a set number of pixels so that a different portion of the image is displayed. In this case the height of each button is 25px so in order to display the mouseover state for the green button, the background position is shifted on the y axis by -25px.


Click here to view full size, 1600px wide
I have detailed the measurements of the buttons that I am using in these examples. I have just created a basic square shape but you could create any shape or design of button you wish, as long as you stick to the size formula below.

The overall length of the button image is 1600px wide, which will allow for a maximum button width of 800px. If you need a wider button than this then increase the overall width. Take note that the width of the small portion of button on the left must not be less than the amount of pixels worth of padding you will apply to the left of your button not forgetting to take into account any radius for rounded buttons etc. Also make sure that the transparent space must end at exactly 50% of the overall width of the button image.

Here is the css required;

.btn {
    background-image: url(/path/to/btnPanel.png);
    background-repeat: no-repeat;
    height: 25px;
    padding: 0 0 0 9px;
    display: inline-block;
    margin: 2px;
}
.btn input, .btn button, .btn a {
    height: 25px;
    border: none;
    background-image: url(/path/to/btnPanel.png);
    background-repeat: no-repeat;
    padding: 0 9px 0 0;
    font-size: 16px;
    display: block;
    line-height: 25px;
}
.btnGrn {background-position: 0 0;}
.btnGrn:hover {background-position: 0 -25px;color: #000;}
.btnGrn input, .btnGrn button, .btnGrn a {background-position: 100% 0;}
.btnGrn input:hover, .btnGrn button:hover, .btnGrn a:hover {background-position: 100% -25px;color: #000;}

.btnRed {    background-position: 0 -100px;}
.btnRed:hover {background-position: 0 -125px;color: #000;}
.btnRed input, .btnRed button, .btnRed a {    background-position: 100% -100px;}
.btnRed input:hover, .btnRed button:hover, .btnRed a:hover {background-position: 100% -125px;color: #000;}

.btnBlue {    background-position: 0 -50px;}
.btnBlue:hover {background-position: 0 -75px;color: #000;}
.btnBlue input, .btnBlue button, .btnBlue a {    background-position: 100% -50px;}
.btnBlue input:hover, .btnBlue button:hover, .btnBlue a:hover {background-position: 100% -75px;color: #000;}

.btnAmber {    background-position: 0 -150px;}
.btnAmber:hover {background-position: 0 -175px;color: #000;}
.btnAmber input, .btnAmber button, .btnAmber a {    background-position: 100% -150px;}
.btnAmber input:hover, .btnAmber button:hover, .btnAmber a:hover {background-position: 100% -175px;color: #000;}

And you invoke the buttons using the following;

<span class="btn btnGrn"><button value="submit" name="myButton">My Button</button></span>
<span class="btn btnGrn"><input type="submit" name="submit" value="continue"></span>
<span class="btn btnBlue"><input type="submit" name="submit" value="go back"></span>
<span class="btn btnRed"><input type="submit" name="submit" value="cancel"></span>

I hope this post helped you out, please feel free to download and use the created buttons as a starting point for your own projects.

, , ,

Blocking aggressive search engine spiders, baidu sogou

Filed in .htaccess | General Leave a comment

I recently had an issue where one of our sites ate through a gig of bandwidth in less than a day, it’s not a busy site so this change in traffic volume was picked up fairly quickly. You might instantly jump to the conclusion that maybe a recent google algorithm update had caused the sites popularity to increase and that the increase in bandwidth use was not unwanted. However, it turned out that although the increase in site traffic was search related, it was not a human searcher that was creating it but that of a search engine spider aggressively crawling the site in what can only be described as an infestation.

Search engine spiders should not really be unwanted, after all they are what help bring new visitors to your site and increase your audience. But if the spiders are causing detriment to your site speed and your precious bandwidth then you need to look more closely at the benefits of allowing such aggressive spidering behavior.
In this case the spider was from the Chinese search engine Baidu, showing up as BaiduSpider. I decided that the trade off in favour of site performance against visibility to users of the Baidu search engine was valid and set about blocking the spiders access to any part of the site.

The answer comes in a couple of short lines that you can add to your sites’ .htaccess file. I prefer this approach rather than using robots.txt to take care of blocking the bauduspider.

RewriteCond %{HTTP_USER_AGENT} Baiduspider
RewriteRule ^.*$ - [F]

The rewrite condition checks the useragent against the name ‘Baiduspider’ and if it matches returns a 403 Forbidden response. In time this will slow down the onslaught of access attempts from this spider and your site can return to normal. I personally found that site performance picked up almost instantaneously.

, ,

jQuery selectmenu passing validation when default unchanged issue solution found

Filed in css | forms | Javascript | jQuery Leave a comment

I came across a problem with the ui.selectmenu plugin after I updated the jQuery validation plugin from 1.8 to 1.9.

The Problem

When using fnagels jquery selectmenu along with the updated v1.9 validation plugin I found that if you left the initial value of the select element unchanged and submitted the form, the form would pass validation. This issue appeared when I had the default value of “”;

<option value="">Select One</option>

In the validation plugin, there is an ignore option that when used will prevent this behavior and allow form that have been styled with the ui selectmenu plugin to properly validate. The technical reason behind why the validation plugin behaves the way it does with this option is down to how the selectmenu does its magic. It sets the select as a hidden element which the validation plugin ignores when parsing the form elements.

I was stumped for a while, but luckily others had been there before me and I came across this discussion;
selectmenu does not support required validation and also a jsfiddle linked from this discussion that illustrated the fix in place, JsFiddle.

, , ,

Automated remote cpanel backups with php via cron and ftp

Filed in cpanel | cron | PHP Leave a comment

So I recently went through a very frustrating experience that involved a hard drive failure on a reseller hosting account that we have. The account houses many domains for us as we don’t yet have the need for a dedicated server, all our sites tick away nicely so we like to try and keep costs down as low as possible without compromising on quality of service. Our hosting provider is very reliable and although I rate their support very highly, fast response times and always extremely helpful, I briefly questioned that belief and whether we should keep our business with them when disaster struck.

cPanel / WHM Backups + Server Hard Drive Failure = Developer Disaster

Now we all know that hardware can fail, usually not at the most convenient times, so we can forgive the odd hiccup here and there. But on this occasion there was a hard drive failure that pushed the server offline and unfortunately also was extensive enough that the data was unrecoverable. For the majority of our clients this should not have posed much of a problem as they have largely static sites, they would have suffered some downtime on their websites and not have any access to their POP email. Those with dynamic sites and those that rely on IMAP or Webmail were not so lucky. You might be thinking that all should be OK with the power of the server backup, but unfortunately due to the timing of the failure and what looks like a spacious backup routine, about 2 days worth of code, mail, and sql data had gone astray!

So that leads me on to the point of this post. Although your hosting provider may state that your data is backed up, unless you run a dedicated server, you have no control over the schedule of the backup routine. Well, that is the case unless you take matters into your own hands.

I wanted to control what data we had backed up and when the backup was created, allowing us control over how old a backup can be. Minimising the data loss is key to keeping our clients happy and making sure we don’t waste time having to rewrite code that gets fried along with a hard drive! I have put together a couple of PHP scripts that can be run via cron, one to backup a list of sites to a remote server and the other runs on the remote server cleaning up out of date backups. I have kept them simple so that with very little effort they can be dropped in place and up and running without having to setup databases or fiddle around with other configs to get them working.

The first script is what backs up the site, credit to Mikehiggenbottom.com for the code this is based on. This file sits outside of the public_html, i.e. /home/someusername/backup_scripts/backup.php

<?php
ob_start();
// Automatic cPanel FTP backup
// This script contains passwords. Do not put it in a public folder.

// idea originally conceived by http://mikehigginbottom.com/content/automatic-cpanel-backups
// modified to run from cron and only backup each site after X hours.
// I did this to account for some sites we host that heavily use IMAP mail or update their content
// often, sometimes many times a day and may need a more rigorous backup schedule.

/*
domain to backup => domain name without www
backupdelay => number of hours in between backups
cpaneluser => username to login to cpanel for the domain
cpanelpass => password to login to cpanel for the domain
cpanelskin => the name of the current graphical frontend to cpanel for that domain. This can be worked out from your cpanel URL when you log in e.g. http://mycompany.com:2082/frontend/x3/index.html
*/
$sites[] = array('domaintobackup'=>'domain1.com','backupdelay'=>'12','cpaneluser'=>'username','cpanelpass'=>'password','cpanelskin'=>'x3');
$sites[] = array('domaintobackup'=>'domain2.com','backupdelay'=>'36','cpaneluser'=>'username','cpanelpass'=>'password','cpanelskin'=>'x3');

/* Remote Host Details */
// FTP host details
$ftpmode = "ftp";
// "ftp" for active,
// "passiveftp" for passive,
// "scp" for scp - most secure
$ftpuser = "remoteuser";
$ftppass = "remotepass";
$ftphost = "remoteftphost.com"; // Full hostname or IP address for FTP host
$ftpport = "21";
$ftpfold = "/backup_files/";      // Destination folder for backup files

$notifyemail = "emailto@notify.com";
$sendfromcpanel = 0;
$sendfromcron = 1;
$headers = 'From: YourCompany Backups <backups@yourcompany.com>' . "\r\n" .
    'Reply-To: backups@yourcompany.com' . "\r\n" .
    'X-Mailer: PHP/' . phpversion();$secure = 0; // Set to 1 for SSL (requires SSL support)
$debug = 0;  // Set to 1 to have web page result appear in your cron log

$docRoot = "/home/yourusername/";

foreach ($sites as $site) {
	$files = array();
	$ftpconn = ftp_connect($ftphost);
	if (@ftp_login($ftpconn, $ftpuser, $ftppass)) {
		ftp_pasv($ftpconn, true);
		// check backup directory exists, if not, create it then traverse into it
		if (!@ftp_chdir($ftpconn,$ftpfold.$site['domaintobackup'])) {
			ftp_mkdir($ftpconn,$ftpfold.$site['domaintobackup']);
			ftp_chdir($ftpconn,$ftpfold.$site['domaintobackup']);
		}
		// list all files / directories within directory
		$ftpfiles = ftp_nlist($ftpconn, '.');
		foreach ($ftpfiles as $file) {
			// get the last modified time of each file
			$ftime = ftp_mdtm($ftpconn, $file);
			if ($ftime > 0) { // directories come back as -1
				$files[$file] = $ftime;
			}
		}
		arsort($files);
		reset($files);
		// get the newest modified file
		$firstfile = key($files);
		$firstfiletime = current($files);
		ftp_close($ftpconn);
	} else {
		echo "Ftp connection failed.... Bailing out!\n";
		$output = ob_get_contents();
		mail($notifyemail, "Server Backup Routine", $output, $headers);
	}

	// has X hours passed since the timestamp of the last backup file
	if ($firstfiletime < strtotime("-".$site['backupdelay']." hours")) {

		if ($secure) {
			$url = "ssl://".$site['domaintobackup'];
			$port = 2083;
		} else {
			$url = $site['domaintobackup'];
			$port = 2082;
		}

		$socket = fsockopen($url,$port);
		if (!$socket) {
			echo "Failed to open socket connection… Bailing out!\n";
			exit;
		}

		$authstr = $site['cpaneluser'] . ":" . $site['cpanelpass'];
		$pass = base64_encode($authstr);

		$params = "dest=$ftpmode".($sendfromcpanel ? "&email_radio=$sendfromcpanel&email=$notifyemail" : "").
		"&server=$ftphost&user=$ftpuser&pass=$ftppass&port=$ftpport".
		"&rdir=$ftpfold{$site['domaintobackup']}&submit=Generate Backup";

		fputs($socket,"POST /frontend/".$site['cpanelskin'].
		"/backup/dofullbackup.html?".$params." HTTP/1.0\r\n");
		fputs($socket,"Host: {$site['domaintobackup']}\r\n");
		fputs($socket,"Authorization: Basic $pass\r\n");
		fputs($socket,"Connection: Close\r\n");
		fputs($socket,"\r\n");

		while (!feof($socket)) {
			$response = fgets($socket,4096);
			if ($debug)	{
				echo $response . "\n";
			}
		}

		echo $site['domaintobackup']." complete.\n";
		$output = ob_get_contents();
		mail($notifyemail, "Server Backup Routine", $output, $headers);
		fclose($socket);
		exit;
	} else {
		echo "The site, ".$site['domaintobackup']." was last backed up at ".strftime("%H:%M %a %e %b %Y", $firstfiletime).". The earliest it will be backed up will be ".strftime("%H:%M %a %e %b %Y", strtotime("+".$site['backupdelay']." hours", $firstfiletime)).".... Bailing out!\n";
	}
}
ob_end_clean();
?>

I have set this to run every 5 minutes via cron,

*/5 * * * * php /home/yourusername/backup_scripts/backup.php >/dev/null 2>&1

It uses cPanels built in backup routine, triggered by a form post, to generate a full backup and send it via ftp to a remote server. This is a native feature built into cpanel that you can do manually, but by automating the form posts through the php functions fsockopen() and fputs(), we can simulate logging into cpanel, posting the form complete with ftp details of where to send the generated backup file. The ftp login credentials used allow login into the /home/username/ folder, above the public_html folder meaning that backup files cannot be directly accessed via a browser. If you want this functionality, check your ftp login credentials default/initial directory.
The script will check for the existence of the backup folder on the remote filesystem and create it should it not exist. It also checks to see if a previous backup has been made and if one has been made within the last X hours then no new backup will be performed. It checks this based on the timestamp of the last time the file was modified.

The second script deals with the cleanup of the remote filesystem. It checks to see if there are multiple backup files from the same domain and removes them leaving only the most recent backup file. This script is run on the remote host again using cron.

<?php
// This is the companion script to the backup routine. This has to run locally to where the backups are stored. It is advisable to save this file
// outside your public directory and run it by cron.
$notifyemail = "emailto@notify.com";
$sendemail = true;
$headers = 'From: YourCompany Backups Cleanup <backups@yourcompany.com>' . "\r\n" .
    'Reply-To: backups@yourcompany.com' . "\r\n" .
    'X-Mailer: PHP/' . phpversion();

$docRoot = "/home/remoteusername/backup_files/";
if (is_dir($docRoot)) {
	$files = array();
	$dp = opendir($docRoot);
	while (false !== ($dir = readdir($dp))) {
		if (is_dir($docRoot.$dir) && $dir != "." && $dir != "..") {
			$fdp = opendir($docRoot.$dir);
			$dirfiles = array();
			while (false !== ($file = readdir($fdp))) {
				if (!is_dir($docRoot.$dir.'/'.$file)) {
					$ftime = filemtime($docRoot.$dir.'/'.$file);
					$dirfiles[$ftime] = array(
					'name'		=> $file,
					'fpath'		=> $docRoot.$dir.'/'.$file,
					'size'		=> filesize($docRoot.$dir.'/'.$file),
					'mod'		=> $ftime,
					'status'		=> 'OK',
					);
				}
			}
			arsort($dirfiles);
			if (sizeof($dirfiles) > 1) {
				reset($dirfiles);
				next($dirfiles);
				$currdirfile = key($dirfiles);
				$dirfile = current($dirfiles);
				if (file_exists($dirfile['fpath'])) {
					unlink($dirfile['fpath']);
					$dirfiles[$currdirfile]['status'] = "Deleted";
				}
			}
			$files[] = array(
			'name'		=> $dir,
			'contents'	=> $dirfiles,
			);
		}
	}
	if ($sendemail) {
		$mail = 	"Backup Folders Cleanup\n".
					"---------------------------\n\n";
		foreach ($files as $dvalue) {
			$mail .= $dvalue['name']."\n"."=====================================\n";
			foreach ($dvalue['contents'] as $fvalue) {
				$mail .= "\t".$fvalue['name']." => ".$fvalue['status']."\n";
			}
			$mail .= "\n\n";
		}
		mail($notifyemail, "Backup Folders Cleanup", $mail, $headers);
	}
}
?>

This file is set to run every hour via cron;

20 * * * * php /home/remoteusername/backup_scripts/backup.php >/dev/null 2>&1

So there you have it, I hope that these snippets of code are of use to somebody else, my only regret is that I did not have something like this in place before.

, , , ,

Css positioning tips

Filed in css Leave a comment

Sometimes the simplest of tasks gets you so frustrated that you create a mental block…. css positioning got me this way.

I found a great tutorial though that lays it all out in a fairly straight forward manner, .

My thanks to the author.

, ,

Piwigo Photo Gallery PHP Software – Upload Problem – Issue – Fails without error

Filed in PHP | Piwigo 1 Comment

I recently started using this excellent open source php photo gallery script called Piwigo. It is pretty easy to skin to your own needs and there are plenty of plugins and extensions for it, plus if you are a bit handy you can dive in there yourself and knock up your own plugins or modifications.

So, anyway, on to an issue I found recently.

Whilst uploading some of our honeymoon photos straight from the files off our camera, I encountered a problem where some larger (both in resolution and file size) photos would appear to upload without any issue but they would never appear as uploaded once the process had completed. Additionally, they did not show up in the database either!

Having already faced an issue with the PHP configuration when I first installed the software, I thought that the answer lay there as smaller photos uploaded without any issue. As a side note the initial configuration problem I faced was with the software complaining of a config mismatch between “upload_max_filesize” and “post_max_size”. As I am currently in a shared hosting environment and have no access to the main php.ini this was solved by creating a php.ini file in the root of the installation (public_html in my case) with the following lines;

upload_max_filesize = 40M;
post_max_size = 40M;

Moving on, as some images were uploading fine and the larger ones were not, I guessed that the issue most likely lay with the memory limit controlled by the main php.ini, so I added a line to my php.ini in the root of my installation;

memory_limit = -1;

Now, this solution worked for one of the 2 methods that Piwigo uses for image uploads, the one that worked being the form based POSTed one. After a bit of sitting back and plain thinking I figured that flash based upload form must have its own document root and set about adding a php.ini into the following folder;

/public_html/admin/include/uploadify/

Just to be safe a added all 3 lines of the php.ini from the root of the installation;

memory_limit = -1;
upload_max_filesize = 40M;
post_max_size = 40M;

Voila… both the flash based upload and form based upload both work with the larger images I was trying to unsuccessfully upload before!

Hope this helps someone out with the same problems as I have had.

,

magento multiple stores howto useful article

Filed in Ecommerce | Magento Leave a comment

,

jQuery and php ajax image upload

Filed in Uncategorized Leave a comment

Recently had to revisit uploading of images into a project and decided it was time to refactor a previous uploader to make it a little bit more user friendly.

The story will unfold here but I will start by just making a note for myself about what jQuery plugin I have used.

jQuery ajax upload handler

TOP