Topic: Yetanother Smarty / xAjax Setup

Howdy, All,

Have used this setup on several sites.

There are probably more elegant solutions out there, but this is one I've gotten to work.  Am posting it here so folks can see an entire example of a working site using PHP, Smarty, xAjax, and mySQL.

The main concept here is naming files of various types with the same 'base' name as their php counterparts, then checking for existence of those files in the Smarty templates, loading as necessary.

The default tag of one curly bracket for Smarty template var & other calls was changed to two, mainly so as not to potentially confuse other javascript being loaded.

Smarty *always* loads index.tpl, but I programatically assign what content gets loaded into that template.

A query_string of 'p' is used to determine content, i.e., http://my_page.com?p=showrecs

So, if the php file "showrecs.php" needed some custom css, I'd also have a showrecs.css.  Same with javascript.

This keeps me from having one huge (and potentially conflicting) stylesheet, or javascript code block.  Desired code is loaded on an as-needed basis.

The particular setup shown below is for an events' vendor sign-up page, which also has a back-end online admin database.  Each of these is its own subdomain of the main page that Joe User will visit (general info about the event).  I can toggle a maintenance mode value (stored in a mySQL) from the admin on both the admin & sign-up pages, via an added drop-down in the menu.  If maintenance mode is active, folks will see a static page telling them to try back later.  My user ID is exempt from maintenance mode, though I'll see a visual on both the admin & sign-up pages reminding me I'm in that mode.  My user ID is also the only one that sees the special drop-down in the admin page.  The login for both admin & vendors uses a SHA512 hash, and the site has a wildcard SSL cert.  .htaccess will force a redirect to the secure site if it's not requested.  So sure, a really adventurous person could probably hack in, but they'd have to work at it.

Folder structure is pretty simple within the web space:
/ - all main php pages - index.php, showrecs.php, etc.
/code - all libraries, with sub-folders such as xajax, css, js, phpmailer, etc...
/img - banner graphics, etc.

And now the code...

Contents of /index.php

<?php
require_once("code/_init.php");
$Smarty->display(($isMaint && !$isAdmin) ? 'maint.tpl' : 'index.tpl');
?>

Contents of /code/_init.php

<?php
session_start();
set_include_path($_SERVER["DOCUMENT_ROOT"] . DIRECTORY_SEPARATOR . "code" . PATH_SEPARATOR . (($_SERVER["SERVER_ADDR"] == "127.0.0.1") ? "/path/to/local/pear" : "/usr/local/lib/php")  . PATH_SEPARATOR . get_include_path());
error_reporting(E_ALL);
ini_set('register_globals', false);
ini_set('magic_quotes_gpc', false);
set_magic_quotes_runtime(false);
require_once("_defs.php");
require_once("_functions.php");
require_once("_init_db.php");
require_once("_init_smarty.php");
require_once("_init_page.php");
require_once("_init_ajax.php");
?>

Contents of /code/_defs.php
I include references to live & dev sites so code doesn't have to change.  Some live sites may have 'siblings', i.e. additional sub or add-on domains running under the same user (my production server runs on a flavor of *nix), with distinct occurrences of Smarty needed for each, so that's dealt with here as well.

<?php
require_once("_defs_page_titles.php");
define("DEF_PAGE", "home");
define("DEV_SITE", "http://my_local_site_name");
define("LIVE_SITE", "http://my_live_site.com");
define("PAGE_TITLE", "My Site's Base Title");
define("REDIRECT_NO_ACCESS","http://take_me_somewhere_else.com");
define("ROOT_DIR", "");
define("SMARTY_HOME", "my_server_sub_folder");
define("SMARTY_INSTANCE", "_sites/my_local_instance_folder/");
define("SMARTY_LOCAL", "/path/to/local/smarty/files");
define("TABLE_PREFIX", "_db_");
?>

DEF_PAGE is the content that will get loaded if the user (accidentally or maliciously) specifies a page that doesn't exist.  There exists a home.php as well as home.tpl

Contents of /code/_def_page_titles.php
This is used to generate a portion of a page title.

<?php
unset($pageTitles);
$pageTitles["my_page_1"] = "Title For Page 1";
$pageTitles["my_page_2"] = "Title For Page 2";
$pageTitles["my_other_page"] = "Title For Other Page";
$pageTitles["my_page_4"] = "Etc., Etc., Etc.";
?>

/code/functions.php contains a whole bunch of code.  wink

