Website Forum Integration

Simple Machines Website-Forum Integration
We get asked quite a bit about which CMS (Content Management System) or Portal we use here at Simple Machines. The answer is that we don't! We use the (Server Side Include) SSI.php file that is included with SMF. However, in this case, we wrap SSI.php in another file called integrate.php in order to simplify the entire process.

The goals when setting up the integration was:
 * Make full use of SMF's layer and template system.
 * Consolidate redundant code into a central file.
 * Make each file easy to create and update.
 * Allow full flexibility while doing the above.

Each file is broken up into 4 main parts
 * 1) Setup variables
 * 2) Bringing in integrate.php
 * 3) A source function
 * 4) A template function

The integrate.php file can handle many variables — the current version has 10 — but most pages use two main variables. These variables mainly set the default values so it doesn't need to override them later.

Bringing in integrate.php is simply a matter of using a require_once with the path to the file.

The source function can do anything a normal SMF source function can do. So it may query the database to get some data, do some calculations, redirect to another page, whatever is needed. Just like in SMF it is important to keep your source type of actions — collecting and organizing data, doing calculations, doing massive queries, etc — from your display. However not every file actually needs a source function, for example our About page just outputs text, so a setup variable was setup in order to turn that off.

The template function is what actually displays whatever it is we want to display. It is called just like a SMF template function is called.

integrate.php
The integrate.php file is where the majority of the complex code is located, even then its not that complex. So let me break it down for you.

<?php global $siteurl; $siteurl = 'http://www.simplemachines.org'; $context['siteurl'] = $siteurl; Here we just want to setup a variable that stores where our website is located. Useful for when you need to output a URL.

if (basename($_SERVER['PHP_SELF']) == 'integrate.php') {	header('Location: ' . $siteurl); die; } The integrate.php file doesn't actually display anything useful if you navigate to it. In fact it would cause some errors since the source and template functions aren't defined. As such we just want to redirect people away from the file.

$ssi_theme = 10; $context['outside_forum'] = true; $ssi_maintenance_off = true; require('ABSOLUTE PATH/community/SSI.php'); Here we setup a few variables for SSI.php itself. The ssi_theme variable sets the actual theme to use while the $ssi_maintenance_off means that even if the forum is in maintenance mode the site will still work. The $context['outside_forum'] is used by the templates that serve both the website and the forum in order to change output depending on location.

if (!empty($redirect)) redirectexit(str_replace('{SITEURL}', $siteurl, $redirect)); We have a couple of files that we keep around for legacy purposes, but that are covered in other files. So we just redirect the user to those files.

// Is this page restricted? if (!empty($pagePermissions)) isAllowedTo($pagePermissions); This allows use to restrict access to a particular page by permission. I don't think I've used it yet, but it's nice to know the functionality is there if I want to use it.

if (!empty($pageIsIndex) && empty($user_info[$pageIsIndex])) redirectexit($siteurl); We have some pages that are for the team only and this provides an easy way to limit access to those pages.

// Ok we are outside of the forum so load up some more stuff. loadTemplate('SiteTools'); There are some functions that we only need when outside of the forum, so we put them into their own template file and load it when we need to.

$callback = isset($callback) && $callback === false ? false : (!empty($callback) ? $callback : 'file_source'); This sets the name of the source function The first thing it checks for is if the variable was set to false, if that is true that means the file doesn't need a source function. Otherwise it looks to see if the callback function name was given and if so uses that, if not it uses the default name, which most files use.

$context['sub_template'] = !empty($template) ? $template : 'file_template'; Just like the source function except in this case there is no option to not set a template function.

if (!empty($section)) $context['section'] = $section; $context['section'] sets the active part of the site wide menu. If no value is specified the menu function itself sets a default.

if (!empty($menulink)) $context['menulink'] = $menulink; On pages with a side menu, this sets the active menu item. If none is set then it doesn't select an active link.

if (!empty($sectionheader)) $context['sectionheader'] = $sectionheader; The section header is the text that is above the menu bar and below the user bar.

$pagetxtIndex = isset($pagetxtIndex) ? $pagetxtIndex : (!empty($menulink) ? $menulink : ''); One of our goals is to have our entire site using a language file which might one day be translated. So this provides a way of selecting the set of strings for this particular page. Normally the menulink is enough, but this provides a way to override it (for example pages that share the same menu link).

