About the technical set up of the website
This memo describes the “architecture” of my www.gerben.org website. It explains what blocks a webpage consists of, and how the scripting generates these blocks. The goal is that you get an idea of how I implemented this website. Occassionally, I slightly simplified the description, as not to complicate it with distracting details.
I am assuming that you have some familiarity with PHP and HTML. If you don't know PHP or HTML, you can find many good tutorials on the Web for free (just google on “php tutorial” or “html tutorial”).
Each webpage of my website uses the same blocks. The figure shows these blocks. The blocks are:
As you may have noticed when browsing my website, all pages use the same top-level template file: index.php. This file is actually quite simple. It defines the blocks mentioned above and either directly provides the contents of a block or it calls a PHP function for that. Defining a block is nothing more than enclosing the block's contents in an HTML DIV tag. For example:
The index.php file references a cascading style sheet file (.css-file) that uses these HTML divisions to properly layout the page. The nice thing about cascading style sheets is that the content and the layout are nicely separated. Here I will focus on the content part and not on the layout part.
As the header and footer are fixed for all pages, the index.php file itself simply contains the header and the footer. The menu and the main content blocks are different for each page, and therefore the index.php file calls PHP functions that generate the contents of these blocks. To know what content to generate, these functions need to know what the currently requested page is. This information is in the id parameter of the URL. For example, if the URL is www.gerben.org/index.php?id=GettingToYes, then the currently requested page is 'GettingToYes'. The scripting stores the id parameter in the $menuId variable.
The index.php file contains the following code for the main content:
<?php gsEchoPageMainContent($menuId); ?>
And the code for the menu block is similar:
<?php gsEchoMenuLines($menuId); ?>
Before discussing how the gsEchoPageMainContent and gsEchoMenuLines functions work, it useful to have an understanding the menu meta data. You read about this in the next section.
The menu meta data is all stored in a single file: menu.ini. This file has the same structure as a Windows .ini-file. Here is the part of the file's content describing the GettingToYes page (or menu item; note how you can use page and menu item interchangeably, as each page has a menu item and vice versa):
name="Getting to YES"
booktitle="Getting to YES"
booksubtitle="Negotiating Agreement Without Giving In"
bookauthors="Roger Fisher, William Ury & for the second edition Bruce Patton"
The first line (“[GettingToYes]”) indicates the id of the menu item. All other lines describe attributes of that menu item. Each menu item must have at least the 'name', 'parent' and 'content' attributes. The scripting uses the 'name' and 'parent' attributes for constructing the menu block. The 'name' attribute contains the name to display in the menu, and the 'parent' attribute contains information on the menu hierarchy.
The scripting uses the 'content' attribute when generating the main content block. All other attributes are specific to the PHP file mentioned in the 'content' attribute. I come back to this later.
Here are two other example entries in the menu.ini file:
categorytitle="Boeken over onderhandelen"
The scripting needs to load and parse the menu.ini file, so it can use the information in the file. For this, the scripting uses only a single, standard PHP function parse_ini_file:
$menu = parse_ini_file("menu.ini", TRUE /*process sections*/);
The second parameter (the 'process sections' flag) makes it a two-dimensional associative array instead of a one-dimensional one. The first dimension is the sections (a section has a header like [GettingToYes]) and the second dimension is the attributes of a section. (If the flag were FALSE, then the function simply ignores the section headers.)
To get an attribute of a menu item, a simple expression like
suffices. Note that this expression evaluates to 'Onderhandelen'.
I am quite happy with the use of the ini-file. Initially, I contemplated using XML for the menu meta data, but the use of the ini-file is so much simpler.
The function that generates the menu block (gsEchoMenuLines($menuId)) queries the menu meta data, and based on the queries it outputs (echoes) the menu lines. Only the menu items towards the current menu item are expanded, all other menu items do not show their sub-items.
The function that generates the main content (gsEchoPageMainContent($menuId)) is extremely simple: it just queries the menu meta data for the 'content' attribute, and then includes that file. The 'content' attribute typically points to a template file (e.g. booktemplate.php). By including that file the PHP scripting in that file gets executed.
Consider for example the booktemplate.php template. It is the template used for all book reviews on the site. The template queries the menu meta data for a whole range of attributes (all of them have the 'book' prefix), and the scripting generates contents based on these attributes. Some attributes are mandatory for the booktemplate.php template (e.g. booktitle), while others are optional (e.g. booktitle_nl, which indicates the availability of a dutch translation). The template checks whether the booktitle_nl attribute is available. Only if that is the case, it outputs information on the dutch translation.
The book template also checks for the presence of the bookimage attribute. If present, it inserts the image, properly scaled, to the page.
As you may have noticed, the menu meta data contains all information needed to generate a page, except for one important piece of information: the main text block (in case of the booktemplate.php template, this contains the actual book review). All text blocks are part of a single file textblocks.html. This file is well-formed HTML file, which makes it easy to edit with a standard HTML WYSIWYG editor (I use NVU). Each text block in it must start and end with a specific marker: '<H1>BEGIN_NameOfTextBlock</H1>' and '<H1>END_NameOfTextBlock</H1>'. For example, the text block for the Getting To Yes page starts with '<H1>BEGIN_GettingToYes</H1>' and ends with '<H1>END_GettingToYes</H1>'. The HTML H1 tags are there to make the markers stand out when editing the textblocks.html file. Typically, the name of the text block is the same as the name of the menu item.
The following code shows how the scripting retrieves a text block from the textblock.html file. It uses a regular expression to find the markers and the enclosed text block. The two standard PHP functions used are file_get_contents and preg_match. The $name parameter is the name of the text block (e.g. 'GettingToYes').
// Load complete file with single function call
// Build matchstring (/s option means:
// treat string as a single line)
$matchstring="/<[hH]1>\\s*BEGIN_$name.*?[hH]1>" . '(.*?)' . "<[hH]1>\\s*END_$name.*?[hH]1>/s";
// The textblock result array contains the complete match in
// $textblock (i.e. the wanted text block including
// the markers) and the match of the first parenthesis
// clause in $textblock (i.e. the wanted text block
// without the markers)
preg_match($matchstring, $allTextBlocks, $textblock);
Next to the booktemplate.php template, there are also – among others – the categorytemplate.php and defaulttemplate.php templates. The category template queries the menu meta data to generate a page that gives an overview of the childs of the current menu item. The default template is a template that resembles the booktemplate.php template, only it is generic and not specific to books.
The website scripting and set up results in a website that is easy to maintain and extend. Adding a new page is nothing more than adding an entry in menu.ini and adding a text block in textblock.html. It is also easy to add a new content template if needed.
Note that both the menu meta data and the text blocks implementation are hidden behind a simple and clean interface (gsGetPageAttr($menuId, $attrName) and gsGetTextBlock($name)). This means that they can easily be replaced with a different implementation, like one using a MySQL database, if and when needed.