Page 1 of 1

Separation Anxiety - Refactoring Guidance Needed

Posted: Mon Jan 21, 2008 12:47 pm
by thinsoldier
I'm trying to redo my little news article system to be able to use it in other areas that are often called something other than "news".

Any suggestions would be great.

UPDATE
I've stripped out everything except the program flow if() statements and just have comments explain what the code in each part does. Does it make any more sense now? Does the organization/separation seem any better?

Code: Select all

<?php
// news.php (the "page based controller I guess??")
 
ob_start();
 
include_once('config.php');
include_once('class.NewsModule.php');
include_once ('functions.php');
 
 
 
// get current action from GET or POST
if(!isset($cmd)){$cmd='searchform'; }
if(!isset($behaviour)){$behaviour='insert'; }
if(!empty($id) and $cmd==='showform'){$behaviour = 'update';}
 
 
 
// the AdminCoreModule object's __constructor sets default form fields:
// headline, published, summary, body, credit, feature, link
$M = new NewsModule;
 
 
// you can more form fields or modify or remove the defaults
unset($M->formfields['summary']); // client says they don't need a summary field
 
 
// These variables MUST be set before you can really start using the object
// I could extend a child class from AdminCoreModule that has these already set (new NewsModule)
$M->table = 'news';
$M->section ='news';
$M->depttext = 'Fire Dept';
$M->behaviour = $behaviour;
$M->id = @$id; // tell object the id of the record your working on
 
// now start manipulating the object
 
// UseDepartments also has an effect on the Form 
// in that they determine whether the Departments field is visible 
// and whether the javascript that enables cloning multiple Department fields is active.
// Should those 2 not be a part of this section?
// High level users (0 & 1) can assign/search things by Departments
if ($_SESSION['level'] < 2)
{
  $M->UseDepartments();
}
 
 
 
/////////////////////////////////////////////////
// Search Form
/////////////////////////////////////////////////
if ($cmd === "searchform") // Display a form to search for articles
{
  $search = new HTMLWidget_searchform;
  // configure with same values as $M a few lines up since it's working with the same tables
  $search->table = 'news';
  $search->section ='news';
  $search->depttext = 'Fire Dept';
  echo $search->ShowForm();
}
 
 
 
/////////////////////////////////////////////////
// Search Results
/////////////////////////////////////////////////
if ($cmd === "searchresults")
{
  $results = new HTMLWidget_searchresults;
  $results->table = 'news';
  $results->section ='news';
  $results->depttext = 'Fire Dept';
  echo $results->ShowResults();
}
 
 
 
 
/////////////////////////////////////////////////
// Database manipulation
// Insert or Update a record
/////////////////////////////////////////////////
// If the add/edit form is submitted this method is triggered
// it handles error checking and adding/updating the database
// If there were errors then nothing gets updated 
// and $M->errors array is populated with error messages
if($cmd=== 'add_confirm' or $cmd === 'edit_confirm')
{
  $M->AddEditConfirm();
}
 
 
 
 
/////////////////////////////////////////////////
// Show the Form
/////////////////////////////////////////////////
// if $cmd is 'showform' or if $M->errors has error messages in it
// show the add/update form (again)
if($cmd === 'showform' or count($M->GetErrors()) > 0 ) 
{
  if($behaviour === 'update') // Display the update form
  { 
    // query to get all the data for the $id being edited
    
    // Modify that data as needed to work properly later on.
    // The modifications needed here would change from section to section.
      
    // now loop through the $M->FormFields array
    // and tell all the formfield objects where to get their values to display for editing
    
    // Now that the $M object is prepared properly,
    // include a file that makes use of the formfield objects to display the form.
    // This file is a bit of a mix of php & html since depending on certain values set
    // in the $M object some parts of the form will be displayed differently.
    // The form is a bit complex. It uses a lot of javascript for drag/drop elements and a little
    // ajax for combo-boxes for categories and sub-categories.
    // Another reason for the html/php mix is that sometimes parts of this file 
    // need to show user data from their $_SESSION.
    // I don't know of any other way to display session data other than to put some php inside
    // of the html......???
    include('tpl.form_core.php');
  }
  // else
  if($behaviour === 'insert') // Display an empty form
  {
    // Don't bother querying anything, just display an empty form
    include('tpl.form_core.php');
  }
 }
 
 
