Chris Hope's LAMP Blog - The Electric ToolboxChris Hope's LAMP Blog - The Electric Toolbox

Linux Apache MySQL and PHP articles by Chris Hope

This is Chris Hope's blog for Linux, Apache, MySQL and PHP (known as LAMP) and Javascript/jQuery. "The Electric Toolbox" is the name of my company and I started this website several years ago with articles about web programming, Linux and Windows tips and tricks, howtos etc.

I post new content every day; read my posting schedule for more details.

The ten most recent articles can be found below in their entirety. Navigating the sections in the right navigation (under Categories) will bring up all the other posts, and you can also use the search box at the top of the page to find what you might be looking for.

Extract the first paragraph text from a web page with PHPExtract the first paragraph text from a web page with PHP

Posted February 8th, 2010 in PHP

This post looks at how to extract the first paragraph from an HTML page using PHP's strpos and substr functions to find the location of the first <p> and </p> tags and get the content between them.

Using strpos and substr

Assuming the content to extract the paragraph from is in the variable $html (which may have come from a file, database, template or downloaded from an external website), use the following code to work out the position of the first <p> tag, the first </p> tag after that tag, and then get all the HTML between them including the opening and closing tags:

$start = strpos($html, '<p>');
$end = strpos($html, '</p>', $start);
$paragraph = substr($html, $start, $end-$start+4);

Line 1 gets the position of the first opening <p> tag

Line 2 gets the position of the first </p> after the first opening <p>

Line 3 then uses substr to get the HTML. The third parameter is the number of characters to copy and is calculated by subtracting $start from $end and adding on the length of "</p>" so it is included in the extracted HTML.

Converting to plain text

If the extracted paragraph needs to be in plain text rather than HTML, use the following to remove the HTML tags and convert HTML entities into normal plain text:

$paragraph = html_entity_decode(strip_tags($paragraph));

Notepad++ fails to updateNotepad++ fails to update

Posted February 7th, 2010 in Applications

Notepad++ is a feature rich Windows Notepad replacement. It contains an automatic package update feature in more recent versions but if you are using an older version you will be prompted to download an update package and then apparantly nothing happens.

The prompt

After starting Notepad++, if there is a package update available it will show the following prompt:

an update package is available

This says "An update package is available, do you want to download it?".

If you click "Yes" then it downloads the update and displays another dialog "Notepad++ is opened. Updater will close it in oreer ot process the installation. Continue?" Clicking "Yes" then appears to do nothing. Starting up Notepad++ again shows the first update prompt shown above again.

Version requirement

The updater only works for Notepad++ versions 5.6.4 and higher. If you have an older version then it will not work, so you have to download an up to date version of Notepad++ from Sourceforge.

Which version

To check which version you are running click the ? menu item in the main menu then "About Notepad++" as shown in the screenshot below.

select about notepad++

This will then show the about dialog with the version number, which I have highlighted with a red box in the final screenshot below.

about notepad++ dialog

After installing and running, Notepad++ will again check for updates and prompt to download and install with the improved updater. This time when prompted to install and restart it actually does.

Add an icon before or after a link with CSSAdd an icon before or after a link with CSS

Posted February 6th, 2010 in HTML and CSS

CSS has a matching syntax for selectors which makes it possible to match the filename extension at the end of an href. This makes it easy to add an icon which relates to the link before or after the text part of the link.

Matching the filename extension

This works in all modern browsers. It does not work in Internet Explorer 6 but that is becoming less relevent these days. If the browser does not support the matching syntax then it simply won't render the image or any of the associated styling so it won't break anything.

For example, to match all zip files add the following to your CSS file:

a[href$='.zip'] {
    /* styling goes here */
}

The $= makes the match at the end of the href part of the <a> tag.

Adding an image icon to the link

Simply add the necessary styling to add a background image aligned either to the left or right of the link, and then enough padding to the right or left respectively so the text doesn't go over the top of the image.

The doc/docx and xls/xlsx examples add the icon to the right of the text in the link and the pdf and zip examples to the left. Make sure the padding is at least the width of the image.

In the example presented below the images are 16px x 16px so a padding of 18px allows for a couple of pixels space between the text and the icon.

