FreeBSD Programming Primer – Part 4

}

May 28, 2013

In the fourth part of our series on programming, we will continue to develop our CMS. Here we will examine how a modern CMS dynamically generates and controls content and implement a similar model in our PHP code.

What you will learn
How to configure a development environment and write HTML, CSS, PHP, and SQL code

What you should know
BSD and general PC administration skills
In the early days of the World Wide Web, HTML pages were literally handcrafted masterpieces of content. Before applications such as Dreamweaver arrived that allowed content providers to design attractive pages with the ease of a document produced in a word processor, it was a matter of writing copious amounts of HTML for each page, checking that the links and the HTML were correct, and repeating for each page. This model was highly inefficient, as not only was a lot of the HTML repeated across pages, the chances of errors coming in and either causing the page to render incorrectly or pointing to the wrong address became greater as the site grew. Managing a website with 100 pages is possible; a website with 10,000 pages a nightmare.

The complex sites we see today on the Internet would be impossible without the Content Management System. Yet even now, large innovative sites are moving away from the CMS model toward frameworks that consider the locally provided content to be only a part of the website with 3rd party content supplying a significant proportion of the content.

While the technology meets the ethos of the web in that data can be shared freely, it poses the web designer and brand manager with a huge challenge – how can we take disparate pieces of content and serve these in a “wrapper” that to our website visitors appears as if it seamlessly represents our brand values? How can we divorce the business process from the presentation? Is it possible for a website to develop a unique “personality” while at the same time remaining fresh, dynamic and easily changeable?

These hurdles are being overcome with the use of CSS (Cascading Style Sheets) and templating technologies. While the CSS manages the color, fonts, size, etc. of the content, templates allow us to adjust the order and visibility of the content. For example, we want to generate widely different content (both from a stylized and literal content perspective) depending on website section, page number and content type. See Figure 1 – Page generation process.

MySQL Interface
As it is important that we can quickly test our CMS, for those that would prefer the “Cut, Paste and Click” approach rather than managing long SQL statements via the command line, you can use a lightweight web-based database manager. The lightest of these (a single PHP page) is Adminer. An alternative is SQL buddy, and either of these can be quickly installed if desired by downloading the archive and extracting into a folder under the /usr/home/dev/data. The web-based interface can then be accessed from: http://myserver/dirname. See Table 1 – Useful links.

Adding New Content Types

At the moment, we only have one content type – a page. This is stored in the pages table and holds the following content as shown in Table 1.
Table 1.
id title h1 body
1 My first page Page header Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris interdum auctor tellus sed dignissi…

This results in the following output as seen in Figure 2:

Now let us create a second page in our database: Method 1 – Via CLI: $ mysql -uroot -p’cms-password’; mysql> use freebsdcms; mysql> INSERT INTO `pages` (`title`, `h1`, `body`) -> VALUES (‘My second page’, ‘H1’, ‘2’); Method 2 – Via saved SQL statement: If you prefer, create a SQL file createpage2.sql in the SQL directory with the following content: USE freebsdcms; INSERT INTO `pages` (`title`, `h1`, `body`) VALUES (‘My second page’, ‘H1’, ‘2’); Then execute this at the command line: $ mysql -uroot -p’cms-password’ < createpage2.sql Method 3 – Via Adminer / SQL Buddy: Alternatively use the SQL command function in Adminer to execute the following SQL statement: INSERT INTO `pages` (`title`, `h1`, `body`) VALUES (‘My second page’, ‘H1’, ‘2’); Houston, We Have a Problem We now have two pages in our database, but index.php still contains the following code: // Build page – use first record in database $page[‘id’] = 1; buildpage($page); This hard-wires index.php to only serve a page with an ID of 1. Depending on the URL passed to the webserver, we want to serve that type of content. E.g. http://mysite/pages/1 will serve a page with ID of 1, whereas http://mysite/faqs/1 will serve an FAQ with an ID of 1, etc. Visiting http://mysite will return the home page (Page 1). This leads us to the next problem – where do we store the content types? We could include this in a separate MySQL table, but this would require an additional SQL query to be executed every time a page is loaded. As content types will not be changed very often, we can create another include file that defines our content types. We can then automatically use a custom template depending on the content type to post process our specific content. First of all, we need to make some modifications to Apache so that it serves our index.php page as default. Edit the line in /usr/local/etc/apache22 /httpd.conf to match the following: DirectoryIndex index.php Find the section marked and add the following: # # Redirect on error via our CMS # ErrorDocument 401 /index.php ErrorDocument 403 /index.php ErrorDocument 404 /index.php ErrorDocument 500 /index.php This will force all traffic to be passed to our index.php for processing. As root, delete our unwanted files then restart Apache: $ rm /home/dev/data/index.xhtml $ rm /home/dev/data/index.html $ apachectl restart When you visit http://mysite or http://mysite/, page 1 should be displayed. Now for the modifications that will facilitate content type routing and theme control. Create a file in the includes directory called content.inc with the content from Listing 1. Listing 1: title <!–?php
/*
*
* content.inc
* Defines content types for our CMS
*
*/
// Define the content type. This must match any tables defined in our
// CMS
$content_types[] = 'page';
$content_types[] = 'faq';
$content_types[] = 'news';
// Map each content type to a table. Each content type must be matched
// to a corresponding table
$content_tables['page'] = 'pages';
$content_tables['faq'] = 'faqs';
$content_tables['news'] = 'news';
Create the following template file pages_template.inc in the templates directory shown in Listing 2.
Listing 2. title
tag is mandatory * */ render($theme[‘title’]); render($theme[‘debug’]); render($theme[‘h1’]); render($theme[‘timestamp’]); render($theme[‘body’]); render($theme[‘licence’]); ?> Remove the following section entirely from index.php: // Build page – use first record in database $page[‘id’] = 1; buildpage($page); Replace with the one shown in Listing 3. Listing 3. title // First we need to parse the URL that was passed to us to extract the // id and the content type. $URI = $_SERVER[‘REQUEST_URI’]; if($URI == ‘/’){ // If this is a request to root (/) redirect to page 1 $request = array(‘pages’,1); buildpage($request); }else{ // Parse the request, if it is valid get the content from our DB $request = parse_request($URI); if(!is_null($request)){ buildpage($request); }else{ echo “Invalid request”; } } Remove entirely the function call buildpage($page) from core.inc. Replace with the code shown in Listing 4. Listing 4. title function buildpage($request) { // Content definitions require INCLUDES.’content.inc’; // Routes our incoming request to the right content type and pulls // the content from out DB. $content_type = $request[0]; $id = $request[1]; $template_file = TEMPLATES . $content_type . ‘_template.inc’; // Build the SQL and get the result $sql = “SELECT * FROM $content_type WHERE id=’$id’ LIMIT 1”; $result = mysql_select($sql); // Check we have some content to display if($result[0] == 0){ echo ‘No data’; return; } // Check we have a template file if(!file_exists($template_file)){ echo ‘No template’; return; } // Don’t write any output to browser yet as we want to post process // our content using a theme. If enabled use our optimization // callback to remove white space etc. ob_start(“optimize_callback”); // Output our page header outfile(TEMPLATES . ‘header.inc’); // Create our body echo wraptag(‘title’, $result[‘title’]); echo HEAD; echo BODY; // Generate a unique ID based on content type // Map the requested content type from our real table name $ct = array_search($content_type, $content_tables); echo ‘

‘; // If we are in debug mode, show an alert if(DEBUG){ $theme[‘debug’] = div(‘¶’, ”, ‘debug’); } // Dump the title & id out to our theme template $theme[‘id’] = $result[‘id’]; $theme[‘title’] = $result[‘title’]; // As we don’t know how many fields we will have in our content // type after our id, iterate through each in turn and wrap // the field with a div $offset = $result[1] – 1; $pos = 0; foreach($result as $key => $value){ if($pos > $offset){ $theme[$key] = div($result[$key], $key.’-‘.$id, $key); } $pos ++; } // Add our standard copyright notice $theme[‘licence’] = div(ahref(COPYRIGHT, LICENCE, ‘Copyright and licence details’),”,’licence’); // Include our template file require_once($template_file); // Close our content type tag echo ‘

