Building a Site Engine Using PHP, Part 3

In this third article of the series, I’ll show you how the database and directories should be set up. I’ll also talk about the how to create, install, and use a content block, which mostly relies on arrays and array functions. I’ll cover proper authentication methods for such a project so that multiple sites can run off the same users table in the database, while not barring a username from being used on a site because it is being use on another, all the while keeping the authentication accurate and secure from break-ins. (For part 2, see here.)

Give Me Direction

The file system is very important to the site engine. If a directory isn’t named right, the engine won’t know it’s there, in which case it won’t load the files from the directory. So I’ll explain the files system to the best of my ability (since I designed it, my ability should be pretty good). With the help of my friends “Windows Explorer” and “Adobe Photoshop”, I made this little diagram to help explain it all.

Building a Site Engine with PHP

Figure 1

The best way to explain this is to say that the directories labeled in black are the ones that must be labeled the way they are in the diagram; the directories in red are directories that are named depending on what is in them; and the blue is an optional folder in case you have images, CSS, JavaScript, or whatever you want to put in it that’s relevant to your theme. It can be named whatever you want it to be name as long as the theme files know where to look.

The “ROOT_DIR” directory can be named whatever you would like to be named as long as the server is set up to know where the site engine files are, configure it accordingly to your operating system’s and server’s specifications.

The “inc” folder should always be named “inc”. This is where all the engine files reside.

The “plug-ins” directory should always be named “plug-ins”. This is the directory that we’ll be putting all our plug-ins into.

The “plug-in_name” directory should be named accordingly, based on what plug-in is contained in it. This is also known to the engine as the name of the plug-in. That’s because all the actual plug-in files are named “main.plug.php” so the engine knows the difference by the name of the directory it is in. There needs to be one directory for each plug-in.

The “blocks” directory should always be named “blocks”. This is where all the blocks that depend on the plug-in, whose directory they are in, reside. Each plug-in directory contains one of these.

The “modules” block directory should always be named “modules”. This is where all the modules that depend on the plug-in, whose directory they’re in, reside. Each plug-in directory contains one of these.

The “templates” directory should always be named “templates”. This is where all the files that define the templates for each site reside.

The “site_name” directory should be the same as the host that is defined for the particular site. If your site is hosted at subdomian.url.com the host of the site would be “subdomain”, therefore the directory’s name should be “subdomain” if the site is hosted at www.url.com the host would be “url”, therefore the directory’s name would be “url”. There needs to be one directory for each site.

The “template_name” directory should be named the same as the name of the template. If your template is named “Default” the directory should be named “Default”. There can be multiple templates for each site, but at least one per site.

The “images” directory can be named whatever you want it to be named. I just included it in the diagram as an example of where you’d put your images, CSS, JavaScript, or anything else the particular template depends on.

Here is an example of what the directory would look like for the engine running 2 sites, one of the sites having two templates, and five plug-ins, each with a few modules and blocks.

Building a Site Engine with PHP

Figure 2

Note. The two directories that are marked with an asterisk (Fig. 1) can actually be renamed to anything you want, although that’s a rather advanced topic that involves a deeper look into the database, the file system, and the plug-ins and module systems

That sums it all up for the file system. It’s actually pretty straight forward and painless when you actually start to work with it.

{mospagebreak title=Wanna Go Out on a Data?}

The database is just as easy and logical as the file system — actually it’s a lot easier. The first thing I’d like to show you is all the different tables, and what they’re used for.

Block_location: This table contains all the data that directs the blocks on where to load, when to load, and what site to load on.

Blocks: This table contains all the data for the block’s title, filename, plug-in dependence, module dependence, authentication level, and verification of what site to load on.

Group_users: This table contains all the data that specifies what groups a user belongs to and on which site.

Groups: This table contains all the data that defines the group’s names.

Module_status: This table contains all the data that regulates what modules are initialized and for which site.

Modules: This table contains all the data that specifies a module’s name, filename, directory, author name, version number, module type, and plug-in dependence.

Plugin_status: This table contains all the data that regulates what plug-ins are initialized and for which site.

Plugins: This table contains all the data that specifies a plug-in’s name, filename, directory, author name, version number, plug-in type, and loading priority.

Sites: This table contains all the data that defines each site’s name and host.

Template_users: This table contains all the data that specifies what template is currently being used, and by what user (if users are set up to change their template).