a[href$='.doc'], a[href$='.docx'] {
    background: transparent url(/images/content/icon_word.gif) center right no-repeat;
    padding-right: 18px;
}
a[href$='.xls'], a[href$='.xlsx'] {
    background: transparent url(/images/content/icon_excel.gif) center right no-repeat;
    padding-right: 18px;
}
a[href$='.pdf'] {
    background: transparent url(/images/content/icon_pdf.gif) center left no-repeat;
    padding-left: 18px;
}
a[href$='.zip'] {
    background: transparent url(/images/content/icon_zip.gif) center left no-repeat;
    padding-left: 18px;
}

Working example

Here's the above example working in action. This won't work if you are viewing this in an RSS feed reader so click through to view it online in a web browser.

Browser feature detection with jQuery $.supportBrowser feature detection with jQuery $.support

Posted February 5th, 2010 in Javascript

Rather than working out the engine and version with Javascript for doing stuff that may work differently in different browsers, it's better to use feature detection instead. jQuery has the $.support property which has a number of flags to indicate the presence of various features or bugs. I'll look at a couple of the more useful flags here, but be sure to read the documentation for full details about all the properties.

Show all the flags using $.each

The following jQuery code loops through all the flags available using jQuery's $.each function appending the values to an unordered list. This needs to be done in a document ready block as some of the properties are null until document ready.

$(document).ready(function() {

    $("#example").html('<ul></ul>');
    var example_ul = $("#example ul");
    $.each($.support, function(key, val) {
    	example_ul.append('<li><strong>' + key + "</strong> : " + val + "</li>");
    });
    
});

Here's the above code in action. It will not work if you are reading this in a feed reader so be sure to click through to view this post in a web browser.

The $.support information will render here.

boxModel flag

The $.support.boxModel flag indicates whether or not the browser supports the W3C CSS Box Model. This property is null until the document is ready, and can be useful if you need to make calculations based on the width of an element that has margins, padding and/or borders. It's true for all browsers except for Internet Explorer when it's in quirks mode.

The simplest solution is to make sure IE is in standards mode and not in quirks mode then you don't need to worry about whether the box model is being adhered to or not. However, in some cases it may not be possible to simply change the doctype (or add one) which makes IE go into standards compliant mode as it break other stuff. In this case using jQuery's $.support.boxModel flag may be useful.

noCloneEvent flag

Another potentially useful flag is the noCloneEvent flag. If it's true then DOM elements that are cloned will be created without cloning any event handlers (which is the case for non-IE browsers); if it's false then cloned DOM elements will also have their event handlers cloned (this is the case for Internet Explorer).

I personally think the naming of this flag is a bit confusing (cloneEvent would be better with the true/false reversed) but I can see the use of this flag should you need to clone a DOM element and need to know if event handlers attached to the element need to be recreated if they will not be cloned.

Other flags

The boxModel and noCloneEvent flag are the two I see as being the most useful - if I'd known about these properties in the past I would have used the boxModel one on at least one website. Make sure to read the documentation for a basic overview of each of the other flags.

Catch uncaught exceptions with PHPCatch uncaught exceptions with PHP

Posted February 4th, 2010 in PHP

This is the third in a series of posts about exception handling in PHP and looks at how to specify a default exception handler. The default handler is called for any exceptions that occur which are not enclosed in a try..catch block.

Normally catch exceptions in a try..catch block

If an exception is handled inside a try..catch block it will not result in an error. For example no error will be output using the code below, unless you choose to echo something in the catch section.

try {
    throw new Exception("This is an example exception");
}
catch(Exception $e) {
   
}

When an exception occurs that is not in a try..catch block

However, if the exception occurs and is not inside a try..catch block:

throw new Exception("This is an example exception");

then an error will be echoed to standard output:

Fatal error: Uncaught exception 'Exception' with message 'This is an example exception' in /common/websites/test/exceptions-example.php:5
Stack trace:
#0 {main}
  thrown in /common/websites/test/exceptions-example.php on line 5

Creating an exception handler

Normally you'd put something which could possibly have an exception into a try..catch block but it's possible instead to handle all exceptions with a specified exception handler using the set_exception_handler() function.

This may not necessarily be the best practise but it is possible to do and may help catch out errors in your code which you may not have realised could throw an exception.

This is done by defining an exception handling function and setting it like so:

set_exception_handler('my_exception_handler');

function my_exception_handler($e) {
    // do some erorr handling here, such as logging, emailing errors
    // to the webmaster, showing the user an error page etc
}

Now when any exception occurs that is not handled by a try..catch block it will run the my_exception_handler() function. Obviously you can call your own exception handler whatever you want it to be.

Exception Series

This is the third post in a weekly series of six about PHP exceptions. Read the previous post "PHP Exceptions - available information", stay tuned this time next week for the next post "Replace error reporting with exception handlers with PHP" and use the links below to subscribe to my RSS feed, by email, or follow me on Twitter or Facebook to keep up to date with my daily postings and with this series.

An alternative to ORDER BY RAND() for MySQLAn alternative to ORDER BY RAND() for MySQL

Posted February 3rd, 2010 in MySql and PHP

I've posted previously about how to randomly order a resultset with MySQL using RAND() but the issue with RAND() is it will be inefficient on large tables because each row needs to have the random number calculated before the resultset can be ordered. This post looks at an alternative which requires two queries but will be much more efficient for large tables.

The alternative, and a note about INNODB vs MyISAM tables

The alternative suggested in this post uses COUNT(*) first to get the number of records in the table and then picks the record by using MySQL's LIMIT syntax. This works well for MyISAM tables which cache the number of records in a table but not for INNODB tables which do not.

Example table

I often use an example table containing fruit. Here's the output from SELECT * FROM fruit:

+----------+--------+-----------+
| fruit_id | name   | somevalue |
+----------+--------+-----------+
|        1 | Banana |         2 |
|        2 | Orange |         4 |
|        3 | Cherry |         3 |
|        4 | Apple  |         1 |
+----------+--------+-----------+

MySQL only solution

It's possible to do this entirely with MySQL SQL queries without having to run code in PHP or other programming language. The following SQL queries first gets a count from the table, then selects a random offset based on that count. It then prepares a statement so the calculated offset can be used and executes the statement. Note that the offset is cast as a signed integerl without this you'll get the error message "ERROR 1210 (HY000): Incorrect arguments to EXECUTE".

SELECT @count := COUNT(*) FROM fruit;
SET @offset = CONVERT(FLOOR(RAND() * @count), SIGNED);
PREPARE mystatement FROM "SELECT * FROM fruit LIMIT ?, 1";
EXECUTE mystatement USING @offset;
DEALLOCATE PREPARE mystatement;

The output from the above will result in a random record returned each time e.g.:

+----------+--------+-----------+
| fruit_id | name   | somevalue |
+----------+--------+-----------+
|        3 | Cherry |         3 |
+----------+--------+-----------+

This works from the MySQL command line but doesn't appear to work in tools like phpMyAdmin (which does successfully execute the SQL but doesn't output any data) or MySQL Query Browser. It does work programatically from PHP (and therefore will for other programming languages). For exmample this:

mysql_query('SELECT @count := COUNT(*) FROM fruit');
mysql_query('SET @offset = CONVERT(FLOOR(RAND() * @count), SIGNED)');
mysql_query('PREPARE mystatement FROM "SELECT * FROM fruit LIMIT ?, 1"');
$res = mysql_query('EXECUTE mystatement USING @offset');
$row = mysql_fetch_assoc($res);
print_r($row);

outputs this:

Array
(
    [fruit_id] => 1
    [name] => Banana
    [somevalue] => 2
)

Using a programming language

Doing this outside MySQL is done in a similar way by getting the count from the table first, working out a random offset and then running a second query to get the record. This example uses PHP:

$res = mysql_query("SELECT COUNT(*) FROM fruit");
$row = mysql_fetch_array($res);
$offset = rand(0, $row[0]-1);

$res = mysql_query("SELECT * FROM fruit LIMIT $offset, 1");
$row = mysql_fetch_assoc($res);

Conclusion

My example only uses a small table and in this instance would be easier to simply use "ORDER BY RAND()" but ordering in this way is inefficient and performance will start to suffer as the table grows. For MyISAM tables it will be more efficient to run a couple of queries instead to work out an offset based on the number of records in the table, and then use MySQL's LIMIT syntax to pull that record out of the table as shown in this post.

Detecting the browser engine and version with jQueryDetecting the browser engine and version with jQuery