if ($cmd === "delete_question")// Warn of deletion
{
  // some reusable html that asks the user if they are sure
  // they want to delete the current record
  // submit buttons [yes] [cancel]
  include('tpl.delete_question.php');
}
 
 
if($cmd === "delete_perform") // actually delete the current record
{ 
  $M->DeleteItem();
  // display a little confirmation message here
}
 
$content = ob_get_contents();
ob_end_clean();
 
 
 
 
 
 
 
// the view could be an include that uses $content
// or a template or something class that you send the $content variable to
//------------------
//      VIEW
//------------------
$H = new AdminHead;
$H->PrefixTitle('Public News - ');
$H->ExtraCSS('some css specific to this page');
echo $H->Output(); //echoes a standard <head>
?>
<div id="admin-container">
  <?php include('../includes/inc.admin_header.php'); ?>
  <?php  include('../includes/inc.admin_navigation.php'); ?>
 
  <div id="content">
    <? echo $content ?>
  </div>
 
  <?php include('../includes/inc.admin_footer.php');?>
</div>
</body>
</html>

Code: Select all

<? // class.NewsModule.php
class NewsModule extends AdminCoreModule
{
 
  public $table; // the main table of the module being worked with
 
  public $id;   // the id of the record in the main table being worked on
  
  public $rel8cat = 'catrelations'; // name of table containing relationships between categories table and records in other tables
  
  public $rel8dept = 'deptrelations'; // name of table containing relationships between departments table and records in other tables
  
  public $photosizes; // ?? array of thumb/medium/large sizes?
  private $newPhotoNames = array(); // holds the generated names of uploaded photos.
  private $newDocumentNames = array(); // holds the generated names of uploaded documents.
 
  public $cmd = ''; // The current "Action" to be performed
  public $behaviour = ''; // If database related actions occur, sets if it's and INSERT or UPDATE
  
  public $category_cloning = true; //whether or not the <form> allows you to pick multiple categories
  
  public $formfields = array(); 
  // array of objects made with the FormField class for the regular fields - use AddFormField($object)
  // in the file that uses this class this array is then looped through and spat out to build 
  // the form elemnts that are seen
  // Before spitting out the fields you can add to this array via the class methods
  public $errors = array(); // errors to be shown to user if things don't go right.
  public $debugs = array(); // errors only the developers should see (mysql_error() etc.)
  
 
  
  function __construct()
  {
    // set up defaults used throughout
  
    // Define size of thumbnail/full view of uploaded images
    $this->photoField = 'photo';
    $this->photoSizes = array(100,200,400);
    $this->photoDestinations = array();
    
    
    // Define the Fields shown in the form and add
    // the form field objects to the $formfields array of this class
    $f = new FFtext('headline');
    $f->Label->text = 'Head Line';
    $f->defaultvalue = 'This article needs a Head Line';
    $f->required = true;
    $this->AddFormField($f, 'headline');
    
    $f = new FFselectMDY('published');
    $f->Label->text = 'Date Published';
    $f->required = true;
    $this->AddFormField($f, 'published');
 
    $f = new FFtextarea('summary','enter short description here');
    $this->AddFormField($f, 'summary');   
 
    $f = new FFtextarea('body','enter article body here');
    $f->requird=true;
    $this->AddFormField($f, 'body');
    
    $frequency = array('yes'=>'Yes, Feature','no'=>'Do not feature');
    $f = new FFradioGroup('testradio', $frequency );
    $f->required = true;
    $f->errormessage = 'Featuring of the article was not indicated';
    $f->grouplabel = '<p>* Feature this item:</p>';
    $this->AddFormField($f, 'feature');
  }
  
 
  
/**
* executes a series of methods & functions to add/update an item in the database
* This would have been modified from whatever was in the base class
*/
function  AddEditConfirm()
{
  // error checks on required fields
 
  // Loop through the array of defined form field objects.
  // formfield objects perform basic validation on themselves
  // if they are set as "required" fields
 
  // Upload any submitted photo files
  if (count($this->GetErrors()) < 1) {
    $this->UploadPhotos();
    $this->UploadDocuments();
  }
 
  // Only continue if there are no errors from errorchex or uploading files
  if (count($this->GetErrors()) < 1) 
  {
    // Clean submitted data
    
    // build the SQL string for adding/updating the database
    
    // update the main data table
    
    // update the related tables
      $this->updateRelations('category', 'subcategory', 'catrelations');      
      $this->updateRelations('department','subdepartment','deprelations');
      $this->UpdateMedia();
        
      // Tell the user that everything worked.
      echo "<h2>Successful $this->behaviour made.</h2>";
      echo '<p><a href="'.thispage().'?cmd=showform&id='.$lastid.'">Continue editing this Item.</a></p>';
    }
  } 
} // END AddEditConfirm method
 
 
 