Templates: This table contains all the data that defines each template’s name, site, file, and status.

Users: This table contains all the data that defines each user’s name, password, and what site it is registered on.

Here is the actual SQL dump of the site engine’s database. If you would like to, can load this into a query and set up your database instantly. For more reference on MySQL tables, definitions, and query syntax please visit www.mysql.com.

# Table: ‘block_location’
#
CREATE TABLE `block_location` (

  `bl_ID` int(11) NOT NULL auto_increment,

  `block_ID` int(11) default ‘0’,

  `block_row` int(11) default ‘0’,

  `block_col` int(11) default ‘0’,

  `block_page` varchar(255) default ‘all’,

  `site_ID` int(11) default ‘1’,

  `user_ID` int(11) default ‘0’,

  PRIMARY KEY  (`bl_ID`),

  UNIQUE KEY `bl_ID` (`bl_ID`),

  KEY `bl_ID_2` (`bl_ID`)

) TYPE=MyISAM;

 

# Table: ‘blocks’
#
CREATE TABLE `blocks` (

  `block_ID` int(11) NOT NULL auto_increment,

  `block_title` varchar(255) default ‘Block Title’,

  `block_file` varchar(255) default ‘0’,

  `plugin_ID` int(11) default ‘0’,

  `group_ID` int(11) default ‘0’,

  `site_ID` int(11) default NULL,

  `mod_ID` int(11) default NULL,

  PRIMARY KEY  (`block_ID`),

  UNIQUE KEY `block_ID` (`block_ID`),

  KEY `block_ID_2` (`block_ID`)

) TYPE=MyISAM;

 

# Table: ‘group_users’
#
CREATE TABLE `group_users` (

  `gu_ID` int(11) NOT NULL auto_increment,

  `group_ID` int(11) default ‘0’,

  `user_ID` int(11) default ‘0’,

  `site_ID` int(11) default ‘0’,

  PRIMARY KEY  (`gu_ID`),

  UNIQUE KEY `gu_ID` (`gu_ID`),

  KEY `gu_ID_2` (`gu_ID`)

) TYPE=MyISAM;

 

# Table: ‘groups’
#
CREATE TABLE `groups` (

  `group_ID` int(11) NOT NULL auto_increment,

  `group_name` varchar(255) default NULL,

  PRIMARY KEY  (`group_ID`),

  UNIQUE KEY `group_ID` (`group_ID`),

  KEY `group_ID_2` (`group_ID`)

) TYPE=MyISAM;

 

# Table: ‘module_status’
#
CREATE TABLE `module_status` (

  `ms_ID` int(11) NOT NULL auto_increment,

  `mod_ID` int(11) default ‘0’,

  `mod_status` varchar(255) default ‘not_initialized’,

  `site_ID` int(11) default ‘0’,

  PRIMARY KEY  (`ms_ID`),

  UNIQUE KEY `ms_ID` (`ms_ID`),

  KEY `ms_ID_2` (`ms_ID`)

) TYPE=MyISAM;

 

# Table: ‘modules’
#
CREATE TABLE `modules` (

  `mod_ID` int(11) NOT NULL auto_increment,

  `mod_name` varchar(255) default NULL,

  `mod_dir` varchar(255) default NULL,

  `mod_file` varchar(255) default NULL,

  `mod_author` varchar(255) default NULL,

  `mod_version` varchar(255) default ‘1.0’,

  `mod_type` varchar(255) default ‘public’,

  `plugin_ID` int(11) default NULL,

  PRIMARY KEY  (`mod_ID`),

  UNIQUE KEY `mod_ID` (`mod_ID`),

  KEY `mod_ID_2` (`mod_ID`)

) TYPE=MyISAM;

 

# Table: ‘plugin_status’
#
CREATE TABLE `plugin_status` (

  `ps_ID` int(11) NOT NULL auto_increment,

  `plugin_ID` int(11) default ‘0’,

  `plugin_status` varchar(255) default ‘not_initialized’,

  `site_ID` int(11) default ‘0’,

  PRIMARY KEY  (`ps_ID`),

  UNIQUE KEY `ps_ID` (`ps_ID`),

  KEY `ps_ID_2` (`ps_ID`)

) TYPE=MyISAM;

 

