Category Archives: Tips

How To Maintain Basic and PRO Versions With Same Code Base

When you are selling “out of the box” software you quickly start getting customization inquiries. We get this all the time. Usually I have to handle these in case-to-case basis, means we’d just hack a custom version for the client and move on. Some requests are seen more often than once and I often figure out it would be a good idea to add their customization as a future feature for the next version of the upgrade. (In this case of course I let the client know what I think, and charge less than otherwise).

The problem is if you add every interesting feature, your core version will quickly become bloated. On top of that you may want to charge more for all this hard work and the evolution of your program. But what about the customers who were happy with the old version and would prefer that? The solution to this is called segmentation and marketers will tell you it helps you make more money. As marketing is not the point of this post, I’ll focus on the technical side: how do you actually handle two or more version without getting insane?

Lego Architecture 21005 - Fallingwater

The Solution: Keep Same Core Code Base

This isn’t really anything revolutional. Have a look at how WordPress manages extra functionality in plugins for example. You can follow the same logic – keep your core module extendable and then your PRO, Ultra and Mega versions can be built as “plugins” to it. But unless you are building a huge application that allows as diverse functionality like WordPress and its plugins, you don’t really need the complex API with hooks and all that. I’ll tell you the much simpler solution we used in our Watu PRO Intelligence module (yeah, it’s all for WordPress however you can apply the same ideas everywhere).

Step 1: Package Modules

The obvious first step is to keep your extra functionality encapsulated in classes. If you use the MVC concept (recommended), you should make a folder for your PRO version functionality and inside the folder place at least the following folders:

/models
/views
/controllers

I also like to include the module’s own images etc:

/css
/images
/js

So all the stuff of the module is placed there.

Then, if you plan to support more than one “module” or “version” of your app (let’s say Basic, PRO, and Corporate), ideally you should place all the modules in a folder. So, here is example folder structure of your application:

-/app folder
-/models
-/views
-/controllers
-…
-/modules
– -/pro
– – -/models
– – -/views
– – -…
– -/corportate
– – -/…

I hope you get the idea. Now, as everything is packaged, how do we include this extra functionality in a really simple way?

Step 2: Function to check if module exists

Here is how: you simply need to check if some of the module files exist. So let’s create a simple function in your core application.
I’ll keep this one procedural to make the code shot, you feel free to make it a static or non-static class method. All example code is in PHP:

function module_exists($module) { 
   switch($module) { 
      case 'pro': 
         if(file_exists(BASE_PATH."/modules/pro/models/basic.php")) return true; 
         else return false; 
      break; 
      case 'corporate': 
         if(file_exists(BASE_PATH."/modules/corporate/models/basic.php")) return true; 
         else return false; 
      break; 
   } 

   // if no match, return false 
   return false;
}

As you can see this is a really basic stuff. In the code above you have your absolute or relative path to the root folder in BASE_PATH constant and that your both “extra” versions have model file called basic.php. You can obviously have different structure and names. The idea remains the same – simply check if the file is there. This way you can package your Basic version with empty modules directory, your “PRO” version with modules/pro folder, but without modules/corporate. and your “Corporate” version with all the files and folders.

Then all you’ll need to do when you zip your packages for sale will be to remove the unnecessary folders.

Step 3: Run code conditionally

Then obviously you will need to place conditions here and there in your core app that will define whether a “module” code will be run. In “model” or “controller” level you will probably have something like this:

// some core method
function some_method() {
   // some core code here
   ///...
   if(module_exists('pro')) {
      PROModuleBasicModel::do_something();
   } 

   if(module_exists('corporate')) {
     CorporateModuleBasicModel::do_corporate_thing();
   } 
}

Of course this adds some extra code to your core app but it’s a great tradeoff compared to keeping 2 or 3 completely different codebases and trying to apply updates and changes to all of them.

At a view level you can probably just use an include like this:

<!-- example view code: some HTML below -->
   <h1>My Module Form</h1>
   <form method="post">
      <fieldset>
        <legend>Basic User Data</legend>
        <div><label>User email:</label> <input type="text" name="email"></div>
        .....
      </fieldset>
      <?php if(module_exists('pro')): 
         require(BASE_PATH."/modules/pro/views/user_details.html");
        endif;?>
      <?php if(module_exists('corporate')): 
        require(BASE_PATH."/modules/corporate/views/user_details.html");
        endif;?>
   </form>
<!-- end of the example -->