Posted February 2nd, 2010 in Javascript

While you ideally shouldn't use browser detection to choose whether or not to render something in a particular fashion, jQuery does provide the capability to work out which browser rendering engine and version is being used with $.browser. There is also $.support which provides information about what the rendering engine supports and I will look at that in a later post.

Working example

A piece of Javascript directly after this paragraph will show the browser flags that jQuery detects and the version. If you are reading this in a feed reader then this will not work and you will need to click through to read this article in a web browser.

Warning: $.browser is deprecated

The $.browser property was deprecated in jQuery 1.3 but there are no immediate plans to remove it from jQuery. As I noted in the first paragraph, ideally it shouldn't be used anyway but there may be times for debugging or logging that it may be useful to record this information.

$.browser properties

jQuery's $.browser has several true/false flags which indicate which browser engine is being used. These can be tested in conditional statements or the values dispayed in an each loop.

The available flags are:

  • mozilla
  • msie
  • opera
  • safari
  • webkit (added in jQuery 1.4)

For jQuery versions < 1.4 Chrome and Safari are flagged as safari; in jQuery 1.4 they are both flagged as both safari and webkit. In future versions they will presumably instead only be flagged as webkit.

There is also a "version" property which reports the rendering engine's version number. This is not the same as the browser's version number. For example, for Chrome version 4.0.249.78 $.browser.version will be 532.5; for Firefox 3.5.7 $.browser.version will be 1.9.1.7

Using $.browser in a conditional

The following example does one thing or another depending on the flag that is set:

if($.browser.webkit) {
    // do something
}
else if($.browser.safari) {
    // do something
}
else if($.browser.opera) {
    // do something
}
else if($.browser.msie) {
    // do something
}
else if($.browser.mozilla) {
    // do something
}

Using $.browser with $.each

To loop through all the properties in $.browser do the following:

$.each($.browser, function(key, val) {
    document.write(key + " : " + val + "<br />");
});

This is what is done in the working example at the start of this post. Instead of writing it to the document the information could be put into a string and sent along with an AJAX request etc.

Monthly Roundup - January 2010 - StatisticsMonthly Roundup - January 2010 - Statistics

Posted February 2nd, 2010 in Monthly Roundup

My monthly stats round up posts a summary of the most read posts over the previous month, numbers of visitors to the blog and any changes I have made over the month that may have affected visitor numbers etc. This is the monthly stats roundup for January 2010.

25 most popular articles in January 2010

The following were the top 25 most accessed articles on this blog in January 2010, along with the original post date. The list is compiled via the Google Analytics API to get the unique pageviews for posts in the month. Last month's rank is shown in square brackets after the post title, or [-] if it wasn't in the top 25 last month.

Note that unique pageviews means it's only counted once for each visitor so if someone views the page multiple times it's still only counted once for that user.

  1. How to get and set form element values with jQuery [1] (November 13th 2008)
  2. How to check and uncheck a checkbox with jQuery [2] (November 14th 2008)
  3. Setting cookies with jQuery [4] (June 9th 2009)
  4. Loading content with jQuery AJAX - using a loading image [3] (February 3rd 2009)
  5. Using the HTTP_REFERER variable with PHP [6] (April 18th 2008)
  6. Howto Restart Apache [8] (December 22nd 2004)
  7. Windows Vista Black Screen After Login [5] (May 29th 2008)
  8. Delete All Data in a MySQL Table [10] (July 16th 2004)
  9. Cross Table Update with MySQL [9] (March 1st 2004)
  10. Windows Vista Black Screen After Login - Resolved [7] (June 26th 2008)
  11. Using setTimeout() with Javascript [11] (January 27th 2009)
  12. Footer example using HTML and CSS [12] (January 8th 2009)
  13. MySQL utility commands [-] (December 30th 2009)
  14. Use append() to add text/html to an element with jQuery [15] (June 5th 2009)
  15. How to tell if an element is visible with jQuery [-] (September 18th 2009)
  16. Formatting Dates and Times with MySQL [13] (July 17th 2004)
  17. Upper case and lower case strings with MySQL [14] (July 29th 2008)
  18. How to check an MD5 hash on a file [18] (January 19th 2004)
  19. Maximum length for MySQL TEXT field types [-] (February 4th 2009)
  20. Open firewall ports for MSN Messenger and ICQ [16] (February 13th 2004)
  21. Add options to an HTML select box with Javascript [26] (March 3rd 2009)
  22. How to do the TM symbol in HTML [23] (November 8th 2008)
  23. Describe table structure with MS SQL Server [21] (July 23rd 2008)
  24. Test if a file exists with the BASH shell [22] (August 27th 2008)
  25. Install yum with rpm on CentOS [17] (October 16th 2007)