# Table: ‘plugins’
#
CREATE TABLE `plugins` (

  `plugin_ID` int(11) NOT NULL auto_increment,

  `plugin_name` varchar(255) default ‘0’,

  `plugin_dir` varchar(255) default ‘plugins’,

  `plugin_file` varchar(255) default ‘0’,

  `plugin_author` varchar(255) default ‘0’,

  `plugin_version` varchar(255) default ‘0’,

  `plugin_type` varchar(255) default ‘private’,

  `plugin_priority` int(11) default ‘0’,

  PRIMARY KEY  (`plugin_ID`),

  UNIQUE KEY `plugin_ID` (`plugin_ID`),

  KEY `plugin_ID_2` (`plugin_ID`)

) TYPE=MyISAM;

 

# Table: ‘sites’
#
CREATE TABLE `sites` (

  `site_ID` int(11) NOT NULL auto_increment,

  `site_name` varchar(255) NOT NULL default ‘0’,

  `site_host` varchar(255) default ‘0’,

  PRIMARY KEY  (`site_ID`),

  UNIQUE KEY `site_ID` (`site_ID`),

  KEY `site_ID_2` (`site_ID`)

) TYPE=MyISAM;

 

# Table: ‘template_users’
#
CREATE TABLE `template_users` (

  `tu_ID` int(11) NOT NULL auto_increment,

  `user_ID` int(11) default ‘0’,

  `t_ID` int(11) default ‘0’,

  `site_ID` int(11) default ‘0’,

  PRIMARY KEY  (`tu_ID`),

  UNIQUE KEY `tu_ID` (`tu_ID`),

  KEY `tu_ID_2` (`tu_ID`)

) TYPE=MyISAM;

 

# Table: ‘templates’
#
CREATE TABLE `templates` (

  `t_ID` int(11) NOT NULL auto_increment,

  `t_name` varchar(255) default NULL,

  `t_file` varchar(255) default NULL,

  `t_status` varchar(255) default ‘0’,

  `site_ID` int(11) default NULL,

  PRIMARY KEY  (`t_ID`),

  UNIQUE KEY `t_ID` (`t_ID`),

  KEY `t_ID_2` (`t_ID`)

) TYPE=MyISAM;

 

# Table: ‘users’
#
CREATE TABLE `users` (

  `user_ID` int(11) NOT NULL auto_increment,

  `user_name` varchar(255) default NULL,

  `user_pass` varchar(255) default NULL,

  `site_ID` int(11) default ‘1’,

  PRIMARY KEY  (`user_ID`),

  UNIQUE KEY `user_ID` (`user_ID`,`user_name`),

  KEY `user_ID_2` (`user_ID`)

) TYPE=MyISAM;

{mospagebreak title=Stop Blocking Me}

Now it’s about time to talk about the block system. Don’t worry if the concept escapes you; it’s taken me a lot of time and trial and error to get this all working right. Since I’m such a nice guy, I’m going to save you the trouble and tell you all about how it works so you don’t have to pull your hair out. The block system can be very confusing if you don’t pay close attention to what it does. Working with the block system can cause a lot of headaches. Now, on to the block system.

The block system is very intricate, so take your time when working with it or it can get very out of hand. At the beginning of our class, define all the variables that are going to be used in the class. Since PHP5 had been in beta 1 stage, it allows you to declare your variables at the beginning of a class with public or private. We want to define all ours as public so they can be used elsewhere in the site engine. Here are all the variables we’ll be using:

    public $bcount;
    public $blocks_list;
    public $columns;
    public $rows;
    public $cur_block;
    public $cur_tags;

As you can see, we’ll use nine variables through out the blocks plug-in. $bcount is a variable that will hold the number of blocks that have actually been loaded. $columns is the variable that we’re going to use to hold an array of each column that’s in our template. $rows is basically the same as $columns, but instead of holding an array of columns, it’s going to hold an array of the rows in each column, as we use the column. $cur_block will hold the data for the block that’s currently being parsed, in our script. $cur_tags is the array that will hold all the block information that will replace the tags in our block template, with the actual block information. $blocks_list is the most important — it’s an array that holds all the information about our blocks, information such as the block’s titles, content, what column to load in, and what order it is in the column.

Now run our SQL query to select all the blocks that will be shown on the current page. Start by making our function. Name it blocks() so that when the blocks class is called it will automatically run the function.

