You are on page 1of 142

PHP MySQL Tutorial

arman@php-mysql-tutorial.com

http://www.php-mysql-tutorial.com/

NOTE:
This tutorial currently only covers PHP4. When my web host support PHP5 I'll start adding some PHP5 specific features. Stay tuned for more php mysql tutorial.

PHP MySQL Tutorial

1.

Installing Apache PHP and MySQL In case you haven't installed the trio yet don't skip this section. This page explains about installing Apache, PHP and MySQL on Windows plus some images to make things clearer. It also covers modifying Apache configuration and PHP configuration so the two can work together. PHP Tutorial Give you enough to get started. First, you will learn how to open and close PHP blocks, continued with using comments, a brief explanation about PHP variables and types. Then you'll learn about manipulating strings, control structures, functions and how to use web forms. MySQL Tutorial You will learn about starting MySQL, adding new MySQL user, creating a database and tables. Then you'll learn about the SQL queries to insert data,get the data, update and delete. Connecting to MySQL database This is where you start to put PHP and MySQL together. This page explains how to open and close MySQL connection with PHP. Creating a MySQL database Obviously you will need to create your database first. This part explains how to create MySQL database and table through PHP Insert Data To MySQL Database After you have the database and tables ready it's time to learn how to insert your data into the database. Getting The Data Once you have the data stored in the database surely you want to get it back. This page explains how to get your data out of MySQL plus how to convert your query result into Excel format Using Paging This one explains how to show your query result into multiple pages and how to create the navigation link. Also show the problem that might happen when using paging and the solution. Update and Delete Explains how to update and delete your data and how to use table locking to prevent violation of data integrity.

2.

3.

4.

5.

6.

7.

8.

9.

10. Using PHP To Backup MySQL Database In this page you can learn three different ways to backup your MySQL database 11. Form Validation This one explains how to validate HTML form on server side using PHP plus client side form validation using Javascript to make your form more user friendly. 12. Creating a Guestbook Guestbook is one of the most common feature for a website and this tutorial will teach you how to create your own guestbook using PHP and MySQL. It also explain how to use PHP functions to prevent code injection and the use of paging. 13. Uploading Files to MySQL This page describe how to upload a file to MySQL database and how to download it back. 14. Creating a Content Management System (CMS) Content Management System is getting more and more popular by the day. This tutorial explains how to create a simple CMS, how to add, modify and delete content using web form. 15. User Authentication This part explain three methods of authenticating a user. The first is hardcoding the user info in the script itself. The second one check for the user id and password in database. The third one add an random number verification. 16. Image Gallery Just another tutorial on making an image gallery 17. Finding Web Hosting for PHP and MySQL

Just some tips for choosing the right host. 18. Freelance PHP and MySQL jobs Internet is the best place to find freelance jobs. This page show one of them and also some list you need to think about before going freelancing. 19. Q & A I put some of the questions that i received here ( plus the answers ). Also added the common queries regarding php and mysql 20. PHP MySQL Bookstore Since this tutorial doesn't cover everything about php and mysql programming I decided to add a book store on this site. Here you can find several great books about programming with PHP and MySQL. And if you're thinking about replacing your old computer checkout the computer store 21. Shopping Cart Tutorial I finally complete this tutorial. It's kindof big so i put it in it's own domain. Because it's new i'm begging you to send me your critiques. The demo site is working with the administration stuff disabled ( like add/modify/delete product ) but you can download the source code so you can try it on your computer. 22. Online resources Some website related to PHP and MySQL. Actually a couple of these resources are not related to PHP or MySQL but I put them there anyway because they are useful. Check it out, you may find some that interest you

Contents
1. Installing Apache PHP and MySQL 2. PHP Tutorial 3. MySQL Tutorial o Starting MySQL o Add New MySQL User o Create New MySQL Database o Create New Table o Insert Data to MySQL o Get Data from MySQL o MySQL Update and Delet o Delete Data 4. Connect to MySQL Database 5. Create MySQL Database with PHP 6. Insert Data to MySQL Database 7. Get Data from MySQL database o Freeing the memory? o Convert MySQL Query to Excel 8. Using Paging 9. MySQL Update and Delete 10. Using PHP to Backup MySQL database o Execute a Database Backup Query from PHP file o Run mysqldump using system() function o Use phpMyAdmin to do the backup 11. Form Validation with PHP 12. Create A Guestbook o Create the Sign-Guestbook Form o View the entries o Showing the entries in multiple pages o Room for Improvements 13. Uploading Files to MySQL Database o Downloading Files from MySQL Database o Uploading Files to File Server Using PHP o Downloading o Preventing Direct Access o Handling duplicate file names 14. Content Management System (CMS) o Admin Page for Content Management System (CMS) o Delete Article o Edit Article 15. User Authentication o Basic User Authentication o Checking if the usesr is logged in or not o Better User Authentication: Storing o Uder id and Password in Database o User Authentication with Image Verification o The Login Form o Improving the Verification Image 16. Image Gallery o Image Gallery Administration Page o Login and Logout o Admin Page Layout o Admin: Add New Album o Admin: Album List o Modify and Delete Album o Delete Album o Admin: Add Image

o o o o

Image List Modify and Delete Image Album List Image List and Detail

17. Finding Web Hosting for PHP and MySQL 18. Freelance PHP MySQL Jobs 19. Q & A 20. PHP MySQL Bookstore 21. Shopping Cart Tutorial o Introduction o Shopping Cart Data Base Design o Database Abstract o Admin Control Panel o Admin login o Admin-View Category o Admin-Add Category o Admin-Edit Category o Admin-Delete Category o Admin-Add Product o Admin-View Product o Admin-Edit Product o Admin-Delet Product o Admin-Order Management o Admin-Site Configuration o Admin-User Management o Shop-Main Page o Shop-Browse Category o Shop-View Product List o Shop-View Product Detail o Shop-Add to Cart o Shop-View Shopping Cart o Shop-Chechkout o Shipping and Mayment info o Checkout confirmation o Payment Processing with PayPal o Handling PayPal Instant Payment Notification (IPN) o Shopping Cart Source Code o Shopping Cart Resources 22. PHP MySQL Resources

1. Installing Apache PHP and MySQL


PHP and MySQL are usually associated with LAMP (Linux, Apache, MySQL, PHP). However, most PHP developer ( including me ) are actually using Windows when developing the PHP application. So this page will only cover the WAMP ( Windows, Apache, MySQL, PHP ). You will learn how to install Apache, PHP, and MySQL under Windows platform. The first step is to download the packages :

Apache : www.apache.org PHP : www.php.net MySQL : www.mysql.com


You should get the latest version of each packages. As for the example in this tutorial i'm using Apache 2.0.50 ( apache_2.0.50-win32-x86-no_ssl.msi ), PHP 4.3.10 ( php-4.3.10-Win32.zip ) and MySQL 4.0.18 ( mysql4.0.18-win.zip ). Now let's start the installation process one by one.

Installing Apache Installing PHP Modifying Apache Configuration Installing MySQL Modifying PHP Configuration File Installing Apache
Installing apache is easy if you download the Microsoft Installer ( .msi ) package. Just double click on the icon to run the installation wizard. Click next until you see the Server Information window. You can enter localhost for both the Network Domain and Server Name. As for the administrator's email address you can enter anything you want. I'm using Windows XP and installed Apache as Service so everytime I start Windows Apache is automatically started.

Click the Next button and choose Typical installation. Click Next one more time and choose where you want to install Apache ( I installed it in the default location C:\Program Files\Apache Group ). Click the Next button and then the Install button to complete the installation process. To see if you Apache installation was successful open up you browser and type http://localhost in the address bar. You should see something like this :

By default Apache's is set to htdocs directory. The document root is where you must put all your PHP or HTML files so it will be process by Apache ( and can be seen through a web browser ). Of course you can change it to point to any directory you want. The configuration file for Apache is stored in C:\Program Files\Apache Group\Apache2\conf\httpd.conf ( assuming you installed Apache in C:\Program Files\Apache Group ) . It's just a plain text file so you can use Notepad to edit it. For example, if you want to put all your PHP or HTML files in C:\www just find this line in the httpd.conf : DocumentRoot "C:/Program Files/Apache Group/Apache2/htdocs" and change it to : DocumentRoot "C:/www" After making changes to the configuration file you have to restart Apache ( Start > Programs > Apache HTTP Server 2.0.50 > Control Apache Server > Restart ) to see the effect.

document root

Another configuration you may want to change is the This is the file that Apache will show when you request a directory. As an example if you type http://www.php-mysql-tutorial.com/ without specifying any file the index.php file will be automatically shown. Suppose you want apache to use index.html, index.php or main.php as the directory index you can modify the DirectoryIndex value like this : DirectoryIndex index.html index.php main.php Now whenever you request a directory such as http://localhost/ Apache will try to find the index.html file or if it's not found Apache will use index.php. In case index.php is also not found then main.php will be used.

directory index.

Installing PHP
First, extract the PHP package ( php-4.3.10-Win32.zip ). I extracted the package in the directory where Apache was installed ( C:\Program Files\Apache Group\Apache2 ). Change the new created directory name to php ( just to make it shorter ). Then copy the file php.ini-dist in PHP directory to you windows directory ( C:\Windows or C:\Winnt depends on where you installed Windows ) and rename the file to php.ini. This is the PHP configuration file and we'll take a look what's in it later on. Next, move the php4ts.dll file from the newly created php directory into the sapi subdirectory. Quoting from php installation file you can also place php4ts.dll in other places such as :

In the directory where apache.exe is start from ( C:\Program

Files\Apache Group\Apache2 \bin) In your %SYSTEMROOT%\System32, %SYSTEMROOT%\system and %SYSTEMROOT% directory.

In your whole %PATH%

Note: %SYSTEMROOT%\System32 only applies to Windows NT/2000/XP)

Side Note : Thanks to Shannon Tang for pointing this out


Modifying Apache Configuration
Apache doesn't know that you just install PHP. We need to tell Apache about PHP and where to find it. Open the Apache configuration file in C:\Program Files\Apache Group\Apache2\conf\httpd.conf and add the following three lines : LoadModule php4_module php/sapi/php4apache2.dll AddType application/x-httpd-php .php AddType application/x-httpd-php-source .phps The first line tells Apache where to load the dll required to execute PHP and the second line means that every file that ends with .php should be processed as a PHP file. You can actually change it to anything you want like .html or even .asp! The third line is added so that you can view your php file source code in the browser window. You will see what this mean when you browse this tutorial and click the link to the example's source code like this one. Now restart Apache for the changes to take effect ( Start > Programs > Apache HTTP Server 2.0.50 > Control Apache Server > Restart ) . To check if everything is okay create a new file, name it as test.php and put it in document root directory ( C:\Program Files\Apache Group\Apache2\htdocs ). The content of this file is shown below. <?php phpinfo(); ?> phpinfo() is the infamous PHP function which will spit out all kinds of stuff about PHP and your server configuration. Type http://localhost/test.php on your browser's address bar and if everything works well you should see something like this :

Installing MySQL
First extract the package ( mysql-4.0.18-win.zip ) to a temporary directory, then run setup.exe. Keep clicking the next button to complete the installation. By default MySQL will be installed in C:\mysql. Open a DOS window and go to C:\mysql\bin and then run mysqld-nt --console , you should see

some messages like these :

C:\mysql\bin>mysqld-nt --console
InnoDB: The first specified data file .\ibdata1 did not exist: InnoDB: a new database to be created! 040807 10:54:09 InnoDB: Setting file .\ibdata1 size to 10 MB InnoDB: Database physically writes the file full: wait... 040807 10:54:11 InnoDB: Log file .\ib_logfile0 did not exist: new to be created InnoDB: Setting log file .\ib_logfile0 size to 5 MB InnoDB: Database physically writes the file full: wait... 040807 10:54:12 InnoDB: Log file .\ib_logfile1 did not exist: new to be created InnoDB: Setting log file .\ib_logfile1 size to 5 MB InnoDB: Database physically writes the file full: wait... InnoDB: Doublewrite buffer not found: creating new InnoDB: Doublewrite buffer created InnoDB: Creating foreign key constraint system tables InnoDB: Foreign key constraint system tables created 040807 10:54:31 InnoDB: Started mysqld-nt: ready for connections. Version: '4.0.18-nt' socket: '' port: 3306 Now open another DOS window and type C:\mysql\bin\mysql if your installation is successful you will see the MySQL client running :

C:\mysql\bin>mysql
Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 1 to server version: 4.0.18-nt Type 'help;' or '\h' for help. Type '\c' to clear the buffer. mysql> Type exit on the mysql> prompt to quit the MySQL client.

Now let's install MySQL as a Service The process is simple just type mysqld-nt --install to install the service and net start mysql to run the service. But make sure to shutdown the server first using mysqladmin -u root shutdown C:\mysql\bin>mysqladmin -u root shutdown

C:\mysql\bin>mysqld-nt --install
Service successfully installed. C:\mysql\bin>net start mysql The MySQL service was started successfully.

C:\mysql\bin>mysql Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 1 to server version: 4.0.18-nt Type 'help;' or '\h' for help. Type '\c' to clear the buffer. mysql>

Modifying PHP Configuration File ( php.ini )


PHP stores all kinds of configuration in a file called php.ini.You can find this file in the directory where you installed PHP. Sometimes you will need to modify this file for example to use a PHP extension. I won't explain each and every configuration available just the ones that often need modification or special attention. Some of the configurations are : 1. register_globals 2. error_reporting and display_errors 3. extension and extension_path 4. session.save_path 5. max_execution_time

register_globals

On and after 4.2.0 the default value is Off. The reason for this change is because it is so easy to write insecure code with this value on. So make
Before PHP 4.2.0 the default value for this configuration is sure that this value is Off in php.ini.

error_reporting and display_errors


Set the value to error_reporting = E_ALL during development but after production set the value to error_reporting = E_NONE . The reason to use E_ALL during development is so you can catch most of the nasty bugs in your code. PHP will complain just about any errors you make and spit out all kinds of warning ( for example if you're trying to use an uninitialized variable ). However, after production you should change the value to E_NONE so PHP will keep quiet even if there's an error in your code. This way the user won't have to see all kinds of PHP error message when running the script. One important thing to note is that you will also need to set the value of display_erros to On. Even if you set error_reporting = E_ALL you will not get any error message ( no matter how buggy our script is ) unless display_errors is set to On.

extension and extension_path


PHP4 comes with about 51 extensions such as GD library ( for graphics creation and manipulation ), CURL, PostgreSQL support etc. These extensions are not turned on automatically. If you need to use the extension, first you need to specify the location of the extensions and then uncomment the extension you want. The value of extension_path must be set to the directory where the extension is installed which is PHP_INSTALL_DIR/extensions, with PHP_INSTALL_DIR is the directory where you install PHP. For example I installed PHP in C:\Program Files\Apache Group\Apache2\php so the extensions path is : extension_path = C:/Program Files/Apache Group/Apache2/php/extensions/ Don't forget to add that last slash or it won't work After specifying the extension_path you will need to uncomment the extension you want to use. In php.ini a comment is started using a semicolon (;). As an example if you want to use GD library then you must remove the semicolon at the beginning of ;extension=php_gd2.dll to extension=php_gd2.dll

session.save_path
This configuration tells PHP where to save the session data. You will need to set this value to an existing directory or you will not be able to use session. In Windows you can set this value as session.save_path = c:/windows/temp/

max_execution_time
The default value for max_execution_time is 30 ( seconds ). But for some scripts 30 seconds is just not enough to complete it's task. For example a database backup script may need more time to save a huge database. If you think your script will need extra time to finish the job you can set this to a higher value. For example to set the maximun script execution time to 15 minutes ( 900 seconds ) you can modify the configuration as max_execution_time = 900 PHP have a convenient function to modify PHP configuration in runtime, ini_set(). Setting PHP configuration using this function will

not make the effect permanent. It last only until the script ends.

That's it, now you have Apache, PHP and MySQL installed and running smoothly. It's time to move on to the next section, PHP Tutorial.

2. PHP Tutorial
What is PHP? PHP is a web programming language used to write dynamic webpages. In this tutorial you will learn basics of PHP. I will assume you already know a bit about programming language so I won't cover the whole thing. This PHP Tutorial will explain the followings :

Opening and ending PHP tags Comments Variables Types Playing with strings Control structures Functions Using Forms
First, make sure you already have PHP installed on your computer. If you don't have it installed visit the first part of this tutorial, Install Apache PHP and MySQL. By the way this tutorial only covers PHP 4 not the old PHP 3 ( I wonder if anyone still use it ? ). By the way, if you don't already use a suitable editor for coding check out this page. If you already have your favorite editor just skip it and move on to the next part : Opening and ending PHP tags

PHP Editors
Because PHP files are just plain text files you can use any editor to create and edit PHP files. But even though you can use simple text editor like Windows Notepad it is alot easier if you use an editor capable of syntax highlighting and advance text manipulation. For a simple file like hello.php syntax highlighting will do nothing good, but when you write long pages it's alot easier to find mistakes just by looking at the text color. Some editors you can use are :

Editplus Macromedia Dreamweaver Frontpage Eclipse ( with PHP plugin )


Personally I like using Dreamweaver and Eclipse. I like Dreamweaver because my PHP code is usually mixed with HTML, and creating HTML tables and forms are just so easy in Dreamweaver. However, Dreamweaver doesn't support CVS (Concurrent Versioning System) which is critical when running projects with many programmers. So currently I'm using Eclipse for creating PHP files. By the way Eclipse is a free software and the PHP plugin is also free. If you have tight budget but in need of a good editor you should try Eclipse.

Opening & Ending PHP Tags


To open a block of PHP code in a page you can use one of these four sets of opening and closing tags

Opening Tag <? <?php <% <script language="php">

Closing Tag ?> ?> %> </script>

The first pair (<? and ?>) is called short tags. You should avoid using short tags in your application especially if it's meant to be distributed on other servers. This is because short tags are not always supported ( though I never seen any web host that don't support it ). Short tags are only available only explicitly enabled setting the short_open_tag value to On in the PHP configuration file php.ini. So, for all PHP code in this website I will use the second pair, <?php and ?>. Now, for the first example create a file named hello.php ( you can use NotePad or your favorite text editor ) and put it in your web servers root directory. If you use Apache the root directory is APACHE_INSTALL_DIR\htdocs, with APACHE_INSTALL_DIR is the directory where you install Apache. So if you install Apache on C:\Program Files\Apache Group\Apache2\htdocs then you put hello.php in C:\Program Files\Apache Group\Apache2\htdocs Example : hello.php Source code : hello.phps <html> <head> <title>My First PHP Page</title> </head> <body>

<?php echo "<p>Hello World, How Are You Today?</p>"; ?> </body> </html> To view the result start Apache then open your browser and go to http://localhost/hello.php or http://127.0.0.1/hello.php. You should see something like this in your browser window. The example above shows how to insert PHP code into an HTML file. It also shows the echo statement used to output a string. See that the echo statement ends with a semicolon. Every command in PHP must end with a semicolon. If you forget to use semicolon or use colon instead after a command you will get an error message like this Parse error: parse error, unexpected ':', expecting ',' or ';' in c:\Apache\htdocs\examples\get.php on line 7 However in the hello.php example above omitting the semicolon won't cause an error. That's because the echo statement was immediately followed by a closing tag.

Using Comments
Comment is a part of your PHP code that will not be translated by the PHP engine. You can use it to write documentation of your PHP script describing what the code should do. A comment can span only for one line or span multiple line. PHP support three kinds of comment tags : 1. // This is a one line comment 2. # This is a Unix shell-style comment. It's also a one line comment 3. /* ..... */ Use this multi line comment if you need to. Example : comments.php Source code : comments.phps <?php echo "First line <br>"; // You won't see me in the output // I'm a one liner comment /* Same thing, you won't see this in the output file */ echo "The above comment spans two lines <br>"; # Hi i'm a Unix shell-style comment # Too bad you can't see me echo "View the source of this file, you'll see no comments here <br>"; ?>

PHP Variables
Variables in PHP are represented by a dollar sign followed by the name of the variable. The variable name is case-sensitive, so $myvar is different from $myVar. A valid variable name starts with a letter or underscore, followed by any number of letters, numbers, or underscores. Example : <?php $myvar = "Hello"; // valid $yourVar_is-123 = "World"; // valid $123ImHere = "Something"; // invalid, starts with number ?>

Variable Scope
The scope of a variable is the context within which it is defined. Basically you can not access a variable which is defined in different scope. The script below will not produce any output because the function Test() declares no $a variable. The echo statement looks for a local version of the $a variable, and it has not been assigned a value within this scope. Depending on error_reporting value in php.ini the script below will print nothing or issue an error

message. Example : scope.php Source code : scope.phps <?php $a = 1; // $a is a global variable function Test() { echo $a; // try to print $a, but $a is not defined here } Test(); ?> If you want your global variable (variable defined outside functions) to be available in function scope you need to use the$global keyword. The code example below shows how to use the $global keyword. Example : global.php Source code : global.phps <?php $a = 1; // $a is defined in global scope ... $b = 2; // $b too function Sum() { global $a, $b; // now $a and $b are available in Sum() $b = $a + $b; } Sum(); echo $b; ?>

PHP Superglobals
Superglobals are variables that available anywhere in the program code. They are :

$_SERVER
Variables set by the web server or otherwise directly related to the execution environment of the current script. One useful variable is $_SERVER['REMOTE_ADDR'] which you can use to know you website visitor's IP address Example : ip.php Source code : ip.phps Your computer IP is <?php echo $_SERVER['REMOTE_ADDR']; ?>

$_GET
Variables provided to the script via HTTP GET. You can supply GET variables into a PHP script by appending the script's URL like this : http://www.php-mysqltutorial.com/../examples/get.php?name=php&friend=mysql or set the a form method as method="get" Example: get.php Source code : get.phps <?php echo "My name is {$_GET['name']} <br>"; echo "My friend's name is {$_GET['friend']}"; ?> Note that I put $_GET['name'] and $_GET['friend'] in curly brackets. It's necessary to use these curly brackets when you're trying to place the value of an array into a string. You can also split the string like this : echo "My name is " . {$_GET['name']} . "<br>"; but it is easier to put the curly brackets.

$_POST
Variables provided to the script via HTTP POST. These comes from a form which set method="post"

$_COOKIE $_FILES
Variables provided to the script via HTTP cookies. Variables provided to the script via HTTP post file uploads. You can see the example in Uploading files to MySql Database

$_ENV $_REQUEST $GLOBALS


Contains a reference to every variable which is currently available within the global scope of the script. You usually won't need the last three superglobals in your script. Variables provided to the script via the environment. Variables provided to the script via the GET, POST, and COOKIE input mechanisms, and which therefore cannot be trusted. It's use the appropriate $_POST or $_GET from your script instead of using $_REQUEST so you will always know that a variable comes from POST or GET.

Variable Types
PHP supports eight primitive types. Four scalar types:

boolean : expresses truth value, TRUE or FALSE. Any non zero values and non empty string are also counted as TRUE. integer : round numbers (-5, 0, 123, 555, ...) float : floating-point number or 'double' (0.9283838, 23.0, ...) string : "Hello World", 'PHP and MySQL, etc
Two compound types:

array object
And finally two special types:

resource ( one example is the return value of mysql_connect() function) NULL