‘; // Output our HTML page footer outfile(TEMPLATES . ‘footer.inc’); // Flush it all out and display ob_end_flush(); } Add the function calls from Listing 5 to the end of core.inc. Listing 5. title function parse_request($URI){ // Returns the type of content and the ID // of the content requested. // parse_request(/page/1) // $array[‘page’][1] // parse_request(/rubbish/123456) // NULL // Content definitions require_once INCLUDES.’content.inc’; $ct = NULL; $id = NULL; $valid = 0; // Fetch the parameters from the URL $array = explode(‘/’,$URI); // We don’t need the first ‘/’ – delete the first empty // array item $a = array_shift($array); // Check we have 2 parameters $paramcount = count($array); if($paramcount == 2){ // First test passed – We have 2 parameters $valid ++; $ct = $array[0]; $id = $array[1]; } if(in_array($ct,$content_types)){ // If content type matches our list second test passed $valid ++; // Map the requested content type to our real table name $array[0] = $content_tables[$ct]; } if(is_numeric($id)){ // If ID is a number, third test passed $valid ++; } if($valid == 3){ // Valid parameters passed, return content type and page ID return $array; }else{ // Test failed – return NULL return NULL; } } function optimize_callback($buffer){ // Replace all spaces and cruft between tags if(OPTIMIZE){ $b = preg_replace(‘~>s+<~’, ‘><‘, $buffer); $b = preg_replace(‘/rn|r|n/’,”,$b); $b = preg_replace(‘!s+!’, ‘ ‘, $b); return $b; } } Replace mysql.inc with the Listing 6. Listing 6. title connect_errno > 0){ die(‘Unable to connect to database [‘ . $db->connect_error . ‘]’); } if(!$result = $db->query($sql)){ if(DEBUG){ die(‘There was an error running the query [‘.$db->error.’]’); }else{ die(”); } } // Pass our results to an array to be returned $r = array(); $r[] = $result->num_rows; // No of rows returned $r[] = $db->field_count; // No of columns in table $r[] = $db->affected_rows; // No of rows affected e.g. update / delete // Append the results to our result count if($result->num_rows != 0){ $r = array_merge($r, $result->fetch_array(MYSQLI_ASSOC)); } // Free the result $result->free(); // Close the connection $db->close(); return $r; } Replace html.inc with Listing 7. Listing 7. title <!–?php
/*
*
* html.inc
* Contains core html functions for our CMS
*
*/
function wraptag($tag, $text) {
// Wraps $text with compliant tags
// wraptag('p',sometext)
//

sometext
 
return ‘<‘ . $tag . ‘>’ . $text . ‘‘; } function div($divcontent, $class, $id = ”) { // Generates a div tag $text with compliant tags // div(‘content’,’class’) //

content

// div(‘content’,’class’,’id’) //

content

// div(‘content’,”,’id’) //

content

// div(‘content’,”,”) //

content

if ($id != ”) { $id = ‘ id=”‘ . $id . ‘”‘; } if ($class != ”) { $class = ‘ class=”‘ . $class . ‘”‘; } return ‘<div’ .=”” a=”” an=”” click=”” compliant=”” divcontent=”” function=”” generates=”” href=”http://freebsd.org” id=”” tag=”” tags=”” text=”” title=”Click here” with=””>
Click here // ahref(‘Click here’,freebsd.org,’Link title’) // Click here if ($title == ”) { $title = $url; } $ahref = ‘‘ . $text . ‘‘; return $ahref; } function render($field){ // Renders via template echo $field; } Append the following to cms.inc: // Optimize output by removing white space between tags etc. define(“OPTIMIZE”, true);
Testing and Adding New Content
That is a lot of code we have added, but we now have a major jump in functionality. We can create any number of content types now by creating a new table (e.g. faq, news, etc.) The only essential fields we must define are ID and TITLE. After these two fields you may define as many or as few as you require. You will need to create a matching template file with the fields you want to display or else the content will be unable to render. Once you have added new records to your content type (Adminer makes this quick and easy), the content can be accessed via your browser at: http://mysite/mycontenttype/mypageid. If you attempt to access invalid content, you will be presented with a rudimentary error message.
In the next article in the series, we will look at theming in detail and how we can lay out the site using a combination of templates and CSS.

Useful Links:

  • SQL buddy – http://sqlbuddy.com
  • Adminer – http://www.adminer.org
 Errata
 In the previous article of this series the following syntax was incorrect:
 #dev mysql -u root password 'cms-password' < createdb.sql
 #dev mysql -u root password 'cms-password' < createpagetbl.sql
 #dev mysql -u root password 'cms-password' < createpage.sql
 It should have read:
 #dev mysql -u root -p'cms-password' < createdb.sql
 #dev mysql -u root -p'cms-password' < createpagetbl.sql
 #dev mysql -u root -p'cms-password' < createpage.sql
 Our apologies.

Bio
Rob Somerville has been passionate about technology since his early teens. A keen advocate of open systems since the mid-eighties, he has worked in many corporate sectors including finance, automotive, airlines, government and media in a variety of roles from technical support, system administrator, developer, systems integrator and IT manager. He has moved on from CP/M and nixie tubes but keeps a soldering iron handy just in case.

Join iX Newsletter

iXsystems values privacy for all visitors. Learn more about how we use cookies and how you can control them by reading our Privacy Policy.
π