if (!empty($section) && !empty($pagetxtIndex) && isset($sitetxt[$section][$pagetxtIndex])) {	$pagetxt = $sitetxt[$section][$pagetxtIndex]; } Our text strings are stored in a multiple dimension array. In order to make using them easier we map $pagetxt to a particular $sitetxt sub array. But we only do this if the needed information is provided.

$context['page_title'] = !empty($title) ? $title : (!empty($pagetxt) && !empty($pagetxt['title']) ? $pagetxt['title'] : 'Simple Machines LLC'); This simply sets the page title. This actually allows two easy ways of setting the page title: give $title a value or have a title index in the pagetxt variable. The “Simple Machines LLC” is just there as a backup, so we'll always have a page title.

if (empty($noSideBar)) $context['template_layers'][] = 'menubar'; This allows us to not include the menu bar — like the home page. The menu bar itself is just another layer that is included. This makes the page itself really clean and also helps ensure consistency.

if ($callback !== false) call_user_func($callback); Unless we already specified we don't have a source function we call it here and get to the good stuff.

obExit; ?> obExit is responsible for calling the templates in the correct order and it also serves as an ending point to the script. Also obExit provides a some functionality that is important to do in SMF 2.0.

Some examples
Ok now that I've hit you with the hard stuff lets take a look at two pages that use it. The first is index.php for our home page.

<?php

$noSideBar = true; $section = 'index'; $pagetxtIndex = 'home'; This is all of our setup variables. We turn off the side bar, set the section and pagetxt index.

require_once('integrate.php'); This starts the whole process.