In PHP an array can have numeric key, associative key or both. The value of an array can be of any type. To create an array use the array() language construct like this. Example : array.php Source code : array.phps <?php $numbers = array(1, 2, 3, 4, 5, 6); $age = array("mom" => 45, "pop" => 50, "bro" => 25); $mixed = array("hello" => "World", 2 => "It's two"; echo echo echo echo ?> "numbers[4] = {$numbers[4]} <br>"; "My mom's age is {$age['mom']} <br>"; "mixed['hello'] = {$mixed['hello']} <br>"; "mixed[2] = {$mixed[2'}";

When working with arrays there is one function I often used. The print_r() function. Given an array this function will print the values in a format that shows keys and elements Example : printr.php Source code : printr.phps <?php $myarray = array(1, 2, 3, 4, 5); $myarray[5] = array("Hi", "Hello", "Konnichiwa", "Apa Kabar"); echo '<pre>'; print_r($myarray); echo '</pre>'; ?> Don't forget to print the preformatting tag <pre> and </pre> before and after calling print_r(). If you don't use them then you'll have to view the page source to see a result in correct format.

Type Juggling
In PHP you don't need to explicitly specify a type for variables. A variable's type is determined by the context in which that variable is used. That is to say, if you assign a string value to variable $var, $var becomes a string. If you then assign an integer value to $var, it becomes an integer.

An example of PHP's automatic type conversion is the addition operator '+'. If any of the operands is a float, then all operands are evaluated as floats, and the result will be a float. Otherwise, the operands will be interpreted as integers, and the result will also be an integer. Note that this does NOT change the types of the operands themselves; the only change is in how the operands are evaluated. Example : <?php $myvar $myvar $myvar $myvar ?>

= "0"; // $myvar is string (ASCII 48) += 2; // $myvar is now an integer (2) = $foo + 1.3; // $myvar is now a float (3.3) = 5 + "10 Piglets"; // $foo is integer (15)

Type Casting
To cast a variable write the name of the desired type in parentheses before the variable which is to be cast. Example : casting.php Source code : casting.phps <?php $abc = 10; // $abc is an integer $xyz = (boolean) $abc; // $xyz is a boolean echo "abc is $abc and xyz is $xyz <br>"; ?> The casts allowed are:

(int), (integer) - cast to integer (bool), (boolean) - cast to boolean (float), (double), (real) - cast to float (string) - cast to string (array) - cast to array (object) - cast to object
Playing With Strings
Strings are probably what you will use most in your PHP script. From concatenating, looking for patterns, trim, chop etc. So I guess it's a good idea to take a better look at this creature. We will also take a peek at some string functions that you might find useful for everyday coding.

Creating a string
To declare a string in PHP you can use double quotes ( " ) or single quotes ( ' ). There are some differences you need to know about using these two. If you're using double-quoted strings variables will be expanded ( processed ). Special characters such as line feed ( \n ) and carriage return ( \r ) are expanded too. However, with single-quoted strings none of those thing happen. Take a look at the example below to see what I mean. Note that browsers don't print newline characters ( \r and \n ) so when you open string.php take a look at the source and you will see the effect of these newline characters. Example : string.php Source code : string.phps <?php $fruit = 'jamblang'; echo "My favourite fruit is $fruit <br>"; echo 'I lied, actually I hate $fruit <br>'; echo "\r\n My first line \r\n and my second line <br>\r\n"; echo ' Though I use \r\n this string is still on one line <br>'; ?>

String Concatenation
To concat two strings you need the dot ( . ) operator so in case you have a long string and for the sake of readability you have to cut it into two you can do it just like the example below. Actually if you need to write a loong string and you want to write it to multiple lines you don't need concat the strings. You can do it just like the second example below where $quote2 is split into three lines. Example : concat.php Source : concat.phps <?php

$quote1 = "Never insult Dumbledore " . "in front of me!"; $quote2 = "Nami, you are my nakama!"; echo $quote1 . "<br>"; echo $quote2; ?>

String Functions

substr($string, $start, $end) <?php

: get a chunk of $string

// print '12' echo substr('123456789', 0, 2); // print '56789' echo substr('123456789', 4); // print '89' echo substr('123456789', -2); // print '456' echo substr('123456789', 3, -4); ?>

str_repeat($string, $n)

: repeat $string $n times

For example if you want to print a series of ten asteriks ( * ) you can do it with a for loop like this : <?php for ($i = 0; $i < 10; $i++) { echo '*'; } ?> Or you can go the easy way and do it like this : <?php echo str_repeat('*', 10); ?>

strrchr($string, $char)

: find the last occurence of the character $char in $string

For example: you want to get the file extension from a file name. You can use this function in conjunction with substr() <?php $ext = substr(strrchr($filename, '.'), 1); ?> What the above code do is get a chunk of $filename starting from the last dot in $filename then get the substring of it starting from the second character ( index 1 ). To make things clearer suppose $filename is 'tutorial.php'. Using strrchr('tutorial.php', '.') yield '.php' and after substr('.php', 1) we get the file extension; 'php'

trim($string)

: remove extra spaces at the beginning and end of $string

<?php // print 'abc def' echo trim(' abc def ?>

');

addslashes($string)

: adding backslashes before characters that need to be quoted in $string

This function is usually used on form values before being used for database queries. You will see this function used a lot in this tutorial ( like this one ) so there's no need to present an example here.

explode($separator, $string)

: Split $string by $separator

This function is commonly used to extract values in a string which are separated by a a certain separator string. For example, suppose we have some information stored as comma separated values. To extract each values we ca do it like shown below <?php // extract information from comma separated values $csv = 'Uzumaki Naruto,15,Konoha Village'; $info = explode(',', $csv); ?> Now, $info is an array with three values : Array ( [0] => Uzumaki Naruto [1] => 15 [2] => Konoha Village ) We can further process this array like displaying them in a table, etc.

implode($string, $array)

: Join the values of $array using $string

This one do the opposite than the previous function. For example to reverse back the $info array into a string we can do it like this : <?php $info = array('Uzumaki Naruto', 15, 'Konoha Village'); $csv = implode(',', $info); ?> Another example : Pretend we have an array containing some values and we want to print them in an ordered list. We can use the implode() like this : <?php // print ordered list of names in array $names = array('Uchiha Sasuke', 'Haruno Sakura', 'Uzumaki Naruto', 'Kakashi'); echo '<ol><li>' . implode('</li><li>', $names) . '</li></ol>'; ?> The result of that code is like an ordered list just like shown below 1. Uchiha Sasuke 2. Haruno Sakura 3. Uzumaki Naruto 4. Kakashi By the way, i did write the above php code to print that list instead of writing the list directly

number_format($number)

: display a number with grouped thousands

When displaying numbers it's usuallly more readable if the numbers is properly formatted like 1,234,567 instead of 1234567. Using this function is very simple like shown below <?php // display 15,120,777 echo number_format(15120777); ?>

Control Structures
The next examples will show you how to use control structures in PHP. I won't go through all just the ones that i will use in the code examples in this site. The control structures are

if else while for

If Else
The if statement evaluates the truth value of it's argument. If the argument evaluate as TRUE the code following the if statement will be executed. And if the argument evaluate as FALSE and there is an else statement then the code following the else statement will be executed. Example : visitor- info.php Source code : visitor-info.phps <?php $ip = $_SERVER['REMOTE_ADDR']; $agent = $_SERVER['HTTP_USER_AGENT']; if(strpos($agent, 'Opera') !== false) $agent = 'Opera'; else if(strpos($agent, "MSIE") !== false) $agent = 'Internet Explorer'; echo "Your computer IP is $ip and you are using $agent"; ?>

The strpos() function returns the numeric position of the first occurrence of it's second argument ('Opera') in the first argument ($agent). If the string 'Opera' is found inside $agent, the function returns the position of the string. Otherwise, it returns FALSE. When you're using Internet Explorer 6.0 on Windows XP the value of $_SERVER['HTTP_USER_AGENT'] would be something like: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1) and if you're using Opera the value the value may look like this : Mozilla/4.0 (compatible; MSIE 6.0; MSIE 5.5; Windows NT 5.1) Opera 7.0 [en] So if i you use Opera the strpos() function will return value would be 61. Since 61 !== false then the first if statement will be evaluated as true and the value of $agent will be set to the string 'Opera'. Note that I use the !== to specify inequality instead of != The reason for this is because if the string is found in position 0 then the zero will be treated as FALSE, which is not the behaviour that I want.

While Loop
The while() statement is used to execute a piece of code repeatedly as long as the while expresssion evaluates as true. For example the code below will print the number one to nine. Example : while.php Source code : while.phps <?php $number = 1; while ($number < 10) { echo $number . '<br>'; $number += 1; } ?> You see that I make the code $number += 1; as bold. I did it simply to remind that even an experienced programmer can sometime forget that a loop will happily continue to run forever as long as the loop expression ( in this case $number < 10 ) evaluates as true. So when you're creating a loop please make sure you already put the code to make sure the loop will end in timely manner.

Break
The break statement is used to stop the execution of a loop. As an example the while loop below will stop when $number equals to 6. Example : break.php Source code : break.phps <?php $number = 1; while ($number < 10) {

echo $number . '<br>'; if ($number == 6) { break; } $number += 1; } ?> You can stop the loop using the break statement. The break statement however will only stop the loop where it is declared. So if you have a cascading while loop and you put a break statement in the inner loop then only the inner loop execution that will be stopped. Example : break2.php Source code : break2.phps <?php $floor = 1; while ($floor <= 5) { $room = 1; while ($room < 40) { echo "Floor : $floor, room number : $floor". "$room <br>"; if ($room == 2) { break; } $room += 1; } $floor += 1; echo "<br>"; } ?> If you run the example you will see that the outer loop, while ($floor <= 5), is executed five times and the inner loop only executed two times for each execution of the outer loop. This proof that the break statement only stop the execution of the inner loop where it's declared.

For
The for loop syntax in PHP is similar to C. For example to print 1 to 10 the for loop is like this <?php for ($i = 1; $i <= 10; $i++) { echo $i . '<br>'; } ?> A more interesting function is to print this number in a table with alternating colors. Here is the code Example : alternate-colors.php Source : alternate-colors.phps <table width="200" border="0" cellspacing="1" cellpadding="2"> <tr> <td bgcolor="#CCCCFF">Alternating row colors</td> </tr> <?php for ($i = 1; $i <= 10; $i++) { if ($i % 2) { $color = '#FFFFCC'; } else { $color = '#CCCCCC'; } ?> <tr> <td bgcolor="<?php echo $color; ?>"><?php echo $i; ?></td> </tr> <?php } ?> </table> This code display different row colors depending on the value of $i. If $i is not divisible by two it prints yellow otherwise it prints gray colored rows.

Using Functions
Real world applications are usually much larger than the examples above. In has been proven that the best way to develop and maintain a large program is to construct it from smaller pieces (functions) each of which is more manageable than the original program. A function may be defined using syntax such as the following: <?php function addition($val1, $val2) { $sum = $val1 + $val2; return $sum; } ?>

Using Default Parameters


When calling a function you usually provide the same number of argument as in the declaration. Like in the function above you usually call it like this : $result = addition(5, 10); But you can actually call a function without providing all the arguments by using default parameters. Example : default-param.php Source code : default-param.phps <?php function repeat($text, $num = 10) { echo "<ol>\r\n"; for($i = 0; $i < $num; $i++) { echo "<li>$text </li>\r\n"; } echo "</ol>"; } // calling repeat with two arguments repeat("I'm the best", 15); // calling repeat with just one argument repeat("You're the man"); ?> Function repeat() have two arguments $text and $num. The $num argument has a default value of 10. The first call to repeat() will print the text 15 times because the value of $num will be 15. But in the second call to repeat() the second parameter is omitted so repeat() will use the default $num value of 10 and so the text is printed ten times.

Returning Values
Applications are usually a sequence of functions. The result from one function is then passed to another function for processing and so on. Returning a value from a function is done by using the return statement. Example : return.php Source code : return.phps <?php $myarray = array('php tutorial', 'mysql tutorial', 'apache tutorial', 'java tutorial', 'xml tutorial'); $rows = buildRows($myarray); $table = buildTable($rows); echo $table; function buildRows($array) { $rows = '<tr><td>' . implode('</td></tr><tr><td>', $array) . '</td></tr>'; return $rows;

} function buildTable($rows) { $table = "<table cellpadding='1' cellspacing='1' border='1'>$rows</table>"; return $table; } ?> You can return any type from a function. An integer, double, array, object, resource, etc. Notice that in buildRows() I use the built in function implode(). It joins all elements of $array with the string '</td></tr><tr><td>' between each element. I also use the '.' (dot) operator to concat the strings. You can also write buildRows() function like this. <?php ... function buildRows($array) { $rows = '<tr><td>'; $n = count($array); for($i = 0; $i < $n - 1; $i++) { $rows .= $array[$i] . '</td></tr><tr><td>'; } $rows .= $array[$n - 1] . '</td></tr>'; return $rows; } ... ?> Of course it is more convenient if you just use implode().

bgcolor='#FFCC00'

Using Form
Using forms in a web based application is very common. Most forms are used to gather information like in a signup form, survey / polling, guestbook, etc. A form can have the method set as post or get. When using a form with method="post" you can use $_POST to access the form values. And when the form is using method="get" you can use $_GET to access the values. The $_REQUEST superglobal can be used to to access form values with method="post" and method="get" but it is recommended to use $_POST or $_GET instead so you will know from what method did the values come from. Here is an example of HTML form : Example : form.php Source code : form.phps <form method="post" action="<?php echo $_SERVER['PHP_SELF'];?>"> Name : <input name="username" type="text"><br> Password : <input name="password" type="password"><br> <input name="send" type="submit" value="Send!"> </form> The form above use $_SERVER['PHP_SELF'] as the action value. It is not required for the form to perform correctly but it's considered good programming practice to use it. Below is the PHP code used to access form values : Example : form.php Source code : form.phps <?php if(isset($_POST['send'])) { echo "Accessing username using POST : " . echo "Accessing username using REQUEST : " . "<br>"; $password = $_POST['password']; echo "Password is $password"; }

$_POST['username'] . "<br>"; $_REQUEST['username'] .

?> The if statement is used to check if the send variable is set. If it is set then the form must have been submitted. The script then print the value of username using $_POST and $_REQUEST

Using Array As Form Values


Take a look at the code example below. The form have five input with the same name, language[]. Using the same input name is common for checkboxes or radio buttons. Example : form-array.php Source code : form-array.phps <form method="post" action="<?php echo $_SERVER['PHP_SELF'];?>"> Select the programming languages you can use<br> <input name="language[]" type="checkbox" value="C++"> C++<br> <input name="language[]" type="checkbox" value="Java"> Java<br> <input name="language[]" type="checkbox" value="PHP"> PHP<br> <input name="language[]" type="checkbox" value="ASP"> ASP<br> <input name="language[]" type="checkbox" value="Delphi"> Delphi<br> <input name="send" type="submit" id="send" value="Send!"> </form> The PHP code below print the value of language after the form is submitted. Go ahead and try the example. Try checking and unchecking the options to see the effect. Example : form-array.php Source code : form-array.phps <?php if(isset($_POST['language'])) { $language = $_POST['language']; $n = count($language); $i = 0; echo "The languages you selected are \r\n" . "<ol>"; while ($i < $n) { echo "<li>{$language[$i]}</li> \r\n"; $i++; } echo "</ol>"; } ?> From the above code you will notice that $language is an array. This is because in the form code language is repeated several times. When you specify the same input name in a form, PHP will treat it as an array. There is one important issue you should know when using forms, that is form validation. The code above does not check whether the input is correct such as checking if the user is entering any value into the textbox or is the user selecting any checkboxes. This is actually a bad practice because you should never put a web form without any validation. To learn about validating form input you can go to this page : Form Validation With PHP

That's it. You just completed the basics of PHP programming. When you're ready for more advance programming in PHP check out the book store. You can find plenty good books in there with deeper coverage on PHP programming As is said earlier if you haven't got the PHP manual you should download it now from php.net. You can find lots of interesting (and important) stuff in it. You can get the manual in various format but I recommend that you get the compiled HTML (CHM) version. It's easier to read plus it has search capability and loads of user contributed notes, very useful. Now let's move on the next section : MySQL Tutorial

3. MySQL Tutorial
This mysql tutorial covers the basic stuff that you can do with MySQL. But before we go any further make sure you already have MySQL installed, if you don't have it installed check out this page : Installing PHP and MySQL (plus Apache). This part of tutorial covers the following :

Starting MySQL
How to start mysql server and client from the command line ( DOS )

Adding New Users To MySQL


How to add new MySQL users. Skip this part if you don't intend to add more user other that the default

Create a new database Create tables Insert data Get the data Update & Delete data
Starting MySQL
Before starting the MySQL client make sure the server is turned on. If you run Windows 9x start the mysqld.exe or if you run Windows 2000/XP run the mysqld-nt.exe

The --console option tells mysqld-nt not to remove the console window. if you are running Windows NT/2000/XP it's better to install MySQL as a service. That way mysql server will be automatically started when you start Windows. Use mysqld-nt --install to install mysqld as a service and mysqld-nt --remove to remove mysqld from the service list Once the server is on, open another DOS window and type mysql, you should see something like this C:\>mysql Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 3 to server version: 4.0.18-nt Type 'help;' or '\h' for help. Type '\c' to clear the buffer. mysql> Or if you need to specify a user name and password you can start mysql like this : C:\>mysql -h localhost -u root -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 4 to server version: 4.0.18-nt Type 'help;' or '\h' for help. Type '\c' to clear the buffer. mysql> You can also specify the database you want to use. If you already have a database named petstore you can start mysql as C:\>mysql petstore Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 5 to server version: 4.0.18-nt Type 'help;' or '\h' for help. Type '\c' to clear the buffer. mysql> Once you're done you can end mysql client by typing quit or exit at the mysql> prompt mysql> exit Bye

C:\> Note : If you get this kind of error message when trying to run mysql from the DOS window C:\>mysql 'mysql' is not recognized as an internal or external command, operable program or batch file. that means you haven't set the path to mysql bin directory. To solve the problem you can add the following line to your autoexec.bat file : path=%path%;c:\mysql\bin assuming that you install MySQL in c:\mysql. Or if you're using Windows XP you can go to : Start->Settings->Control Panel->System->Advanced->Environment Variables Chose Edit on the System Variables section and add c:\mysql\bin to the path environment variable.

Add New MySQL User


For adding a new user to MySQL you just need to add a new entry to user table in database mysql. Below is an example of adding new user phpcake with SELECT, INSERT and UPDATE privileges with the password mypass the SQL query is : mysql> use mysql; Database changed mysql> INSERT INTO user (host, user, password, select_priv, insert_priv, update_priv) VALUES ('localhost', 'phpcake', PASSWORD('mypass'), 'Y', 'Y', 'Y'); Query OK, 1 row affected (0.20 sec) mysql> FLUSH PRIVILEGES; Query OK, 1 row affected (0.01 sec) mysql> SELECT host, user, password FROM user WHERE user = 'phpcake'; +-----------+---------+------------------+ | host | user | password | +-----------+---------+------------------+ | localhost | phpcake | 6f8c114b58f2ce9e | +-----------+---------+------------------+ 1 row in set (0.00 sec)

When adding a new user remember to encrypt the new password using PASSWORD() function provided by MySQL. As you can see in the above example the password mypass is encrypted to 6f8c114b58f2ce9e. Notice the the FLUSH PRIVILEGES statement. This tells the server to reload the grant tables. If you don't use it then you won't be able to connect to mysql using the new user account (at least until the server is reloaded). You can also specify other privileges to a new user by setting the values of these columns in user table to 'Y' when executing the INSERT query :

Select_priv Insert_priv Update_priv Delete_priv Create_priv Drop_priv Reload_priv Shutdown_priv Process_priv File_priv Grant_priv References_priv Index_priv Alter_priv
I think you can guess what those privileges serve by reading it's name

Create New MySQL Database


You need to use mysqladmin to create MySQL database. The command is simple just write mysqladmin in a dos window followed by the database name you want to create

C:\>mysqladmin create petstore C:\>mysql Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 3 to server version: 4.0.18-nt Type 'help;' or '\h' for help. Type '\c' to clear the buffer. mysql> SHOW databases; +----------+ | Database | +----------+ | mysql | | petstore | | test | +----------+ 2 rows in set (0.00 sec) mysql> You can also type the query in mysql> prompt like this mysql> CREATE database petstore; Query OK, 1 row affected (0.00 sec) To show available databases in mysql use the command show databases on mysql> prompt. Now use the database by typing USE petstore and then type SHOW tables to see what tables are available in the database mysql> USE petstore; Database changed mysql> SHOW tables; Empty set (0.00 sec) Next I will show you how to create table in mysql database

Create New Table

For this example we'll create two tables. The first one describe the species of animals available in a pet store and the second will store the data of a pet in the store. The table names will be species and pet
The species table will consist of the id and the animal species, and the pet table will consist of animal id, species, sex, and price mysql> CREATE TABLE species (id INT NOT NULL AUTO_INCREMENT, species varchar(30) NOT NULL, primary key(id)); Query OK, 0 rows affected (0.02 sec) mysql> CREATE TABLE pet(id INT NOT NULL AUTO_INCREMENT, sp_id INT NOT NULL, sex CHAR(1) NOT NULL, price DECIMAL(4,2) NOT NULL, primary key(id)); Query OK, 0 rows affected (0.03 sec) mysql> SHOW tables; +--------------------+ | Tables_in_petstore | +--------------------+ | pet | | species | +--------------------+ 2 rows in set (0.00 sec) mysql> DESCRIBE species; +---------+-------------+----+-----+---------+---------------+ | Field | Type |Null| Key | Default | Extra | +---------+-------------+----+-----+---------+---------------+ | id | int(11) | | PRI | NULL | auto_increment| | name | varchar(30) | | | | | +---------+-------------+----+-----+---------+---------------+ 2 rows in set (0.05 sec) mysql> DESC pet; +-------+--------------+----+-----+---------+----------------+ | Field | Type |Null| Key | Default | Extra | +-------+--------------+----+-----+---------+----------------+ | id | int(11) | | PRI | NULL | auto_increment | | sp_id | int(11) | | | 0 | | | sex | char(1) | | | | | | price | decimal(4,2) | | | 0.00 | | +-------+--------------+----+-----+---------+----------------+ 4 rows in set (0.00 sec) The SQL sytax to create table is : CREATE TABLE <tablename> (<list of fields>)

The DESCRIBE or DESC statement is used to show a description of a table. You can also use EXPLAIN or SHOW COLUMNS mysql> EXPLAIN pet; +-------+--------------+----+-----+---------+----------------+ | Field | Type |Null| Key | Default | Extra | +-------+--------------+----+-----+---------+----------------+ | id | int(11) | | PRI | NULL | auto_increment | | sp_id | int(11) | | | 0 | | | sex | char(1) | | | | | | price | decimal(4,2) | | | 0.00 | | +-------+--------------+----+-----+---------+----------------+ 4 rows in set (0.00 sec) mysql> SHOW COLUMNS FROM pet; +-------+--------------+----+-----+---------+----------------+ | Field | Type |Null| Key | Default | Extra | +-------+--------------+----+-----+---------+----------------+ | id | int(11) | | PRI | NULL | auto_increment | | sp_id | int(11) | | | 0 | | | sex | char(1) | | | | | | price | decimal(4,2) | | | 0.00 | | +-------+--------------+----+-----+---------+----------------+ 4 rows in set (0.00 sec)

Insert Data To MySQL


You can insert data to the tables directly from mysql> prompt or by loading a file containing the data. The values will be tab separated one line represent one record. To insert the data directly from mysql use the INSERT statement. The format for INSERT is INSERT INTO <table name> (column1, column2, ....) values ( 'value1', 'value2', ...). For example to insert a species name into the table species the command is like this : mysql> INSERT INTO species (name) values ('Cat'); Query OK, 1 row affected (0.00 sec) Remember to put single quotes around a value it is a string. Notice that i don't have to set the value of id because id have AUTO_INCREMENT attribute. Whenever you insert a new record to the table the value of id is set automatically by mysql with increasing values. The AUTO_INCREMENT attribute is commonly used to create unique identity for new rows Next example will show how to insert data from a text file, you can get the file here and try in on your computer. mysql> LOAD DATA LOCAL INFILE "insert.txt" INTO TABLE species; Query OK, 3 rows affected (0.00 sec) You can also run SQL queries directly from the DOS prompt. Assuming insert.txt is in C:\ you can run the query in insert.txt like this C:\mysql < insert.txt

Get Data From MySQL


Retrieving the table data is easy, just use the SELECT statement like this mysql> SELECT * FROM species; +----+--------+ | id | name | +----+--------+ | 1 | Cat | | 2 | Bird | | 3 | Fish | | 4 | Turtle | +----+--------+ 4 rows in set (0.00 sec) The * from the SELECT statement means select all columns. If you only want the names you can write mysql> SELECT name FROM species; +--------+ | name | +--------+ | Cat | | Bird | | Fish | | Turtle | +--------+ 4 rows in set (0.00 sec)

To select only the records that interest you, you can use WHERE statement followed by the definition. For example to select a record from species table where id equals 4 you can do this : mysql> SELECT * FROM species WHERE id = 4; +----+--------+ | id | name | +----+--------+ | 4 | Turtle | +----+--------+ 1 row in set (0.59 sec) If you want to order the returned rows by a criteria you can use ORDER BY like this : mysql> SELECT * FROM species ORDER BY name; +----+--------+ | id | name | +----+--------+ | 2 | Bird | | 1 | Cat | | 3 | Fish | | 4 | Turtle | +----+--------+ 4 rows in set (0.00 sec) By default the result is sorted in ascending order. So this query will give the same result : mysql> SELECT * FROM species ORDER BY name ASC; +----+--------+ | id | name | +----+--------+ | 2 | Bird | | 1 | Cat | | 3 | Fish | | 4 | Turtle | +----+--------+ 4 rows in set (0.00 sec) The ASC means ascending order. To get a descending order you just change the ASC with DESC like this : mysql> SELECT * FROM species ORDER BY name DESC; +----+--------+ | id | name | +----+--------+ | 4 | Turtle | | 3 | Fish | | 1 | Cat | | 2 | Bird | +----+--------+ 4 rows in set (0.00 sec)

MySQL Update And Delete


The statement UPDATE is used to change the value in a table. For example to change to species name from Turtle to Dog mysql> UPDATE species SET name = 'Dog' WHERE name = 'Turtle'; Query OK, 1 row affected (0.02 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> SELECT * FROM species; +----+-------+ | id | name | +----+-------+ | 1 | Cat | | 2 | Bird | | 3 | Fish | | 4 | Dog | +----+-------+ 4 rows in set (0.00 sec)

Delete Data
The DELETE statement deletes rows from a table that satisfy the condition given by the WHERE clause. For example to delete a record from species table where id equals 1 mysql> DELETE FROM species WHERE id = 1; Query OK, 1 row affected (0.00 sec) mysql> SELECT * FROM species; +----+-------+ | id | name | +----+-------+ | 2 | Bird | | 3 | Fish | | 4 | Dog |

+----+-------+ 3 rows in set (0.01 sec)

Now that you alredy know the basics of MySQL it's time to continue the tutorial. You will start learning how to connect to MySQL database using PHP. By the way this is a good time for you to download download the MySQL reference manual ( if you haven't done so). It covers in detail about everything you need to know about MySQL. As with the PHP manual you should download the compiled HTML version of MySQL manual because it's easier to browse than other formats.

4. Connect to MySQL Database


Opening a connection to MySQL database from PHP is easy. Just use the mysql_connect() function like this <?php $dbhost = 'localhost'; $dbuser = 'root'; $dbpass = 'password'; $conn = mysql_connect($dbhost, $dbuser, $dbpass) or die ('Error connecting to mysql'); $dbname = 'petstore'; mysql_select_db($dbname); ?> $dbhost is the name of MySQL server. When your webserver is on the same machine with the MySQL server you can use localhost or 127.0.0.1 as the value of $dbhost. The $dbuser and $dbpass are valid MySQL user name and password. For adding a user to MySQL visit this page : MySQL Tutorial Don't forget to select a database using mysql_select_db() after connecting to mysql. If no database selected your query to select or update a table will not work.

Sometimes a web host will require you to specify the MySQL server name and port number. For example if the MySQL server name is db.php-mysql-tutorial.com and the port number is 3306 (the default port number for MySQL) then you you can modify the above code to : <?php $dbhost = 'db.php-mysql-tutorial.com:3306'; $dbuser = 'root'; $dbpass = 'password'; $conn = mysql_connect($dbhost, $dbuser, $dbpass) or die ('Error connecting to mysql'); $dbname = 'petstore'; mysql_select_db($dbname); ?> It's a common practice to place the routine of opening a database connection in a separate file. Then everytime you want to open a connection just include the file. Usually the host, user, password and database name are also separated in a configuration file. An example of config.php that stores the connection configuration and opendb.php that opens the connection are : Source code : config.phps , opendb.phps <?php // This $dbhost $dbuser $dbpass $dbname ?>

is an example of config.php = 'localhost'; = 'root'; = 'password'; = 'phpcake';

<?php // This is an example opendb.php $conn = mysql_connect($dbhost, $dbuser, $dbpass) or die ('Error connecting to mysql'); mysql_select_db($dbname); ?> So now you can open a connection to mysql like this : <?php include 'config.php'; include 'opendb.php'; // ... do something like insert or select, etc ?>

Closing the Connection


The connection opened in a script will be closed as soon as the execution of the script ends. But it's better if you close it explicitly by calling mysql_close() function. You could also put this function call in a file named closedb.php. Source code : closedb.phps <?php // an example of closedb.php // it does nothing but closing // a mysql database connection mysql_close($conn); ?> Now that you have put the database configuration, opening and closing routines in separate files your PHP script that uses mysql would look something like this : <?php include 'config.php'; include 'opendb.php'; // ... do something like insert or select, etc include 'closedb.php'; ?>

5. Create MySQL Database With PHP


To create a database use the mysql_query() function to execute an SQL query like this <?php include 'config.php'; include 'opendb.php'; $query = "CREATE DATABASE phpcake"; $result = mysql_query($query); include 'closedb.php'; ?> Please note that the query should not end with a semicolon. PHP also provide a function to create MySQL database, mysql_create_db(). This function is deprecated though. It is better to use mysql_query() to execute an SQL CREATE DATABASE statement instead like the above example. If you want to create MySQL database using PHP mysql_create_db() function you can do it like this : <?php include 'config.php'; include 'opendb.php'; mysql_create_db('phpcake'); include 'closedb.php'; ?> If you want to create tables in the database you just created don't forget to call mysql_select_db() to access the new database. Note: some webhosts require you to create a MySQL database and user through your website control panel (such as CPanel). If you get an error when trying to create database this might be the case.

Creating the Tables


To create tables in the new database you need to do the same thing as creating the database. First create the SQL query to create the tables then execute the query using mysql_query() function. Example : contact.php Source code : contact.phps <?php include 'config.php'; include 'opendb.php'; $query = 'CREATE DATABASE phpcake'; $result = mysql_query($query); mysql_select_db('phpcake') or die('Cannot select database'); $query = 'CREATE TABLE contact( '. 'cid INT NOT NULL AUTO_INCREMENT, '. 'cname VARCHAR(20) NOT NULL, '. 'cemail VARCHAR(50) NOT NULL, '. 'csubject VARCHAR(30) NOT NULL, '. 'cmessage TEXT NOT NULL, '. 'PRIMARY KEY(cid))'; $result = mysql_query($query); include 'closedb.php'; ?> Of course when you need to create lots of tables it's a good idea to read the query from a file then save in $query variable instead of writing the query in your script. <?php include 'config.php'; include 'opendb.php'; $queryFile = 'myquery.txt'; $fp = fopen($queryFile, 'r'); $query = fread($fp, filesize($queryFile)); fclose($fp); $result = mysql_query($query); include 'closedb.php';

?>

Deleting a Database
As with creating a database, it is also preferable to use mysql_query() and to execute the SQL DROP DATABASE statement instead of using mysql_drop_db() <?php include 'config.php'; include 'opendb.php'; // ... do something here $query = 'DROP DATABASE phpcake'; $result = mysql_query($query); // ... probably do something here too include 'closedb.php'; ?> After the database and the tables are ready it's time to put something into the database.

6. Insert Data To MySQL Database


Inserting data to MySQL is done by using mysql_query() to execute INSERT query. Note that the query string should not end with a semicolon. Below is an example of adding a new MySQL user by inserting a new row into table user in database mysql : Example : insert.php Source code : insert.phps <?php include 'library/config.php'; include 'library/opendb.php'; mysql_select_db($mysql); $query = "INSERT INTO user (host, user, password, select_priv, insert_priv, update_ priv) VALUES ('localhost', 'phpcake', PASSWORD('mypass'), 'Y', 'Y', 'Y')"; mysql_query($query) or die('Error, insert query failed'); $query = "FLUSH PRIVILEGES"; mysql_query($query) or die('Error, insert query failed'); include 'library/closedb.php'; ?> In the above example mysql_query() was followed by die(). If the query fail the error message will be printed and the script's execution is terminated. Actually you can use die() with any function that might not execute properly. That way you can be sure that the script won't continue to run when an error occured. In a real application the values of an INSERT statement will be form values. As a safe precaution always escape the values using addslashes() if get_magic_quotes_gpc() returns false. Below is an example of using form values with INSERT. It's the same as above except that the new username and password are taken from $_POST : Example : adduser.php Source code : adduser.phps <html> <head> <title>Add New MySQL User</title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> </head> <body> <?php if(isset($_POST['add'])) { include 'library/config.php'; include 'library/opendb.php'; $username = $_POST['username']; $password = $_POST['password']; $query = "INSERT INTO user (host, user, password, select_priv, insert_priv, update_ priv) VALUES ('localhost', '$username', PASSWORD('$password'), 'Y', 'Y', 'Y')"; mysql_query($query) or die('Error, insert query failed'); $query = "FLUSH PRIVILEGES"; mysql_query($query) or die('Error, insert query failed'); include 'library/closedb.php'; echo "New MySQL user added"; } else { ?> <form method="post"> <table width="400" border="0" cellspacing="1" cellpadding="2"> <tr> <td width="100">Username</td> <td><input name="username" type="text" id="username"></td> </tr> <tr> <td width="100">Password</td> <td><input name="password" type="text" id="password"></td> </tr> <tr> <td width="100">&nbsp;</td> <td>&nbsp;</td> </tr> <tr> <td width="100">&nbsp;</td> <td><input name="add" type="submit" id="add" value="Add New User"></td> </tr> </table> </form> <?php

} ?> </body> </html>

7. Get Data From MySQL Database


Using PHP you can run a MySQL SELECT query to fetch the data out of the database. You have several options in fetching information from MySQL. PHP provide several functions for this. The first one is mysql_fetch_array()which fetch a result row as an associative array, a numeric array, or both. Below is an example of fetching data from MySQL, the table contact have three columns, name, subject and message. Example : select.php Source code : select.phps, contact.txt <?php include 'config.php'; include 'opendb.php'; $query = "SELECT name, subject, message FROM contact"; $result = mysql_query($query); while($row = mysql_fetch_array($result, MYSQL_ASSOC)) { echo "Name :{$row['name']} <br>" . "Subject : {$row['subject']} <br>" . "Message : {$row['message']} <br><br>"; } include 'closedb.php'; ?> The while() loop will keep fetching new rows until mysql_fetch_array() returns FALSE, which means there are no more rows to fetch. The content of the rows are assigned to the variable $row and the values in row are then printed. Always remember to put curly brackets when you want to insert an array value directly into a string. In above example I use the constant MYSQL_ASSOC as the second argument to mysql_fetch_array(), so that it returns the row as an associative array. With an associative array you can access the field by using their name instead of using the index . Personally I think it's more informative to use $row['subject'] instead of $row[1]. PHP also provide a function called mysql_fetch_assoc() which also return the row as an associative array. <?php include 'config.php'; include 'opendb.php'; $query = "SELECT name, subject, message FROM contact"; $result = mysql_query($query); while($row = mysql_fetch_assoc($result)) { echo "Name :{$row['name']} <br>" . "Subject : {$row['subject']} <br>" . "Message : {$row['message']} <br><br>"; } include 'closedb.php'; ?> You can also use the constant MYSQL_NUM, as the second argument to mysql_fetch_array(). This will cause the function to return an array with numeric index. <?php include 'config.php'; include 'opendb.php'; $query = "SELECT name, subject, message FROM contact"; $result = mysql_query($query); while($row = mysql_fetch_array($result, MYSQL_NUM)) { echo "Name :{$row[0]} <br>" . "Subject : {$row[0]} <br>" . "Message : {$row[0]} <br><br>"; } include 'closedb.php'; ?> Using the constant MYSQL_NUM with mysql_fetch_array() gives the same result as the function mysql_fetch_row().

There is another method for you to get the values from a row. You can use list(), to assign a list of variables in one operation. <?php include 'config.php'; include 'opendb.php'; $query = "SELECT name, subject, message FROM contact"; $result = mysql_query($query); while(list($name,$subject,$message)= mysql_fetch_row($result)) { echo "Name :$name <br>" . "Subject : $subject <br>" . "Message : $row <br><br>"; } include 'closedb.php'; ?> In above example, list() assign the values in the array returned by mysql_fetch_row() into the variable $name, $subject and $message. Of course you can also do it like this <?php include 'config.php'; include 'opendb.php'; $query = "SELECT name, subject, message FROM contact"; $result = mysql_query($query); while($row = { $name $subject $message mysql_fetch_row($result)) = $row[0]; = $row[1]; = $row[2];

echo "Name :$name <br>" . "Subject : $subject <br>" . "Message : $row <br><br>"; } include 'closedb.php'; ?> So you see you have lots of choices in fetching information from a database. Just choose the one appropriate for your program

Freeing the memory ?


In some cases a query can return large result sets. As this results are stored in memory there's a concern about memory usage. However you do not need to worry that you will have to call this function in all your script to prevent memory congestion. In PHP all results memory is automatically freed at the end of the script's execution. But you are really concerned about how much memory is being used for queries that return large result sets you can use mysql_free_result(). Calling this function will free all memory associated with the result identifier ( $result ). Using the above example you can call mysql_free_result() like this : <?php include 'config.php'; include 'opendb.php'; $query = "SELECT name, subject, message FROM contact"; $result = mysql_query($query); while($row = mysql_fetch_row($result)) { ... } mysql_free_result($result); include 'closedb.php'; ?>

Convert MySQL Query Result To Excel

Using PHP to convert MySQL query result to Excel format is also common especially in web based finance applications. The finance data stored in database are downloaded as Excel file for easy viewing. There is no special functions in PHP to do the job. But you can do it easily by formatting the query result as tab separated values or put the value in an HTML table. After that set the content type to application/vnd.ms-excel Example : convert.php Source : convert.php, students.txt <?php include 'library/config.php'; include 'library/opendb.php'; $query = "SELECT fname, lname FROM students"; $result = mysql_query($query) or die('Error, query failed'); $tsv = array(); $html = array(); while($row = mysql_fetch_array($result, MYSQL_NUM)) { $tsv[] = implode("\t", $row); $html[] = "<tr><td>" .implode("</td><td>", $row) . } $tsv = implode("\r\n", $tsv); $html = "<table>" . implode("\r\n", $html) . "</table>"; $fileName = 'mysql-to-excel.xls'; header("Content-type: application/vnd.ms-excel"); header("Content-Disposition: attachment; filename=$fileName"); echo $tsv; //echo $html; include 'library/closedb.php'; ?> In the above example $tsv is a string containing tab separated values and $html contain an HTML table. I use implode() to join the values of $row with tab to create a tab separated string. After the while loop implode() is used once again to join the rows using newline characters. The headers are set and the value of $tsv is then printed. This will force the browser to save the file as mysql-toexcel.xsl Try running the script in your own computer then try commenting echo $tsv and uncomment echo $html to see the difference.

"</td></tr>";

Next, how to split your query result to multiple pages by applying paging.

8. Using Paging
Ever use a Search Engine? I'm sure you have, lots of time. When Search Engines found thousands of results for a keyword do they spit out all the result in one page? Nope, they use paging to show the result little by little. Paging means showing your query result in multiple pages instead of just put them all in one long page. Imagine waiting for five minutes just to load a search page that shows 1000 result. By splitting the result in multiple pages you can save download time plus you don't have much scrolling to do. To show the result of a query in several pages first you need to know how many rows you have and how many rows per page you want to show. For example if I have 295 rows and I show 30 rows per page that mean I'll have ten pages (rounded up).

For the example I created a table named randoms that store 295 random numbers. Each page shows 20 numbers. Example: paging.php Source code :paging.phps <?php include 'library/config.php'; include 'library/opendb.php'; // how many rows to show per page $rowsPerPage = 20; // by default we show first page $pageNum = 1; // if $_GET['page'] defined, use it as page number if(isset($_GET['page'])) { $pageNum = $_GET['page']; } // counting the offset $offset = ($pageNum - 1) * $rowsPerPage; $query = " SELECT val FROM randoms " . " LIMIT $offset, $rowsPerPage"; $result = mysql_query($query) or die('Error, query failed'); // print the random numbers while($row = mysql_fetch_array($result)) { echo $row['val'] . '<br>'; } // ... more code here ?> Paging is implemented in MySQL using LIMIT that take two arguments. The first argument specifies the offset of the first row to return, the second specifies the maximum number of rows to return. The offset of the first row is 0 ( not 1 ). When paging.php is called for the first time the value of $_GET['page'] is not set. This caused $pageNum value to remain 1 and the query is : SELECT val FROM randoms LIMIT 0, 20 which returns the first 20 values from the table. But when paging.php is called like this http://www.phpmysql-tutorial.com/examples/paging/paging.php?page=4 the value of $pageNum becomes 4 and the query will be : SELECT val FROM randoms LIMIT 60, 20 this query returns rows 60 to 79. After showing the values we need to print the links to show any pages we like. But first we have to count the number of pages. This is achieved by dividing the number of total rows by the number of rows to show per page : $maxPage = ceil($numrows/$rowsPerPage); <?php // ... the previous code // how many rows we have in database