// The rest of the methods are from the base class and would usually have no reason to ever be changed
// no matter what the module was being used for (at least from my experience with dozens of corporate clients)
 
 
 
 
/**
* Adds the FormField class object to the array of form fields within this class and associates each field object with a database field in the main table.
* @param object $obj A FormField object
* @param string $databaseField The name of the field in the DB that this form input is associated with.
*/  
  public function AddFormField($obj, $databaseField)
  {
    $this->formfields[$databaseField] = $obj;
  }
 
  //loops through all the form fields and echos their markup
  public function ShowFormFields()
  {...}
  
  // displays a link that the javascript will look for to enable assigning multiple categories
  public function CloneCategoryLink()
  {...}
 
  // displays a special combo box for adding categories
  public function CategorySelect_adding()
  {...}
 
  // displays a special combo box for editing categories
  public function CategorySelect_editing()
  {...}
 
  /**
  * Gathers Data for display on search results page.
  * This function is a bit of a monster. Probably needs refactoring
  */
  function SearchResults()
  {...}
  function errorchex($field, $label='', $custom_message='')
  {...}
  function SetError($error)
  {...}
  function GetErrors()
  {...}
  function ShowErrors()
  {...}
  function UploadPhotos()
  {...}
  function UploadDocuments()
  {...}
  function UpdateMedia()
  {...}
  function UpdateRelations($postParent, $postSub, $relateTable)
  {...}
  function DeleteItem()
  {...}
} // end class
?>

Code: Select all

<?php // tpl.delete_question.php
$result=mysql_query("SELECT * FROM $M->table WHERE id='$M->id' LIMIT 1" ) or die(mysql_error());
$details = mysql_fetch_assoc($result);
?>
<h2>WARNING: Delete News Item</h2>
<p>You are about to permanently delete the following news item including it's photos and documents:</p>
 
<p><?php echo convert_date($details['published']);?> <?php echo $details['headline'];?></p>
 
<p>Are you sure you wish to proceed?</p>
 
<p><a href="<?=thispage()?>"><b>No, I do not wish to delete this article.<b></a></p>
 
<p><a href="<?=thispage()?>?cmd=delete_perform&id=<?php echo $id;?>">Yes,  delete this article</a></p>
 
 
 
 

Code: Select all

tpl.form_core.php
 
<h2>News Article</h2>
<p>Fields marked with  (<span class="required">*</span>) are required.</p>
 
<?
if(!is_object($M)){ die('$M object is required'); }
 
echo $M->ShowErrors(); //if the form was submitted and had errors they would be shown here 
?>
 
 
 
 
<form id="form" action="<?=thispage()?>" method="post" enctype="multipart/form-data">
<? if($behaviour === 'insert') { ?>
    <input type="hidden" name="cmd" value="add_confirm" />
<? } ?>
<? if($behaviour === 'update') { ?>
    <input type="hidden" name="cmd" value="edit_confirm" />
    <input type="hidden" name="behaviour" value="update" />
    <input type="hidden" name="id" value="<?=$id?>" />
<? }
 
 
// Show the special <select> combo box field for categories
if ($behaviour === 'insert') 
{ $M->CategorySelect_adding; } // empty one for inserting
else { $M->CategorySelect_editing(); } // populated one for updating
 
// Now show all the default/developer defined fields from the $M object
$M->ShowFormFields(); 
?>
<div>
    <input type="submit" name="submit" id="submit" class="submit" value="<?=ucfirst($behaviour)?> Article" />
</div>
 
<!-- 
100x More html and a little more php goes here.
Makes use of the info in the $M object about photos, documents, videos related to the current record
and builds an ajax based interface for rearranging, deleting, editing captions on those files.
-->
</form>

Re: Separation Anxiety - Refactoring Guidance Needed

Posted: Wed Jan 23, 2008 12:13 pm
by Christopher
Ok ... I have looked through the archive and get a little sense of what is going on. Still not too clear though.