function blocks(){

Now declare all the variables that we’ll use as global. That way we’ll be able to use all the data from the other classes in this function. We’re also going to give our $bcount variable the value of 0 because we don’t have any blocks loaded just yet.

        global $sql,$plugins,$modules,$site,$user;

        $this->bcount=0;

Now that we’re started on the function, we want to start a loop to make part of our query; this particular loop is going to count the number of groups the user belongs to from the $user->user[‘gids’] variable that I’ll go over in a minute, then it’s going to get the value of each group ID. Then it will generate the part of our query that makes it so that only blocks authorized for users authentication level are selected. Just a personal tip here — when you make a script that generates a query or part of a query, it’s usually a good idea to echo the query to the browser to make sure it’s generating exactly what you want.

        $i=0;
        $b_grp=””;
        while($i<$count=count($user->user['gids'])){
            $b_grp.=”blocks.group_ID = ‘{$user->user['gids'][$i]}'”;
            $i++;
            if($i==$count){
                $b_grp.=” “;
            }else{
                $b_grp.=” OR “;   
            }
        }

Now it’s time for the meat of the blocks plug-in, the main SQL query. This query is fine tuned to select only the block information from the blocks that are supposed to be shown on the requested page, that meet a few prerequisites. For the blocks to be selected, the blocks must have a plug-in that’s been initialized; they must have a module that’s been initialized; they must have a site ID that’s the same as the ID of the site that’s requesting them; they must be within the authentication levels as the user requesting them; and they must be set to show up on the current page, or all pages. Another thing about the query is that we use the $b_grp, that contains our user group part of our query, in the query, as you can see in the following query.

        $blocks=$sql->_query(“SELECT
                                  `blocks`.`block_ID`,
                                  `blocks`.`block_title`,
                                  `blocks`.`block_file`,
                                  `block_location`.`block_col`,
                                  `block_location`.`block_row`,
                                  `plugins`.`plugin_dir`,
                                  `plugins`.`plugin_file`
                            FROM
                                  `block_location`,
                                  `blocks`,
                                  `plugin_status`,
                                  `plugins`,
                                  `module_status`,
                                  `modules`
                            WHERE
                                  (`blocks`.`block_ID` = `block_location`.`block_ID`) AND
                                  (`blocks`.`plugin_ID` = `plugin_status`.`plugin_ID`) AND
                                  (`plugins`.`plugin_ID` = `plugin_status`.`plugin_ID`) AND
                                  (`plugin_status`.`plugin_status` = ‘initialized’) AND
                                  (`blocks`.`mod_ID` = `module_status`.`mod_ID`) AND
                                  (`modules`.`mod_ID` = `module_status`.`mod_ID`) AND
                                  (`module_status`.`mod_status` = ‘initialized’) AND
                                   $bgs
                                  (`block_location`.`site_ID` = ‘{$site->site['ID']}’) AND
                                  ((`block_location`.`block_page` = ‘all’) OR
                                  (`block_location`.`block_page` = ‘{$site->site['page']}’))
                            ORDER BY
                                  `block_location`.`block_col`,
                                  `block_location`.`block_row`”);

After the query returns the results in the $blocks variable, get the information from it with the _fetch_object() function that we made in the SQL class; this enables us to call the results from the query as an object. Then we’ll rebuild it into another array that will be a little more user friendly to us later on in the project. The reason the new array is easier to use is because we set the array keys to the coordinates of the block; that way the engine knows that the block in the array that’s set as $blocks_list[1][2] (read as blocks list column 1 row 2) as the 2nd block down in the 1st column.

        while($block=$sql->_fetch_object($blocks)){
            if(file_exists(“{$block->plugin_dir}/{$block->plugin_file}/blocks/{$block->block_file}.blk.php”)){
                $this->blocks_list['column'.$block->block_col][$block->block_row]['block_path']=”{$block->plugin_dir}/{$block->plugin_file}/blocks/{$block->block_file}.blk.php”;
                $this->blocks_list['column'.$block->block_col][$block->block_row]['block_ID']=$block->block_ID;
                $this->blocks_list['column'.$block->block_col][$block->block_row]['block_title']=$block->block_title;
            }else{
                $this->blocks_list['column'.$block->block_col][$block->block_row]['block_path']=”plugins/missing/blocks/404.blk.php”;
                $this->blocks_list['column'.$block->block_col][$block->block_row]['block_ID']=$block->block_ID;
                $this->blocks_list['column'.$block->block_col][$block->block_row]['block_title']=”Missing Block”;
            }
        }

For example, if I were to replace the variables in the following code with their values, for the 1st block in the 2nd column, the result would look like this:

$this->blocks_list['column2'][1]['block_path']=”plugins/test/blocks/test.blk.php”;
$this->blocks_list['column2'][1]['block_ID']= “4”;
$this->blocks_list['column2'][1]['block_title']=”Test Block”;

The reason we use the actual words “column1”, “column2”, and “column3” instead of just using the column numbers is because those will also be used by the template plug-in to replace tags with the blocks in the correct column. After we build the $blocks_list array, we’ll go ahead and call the function parse_blocks() which actually puts the blocks in loading order, then “packs” them (as I’d say) and sends them over to the template plug-in that we’ll be discussing in the new article.

        $this->parse_blocks();
    }