$query $result $row $numrows

= = = =

"SELECT COUNT(val) AS numrows FROM randoms"; mysql_query($query) or die('Error, query failed'); mysql_fetch_array($result, MYSQL_ASSOC); $row['numrows'];

// how many pages we have when using paging? $maxPage = ceil($numrows/$rowsPerPage); // print the link to access each page $self = $_SERVER['PHP_SELF']; $nav = ''; for($page = 1; $page <= $maxPage; $page++) { if ($page == $pageNum) { $nav .= " $page "; // no need to create a link to current page } else { $nav .= " <a href=\"$self?page=$page\">$page</a> "; } } // ... still more code coming ?> The mathematical function ceil() is used to round up the value of $numrows/$rowsPerPage. In this case the value of total rows $numrows is 295 and $rowsPerPage is 20 so the result of the division is 14.75 and by using ceil() we get $maxPage = 15 Now that we know how many pages we have we make a loop to print the link. Each link will look something like this: <a href="paging.php?page=5">5</a> You see that we use $_SERVER['PHP_SELF'] instead of paging.php when creating the link to point to the paging file. This is done to avoid the trouble of modifying the code in case we want to change the filename.

We are almost complete. Just need to add a little code to create a 'Previous' and 'Next' link. With these links we can navigate to the previous and next page easily. And while we at it let's also create a 'First page' and 'Last page' link so we can jump straight to the first and last page when we want to.

<?php // ... the previous code // creating previous and next link // plus the link to go straight to // the first and last page if ($pageNum > 1) { $page = $pageNum - 1; $prev = " <a href=\"$self?page=$page\">[Prev]</a> "; $first = " <a href=\"$self?page=1\">[First Page]</a> "; } else { $prev = '&nbsp;'; // we're on page one, don't print previous link $first = '&nbsp;'; // nor the first page link } if ($pageNum < $maxPage) { $page = $pageNum + 1; $next = " <a href=\"$self?page=$page\">[Next]</a> "; $last = " <a href=\"$self?page=$maxPage\">[Last Page]</a> "; } else { $next = '&nbsp;'; // we're on the last page, don't print next link $last = '&nbsp;'; // nor the last page link } // print the navigation link echo $first . $prev . $nav . $next . $last; // and close the database connection

include '../library/closedb.php'; // ... and we're done! ?> Making these navigation link is actually easier than you may think. When we're on the fifth page we just make the 'Previous' link point to the fourth. The same principle also apply for the 'Next' link, we just need to add one to the page number. One thing to remember is that we don't need to print the 'Previous' and 'First Page' link when we're already on the first page. Same thing for the 'Next' and 'Last' link. If we do print them that would only confuse the one who click on it. Because we'll be giving them the exact same page.

We got a problem here...


Take a look at this slightly modified version of paging.php. Instead of showing 20 numbers in a page, I decided to show just three. See the problem already? Those page numbers are running across the screen! Yuck! This call for a little modification to the code. Instead of printing the link to each and every page we will just saying something like "Viewing page 4 of 99 pages". Than means we havel remove these code : <?php // ... the previous code $nav = '';

for($page = 1; $page <= $maxPage; $page++) { if ($page == $pageNum) { $nav .= " $page "; // no need to create a link to current page } else { $nav .= " <a href=\"$self?page=$page\">$page</a> "; } } // ... the rest here ?> And then modify this one <?php // ... // print the navigation link echo $first . $prev . $nav . $next . $last; // ... ?>

Into this <?php // ... // print the navigation link echo $first . $prev . " Showing page $pageNum of $maxPage pages " . $next . $last; // ... ?> Take a look at the at the result here, and you can get the source code here

Using Paging ( Part 2 )


When there's more than one column involved in paging there isn't much that we need to modify. We only need to decide how to count the total number of rows we have in the table. Consider the student table. This table have five columns as shown in the SQL below. Source : examples/source/student.sql CREATE TABLE student( id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(30) NOT NULL, address VARCHAR(50) NOT NULL, age TINYINT UNSIGNED NOT NULL, register_date DATE NOT NULL, PRIMARY KEY (id) );

In the select query we just select all the columns. You can also use SELECT * instead of mentioning all the column names ( SELECT id, name, address, age, register_date ). But personally i prefer writing the column names in the query so that by reading the code i know what the column names in a table without having to check the database. Example: paging4.php Source code :paging4.phps <?php include 'library/config.php'; include 'library/opendb.php'; // how many rows to show per page $rowsPerPage = 3; // by default we show first page $pageNum = 1; // if $_GET['page'] defined, use it as page number if(isset($_GET['page'])) { $pageNum = $_GET['page']; } // counting the offset $offset = ($pageNum - 1) * $rowsPerPage; $query = "SELECT id, name, address, age, register_date FROM student LIMIT $offset, $rowsPerPage"; $result = mysql_query($query) or die('Error, query failed'); // print the student info in table echo '<table border="1"><tr><td>Student Id</td><td>Name</td><td>Address</td><td>Age</td><td>Register Date</td></tr>'; while(list($id, $name, $address, $age, $regdate) = mysql_fetch_array($result)) { echo "<tr><td>$id</td><td>$name</td><td>$address</td> <td>$age</td><td>$regdate</td></tr>"; } echo '</table>'; echo '<br>'; // ... more code here ?> In this example we print the result in table. Before looping through the array we just echo the starting table code and the header which displays the column names. Then in the loop we just print the values in a HTML table row. The next thing is finding out the total number of rows. There are several ways to do it. The first one is shown below. It's the same method used in previous examples. We just use the COUNT() function Example: paging4.php Source code :paging4.phps <?php // ... previous code here // how many rows we have in database

$query = "SELECT COUNT(id) AS numrows FROM student"; $result = mysql_query($query) or die('Error, query failed'); $row = mysql_fetch_array($result, MYSQL_ASSOC); $numrows = $row['numrows']; // ... just the same code that prints the prev & next link ?> You can also count any other columns since they all yield the same result. So your query can be rewritten into this : <?php // ... $query = "SELECT COUNT(name) AS numrows FROM student"; // ... ?> Or this : <?php // ... $query = "SELECT COUNT(age) AS numrows FROM student"; // ... ?> There is another way to count the total rows. Instead of using COUNT() function in the query you use a simple SELECT <column> and use myql_num_rows() to see how many rows returned. Take a look at the code below. We now separate the query into two parts. One is the normal SELECT query and the second is the SQL that performs the paging. After finish printing the result you can reuse the first part of the query to find the total number of rows. Example: paging5.php Source code :paging5.phps <?php // ... same old code to get the page number and counting the offset $query = "SELECT id, name, address, age, register_date FROM student "; $pagingQuery = "LIMIT $offset, $rowsPerPage"; $result = mysql_query($query . $pagingQuery) or die('Error, query failed'); // ... the code that prints the result in a table // how many rows we have in database $result = mysql_query($query) or die('Error, query failed'); $numrows = mysql_num_rows($result); // ... and here is the code that print the prev & next links ?>

There is another advantage in separating the original query with the paging query. In case you only wish to list all student whose age is older than 15. You just need to modify the original query and you don't have to worry about changing the query to find the total number of rows. The example is shown below : <?php // ... same old code to get the page number and counting the offset $query = "SELECT id, name, address, age, register_date FROM student WHERE age > 15"; $pagingQuery = "LIMIT $offset, $rowsPerPage"; $result = mysql_query($query . $pagingQuery) or die('Error, query failed'); // ... the code that prints the result in a table // how many rows we have in database $result = mysql_query($query) or die('Error, query failed'); $numrows = mysql_num_rows($result); // ... and here is the code that print the prev & next links ?> The disadvantage of this method is that the second execution of mysql_query() will retrieve all columns

from the database. This is very useless since we're not going to use them. We only interested in finding the total rows returned by that query. In the end it's really up to you to decide which method you prefer. To get all the source for this paging examples click here

9. MySQL Update and Delete


There are no special ways in PHP to perform update and delete on MySQL database. You still use mysql_query() to execute the UPDATE or DELETE statement. For instance to update a password in mysql table for username phpcake can be done by executing an UPDATE statement with mysql_query() like this: Example : update.php Source code : update.phps <?php include 'library/config.php'; include 'library/opendb.php'; mysql_select_db('mysql') or die('Error, cannot select mysql database'); $query = "UPDATE user SET password = PASSWORD('newpass')". "WHERE user = 'phpcake'"; mysql_query($query) or die('Error, query failed'); include 'library/closedb.php'; ?> There is one important thing that you should be aware of when updating and deleting rows from database. That is data integrity. If you're using InnoDB tables you can leave the work of maintaining data integrity to MySQL . However when you're using other kind of tables you need to enforce the data integrity manually. To make sure that your update and delete queries will not break the data integrity. You have to make appropriate update and delete queries for all tables referencing to the table you update or delete. For example, suppose you have two tables, Class and Student. The Student table have a foreign key column, cid which references to the class_id column in table Class. When you want to update a class_id in Class table you will also need to update the cid column in Student table to maintain data integrity. Suppose i want to change the class_id of Karate from 3 to 10. Since there is a row in Student table with cid value of 3, I have to update that row too. $query = "UPDATE Class SET class_id = 10 WHERE class_id = 3"; mysql_query($query); $query = "UPDATE Student SET cid = 10 WHERE cid = 3"; mysql_query($query); Below are the data in Table and Student class before an update query :

Table Class

class_id

class_name

Silat

2 3 4

Kungfu Karate Taekwondo

Table Student

student_id

student_name

cid

Uzumaki Naruto

Uchiha Sasuke

Haruno Sakura

Now the content of Table and Student class after the update query are :

Table Class

class_id

class_name

Silat

2 10 4

Kungfu Karate Taekwondo

Table Student

student_id

student_name

cid

Uzumaki Naruto

Uchiha Sasuke

10

Haruno Sakura

You can go as far as creating your own functions in PHP to ensure the data integrity. I have done this before and I hope you don't do it. Save yourself the headache and just write appropriate queries to maintain your data integrity whenever you update / delete rows from a table. This means that whenever you make a query to update / delete always consult your database design to see if you need to update / delete another table to maintain data integrity. Your code will be more portable like this.

Using LOCK TABLES


When your web application is used by more than one user using LOCK TABLES before any update / delete query is a safe bet. This will make sure that only one user change the table at a time. Using the above update code examples again, suppose there are two users. The first one want to update one row in Class table and the second want to delete it $query = "LOCK TABLES Class WRITE, Student WRITE"; mysql_query($query); $query = "DELETE FROM Class WHERE class_id = 3"; mysql_query($query);

$query = "DELETE FROM Student WHERE class_id = 3"; mysql_query($query); $query = "UNLOCK TABLES"; mysql_query($query); The update queries above can be rewritten as : $query = "LOCK TABLES Class WRITE, Student WRITE"; mysql_query($query); $query = "UPDATE Class SET class_id = 10 WHERE class_id = 3"; mysql_query($query); $query = "UPDATE Student SET cid = 10 WHERE cid = 3"; mysql_query($query); $query = "UNLOCK TABLES"; mysql_query($query); By issuing the LOCK TABLES all other users are blocked from reading and writing to the tables. So you're update / delete query will continue to completion without any worries that the intended table already changed by another user

10. Using PHP To Backup MySQL Database


There are at least three ways to backup your MySQL Database : 1. Execute a database backup query from PHP file. 2. Run mysqldump using system() function. 3. Use phpMyAdmin to do the backup.

Execute a database backup query from PHP file


Below is an example of using SELECT INTO OUTFILE query for creating table backup : <?php include 'config.php'; include 'opendb.php'; $tableName = 'mypet'; $backupFile = 'backup/mypet.sql'; $query = "SELECT * INTO OUTFILE '$backupFile' FROM $tableName"; $result = mysql_query($query);

include 'closedb.php'; ?> To restore the backup you just need to run LOAD DATA INFILE query like this : <?php include 'config.php'; include 'opendb.php'; $tableName = 'mypet'; $backupFile = 'mypet.sql'; $query = "LOAD DATA INFILE 'backupFile' INTO TABLE $tableName"; $result = mysql_query($query);

include 'closedb.php'; ?> It's a good idea to name the backup file as tablename.sql so you'll know from which table the backup file is

Run mysqldump using system() function


The system() function is used to execute an external program. Because MySQL already have built in tool for creating MySQL database backup (mysqldump) let's use it from our PHP script <?php include 'config.php'; include 'opendb.php'; $backupFile = $dbname . date("Y-m-d-H-i-s") . '.gz'; $command = "mysqldump --opt -h $dbhost -u $dbuser -p $dbpass $dbname | gzip > $backupFile"; system($command); include 'closedb.php'; ?>

Use phpMyAdmin to do the backup


This option as you may guessed doesn't involve any programming on your part. However I think i mention it anyway so you know more options to backup your database. To backup your MySQL database using phpMyAdmin click on the "export" link on phpMyAdmin main page. Choose the database you wish to backup, check the appropriate SQL options and enter the name for the backup file.

11. Form Validation With PHP

Whenever you make a form you should not leave it alone without any form validation. Why? Because there is no guarantee that the input is correct and processing incorrect input values can make your application give unpredictable result. You can validate the form input on two places, client side and server side. Client side form validation usually done with javascript. Client side validation makes your web application respond 'faster' while server side form validation with PHP can act as a backup just in case the user switch off javascript support on her browser. And since different browsers can behave differently there is always a possibility that the browser didn't execute the javascript code as you intended.

Some things you need to check :

empty values numbers only input length email address strip html tags
To show form validation with php in action I'll use the contact form in this website. Click here to see the contact form and then take a look at the source code. This contact form requires four input :

sender name sender email message subject message body


First let's focus on the client side validation. On the "Send Message" button I put this javascript code : onClick="return checkForm();", which is triggered when you click on it. Clicking the button will run the function checkForm().Every input is checked to see whether they are valid input. When an invalid input is found the function returns false so the form is not submitted. When you insert valid input the function will return true and the form is submitted. Go ahead and play around with the form. Try entering only spaces for the input value or enter glibberish string as email address. The code snippet below shows the client part of contact form. Example : contact.php Source code : contact.phps <html> <head> <title>Contact Form</title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <style type="text/css"> // CSS goes here </style> <script language="JavaScript"> function checkForm() { var cname, cemail, csubject, cmessage; with(window.document.msgform) { cname = sname; cemail = email; csubject = subject; cmessage = message; } if(trim(cname.value) == '') { alert('Please enter your name'); cname.focus(); return false; } else if(trim(cemail.value) == '') { alert('Please enter your email'); cemail.focus(); return false; } else if(!isEmail(trim(cemail.value))) { alert('Email address is not valid'); cemail.focus(); return false; } else if(trim(csubject.value) == '') { alert('Please enter message subject');

csubject.focus(); return false; } else if(trim(cmessage.value) == '') { alert('Please enter your message'); cmessage.focus(); return false; } else { cname.value = trim(cname.value); cemail.value = trim(cemail.value); csubject.value = trim(csubject.value); cmessage.value = trim(cmessage.value); return true; } } function trim(str) { return str.replace(/^\s+|\s+$/g,''); } function isEmail(str) { var regex = /^[-_.a-z0-9]+@(([-_a-z0-9]+\.)+(ad|ae|aero|af|ag| ai|al|am|an|ao|aq|ar|arpa|as|at|au|aw|az|ba|bb|bd|be|bf|bg| bh|bi|biz|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg| ch|ci|ck|cl|cm|cn|co|com|coop|cr|cs|cu|cv|cx|cy|cz|de|dj|dk| dm|do|dz|ec|edu|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb| gd|ge|gf|gh|gi|gl|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn| hr|ht|hu|id|ie|il|in|info|int|io|iq|ir|is|it|jm|jo|jp|ke|kg| kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly| ma|mc|md|mg|mh|mil|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|museum| mv|mw|mx|my|mz|na|name|nc|ne|net|nf|ng|ni|nl|no|np|nr|nt|nu| nz|om|org|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|pro|ps|pt|pw|py|qa| re|ro|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st| su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tm|tn|to|tp|tr|tt|tv|tw|tz| ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za| zm|zw)|(([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5])\.){3}([0-9][09]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5]))$/i; return regex.test(str); } </script> </head> <body> <form method="post" name="msgform"> <table width="500" border="0" align="center" cellpadding="2" cellspacing="1" class="maincell"> <tr> <td width="106">Your Name</td> <td width="381"><input name="sname" type="text" class="box" id="sname" size="30"></td> </tr> <tr> <td>Your Email</td> <td> <input name="email" type="text" class="box" id="email" size="30"> </td></tr> <tr> <td>Subject</td> <td><input name="subject" type="text" class="box" id="subject" size="30"></td> </tr> <tr> <td>Message</td> <td><textarea name="message" cols="55" rows="10" wrap="OFF" class="box" id="message"></textarea></td> </tr> <tr align="center"> <td colspan="2"><input name="send" type="submit" class="bluebox" id="send" value="Send Message" onClick="return checkForm();"></td> </tr> <tr align="center"> <td colspan="2">&nbsp;</td> </tr> </table> </form> </body> </html>

Now we'll take a better look at checkForm() function : function checkForm() { var cname, cemail, csubject, cmessage; with(window.document.msgform) { cname = sname; cemail = email; csubject = subject; cmessage = message;

} // ... the rest of the code } In the beginning of the function I use the keyword var to declare four variables to reference the form input . They are cname, cemail, csubject and cmessage. These variables will reference the form input sname, email, subject and message respectively.

Javascript treats a document and it's element as object. The message form is named msgform so to access is we use window.document.msgform and to access the sname input text we can use window.document.msgform.sname. To avoid the hassle of writing the window.document.msgform part whenever we want to access a form object I use the with() keyword. Without it the checkForm() function would look like : function checkForm() { var cname, cemail, csubject, cmessage; cname cemail csubject cmessage = = = = window.document.msgform.sname; window.document.msgform.email; window.document.msgform.subject; window.document.msgform.message;

// ... the rest of the code } Next we'll validate each form input. function checkForm() { // variable declarations goes here ... if(trim(cname.value) == '') { alert('Please enter your name'); cname.focus(); return false; } else if(trim(cemail.value) == '') { alert('Please enter your email'); cemail.focus(); return false; } else if(!isEmail(trim(cemail.value))) { alert('Email address is not valid'); cemail.focus(); return false; } // The rest of validation code goes here ... } To access the value of the name input box we use cname.value. The name values is trimmed to remove extra spaces from the beginning and end of the name. If you do not enter your name or only entering spaces then an alert box will pop up. Using cname.focus() the cursor will be placed to the name input box and then checkForm() return false which cancel the form submit.

The code above uses trim() function. This is not a built in javascript function. I can't understand why there is no trim() function in javascript, even VBScript has it. Anyway it's not a big deal because we can just make our own trim() function. The solution here uses regular expression to replace any spaces in the beginning and end of a string with blank string. function trim(str) { return str.replace(/^\s+|\s+$/g,''); } The forward slash (/) is used to create a regular expression. Note that it is not a string, you don't have to use quotes and it won't work if you use quotes. Let's chop the regular expression notation so we can understand it better :

^ : the beginning of a string $ : end of string. \s : single whitespace character (tab also count as whitespace) + : one or more | : conditional (OR)

g : global, mainly used for search and replace operation


So in english the search replace function above can be read as : "Replace one or more whitespace character from the beginning or ending of a string with blank character" As for the email input, we need to double check it. First, check if the email is entered and second check if the input is in a valid email format. For the second check well use isEmail() function. This function also uses regular expression. A valid email format can be described as : [ a string consisting of alphanumeric characters, underscores, dots or dash ] @ ( [ a valid domain name ] DOT [ a valid TLD ]) OR [a valid IP adress ] In case you're wondering TLD means Top Level Domain such as com, net, org, biz, etc. When you see the source code you will see that the regular expression in isEmail() function is actually written in one line. I have to break them into multiple lines just to fit the space. The PHP Manual explains the regular expression syntax for PHP in depth, but if you want to learn regular expression for javascript you can go to : http://www.regular-expressions.info Finally, if all input are considered valid checkForm() returns true and the form will be submitted. This will set the $_POST['send'] variable and now we start validating the input on the server side using PHP. <?php $errmsg $sname $email $subject $message = = = = = ''; ''; ''; ''; ''; // // // // // error message sender's name sender's email addres message subject the message itself

if(isset($_POST['send'])) { $sname = $_POST['sname']; $email = $_POST['email']; $subject = $_POST['subject']; $message = $_POST['message']; if(trim($sname) == '') { $errmsg = 'Please enter your name'; } else if(trim($email) == '') { $errmsg = 'Please enter your email address'; } else if(!isEmail($email)) { $errmsg = 'Your email address is not valid'; } else if(trim($subject) == '') { $errmsg = 'Please enter message subject'; } else if(trim($message) == '') { $errmsg = 'Please enter your message'; } // ... more code here ?> The PHP validation is doing the same thing as the javascript validation. It check each value to see if it's empty and if it is we consider that as an error. We also recheck the validity of the email address. When we find an error we set the value of $errmsg. We will print this value so the user can fix the error. If everything is okay the value of $errmsg will be blank. So we continue processing the input. <?php // ... previous validation code if($errmsg == '') { if(get_magic_quotes_gpc()) { $subject = stripslashes($subject); $message = stripslashes($message); }

$to = "email@yourdomain.com"; $subject = '[Contact] : ' . $subject; $msg = "From : $sname \r\n " . $message; mail($to, $subject, $msg, "From: $email\r\nReturn-Path: $email\r\n"); // ... more code here ?> Some web host set the PHP directive magic_quotes_gpc to On which runs addslashes() to every GET, POST, and COOKIE data so we got and extra work to strip the slashes from the input. Because the addslashes() function only add slashes before single quote ( ' ), double quote ( " ), backslash ( \ ) and NULL, we only need to worry about the $subject and $message. This is because (usually ) only these two can contain such characters. However, we can't be sure if magic_quotes_gpc is On or Off so we have to check it's value first using the get_magic_quotes_gpc() function After finishing all that boring job of validating the input we finally come to the last, and the most important step, sending the message using the mail() function. The first parameter we pass to the mail() function is the receiver's email address. The second is the email subject. The third is the message itself and the fourth is an additional headers. I'm sure you already understand the purpose of the first three parameters so I'll just discuss about the fourth one, the additional parameter ( additional headers ) "From: $email\r\nReply-To: $email\r\nReturn-Path: $email\r\n" Each headers are separated by the "\r\n" ( newline ) characters. The first two ( From and Reply-To ) is self explanatory. But what about the third one ( Return-Path )? The reason is some spam filter will check the Return-Path header and compare it with the From header. If these two don't match then the email is considered as spam and you're email won't get delivered ( or sent to the spam folder ). So it's better to play safe and put Return-Path header when we want to send an email to make sure it gets delivered.

That's it. I hope this tutorial can give you a clear idea on validating form, both on client side and server side. But if doesn't, then just use the contact form and let me know about it :-)

12. Creating A Guestbook Using PHP and MySQL


You've seen it at least once right? Guestbook is one of the most common thing to find in a website. In this tutorial we'll create a guestbook using PHP and MySQL. I have split this tutorial into two section, each covering a specific feature of the guestbook.

Creating The Sign-Guestbook Form


This part will cover creating the database tables, the guestbook form and the process of saving the entry to database

Viewing The Entries


You want to see the guestbook entries of course. This section covers fetching the entries from database and put int into an HTML table. You will also learn to show the entries in multiple pages using MySQL paging.

I think you should take a quick look what the finished guestbook look like. Just click here to see it.

Creating The Sign-Guestbook Form


We start by creating the table to store the data, guestbook. There are six fields in the guestbook table: 1. 2. 3. 4. 5. 6. id name email url message entry_date : the unique identifier for an entry in the guestbook : the visitor's name : visitor's email address : visitor's website url, if she has one : the message : when did this entry added

I have put the SQL query needed to create the table in guestbook.txt. Below is the HTML form code. It's pretty simple, we have text box for name, email and url plus a textarea to hold the message. The submit button is attached with a javascript function because we want to check the input values before the page is submitted. Example :guestbook.php Source code : guestbook.phps, guestbook.txt <form method="post" name="guestform"> <table width="550" border="0" cellpadding="2" cellspacing="1"> <tr> <td width="100">Name *</td> <td> <input name="txtName" type="text" size="30" maxlength="30"></td> </tr> <tr> <td width="100">Email</td> <td> <input name="txtEmail" type="text" size="30" maxlength="50"></td> </tr> <tr> <td width="100">Website URL</td> <td> <input name="txtUrl" type="text" value="http://" size="30" maxlength="50"></td> </tr> <tr> <td width="100">Message *</td> <td> <textarea name="mtxMessage" cols="80" rows="5"></textarea></td> </tr> <tr> <td width="100">&nbsp;</td> <td> <input name="btnSign" type="submit" value="Sign Guestbook" onClick="return checkForm();"></td> </tr> </table> </form> Below is the javascript code to check the input form. The checkForm() function is called when the "Sign Guestbook" button is clicked. The mandatory fields are name and message so if either is empty we pop an alert box to tell the visitor to enter the name and message. Email is not a mandatory field so we only check if in an email address is

entered but we won't complain if there's none . function checkForm() { // the variables below are assigned to each // form input var gname, gemail, gurl, gmessage; with(window.document.guestform) { gname = txtName; gemail = txtEmail; gurl = txtUrl; gmessage = mtxMessage; } // if name is empty alert the visitor if(trim(gname.value) == '') { alert('Please enter your name'); gname.focus(); return false; } // alert the visitor if email is empty or // if the format is not correct else if(trim(gemail.value) != '' && !isEmail(trim(gemail.value))) { alert('Please enter a valid email address or leave it blank'); gemail.focus(); return false; } // alert the visitor if message is empty else if(trim(gmessage.value) == '') { alert('Please enter your message'); gmessage.focus(); return false; } else { // when all input are correct // return true so the form will submit return true; } } /* Strip whitespace from the beginning and end of a string */ function trim(str) { return str.replace(/^\s+|\s+$/g,''); } /* Check if a string is in valid email format. */ function isEmail(str) { var regex = /^[-_.a-z0-9]+@(([-a-z0-9]+\.)+(ad|ae|aero|af|ag| ai|al|am|an|ao|aq|ar|arpa|as|at|au|aw|az|ba|bb|bd|be|bf|bg|bh| bi|biz|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci| ck|cl|cm|cn|co|com|coop|cr|cs|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz| ec|edu|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gh| gi|gl|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie| il|in|info|int|io|iq|ir|is|it|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr| kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|mg|mh|mil|mk| ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|museum|mv|mw|mx|my|mz|na|name|nc| ne|net|nf|ng|ni|nl|no|np|nr|nt|nu|nz|om|org|pa|pe|pf|pg|ph|pk| pl|pm|pn|pr|pro|ps|pt|pw|py|qa|re|ro|ru|rw|sa|sb|sc|sd|se|sg|sh| si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tm| tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn| vu|wf|ws|ye|yt|yu|za|zm|zw)|(([0-9][0-9]?|[0-1][0-9][0-9]|[2] [0-4][0-9]|[2][5][0-5])\.){3}([0-9][0-9]?|[0-1][0-9][0-9]|[2] [0-4][0-9]|[2][5][0-5]))$/i; return regex.test(str); }

After the form is submitted our job turns to saving the input into the database. In the code below I include config.php and opendb.php which contain the database configuration and the code needed to open a connection to MySQL. It's a good practice to put these actions in separate file. That way everytime you need to connect to MySQL you can include these files instead of rewriting the code. Also you can change the database information from just one file instead of changing it in every file that use MySQL. To see what the content of config.php, opendb.php and closedb.php go to : Connecting to MySQL database <?php include 'library/config.php';

include 'library/opendb.php'; if(isset($_POST['btnSign'])) { include 'library/config.php'; include 'library/opendb.php';

$name $email $url $message

= = = =

trim($_POST['txtName']); trim($_POST['txtEmail']); trim($_POST['txtUrl']); trim($_POST['mtxMessage']);

if(!get_magic_quotes_gpc()) { $message = addslashes($message); }

// if the visitor do not enter the url // set $url to an empty string if ($url == 'http://') { $url = ''; } $query = "INSERT INTO guestbook (name, email, url, message, entry_date) VALUES ('$name', '$email', '$url', '$message', current_date)"; mysql_query($query) or die('Error, query failed'); header('Location: ' . $_SERVER['REQUEST_URI']); exit; } ?> The script check if the $_POST['btnSign'] variable is set. If it is then the "Sign Guestbook" button must have been clicked and now we can read name, email, url and message from the $_POST global variable. After that we create an INSERT query string and execute the query using mysql_query(). Sometimes a message can contain single quotes, we need to escape these single quotes ( replacing it with \' ) otherwise MySQL will think that it's the end of a string and the query will fail. We use the addslashes() function to escape the string. Unfortunately some web hosts set the magic_quotes_gpc setting on. This will make values containing single-quotes in $_GET, $_POST and $_COOKIE will be automatically escaped. If we use addslashes() when the string is already escaped the result would be a mess. To check if magic_quotes_gpc is On use get_magic_quotes_gpc(). If it returns true then we don't have to call addslashes(). Ok, now affter all input is ready we can build the query string to enter the name, email, url, message and entry date. Note that for the entry_date field we use current_date. This is not a PHP variable or function, it's a built in MySQL function that returns ( guess what? ) the current date. You also see that I didn't explicitly insert the value of id field. This is because id is set as auto_increment so when we insert a new row into the table a new value for id is automatically generated ( incremented for each new row). After inserting the new guestbook entry the next thing we do is redirect back to current page using header('Location: ' . $_SERVER['REQUEST_URI']); Why? The redirect is just to prevent double submission. Suppose we don't use the redirect and the visitor hit the refresh button after signing up the guestbook then the form will be submitted again. Note : If you get this kind of error message Warning: Cannot modify header information - headers already sent by (output started at C:\webroot\guestbook\library\config.php:7) in C:\webroot\guestbook\guestbook.php on line 43 this mean the redirect failed because you already sent something to the browser. I got the error message above because i "accidentally" have a space right after the closing tag ( ?> ) in config.php. By removing this space the error is fixed.

This kind of errror is actually very common to see when your code is sending headers and fixing it is easy like the example above. Just check the file pointed by the error message and see if you accidentally sent ( print ) anyhing to the browser.

Pheww, we just finished the first part of our guestbook. Now it's time to create the script which will show the guestbook entries. We'll also try to split the entries into multiple pages when they are too much to be shown in one page.

Creating A Guestbook Using PHP and MySQL ( Part 2 )


Welcome to the second part of this guestbook tutorial. In case you haven't read the first section then go here to read it. In this second part we'll add some code to our previous guestbook script which will allow us to view the entries. Without further ado let's start working on it.

Viewing the entries


Example : guestbook.php Source code :guestbook.phps <?php include 'library/config.php'; include 'library/opendb.php'; // ... the code to save guestbook entries } ?> <html> <head> <title>Guestbook</title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <script language="JavaScript"> // ... the rest of javascript code goes here </script> </head> <body> <!-- this is where we put the guestbook form --> <?php // prepare the query string $query = "SELECT id, name, email, url, message, DATE_FORMAT(entry_date, '%d.%m.%Y') ". "FROM guestbook ". "ORDER BY id DESC "; $result = mysql_query($query) or die('Error, query failed'); // if the guestbook is empty show a message if(mysql_num_rows($result) == 0) { ?> <p><br><br>Guestbook is empty </p> <?php } else { // get the entries while($row = mysql_fetch_array($result)) { // list() is a convenient way of assign a list of variables // from an array values list($id, $name, $email, $url, $message, $date) = $row; // change all HTML special characters, // to prevent some nasty code injection $name = htmlspecialchars($name); $message = htmlspecialchars($message); // convert newline characters to HTML break tag ( <br> ) $message = nl2br($message); ?>

<table width="550" border="1" cellpadding="2" cellspacing="0"> <tr> <td width="80" align="left"> <a href="mailto:<?=$email;?>"> <?=$name;?> </a> </td> <td align="right"><small><?=$date;?></small></td> </tr> <tr> <td colspan="2"> <?=$message;?> <?php if($url != '') { // make the url clickable by formatting it as HTML link $url = "<a href='$url' target='_blank'>$url</a>"; ?> <br> <small>Homepage : <?=$url;?></small> <?php } ?> </td> </tr> </table> <br> <?php } // end while When you just created the guestbook, there are no entry in guestbook table. We use mysql_num_rows()to check how many guestbook entries we have. If mysql_num_rows() returns 0 that means the table is empty and we can print a message saying that the guestbook is empty. If there are already entries in the guestbook we then loop to get all the rows. I use list() to extract the values of a row into the variables $id, $name, $email, $url and $message. An additional step is needed for the $name and $message. For these two we use htmlspecialchars() before printing their value. This function will convert all special characters to HTML entities. As an example suppose I enter the string <b>I am a wizard</b> in the message textarea. After applying htmlspecialchars() it will be converted to &lt;b&gt;I am a wizard&lt;/b&gt; What's the point of using htmlspecialchars()? Well, the answer is because some people may try to abuse your guestbook. Some will enter a simple HTML bold formatted message like the example above but some may even try to input a javascript code in the message. As an example I could enter a script like this : <script> while(true) { window.open("http://www.google.com"); } </script> If I don't use htmlspecialchars() and show it as is then when we view the guestbook entries this code will continously open a new window of www.google.com. Won't do any harm if you have a popup blocker ready. But for those unlucky people who haven't got it installed will have their desktop filled with new windows in no time. Very annoying indeed. One more thing added for $message. We also use the function nl2br() to convert any newline characters ( that's \r OR \n OR both ) into HTML break tags ( <br> ). Because web browser "ignores'" newline characters, we need nl2br() to preserve the message formatting. This way if you explicitly enter a three line message it will also be shown as a three line message. Ok, now that we have the values ready we just need to put them in the HTML table. In above example I use <?=$name;?> to print the value of $name. I can also use <?php echo $name; ?>, but it's easier to use the first form. Now we're one step closer to finishing the guestbook. We just need to add a little more code for paging. Surely you don't want to show all the entries in one page. If you have a hundred entries the page will take forever to load. So let's add that little code to split the result into multiple pages.

Showing the entries in multiple pages


Example : guestbook.php Source code :guestbook.phps <?php // how many guestbook entries to show per page $rowsPerPage = 10; // by default we show first page $pageNum = 1; if(isset($_GET['page'])) { $pageNum = $_GET['page']; }

$offset = ($pageNum - 1) * $rowsPerPage; // prepare the query string $query = "SELECT id, name, email, url, message, DATE_FORMAT(entry_date, '%d.%m.%Y') ". "FROM guestbook ". "ORDER BY id DESC ". "LIMIT $offset, $rowsPerPage"; // ... the rest of the code ?> First we set how many entries we want to show per page ( $rowsPerPage ). We will use this value with the LIMIT keyword in our query so the query will only get a chunk of all entries available. The logic flow is like this. When the page is first loaded the $_GET['page'] is not yet initialized so we use the default $pageNum = 1. We then use $pageNum to count the offset ( the index of the first result we want to show ). As an example, if $pageNum = 1, $offset will be (1 - 1) * 10 = 0. Our limit query will be "LIMIT 0, 10". This will select the first ten entries from our guestbook table. Another example . When $pageNum = 3, $offset = 20, limit query is "LIMIT 20, 10" which will select ten result starting from the 20th index Now that we have the query ready we need to create the navigation link so our visitor can easily move from the first page to other pages. We simply print the page number as a hyperlink. So when a visitor click on a page number the script will show the entries for the specified page. The code needed is shown below <?php // .... previous code $query $result $row $numrows = = = = "SELECT COUNT(id) AS numrows FROM guestbook"; mysql_query($query) or die('Error, query failed'); mysql_fetch_array($result, MYSQL_ASSOC); $row['numrows'];

$maxPage = ceil($numrows/$rowsPerPage); $nextLink = ''; if($maxPage > 1) { $self = $_SERVER['PHP_SELF']; $nextLink = array(); for($page = 1; $page <= $maxPage; $page++) { $nextLink[] = "<a href=\"$self?page=$page\">$page</a>"; } $nextLink = "Go to page : " . implode(' &raquo; ', $nextLink); } include 'library/closedb.php'; ?> <table width="550" border="0" cellpadding="2" cellspacing="0"> <tr> <td align="right" class="text"> <?=$nextLink;?> </td> </tr> </table> <?php } ?> First we count the total number of entries we have ( $numrows ) then we find the maximum page numbers. To do this we just need the ceil() function to round the number up. For example, suppose we have 34 entries in our guestbook database and we want to show 10 entries per page. From these numbers we know that we wil split the result in ceil( 34 / 10) = 4 pages. If the entries span in more than one page we do a loop to create the links. The link will look something like this : guestbook.php?page=3 Note that in above code we use $_SERVER['PHP_SELF'] instead of using the filename itself, guestbook.php. This is done to save the trouble of modifying the code if someday we want to change the

filename. We temporarily put the links in an array, $nextLink. Once we get all the links in there we just join them all using implode(). And now our guestbook is done. Congratulations to you :-). If you want the source code for this guestbook tutorial just click here . The zip file contain all the files required but dont' forget to modify library/config.php to match your own settings.

Room For Improvements


Our guestbook script is actually very simple. You can really make lots of improvements, suc as :

Flood prevention
Prevent the visitor from signing the guestbook over and over again. You can log the visitor's IP and before saving the entry check the database if there's already an entry from such IP in the past hour ( or minute ). You can also use cookie for this

Bad words filtering


Before saving the message strip out any bad words. You can create an array listing the words you want to omit and then check the message against the list

Message size limitation


This is to prevent the visitor to enter a very long message. Spammers usually do this. Advertising their website in guestbooks.

Emoticons
You simply need to replace some special set of characters like :) or :( into an image tag. For example changing :) into <img src="emoticons/smile.gif">