The ps_news.php file appears to be a Page Controller. The file ps_class.AdminCoreModule.php contains a Form Controller class. The ps_inc.admin_news_withClass.php file appear to be a template.

The two controllers seem clear enough (I would rather not see the mix of application code and HTML though). The last file is where the mess seems to be. It it essentially a big switch statement for the various states of the news area. That should probably be split up, and you can see why people have gone to the Action Controller style to organize this very common type of code. You can even tell by the file name that that is where the trouble is. ;)

So my question is: what is your plan/goal in improving this system?

Re: Separation Anxiety - Refactoring Guidance Needed

Posted: Wed Jan 23, 2008 8:29 pm
by thinsoldier
The news.php is actually sort of a template too. That's where the header/footer of the html page can be changed from project to project to achieve different looks.

The inc.admin_news_withClass.php is .... well I don't really know what to call that. It's like the "guts" of the system. All the parts that I'm sure I will re-use (with some configuration) in future projects/modules is what's in there. But I guess it's also sort of a template since the $M object created in news.php gets used a lot inside of this include.


It it essentially a big switch statement for the various states of the news area. That should probably be split up
How would you split it up?


What should I do when I want to use php logic to decide whether or not certain parts of the displayed html should be displayed or not. Only thing I can think of is to put an if statement around a code block but then people say it's bad to mix the php and html every time. What do you do?


I was thinking of taking all of the if statements (switch staments) and putting them all together in news.php.


Taking everything from line 56 down of news.php (see above) and putting that in its own file like a proper template.


Taking all the contents of the main if statements in admin_news_withClass.php and putting them in the class as separate methods. And put the if statements themselves in news.php


So then News.php would:
Dnclude all the configuration files.
Define the object that does all the work.
Modify the class vars the allow the script to perform the same functions and display the same re-usable form with different tables.
Have all the control flow if() statments.
Capture the output of the methods run by those if statments using output buffering.
Then give that data to the View template for display as the content area of the page.


But that still leaves me wondering how/where I'm going to perform custom handling of certain fields in certain areas where this get use where the requirements might differ from the built in defaults (the $M->AddEditConfirm method). I guess I'll extend the class and Modify that method's behaviour whenever necessary.

Re: Separation Anxiety - Refactoring Guidance Needed

Posted: Wed Jan 23, 2008 9:14 pm
by Christopher
thinsoldier wrote:How would you split it up?
Action Controller style is to have a method for each "action". In turn they load Models, Views, Helpers, and are often passed Request, Response and Config objects. It formalizes the structure of the app and makes you modularize things. As the application grows the complexity stays fairly constant.

Re: Separation Anxiety - Refactoring Guidance Needed

Posted: Fri Jan 25, 2008 8:59 am
by thinsoldier
updated first post

Re: Separation Anxiety - Refactoring Guidance Needed

Posted: Tue Sep 16, 2008 4:34 pm
by thinsoldier
arborint wrote:
thinsoldier wrote:How would you split it up?
Action Controller style is to have a method for each "action". In turn they load Models, Views, Helpers, and are often passed Request, Response and Config objects. It formalizes the structure of the app and makes you modularize things. As the application grows the complexity stays fairly constant.

Ok, It's been a while but I think I'm heading in this direction now.

What do you think?

Code: Select all

 
<?php
//===== site.com/details.php =======//
// it's not a site-wide _front_ controller
// just the page the visitor accesses for this section of the site.
 
require_once('includes/config.php');
 
// The regex that should match
// & the method that should happen as a result
$routes = array(
    array('regex'=>'/similar/', 'action'=>'similar'),
    array('regex'=>'|/virtualtour/|', 'action'=>'virtualtour'),
    array('regex'=>'|/sendtofriend/|' , 'action'=>'sendtofriend' ),
    array('regex'=>'|/visit/|' , 'action'=>'arrangeavisit' ),
    array('regex'=>'|/print/|' , 'action'=>'print' ),
    array('regex'=>'|/|' , 'action'=>'details' )
);
 
$router = new Router($routes);
 
$controller = new DetailsRWController();
$controller->{$router->Action}();
$controller->page->Render();
 