functions.php's job is to act as a kind of shorthand for whatever I need the php code to do.  It's around 1,400 lines to date, things like figuring out if Smarty debug mode is needed, getting the value of post, get or session vars, querying the database, spitting out a prettyfied print_r for a var or array, etc.  I've built this up over time to standardize and simplify calls within any page.  For example, to query the database I use dbQ($query, $type) - and just pass the plain query to it, optionally specifying an expected return type such as associative array, single var, row, and so on.  Other functions will parse the of the query & return the where clause, etc.  I guess it should really be a collection of classes but I just keep adding on to it.  You can thank old-school mainframe-style programming habits for that.

Contents of /code/_init_db.php
I've been quite happy using ezSQL for a few years now, you can of course substitute your own library.

<?php
require_once("ez_sql_core.php");
require_once("ez_sql_mysql.php");
require_once("ez_results.Smarty.php");
$ezr->num_results_per_page = 25;
$ezr->num_browse_links = 10;
$isDev = isDev();
$dbHost = $isDev ? "my_local_host" : "my_live_host";
$dbUser = $isDev ? "my_local_user" : "my_live_user";
$dbPW = $isDev ? "my_local_pw" : "my_live_pw";
$dbDB = $isDev ? "my_local_db" : "my_live_db";   
$db = new ezSQL_mysql($dbUser, $dbPW, $dbDB, $dbHost);
require_once("DbSafe.php");
$dbSafe = new DbSafe();
?>