Mail notification of new entry


Just use the mail() function after saving the message

Allow a specific set of HTML tags


This also can be achieved by simply searching and replacing unwanted HTML tags.

13. Uploading Files To MySQL Database


Using PHP to upload files into MySQL database sometimes needed by some web application. For instance for storing pdf documents or images to make som kind of online briefcase (like Yahoo briefcase). For the first step, let's make the table for the upload files. The table will consist of. 1. id : Unique id for each file 2. name : File name 3. type : File content type 4. size : File size 5. content : The file itself For column content we'll use BLOB data type. BLOB is a binary large object that can hold a variable amount of data. MySQL have four BLOB data types, they are :

TINYBLOB BLOB MEDIUMBLOB LONGBLOB


Since BLOB is limited to store up to 64 kilobytes of data we will use MEDIUMBLOB so we can store larger files ( up to 16 megabytes ). Example : upload.txt CREATE TABLE upload ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(30) NOT NULL, type VARCHAR(30) NOT NULL, size INT NOT NULL, content MEDIUMBLOB NOT NULL, PRIMARY KEY(id) ); Uploading a file to MySQL is a two step process. First you need to upload the file to the server then read the file and insert it to MySQL. For uploading a file we need a form for the user to enter the file name or browse their computer and select a file. The input type="file" is used for that purpose.

Example : upload.php Source code : upload.phps <form method="post" enctype="multipart/form-data"> <table width="350" border="0" cellpadding="1" cellspacing="1" class="box"> <tr> <td width="246"> <input type="hidden" name="MAX_FILE_SIZE" value="2000000"> <input name="userfile" type="file" id="userfile"> </td> <td width="80"><input name="upload" type="submit" class="box" id="upload" value=" Upload "></td> </tr> </table> </form> An upload form must have encytype="multipart/form-data" otherwise it won't work at all. Of course the form method also need to be set to method="post". Also remember to put a hidden input MAX_FILE_SIZE before the file input. It's to restrict the size of files. After the form is submitted the we need to read the autoglobal $_FILES. In the example above the input name for the file is userfile so the content of $_FILES are like this : $_FILES['userfile']['name'] The original name of the file on the client machine. $_FILES['userfile']['type'] The mime type of the file, if the browser provided this information. An example would be "image/gif". $_FILES['userfile']['size'] The size, in bytes, of the uploaded file. $_FILES['userfile']['tmp_name'] The temporary filename of the file in which the uploaded file was stored on the server. $_FILES['userfile']['error'] The error code associated with this file upload. ['error'] was added in PHP 4.2.0

Example : upload.php Source code : upload.phps <?php if(isset($_POST['upload']) && $_FILES['userfile']['size'] > 0) { $fileName = $_FILES['userfile']['name']; $tmpName = $_FILES['userfile']['tmp_name']; $fileSize = $_FILES['userfile']['size']; $fileType = $_FILES['userfile']['type']; $fp = fopen($tmpName, 'r'); $content = fread($fp, filesize($tmpName)); $content = addslashes($content); fclose($fp); if(!get_magic_quotes_gpc()) { $fileName = addslashes($fileName); } include 'library/config.php'; include 'library/opendb.php'; $query = "INSERT INTO upload (name, size, type, content ) ". "VALUES ('$fileName', '$fileSize', '$fileType', '$content')"; mysql_query($query) or die('Error, query failed'); include 'library/closedb.php'; echo "<br>File $fileName uploaded<br>"; } ?> Before you do anything with the uploaded file. You should not assume that the file was uploaded successfully to the server. Always check to see if the file was successfully uploaded by looking at the file size. If it's larger than zero byte then we can assume that the file is uploaded successfully. PHP saves the uploaded file with a temporary name and save the name in $_FILES['userfile']['tmp_name']. Our next job is to read the content of this file and insert the content to database. Always make sure that you use addslashes() to escape the content. Using addslashes() to the file name is also recommended because you never know what the file name would be. That's it now you can upload your files to MySQL. Now it's time to write the script to download those files.

Downloading Files From MySQL Database


When we upload a file to database we also save the file type and length. These were not needed for uploading the files but is needed for downloading the files from the database. The download page list the file names stored in database. The names are printed as a url. The url would look like download.php?id=3. To see a working example click here. I saved several images in my database, you can try downloading them. Example : download.php Source code : download.phps <html> <head> <title>Download File From MySQL</title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> </head> <body> <?php include 'library/config.php'; include 'library/opendb.php'; $query = "SELECT id, name FROM upload"; $result = mysql_query($query) or die('Error, query failed'); if(mysql_num_rows($result) == 0) { echo "Database is empty <br>"; } else { while(list($id, $name) = mysql_fetch_array($result)) { ?> <a href="download.php?id=<?php=$id;?>"><?php=$name;?></a> <br> <?php } }

include 'library/closedb.php'; ?> </body> </html>