Code: Select all

 
//===== site.com/app/controllers/DetailsRWController.php =======//
// the methods in here _will_ make use of various template files inside of
// site.com/app/views/detailsRW/
// next time I work on this
// I also need to declare class vars
class DetailsRWController
{
 
 
    // prepare anything commonly needed by most actions
    function __construct()
    {
        // Before doing anything, make sure the url ends with a slash
        //   This is needed for stuff like the /similar/ link to go to the correct url
        //   without having to put the whole long url in the href, can just use href="similar/"
        // If the url doesn't end with a slash, redirect to the same url with the slash added.
        if(!preg_match('|/$|', $_SERVER['REQUEST_URI']))
        { header('Location: '.$_SERVER['REQUEST_URI'].'/'); exit; }
    
        $this->R = $R = new PropertyLogic((int)$_GET['lot']);
        $R->LocationString = implode(', ' , locationToString($R->District,$R->Location,$R->Island) );
        
        require_once('classes/ResultItem.php');
        $this->ResultItem = new ResultItem($R->dataset); // mini html template for a single result item
    
        // If the model can't load the requested listing then just pull in the 404 page.
        // Could have redirected to the 404 instead I guess.
        if(!empty($R->errors)) {
            $site_map_error = true;
            include('sitemap.php');
            exit;
        }
    
        //--------------------------------------------------------
        // By this point nothing weird happened, 
        // no redirects happened, 
        // so continue as planned
        //--------------------------------------------------------
 
        $this->page = $page = new Site_X_Page();
        $page->SelectedTopNav = 'Find a Property';
        $page->Head->PrefixTitle($R->HTMLtitle.' - ');
        $page->SideContent = QuickSearch(); //mini search form html
        
        // if it was an ajax call (url has ajax: /view/abc/def/432/choice/ajax/other/) 
        // then change the overall design template to be a bare bones one
        // suitable for the LightView iframe overlay
        $this->isIframe = false;
        if(preg_match('|/ajax/|', $_SERVER['REQUEST_URI']))
        { 
            $page->LayoutTemplate = 'templates/iframe4lightview.php';
            $this->isIframe = true; // a var other parts of the code can check to see if it was an ajax request
        }
    } // end constructor
    
    
    function similarAction()
    {
        // percentage of price difference above/below current property
        $this->R->similar_seed = 0.15; 
        // redirect to search results page to show 
        // only properties similar to this one
        $this->R->SimilarSQLRedirect(); 
        exit;
    }
 
 
    // I think all this $page stuff could/should go into the template file itself
    function detailsAction()
    {
        $R = $this->R; 
        $page = $this->page;
        // otherwise show the main detail page.
        $page->Head->LinkCSS('/css/details.css');
        $page->Head->LinkCSS('/css/lightview.css');
        $page->Head->LinkJS(PhillipsHead::scriptaculous_js.'?load=effects');
        $page->Head->LinkJS('/js/lightview.js');
    
$js = <<<HEREDOC
document.observe("dom:loaded", function() {
    document.observe('lightview:loaded', function() {
        $$('#video a, #friend a, #visit a').each(
        function(ele) {
            ele._view.href = ele.href + 'ajax/'; // ajaxify lightview url
        })
    });
});
HEREDOC;
        $page->Head->ExtraJS($js);
        $page->Content = include('templates/_details.php');
        $R->IncreaseViews();
    }
 
 
    function virtualtourAction()
    {
        $R = $this->R;
        $page = $this->page;
        $ResultItem = $this->ResultItem;
        $isIframe = $this->isIframe;
 
        // are we viewing a particular media record?
        // check for media(id#) at the very end of the url string.
        preg_match('|media(\d{0,})/$|', $_SERVER['REQUEST_URI'], $mediamatches);
        
        $mediaID = (count($mediamatches) > 1) ? $mediamatches[1] : null;
    
        include('_RE2/public/VirtualTour.php');
    
        // Grab either the first video or the video specified by $mediaID (if it's set)
        $tour = new VirtualTour_Listing((int)$_GET['lot'], $mediaID);
    
        $tour->LinkBase = $R->ModLink; // Nav links in the view requires a full url starting point
        // They also need to know if to link to full or iframe versions of the page
        // by having /ajax/ in the url
        if(@$this->isIframe){ $tour->LinkBase .= 'ajax/'; }
        
        // The javascript generated by the MediaDisplay class uses a relative link to the video file
        // but because this site uses friendly urls this needs to change to an absolute url
        $tour->mediaScript = str_replace('video/', '/video/', $tour->mediaScript); 
        
        $page->Content =    include('templates/_virtualtour.php');
    }
 
 
 