And these included views could probably include additional sections (fieldsets) that include extra options and fields offered by the more advanced modules.

That’s it in short. You can package your apps and start selling different versions. When you upgrade your core version, you’ll just copy the changed files to your other zips and you are all set. Feel free to use SVN or Git branches for this, the concept remains the same.

If you have better ideas, I’ll be happy to hear them!


					

3 Smart PHP-Generated Drop Down Menus (Tutorial)

In our software we often use drop-down menus. To make things easier I have coded functions for the most commonly used ones – date drop down, drop down to list database entries, and drop down to be populated from simple non numerated PHP array. All these drop downs are for example used in our autoresponder software. They just generate HTML code which is then output in your HTML forms.

Just recently I decided to encapsulate them in a class and share with the community. So if you are lazy to read, go right to the code at PHP Classes. The class is called QuickDD.

For the more curious people who want to learn, here is this tutorial explaining the stuff.

The Class

Using class isn’t that important in our case because there is almost no shared data between the 3 drop down generating functions (except the errors array). But we’ll create a class so we can keep it encapsulated at least:

class QuickDD
{
    // this variable will be filled with errors. 
    // You can print it out with print_r so you can see errors
    public static $errors=array();

    // used by the date drop down. Feel free to change start/end year
    public static $start_year=1900;
    public static $end_year=2100;

You can very well just use three procedural functions. But from now on, let’s stick with the OO example.

The Date Drop-Down

This is the most complex one. I know there are many cool scripts like jQuery calendar but sometimes they are a bit painful to work with. Simple drop-down date selection is easy to understand, works in all browser, and takes no javascript to implement. So, let’s build one:

    // Outputs date drop down for selecting month, day, year 
    // Arguments:
    // @param $name - string, the base name of the "select" form tag.  
    // Quick DD will add suffix to each of the 3 tags:
    // "year" for year, "month" for month, and "day" for day
    // @param $date - string in MySQL format, optional pre-selected date
    // @param $format - string in the type YYYY-MM-DD showing how to output the 
    // drop-downs. If $format has anything insane (like 5 Y or single-digit month)
    // we'll quietly switch to something sane instead of raising exception.
    // but we may log an error
    // @param $markup - if presented it should contain any markups that will be added
    // to each dropdown, in the same order that the parts are displayed.

And the function starts this way:

static function date($name, $date=NULL, $format=NULL, $markup=NULL)
    {

It’s static because we wan to call it anywhere without creating object. When you just want to encapsulate methods in a class for organization reasons, and there is no class instance data, it’s better to use static methods.

At the beginning of the function we want to make sure the params passed are so-so sane:

        // normalize params
        if(empty($date) or !preg_match("/\d\d\d\d\-\d\d-\d\d/",$date)) $date=date("Y-m-d");
        if(empty($format)) $format="YYYY-MM-DD";
        if(empty($markup)) $markup=array();

Now let’s explode the optionally passed pre-selected date, and start to buffer the output of the method:

$parts=explode("-",$date);
$html="";

// read the format
$format_parts=explode("-",$format);

And in $format_parts now we have the format as requested when calling the method. Now the most important part starts:

        // let's output
        foreach($format_parts as $cnt=>$f)
        {
            if(preg_match("/[^YMD]/",$f)) 
            { 
                self::$errors[]="Unrecognized format part: '$f'. Skipped.";
                continue;
            }

As you see we go through each parts of the format string and check whether it contains anything else than the allowed symbols Y, M, and D. If yes, we add to errors array and skip that part. Errors array can be output with print_r when you use the method to see whether there are errors (this will become more clear in the usage example that will come after the code.

Now let’s add the year drop-down:

            // year
            if(strstr($f,"Y"))
            {
                $extra_html="";
                if(isset($markup[$cnt]) and !empty($markup[$cnt])) $extra_html=" ".$markup[$cnt];
                $html.=" <select name=\"".$name."year\"".$extra_html.">\n";

                for($i=self::$start_year;$i<=self::$end_year;$i++)
                {
                    $selected="";
                    if(!empty($parts[0]) and $parts[0]==$i) $selected=" selected";
                    
                    $val=$i;
                    // in case only two digits are passed we have to strip $val for displaying
                    // it's either 4 or 2, everything else is ignored
                    if(strlen($f)<=2) $val=substr($val,2);        
                    
                    $html.="<option value='$i'".$selected.">$val</option>\n";
                }

                $html.="</select>";    
            }

Few things to note: $extra_html is populated by the element of the $markup argument which corresponds to the position of where YYYY (or YY) is in the format. So if you pass format like "DD-MM-YY" the third element of the array argument $markup will be used. If empty, nothing is used.

Then see how we prepend "year" to the $name argument to create the name of the drop-down. So each drop-down has its own name. For example if you call the method with $name equal to "date" your 3 drop downs will have names "dateyear", "datemonth", and "dateday" and can be accessed in $_POST or $_GET with these names.

Next we go through start and end year in a loop and output the option tags. In case $date argument is passed, the $parts value will contain year in $parts[0] (Because $date argument should always be passed in the MySQL YYYY-MM-DD format). So we check if such value is passed and when it matches $i we set the var $selected so the option tag can have selected attribute.

Finally see how we check if the format should be YYYY or YY and substring the output to meet that.

Then comes month drop down which is nearly the same:

            // month
            if(strstr($f,"M"))
            {
                $extra_html="";
                if(isset($markup[$cnt]) and !empty($markup[$cnt])) $extra_html=" ".$markup[$cnt];
                $html.=" <select name=\"".$name."month\"".$extra_html.">\n";

                for($i=1;$i<=12;$i++)
                {
                    $selected="";
                    if(!empty($parts[1]) and intval($parts[1])==$i) $selected=" selected";
                    
                    $val=sprintf("%02d",$i);
                        
                    $html.="<option value='$val'".$selected.">$val</option>\n";
                }

                $html.="</select>";    
            }

The thing to notice here is the usage of sprintf which makes sure leading zero is always shown.

And now, day:

          // day - we simply display 1-31 here, no extra intelligence depending on month
            if(strstr($f,"D"))
            {
                $extra_html="";
                if(isset($markup[$cnt]) and !empty($markup[$cnt])) $extra_html=" ".$markup[$cnt];
                $html.=" <select name=\"".$name."day\"".$extra_html.">\n";

                for($i=1;$i<=31;$i++)
                {
                    $selected="";
                    if(!empty($parts[2]) and intval($parts[2])==$i) $selected=" selected";
                    
                    if(strlen($f)>1) $val=sprintf("%02d",$i);
                    else $val=$i;
                        
                    $html.="<option value='$val'".$selected.">$val</option>\n";
                }

                $html.="</select>";    
            }

Hopefully you noticed how looping through format parts first let's us position the dropdown in the same order as given in the $format parameter.

So essentially that's it, we can now output the $html:

       // that's it, return dropdowns:
        return $html;
    }

Again, the full source code is available at PHP Classes so you can see it there as a whole.

Using the Date Drop Down:

Here is how to call this now:

    QuickDD::$start_year=2009;
    QuickDD::$end_year=date("Y")+10;

    // see how we pass the base name for the dropdowns, values, and markup array which will
    // set DOM ID, CSS class, and Javascript callback to every drop-down. 
    // Of course all this is optional
    echo QuickDD::date("final", "2012-12-21", "DD-MM-YYYY", array(" id='selectDay' class='some_class' onchange='doSomething(this.value);' "," id='selectMonth' class='some_class' onchange='doSomething(this.value);' ", " id='selectYear' class='some_class' onchange='doSomething(this.value);' "));    
    
    if(!empty(QuickDD::$errors)) print_r(QuickDD::$errors);

This way you can add all kind of javascript and CSS markup, pre-selected values etc. But of course it can be called also in much simpler way using the defaults:

QuickDD::date("date");

Really simple.

Drop-down With Associative Arrays

These are very often used in DB driven apps. In our example we'll have a simple DB table with students where each student has an ID, email, and name. And we want to display a drop down menu to display a student. Visually the student name will be shown, but the value attribute will show the student ID so we can process it further in the app.

Here's the beginning:

// Outputs drop-down populated from associative array (usually coming from database)
    // @param $rows - the associative array
    // @param $value_field - the name of the key from the array whose value will be placed into
    // the option tag "value" attribue
    // @param $display_field - the name of the array key that will be used for display text
    // @param $sel - optionally a pre-selected value (for example when editing DB record)
    public static function hash($rows, $value_field, $display_field, $sel=NULL)
    {

Now the main function code is just few lines:

$html="";
        foreach($rows as $cnt=>$row)
        {
            if(is_array($sel))
            {           
                // multiple-select drop-down
                if(@in_array($row[$value_field],$sel)) $selected=' selected';
                else $selected='';
            }
            else
            {
                // single-select drop-down
                if($sel==$row[$value_field]) $selected=' selected';
                else $selected='';
            }
            $html.="<option value=\"$row[$value_field]\"".$selected.">$row[$display_field]</option>\n";
        }
        return $html;

What we do here:
1. We start with $html buffer
2. Then loop through the DB records
3. Check if pre-selected value is passed, whether it's array of values (useful in multiselect drop downs), and check if it matches the current row.
4. Output the display field and value fields appropriately.

That's it. And here's how to use this method:

<select name="student_id">
    <option value="">- Please select -</option>
    <?php echo QuickDD::hash($students, "id", "name", 2);?>
    </select>

Drop-down With Simple Arrays

Simple arrays means ones that are not associative although you can use associative too. The function will not look for keys though, it will work only with values. So it's good for list of colors (like in this example), numbers, names, currencies etc. The function accepts one array for displays, and one for value attributes of option tags. But more often you'd probably call it with the same array in both places so what you see is what you get as option value. Here's the function, then the example will help you understand better:

 // outputs dropdowns using simple arrays for value attribute and for display
    // @param $values - the array of values that goes to "value" attribute
    // @param $displays - the array of texts that will be displayed in the options
    // @param $sel - pre-selected value
    public static function arrays($values, $displays, $sel=NULL)
    {
        $html="";
        foreach($values as $cnt=>$value)
        {
            if(is_array($sel))
            {
                // for multiple-select drop-down
                if(in_array($value,$sel)) $selected=' selected';
                else $selected='';
            }
            else
            {
                // single-select drop-down
                if($sel==$value) $selected=' selected';
                else $selected='';
            }
            $html.="<option value=\"$value\"".$selected.">$displays[$cnt]</option>\n";
        }
        
        return $html;
    }

It's very similar to the previous one. But instead of getting values by keys, we browse through each array.

Here's example usage with colors:

<p>Select color: <?php
    // here's the color array
    $colors=array("black","white","blue","green","red","orange","yellow");
    ?>
    <select name="color">
    <option value="">- Please select -</option>
    <?php echo QuickDD::arrays($colors, $colors);?>
    </select></p>

That's it. You can improve the class by adding more error checking and some friendlier way to display the errors (for example in HTML comments).

Do you have ideas for other often used drop-downs that can be added to the class?

PHP/MySQL Tutorial: Classifieds Software In 1 Hour

In this tutorial we will build a basic classifieds software in PHP and MySQL. The software will do the following:

[ad#square-250] 1. Allow everyone to submit classified (no registration required)
2. Classifieds will be listed in categories
3. The main page will list all categories along with the number of classifieds in them
4. Each category will list the classifieds in it ordered by the time of posting, descending order
5. A small administration will be provided where the administrator will add/edit categories. This page is not included in the tutorial as for testing you can just use phpMyAdmin and add/edit/delete categories from it.

This software will use the MVC concept (although very simple one).

Database Structure

The database structure will be simple. We need 3 tables: classifieds, categories and admin. Here are the CREATE TABLE statements with some short explanation:

CREATE TABLE `classifieds` (

`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,

`category_id` INT UNSIGNED NOT NULL,

`title` VARCHAR(255) NOT NULL,

`content` TEXT NOT NULL,

`contact_details` TEXT NOT NULL,

`date` DATE NOT NULL

);

Optionally you can make category_id a foreign key to the next table so when a category is deleted, the classifieds in it are deleted too (or use RESTRICT clause and disallow deleting a category that has classifieds in it).

CREATE TABLE `categories` (

` id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,

` name` VARCHAR(255) NOT NULL

);

This categories table is very simple as categories contain only name. You may decide to add description, some kind of status field (active/inactive) and so on.

The following table will contain the login information for the administrator:

CREATE TABLE admin (

` id` TINYING UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,

` email` VARCHAR(255) NOT NULL,

` pass` VARCHAR(100) NOT NULL

);

Most probably you will use only one admin (superadmin), but having a table for the administrator’s login will allow you to add and use more than 1 admin in the future if that becomes a requirement.

Models

As we follow the MVC concept we’ll delegate the business logic of this software to the model layer. Models are PHP classes that control the database operation for creating, editing and showing classifieds and categories.

For simplicity in this tutorial I will use SQL queries inside the models for every database operation. However it’s highly recommended that you use some database class rather than the mysql_query calls. It’s also a good idea to use some sort of ActiveRecord/ORM implementation so add/edit/delete operations can be handled without typing SQL. For example see PHP ActiveRecord or the basic model in Celeroo framework. Discussing ORM itself is out of the scope of this tutorial so let’s continue with the other things.

We will use two models – one for categories and one for classifieds. At this point we will not build model for admin as the only place where we’ll deal with the admin table will be authentication. I am leaving authentication out of this tutorial. You can see how to build simple PHP/MySQL user authentication here.

Model Category:

<?php
class Category()
{
   function __construct()
   {
      // for this tutorial there will be nothing inside this method
      // in the real app you could possibly add some initialization
   }

   function add($name)
   {
      $this->filter($name);
      $q="INSERT INTO categories (name) VALUES (\"$name\")";
      mysql_query($q) or die(mysql_error());
   }

   function edit($name, $id)
   {
      $this->filter($name);
      // add some basic security
      if(!is_numeric($id)) exit;

      $q="UPDATE categories SET name=\"$name\" WHERE id='$id'";
      mysql_query($q);
   }

   // category name allows only alphanumeric
   private function filter($name)
   {
      $name=preg_replace("/\W/","",$name);
      return $name;
   }

   function delete($id)
   {
       if(!is_numeric($id)) exit;

       $q="DELETE FROM categories WHERE id='$id'";
       mysql_query($q);
   }

   // method to list the categories for admin edit/delete
   function list()
   {
       $q="SELECT * FROM categories ORDER BY name";
       $result=mysql_query($q);

       $cats=array();

       while($cat=mysql_fetch_array($result)) $cats[]=$cat;
       return $cats;
   }

   // method to return categories along with number of ads in them
   function list_count()
   {
       $q="SELECT COUNT(tA.id) as classifieds, tC.name as name, tC.id as id
       FROM categories tC LEFT JOIN classifieds tA ON tA.category_id=tC.id
       ORDER BY tC.name";
       $result=mysql_query($q);
       $cats=array();

       while($cat=mysql_fetch_array($result)) $cats[]=$cat;
       return $cats;
   }

   // method to return full category data for a single category
   function select($id)
   {
      if(!is_numeric($id)) exit;

       $q="SELECT * FROM categories WHERE id='$id'";
       $category=mysql_fetch_array(mysql_query($q));
       return $category;
    }
}
?>

Few things to notice here: in the methods that receive $id we are checking whether it is numeric to prevent eventual SQL injections.

Another thing to pay attention to is the LEFT JOIN in list_count method. If we don’t use LEFT only the categories which have classifieds in them will be returned. But we want to show all, even those with zero ads inside.

Then we need a simple class to add a classified and list classifieds:

<?php
class Classified
{
   function __construct()
   {
      // empty in our basic version
   }

   function add($vars)
   {
       if(!is_numeric($vars['category_id'])) exit;

       $vars['title']=mysql_real_escape_string($vars['title']);
       $vars['content']=mysql_real_escape_string($vars['content']);
       $vars['contact_details']=mysql_real_escape_string($vars['contact_details']);

       $q="INSERT INTO classifieds (category_id,title,content,contact_details,date)
       VALUES ('$vars[category_id]',\"$vars[title]\",\"$vars[content]\",
       \"$vars[contact_details]\",CURDATE())";
       mysql_query($q);

       $id=mysql_insert_id();
       return $id;
   }

   // list classifieds in a category
   function list($category_id)
   {
       if(!is_numeric($category_id)) exit;

       $q="SELECT * FROM classifieds WHERE category_id='$category_id'
       ORDER BY id DESC";
       $result=mysql_query($q);

       $ads=array();
       while($ad=mysql_fetch_array($result)) $ads[]=$ad;
       return $ads;
   }
}
?>

The code here is pretty straightforward. Notice the security checks and mysql_real_escape_string calls to prepare the data for inserting in the DB.

Controllers

The controllers are parts of the program which handle the interaction with the user input and prepare the output. In our case the controllers will be simple.

Home page:

This will be index.php so it gets loadedby default. It needs to list all the categories along with the number of classifieds.

<?php
// this may not be required if you use php autoload function
require_once("models/category.php"); // assuming we have put models in models/ folder

$_category=new Category();
$cats=$_category->list_count();

require_once("views/index.html");
?>

As you can see once you’ve built model layer, controllers are pretty simple. This one just creates a model instance $_category and calls the method list_counts to receive array of categories.

Submit classified

We need a page that will allow people to submit classifieds. It will be linked from each category page and the category ID will be passed in the URL. Let’s call this page submit.php:

<?php
// again this may be unnecessary
require_once("models/classified.php");

$_classified=new Classified();

if(!empty($_POST['submit']))
{
   $id=$_classified->add($_POST);

   // redirect to see the posted classified
   header("Location: classified.php?id=$id");
   exit;
}

require("views/submit.html");
?>

This page initially displays the posting form. When the form is submitted, it uses the model add() method to insert the classified, to get its ID and to redirect to the page where the classified is shown. Simple, isn’t it?

Category page

This page will be reached when the visitor clicks a link on the homepage. The links on the homepage will contain the category_id in the URL, for example like this: category.php?id=X. When you see the views everything will become clear.

So here is category.php controller:

<?php
require_once("models/category.php");
require_once("models/classified.php");

$_category=new Category();
$_classified=new Classified();

// select category
$category=$_category->select($_GET['id']);
$classifieds=$_classified->list($category['id']);

require_once("views/category.html");
?>

Note that for simplicity I have omitted the DB connection part from the controllers. It’s highly recommended that you create a file which will create the database connection and then include/require this file in all controllers so the connection is available in them.

Views

Views are the HTML pages that display the content and web forms to the users. They complete the entire software.

You need to create header and footer HTML files. The header will contain not only the visible header but also the HTML head tag with CSS, javascripts etc. Let’s call these files header.html and footer.html and put them in folder views/ where will be all the other views as well.

Home page:

This will be called index.html and is included from index.php controller. Here is the content:

<?php require(“views/header.html”);?>
<h1>Home Page</h1>

<?php foreach($cats as $cat):?>
<div style=”float:left;”>

<h2><a href=”category.php?id=<?=$cat[‘id’]?>”><?=$cat[‘name’]?></a></h2>

<p><?=$cat[‘classifieds’]?> ads</p>
</div>
<?php endforeach;?>

<?php require(“views/footer.html”);?>

Note that in the views I use the alternative PHP control structure syntax. It clutters the HTML code less and doesn’t confuse so much designers that may eventually need to work with your views.

Now this page goes to category.php which lists the ads in a category. Here is the associated view category.html:

Category page:

<?php require(“views/header.html”);?>

<h1><?=$category[‘name’]?></h1>

<p><a href=”submit.php?category_id=<?=$category[‘id’]?>”>Submit classified</a></p>

<?php foreach($classifieds as $classified):?>

<div style=”clear:both;border:1px solid black;padding:5px;margin:5px;”>

<p><b><?=$classified[‘title’]?></b></p>

<p><?=nl2br($classified[‘content’]);?></p>

<p>Contact details: <?=nl2br($classified[‘contacts’]);?></p>

<p>Published at: <?=$classified[‘date’]?></p>

</div>

<?php endforeach;?>

<?php require(“views/footer.html”);?>

Finally we need the web form for submitting new classified. Let’s call this page submit.html:

Submit classified:

<?php require(“views/header.html”);?>

<h1>Submit classified</h1>

<form method=”post” action=”submit.php”>

<input type=”hidden” name=”submit” value=”1″>

<input type=”hidden” name=”category_id” value=”<?=$_GET[‘category_id’]?>”>

<p><label>Title:</label> <input type=”text” name=”title”></p>

<p><label>Content:</label> <textarea name=”content”></textarea></p>

<p><label>Contact details:</label> <textarea name=”contacts”></textarea></p>

<p><input type=”submit” value=”Submit classified”></p>

</form>

<?php require(“views/footer.html”);?>

That’s it! Once you create the database connection and include the file in all controllers, your software will be ready to run.

You can build this software for just one hour.

How to improve the software

Of course this is a very basic and simple classifieds software. Here are some ideas how you can improve it:

– Require registration and allow users login and edit/delete their ads
– Make sure classifieds automatically expire after some time
– Add validations on the submit classified page
– Add contact form to allow visitors contact the classified posters
– Add pagination in the category page
– Format the classified post date to some better human readable format
– Check for duplicate ads
– Allow unlimited levels of subcategories
– and so on

This tutorial is giving you the basic and then you can continue and elaborate the software as much as you wish.

Any comments are welcome.