When you click the download link, the $_GET['id'] will be set. We can use this id to identify which files to get from the database. Below is the code for downloading files from MySQL Database. Example : download.php Source code : download.phps <?php if(isset($_GET['id'])) { // if id is set then get the file with the id from database include 'library/config.php'; include 'library/opendb.php'; $id = $_GET['id']; $query = "SELECT name, type, size, content " . "FROM upload WHERE id = '$id'"; $result = mysql_query($query) or die('Error, query failed'); list($name, $type, $size, $content) = mysql_fetch_array($result); header("Content-length: $size"); header("Content-type: $type"); header("Content-Disposition: attachment; filename=$name"); echo $content; include 'library/closedb.php'; exit; } ?> Before sending the file content using echo first we need to set several headers. They are : 1. header("Content-length: $size") This header tells the browser how large the file is. Some browser need it to be able to download the file properly. Anyway it's a good manner telling how big the file is. That way anyone who download the file can predict how long the download will take. 2. header("Content-type: $type") This header tells the browser what kind of file it tries to download. 3. header("Content-Disposition: attachment; filename=$name"); Tells the browser to save this downloaded file under the specified name. If you don't send this header the browser will try to save the file using the script's name (download.php). After sending the file the script stops executing by calling exit. NOTE : When sending headers the most common error message you will see is something like this : Warning: Cannot modify header information - headers already sent by (output started at C:\Webroot\library\config.php:7) in C:\Webroot\download.php on line 13 This error happens because some data was already sent before we send the header. As for the error message above it happens because i "accidentally" add one space right after the PHP closing tag ( ?> ) in config.php file. So if you see this error message when you're sending a header just make sure you don't have any data sent before calling header(). Check the file mentioned in the error message and go to the line number specified

Next we will use a different method for uploading files. Instead of saving the file content to the database we will store the file in the server and just save the file path.

Uploading Files To File Server Using PHP


Now, we will make another upload script. But this time we won't save the file in the database. We will only store the file info there but the real file is stored in the file server. We need a little modification to the upload table. Instead of using BLOB datatype we just use VARCHAR to store the file path. Example : upload2.txt CREATE TABLE upload2 ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(30) NOT NULL, type VARCHAR(30) NOT NULL, size INT NOT NULL, path VARCHAR(60) NOT NULL, PRIMARY KEY(id) );

The HTML form we use is no different with the previous one since the real changes will take place in the PHP codes. Example : upload2.php Source code : upload2.phps <form method="post" enctype="multipart/form-data"> <table width="350" border="0" cellpadding="1" cellspacing="1" class="box"> <tr> <td width="246"> <input type="hidden" name="MAX_FILE_SIZE" value="2000000"> <input name="userfile" type="file" id="userfile"> </td> <td width="80"><input name="upload" type="submit" class="box" id="upload" value=" Upload "></td> </tr> </table> </form> Okay, now let's take a look at the upload process. First we need to specify the directory to store the uploaded files. We store the directory name in $uploadDir. Note that PHP must have write access to $uploadDir or else the upload will fail. If you're web host using a Linux server you may need to set the permission for the upload directory to 777. Example : upload2.php Source code : upload2.phps <?php $uploadDir = 'C:/webroot/upload/'; if(isset($_POST['upload'])) { $fileName = $_FILES['userfile']['name']; $tmpName = $_FILES['userfile']['tmp_name']; $fileSize = $_FILES['userfile']['size']; $fileType = $_FILES['userfile']['type']; $filePath = $uploadDir . $fileName; $result = move_uploaded_file($tmpName, $filePath); if (!$result) { echo "Error uploading file"; exit; } include '../library/config.php'; include '../library/opendb.php'; if(!get_magic_quotes_gpc()) { $fileName = addslashes($fileName); $filePath = addslashes($filePath); } $query = "INSERT INTO upload2 (name, size, type, path ) ". "VALUES ('$fileName', '$fileSize', '$fileType', '$filePath')"; mysql_query($query) or die('Error, query failed : ' . mysql_error()); include '../library/closedb.php'; echo "<br>Files uploaded<br>"; }

?> The key here is the move_uploaded_file() function. This function will move the uploaded files from the temporary upload directory to the location that we earlier ( $uploadDir . $fileName ). If for some reason the functioncannot move the file it will return false and we exit the script because continuing the script is no use.

Downloading
For listing the download files we just need to copy from the previous script. The real difference start when you click on the download link. Example : download2.php Source code : download2.phps if(isset($_GET['id'])) { include '../library/config.php'; include '../library/opendb.php'; $id = $_GET['id']; $query = "SELECT name, type, size, path FROM upload2 WHERE id = '$id'"; $result = mysql_query($query) or die('Error, query failed'); list($name, $type, $size, $filePath) = mysql_fetch_array($result); header("Content-Disposition: attachment; filename=$name"); header("Content-length: $size"); header("Content-type: $type"); readfile($filePath); include '../library/closedb.php'; exit; } After fetching the file info from the database and sending the required headers the next thing we need to do is read the file content from the server and send it to the browser. We can accomplish this by using readfile() function.

The Problems
When using this method of uploading files there are two problems that we need to take care of. They are : 1. Preventing direct access to the uploaded files 2. Handling duplicate file names

Preventing direct access


For this example the upload directory where the files are stored is /home/arman198/public_html/examples/upload/files/. Using your browser you see the upload directory by clicking here. This is ( usually ) a bad thing because anyone can see directly the file list and download them all. If you don't want to prevent people from seeing the content of the upload directory you could create an empty file, name it index.html then put that file to the upload directory. This is certainly not the optimal solution because maybe some people will try guessing the files names. A better approach is to move the upload directory away from your web root. For example, the web root for this site is: /home/arman198/public_html/ to prevent direct listing i can set the upload directory to /home/arman198/upload/. This way an outsider cannot see directly what's inside the upload directory. For example, even if you go to this url : http://www.php-mysql-tutorial.com/../upload/ you can't see the upload directory

Handling duplicate file names


When saving the files into the MySQL database we don't have to worry about this. The table for saving the files uses an id as the primary key so even we put ten files with the same name there won't be any problem since we access the files using that unique id. The problem arise when saving the files to file server. The move_uploaded_file() function will overwrite a file with the same name and this is certainly not a desired behaviour. To prevent this we just need to modify the file name. In this example the file names are changed into a random string, 32 characters long. Example : upload3.php Source code : upload3.phps

<?php // ... same code as before // get the file extension first $ext = substr(strrchr($fileName, "."), 1); // make the random file name $randName = md5(rand() * time()); // and now we have the unique file name for the upload file $filePath = $uploadDir . $randName . '.' . $ext; $result = move_uploaded_file($tmpName, $filePath); // ... same code as before } ?> First we extract the file extension from the file name using strrchr() function combined with substr(). Then using md5() we generate the 32 characters long of random string. It will look something like 7d1d1da5aac5ad72b293165e8e6fe89b. After we join them up we get the new unique file name. This way the chance of stumbling upon a duplicate name problem is very very slim. As for the download part there's no change required. All we did was change the file name on the server so the previous download script ( download2.phps ) will do just fine

That's it. We're done. If you need the source codes for this php upload tutorial you can download them as zip file here. Make sure you change the library/config.php file to match your own settings and change the $uploadDir too if necessary.

14. Content Management System (CMS) Using PHP And MySQL


A Content Management System ( CMS ) is used to add, edit, and delete content on a website. For a small website, such as this, adding and deleting a page manually is fairly simple. But for a large website with lots of pages like a news website adding a page manually without a content management system can be a headache. A CMS is meant to ease the process of adding and modifying new content to a webpage. The pages content are stored in database, not in the file server. This tutorial will present an example of a simple content management system. You will be able to add, edit and delete articles using HTML forms. For the database table we'll call it the news table. It consist of three columns :

id : The article's id title : The title of an article content : The article itself

First we need to create a script to add an article. It is just a form where a user can enter the article's title and content. Example : cms-add.php Source code : cms-add.phps , cms.txt <form method="post"> <table width="700" border="0" cellpadding="2" cellspacing="1" align="center"> <tr> <td width="100">Title</td> <td><input name="title" type="text"></td> </tr> <tr> <td width="100">Content</td> <td><textarea name="content" cols="50" rows="10"></textarea></td> </tr> <tr> <td width="100">&nbsp;</td> <td>&nbsp;</td> </tr> <tr> <td colspan="2" align="center"><input name="save" type="submit" value="Save Article"></td> </tr> </table> </form> Whe an article is added the script just insert the article into the database. An article id is automatically generated by MySQL because the id column was created with AUTO_INCREMENT parameter . <?php if(isset($_POST['save'])) { $title = $_POST['title']; $content = $_POST['content']; if(!get_magic_quotes_gpc()) { $title = addslashes($title); $content = addslashes($content); } include 'library/config.php'; include 'library/opendb.php'; $query = " INSERT INTO news (title, content) ". " VALUES ('$title', '$content')"; mysql_query($query) or die('Error ,query failed'); include 'library/closedb.php'; echo "Article '$title' added"; } ?>

Now that we have the script to add articles let's create another script to view those articles. The script is list the title of articles available in database as clickable links. The article link have the article id appended like this http://www.php-mysql-tutorial.com/examples/cms/article1.php?id=3

One possible implementation of article1.php is presented below : Example : article1.php Source code : article1.phps <?php include 'library/config.php'; include 'library/opendb.php'; // if no id is specified, list the available articles if(!isset($_GET['id'])) { $self = $_SERVER['PHP_SELF']; $query = "SELECT id, title FROM news ORDER BY id"; $result = mysql_query($query) or die('Error : ' . mysql_error()); // create the article list $content = '<ol>'; while($row = mysql_fetch_array($result, MYSQL_NUM)) { list($id, $title) = $row; $content .= "<li><a href=\"$self?id=$id\">$title</a></li>\r\n"; } $content .= '</ol>'; $title = 'Available Articles'; } else { // get the article info from database $query = "SELECT title, content FROM news WHERE id=".$_GET['id']; $result = mysql_query($query) or die('Error : ' . mysql_error()); $row = mysql_fetch_array($result, MYSQL_ASSOC); $title = $row['title']; $content = $row['content']; } include 'library/closedb.php'; ?> // ... more code here When article1.php is first called the $_GET['id'] variable is not set and so it will query the database for the article list and save the list in the$content variable as an ordered list. The variable $title and $content will be used later when we print the result page. Take a look at the code below : Example : article1.php Source code : article2.phps <?php // ... previous code ?> <html> <head> <title> <?php echo $title; ?> </title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <style type="text/css"> // ... some css here to make the page look nicer </style> </head> <body> <table width="600" border="0" align="center" cellpadding="10" cellspacing="1" bgcolor="#336699"> <tr> <td bgcolor="#FFFFFF"> <h1 align="center"><?php echo $title; ?></h1> <?php echo $content; // when displaying an article show a link // to see the article list if(isset($_GET['id'])) { ?> <p>&nbsp;</p> <p align="center"><a href="<?php echo $_SERVER['PHP_SELF']; ?>">Article List</a></p> <?php } ?> </td>

</tr> </table> </body> </html> If you click on an article link the script will fetch the article's title and content from the database, save it to $title and $content variable and print the HTML file . At the bottom of the page we place a code to show the link to the article list which is the file itself without any query string ( $_SERVER['PHP_SELF'] ) With this implementation each article request involve one database query. For a heavy load website with lots of articles using the above implementation can cause a very high amount of database-request. So we need a better cms solution to reduce the load. One feasible solution is to implement caching ( cache ) which load an article from the database only once when the article was first requested. The article is then saved to a cache directory as a regular HTML file. Subsequent request to the article will no longer involve any database request. The script just need to read the requested article from the cache directory. Example : article2.php Source code : article2.phps <?php include 'library/config.php'; include 'library/opendb.php'; $cacheDir = dirname(__FILE__) . '/cache/'; if (isset($_GET['id'])) { $cacheFile = $cacheDir . '_' . $_GET['id'] . '.html'; } else { $cacheFile = $cacheDir . 'index.html'; } if (file_exists($cacheFile)) { header("Content-Type: text/html"); readfile($cacheFile); exit; } // ... more code coming ?>

First we need to specify the cache directory where all cache files are located. For this example the cache directory is located in the same place as the article2.php script. I mean if article2.php is stored in C:/webroot then the cache dir is in C:/webroot/cache/ The script thent check if the article was already in the cache. An article is saved into the cache directory using a filename generated from it's id. For example if you request the article using a link like this : http://www.php-mysql-tutorial.com/examples/cms/article2.php?id=3 Then the cache file for the article is _3.html This filename is just an underscore ( _ ) followed by the article id. In case article2.php is called like this : http://www.php-mysql-tutorial.com/examples/cms/article2.php no id is defined so we make the cache file name as index.html If the cache file is found , the content is read and printed using readfile() and the script terminate. When the article is not found in the cache then we need to look in the database and get the page content from there. Example : article2.php Source code : article2.phps <?php // ... previous code

if(!isset($_GET['id'])) { $self = $_SERVER['PHP_SELF'];

$query = "SELECT id, title FROM news ORDER BY id"; $result = mysql_query($query) or die('Error : ' . mysql_error()); $content = '<ol>'; while($row = mysql_fetch_array($result, MYSQL_NUM)) { list($id, $title) = $row; $content .= "<li><a href=\"$self?id=$id\">$title</a></li>\r\n"; } $content .= '</ol>'; $title = 'Available Articles'; } else { // get the article info from database $query = "SELECT title, content FROM news WHERE id=".$_GET['id']; $result = mysql_query($query) or die('Error : ' . mysql_error()); $row = mysql_fetch_array($result, MYSQL_ASSOC); $title = $row['title']; $content = $row['content']; } include 'library/closedb.php'; // ... still more code coming ?> As you can see above the process of fetching the article list and content is the same as article1.php. But before showing the page we have to start output buffering so we can save the content of the generated HTML file. See the code below. Just before printing the html we callob_start() to activate output buffering. From this point no output is sent from the script to the browser. So in the code example below anything between <html> and </html> tag is not sent to the browser but stored in an internal buffer first. After the closing html tag we useob_get_contents() to get the buffer content and store int in a temporary variable, $buffer. We then call ob_end_flush() which stop the output buffering ( so the page is now sent to the browser ). Example : article2.php Source code : article2.phps <?php // ... previous code ob_start(); ?> <html>

// ... same html code as article1.php

</html> <?php // get the buffer $buffer = ob_get_contents(); // end output buffering, the buffer content // is sent to the client ob_end_flush(); // now we create the cache file $fp = fopen($cacheFile, "w"); fwrite($fp, $buffer); fclose($fp); ?>

Now that we have the file content we can write the cache file using the filename generated earlier ( using underscore plus the article id ). From now on any request to the article will no longer involve a database query. At least until the article is updated.

Next we will need an admin page for our content management system. It is where we can edit and delete the articles.

Admin Page For Content Management System (CMS)


Now we start creating the administration page for our content management system. This page will list all the articles we have in database and also a menu for article maintenance including add, view, update and delete articles. The view command is just a link to the article2.php so I won't explain about it anymore and we can focus on deleting and modifying an article.

Delete Article
Go the admin page, and try to delete an article ( if there's no article create one first ). When you want to delete an article, a little javascript confirmation window will pop up. You should always put this kind of confirmation when trying to delete something. Just to make sure you don't delete by accident. Example : cms-admin.php Source code : cms-admin.phps <?php include 'library/config.php'; include 'library/opendb.php'; if(isset($_GET['del'])) { $query = "DELETE FROM news WHERE id = '{$_GET['del']}'"; mysql_query($query) or die('Error : ' . mysql_error());

// then remove the cached file $cacheDir = dirname(__FILE__) . '/cache/'; $cacheFile = $cacheDir . '_' . $_GET['id'] . '.html'; @unlink($cacheFile); // and remove the index.html too because the file list // is changed @unlink($cacheDir . 'index.html'); // redirect to current page so when the user refresh this page // after deleting an article we won't go back to this code block header('Location: ' . $_SERVER['PHP_SELF']); exit; } // ... more code here ?> Deleting an article is a simple process. When you click on an 'Delete' link. The admin page will reload to something like : cms-admin.php?del=3 where 3 is the article id that we want to delete.

Once we have the article id we just need to execute a delete query and article will be deleted from the database. Then we delete the cache file ( if it exists ) and the index.html file too because the file list already changed. In the code above we use an @ before the unlink() command. This is to supress any error message in case the cache file is not found ( i.e. the article is never requested before / recently updated ) After that we reload the admin page ( omitting the 'del' query string ). We need to do this because if the admin is refreshing the page we will go back executing the delete query again. It's a waste of database resource. Here is the rest of admin page code. We just fetch the article list from the database, print the article title and add some link to view, edit, delete and add article. Example : cms-admin.php Source code : cms-admin.phps // ... previous code <html> <head> <title>Admin Page For Content Management System (CMS)</title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <script language="JavaScript"> function delArticle(id, title) { if (confirm("Are you sure you want to delete '" + title + "'")) { window.location.href = 'cms-admin.php?del=' + id;

} } </script> </head> <body> <?php $query = "SELECT id, title FROM news ORDER BY id"; $result = mysql_query($query) or die('Error : ' . mysql_error()); ?> <table width="600" border="0" align="center" cellpadding="5" cellspacing="1" bgcolor="#999999"> <tr align="center" bgcolor="#CCCCCC"> <td width="500"><strong>Title</strong></td> <td width="150"><strong>Action</strong></td> </tr> <?php while(list($id, $title) = mysql_fetch_array($result, MYSQL_NUM)) { ?> <tr bgcolor="#FFFFFF"> <td width="500"> <?php echo $title;?> </td> <td width="150" align="center"> <a href="article2.php?id=<?php echo $id;?>" target="_blank">view</a> | <a href="cms-edit.php?id=<?php echo $id;?>">edit</a> | <a href="javascript:delArticle('<?php echo $id;?>', '<?php echo $title;?>');">delete</a></td> </tr> <?php } include 'library/closedb.php'; ?> </table> <p align="center"><a href="cms-add.php">Add an article</a></p> </body> </html>

Edit Article
To complete our content management sytem we'll make the edit page. It's interface is basically the same with cmsadd.php but we just need to add some code to fetch the article information from database. One important thing to remember when editing a page is that the page content may contain html tags or even javascript tags. If we put the content as is in the textarea wrong stuff could happen. To prevent this we need to use htmlspecialchars(). This function will change special HTML characters like < and > into &lt; and &gt;. If you're curious what could happen when the content are put as is, go ahead and try removing the htmlspecialchars() code and see the result.. Example : cms-edit.php Source code : cms-edit.phps <html> <head> <title>Edit An Article</title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <style type="text/css"> <!-.box { font-family: Arial, Helvetica, sans-serif; font-size: 12px; border: 1px solid #000000; } --> </style> </head> <body> <?php include 'library/config.php'; include 'library/opendb.php'; if(isset($_GET['id'])) { $query = "SELECT id, title, content ". "FROM news ". "WHERE id = '{$_GET['id']}'"; $result = mysql_query($query) or die('Error : ' . mysql_error()); list($id, $title, $content) = mysql_fetch_array($result, M); $content = htmlspecialchars($content); }

MYSQL_NU

else if(isset($_POST['save'])) { $id = $_POST['id']; $title = $_POST['title']; $content = $_POST['content']; if(!get_magic_quotes_gpc()) { $title = addslashes($title); $content = addslashes($content); } // update the article in the database $query = "UPDATE news ". "SET title = '$title', content = '$content' ". "WHERE id = '$id'"; mysql_query($query) or die('Error : ' . mysql_error()); // then remove the cached file $cacheDir = dirname(__FILE__) . '/cache/'; $cacheFile = $cacheDir . '_' . $_GET['id'] . '.html'; @unlink($cacheFile); // and remove the index.html too because the file list // is changed @unlink($cacheDir . 'index.html'); echo "Article '$title' updated"; // now we will display $title & content // so strip out any slashes $title = stripslashes($title); $content = stripslashes($content); } include 'library/closedb.php'; ?> <form method="post"> <input type="hidden" name="id" value="<?=$id;?>"> <table width="700" border="0" cellpadding="2" cellspacing="1" class="box"> <tr> <td width="100">Title</td> <td><input name="title" type="text" class="box" id="title" value="<?=$title;?>"></td> </tr> <tr> <td width="100">Content</td> <td><textarea name="content" cols="50" rows="10" class="box" id="content"><?=$content;?></textarea></td> </tr> <tr> <td width="100">&nbsp;</td> <td>&nbsp;</td> </tr> <tr> <td colspan="2" align="center"><input name="update" type="submit" class="box" id="update" value="Update Article"></td> </tr> </table> <p align="center"><a href="cms-admin.php">Back to admin page</a></p> </form> </body> </html> When the form is submitted we begin the updating process. First we use an UPDATE query to modify the article in the database. Then we remove the cache file and the index.html because the content of these two files may no longer accurate. We also use an @ before the unlink command to supress any error message. You may already notice this but we already use the $cacheDir variable in three scripts ( article2.php, cmsadmin.php, cms-edit.php ). It's a good idea to put this variable in the config.php file. So if we want to specify a different cache directory we only need to change the variable in one file instead of three.

Okay, now that we have completed the tutorial maybe you start thinking that this system is too simple. If you do think so, you are absolutely right :-). There are a whole bunch of great content management system solutions out there. Mambo and Website Baker are two of them. These two are really cool ( and free ). Mambo has lots of features, it is really a complete content management system solution. Website Baker is similar to Mambo but it's simpler and easier to use and learn. I suggest you get one of those two, use it and learn from the source code. You really can learn a lot from it. By the way all the files needed for this cms tutorial can be downloaded here

15. User Authentication


Here are some authentication method that we'll discuss 1. Basic authentication We hard code the username and password combination in the login script itself. Suitable for simple application 2. User & password stored in database A very common method. We store all the user name and password information in the database 3. User authentication with image verification This is a more advance method of user authentication. We can prevent any automatic login by a robot ( script ) by using this method All three of these methods will use the session. Since session is supported by PHP by default you don't need to configure anything to use it but you do need to use a recent version of PHP. By recent i mean greater than PHP 4.3.2. Using lesser than that version may cause the script to work only occassionally ( sometimes it won't work at all ). Don't ask me what cause that cause i don't about the reason too :-) For the third method you will need GD library which is already bundled in PHP but you will need to enable the GD support before using the library. To see if GD library is already enabled save the following code, execute it, and see what the result is Source : gdtest.phps <?php if (function_exists('imagecreate')) { echo "GD Library is enabled <br>\r\n<pre>"; var_dump(gd_info()); echo "</pre>"; } else { echo 'Sorry, you need to enable GD library first'; } ?> If GD is not enabled yet open up php.ini and search for "extension=php_gd2.dll" ( without the quotes ). Uncomment the line by removing the semicolon ( ; ). After restarting your webserver try running the test script again. You should see the message saying that GD is enabled.

Basic User Authentication


With this basic authentication method we store the user information ( user id and password ) directly in the script. This is only good if the application only have one user since adding more user means we must also add the new user id and password in the script. Let's start by making the login form first. You can see the code below. Example : basic/login.php Source : basic/login.phps <?php // ... we will put some php code here ?> <html> <head> <title>Basic Login</title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> </head> <body> <?php if ($errorMessage != '') { ?> <p align="center"><strong><font color="#990000"><?php echo $errorMessage; ?></font></strong></p> <?php } ?> <form method="post" name="frmLogin" id="frmLogin"> <table width="400" border="1" align="center" cellpadding="2" cellspacing="2"> <tr> <td width="150">User Id</td> <td><input name="txtUserId" type="text" id="txtUserId"></td> </tr> <tr> <td width="150">Password</td> <td><input name="txtPassword" type="password" id="txtPassword"></td> </tr> <tr> <td width="150">&nbsp;</td> <td><input type="submit" name="btnLogin" value="Login"></td> </tr>

</table> </form> </body> </html> Nothing sophisticated in that form. It's just a basic login form with two input for entering the user id and password. Make sure that the form method is set to post since we certainly don't want to show up the user id and password in the address bar. Right before the login form we there's a code for printing an error message. We can ignore this for now since we'll be talking about it shortly. Once we submit the form we can start the authentication process. We simply check if the user id and password exist in $_POST and check if these two match the hardcoded user id and password. Example : basic/login.php Source : basic/login.phps <?php // we must never forget to start the session session_start(); $errorMessage = ''; if (isset($_POST['txtUserId']) && isset($_POST['txtPassword'])) { // check if the user id and password combination is correct if ($_POST['txtUserId'] === 'theadmin' && $_POST['txtPassword'] === 'chumbawamba') { // the user id and password match, // set the session $_SESSION['basic_is_logged_in'] = true; // after login we move to the main page header('Location: main.php'); exit; } else { $errorMessage = 'Sorry, wrong user id / password'; } } ?> // ... here is the login form shown previously But before we start matching the user id and password. We must start the session first. Never forget to start the session before doing anything to the session since it won't work. You can see above that the hardcoded user id and password are "theadmin" and "chumbawamba". If the submitted user id and password match these two then we set the value of $_SESSION['basic_is_logged_in'] to true. After that we move the application's main page. In this case it's called main.php If the user id and password don't match we set the error message. This message will be shown on top of the login form. Note : When starting the session you may stumble upon this kind of error : Warning: session_start(): Cannot send session cache limiter - headers already sent (output started at C:\Webroot\examples\user-authentication\basic\login.php:1) in C:\Webroot\examples\userauthentication\basic\login.php on line 3 PHP will spit this error message if the script that call session_start() already send something ( a blank space, newline, etc ). The error above happen when i add a single space on the first line right before the php opening tag ( <?php ). Thankfully the error message shows where the output started so fixing this kind of error is simple. After removing that extra space the error is fixed.

Checking if the user is logged in or not


Since the application main page, main.php, can only be accessed by those who already authenticated themselves we must check that before displaying the page. The checking process is fairly simple. We just see if $_SESSION['basic_is_logged_in'] is set or not. If it is set we check if the value is true. If either of this condition is not met then the one accessing this page haven't login yet. And so we redirect to the login page and quit the script. If $_SESSION['basic_is_logged_in'] is set and it's value is true then we can continue showing the rest of the page. Here is the code for main.php Example : basic/main.php Source : basic/main.phps <?php // like i said, we must never forget to start the session session_start();

// is the one accessing this page logged in or not? if (!isset($_SESSION['basic_is_logged_in']) || $_SESSION['basic_is_logged_in'] !== true) { // not logged in, move to login page header('Location: login.php'); exit; } ?> <html> <head> <title>Main User Page</title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> </head> <body> <p>This is the main application page. You are free to play around here since you are an autenthicated user :-) </p> <p>&nbsp;</p> <p><a href="logout.php">Logout</a> </p> </body> </html> A little note about naming a session variable. As you can see the session that we used to mark whether a user is logged in or not is named 'basic_is_logged_in'. When setting a name for a session variable it's a good thing to use the application name as the prefix. In this case the prefix is 'basic_' . This is especially important when you have multiple application on one site where each requires different login information. For example, suppose we have a cms application and a link exchange application where each have their own user authentication system. In both application we use the session variable $_SESSION['is_logged_in']. In this case if we already logged in in the cms application we will no longer be required to login in the link exchange application since both are using the same session name. This is usually not an intended feature. To avoid that kind of thing we can instead use $_SESSION['cms_is_logged_in'] and $_SESSION['exchange_is_logged_in']

The Logout Script


No login script is complete without the logout script right? So let's start making the logout script now. The process of logging out a user is actually depends on how we check if a user is logged in or not. In our case we check if $_SESSION['basic_is_logged_in'] is already set or not and check whether it's value is true. Using this information we can build the logout script to simply unset this session or set the session value to false. The logout script below use the first method ( unset the session ). Here is the code : Example : basic/logout.php Source : basic/logout.phps <?php // i will keep yelling this // DON'T FORGET TO START THE SESSION !!! session_start(); // if the user is logged in, unset the session if (isset($_SESSION['basic_is_logged_in'])) { unset($_SESSION['basic_is_logged_in']); } // now that the user is logged out, // go to login page header('Location: login.php'); ?> Before we unset the session we first check if the session is actually exist or not. In case you access the logout script before using the login form then this session variable won't exist yet. Unsetting a variable is done simply by using the unset() statement. After we unset the session the next thing we do is simply moving to the login page. Pretty simple huh ? Another note : You may already notice this but in each script i keep repeating about not to forget to start the session. The reason is that it is a very very very common error to forget about it when handling session. Once i spent a lot of time debugging a script and it was all because i forgot to add that one line. So please remember this : DON'T FORGET TO START THE SESSION !!!

Next we will make a better login method where the user info is stored in the database

Better User Authentication : Storing user id & Password In Database


A more common method of authenticating a user is by checking the database to see if the submitted user id and password combination exist. To use this kind of authentication we must first build the database table. The sql code to build it is shown below. We also add two user accounts for testing the login script Source : database/tbl_auth_user.sql CREATE TABLE tbl_auth_user ( user_id VARCHAR(10) NOT NULL, user_password CHAR(32) NOT NULL, PRIMARY KEY (user_id) ); INSERT INTO tbl_auth_user (user_id, user_password) VALUES ('theadmin', PASSWORD('chumbawamba')); INSERT INTO tbl_auth_user (user_id, user_password) VALUES ('webmaster', PASSWORD('webmistress')); We will use the same html code to create login form created in previous example. We will only need to modify the login process a bit. The login script's content is shown below : Example : database/login.php Source : database/login.phps <?php // we must never forget to start the session session_start(); $errorMessage = ''; if (isset($_POST['txtUserId']) && isset($_POST['txtPassword'])) { include 'library/config.php'; include 'library/opendb.php'; $userId = $_POST['txtUserId']; $password = $_POST['txtPassword']; // check if the user id and password combination exist in database $sql = "SELECT user_id FROM tbl_auth_user WHERE user_id = '$userId' AND user_password = PASSWORD('$password')"; $result = mysql_query($sql) or die('Query failed. ' . mysql_error()); if (mysql_num_rows($result) == 1) { // the user id and password match, // set the session $_SESSION['db_is_logged_in'] = true; // after login we move to the main page header('Location: main.php'); exit; } else { $errorMessage = 'Sorry, wrong user id / password'; } include 'library/closedb.php'; } ?>

// ... same html login form as previous example Instead of checking the user id and password against a hardcoded info we query the database if these two exist in the database using the SELECT query. If we found a match we set the session variable and move to the main page. Note that the session name is prefixed by 'db_' to make it different than the previous example. For the next two scripts ( main.php and logout.php ) the code is similar to previous one. The only difference is the session name. Here is the code for these two Example : database/main.php Source : database/main.phps <?php session_start(); // is the one accessing this page logged in or not? if (!isset($_SESSION['db_is_logged_in']) || $_SESSION['db_is_logged_in'] !== true) { // not logged in, move to login page header('Location: login.php');

exit; } ?> // ... some html code here Example : database/logout.php Source : dabase/logout.phps <?php session_start(); // if the user is logged in, unset the session if (isset($_SESSION['db_is_logged_in'])) { unset($_SESSION['db_is_logged_in']); } // now that the user is logged out, // go to login page header('Location: login.php'); ?>

The next part is the most interesting one of the authentication methods. We will display an image and the user must enter the random number displayed in the image to complete the login process.

User Authentication With Image Verification


In some cases you may want your loging form to be able to prevent automatic login by a robot ( script ). To achieve this we can create a login form which displays an image showing random numbers. The login form will have an extra input field to enter the values shown. Take a look at the login form. The numbers shown there will change everytime you refresh the page. Go ahead and try refreshing that page you will see the numbers always change. Before working on the login form we must take care of the script that create the verification image first. Here is the code : Example : image-verification/randomImage.php Source : image-verification/randomImage.phps <?php session_start(); // generate 5 digit random number $rand = rand(10000, 99999); // create the hash for the random number and put it in the session $_SESSION['image_random_value'] = md5($rand); // create the image $image = imagecreate(60, 30); // use white as the background image $bgColor = imagecolorallocate ($image, 255, 255, 255); // the text color is black $textColor = imagecolorallocate ($image, 0, 0, 0); // write the random number imagestring ($image, 5, 5, 8, $rand, $textColor); // send several headers to make sure the image is not cached // taken directly from the PHP Manual // Date in the past header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // always modified header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); // HTTP/1.1 header("Cache-Control: no-store, no-cache, must-revalidate"); header("Cache-Control: post-check=0, pre-check=0", false);

// HTTP/1.0 header("Pragma: no-cache");

// send the content type header so the image is displayed properly header('Content-type: image/jpeg'); // send the image to the browser imagejpeg($image); // destroy the image to free up the memory imagedestroy($image); ?> To create a five digit random number we use rand() function and specify that the random number must be between 10000 and 99999. We put the hash value of this random number in the session. This hash value will be used by the login script to check if the entered number is correct. Next we create a small image, 60 x 30 pixels, using imagecreate(). We set the background color to white ( RGB = 255, 255, 255 ) using imagecolorallocate() function. Note that the first call to imagecolorallocate() will always set the background color for the image. Then we set the text color as black ( RGB = 0, 0, 0 ). Feel free to change the color text to your liking. To print the random number to the image we use the function imagestring(). In the script above we call this function like this : imagestring ($image, 5, 5, 8, $rand, $textColor); The first argument passed to this function is the image handler ( $image ). The second one ( 5 ) is the font. You can choose from one to five where one is the smallest font. The third and fourth parameter is the horizontal and vertical coordinate where we will print the image. The top left corner is defined as 0, 0. The last one is the text color which is black as mentioned earlier. After we got the image ready we can now send it to the browser. But before doing that we must set several headers to make sure that the image is not cached. If the image is cached then the login form will show the same image even if you refresh it. That will cause a problem since the random number is always different. Finally after everything is set we send the image to the browser using imagejpeg() and to free the memory we use imagedestroy().

The Login Form


The login form is pretty much the same but only have extra field to enter the displayed number. Example : image-verification/login.php Source : image-verification/login.phps <?php // ... the login script is up here ?> <html> <head> <title>Basic Login</title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> </head> <body> <?php if ($errorMessage != '') { ?> <p align="center"><strong><font color="#990000"><?php echo $errorMessage; ?></font></strong></p> <?php } ?> <form action="" method="post" name="frmLogin" id="frmLogin"> <table width="500" border="1" align="center" cellpadding="2" cellspacing="2"> <tr> <td width="150">User Id</td> <td><input name="txtUserId" type="text" id="txtUserId"></td> </tr> <tr> <td width="150">Password</td> <td><input name="txtPassword" type="password" id="txtPassword"></td> </tr> <tr> <td width="150">Enter Number</td> <td><input name="txtNumber" type="text" id="txtNumber" value=""> &nbsp;&nbsp;<img src="randomImage.php"></td> </tr> <tr> <td width="150">&nbsp;</td> <td><input name="btnLogin" type="submit" id="btnLogin" value="Login"></td> </tr> </table> </form> </body> </html>

To check if the login information is correct we first check if the entered number is the same one as displayed in the image. To do this we check the hash of the entered number and see if it match the one saved in the session. If the number don't match we just set an error message. If the number do match we continue checking the given user id and password just like the previous example. If the userid and password combination is correct we set $_SESSION['image_is_logged_in'] to true and move on to the main page Example : image-verification/login.php Source : image-verification/login.phps <?php // we must never forget to start the session session_start(); $errorMessage = ''; if (isset($_POST['txtUserId']) && isset($_POST['txtPassword'])) { // first check if the number submitted is correct $number = $_POST['txtNumber']; if (md5($number) == $_SESSION['image_random_value']) { include 'library/config.php'; include 'library/opendb.php'; $userId = $_POST['txtUserId']; $password = $_POST['txtPassword'];

// check if the user id and password combination exist $sql = "SELECT user_id FROM tbl_auth_user WHERE user_id = '$userId' AND user_password = PASSWORD('$password')"; $result = mysql_query($sql) or die('Query failed. ' . mysql_error()); if (mysql_num_rows($result) == 1) { // the user id and password match, // set the session $_SESSION['image_is_logged_in'] = true; // remove the random value from session $_SESSION['image_random_value'] = ''; // after login we move to the main page header('Location: main.php'); exit; } else { $errorMessage = 'Sorry, wrong user id / password'; } include 'library/closedb.php'; } else { $errorMessage = 'Sorry, wrong number. Please try again'; } } ?> We don't need to discuss about main.php and logout.php since they are the same as previous example except the session name is now called $_SESSION['image_is_logged_in']. So instead of working on those two files let's move on to a more interesting stuff...

Improving The Verification Image


We can improve the verification image in at least two ways. They are : 1. Using alphanumeric characters as the verification code instead of numbers 2. Using backgound images For the first improvement the only thing we need to change is the way we generate the code. Take a look at the code below Example : image-verification/randomImage2.php Source : image-verification/randomImage2.phps <?php session_start(); $alphanum = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; // generate the verication code $rand = substr(str_shuffle($alphanum), 0, 5); // ... no changes after this point ?>

We start by defining the characters that we want to use in the verification code. For this example we use upper case alphabet plus numbers. The code is generated using the combination of str_shuffle() and substr() function. Using str_shuffle() we jumble all the characters in $aplhanum and then using substr() we take just the first five characters. The result will look something like "D79ZG". Just run the example and see it for yourself. The second improvement is by using background images. Maybe you already know this but there are software/scripts that can extract the characters displayed as images. And if the verification image only use plain background color identifying the characters wil be quite easy. For this reason we will make the verification code displayed on a background image. In this tutorial we will only use four different background images. You can add as many background images as you want in your own code. Here are the background images :

background image #1 : background image #2 : background image #3 : background image #4 :


Note : When you want to create a background image make sure the code will still be readable on them. For instance it's quite hard ( at least for me ) to recognize the code when the code is displayed on image #1 and image #4. Go take a look if you don't believe me. Here is the code for this second improvement Example : image-verification/randomImage3.php Source : image-verification/randomImage3.phps <?php session_start(); $alphanum = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; // generate the verication code $rand = substr(str_shuffle($alphanum), 0, 5); // choose one of four background images $bgNum = rand(1, 4); $image = imagecreatefromjpeg("background$bgNum.jpg"); $textColor = imagecolorallocate ($image, 0, 0, 0); // write the code on the background image imagestring ($image, 5, 5, 8, $rand, $textColor); // ... no changes after this point ?> After making the verification code we randomly pick one background image. Then we create an image object from the chosen background using imagecreatefromjpeg() and draw the code on the background. The rest of the code is the same as randomImage1.php and randomImage2.php so no need to explain it here.

Okay that is it. The three method of user authentication. Just pick one that fit your application. Btw, all the code for the three methods can be downloaded here

16. PHP MySQL Image Gallery


On the previous tutorial you already know how to upload and download files to the server. Now we're gonna reuse the codes to build an image gallery. It's a simple image gallery where the admin ( that's you ) is the only one who can add/modify/delete the album and images. The "normal folks" can only browse around the image gallery checking out the images from one album to another The admin section contain the following :

Add New Album Album List Edit & Delete Album Add Image Image List Edit & Delete Image
And the visitor page contain these :

Display Album List Display Image List Display Image Detail


Now, before we go straight to the codes we need to talk about the database design, directory layout, and configurations.

Database Design
For a simple image gallery like this we only need two tables, tbl_album and tbl_image. Here is the SQL to create these tables Source code : image-gallery.sql CREATE TABLE tbl_album ( al_id INT NOT NULL AUTO_INCREMENT, al_name VARCHAR(64) NOT NULL, al_description TEXT NOT NULL, al_image VARCHAR(64) NOT NULL, al_date DATETIME NOT NULL, PRIMARY KEY(al_id) );

CREATE TABLE tbl_image ( im_id INT NOT NULL AUTO_INCREMENT, im_album_id INT NOT NULL, im_title VARCHAR(64) NOT NULL, im_description TEXT NOT NULL, im_type VARCHAR(30) NOT NULL, im_image VARCHAR(60) NOT NULL, im_thumbnail VARCHAR(60) NOT NULL, im_date DATETIME NOT NULL, PRIMARY KEY(im_id) );

Directory Layout
The image below show the file organization for the image gallery

The images directory is where we kept all of the images. The image icons are stored in the thumbnail sub-directory uder the gallery. Please remember to set write access to the album, gallery, and thumbnail directories otherwise the gallery script will not be able to save the images.

Configurations
There are some constants in the config file that you should change :

1. ALBUM_IMG_DIR, GALLERY_IMG_DIR These are the absolute path to the images directories 2. THUMBNAIL_WIDTH The PHP script will create a thumbnail ( icons ) for each image that you upload. In addition when you add an album image that image will also resized automatically. One more note. If you want to test this gallery on your own computer please make sure you already have GD library installed. To check if GD library is installed on your system save the following code and run it. <?php if (function_exists('imagecreate')) { echo 'OK, you already have GD library installed'; } else { echo 'Sorry, it seem that GD library is not installed/enabled'; } ?> After taking care of the configurations we'll take a quick tour on the admin page

Image Gallery Administration Page

Login and logout


Before you can access the admin pages you will need to login first. The login method is very basic we just check the given userid and password against the hardcoded value in the login script. Here's the code : Example : admin/login.php Source : admin/login.phps $errMsg = ''; if (isset($_POST['txtUserid'])) { if ($_POST['txtUserid'] == 'bigbadwolf' && $_POST['txtUserpw'] == 'huffnpuff') { $_SESSION['isLogin'] = true; header('Location: index.php?page=list-album'); exit; } else { $errMsg = "Wrong Id/Password"; } } The userid is bigbadwolf and the password is huffnpuff . If the user enter the matching userid and password we set the session variable $_SESSION['isLogin'] to true. Then we send a header to redirect the user to the album list. To see if the user has logged in or not we call the function checkLogin() from the admin pages. It merely check the existence of the session variable we speak of earlier and it's value. If the session variable exist and it's value is set to true then we can say that the user has logged in. If you think that this login method is not very good you could easily plug other login method from this login tutorial The logout part is fairly simple also. We just need to set the value of $_SESSION['isLogin'] to false then redirect the user to the login page. That's it.

Admin Page Layout


If you see the source code for the admin main page you can see that it act more like a "frame" meaning that it's content is included from other files. Check out the album list page ( you may need to login first ).The tables containing the album list are included from this file. Now click the "Add Album" button. The form is included from this one. Here's the snapshot to make it clearer

Take a look at the code snippet below. Source code : admin/index.phps // ... some code here $page = (isset($_GET['page']) && $_GET['page'] != '') ? $_GET['page'] : 'list-album'; $allowedPages = array('list-album', 'add-album', 'album-detail', 'modify-album', 'list-image', 'add-image', 'image-detail', 'modify-image'); if (in_array($page, $allowedPages)) { include $page . '.php'; } else { ?> <table width="100%" border="0" align="center" cellpadding="2" cellspacing="1"> <tr> <td width="30" align="center"><strong>Error : The Page You're Looking For Doesn't Exist</strong></td> </tr> </table> <?php } ?> To decide which page to include index.php will look up the $_GET variable to see if $_GET['page'] is available. For example the url for the image list page is "index.php?page=list-image". So in this case the value of $_GET['page'] is "list-image" so the file we need to include is "list-image.php". If $_GET['page'] is empty or doesn't exist then we'll just use "list-album" as the default page. Before including any file we first check if the intended file is in our list of allowed pages. You see, the bad thing about including other pages is that it can cause serious security problem if you don't take some precautions. Here's an example. Suppose an evil ( or nosy ) guy decided to enter this url "index.php?page=../../../super-secret-file", then without checking the requested page against the list of allowed pages you could expose some secret code to him or even worse. In the code above you can see that if a user is requesting a page which is not in the list we just display an error message. We don't check if the file actually exist or not. If it's not in the allowed list then we will never include it.

Okay, now let's start making the image gallery from the first part, adding a new album

Admin : Add New Album


This is a very simple form where you can enter the album name, description and image. After you click the "Add Album" button the script will do the followings : 1. Save the album image, resize it if necessary 2. Save the album information to database Below is the screenshot of the form:

And here is the code snippet : Example : admin/add-album.php Source code : admin/add-album.phps require_once '../library/config.php'; require_once '../library/functions.php'; if(isset($_POST['txtName'])) { $albumName = $_POST['txtName'];

$albumDesc = $_POST['mtxDesc']; $imgName = $_FILES['fleImage']['name']; $tmpName = $_FILES['fleImage']['tmp_name']; // we need to rename the image name just to avoid // duplicate file names // first get the file extension $ext = strrchr($imgName, "."); // then create a new random name $newName = md5(rand() * time()) . $ext; // the album image will be saved here $imgPath = ALBUM_IMG_DIR . $newName; // resize all album image $result = createThumbnail($tmpName, $imgPath, THUMBNAIL_WIDTH); if (!$result) { echo "Error uploading file"; exit; } if (!get_magic_quotes_gpc()) { $albumName = addslashes($albumName); $albumDesc = addslashes($albumDesc); } $query = "INSERT INTO tbl_album (al_name, al_description, al_image, al_date) VALUES ('$albumName', '$albumDesc', '$newName', NOW())"; mysql_query($query) or die('Error, add album failed : ' .

mysql_error());

// the album is saved, go to the album list echo "<script>window.location.href='index.php?page=list-album';</script>"; exit; }

Since we save the images as files instead inserting them to the database we need to make sure there won't be any name duplication problem. To prevent this we just generate some random name for every images that we upload. Take a look at code below : $ext = strrchr($imgName, "."); $newName = md5(rand() * time()) . $ext; The first line is to extract the file extension from the file name. As an example let say we upload an image named "hyperalbum.jpg". Then strrchr("hyperalbum.jpg", ".") will return ".jpg". On the second line we generate a random number using rand() multiply it with current time and generate the hash code using md5(). It is a very common practice to use the combination of md5(), rand() and time() functions to generate a random name. After we append the file extension to the new name we can then use it to save the uploaded image. But before we save the image we need to resize the image if it's too large. As you can see in the album list we only need small images for the album icons. To make the thumbnail we use createThumbnail() function defined in functions.php . Once everything is saved we print a little javascript code to go to the album list page. Note that we cannot simply use header("Location: index.php?page=list-album") to redirect to the album list page since a call to header() will only have an effect when no other output in sent before the call.

Admin : Album List


When your first login to the admin area and after adding a new album you can see this page. There's nothing really interesting on this one. It just a plain list of albums where we can see the albums we have and how many images on each album. Here is the snapshot :

In the "Images" column you can see how many images contained in an album. If you click on the number you will go to the image list so can see all the images in a particular album. And i'm sure i don't need to explain what that button with "Add Album" written on it does. If you re-read the sql containing the table definitions of this gallery you can see that tbl_album doesn't contain any column storing the number of images in it. That number is the result of the left join in the sql query. You can see the sql code below. $sql = "SELECT al_id, al_name, al_image, COUNT(im_album_id) AS al_numimage FROM tbl_album al LEFT JOIN tbl_image im ON al.al_id = im.im_album_id GROUP by al_id ORDER BY al_name "; In this query we must use LEFT JOIN instead of INNER JOIN because an album can have zero image in it. If we use INNER JOIN then the empty albums will not be shown in the list. Now, if you right click on an album icon and view it's properties you can see that the url fo the icon is pointing to a PHP script instead of an image. The url look like this : viewImage.php?type=album&name=3b6a267a967d7535ff3b1ebc3d9e3c1e.jpg In this image gallery whenever we would want to display an album or image icon or the full-size image we always use the viewImage.php file instead of linking to the actual image. There are several reasons to do this. The first is so you could move the images directory outside of your web root to prevent leechers from taking all the images. The image gallery in our example doesn't do this. You could go to the images directory and list all the images in the gallery. If you set the value of ALBUM_IMG_DIR and GALLERY_IMG_DIR to a directory outside your webroot then you can prevent this. For example if your web root is /home/myname/public_html you can set ALBUM_IMG_DIR to /home/myname/images/album and GALLERY_IMG_DIR to /home/myname/images/gallery/. The second reason is that you may want to restrict the access your gallery. For example the visitors must login before they can see the images. In viewImage.php you could check for the session variable to determine that. So if the visitors hasn't login yet you just display some blank or warning images You can checkout the code here. It's really a simple script which requires two inputs. The type of image you wish to display ( album icon, image icon or full size image ) and the image file name. Then we only need to set the appropriate headers, read the image file and send it to the browser. Next we'll see how to modify & delete an album.

Modify & Delete Album


Here is the page where you can modify an album's name, description and image icon

We display the album thumbnail here just to make sure we're updating the right album and because i think the form is way too dull without any images. After you hit the "Modify" button we just need to save the new name & description to the database. However, before we changing the thumbnail we must first check if a new thumbnail image is provided or not. Here is the code snippet : Source : modify-album.phps // ... some code here if(isset($_POST['txtName'])) { $albumId = $_POST['hidAlbumId']; $albumName = $_POST['txtName']; $albumDesc = $_POST['mtxDesc']; if (!get_magic_quotes_gpc()) { $albumName = addslashes($albumName); $albumDesc = addslashes($albumDesc); } if ($_FILES['fleImage']['tmp_name'] != '') { $imgName = $_FILES['fleImage']['name']; $tmpName = $_FILES['fleImage']['tmp_name']; // just like when we add this album // we will need to rename the image name to avoid // duplicate file name problem $newName = md5(rand() * time()) . strrchr($imgName, "."); // resize the new album image $result = createThumbnail($tmpName, ALBUM_IMG_DIR . $newName, THUMBNAIL_WIDTH); if (!$result) { echo "Error uploading file"; exit; } // since a new image for this album is specified // we'll need to delete the old one $sql = "SELECT al_image FROM tbl_album WHERE al_id = $albumId"; $result = mysql_query($sql) or die('Error, get album info failed. ' . mysql_error()); $row = mysql_fetch_assoc($result); unlink(ALBUM_IMG_DIR . $row['al_image']); $newName = "'$newName'"; } else { // don't change the image $newName = "al_image"; } $query = "UPDATE tbl_album SET al_name = '$albumName', al_description = '$albumDesc', al_image = $newName WHERE al_id = $albumId";

mysql_query($query) or die('Error, modify album failed : ' . mysql_error()); // after saving the modification go to the detail page echo "<script>window.location.href='index.php?page=album-detail&alId=$albumId';</script>"; } // ... more code down here As you can see on the code above we update the album thumbnail only if a new image is provided. The process is no different than when we add a new album. After the new image is uploaded and the thumbnail is created successfully we then delete the old one using unlink(). That's the php function you'll need when you want to delete a file. Now. you need to take a look at the above code again but this time focus on this little piece // ... some code here if ($_FILES['fleImage']['tmp_name'] != '') { // ... upload the new image & delete the old one $newName = "'$newName'"; } else { $newName = "al_image"; } $query = "UPDATE tbl_album SET al_name = '$albumName', al_description = '$albumDesc', al_image = $newName WHERE al_id = $albumId"; // ... and more down here If you're wondering why in the if block $newName is surrounded by single quotes but in the else block $newName is given the value "al_image" without the single quotes, here's the explanation. Note that in the sql query string $albumName and $albumDesc is surrounded by quotes but $newName isn't. We need to do this so that when the image is not changed we can set the query to leave the image alone. Here's an example so you can understand it better. For the first scenario imagine that the admin update an album ( id = 123 ) and modify it's name to "my new album", description is changed to "my new description" and he also give a new image and it is saved under the new name "3b6a267a967d7535ff3b1ebc3d9e3c1e.jpg". The script will go through the if block because the value of $_FILES['fleImage']['tmp_name'] won't be empty and then the value of $query will be ... UPDATE tbl_album SET al_name = 'my new album', al_description = 'my new description', al_image = '3b6a267a967d7535ff3b1ebc3d9e3c1e.jpg' WHERE al_id = 123 ... which will update the image information including the image name. For the second scenario the admin only change the name and description but leave the image alone.Now the script will go to the else block and the value of $query will be ... UPDATE tbl_album SET al_name = 'my new album', al_description = 'my new description', al_image = al_image WHERE al_id = 123 ... which will update the album name and description but leave the old image name as it is. This is certainly not the only way to do it. You can easily add an if else statement and make a new query for each scenario. I'm just showing an alternative.

Delete Album
You can delete an album by clicking the "Delete" link on the album list. You'll see a confirmation box to make sure you really want to delete an album because when you delete album all images are deleted also. The code for deleting an album is located in the index.php file. The code flow is like this : 1. Get the album id 2. Make a query to get the name of that album and the thumbnail filename. Print an error message if the album doesn't exist 3. If the album exist get images file name and delete the images plus the album icon 4. Delete the album data and the images from the database Here is the code Source code : admin/index.phps // ... some code on top

if (isset($_GET['deleteAlbum']) && isset($_GET['album']) ) { $albumId = $_GET['album']; // get the album name since we need to display // a message that album 'foo' is deleted $result = mysql_query("SELECT al_name, al_image FROM tbl_album WHERE al_id = $albumId") or die('Delete image failed. ' . mysql_error()); if (mysql_num_rows($result) == 1) { $row = mysql_fetch_assoc($result); $albumName = $row['al_name']; $albumImage = $row['al_image']; // get the image filenames first so we can delete them // from the server $result = mysql_query("SELECT im_image, im_thumbnail FROM tbl_image WHERE im_album_id = $albumId") or die(mysql_error()); while ($row = mysql_fetch_assoc($result)) { unlink(GALLERY_IMG_DIR . $row['im_image']); unlink(GALLERY_IMG_DIR . 'thumbnail/' . $row['im_thumbnail']); } unlink(ALBUM_IMG_DIR . $albumImage); $result = mysql_query("DELETE FROM tbl_image WHERE im_album_id = $albumId") or die('Delete image failed. ' . mysql_error()); $result = mysql_query("DELETE FROM tbl_album WHERE al_id = $albumId") or die('Delete album failed. ' . mysql_error()); // album deleted successfully, let the user know about it echo "<p align=center>Album '$albumName' deleted.</p>"; } else { echo "<p align=center>Cannot delete a non-existent album.</p>"; } } // ... and some more on the bottom Now we start adding some images into the albums

Admin : Add Image


Adding a new image is easy. Just select in which album you want the image stored, supply the image name, description, add the image and click the "Add Image" button. Here's the form screenshot :

The image title is limited to 64 characters however there is no restriction on the length of the image description And, you are free to use any html tags in the description. The code for this part is very similar to the one when adding new album so i won't explain it in detail here. You could read the source code and i'm sure you'll understand. The difference is that the image is stored in GALLERY_IMG_DIR and the image icon and store it in GALLERY_IMG_DIR/thumbnail.

Image List
After you add a new image or when you click on "List Image" on the left navigation link you can see the list of all images. By default this page will show all images from all albums. To show images just from one album you can select the album name from the combo box on the top right corner. When

you select an album it will trigger the onChange event of the combo box and call a javascript function called viewImage() along with the id of the selected album. The function will then redirect you to the appropriate list.

The javascript code is show below. Source code : admin/index.phps function viewImage(albumId) { if (albumId != '') { window.location.href = 'index.php?page=list-image&album=' + albumId; } else { window.location.href = 'index.php?page=list-image'; } } When you select a specific album from the combo box the album id is sent to this function. The if block is executed and you go to the page for that specific album. But if you select the "--- All Album--" option then the value of albumId will be an empty string. The else block is executed and you'll go the the default image list. Next, modify and delete image

Modify & Delete Image


Here is the snapshot of the modify image form. It's very similar to the form to add the image except for the image thumbnail. Clicking on the thumbnail will call the javascript function viewLargeImage() which will open a popup displaying the full-sized image. If you checkout the form code you can see that we use htmlspecialchars() function when printing the image description in the textarea. Since html tags are allowed in the description we need that function so the html code in the description won't screw up the page.

After you click the "Modify Image" button we continue saving the new information and also save the new image ( if a new one is provided ). The process is just a repetition of the code to modify album data and image. The difference is that now we modify two images, the full-sized image and the thumbnail. Example : admin/index.php?page=modify-image&imgId=13 Source code : admin/modify-image.phps // ... some code here if (isset($_POST['txtTitle'])) { $albumId = $_POST['cboAlbum']; $imgTitle = $_POST['txtTitle']; $imgDesc = $_POST['mtxDesc']; if ($_FILES['fleImage']['tmp_name'] != '') { $images = uploadImage('fleImage', GALLERY_IMG_DIR); if ($images['image'] == '' && $images['thumbnail'] == '') { echo "Error uploading file"; exit; } $image = "'" . $images['image'] . "'"; $thumbnail = "'" . $images['thumbnail'] . "'"; $sql = "SELECT im_image, im_thumbnail FROM tbl_image WHERE im_id = $imgId"; $result = mysql_query($sql) or die('Error, get image info failed. ' . mysql_error()); $row = mysql_fetch_assoc($result); unlink(GALLERY_IMG_DIR . $row['im_image']); unlink(GALLERY_IMG_DIR . 'thumbnail/' . $row['im_thumbnail']); } else { // the old image is not replaced $image = "im_image"; $thumbnail = "im_thumbnail"; } if (!get_magic_quotes_gpc()) { $albumName = addslashes($albumName); $albumDesc = addslashes($albumDesc); } $sql = "UPDATE tbl_image SET im_album_id = $albumId, im_title = '$imgTitle', im_description = '$imgDesc', im_image = $image, im_thumbnail = $thumbnail, im_date = NOW() WHERE im_id = $imgId"; mysql_query($sql) or die('Error, update image failed : ' .

mysql_error()); echo "<script>window.location.href = 'index.php?page=image-detail&imgId=$imgId';</script>"; } // ... more code here

Delete Image
To delete an image we just need the image id an the album id. Then we get the image and icon name so we can delete them from the server. Once the files are deleted we continue removing the image information from the database. Source code : admin/list-image.phps // ... a little code here if (isset($_GET['delete']) && isset($_GET['album']) && isset($_GET['imgId'])) { // get the image file name so we // can delete it from the server $sql = "SELECT im_image, im_thumbnail FROM tbl_image WHERE im_id = {$_GET['imgId']} AND im_album_id = {$_GET['album']}"; $result = mysql_query($sql) or die('Delete album failed. ' . mysql_error()); if (mysql_num_rows($result) == 1) { $row = mysql_fetch_assoc($result); // remove the image and the thumbnail from the server unlink(GALLERY_IMG_DIR . $row['im_image']); unlink(GALLERY_IMG_DIR . 'thumbnail/' . $row['im_thumbnail']); // and then remove the database entry $sql = "DELETE FROM tbl_image WHERE im_id = {$_GET['imgId']} AND im_album_id = {$_GET['album']}"; mysql_query($sql) or die('Delete album failed. ' . mysql_error()); } } // ... a little code there Next, we continue to the "ordinary user" part. Starting from the album list.

Album List
Here is the page that the gallery visitors see. The code behind it is mostly the same as the one in the admin pages. For instance the album list, image list and image detail page are pretty much the same code. The main page ( index.php ) also act like a frame just like the admin main page. The big difference is that we need to make these pages more pretty than the admin pages. I added some CSS to make these pages look better but since my web design skill really sucks all i can add is just some greenish look for it.

The other difference is the way the thumbnails are arranged. On the admin pages we list the albums and images in a simple numbered list in a table. But now the thumbnails in the album list and image list are arranged in rows and columns. It look better that way. Coding the script to display the thumbnails in rows and columns is a little bit tricky ( just a little bit ). Here is the code that does it. // ... some code here

echo '<table width="700" border="0" cellspacing="1" cellpadding="2" align="center">'; $colsPerRow = 4; // width of each column in percent $colWidth = (int)(100/$colsPerRow); // ... more code here First we specify how many columns we want in one table row. For this gallery we set the value to four images in one row. Change this value and the code will automatically rearranged the way the thumbnails are displayed. From the value of $colsPerRow we then calculate the value of each column width using this formula : $colWidth = (int)(100/$colsPerRow). So if $colsPerRow is four then the width is 25 ( in percent ) and if we set $colsPerRow value to three then the column width will be 33 ( percent ). The (int) part in the formula works just like the mathematical function floor(). It round down a fraction to it's closest integer. You can rewrite the formula into this : $colWidth = floor(100/$colsPerRow)but i simply prefer using (int). // ... previous code while ($row = mysql_fetch_assoc($result)) { if ($i % $colsPerRow == 0) { // start a new row echo '<tr>'; } $numImages = $row['al_numimage'] > 1 ? $row['al_numimage'] . ' images' : $row['al_numimage'] . ' image'; echo '<td width="' . $colWidth . '%">' . '<a href="index.php?page=list-image&album=' . $row['al_id'] . '">' . '<img src="viewImage.php?type=album&name=' . $row['al_image'] . '" border="0">' . '<br>' . $row['al_name'] . '</a><br />' . $numImages . '</td>'; if ($i % $colsPerRow == $colsPerRow - 1) { // end this row echo '</tr>'; } $i += 1; } // ... more to come In the while loop we start arranging the thumbnails. Since we build the table rows dynamically we must know when to start and end a row. Here we use the modulo of a counter ( $i ) with the number of columns on each row ( $colsPerRow ). If the result is zero then we start a new row by printing <tr> and if the value is $colsPerRow - 1 we end that row by printing </tr>. Making the columns is the easy part. Just print the opening tag ( <td> ) fill it with the thumbnail, album name and how many images in that album then print the closing tag ( </td> ). After the while loop complete there is one more work to be done. That is to check if the last row is full or not. From the snapshot above you can see that we have five albums so the last row only contain one thumbnail. If we just finish the code here and do nothing the layout could be broken ( not always, depending on how smart your browser is ). To mend it we print blank columns to fill in the empty spaces. To find out how many blank columns needed we use the last value of the counter ( $i ) and keep increasing it's value by one until it's modulo with $colsPerRow reach zero, like this ... // ... previous code // print blank columns if ($i % $colsPerRow != 0) { while ($i++ % $colsPerRow != 0) { echo '<td width="' . $colWidth . '%">&nbsp;</td>'; } echo '</tr>'; } echo '</table>'; To make things clearer take a look at the snapshot below. It's the same snapshot of the album list above but this time the table border is visible so you can see that on the second row we have printed three empty columns.

We can now proceed to the image list page

Image List & Detail


Clicking on one of the album icon from the album list page will bring you to this page. The layout is similar to the album list which means the code is also similar so no need to repeat the explanation here.

Click any of the image icons and you will go to the image detail page.

Image Detail
This is the main stage of the gallery which displays the full-sized image and the description along with other trinkets. Take a look at this one page . You may feel that this is the wrong way to design a web page because the image is too large to fit in the screen. But personally i prefer preserving the original art instead of resizing it to fit into the page design Now it's time to bring your attention to that navigation link that you see on top of every page. I'm talking about this one :

This kind of navigation link is widely known as breadcrumbs. Everytime you browse around the albums and images the breadcrumb always display your current location in the gallery. I'm sure you've seen this many times when surfing the web. This simple piece of navigation is very convenient for the visitors to move around the gallery, moving back from the image detail page to the image list or the album list. The function needed to display the breadcrumb is called showBreadcrumb(). You can find this function in library/functions.php. Here's the function code : Source code : library/functions.phps function showBreadcrumb() { if (isset($_GET['album'])) { $album = $_GET['album']; $sql = "SELECT al_name FROM tbl_album WHERE al_id = $album"; $result = mysql_query($sql) or die('Error, get album name failed. ' . mysql_error()); $row = mysql_fetch_assoc($result); echo ' &gt; <a href="index.php?page=list-image&album=' . $album . '">' . $row['al_name'] . '</a>';

if (isset($_GET['image'])) { $image = $_GET['image']; $sql = "SELECT im_title FROM tbl_image WHERE im_id = $image"; $result = mysql_query($sql) or die('Error, get image name failed. ' . mysql_error()); $row = mysql_fetch_assoc($result); echo ' &gt; <a href="index.php?page=image-detail&album=' . $album . '&image=' . $image . '">' . $row['im_title'] . '</a>'; } } } The first if statement check if there's an album id in the page url. If an album id is found we create a link to the album. The next if block check if an image id is available in the url and if we found one we create a link to the image ( which is the currently displayed page ) There's another navigation means in that page. If you scroll down the page you can see the next and previous link. Using this link you can navigate from one image to another that are still in one album. To find the previous image we search the database for one image in the same album as current image where the id is less than the current image id. // ... above here is the sql query to fetch the image detail. // $image contain the image's information $image = mysql_fetch_assoc($result); // set the initial value for previous and next image id $prev = $next = 0; // get the previous image $sql = "SELECT im_id FROM tbl_image WHERE im_id < $imageId AND im_album_id = {$image['im_album_id']} ORDER BY im_id DESC LIMIT 0, 1"; $result = mysql_query($sql) or die('Error, get image info failed. ' . mysql_error()); if (mysql_num_rows($result) > 0) { $row = mysql_fetch_assoc($result); $prev = $row['im_id']; } // ... the code to get the next image I highlighted the ORDER BY clause in the above code because it is really important. We really need it since there is no guarantee that the database will store the records in orderly fashion. For example, after you update an image the internal order of the records in the database would change so without the ORDER BY clause we get an image which id is less than the current image id BUT it may not be the closest one. So if the current image id is 20 and there are three other images whose id are 15, 17 and 18 then without the ORDER BY clause the sql query may return any one of those ids while the one we actually want is image id 18. Using the ORDER BY clause the query will return the images in descending order ( 18, 17, 15 ). And because we add the LIMIT clause the query will return only one record. The one containing image id 18. The following code is the one to fetch the next image. For this one we just need to find an image in the same album as current image where the id is greater than the current image id. Note that in this code the order clause use ASC instead of DESC so that the returned record will be sorted in ascending order. // ... the code to get previous image // get the next image $sql = "SELECT im_id FROM tbl_image WHERE im_id > $imageId AND im_album_id = {$image['im_album_id']} ORDER BY im_id ASC LIMIT 0, 1"; $result = mysql_query($sql) or die('Error, get image info failed. ' . mysql_error()); if (mysql_num_rows($result) > 0) { $row = mysql_fetch_assoc($result); $next = $row['im_id']; }