Visitor numbers

I get the visitor numbers from Google Analytics. There was quite a big level off at the end of 2009 due to the Thanks Giving, Christmas and New Year holiday period but visitor numbers resumed to better than ever levels from mid January and even though February has less days than January there should still be an increase in numbers from January to February.

Month Visits Pageviews PV Per Visit
August 2009 145,733 179,639 1.23
September 2009 159,128 197,627 1.24
October 2009 185,530 229,555 1.24
November 2009 189,897 234,301 1.23
December 2009 189,386 235,197 1.24
January 2010 214,703 266,466 1.24

Subscribers/Followers

I use Feedburner/Google to serve my RSS feed and give an indication of visitor numbers. I curently have around 575 RSS subscribers, although the number bobs up and down as the week goes by, peaking around Wednesday or Thursday where it's got as high as about 620 recently. This is up by about 100 from the end of December and is the highest level to date.

There are also 9 people receiving the feed by email (1 less than last month). 139 people are following me on Twitter (27 more than last month) and there are 35 people following me on Facebook (9 more than last month). Hello to you all!

How to escape variables with PHP PEAR DB with bound placeholdersHow to escape variables with PHP PEAR DB with bound placeholders

Posted February 1st, 2010 in PHP

A week ago I looked at how to escape a string for use with a SQL query using the PHP PEAR DB escapeSimple function. This is useful for embedding a variable directly into a SQL query string but it can be a lot easier to use bound placeholders instead and let PEAR DB do all the escaping.

Query string and data

The examples below use a query string and array of data as follows:

$query = "SELECT * FROM fruit WHERE name = ?";
$data = array('apple');

Note the ? at the end of the SQL query. This is the bound placeholder and will automatically be escaped by PEAR DB if required. It is therefore safe to pass it a string which has been passed from a web form or similar. In the above example if the word 'apple' contained any characters that required escaping (such as a single quote) then these will be escaped before the query is run.

Using prepare and execute

The first example uses a two step process to first prepare the query with the SQL string and then execute it by passing the data. This is useful if the same SQL query needs to be run multiple times.

$resource = $db->prepare($query);
$result = $db->execute($resource, $data);
while($row = $result->fetchRow()) {
    print_r($row);
}

The example above prepares the query and the resulting resource is then passed to the execute function. The data is retrieved with the fetchRow function and then echoed with print_r.

Cutting down the steps with query

The above can be simplified slightly with a single call to query instead of the two calls to prepare and execute:

$result = $db->query($query, $data);
while($row = $result->fetchRow()) {
    print_r($row);
}

getAll, getCol, getRow functions

There are several other functions which also execute SQL queries and can accept bound placeholders. Some of these are getAll, getCol and getRow:

$rows = $db->getAll($query, $data); // returns all rows
$col = $db->getCol($query, $data); // returns first column from first row only
$row = $db->getRow($query, $data); // returns first row only

Refer to the PEAR DB documentation for more examples and other functions.

Monthly Roundup - January 2010 - Post SummaryMonthly Roundup - January 2010 - Post Summary

Posted February 1st, 2010 in Monthly Roundup

At the start of each month I post a complete list of articles posted in the previous month. This is the monthly summary for January 2010. This is followed up on the second day of the month (i.e. tomorrow) with statistics relating to visitor numbers and most popular posts for the previous month.

The posts made in January 2010 are as follows, broken down into categories. Note that some posts appear in more than one category so appear below more than once. Despite going on holiday for a week at the end of January I continued to post daily posts while I was away, writing them on my new netbook at the beach. I'll review my netbook sometime later this week.

Apache

Applications

HTML and CSS

Javascript

Miscellaneous Postings

MySql

PHP

VMWare

The second part of this monthly roundup, tomorrow, will look at the most popular posts read on the blog in January and some other stats.