The parse_blocks function is an important part of the blocks plug-in, if not the most important. What it does is it looks at the list of blocks that we just put together in the blocks() function, and it puts them in order by column, then by row. After that it calls the block’s template data from the template plug-in, parses it all into an array that contains strings of data that can be outputted to the browser, named $cur_column. Each array key of $cur_column is named as the column name that the data will be put into. After the array is built we send all the data to the template plug-in where it gets used and then sent to the browser.

This function is actually a fairly simple process. First we name the function, and set up our global variables.

    function parse_blocks(){
        global $template,$plugins;

Then we check to make sure the $blocks_list array have been built without errors

        if(is_array($this->blocks_list)){

If the array looks like it’s all right, we then get the keys from the array that contains all the column names that is produced by the template plug-in, and set them to the values of another array called $columns. We then start a for each loop that will select each element from the $columns array, that will then make yet another array based on the keys from the specified column element of the $blocks_list array. We then start another for each loop that will process the data each block in the $blocks_list array.

            $this->columns=array_keys($template->columns);
            foreach($this->columns as $column){
                $this->rows=array_keys($this->blocks_list[$column]);
                foreach($this->rows as $row){

The easiest way to work with the current block at this level is to set it as the value of another easier to work with variable, we’ll call it $cur_block,

                    $this->cur_block = $this->blocks_list[$column][$row];   

After that’s all taken care of, we’ll go ahead and get “down and dirty” with our block data. First, set up a temporary array of all the data for the current block; at the moment the only data we’re worried about is the block title, the block content, and the block ID.

                        $this->cur_tags=array(‘block_title’=>$this->cur_block['block_title'],’block_ID’=>$this->cur_block['block_ID']);

To get the file that contains the block’s content, we’ll have to include it and set it to a variable. That will contain the data after it’s been included. However, it’s a little tricky to do that because if you just include it, all the data from the include will show up at the top of the page instead of in the block. To fix this we turn to output buffering. Traditionally output buffering will stop all data output to the browser until it’s told to stop buffering, at which time it will send all the data that it stopped to the browser. In this case we’ll still tell it to stop buffering, but instead of having it send the data to the browse, we’re going to tell it to get the contents of the buffer, set it as the value of a variable then flush (also known as empty) the current buffer. This way we get all the contents of our include without it sending the data directly to the browser. After we have the content of our block, we’ll add that to the $cur_tags array.

                        ob_start();
                        @include($this->cur_block['block_path']);
                        $bdata=$template->replace($this->cur_tags,ob_get_clean());
                        $this->cur_tags['block_content']=$bdata;

Don’t worry it’s winding down to the end of the blocks plug-in. now we have all our block data right where we want it. So what we’re going to do with it is pass all the info to the replace() function that we have set up in our templates plug-in which will fill in all the tags in our block template with the data in the $cur_tags array. Then it will assign it to the $cur_column variable, using the column name as the array key. Then after all the loops are finished we’ll send all the data to the function that builds the columns and outputs it all to the browser in the, in the templates plug-in.

                        $this->cur_column[$column].=$template->replace($this->cur_tags,$template->loaded->block);
                        unset($this->cur_block,$this->cur_tags,$bdata);
                        $this->bcount++;
                }
            }
            $template->build_cols($this->cur_column);
        }       
    }

}

{mospagebreak title=Insert Username Here}

The user authentication is relatively easy to work with — easier than the blocks. The most important thing about the user authentication is the security behind the login. The password is the most vulnerable piece of information because that’s what everyone wants to get too. Setting cookies can be a security risk, if you don’t use them correctly. With the following authentication methods, you’ll be able to set a cookie that is secure, and on top of that you can use this authentication for every site that you host with the site engine not the mention you make one array that you can use anywhere in the engine to get information about the user. So let’s get started.