This is the end of the image gallery tutorial. To download the code for this image gallery click here . Be sure to modify the configuration file before running the script.

17. Finding Web Hosting For PHP And MySQL


There are a lot of things to consider when choosing a web hosting company. But one thing for sure is that price is no longer important. Web hosting is a very competitive field so the price just keeps getting lower and lower. It is so easy to find cheap web hosting for PHP and MySQL The important things to consider when choosing PHP and MySQL web hosting are : 1. PHP and MySQL versions If a web hosting company say that they support PHP 4 make sure it's the latest version not the 4.0.1 version. Same thing for MySQL but with an extra precaution. Some webhosting companty only support MyISAM tables, so if you need InnoDB make sure you ask if it's available. 2. Specific setting / feature For example, you want to create a PHP script which change a file's permission using chmod(). Guess what, if PHP is run as the server your code won't work and there is nothing you can do about it. This exact thing happen to me with this website. I didn't foresee that i would make such application. Anyway just try imagining what you want to do with your web site and if you are uncertain whether a web host will support a feature, just ask. 3. Connection speed All web hosting company claim that they have fast connection speed, but you have to test it to believe it. Use NetMechanic's free service to test the web host company's homepage. If the result is good then the claim is most likely true. 4. Data transfer When you just started a website it doesn't matter much. But as your website grows you have to make sure you have enough bandwidth. One or two gigabytes (Gb) per month is more than enough for most web sites 5. Access to raw log file and online statistics (log file analyzer) If you are serious in building a website, for commercial purposes for example, this is very critical. You can discover a lot of information from log files like the keywords used to find your website, most visited pages, peak times etc. 6. Storage space Measure your own website, start small but leave room for expansion. For a small website 15 megabytes is plenty. 7. Customer support They have to be there when you need them, period. Try asking some questions before you decide to go with a hosting company. If it takes more than 24 hours for them to reply then find another host. For those of you who just want to experiment you can use MySQL PHP free hosting. There are probably hundreds of them out there, you just need to pick one. They usually place banners or other kinds of advertising on you web page and most are slow. Anyway you can't expect much from free services. Actually even if you just experimenting with PHP and MySQL it's better to have you own web site. Free hosting usually have lots of restriction so you really can't do much experiment like opening a socket connection and stuff like that. There's this one website that rank ten PHP MySQL website hosting company based on price, quality, performance and features. If you need a second opinion you can go there. A bit off topic here. If you intend to build an e-business (for yourself or for a client) instead of just a website you should consider about Site Build It. It's an all-in-one site-building, site-hosting, and site-marketing service. It even outperformed Microsoft's bCentral and Yahoo Web Hosting Pro. The only drawback is that the HTML templates provided look a bit lame. But since now you can upload your own template i guess it's not a big deal anymore.

18. Freelance PHP MySQL Jobs


As far as i know Scriptlance is one of the best place to find freelance programming jobs. Each day you can find about 50 new job postings. Most of the jobs will require advance knowledge on PHP and MySQL like creating a dating website, car rental website, or a shopping cart but some are quite easy like creating a site counter and signup form. When you are new to Scriptlance it's better if you stick to find these easy jobs first and try to get good reviews. As you go learn how to do the more difficult ones. Check out the job (project) descriptions just to see what kind of jobs in demand and keep it in a list. Because there may be similar jobs in the future you should create a draft on the design (algorithm, database, process, etc). That way when you get enough experience and you see a similar job posted you can complete it faster If you want to do some freelance jobs here's a little checklist that might help :

Get a clear description of the project. Ask whenever you are unsure about something. Create a demo /
mockup whenever possible so you can be sure that you and your client are talking about the same thing.

Can you do it? Seriously, can you complete each and every features requested? Your client won't be
happy if you say you can complete the job but fail to do so. One quick way to be sure is to make a prototype before even bidding the project then propose it to your (future) client to see if it is what she expected.

Don't be too optimistic. If you think you can complete a job in one day make sure you can do it in one
day. This include all the testing and bug fixing.

Can you accept the payment? Some will want to pay using PayPal if you can accept PayPal it's no
problem but if you can't maybe you should consider finding another project.

Ask for the PHP and MySQL version used by your client and develop the project using the same
version. This will reduce the risk of creating buggy scripts just because your version and your client's version is different

Test and retest. Be ready to fix bugs. All software have bugs, but make sure you are ready to fix them when it's found.
Your clients will certainly expect a prompt response so give it to them. Even if your script is buggy but if you are prompt in responding and fixing it you can get good reviews.

KYOB
That's short for Kick Your Own Butt ! Freelancing requires hard discipline and because there's no boss or supervisor breathing on your neck it can be hard to feel that you are working. Sure, you still have deadlines, but the client is probably on the other side of the planet and most likely you will never see her face to face. It just doesn't feel the same as an ordinary job. KYOB is probably the most important ability you must master if you really want to be a successful freelancer. If you have the time try reading this e-book : The Webmaster Business Masters Course ( it's free ). It's a bit long ( 61 pages ) but pretty good.

19. Questions and Answers


3. Problem when using mail() on windows machine I tried the mail() function in the example, but i face some problems when sending mail using PHP. Do I need to set up an SMTP server in my PC first? I am using PHP Triad 2-2-1 because it is a full package of PHP software,including the apache, mysqladmin and php. Answer If you want to send email using php on windows machine you will need to install a mailserver. Unlike unix or linux machines windows does not come with it's own mail server. You could search google for "free windows mail server". I used to use argosoft mail server for sending email from windows and it works. You could check their website here : http://www.argosoft.com/rootpages/MailServer/Default.aspx Connecting to mysql database on a free server 4. I want to know how to connect to a mysql database on a free server. What should i change localhost to ? Their ftp server address ? Answer Not the ftp server address but the mysql server address. When you open an account on a free hosting company they usually tell you which mysql server available for ther users. Check the confirmation email they sent when you signup. They might mention about the mysql server name in there. If you can't find it there try login to your account on the free host and look around for the for mysql. The server name is usually something like : mysql345.somefreehost.com. 6. How to get file extension using php

I want to extract the file extentions from a file name. How do i do that from php? Answer There are several ways to do that. First is using the combination of strrpos() and substr() function like this : $ext = substr($fileName, strrpos($fileName, '.') + 1); For example, if $fileName is my-new-house.jpg then strrpos($fileName, '.') will return the last location a dot character in $fileName which is 15. So substr($fileName, strrpos($fileName, '.') + 1) equals to substr($fileName, 16) which return 'jpg' The second is using strrchr() and substr() : $ext = substr(strrchr($fileName, '.'), 1); strrchr($fileName) returns '.jpg' so substr(strrchr($fileName, '.'), 1) equals to substr('.jpg', 1) which returns 'jpg'

7. How to format numbers in php How do i format number using php? For example if i have 123456 and i want to print it as 123.456 or 123,456.00 what functions should i use

Answer PHP's solution to formatting number is the number_format() function. This function take 4 arguments but only the first is mandatory. The arguments are : 1. The number itself 2. Decimals 3. Decimal points 4. Thousand separators Here are some examples : 1. number_format(123456) yield 123,456 2. number_format(123456, 2) yield 123,456.00 3. number_format(123456, 3) yield 123,456.000 4. number_format(123.456, 2) yield 123.46 5. number_format(123.456, 3) yield 123.456 6. number_format(123456, 2, ',', '.') yield 123.456,00 7. number_format(123456, 2, '.', ',') yield 123,456.00 8. number_format(123456.7, 2, '.', ',') yield 123,456.70

Note : this function CANNOT accept three arguments. Only one, two, or four. That means something like number_format(123456, 2, ',') is not a valid function call

8. How to disable mysql warning I have a PHP - MySQL related question. Sorry if you mention this later in the tutorial , i am not there yet. Okay: $conn = mysql_connect($dbhost, $dbuser, $dbpass) or die('The Server seems to be down');

Lets pretend that the connection failed, it will report something like this: Warning: mysql_connect() [function.mysql-connect]: Access denied for user 'root'@'localhost' (using password: YES) in C:\Program Files\Apache Group\Apache2\htdocs\prev0~.php on line 10 The Server seems to be down Is there anyway of doing it so it does not show the actual Warning: mysql.... and ONLY shows "The Server seems to be down" Answer You can force mysql_connect() to be quiet by using '@' like this : @$conn = mysql_connect($dbhost, $dbuser, $dbpass) or die('The Server seems to be down'); or you can directly tell php to 'shut up' when it found any errors by placing this code at the beginning of your script : error_reporting(0);

9. Error about loading the header I'm trying to get your tutorial to work, but seem to have a problem : after I click the 'sign guestbook' the page loads blank, in otherwords I dont see the form or the records, even though the entry is saved to the db ! What I noticed is that the line header('Location: ' . $_SERVER['REQUEST_URI']); I had to comment otherwise I get an error about loading the header. I am running this on the localhost. Otherwise this is a real great tutorial. Answer The error about sending header is usually because there's an extra space or newline at the end of config.php file. PHP always complain if there's already an output before sending headers. Check your config.php file to see if there's any extra spaces or newlines before the opening php tags ( ) The code still work if you remove the redirect code. I only add the redirection cod to prevent multiple form submission if the user refresh the page.

10. Upload & Download limited to 64 kilobytes I recently made an aplication to control files in a organization (thanks for the upload and download blob files with php and mysql). The only question or issue that i have, is that .doc files or word files, are save ok in the db, but when i download them, they appear to size 64k (any file 64k) and when i open them it send me an error. Would you know what is happening here? i would apreciate very much you help. Thank you Answer The old file upload tutorial is using BLOB data type to store the files. It is limited to store up to 64 kilobytes of data so the new tutorial is using MEDIUMBLOB so we can store larger files ( up to 16 megabytes )

21. Shopping Cart Tutorial Introduction


The online shop we're makin here is a basic one without any sophisticated features and stuff. The shop has admin pages ( where the shop admin can create categories, add products, etc ) and the shopper pages ( a.k.a the shop itself ) where all the shopping process takes place. You will learn more about them in subsequent pages. By the way, i did call this tutorial as a shopping cart tutorial but actually we're building an online shop ( a really simple one ). The shopping cart is just part of the shop. But because the term 'shopping cart' is already common to define an online shop solution i just use it instead of naming this site a 'PHP MySQL Online Shop Tutorial'. I have to assume that you already know about PHP and MySQL so i won't explain every code in detail. The codes are not too complicated though, i'm sure you can understand it. I suggest you download the code first so you can run it on your computer. That way it's easier for you to understand this tutorial If you don't want to download the code that's fine but make sure you take a look at the demo site, this is what our shopping cart willl look like. After you browse around you will see that the basic flow of our shop is : 1. 2. 3. 4. 5. 6. A customer visit the site She browse the pages, clicking her way between categories View the product details that she found interesting Add products to shopping cart Checkout ( entering the shipping address, payment info ) Leave ( hopefully to return another time )

Nothing complex here. The customer doesn't need to register for an account. She just buy then leave.

Features Okay, here are the shop features ( or maybe i should call this restrictions ) 1. Flat shipping cost. No complex shipping calculation for this shop right now and i don't have any plan to change this in near future. Payment options including COD ( cash on delivery ) and Paypal For now this shop can only handle COD and payment through Paypal IPN. The reason i pick paypal is because they provide excellent resource for developers so i can test the payment process easily. Configurable image and thumbnail size You can restrict the product image width from the config file. You can also set the thumbnail width you want for all product images that you upload.

2.

3.

File Organization The files in our shop will be organized like this :

The plaincart/library directory contain :

config.php : this is the main configuration file for our shop category-functions.php :functions required for fetching the categories product-functions.php : contain product related functions cart-functions.php : shopping cart specific functions checkout-functions.php : checkout processes are in here common.php : common functions required for the shop and admin pages database.php : contain the database abstraction functions

The plaincart/include contain :

header.php The shop common header. top.php You can place your shop banner here. footer.php Common footer, display the shop address, phone number and email. You can add more information here when needed. shop.css Style sheet file for our shop leftNav.php The left navigation you see on the shop categoryList.php Show the top categories we have productList.php Show the products in certain category productDetail.php You know what this is for, right ? miniCart.php Shown on the right portion of the shop pages, it shows the products in the shoping cart. shippingAndPaymentInfo.php The form to enter shipping and payment info ( step 1 of checkout ) checkoutConfirmation.php Show the order items, shipping & payment info ( step 2 of checkout )

The plaincart/include/paypal directory contain :

paypal.inc.php The configuration file for the paypal payment module ipn.php The script that process payment verification payment.php Contain the form that submit the payment information from this website to paypal website

The plaincart/admin folder will contain all the admin files.You can see that admin folder also contain include and library folder. These will contain specific library files for the admin pages All images required in our shop will be put in plaincart/images directory. The category and product images are put in the category and product sub-folder respectively. The Requirements For this tutorial i'm using these :

Apache 2 PHP 4.3.10 with GD ( graphics library ) support ( you can also use lower version but >= 4.3.7 ) MySQL 4

If you don't have these ready check out this tutorial to install them : Installing Apache PHP & MySQL

What Configurations You Will Need Database Configuration When you install the shopping cart script you will need to modify library/config.php. You need to change the database connection info ( database host, username, password and name ) according to your own configurations. Enabling GD Support The next thing you may need to do is to enable the GD support. This is usually enabled by default by web hosting company but in case you test it on your own computer you may need to enable it manually. First, open the php.ini file using a text editor ( notepad is okay ) and search for this line of code : extension=php_gd2.dll If you see that code preceded by a semicolon ( ; ) that means GD library is not enabled yet. Remove the semicolon to enable GD and then restart the web server ( apache ) for the changes to take effect.

I really hope this shopping cart tutorial is useful for you. Now to take the first step, let's start with the database design

Shopping Cart Database Design


The database design for our shopping cart is quite simple. Below is the summary of what tables we need for this shopping cart plus the short description of each table. You can see the complete SQL needed to build the database here

Table Name tbl_category tbl_product tbl_cart tbl_order tbl_order_item tbl_user tbl_shop_config

Description Storing all product categories The products ( what else ) When the shopper decided to put an item into the shopping cart we'll add the item here This is where we save all orders The items ordered Store all shop admin user account Contain the shop configuration like name, address, phone number, email, etc

The ER ( Entity Relationship ) diagram is shown below.

Now, let's take a better look at each table tbl_category This table store the product categories. From the ER diagram you can see that our current database design enables a category to have a child category and for the child category to have another child category and so on. But for this tutorial we make a restriction that the category will only two level deep like this "Top Category > Manga > Naruto". The reason is to reduce the number of clicks required by a visitor when browsing a category. Another rule is that a product can only be added on the second level category. For example if we have this category structure : Top Category > Manga > Naruto then we can only add a product in "Naruto", not in "Manga". The top level categories will not contain any products and a product can only belong to one category. tbl_product In this table we store the product's name, category id, description, image and thumbnail. For now a product can only have one image. It may not be enough if you want to show a picture of you product from multiple angles so i plan to improve this on future version. When adding a product image in the admin page we don't need to upload the thumbnail too. The script will generate the thumbnail from the main image. The thumbnail size is defined in library/config.php ( THUMBNAIL_WIDTH ) and currently it is set to 75 pixels.

tbl_cart

This table will store all items currently put by the customer. Here we have ct_session_id to save the id of a shopping session. We will explore this further when adding a product to shopping cart tbl_order Finally when the customer finally place the order, we add the new order in this table. The shipping and payment information that the customer provided during checkout are alos saved in this table including the shipping cost. For the order id i decided to use an auto increment number starting from 1001. Why start at 1001 ? Because an order id looks ugly ( at least for me ^^ ) if it' s too short like 1, 2 or 3 so starting the order id from 1001 seems to be a good idea for me. To make the order id start from 1001 we use the following sql : CREATE TABLE tbl_order ( id int(10) unsigned NOT NULL auto_increment, date datetime default NULL, last_update datetime NOT NULL default '0000-00-00 00:00:00', status enum('New', 'Paid', 'Shipped','Completed','Cancelled') NOT NULL default 'New', memo varchar(255) NOT NULL default '', shipping_first_name varchar(50) NOT NULL default '', shipping_last_name varchar(50) NOT NULL default '', shipping_address1 varchar(100) NOT NULL default '', shipping_address2 varchar(100) NOT NULL default '', shipping_phone varchar(32) NOT NULL default '', shipping_city varchar(100) NOT NULL default '', shipping_state varchar(32) NOT NULL default '', shipping_postal_code varchar(10) NOT NULL default '', shipping_cost decimal(5,2) default '0.00', payment_first_name varchar(50) NOT NULL default '', payment_last_name varchar(50) NOT NULL default '', payment_address1 varchar(100) NOT NULL default '', payment_address2 varchar(100) NOT NULL default '', payment_phone varchar(32) NOT NULL default '', payment_city varchar(100) NOT NULL default '', payment_state varchar(32) NOT NULL default '', payment_postal_code varchar(10) NOT NULL default '', PRIMARY KEY ( id) ) TYPE=MyISAM AUTO_INCREMENT=1001 ; You see, we just need to add AUTO_INCREMENT = 1001 right after the create definition. tbl_order_item All ordered items are put here. We simply copy the items from the cart table when the customer place the order. tbl_shop_config This table store the shop information. For now it only have the shop name, address, phone number, contact email address, shipping cost, the currency used in the shop and a flag whether we want to receive an email whenever a customer place an order. tbl_user This table save all the user or admin account. Currently all user is an admin and all can do everything to the shop. I'm planning to add permission level so one admin can do everything, while the other user can only add / update product, manage orders, etc. By the way, we will be using indexes on the tables to speed up queries. As a matter of fact whatever application you make using indexes is a good idea because it can improve the database query performance.

Okay, next we talk about the database abstraction. It's not a difficult stuff so you can skim read it if you like.

Database Abstraction
For our shopping cart we will use a simple database abstraction library. Although this tutorial only use MySQL but using an abstraction layer is still a good thing. We do this just in sow we can easily migrate to another database. In case you don't know, a database abstraction is simply making our own interfaces ( functions ) to call the database function provided by PHP. This way if we change to another database or if the PHP function itself change we just need to modify one file ( database.php ) instead of modifying all our source code. The database functions is stored in library/database.php. You can see the content below

<?php

require_once 'config.php'; $dbConn = mysql_connect ($dbHost, $dbUser, $dbPass) or die ('MySQL connect failed. ' . mysql_error()); mysql_select_db($dbName) or die('Cannot select database. ' . mysql_error()); function dbQuery($sql) { return mysql_query($sql) or die('Query failed. ' . mysql_error()); } function dbAffectedRows() { global $dbConn; return mysql_affected_rows($dbConn); } function dbFetchArray($result, $resultType = MYSQL_NUM) { return mysql_fetch_array($result, $resultType); } function dbFetchAssoc($result) { return mysql_fetch_assoc($result); } function dbFetchRow($result) { return mysql_fetch_row($result); } function dbFreeResult($result) { return mysql_free_result($result); } function dbNumRows($result) { return mysql_num_rows($result); } function dbSelect($dbName) { return mysql_select_db($dbName); } ?>

How to use it ? Below you can see an example on how to use this database abstration library. The first one is using PHP database function directly : <?php require_once 'config.php'; $conn = mysql_connect($dbHost, $dbUser, $dbPass); mysql_select_db($dbName); $sql = "SELECT * FROM tbl_product ORDER BY pd_name"; $result = mysql_query($sql); $products = array(); while ($row = mysql_fetch_assoc($result)) { $products[] = $row; } ?> And the second one is using our database abstraction. We don't have to initiate the connection this time because it is done from database.php : <?php require_once 'config.php'; require_once 'database.php'; $sql = "SELECT * FROM tbl_product

ORDER BY pd_name"; $result = dbQuery($sql); $products = array(); while ($row = dbFetchAssoc(&$result)) { $products[] = $row; } ?> As you can see, using database abstraction isn't so hard at all. Now that we have taken care the database issues we start building the admin pages.

Admin Control Panel


Our shopping cart admin page consist of the following :

Category