function file_source {	global $context, $db_prefix, $smfFunc;

$request = $smfFunc['db_query']('', "	SELECT		m.smileys_enabled, m.poster_time, m.id_msg, m.subject, m.body, t.id_topic, t.id_board,		b.name AS bname, t.num_replies, m.id_member, mem.real_name AS poster_name	FROM {$db_prefix}topics AS t, {$db_prefix}messages AS m, {$db_prefix}members AS mem, {$db_prefix}boards AS b	WHERE t.id_board = 1		AND t.is_sticky = 1		AND b.id_board = 1		AND m.id_msg = t.id_first_msg		AND mem.id_member = m.id_member	ORDER BY t.id_topic DESC	LIMIT 4", __FILE__, __LINE__);

while ($row = $smfFunc['db_fetch_assoc']($request)) {		$bodylen = 200; // Limit the length of the message, if the option is set. if (strlen(str_replace(' ', "\n", $row['body'])) > $bodylen) $row['body'] = htmlspecialchars(substr(str_replace(' ', "\n", un_htmlspecialchars($row['body'])), 0, $bodylen-3) . '...', ENT_QUOTES);

$row['body'] = parse_bbc($row['body'], $row['smileys_enabled']);

censorText($row['body']); censorText($row['subject']);

$context['smfinfo'][] = array(			'title' => $row['subject'],			'link' => $scripturl . '?topic=' . $row['id_topic'] . '.0',			'description' => $row['body'],			'author' => !empty($row['id_member']) ? $scripturl . '?action=profile;u=' . $row['id_member'] : '',			'category' => $row['bname'],			'comments' => $scripturl . '?action=post;topic=' . $row['id_topic'] . '.0',			'pubDate' => gmdate('D, d M Y H:i:s T', $row['poster_time']),			'guid' => $row['id_msg']		); }

$smfFunc['db_free_result']($request);

if (!$user_info['is_team'] && !$user_info['is_charter']) {		$context['welcome_title'] = $pagetxt['welcome']['public']['title']; $context['welcome_text'] = $pagetxt['welcome']['public']['text']; }	elseif ($user_info['is_team']) {		$context['welcome_title'] = $pagetxt['welcome']['team']['title']; $context['welcome_text'] = $pagetxt['welcome']['team']['text']; }	elseif ($user_info['is_charter']) {		$context['welcome_title'] = $pagetxt['welcome']['charter']['title']; $context['welcome_text'] = $pagetxt['welcome']['charter']['text']; }

// the main header $context['sectionheader'] = ''; } This is the source function. Get gets the last 4 topics from the News board that are stickied and gives a short preview of them. The next set of code is simply setting up some text depending on which of the three main groups we are targeting (team, charter members, and regular members).

function template_file_template {	global $context, $siteurl, $sitetxt, $txt, $pagetxt;

echo '  ', $pagetxt['download'], '

 ', $pagetxt['custom'], '

 ', $pagetxt['features'], '

 ', $pagetxt['support'], ' ', $pagetxt['h2'], ' ', $pagetxt['p1'], '

  ', $pagetxt['h3'], ' ';

foreach($context['smfinfo'] as $info) echo '  <dt>', $info['title'], '</a></dt> <dd>', $info['description'], '</dd> </dl>';

echo ' User ';

if ($context['user']['is_logged']) {		echo ' ', sprintf($pagetxt['welcome_user'], $context['user']['name']), ' <ul> <li>'; // Show how many messages there are if ($context['user']['unread_messages'] == '0') {			echo $pagetxt['no_new']; }		elseif($context['user']['unread_messages'] == '1') {			echo sprintf($pagetxt['one_new'], $context['user']['unread_messages']); }		else {			echo sprintf($pagetxt['many_new'], $context['user']['unread_messages']); }		echo ' </li> <li>', $pagetxt['go_profile'], '</li> <li>', $pagetxt['unread'], '</li> </ul>'; }	else {		echo ' ', $pagetxt['login'], ' <form action="http://www.simplemachines.org/community/index.php?action=login2" method="post"> ', $txt['username'], ': <input type="text" id="user" name="user" size="9" value="" /> ', $txt['password'], ': <input type="password" name="passwrd" id="passwrd" size="9" /> <input type="hidden" name="cookielength" value="-1" /> <input type="submit" value="', $txt['login'], '" /> ';	}

echo ' Search 	'; theme_searchbox(false); echo' '; }

?> This is the template function. You should notice it looks a lot like a normal SMF template function.

The next file is the about page index. <?php

$section = 'about'; $menulink = 'about';

$sectionheader = 'About Simple Machines'; $title = 'Who are we?';

$callback = false; Again all of our setup variables. In this case we are manually setting the title. This page is all HTML, so there is no need for a source function, so we turn it off.

require_once('../integrate.php'); By request of our server admin we are now using relative paths to get to integrate.php.

function template_file_template {	global $siteurl, $context;

echo ' Navigation <li id="active" class="first">Who are we?</a></li> <li>Core Values</a></li> <li>Free: it\'s better!</a></li> <li>Simple Machines and Open Source</a></li> </ul> Who are we? <img class="img-floatright" src="../site_images/yabbse.png" alt="YabbSE" /> Past SMF can trace its roots all the way back to a perl powered message board, YaBB. After awhile, there became a demand for a php coded version of YaBB. So that is where YaBBSE comes into play. While YaBBSE was getting bigger and bigger, there were certain aspects of it that just needed improvement and reworking. The decision was made that it was best to separate from YaBBSE because it was a lot different from YaBB and it was best to start from scratch. At this point, SMF started being developed. On September 29th, 2003, the first beta of SMF was released to charter members, SMF 1.0 Beta 1. While this was a huge milestone for SMF, only charter members had access to use it. But on March 10, 2004, SMF made its public debut with the first public SMF release available to everyone.

Present

The people behind SMF are a diverse group of individuals who in their free time, put their efforts together to help make SMF what it is today, and help SMF take strides into the future. Many different uses of skills are expressed within several teams, including a design team, modification team, development team, documentation team, support team, and a language team. Each team works together to help SMF grow and reach its full potential. We are people, just like you, who put our efforts into SMF, driven by passion we have for the software. For a listing of all the team members who have helped put together the latest release of SMF, see the team page</a>.

Future SMF strives to get better and better. Active discussions amongst the team are being made to discuss the future of SMF. In the future, you can expect SMF to be even more user-friendly, offer better and quicker support, and much more. This is just the beginning for SMF, expect the building blocks of SMF and the community to be expanded, but to keep to the original goal: Simple, elegant, powerful, and free.

Navigation <li id="active" class="first">Who are we?</a></li> <li>Core Values</a></li> <li>Free: it\'s better!</a></li> <li>Simple Machines and Open Source</a></li> </ul> '; }

?>

This page hasn't been changed to use $pagetxt but hopefully one day ;)

I hope this rather lengthy post will help you all understand how we have it setup and hopefully will help in doing the same on your site if you wish.