    function sendtofriendAction()
    {   
        $R = $this->R;
        $page = $this->page;
        $ResultItem = $this->ResultItem;
        $isIframe = $this->isIframe;
        $page->Content = include('templates/_sendtofriend.php');
    }
 
 
    function arrangeavisitAction()
    {
        $R = $this->R;
        $page = $this->page;
        $ResultItem = $this->ResultItem;
        $isIframe = $this->isIframe;
        $page->Content = include('templates/_arrangeavisit.php');
    }
 
 
    function printAction()
    {
        $R = $this->R;
        $page = $this->page;
        $ResultItem = $this->ResultItem;
        $isIframe = $this->isIframe;
 
        $page->Head->LinkCSS('/css/details_print.css');
        $page->Content = 'Flyer formatted for print'.htmlprintr($R);
    }
 
}
 
 
// note to self:
// repeated use of stuff like:
//      $R = $this->R;
//      $page = $this->page;
//      $ResultItem = $this->ResultItem;
//      $isIframe = $this->isIframe;
// Isn't needed if I refer to them 
// from within the include files by using $this->
 

Code: Select all

 
//===== site.com/app/Router.php =======//
class Router
{
    public $routes = array();
    public $RouteMatched = false;
    public $Regex;
    public $Action;
    
    function __construct($routes)
    {
        $this->addRoutes($routes);
        $this->execute();
    }
    
    function addRoutes($array)
    {
        foreach($array as $a)
        { $this->routes[] = $a; }
    }
 
    function execute()
    {
        foreach($this->routes as $key=>$route)
        {
            if(preg_match($route['regex'], $_SERVER['REQUEST_URI']))
            {
                $this->RouteMatched = true;
                $this->Regex = $route['regex'];
                $this->Action = $route['action'].'Action';
                break;
            }
        }
    }
}
 
 

Re: Separation Anxiety - Refactoring Guidance Needed

Posted: Tue Sep 16, 2008 4:52 pm
by Christopher
Looks like a reasonable direction. Have you thought about just using a framework. It will provide a bunch of very nice functionality that it would take you years to write yourself -- if ever. Zend, Cake, CodeIgniter, Symfony would all be reasonable choices. And it will get you using better practices right now.

Re: Separation Anxiety - Refactoring Guidance Needed

Posted: Tue Sep 16, 2008 5:22 pm
by thinsoldier
zend and cake dont work on our live server. PHP & mysql versions too old or something. Also our php wasn't compiled with various things that would probably affect all those listed.

I'm also the only OOP writer & user where I'm at. Writing extremely simplified versions of the basic framework stuff is better for my team-mates.
At least I hope it is. Only 1 person so far has used only 1 of the things I've recently with oop.

Also, I've spent a lot of time looking at Zend Framework (but not being able to use myself on our server).
It leaves me utterly lost and confused.
I might be better off trying to understand the concepts through writing things.

I haven't looked at that Skeleton framework in quite a while tho. I remember it not working for me for some reason but I'm sure it was just me overlooking something and not some incompatibility with our old server. I'll try to find some time this month to look through it again.
Any chance they've updated the comments/documentation/examples since...whenever it was I looked at it last?

Re: Separation Anxiety - Refactoring Guidance Needed

Posted: Tue Sep 16, 2008 5:47 pm
by Christopher
thinsoldier wrote:zend and cake dont work on our live server. PHP & mysql versions too old or something. Also our php wasn't compiled with various things that would probably affect all those listed.

I'm also the only OOP writer & user where I'm at. Writing extremely simplified versions of the basic framework stuff is better for my team-mates.
At least I hope it is. Only 1 person so far has used only 1 of the things I've recently with oop.

Also, I've spent a lot of time looking at Zend Framework (but not being able to use myself on our server).
It leaves me utterly lost and confused.
I might be better off trying to understand the concepts through writing things.
It does require some very recent versions for some things. :(
thinsoldier wrote:I haven't looked at that Skeleton framework in quite a while tho. I remember it not working for me for some reason but I'm sure it was just me overlooking something and not some incompatibility with our old server. I'll try to find some time this month to look through it again.
Any chance they've updated the comments/documentation/examples since...whenever it was I looked at it last?
A lot of things have been improved and added in the Skeleton Framework. I would be glad to write an example base application for you that does what you show above -- if you want to see how it would work. PM me if you are interested. I would just need to know some basics like URL style, Access Control, session and database usage, etc.