Add Category Add a new category. View Category List all the category we have. We can also see all the child categories and show many products in each category Modify Category Update a category information, the name, description and image Delete Category Remove a category. All products in it will be set to have cat_id = 0.

Product

Add Product Insert an item into our store. We also need to supply the product image and we'll create a thumbnail automatically from this image View Product View all the products we have. Since our online shop can have many products we can view the products grouped by category. Modify Product Modify product information. We can also remove the product image from this page Delete Product Remove a product from the shop


Order

View Orders Here we can see all the orders we have and their status. When you click the "Order" link on the left navigation you will go straight to the "Paid" orders. The reason is so you can respond immediately upon your customers that already paid for their purchase. Modify Orders Sometimes a customer might contact us saying that she made the wrong order like specifying the wrong product quantity or simply want her order cancelled so she can repeat the buying process again. This page enables the admin to do such a thing.

Shop Configuration This is where we can set and change our online shop appearance, behaviour and information ( like the shop name, main url, etc ).

Below is what admin main page ( admin/index.php ) look like.

By the way the shopping cart name is PlainCart :-) Each sub-module (category, product, etc) will have similar file structure. They are :

index.php list.php add.php modify.php process<sub-module name>.php

The admin/index.php only serves as a simple display when the admin enters the administrator section. On this page (and all other pages in the admin sections ) we check if the one requesting the file is already logged in or not. This way we can be sure that anyone who plays around with the admin pages are those who have the required permission. All admin pages will be using the same template so they will all look alike. Basically each admin file will set the page title, what javascript to include and the main content. If you want to customize the look of the admin pages you only need to modify the template and the css file ( admin.css ) . Here is the code for template.php Source code : admin/include/template.php <?php if (!defined('WEB_ROOT')) { exit; } $self = WEB_ROOT . 'admin/index.php'; ?> <html> <head> <title><?php echo $pageTitle; ?></title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <link href="<?php echo WEB_ROOT;?>admin/include/admin.css" rel="stylesheet" type="text/css"> <script language="JavaScript" type="text/javascript" src="<?php echo WEB_ROOT;?>library/common.js"></script> <?php $n = count($script); for ($i = 0; $i < $n; $i++) { if ($script[$i] != '') { echo '<script language="JavaScript" type="text/javascript" src="' . WEB_ROOT. 'admin/library/' . $script[$i]. '"></script>'; } } ?> </head> <body> <table width="750" border="0" align="center" cellpadding="0" cellspacing="1" class="graybox"> <tr> <td colspan="2"><img src="<?php echo WEB_ROOT; ?>admin/include/banner-top.gif" width="750" height="75"></td> </tr> <tr> <td width="150" valign="top" class="navArea"><p>&nbsp;</p> <a href="<?php echo WEB_ROOT; ?>admin/" class="leftnav">Home</a> <a href="<?php echo WEB_ROOT; ?>admin/category/" class="leftnav">Category</a> <a href="<?php echo WEB_ROOT; ?>admin/product/" class="leftnav">Product</a> <a href="<?php echo WEB_ROOT; ?>admin/order/?status=New" class="leftnav">Order</a> <a href="<?php echo WEB_ROOT; ?>admin/config/" class="leftnav">Shop Config</a> <a href="<?php echo WEB_ROOT; ?>admin/user/" class="leftnav">User</a>

<a href="<?php echo $self; ?>?logout" class="leftnav">Logout</a> <p>&nbsp;</p> <p>&nbsp;</p> <p>&nbsp;</p> <p>&nbsp;</p></td> <td width="600" valign="top" class="contentArea"><table width="100%" border="0" cellspacing="0" cellpadding="20"> <tr> <td> <?php require_once $content; ?> </td> </tr> </table></td> </tr> </table> <p>&nbsp;</p> <p align="center">Copyright &copy; 2005 - <?php echo date('Y'); ?> <a href="http://www.phpwebcommerce.com"> www.phpwebcommerce.com</a></p> </body> </html> You see the bolded section on the top? All file that is meant to be included from another file will have this right at the beginning of the code. So if we request the file directly like this : plaincart/admin/include/template.php it won't display anything. But first, we have to postpone making the admin pages and create the login page first. Because a shop admin certainly must login before doing anything to the shop. NOTE : When you play with the admin pages demo you will see that any changes you make doesn't have any effect at all. This is because i've commented some of the code in the demo. I do this just to make sure all the settings, products, and categories stay the same. The code for download, however, are not commented. So when you install it on your server you can make any changes as you wish

Admin Login
All user account is saved in tbl_user. For simplicity the table will only contain the bare necessities such as user id and password. You can add more column if you want to. This is how the login works 1. 2. 3. 4. The admin enter it's username and password The script check whether that username and password combination do exist in the database If it is set the session then go the admin main page If it's not then show an error message

Below is the login page screenshot :

The login function is called doLogin() and it's located in admin/library/functions.php Source code : admin/library/functions.php function doLogin() { // if we found an error save the error message in this variable $errorMessage = ''; $userName = $_POST['txtUserName']; $password = $_POST['txtPassword']; // first, make sure the username & password are not empty if ($userName == '') { $errorMessage = 'You must enter your username'; } else if ($password == '') { $errorMessage = 'You must enter the password'; } else { // check the database and see if the username and // password combo do match $sql = "SELECT user_id FROM tbl_user WHERE user_name = '$userName' AND

user_password = PASSWORD('$password')"; $result = dbQuery($sql); if (dbNumRows($result) == 1) { $row = dbFetchAssoc($result); $_SESSION['plaincart_user_id'] = $row['user_id']; // log the time when the user last login $sql = "UPDATE tbl_user SET user_last_login = NOW() WHERE user_id = '{$row['user_id']}'"; dbQuery($sql); now that the user is verified we move on to the next page if the user had been in the admin pages before we move to the last page visited (isset($_SESSION['login_return_url'])) { header('Location: ' . $_SESSION['login_return_url']); exit; } else { header('Location: index.php'); exit; } } else { $errorMessage = 'Wrong username or password'; } } return $errorMessage; } If the login is successful this function will set the session variable $_SESSION['plaincart_user_id']. All admin pages will check for this session id using the checkUser() function. If the session id is not found then the function will set a redirection to the login page. The checkUser() function look like this : Source code : admin/library/functions.php function checkUser() { if (!isset($_SESSION['plaincart_user_id'])) { header('Location: ' . WEB_ROOT . 'admin/login.php'); } if (isset($_GET['logout'])) { doLogout(); } } You see that if $_SESSION['plaincart_user_id'] is not set we just redirect to the login page. Very simple right? Another thing that this function check is if there's a 'logout' in the query string. If it is then we call the doLogout() function which will remove the session id. Source code : admin/library/functions.php function doLogout() { if (isset($_SESSION['plaincart_user_id'])) { unset($_SESSION['plaincart_user_id']); session_unregister('plaincart_user_id'); } header('Location: login.php'); } Next we start making the category pages // // // if

Admin - View Category


All the product categories for the online shop are listed here. The sql query for it is pretty simple. We just select category id, parent id and name and using a while loop we show the category one by one. Below is the screenshot for the category list page. You ca see that on each row there's a Modify link and Delete link. Clicking on the Modify link will take you to the category modification page ( admin/category/modify.php ) where you can update the category name, description and image. Clicking on the Delete link will pop a javascript confirmation box asking whether you are sure to delete the category. Using a confirmation is a must when you want to delete something. This will prevent stupid accident where you unknowingly click on the delete link and suddenly the category disappear before you even realize what's going on.

Take a look at the code snippet below : Source code : admin/category/list.php <?php // ... $catId = (isset($_GET['catId']) && (int)$_GET['catId'] >= 0) ? (int)$_GET['catId'] : 0; //... ?>

When the page loads we check for the existence of catId ( category id ) in the query string. This category id is then used as the paramater for the javascript function addCategory() . When you click on the 'Add Category' button the parent id will be sent to category/add.php. Go look at the source code and scroll to the bottom you will see this code : Source code : admin/category/list.php <input name="btnAddCategory" type="button" id="btnAddCategory" value="Add Category" class="box" onClick="addCategory(<?php echo $catId; ?>)">

The addCategory() function is defined in admin/library/category.js. It simply perform a redirect to show the add category page. More detail on adding a category can be found on the next page.

Admin - Add Category


Here the admin can add new product category for the online shop. The information we need are the category name, description, image. Both the name and description are mandatory but the image is not. If we don't have the category image we can leave the field blank. Of course it's not recommended because when the customer come to the shop she will see the default image. The category description here will not be shown anywhere on the shop. It's only purpose is to let the shop owner / admin to know what the category is all about. Take a look at the snapshot below, nothing fancy right?

Take a look at the form source code. The form has a hidden variable called hidParentId. The value is set from category/list.php as explained on the previous page. Go look at the source code and scroll to the bottom you will see this code : <input name="btnAddCategory" type="button" id="btnAddCategory" value="Add Category" class="box" onClick="addCategory(<?php echo $catId; ?>)">

When you submit the form the process then handed to processCategory.php. All kind of category processing ( add, modify, delete ) are done in this file. On top of the script there's a simple switch to call the appropriate function based on the action required. $action = isset($_GET['action']) ? $_GET['action'] : ''; switch ($action) { case 'addCategory' : addCategory(); break; case 'modifyCategory' : modifyCategory(); break; case 'deleteCategory' : deleteCategory(); break; case 'deleteImage' : deleteImage(); break; default : // if action is not defined or unknown // move to main category page header('Location: index.php'); }

On the add category form the form action is set as processCategory.php?action=addCategory so if you look at the code above the script will call addCategory();. If no action is defined we just redirect to category main page. When saving the product image there is a possibility of name conflict. It may seem weird for two categories to have the same image name, but in some cases it can happen. To avoid such conflict we will generate a new name for each category image we upload using the combination of rand(), time() and md5() functions like this : // get the image extension $ext = substr(strrchr($image['name'], "."), 1); // generate a random new file name to avoid name conflict $imagePath = md5(rand() * time()) . ".$ext"; The image name wil then become something like 6c444ed816ce251d610c25154dc28462.jpg. Now it's almost impossible for us to ever hit the name conflict problem. We will use the same name generation for the product image and thumbnail. How does it work ? The time() function will return the number of seconds elapsed since the beginning of ( computer ) time which is January 1, 1970. Using rand() function we get a random value less or equal to the number of seconds. We need to use rand() because this shopping cart can have more than one admin. If two admins happen to submit the form at the same second the result of time() will be the same. As the final step md5() use the random value and return the hash ( a string with 32 characters ). If you feel that using 32 characters for a filename is too much you can use substr() function to cut it like this :

// get the image extension $ext = substr(strrchr($image['name'], "."), 1); // generate a random new file name to avoid name conflict $imagePath = substr(md5(rand() * time()), 0, 10) . ".$ext"; The code above will use only the first ten characters as the file name. Next is the modify category page.

Admin - Edit Category


This page is where you can modify a category information. You can see that the form is just a lame copy from add.php. The difference is that in this page we need to fetch the category information first so we can show it in the input boxes.

Another difference is that in this form we also display the category image. If you change the category image then the old image will be deleted from the server and the new image is uploaded. Take a look a the code below. To get the category info from database we need the category id from the query string. If $_GET['catId'] is not present or empty we just redirect to index.php. If it's present and not empty we fetch the category info.

Source code : admin/category/modify.php <?php if (!defined('WEB_ROOT')) { exit; } // make sure a category id exists if (isset($_GET['catId']) && (int)$_GET['catId'] > 0) { $catId = (int)$_GET['catId']; } else { header('Location:index.php'); } $sql = "SELECT cat_id, cat_name, cat_description, cat_image FROM tbl_category WHERE cat_id = $catId"; $result =& dbQuery($sql); $row =& dbFetchAssoc(&$result); extract($row); // ... put the form down here ?> On the screenshot you can see that we next to the category image we have a delete link. Clicking on the link will call the javascript function deleteImage(). This function will pop a confirmation box and if you confirm the deletion the function will redirect you to processCategory.php where all category related process is taken care of. Below is the code that perform the image deletion Source code : admin/category/processCategory.php function deleteImage()

{ if (isset($_GET['catId']) && (int)$_GET['catId'] > 0) { $catId = (int)$_GET['catId']; } else { header('Location: index.php'); } _deleteImage($catId); // update the image name in the database $sql = "UPDATE tbl_category SET cat_image = '' WHERE cat_id = $catId"; dbQuery($sql); header("Location: index.php?view=modify&catId=$catId"); } To delete the image from the server the deleteImage() function calls _deleteImage(). Please excuse this lame function naming. I just cant' find other name that fit perfectly for this function. After deleting the image we update the category information in database. We only need to set cat_image to an empty string and we're done. The final thing that deleteImage() do is redirect back to the category modification page. We don't redirect to category listing page because the admin may still want to modify the category further.

Admin - Delete Category


When you decide that a category is no longer needed you can remove it from the database. To delete a category go to the category listing page and click on the 'delete' link on the category you wish to delete Deleting a category will delete all product in that category and in all it's children. For example if you delete the "Manga" category than alll product in "Naruto" and "HunterxHunter" will also be deleted. The function used to delete category is called deleteCategory() and it's located in admin/category/processCategory.php The deletion process is like this : 1. 2. 3. Update the cat_id for all products in that category to zero Remove the category image ( if exist ) Delete the category from database

Pretty easy huh? I don't think we need to dive into the source code here because it is quite simple. Just take a look at the code and you will understand Okay, now the category stuff is done we start playing with the next sub modul, the product pages.

Admin - Add Product


Adding a product for the online shop is a straightforward process. Just enter the product information and hit the 'Add Product' button. When adding a product we will require these information :

Category Product name Description Price Quantity in stock Image

All is mandatory except for the image. We can add the product image later.The add product form look like this :

Not much difference from the add category form. We just have more input box. On top of the form you can see the category combo box. We build this so that you can only select the second level category. This is to ensure that all product are added on the second level category and not put in the top level category by mistake. The one responsible to build the list is the buildCategoryOptions() in admin/library/function.php. Below is the snippet for the code that build the combo box. If you happen to click the "Add Product" button while viewing the product list in a category you can see that the category list is preselected to the right category. When the function is building the list options it always check if the current category id is the same as the category id in the function parameter. Source code : admin/library/functions.php <?php // ... some code to fetch the categories from database // build combo box options $list = ''; foreach ($categories as $key => $value) { $name = $value['name']; $children = $value['children']; $list .= "<optgroup label=\"$name\">"; foreach ($children as $child) { $list .= "<option value=\"{$child['id']}\""; if ($child['id'] == $catId) { $list.= " selected"; } $list .= ">{$child['name']}</option>\r\n"; } $list .= "</optgroup>"; } // ... more code here ?> The product image you need to supply is the large size product image which will be shown in the product detail page. The script will generate a thumbnail for it to be shown in the product browsing page. We define the maximum image size and the thumbnail size in config php. We need to restrict the image size so it won't destroy the site layout. Imagine if the image is 1000 pixels wide and 2000 pixels high. It will make the product detail page look awful. Image resizing can be turned on or off. If you set LIMIT_PRODUCT_WIDTH to false on config.php the script will just upload the image without worying about it's size. It's not recommended though. Please note that the image resizing function can only handle jpeg and gif image. That function was actually taken from the php

documentation and i don't know how to improve it yet.

Admin - View Product


This page list all the products we have. We can see all products or just products from certain category. From this page we can see the product detail , add new product, modify and delete. Below is what the page look like. The table shows the product name, thumbnail, category, and modify and delete link. We show the product image if it exist. In case we haven't supply the image the default image is shown instead.

There's a javascript function called viewProduct() attached to the combo box on the top right portion of the page. If you select a category then the page will show product list only from that category. For example if you choose "Naruto" the this page will look like this :

If you wish to view all products again just choose "All Category" from the combo box The code for this page is in admin/product/list.php. It's a very simple page really. It just perform a simple SELECT query and loop through the result to print the contents. If the products returned from the query is more than five we print the paging links to navigate from one result page to another. The code snippet below shows how the paging is done. Source code : admin/product/list.php <?php if (!defined('WEB_ROOT')) { exit; }

if (isset($_GET['catId']) && (int)$_GET['catId'] > 0) { $catId = (int)$_GET['catId']; $sql2 = " AND p.cat_id = $catId"; $queryString = "catId=$catId"; } else { $catId = 0; $sql2 = ''; $queryString = ''; } // for paging // how many rows to show per page $rowsPerPage = 5; $sql = "SELECT pd_id, c.cat_id, cat_name, pd_name, pd_thumbnail FROM tbl_product p, tbl_category c WHERE p.cat_id = c.cat_id $sql2 ORDER BY pd_name"; $result = dbQuery(getPagingQuery($sql, $rowsPerPage)); $pagingLink = getPagingLink($sql, $rowsPerPage, $queryString); $categoryList = buildCategoryOptions($catId); ?> Instead of executing the query directly like this dbQuery($query) we add some paging code first to the sql query by feeding it to getPagingQuery() along with how many results that we want to show on each page. The getPagingQuery() function is located in library/common.php. Here is the code : Source code : library/common.php function getPagingQuery($sql, $itemPerPage = 10) { if (isset($_GET['page']) && (int)$_GET['page'] > 0) { $page = (int)$_GET['page']; } else { $page = 1; } // start fetching from this row number $offset = ($page - 1) * $itemPerPage;

return $sql . " LIMIT $offset, $itemPerPage"; } This function first check the page number. When you click on a paging link there's a page variable embedded in the query string. If the function can't find any page variable on the query string then it just assume that the first page is wanted. Paging on MySQL is done using the LIMIT keyword. The offset is the index where we want to start fetching the result. We also supply how many result that we want . Now after we get the paging query and execute it the next thing we must do is making the page links. This is done by getPagingLink() function. This function is the one responsible for printing the paging link you see on the bottom of the product list. The function only does the followings : 1. 2. 3. 4. Find out how many total results returned by a query Calculate how many pages the results should be split into Determine the first and last page Print page link from first to last.

Somehow i don't feel like explaining the paging process in detail here. If you're interested you can read the full tutorian on pagination here : paging tutorial on www.php-mysql-tutorial.com Next we discuss about adding a new product. When you click on the "Add Product" button you will be taken to the add product screen.

Admin - Edit Product


I don't think i need to explain this because it's the same as modifying a category. We show the form where we can change the product information. Display the image thumbnail if the product has an image and put a delete link right next to it. Here is the form snapshot :

The process of updating the product information is also the same as the category. So we better move on and working on the process of deleting a product.

Admin - Delete Product


This part also don't need much explanation. The process is simple. First we delete any references to this product from tbl_cart and tbl_order_item to maintain data integrity. Then remove the product images and thumbnail and finally remove the product from database. The delete process is like this : 1. Delete all references to this product from tbl_order_item

2. 3. 4.

Delete all references from tbl_cart Delete the product image and thumbnail Delete the product from database