DBSafe is an excellent library ( http://www.michikono.com/code/DbSafe/DbSafe.php ) mainly for safely getting submitted form values into mySQL

Contents of /code/_init_smarty.php
debug mode for Smarty can be toggled in the admin drop-down.  It calls a tiny page that just checks for the existence of the session var & sets or unsets as needed.  Never did set this up for xajax though, that's just a hard-coded true or false.

<?php
$dbg = isset($_SESSION['debug']);
require_once('class.smarty.extended.php');
$Smarty = new smarty_connect(SMARTY_INSTANCE);
$Smarty->debugging = $dbg;
$Smarty->assign("CAL_FMT","%a, %b %d, %Y");
$Smarty->assign("FULLTIME_FMT","%a, %b %d, %Y @ %l:%M %p");
$Smarty->assign("cON","<font color = '#00008B'>");
$Smarty->assign("rON","<font color = '#FF0000'>");
$Smarty->assign("cOFF","</font>");
$Smarty->assign("rOFF","</font>");
$Smarty->assign('H_DEBUG', $dbg);
$Smarty->assign('ISDEV', isDev());
?>

The base Smarty class was extended, mainly for more easily dealing with multiple instances (add-on or subdomains) in one user on the server.
Contents of class.smarty.extended.php

<?php
require_once(getSmartyRoot() . 'Smarty.class.php');
class smarty_connect extends Smarty
{
    function smarty_connect($instance)
    {
        $this->Smarty();
        $this->template_dir = getSmartyRoot() . $instance . 'templates';
        $this->config_dir = getSmartyRoot() . 'config';
        $this->compile_dir = getSmartyRoot() . $instance . 'compiled';
        $this->cache_dir = getSmartyRoot() . $instance . 'cache';
        $this->left_delimiter = '{{';
        $this->right_delimiter = '}}';
    }
}
?>

Contents of /code/_init_page.php
Sometimes I use the checkAllowedIP() function to force a re-direct while under development.  It allows only whomever I choose (by IP) to see the live site.  In this case the admin back-end has a couple of drop-downs that let me set or remove IP's.

<?php
// checkAllowedIP();
$isMaint = isMaintenanceMode();
$isAdmin = isAdmin();
$page = getPage();
switch (true)
{
    case isLoggedIn($page) && loginRequired($page):
        require_once("loggedin_default.php");
        break;
    case !isLoggedIn($page) && loginRequired($page) == true:
        $page = DEF_PAGE;
        break;
}
require_once($page . ".php");
$Smarty->assign('CACHING',checkCaching($page));
$Smarty->assign('CONTENT',$page);
$Smarty->assign('CUSTOM', getCustom($page));
$Smarty->assign('TITLE',getPageTitle($page, $pageTitles));
$Smarty->assign('MAINT', $isMaint);
?>

Contents of /code/_init_ajax.php
<?php
$ajax_page = $page . "_ajax.php";
if (file_exists($ajax_page))
{
    require_once('xajax/xajax_core/xajax.inc.php');
    $xajax = new xajax();    $xajax->configure('javascript URI','/code/xajax/');   
    $xajax->configure('global_xss_filtering', false);
    require_once($ajax_page);
    $xajax->setFlag('debug',$ajaxDebug);
    $xajax->processRequest();
    $Smarty->assign('XJS', $xajax->getJavascript());
}
else
{
    $Smarty->assign('XJS', "");
}
?>

Contents of the main smarty template, index.tpl

{{* Smarty *}}
<html>
    {{include file='_head.tpl'}}
    <body background="#000000">
        {{if $MAINT}}
            <div id='maintMode'>
                MAINTENANCE MODE
            </div>
        {{/if}}
        <div id='outer-outer'>
            <div id='outer'>
                <div id='main'>
                  <div id='body'>
                        {{include file='_header.tpl'}}
                        <div id='content'>
                            <table align='center' width='100%'>
                                <tr>
                                    <td>
                                        {{if $MENU == ""}}
                                                 {{include file = "_menu_default.tpl"}}
                                        {{else}}
                                                 {{include file = "_menu_$MENU.tpl"}}
                                        {{/if}}                                 
                                        {{if $ALT_CONTENT == ""}}
                                          {{include file="$CONTENT.tpl"}}
                                        {{else}}
                                            {{include file="$ALT_CONTENT.tpl"}}
                                        {{/if}}
                                    </td>
                                </tr>
                            </table>
                        </div>
                    </div>
                </div>
                <div id='footer'>
                    {{include file='_footer.tpl'}}
                </div>
            </div>
        </div>
    </body>
</html>


Contents of _head.tpl
{{* Smarty *}}
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=windows-1251" />
    {{if $NOCACHE}}
             <meta http-equiv="cache-control" CONTENT="no-cache">
    {{/if}}
    <title>
        {{$TITLE}}
    </title>
    {{include file='_css.tpl'}}
    {{include file='_js.tpl'}}
</head>

Contents of _css.tpl

<link rel='stylesheet' type='text/css' href='/code/_css/site.css' />
<link rel='stylesheet' type='text/css' href='/code/_css/colors.css' />
<link rel='stylesheet' type='text/css' href='/code/_css/fonts.css' />
<link rel='stylesheet' type='text/css' href='/code/_css/menu.css' />
{{if $CUSTOM.css != ''}}
         <link rel='stylesheet' type='text/css' href='/code/_css/{{$CUSTOM.css}}'>
{{/if}}

Contents of _js.tpl

{{popup_init src="/code/_js/overlib.js"}}
{{if $CUSTOM.js != ''}}
        <script type='text/javascript' src='/code/_js/{{$CUSTOM.js}}'></script>
{{/if}}
{{if $XJS != ""}}
    {{$XJS}}
{{/if}}

Contents of _header.tpl

{{* Smarty *}}
<table align='center' width='100%'>
    <tr valign='top'>
        <td align='center' width='100%'>
                <a href='/'>
                     <img src='img/banner.png' border='0' align='center' alt='Click Here For The Home Page'>
                </a>
                <!-- Include Any Other Global Top-Of-Page Content -->
        </td>
    </tr>
</table>
<hr>


If an individual php page defines the smarty value "ALT_CONTENT", that template file will be shown instead.

For xajax pages, which also live in the root folder of the web space, I just use the same base name as the main php file, but append _ajax to it.  So "my_test_page.php" would also have "my_test_page_ajax.php" (and of course "my_test_page.tpl" as well)

Here's a simple test xajax page with a couple of buttons.  One just counts & updates a div as its going along, one grabs some data from mySQL & displays it, one resets everything.

Contents of /my_test_page.php
If debug mode is needed I just uncomment the line that sets it to true.

<?php
?>

Contents of /my_test_page_ajax.php
<?php
$ajaxDebug = false;
// $ajaxDebug = true;
require_once("code/_init_ajax_server.php");

function test_1()
{
    $objResponse = new xajaxResponse();
    $objResponse->assign("testBox1", "innerHTML", "Begin: " . date('r'));
    $objResponse->assign("testBox2", "innerHTML", "");
    $objResponse->assign("testBox3", "innerHTML", "");
    $objResponse->script('setTimeout("xajax_test_2()", 10);');
    $objResponse->script('setTimeout("xajax_test_3()", 500);');
    return $objResponse;
}

function test_2($ttl = 75000)
{
    $_SESSION["AJAX_TEST_HALT"] = 0;
    $_SESSION["AJAX_TEST_COUNT"] = 0;
    $_SESSION["AJAX_TEST_TOTAL"] = $ttl;
    session_write_close();
    session_start();
    for ($cnt = 1; $cnt <= $ttl; $cnt++)
    {
        if ($cnt % 100 == 0)
        {
            $_SESSION["AJAX_TEST_COUNT"] = $cnt;
            session_write_close();
            session_start();
        }
    }
}

function test_3()
{
    $objResponse = new xajaxResponse();
    $curCnt = $_SESSION["AJAX_TEST_COUNT"];
    $curTtl = $_SESSION["AJAX_TEST_TOTAL"];
    switch (true)
    {
        case $_SESSION["AJAX_TEST_HALT"] == 1:
            break;
        case $curCnt >= $curTtl:
            $objResponse->assign("TestButton1", "value", "Again???");
            $objResponse->assign("testBox2", "innerHTML", "End: " . date('r'));
            $objResponse->assign("testBox3", "innerHTML", "Records: " . number_format($curCnt,0));
            unset($_SERVER["AJAX_TEST_HALT"]);
            unset($_SERVER["AJAX_TEST_COUNT"]);
            unset($_SERVER["AJAX_TEST_TOTAL"]);
            session_write_close();
            session_start();
            break;
        default:
            $objResponse->assign("testBox2", "innerHTML", "Current Count: " . number_format($curCnt,0) . " of " . number_format($curTtl,0) . " (" . intval(($curCnt / $curTtl) * 100) . "%)");
            $objResponse->script('setTimeout("xajax_test_3()", 1000);');
            break;
    }
    return $objResponse;
}

function test_4()
{
    $objResponse = new xajaxResponse();
    $objResponse->assign("TestButton2", "value", "Again???");
    $objResponse->assign("testBox1", "innerHTML", "Click either button to try again...");
    $objResponse->assign("testBox2", "innerHTML", "I Have Chosen: " . dbQ("select concat(first_name, ' ', last_name) as FullName from " . getTable("my_user_table")  . " order by rand() limit 1","v"));
    $objResponse->assign("testBox3", "innerHTML", "");
    return $objResponse;
}

function test_5()
{
    unset($_SERVER["AJAX_TEST_COUNT"]);
    unset($_SERVER["AJAX_TEST_TOTAL"]);
    $_SESSION["AJAX_TEST_HALT"] = 1;
    session_write_close();
    session_start();
    $objResponse = new xajaxResponse();
    $objResponse->script('ClearTimeout("xajax_test_3");');
    $objResponse->assign("TestButton1", "value", "Test 1");
    $objResponse->assign("TestButton2", "value", "Test 2");
    $objResponse->assign("testBox1", "innerHTML", "Click either button to continue...");
    $objResponse->assign("testBox2", "innerHTML", "");
    $objResponse->assign("testBox3", "innerHTML", "");
    return $objResponse;
}

$xajax->register(XAJAX_FUNCTION, "test_1");
$xajax->register(XAJAX_FUNCTION, "test_2");
$xajax->register(XAJAX_FUNCTION, "test_3");
$xajax->register(XAJAX_FUNCTION, "test_4");
$xajax->register(XAJAX_FUNCTION, "test_5");
?> 

Contents of /code/init_ajax_server.php
This is basically a server version of /code/init.php, leaving Smarty out of the picture as there's no need for it...

<?php
set_include_path($_SERVER["DOCUMENT_ROOT"] . DIRECTORY_SEPARATOR . "code" . PATH_SEPARATOR . (($_SERVER["SERVER_ADDR"] == "127.0.0.1") ? "/path/to/local/pear" : "/usr/local/lib/php")  . PATH_SEPARATOR . get_include_path());
error_reporting(E_ALL);
ini_set('register_globals', 'off');
ini_set('magic_quotes_gpc', 'off');
ini_set('magic_quotes_runtime', 'off');
require_once("_defs.php");
require_once("_functions.php");
require_once("_init_db.php");
?>

Contents of my_test_page_ajax.tpl

{{* Smarty *}}
<input type="button" name="TestButton1" id="TestButton1" value="Test 1" onclick="xajax_test_1();"/>
<br>
<input type="button" name="TestButton2" id="TestButton2" value="Test 2" onclick="xajax_test_4();"/>
<br>
<input type="button" name="TestButton3" id="TestButton3" value="Clear" onclick="xajax_test_5();"/>
<br><br><br><br><br>
<div id='testBox1'>
Click either button to continue...
</div>
<div id='testBox2'>
</div>
<div id='testBox3'>
</div>

That's it.  Good luck!

Re: Yetanother Smarty / xAjax Setup

jman,

Wow. We should all think things out so well.
This will take some study.

Thanks for sharing.

Ed

If you ever stop learning you may as well dig a hole, crawl in and pull the top over yourself.