First we need to define our class and the variables we’ll be using. $usr will hold all the information about a user in an array. $username will hold the username passed to the class by the login form, and $userpass will, of course, hold the user’s password that is passed to the script by the login form.

class user{
   
    public $user;

    public $username;
    public $userpass;

Unlike the other classes in the site engine, we want to name the actual login function something different than the class name because we don’t want the login function to be called when the class is called. So, I named it __check_login().n Also set up the global variables we’ll be using and set up the password to match the md5’s password that’s stored in the database. MD5 isn’t really an encoding, rather it’s the calculated hash of the string that’s passed to it.

     function __check_login(){
        global $sql,$site;

  $pass = md5($this->userpass)

Now get all our user’s information from the database. Put the query function into the fetch_row() function to kill two birds with one stone. After the query has been run, check to make sure that the data returned is an array — as it should be if it was a successful login. Then set the information in the $user with the corresponding information that we got from the database.
 
        $cl=$sql->_fetch_row($sql->_query(“SELECT `users`.`user_ID`,`users`.`user_name`,`users`.`user_pass`,`users`.
`site_ID`FROM`users`WHERE (`users`.`site_ID` = ‘{$site->site['ID']}’) AND (`users`.`user_name` = ‘$this->username’) AND (`users`.`user_pass` = ‘$pass’) limit 1″));
        if(is_array($cl)){
            $this->user['ID'] = $cl[0];
            $this->user['name'] = $cl[1];
            $this->user['pass'] = $cl[2];
            $this->user['site'] = $cl[3];

In comes the group users table in the database; we wouldn’t be able to do the next step with out it. After the user’s information is pulled from the users table in the database successfully, run the following query to get the information about what authentication groups the user belongs too. Since all users belong to at least two user groups, the result would be an array, so once again use the fetch_object function to get the results, only this time put it in a while loop. Then during the while loop, set the values to the user[‘gids’] array.

            $gids=$sql->_query(“SELECT `groups`.`group_ID `FROM`group_users`,`groups`WHERE(`groups`.`group_ID` = `group_users`.`group_ID`) AND (`group_users`.`user_ID` = ‘{$this->user['ID']}’) AND (`group_users`.`site_ID` = ‘{$this->usr['site']}’)ORDER BY`groups`.`group_ID` DESC”);
            while($gid=$sql->_fetch_row($gids)){
                $this->user['gids'][] = $gid[0];
            }

After the while loop, check to see if the user[‘gids’] has any values in it, if it doesn’t, assign the group ID’s for a visitor, manually.

            if(!isset($this->user['gids'])){
                $this->user['gids'][] = “1”;
                $this->user['gids'][] = “2”;
            }

Finally after the group ID’s are taken care of, end the if statement that we started when checking to see if the results from selecting the users information, with an else so that way, if the user’s login wasn’t successful, manually apply the information for a visitor to the user array. This doesn’t bar the user from trying to login again; it just sets the user array to what I like to think of as “default settings”.     

        }else{
            $this->user['ID'] = 0;
            $this->user['name'] = “Guest”;
            $this->user['site'] = $site->site['ID'];
            $this->user['gids'][] = “1”;
            $this->user['gids'][] = “2”;
        }
    }

}

Final Thoughts

If you thought the blocks plug-in was confusing, just keep looking at it and thinking of it line by line. Follow the variables and you’ll understand it really fast. It’s quite logical in the way it works, and it teaches a lot about arrays and how they work. The authentication plug-in also shows a few nice ways to work with and utilize arrays in your everyday scripts.

The file system can be changed to your likings — just remember if you change it, that you’ll have to update the plug-ins and module systems, and the blocks plug-in. If you have any confusion when working with the file system, it’s always a good idea to make a test plug-in, a test module, and a test block. That way you can follow it by replacing the variables with the name of your test files to be sure of where the file should be.
 
In the next article, I’ll be telling you about the template system and how to get your actual engine up and running in different environments. In the fifth and final article I can walk you through making a small site on your site engine, and hopefully by then you’ll be able to see fully what it does, how it does it, and why it does those things. By then you should be able the expand it to fit your needs.

[gp-comments width="770" linklove="off" ]

chat sex hikayeleri Ensest hikaye