And here is the code for deleteProduct() function that responsible for this process : Source code : admin/product/processProduct.php function deleteProduct() { if (isset($_GET['productId']) && (int)$_GET['productId'] > 0) { $productId = (int)$_GET['productId']; } else { header('Location: index.php'); } // remove any references to this product from // tbl_order_item and tbl_cart $sql = "DELETE FROM tbl_order_item WHERE pd_id = $productId"; dbQuery($sql); $sql = "DELETE FROM tbl_cart WHERE pd_id = $productId"; dbQuery($sql); // get the image name and thumbnail $sql = "SELECT pd_image, pd_thumbnail FROM tbl_product WHERE pd_id = $productId"; $result =& dbQuery($sql); $row =& dbFetchAssoc($result); // remove the product image and thumbnail if ($row['pd_image']) { unlink(SRV_ROOT . 'images/product/' . $row['pd_image']); unlink(SRV_ROOT . 'images/product/' . $row['pd_thumbnail']); } // remove the product from database; $sql = "DELETE FROM tbl_product WHERE pd_id = $productId"; dbQuery($sql); header('Location: index.php'); }

Admin - Order Management


All customer order will be shown here. You can see the orders and take appropriate action. For example all orders initially have the status "New". When you pack the product and ship it to the customer you can then change the order status to "Shipped". The order status are :

New Paid Shipped Completed Cancelled

Let's take a better look at each status New All orders initially have this status Paid An order's status is changed from "New" to "Paid" by the IPN script after completing the payment process. Shipped After we pack the ordered items and ship it we can change the order status to "Shipped" Completed We got the payment, the customer receveived the goods that mean the order is completed Cancelled

In case you see a suspicious order and you feel that it's a fraud you can set the status to "Cancelled". Or in some case a customer calls you and for some reason she ask you to cancel her order. Here is what the order page look like :

Using the combo box on the top right corner we can view the orders with a certain status. Clicking on an order number will take you to the order detail page. On this page we can also modify the order status

Site Configuration
Here we can set some settings which are used sitewide. They are

Online shop information Shop Name Real world address Phone number Contact email Shipping cost Currency A flag whether to send a notification email to admin when a customer place an order

The shop information is shown on the footer ( include/footer.php ). You really shouldn't leave any field blank. Having these information can assure the customers that your online shop is a real business. This can improve customer trust and also improve your credibility and without those two you really can't expect anyone to buy. The currency chooser is simply a drop down box where you can choose between Dollar, Euro, Poundsterling or Yen. Why only four currencies ? Because i'm making the shop using Dreamweaver and the only special characters for currency that i can find are , , and . Plus the dollar sign ( $ ) that makes four currencies.

Under the currency list is the shipping cost. Since this shop use flat shipping cost a simple text box is sufficient. If you plan to offer different shipping cost like depending on the product weight or shipping courier then you really have to change it to suit your need. The last setting is for sending email whenever a new order is placed.The notification email is sent to the address you specify as the contact email ( in the screenshot above it's franky@tomsworkers.com ). If you run a super busy shop it would be better to turn this off otherwise you will be flooded with emails and your mail server might think someone is spamming you.

Admin - User Management

A user of the shop is actually the shop admin itself. Currently all user are granted the permission to do all administration task. In future version i'll modify this so one user can be assigned to specific task such as managing the products or managing the orders, etc. View User List This display just display all user on the shop

Add User The only information needed are the user name and password.

Here is the code for adding a user. Source code : admin/user/processUser.php function addUser() { $userName = $_POST['txtUserName']; $password = $_POST['txtPassword']; // check if the username is taken $sql = "SELECT user_name FROM tbl_user WHERE user_name = '$userName'"; $result =& dbQuery($sql); if (dbNumRows($result) == 1) { header('Location: index.php?view=add&error=' . urlencode('Username already taken. Choose another one')); } else { $sql = "INSERT INTO tbl_user (user_name, user_password, user_regdate) VALUES ('$userName', PASSWORD('$password'), NOW())"; dbQuery($sql); header('Location: index.php'); } } Before adding the new user info we need to check if the username is taken or not. We just query the database looking for that username. If a row is found that mean the username is already used and we set a redirect to go back to the 'add user' page and send an error message. Since the error message is put in the query string we need to use urlencode(). This function is commonly used when passing a string to a url. Using the above example the url will look like this : index.php?view=add&error=Username+already+taken.+Choose+another+one If the username is still available we just insert the new user info to database. You see that in the insert query we use PASSWORD() function. This is a MySQL function that will encrypt the given password.

Modify User Password For simplicity the user name cannot be changed. Only the password can be changed. The function used for modifying the password simply perform an UPDATE query to update the password

Delete User The deleteUser() function just remove a user from database. No extra steps needed.

Shop - Main Page


This page is usually what the visitor see for the first time when she visit our shop. It consist of five parts. On the left is the category browser. The shopper can click here way through categories to find the product. The right side is where we put the mini shopping cart. If the visitor add a product, this mini cart will show the item. At the very top and bottom are the common header an footer. The top area is usually where we put our store logo. For this tutorial the bottom area is used for displaying the store information (address, email, phone )

The center part is the main area. Here we show the product categories and products. The visitor will ( hopefully ) find her way through the shop, find the item she want, put it into the shopping cart and then buy. What we show in the main area depends on the visitor action. When she first arrive to main page she get the category list. If she click on one of the category then we show the product list for that category. And if she click on a product from the list we show the product detail.

Take a look at the code below, it's the code for index.php, the main page Source code : index.php <?php require_once require_once require_once require_once

'library/config.php'; 'library/category-functions.php'; 'library/product-functions.php'; 'library/cart-functions.php';

$_SESSION['shop_return_url'] = $_SERVER['REQUEST_URI']; $catId = (isset($_GET['c']) && $_GET['c'] != '1') ? $_GET['c'] : 0; $pdId = (isset($_GET['p']) && $_GET['p'] != '') ? $_GET['p'] : 0; // ... more code here ?> First we load the required libraries. The config.php file contain all kind of initialization like setting the database connection, defining constants, and it also load the common libraries. I won't explain the detail, just take a look at the file and i'm sure you will understand what it does. Next is category-functions.php. This file contain the several functions, they are :

formatCategories() This function is used by include/leftNav.php. It's job is to generate the category list where the currently selected category will expand and show the children categories ( if it have any ). getCategoryList() Get a list of first level categories ( i.e. the parent id is zero ) getChildCategories() Get all children categories fetchCategories() Get all categories ( first and second level )

After finish loading the libraries we set a session variable $_SESSION['shop_return_url']. The value for this session is the url of the page that we currently see. This variable is used by the shopping cart. When you click on the 'Continue Shopping' button it will redirect to the url pointed by this session variable. Next step is checking the existence of $_GET['c'] ( category id ) and $_GET['p'] ( product id ). If none exist in the query string we show the category list ( categoryList.php ). If only the category id exist then we show all product in that category ( productList.php ). And if the product id also exist we show that product information ( productDetail.php ). Source code : index.php <?php // ... previous code include 'header.php'; ?> <table width="780" border="1" align="center" cellpadding="0" cellspacing="0"> <tr> <td colspan="3"> <?php include 'top.php'; ?> </td> </tr>

<tr valign="top"> <td width="150" height="400" id="leftnav"> <?php include 'leftNav.php'; ?> </td> <td> <?php if ($pdId) { include 'productDetail.php'; } else if ($catId) { include 'productList.php'; } else { include 'categoryList.php'; } ?> </td> <td width="130" align="center"><?php include 'miniCart.php'; ?></td> </tr> </table> <?php include 'footer.php'; ?> As you can see from the above code, the main page doesn't do much. It "out sourced" almost everything. The header, navigation, content, mini cart and the footer. Of course this is a good thing because if we need to modify some part ( like the left navigation ) we can do it wit less risk of messing with the other part. Now, lets take a deeper look at the header file. One important usage of this file is to set the page title. You may have noticed it that when you're browsing the categories and products the page title change accordingly. Setting up a descriptive page title is especially important when you consider SEO ( search engine optimization ). You see, search engine put heavy emphasize on the page title so we better get this one thing right. Here how it works. By default we set the page title as "My Online Shop" but if a product id is detected in the query string we search the database to find the name of the product. Then we use the product's name as the page title. If we can't find any product id in the query string we do another test to see if there's a category id in there. If we found one we fetch the category name from the database and use it as the page title. Here is the code : Source : include/header.php <?php if (!defined('WEB_ROOT')) { exit; } // set the default page title $pageTitle = 'My Online Shop'; if (isset($_GET['p']) && (int)$_GET['p'] > 0) { $pdId = (int)$_GET['p']; $sql = "SELECT pd_name FROM tbl_product WHERE pd_id = $pdId"; $result = dbQuery($sql); $row = dbFetchAssoc($result); $pageTitle = $row['pd_name']; } else if (isset($_GET['c']) && (int)$_GET['c'] > 0) { $catId = (int)$_GET['c']; $sql = "SELECT cat_name FROM tbl_category WHERE cat_id = $catId"; $result = dbQuery($sql); $row = dbFetchAssoc($result); $pageTitle = $row['cat_name']; } ?> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title><?php echo $pageTitle; ?></title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <link href="include/shop.css" rel="stylesheet" type="text/css"> <script language="JavaScript" type="text/javascript" src="library/common.js"></script> </head> <body> Next we explore further about the category browsing ( that navigation links on the left section of the page ).

Shop - Browse Category

On the left side of the shop pages we show the category list. When the customer clicks on a category on the left side she can see all products in that category. The shop main page include this navigation from include/leftNav.php and as mentioned before, the one responsible to get the list of categories for this navigation is the formatCategories() function in library/category-functions.php In summary here is what formatCategories() do : 1. 2. 3. Get all the children categories Get the parent category and other categories on the same level as the parent Keep looping to get the parent's parent until we reach the top level category

This function is actually a bit too complex for handling two level deep categories. However in the future i want this shop to handle categories more than two level deep so i made this function so it can cope with that. Okay, let's not talk about this function too much and move on with the code for leftNav.php Source code : include/leftNav.php <?php if (!defined('WEB_ROOT')) { exit; } // get all categories $categories = fetchCategories(); // format the categories for display $categories = formatCategories($categories, $catId); ?> <ul> <li><a href="<?php echo $_SERVER['PHP_SELF']; ?>">All Category</a></li> <?php foreach ($categories as $category) { extract($category); // now we have $cat_id, $cat_parent_id, $cat_name $level = ($cat_parent_id == 0) ? 1 : 2; $url = $_SERVER['PHP_SELF'] . "?c=$cat_id"; // for second level categories we print extra spaces to give // indentation look if ($level == 2) { $cat_name = '&nbsp; &nbsp; &raquo;&nbsp;' . $cat_name; } // assign id="current" for the currently selected category // this will highlight the category name $listId = ''; if ($cat_id == $catId) { $listId = ' id="current"'; } ?> <li<?php echo $listId; ?>><a href="<?php echo $url; ?>"><?php echo $cat_name; ?></a></li> <?php } ?> </ul>

The categories are printed as unordered lis ( <ul> ). I guess you already know that unordered list usually shows up like this :

list one list two

child 1 child 2

But thanks to CSS ( Cascading Style Sheet ) we can alter how the browser display the list and give them than boxy look. I must admit that i'm a beginner to css and i learn this from projectseven.com, so if you want to know how the css works you can visit the site later. Next up is the product browsing.

Shop - View Product List


When you click one of the category from the left navigation the main content area will show all product contained in that category. For example if you click on Manga then all four products in this category will be shown. Actually "Manga" doesn't have any product. Those product are belong to it's children categories ( Naruto and HunterXHunter).

If you checkout the source code for productList.php you can see that it first call getChildCategories() function so if you browse "Manga" productList.php will show all products in "Manga" ( zero products ) and it's children "Naruto" and "HunterXHunter" ( four products ). Source code : include/productList.php <?php if (!defined('WEB_ROOT')) { exit; } $productsPerRow = 2; $productsPerPage = 4; $children = array_merge(array($catId), getChildCategories(NULL, $catId)); $children = ' (' . implode(', ', $children) . ')'; $sql = "SELECT pd_id, pd_name, pd_price, pd_thumbnail, pd_qty, c.cat_id FROM tbl_product pd, tbl_category c WHERE pd.cat_id = c.cat_id AND pd.cat_id IN $children ORDER BY pd_name"; $result = dbQuery(getPagingQuery($sql, $productsPerPage)); $pagingLink = getPagingLink($sql, $productsPerPage, "c=$catId"); // ... we got more codes here ?>

The rest of this page code should be pretty clear to you. There's a SELECT query and since we also want to use paging we feed the query to getPagingQuery(). If you don't know what getPagingQuery() does you should go back and read the page about product list in admin section. Below is a snapshot of the product listing

On productList.php the products are displayed so one row display three products like shown above. The number of product in one row can be changed. You just need to modify the value of $productsPerRow. For example modifying this value into 2 give this look :

And here is the code that make this happen Source code : include/productList.php <?php // ... previous code $numProduct = dbNumRows($result); $columnWidth = (int)(100 / $productsPerRow); ?> <table width="100%" border="0" cellspacing="0" cellpadding="20"> <?php if ($numProduct > 0 ) { $i = 0; while ($row = dbFetchAssoc($result)) { extract($row); if ($pd_thumbnail) { $pd_thumbnail = WEB_ROOT . 'images/product/' . $pd_thumbnail; } else { $pd_thumbnail = WEB_ROOT . 'images/no-image-small.png'; } if ($i % $productsPerRow == 0) { echo '<tr>'; } // format how we display the price $pd_price = displayAmount($pd_price); echo "<td width=\"$columnWidth%\" align=\"center\"><a href=\"" . $_SERVER['PHP_SELF'] . "?c=$catId&p=$pd_id" . "\"><img src=\"$pd_thumbnail\" border=\"0\"><br>$pd_name</a><br>Price : $pd_price"; // if the product is no longer in stock, tell the customer if ($pd_qty <= 0) { echo "<br>Out Of Stock"; } echo "</td>\r\n"; if ($i % $productsPerRow == $productsPerRow - 1) { echo '</tr>'; } $i += 1; } if ($i % $productsPerRow > 0) { echo '<td colspan="' . ($productsPerRow - ($i % $productsPerRow)) . '">&nbsp;</td>'; } } else { ?> <tr><td width="100%" align="center" valign="center">No products in this category</td></tr>

<?php } ?> // .. more code down here ?>

Before printing the product we first check we actually have any product at all. If dbNumRows($result) returns zero that means we have no product in current category. So we show a message saying that there are no products in that category. If we do have some products to show we display them in a table where each column's width depend on how many products to show in a row. Using the above example the column width is 100 / 3 = 33%. Next thing we do is loop through the product list and print each product. If the value of ($i % 3) == 0 we start a new row by printing <tr> and if ($i % 3) == 2 end that row using </tr>. When printing the product info we use displayAmount() function to format the look of the product price. This function is located in library/common.php. Here is what the function look like : Source code : library/common.php function displayAmount($amount) { global $shopConfig; return $shopConfig['currency'] . number_format($amount); } What this function does is concat the currency symbol with the formattted amount. So if the amount is 123456 it will be displayed as $123,456. If the product price contains fraction ( like cents ) you need to change this function to this : function displayAmount($amount) { global $shopConfig; return $shopConfig['currency'] . number_format($amount, 2); } Notice that we add extra parameter to number_format(). This extra parameter will make the function display the amount with two decimal point such as $19.95 Back to productList.php. After printing the product price we check if we already run out of that product in inventory. If so we print additional message saying that the product is no longer in stock. Since there is always a possibility that the last row we print does not contain three products we check the value of $i % 3 after the loop . If it's greater than zero that means the last row does not have three products in it so we need to print the empty column like shown on the first snapshot. In case the picture isn't clear enough here is the same snapshot but the table border is set to 1. You can see that on the last row we have 2 empty ( merged ) columns

Shop - View Product Detail


When you click on one of the product from the product list. The product detail page will show up. For each product we display the full-sized image, name, price and 'Add to cart' button. When you want to customize this page remember NOT to change the button to 'Buy Now' because the visitor may not ready to buy yet so don't scare her away. Here is the snapshot of the product detail page. We have full size image on top left corner, the description at the bottom, then the product name, price and an 'Add To Cart' button on the right.

No weird stuff here. This kind of layout is actually very common to see on shopping sites across the internet. And by following the common stuff we can be sure that the visitor won't be confused with this layout. There's one more important thing about the 'Add To Cart' button. We only show this if we still have this product in stock. After we run out of this product we just display 'Out Of Stock' . Here is the code snippet from include/productDetail.php Source code : include/productDetail.php <?php // if we still have this product in stock // show the 'Add to cart' button if ($pd_qty > 0) { ?> <input type="button" name="btnAddToCart" value="Add To Cart" onClick="window.location.href='<?php echo $cart_url; ?>';" class="box">

<?php } else { echo 'Out Of Stock'; } ?>

Shop - Add To Cart


If you click on the 'Add To Cart' button on the product detail page you will be redirected to the shopping cart page ( cart.php ). For example if you want to buy Naruto Volume 2 you will go to : cart.php?action=add&p=19. The code for cart.php is like this : Source code : cart.php <?php require_once 'config.php'; require_once 'cart-functions.php'; $action = (isset($_GET['action']) && $_GET['action'] != '') ? $_GET['action'] : 'view'; switch ($action) { case 'add' : addToCart(); break; case 'update' : updateCart(); break; case 'delete' : deleteFromCart(); break; case 'view' : } // ... more code here to display the cart content ?>

Since we have action=add in the query string the addToCart() function will be called. In short the function will do these : 1. 2. 3. 4. Check if the product exist in database Check if we still have this product in stock ( quantity > 0 ) If the product is already in cart increase the quantity If not add the product to cart

The addToCart() function is located in library/cart-functions.php and the content can be seen below. Source code : library/cart-functions.php function addToCart() { // make sure the product id exist if (isset($_GET['p']) && (int)$_GET['p'] > 0) { $productId = (int)$_GET['p']; } else { header('Location: index.php'); } // does the product exist ? $sql = "SELECT pd_id, pd_qty FROM tbl_product WHERE pd_id = $productId"; $result = dbQuery($sql); if (dbNumRows($result) != 1) { // the product doesn't exist header('Location: cart.php'); } else { // how many of this product we // have in stock $row = dbFetchAssoc($result); $currentStock = $row['pd_qty']; if ($currentStock == 0) { // we no longer have this product in stock // show the error message setError('The product you requested is no longer in stock'); header('Location: cart.php'); exit; } } // current session id

$sid = session_id(); // check if the product is already // in cart table for this session $sql = "SELECT pd_id FROM tbl_cart WHERE pd_id = $productId AND ct_session_id = '$sid'"; $result = dbQuery($sql); if (dbNumRows($result) == 0) { // put the product in cart table $sql = "INSERT INTO tbl_cart (pd_id, ct_qty, ct_session_id, ct_date) VALUES ($productId, 1, '$sid', NOW())"; $result = dbQuery($sql); } else { // update product quantity in cart table $sql = "UPDATE tbl_cart SET ct_qty = ct_qty + 1 WHERE ct_session_id = '$sid' AND pd_id = $productId"; $result = dbQuery($sql); } deleteAbandonedCart(); header('Location: ' . $_SESSION['shop_return_url']); } After finish placing an item into the cart this function do something else. It calls deleteAbandonedCart(). I know it's kind of weird to call this function here but currently it is the best place to call it. We actually have at least three options on how and when to delete abandoned: 1. 2. 3. Call deleteAbandonedCart() when adding a product into cart like shown above Make a new submodul on admin page where we have a button saying 'Delete All Abandoned Cart' Using cron job to remove the abandoned cart periodically.

The first option is what i see as the best option right now, because adding an admin submodul requires extra work, and using cron means installing the shop will become a hassle. Before i forget here is what deleteAbandonedCart() look like : Source code : library/cart-functions.php function deleteAbandonedCart() { $yesterday = date('Y-m-d H:i:s', mktime(0,0,0, date('m'), date('d') - 1, date('Y'))); $sql = "DELETE FROM tbl_cart WHERE ct_date < '$yesterday'"; dbQuery($sql); } We consider a cart is abandoned if it's older than one day. So first we find out what date yesterday was and send a query to database to remove any cart entry where the cart date is less than yesterday date. Okay, after adding the product and removing all abandoned carts we don't send the customer to the shopping cart page. She will stay on the product detail page. But this time the mini cart on the right side will show the product in her shopping cart. The mini cart is included from include/miniCart.php. It displays all product currently in shopping cart. It also show the total amount after the shipping cost. This behaviour ( sending the customer to product page after adding a product ) is actually depend on what kind of shop we're running. If it's a shop where people usually buy more than one kind of product then it's better if we skip showing the shopping cart page and display the product page. But if people on this kind of shop usually only buy one product in a shopping session then it's best if we display the shopping cart page. You just need to modify the redirection to header('Location: cart.php?action=view'); If you're still not sure where to send the shopper after add to cart there's a discussion thread in Webmasterworld forum discussing this issue, check it out. Next we start working on the shopping cart page.

Shop - View Shopping Cart


The shopping cart interface is made more simple than the shop pages. Studies have shown that removing any distraction from the shopping cart page ( and from the checkout pages too ) can lead to higher conversion rates. So for this page the left navigation is removed, the mini cart display is also gone. If the visitor come to this page and the shopping cart is still empty there is a chance that she go there by accident or feeling confused. So now we present her with simple instruction on how to buy stuff in our shop . "Your shopping cart is empty"

"If you find you are unable to add anything to your cart, please ensure that your internet browser has cookies enabled and that any other security software is not blocking your shopping session."

Now if there are already items in the cart we present it to the customer like shown below. Each row shows the product thumbnail name, unit price, quantity and sub total. On each row we have a delete button so the customer can easily remove the item. If you plan to customize the shopping cart interface do not remove the delete button. It will make the delete process difficult for the customer and it certainly not a good thing.

You may have seen it on another shopping cart solution that to remove an item the customer must set the quantity to zero then click the 'Update Cart' button. That is the wrong way to do it because it makes a very simple action difficult.

Shop - Checkout
There are three steps to complete the checkout 1. 2. 3. Fill out the shipping and payment info Confirm the ordered items, shipping and payment info and enter the payment method. Save the order information to the database. If the payment method is COD ( cash on delivery ) go straight to the thank you page. If the customer choose to pay with paypal submit the payment info to paypal server.

Below is the code for checkout.php Source code : checkout.php <?php require_once 'library/config.php'; require_once 'library/cart-functions.php'; require_once 'library/checkout-functions.php'; if (isCartEmpty()) { // the shopping cart is still empty // so checkout is not allowed header('Location: cart.php'); } else if (isset($_GET['step']) && (int)$_GET['step'] > 0 && (int)$_GET['step'] <= 3) { $step = (int)$_GET['step']; $includeFile = ''; if ($step == 1) { $includeFile = 'shippingAndPaymentInfo.php'; $pageTitle = 'Checkout - Step 1 of 2'; } else if ($step == 2) { $includeFile = 'checkoutConfirmation.php'; $pageTitle = 'Checkout - Step 2 of 2'; } else if ($step == 3) { $orderId = saveOrder(); $orderAmount = getOrderAmount($orderId); $_SESSION['orderId'] = $orderId; // our next action depends on the payment method // if the payment method is COD then show the // success page but when paypal is selected

// send the order details to paypal if ($_POST['hidPaymentMethod'] == 'cod') { header('Location: success.php'); exit; } else { $includeFile = 'paypal/payment.php'; } } } else { // missing or invalid step number, just redirect header('Location: index.php'); } require_once 'include/header.php'; ?> <script language="JavaScript" type="text/javascript" src="library/checkout.js"></script> <?php require_once "include/$includeFile"; require_once 'include/footer.php'; ?> On top of this file we check if the shoppping cart is empty. If it is empty the customer is redirected to the cart page. Just to let her know that her shopping cart is still empty and so she cannot checkout. Just like the main page ( index.php ) the checkout page "out sourced" almost everything to other pages. The main part of this file is the switch to load appropriate file depending on which checkout step the customer is on. And now, let's take a better look at these checkout processes one step at a time.

Shipping And Payment Info


If the cart is not empty then we show the shipping and payment form like shown below. You can see the code for this page here. Nothing really interesting in that code because it simply displays a form where the customer can fill in here shipping and payment information.

In case the shipping and payment info are the same, the customer can tick the checkbox that says 'Same as shipping information' and using javascript the payment info fields will have the same value as the shipping info. It's just added for convenience so the customer don't have to repeat all those typings. And lastly she must choose the payment method. The shop can handle payment using Paypal but if the customer is not comfortable with it she can just choose Cash on Delivery. When the form is submitted we don't save the info to database yet. They are just passed to the next page ( the order confirmation page ) as hidden input. Showing the confirmation page is important so that the customer can recheck their info and make sure everything is okay.

Checkout Confirmation
On the order confirmation page we display the ordered items with the shipping and payment information provided earlier. You can't see it in the screenshot but you can be sure that the shipping and payment info is there as hidden inputs. If you don' believe just checkout the source code

If the customer want to modify the shipping/payment info she can click on the "Modify Shipping/Payment Info" and if everything is okay she click on the 'Confirm Order' button and so we begin saving the order to database. First, we insert the shipping and payment info to tbl_order and the ordered items into tbl_order_item. We also modify the product quantity in stock. If you play with the demo you can see that this is disabled so the product quantity is always the same no matter how many items you ordered. Next thing we do is remove the ordered items from cart table because they are no longer needed.

If the payment method is COD the customer will go immediately to the thankyou page. But when paypal is chosen the process is a bit longer. Next we'll start playing with the Paypal stuff

Payment Processing With Paypal


For now plaincart only accept online payment using Paypal. The reason is that they provide pretty extensive resource for developers. Paypal provide the developers with a "sandbox" that allow developers to test their payment module without any actual money transferred. Paypal provide many ways to accept money but the one used for this tutorial is the Instant Payment Notification ( IPN ). The reason i choose IPN is it seem to be the easiest way to integrate paypal to this shopping cart. The details on setting up the business account and the developer account are described at the end of this checkout discussion. If you want to read it first just click here There are three files needed for using Paypal IPN and they are put in include/paypal/. The files are :

paypal.inc.php : containing the configuration variables and paypal specific functions payment.php : this is the form that contain the hidden inputs to send to paypal server ipn.php : checks the POST from paypal and update the database accordingly

Before going straight to the code please take a look at the picture below. It displays the checkout flow starting from the checkout confirmation page up to the thankyou page. The ones with blue background takes place on the merchant site ( plaincart.com ) and the ones with white background takes place in Paypal website.

Here is what happen on each stage : 1. Checkout Confirmation After rechecking the shipping and payment information on this page the customer click on the "Confim Order" button.

2. Submit Payment Info An HTML form is generated containing the payment information ( order id, amount, etc ). You will probaly only see a message like this : "Processing Transaction ... ". But if you take a look at the source you can see the HTML code containing the form and some hidden inputs. This form is submitted to paypal site during page load so if you have a fast connection you may only see it for a split second. The file that generate these hidden inputs is include/paypal/payment.php which is included from checkout.php. You can see that payment.php is not doing anything complex. It just prints the hidden inputs with values coming from the paypal configuration in paypal.inc.php plus the order id, order amount and the item name ( which is hardcoded to "Plaincart Purchase" ).

3. Paypal Login Page Once on the Paypal site you will be asked to login or create a new account. I already made a test account for this so you don't need to signup with paypal just to test this. You can use use this information to login : Email Address: testme@phpwebcommerce.com PayPal Password: phpwebco 4. Payment Detail After login you will see the payment detail page. It look something like this :

There's an optional message box that the customer ( you ) can fill in. Paypal will pass this message along with other payment information to merchat server ( this site ). When you go to the order page in the admin section you can see the message on the order detail page. To continue with the payment process just click on the "Pay" button. 5. Payment Confirmation The next screen you will see is the payment confirmation screen from paypal :

6. Thank You Page Once you click the "Continue" button Paypal will redirect the customer back to the merchant site ( this site ). Since i'm not using a secure connection ( no https:// stuff ) there will be a warning message about you're being redirected to unsecure site. You can ignore it and just click on "Continue" and you will see the thankyou page. Here is the screenshot :

The thankyou page can only be accessed when an order id is found in the session. The order id is put in the session variable after you click the "Confirm" button on the checkout confirmation page. If no order id is found then the customer is sent directly to the shop main page. On this last step of checkout an email is sent to the shop admin notifying about the new order. This behaviour can be changed from the admin page on the shop configuration section.

Here is the code for the thankyou page Source : success.php <?php require_once 'library/config.php'; // if no order id defined in the session // redirect to main page if (!isset($_SESSION['orderId'])) { header('Location: ' . WEB_ROOT); exit; } $pageTitle = 'Checkout Completed Successfully'; require_once 'include/header.php'; // send notification email if ($shopConfig['sendOrderEmail'] == 'y') { $subject = "[New Order] " . $_SESSION['orderId']; $email = $shopConfig['email']; $message = "You have a new order. Check the order detail here \n http://" . $_SERVER['HTTP_HOST'] . WEB_ROOT . 'admin/order/index.php?view=detail&oid=' . $_SESSION['orderId'] ; mail($email, $subject, $message, "From: $email\r\nReturn-path: $email"); } unset($_SESSION['orderId']); ?> <p>&nbsp;</p><table width="500" border="0" align="center" cellpadding="1" cellspacing="0"> <tr> <td align="left" valign="top" bgcolor="#333333"> <table width="100%" border="0" cellspacing="0" cellpadding="0"> <tr> <td align="center" bgcolor="#EEEEEE"> <p>&nbsp;</p> <p>Thank you for shopping with us! We will send the purchased item(s) immediately. To continue shopping please <a href="index.php">click here</a></p> <p>&nbsp;</p></td> </tr> </table></td> </tr> </table> <br> <br> <?php require_once 'include/footer.php'; ?> This is really an oversimplified thank you page. Normally an online shop also provide a link where the customer can check her order online or a notice that an order confirmation email has been sent ,etc. However i haven't made any of those so for now this is sufficient.

There's a "hidden step" between step 4 and 5. The customer can't see it because it happen between Paypal server and the merchant server. On this "hidden step" these things happen : 1. 2. 3. Paypal send a POST request to merchat server to notify about the payment. Then our server send a reply POST to let Paypal know that we have receive it. Paypal send a reply notifying the payment status whether it's verified or not

We will take a deeper look at these on the next page

Handling Paypal Instant Payment Notification (IPN)


When you reach the payment detail page on paypal site and click the "Pay" button paypal will send a POST message to our server. Our script then send a confirmation back to paypal to verify this POST. Here's that flow diagram again.

Paypal Configuration File The script that process the notification is located in include/paypal/ipn.php. But before talking about that file let's see the paypal configuration first. Source code : paypal.inc.php <?php $paypal = array(); $paypal['business'] = "armanpi@phpwebcommerce.com"; $paypal['site_url'] = "http://www.phpwebcommerce.com/plaincart/"; $paypal['image_url'] = ""; $paypal['success_url'] = "success.php"; $paypal['cancel_url'] = "error.php"; $paypal['notify_url'] = "include/paypal/ipn.php"; $paypal['return_method'] = "2"; //1=GET 2=POST $paypal['currency_code'] = "USD"; $paypal['lc'] = "US";

//$paypal['url'] = "https://www.paypal.com/cgi-bin/webscr"; $paypal['url'] = "https://www.sandbox.paypal.com/cgi-bin/webscr"; $paypal['post_method'] = "fso"; //fso=fsockopen(); $paypal['curl_location'] = "/usr/local/bin/curl";

// ... other "not so important" settings down here ?> Here is the description for each configuration : 1. $paypal['business'] The email address that shown as the recipient when you pay $paypal['site_url'] The store's main url. It is NOT ALWAYS the same as the site's homepage url $paypal['image_url'] I'm not using any images to display on paypal site during the checkout process so i just left this blank $paypal['success_url'] When paypal can verify the customer's credit card and everything gone smoothly the customer will be sent back to this url $paypal['cancel_url'] And if something went wrong the customer are taken to this one $paypal['notify_url'] This is the main script that check the verification message sent by paypal. It also update the database if the payment is successful $paypal['return_method'] It sets the http method used by paypal to send the verification message $paypal['currency_code'] What currency will the customer be paying. Other currencies supported are GBP,JPY,CAD, and EUR

2.

3.

4.

5.

6.

7.

8.

9.

$paypal['lc'] The language ( locale ) $paypal['url'] We send all payment information to this url. For testing we use the sandbox url : https://www.sandbox.paypal.com/cgi-bin/webscr But when the store goes live we'll use the real paypal url : https://www.paypal.com/cgi-bin/webscr $paypal['post_method'] The function fsockPost($url,$data) can use several method to send transaction data. They are fso ( fsockopen() ), curl ( curl command line), and libCurl ( php compiled with libCurl support ). We're using fso because a hosting company may not provide PHP with curl support $paypal['curl_location'] If you're using curl as the post method you must supply the path to the curl command on your server

10.

11.

12.

Submitting The Payment Information From the second step on the flowchart above we post some form values to paypal server so they can process the payment. Below you can see the variables sent from payment.php and their description 1. business The merchant's email address. The payment will be sent to the paypal account identified by this email address. The value for this variable is taken from the configuration file ( $paypal['business'] ) amount The amount of payment that the customer must pay. invoice The order id. We need this so we can know which order is being paid item_name Since the customer can buy multiple items i just hard code this one to "Plaincart purchase". return The url where you want to send the customer to after the payment is complete ( $paypal['success_url'] ) cancel_return When the payment fails the customer is redirected here ( $paypal['cancel_url'] ) notify_url The url of the script that checks the IPN notification from paypal and send back a confirmation to paypal ( $paypal['notify_url'] ) rm The return method. The available values are 1 ( GET ) and 2 ( POST ). Since we use POST in this example the value of rm is 2. currency_code In what currency do you want to be paid in ( $paypal['currency_code'] ) . lc The language ( $paypal['lc'] ) cmd The value is _xclick which is hardcode. Paypal provides several kinds of payment processing services so we need to tell them what kind of service we're using. no_shipping Since we already ask the shipping address during checkout. We don't need to ask it again on the paypal site. So we set this value to 1. Use 0 ( zero ) if you want paypal to ask the shipping address instead.

2.

3.

4.

5.

6.

7.

8.

9.

10.

11.

12.

Processing The Payment Here is the code for the IPN script : Source code : include/paypal/ipn.php <?php if (strpos($_SERVER['REMOTE_ADDR'], '66.135.197.') === false) { exit; } require_once './paypal.inc.php'; // repost the variables we get to paypal site // for validation purpose $result = fsockPost($paypal['url'], $_POST);

//check the ipn result received back from paypal if (eregi("VERIFIED", $result)) { require_once '../../library/config.php'; // check that the invoice has not been previously processed $sql = "SELECT od_status FROM tbl_order WHERE od_id = {$_POST['invoice']}"; $result = dbQuery($sql); // if no invoice with such number is found, exit if (dbNumRows($result) == 0) { exit; } else { $row = dbFetchAssoc($result); // process this order only if the status is still 'New' if ($row['od_status'] !== 'New') { exit; } else { // check that the buyer sent the right amount of money $sql = "SELECT SUM(pd_price * od_qty) AS subtotal FROM tbl_order_item oi, tbl_product p WHERE oi.od_id = {$_POST['invoice']} AND oi.pd_id = p.pd_id GROUP by oi.od_id"; $result = dbQuery($sql); $row = dbFetchAssoc($result); $subTotal = $row['subtotal']; $total = $subTotal + $shopConfig['shippingCost']; if ($_POST['payment_gross'] != $total) { exit; } else { $invoice = $_POST['invoice']; $memo = $_POST['memo']; if (!get_magic_quotes_gpc()) { $memo = addslashes($memo); }

$sql = "UPDATE tbl_order SET od_status = 'Paid', od_memo = '$memo', od_last_update = NOW() WHERE od_id = $invoice"; $result = dbQuery($sql); } } } } else { exit; } ?> Right at the top of the script we check if the ip address of the page requester. Since this page is meant only to be accessed by paypal server we disallow any connection that didn't come from paypal. Paypal server's IP address begin with 66.135.197 so if we don't see that in the remote address we just assume someone trying to mess with our script and exit immediately. If the request does come from paypal we send a reply POST to let Paypal know that we have receive their message. Upon receiving our reply Paypal send another reply notifying the payment status whether it's verified or not. If we found the word VERIFIED in the reply that means payment has been made and we move on updating the status for the order Here is the series of checking that the script perform : 1. 2. 3. 4. Check the database to see if the invoice id really exist If it does exist check the order status is still 'New' to prevent double action If it's a new order check the amount of money sent and make sure the currency is correct. When everything is okay we can update the order status to "Paid", add the buyer's memo and modify the update time.

That's it. Once the customer made her payment you can check the order on the admin page. When you click on the "Order" menu on the admin page you can see all paid orders. From there it's up to you as the store owner to decide what to do with those orders.

Creating Your Paypal Account If you want to test the shopping cart script on your own server you should create your own developer account. Here are the steps to setup a developer account with paypal : 1. 2. 3. Go to https://developer.paypal.com ,click on on "Sign Up Now" and complete the registration Paypal will send you a verification email. Click the link in the email to verify your email address Login to PayPal Developer Central

4. 5.

6. 7. 8. 9. 10.

Create a sandbox account by clicking on the "Sandbox" tab Click on "Create Account". Anew window will popup. On the popup screen choose "Personal Account", select a country from the list then hit "Continue". This is just for testing so you can enter false email and use false data to fill in the form. Don't forget to enter the characters in the security image (it's case insensitive) On the next screen just hit the Complete the 'extra ...' stuff Check your paypal inbox Open the welcome email and click the verification link Open the 'extra ...' email and click on the link then enter the number

You will still need to create a "normal" Paypal account not only the developer account before testing. If not all payment status will be "Pending" instead of "Completed". If you don't already have it yet here are the steps to create and configure your paypal account : 1. 2. 3. 4. 5. Create a business account with paypal ( the personal account is not enough ) and add a credit card so your account is verified. If you don't add a credit card then IPN will always say that the payment status is "Pending" Login Go to My Account > Profile > Instant Payment Notification Preferences Click on the "Edit" button Check the checkbox and enter the url where we want to receive the IPN notifications ( in our case it's http://www.phpwebcommerce.com/plaincart/include/paypal/ipn.php) then hit "Save"

Since our script already pass the hidden input called notify_url, which is the url of the script that process IPN notifications, this step is actually not required anymore. I just put this for the sake of completeness.

I did try to make checkout process as simple as possible but somehow i feel it gets quite complex now. If you found something weird / not clear please contact me. Howevert if you're question is about paypal it would be a lot better to ask the questions in the paypal forum especially the IPN section. This forum is packed with paypal experts.

Shopping Cart Source Code


You can download the whole script as a zip file here. To install the shopping cart you need to do these : 1. 2. 3. Extract the zip file ( ex. to C:\Webroot\plaincart ) Create a new MySQL database ( ex. 'plaincart' ); Create the tables using the SQL in plaincart.sql. On windows just open a command prompt window ( Start > Run > cmd.exe ) and go to the directory where you unzip the files then type this : C:\mysql\bin\mysql plaincart < plaincart.sql I'm assuming you install mysql in C:\MySQL Modify the database connection settings in library/config.php.

4.

By the way, someone asked me about the license for the code so here it is : The code is free to use, modify, or enhance. If you want to show you appreciation by placing a link to this site then i'll be very very very grateful :-). If you don't want to link to this site it's allright because the code was meant to be free anyway. Below is the list of the shopping cart files. I only list the php files excluding the css and javascript files.

source/cart.php source/admin/product/modify.php source/admin/product/detail.php source/admin/product/index.php source/admin/product/processProduct.php source/admin/product/list.php source/admin/product/add.php source/admin/processLogin.php source/admin/user/modify.php source/admin/user/processUser.php source/admin/user/changePass.php source/admin/user/index.php source/admin/user/list.php source/admin/user/add.php source/admin/main.php source/admin/category/modify.php source/admin/category/processCategory.php source/admin/category/index.php source/admin/category/list.php source/admin/category/add.php source/admin/index.php source/admin/library/functions.php source/admin/login.php source/admin/include/header.php

source/admin/include/template.php source/admin/include/footer.php source/admin/config/main-lama.php source/admin/config/processConfig.php source/admin/config/main.php source/admin/config/index.php source/admin/order/processOrder.php source/admin/order/detail.php source/admin/order/index.php source/admin/order/list.php source/checkout.php source/error.php source/success.php source/index.php source/library/errorMessage.php source/library/common.php source/library/config.php source/library/database.php source/library/product-functions.php source/library/checkout-functions.php source/library/category-functions.php source/library/cart-functions.php source/plaincart.sql source/db/db.sql source/include/productList.php source/include/header.php source/include/footer.php source/include/top.php source/include/categoryList.php source/include/paypal/process.php source/include/paypal/payment-old.php source/include/paypal/orderform.php source/include/paypal/error.php source/include/paypal/paypal.inc.php source/include/paypal/success.php source/include/paypal/Backup 1 of ipn.php source/include/paypal/testpost.php source/include/paypal/ipn/ipn_error.php source/include/paypal/ipn/ipn_success.php source/include/paypal/ipn/Copy of ipn.php source/include/paypal/ipn/Backup 1 of ipn.php source/include/paypal/ipn/Backup 1 of Copy of ipn.php source/include/paypal/ipn/ipn.php source/include/paypal/ipn.php source/include/paypal/cancelled.php source/include/paypal/payment.php source/include/paypal/includes/config.inc.php source/include/paypal/includes/global_config.inc.php source/include/Copy of shippingAndPaymentInfo.php source/include/productDetail.php source/include/thankyou.php source/include/leftNav.php source/include/checkoutConfirmation.php source/include/shippingAndPaymentInfo.php source/include/miniCart.php

Shopping Cart Resources


Here are a few online resources that you may find useful when building a shopping cart / e-commerce website. 1. E-commerce User Experience 207 Guidelines for E-commerce Sites This report is compiled by Nielsen Norman Group. It contain guidelines on designing a more usable e-commerce website. Marketing Sherpa If you want to run a successful business you must never stop learning. Marketing Sherpa contains extensive articles related to e-commerce. These articles are free usually for a week or two so make sure you signup for their newsletter so when a new article is posted you can grab it before they change it to paid status. WebmasterWorld Forum A very respective and useful site. You really should hang out in the forums. There are plenty useful information here. WilsonWeb Claim to be "The Trusted Guide to Internet Marketing since 1995". The site has plenty of tutorials on building a successful online bussiness

2.

3.

4.

5.

Conversion Chronicles Plenty articles about increasing your online shop conversion. Site Build It! All in one solution for creating websites that get traffic.You can use SBI to create content sites that warm up your potential customers before sending them to your online shop PHP MySQL Tutorial My other site. I'm sure you know what this site has to offer from reading the name :-)

6.

7.

PHP MySQL Resources

PHP Homepage
Of course, I have to list this site here. This is where you can to download the PHP bundle and documentations too It also has a lot of other important resources such as links to various PHP projects. You can also subscribe to PHP mailing list from this website. There mailing list are divided to cover specific issues, so you may want to choose the most appropriate to your needs.

MySQL Homepage
Here you can download the latest MySQL release, get the MySQL news update. The mailing list is also a great resources for anyone who want to build dynamic websites using MySQL, so don't to join.

Zend
PHP is powered by the Zend engine. This website is the official homepage of the company who built the engine. Here you can get the Zend Optimizer. It's a very useful tool which can give your PHP scripts a 40-100% increase in speed. It also has PHP programming contest, a directory of free PHP scripts and applications, articles and tutorials. Zend also offer a PHP Certification. They said it can "Differentiate yourself from competitors when looking for a new job or at your annual salary review". Maybe I'll try to get that certification and see if I can get a better salary :-). By the way, you can get a free chapter of the Zend PHP Certification Guide in Zend homepage. It's really an interesting reading.

PHPBuilder
Great articles and tutorials in PHP programming. Anyone from a beginner to an expert can really learn from this site.

Scriptlance
One of the best place to find freelance programming jobs. Last time i check there are over 300 open project related to PHP. It' really a good place to find freelance projects

DevShed
Lots of resources for open source stuff. Not all articles here is related to PHP or MySQL. Actually you can get a whole breed of web development issues here.

Eclipse.org
My favourite PHP editor. It's heavy ( use lots of memory ) but still great. Built in support for CVS ( Concurrent Versioning System ) makes it really easy to manage projects with multiple developers. By the way if you want to use Eclipse don't forget to get the PHP plugin from sourceforge.net or from phpeclipse.de

PHPPatterns.com
Learn how to apply Design Pattern in PHP. If you already learn Object Oriented Programming (OOP) in PHP this site can help you applying the concept of OOP better. There's a funny article here about how far you can go in applying patterns. It's about writing a Hello World script in the super hard way.

Sitesell Value Exchange


A real Link Exchanger that works. You can find high-value, similarly themed sites, and exchange links to truly increase your link popularity, in a way that the search engines love.

Vision.To Design
Web Applications, PHP/MySQL Advanced Solutions, Web Design

dbQwikSite
It's a software that generates PHP code against your MySQL database so you an speed up your coding.

W3C Link Checker


Use this tool to make sure that your website has no dead links. A very very useful tool. If you're building a website you shouldn't go live before checking the link using this.

ScriptsToProfit.com PHP/MySQL Website Scripts


Offering quality free and affordable PHP/MySQL website scripts to make your online business more interactive.

Big Webmaster Directory


Webmaster tools, scripts and tutorials.

ScriptSearch.com
You can find free scripts, source code, books and code examples in here.

PHPFreaks.com
PHP and MySQL Resources. They have a linux forum too

URL.biz - Programming Search engine optimisation at NetRegistry


Helping more businesses in Australia with search engine submission & optimisation

You might also like