Flourish Demo Site

Posted by Will Bond on 1/5/09 at 1:31 pm, 9 comments

Show all

Recent Blogs

About

Flourish is The PHP Unframework, a general-purpose, object-oriented PHP library designed to reduce code and improve security.

If you've never worked with Flourish before, getting started building a site may seem a little daunting. Included below is the description and source code for a production site written with Flourish and SQLite.

The northshorewebgeeks.com site provides a simple list of upcoming and past meetups for the North Shore Web Geeks meetup. Rather than hand-editing the HTML and RSS feeds, Flourish was set up to provide a simple CMS to easily update the information.

Routing

The site is fairly simple, so I set up Apache with mod_rewrite to create some nice, clean URLs:

/                     # -> index.php  : Home/main page
/rss                  # -> index.php  : RSS feed
/manage               # -> manage.php : Lists all meetups
/manage/add           # -> manage.php : Allows adding a meetup
/manage/{date}/edit   # -> manage.php : Allows editing a meetup
/manage/{date}/delete # -> manage.php : Allows deleting a meetup
/log_in               # -> login.php  : Allows a user to log in
/log_out              # -> login.php  : Lets the user log out
/sup/*                # -> sup/*      : Static files including CSS and images

Anything other than those URLs will cause the page at views/404.php to be shown.

Directory Structure

The files on the filesystem are organized in the following structure:

/                     # The three php scripts that handle requests
/inc/                 # The bootstrap script init.php and config file
/inc/classes/         # The ORM classes
/inc/flourish/        # Flourish classes
/storage/db/          # The SQLite database the source .sql file
/storage/session/     # The PHP session files
/sup/css/             # The CSS files
/sup/img/             # The images
/views/               # The HTML templates for the site

Bootstrap Script and Config

The following code is from inc/init.php and show the initial setup for each request.

<?php
include dirname(__FILE__) . '/config.php';
 
$tmpl = new fTemplating(DOC_ROOT . '/views/');
$tmpl->set('header', 'header.php');
$tmpl->set('footer', 'footer.php');
 
$db = new fDatabase('sqlite', DOC_ROOT . '/storage/db/northshorewebgeeks.db');
fORMDatabase::attach($db);
 
fSession::open();

The configuration script, inc/config.php:

<?php
define('DOC_ROOT', realpath(dirname(__FILE__) . '/../'));
define('URL_ROOT', substr(DOC_ROOT, strlen(realpath($_SERVER['DOCUMENT_ROOT']))) . '/');
 
error_reporting(E_STRICT | E_ALL);
fCore::enableErrorHandling('html');
fCore::enableExceptionHandling('html');
 
fTimestamp::setDefaultTimezone('America/New_York');
 
fAuthorization::setLoginPage(URL_ROOT . 'log_in');
 
// This prevents cross-site session transfer
fSession::setPath(DOC_ROOT . '/storage/session/');
 
 
include DOC_ROOT . '/inc/flourish/constructor_functions.php';
 
 
/**
 * Automatically includes classes
 * 
 * @throws Exception
 * 
 * @param  string $class  Name of the class to load
 * @return void
 */
function __autoload($class)
{
    $flourish_file = DOC_ROOT . '/inc/flourish/' . $class . '.php';
 
    if (file_exists($flourish_file)) {
        return require $flourish_file;
    }
    
    $file = DOC_ROOT . '/inc/classes/' . $class . '.php';
 
    if (file_exists($file)) {
        return require $file;
    }
    
    throw new Exception('The class ' . $class . ' could not be loaded');
}

The Login Page

The login page handles both a user logging in, and a user logging out.

<?php
include './inc/init.php';
 
$action = fRequest::get('action');
 
 
// --------------------------------- //
if ('log_out' == $action) {
    
    fAuthorization::destroyUserInfo();
    fMessaging::create('success', URL_ROOT . 'log_in', 'You were successfully logged out');
    fURL::redirect(URL_ROOT . 'log_in');    
    
}
 
 
// --------------------------------- // 
if ('log_in' == $action) {
    
    if (fRequest::isPost()) {    
        try {
            $valid_login = fRequest::get('username') == 'admin';
            $valid_pass  = fCryptography::checkPasswordHash(
                fRequest::get('password'),
                'fCryptography::password_hash#B8CnJMDK29#acdec016d3e6608703f1684139dcfa40e424eb55'
            );
            
            if (!$valid_login || !$valid_pass) {
                throw new fValidationException('The login or password entered is invalid');    
            }
            
            // We don't have any fancy users, so this is something to indicate the user is logged in
            fAuthorization::setUserToken('1');
            
            fURL::redirect(
                fAuthorization::getRequestedURL(TRUE, URL_ROOT . 'manage')
            );
        
        } catch (fExpectedException $e) {
            fMessaging::create('error', fURL::get(), $e->getMessage());
        }    
    }
 
    include './views/log_in.php';
    
}

The Manage Page

Finally, this is the code that handles listing, adding, editing and deleting meetups from the site.

<?php
include './inc/init.php';
 
fAuthorization::requireLoggedIn();
 
$action = fRequest::getValid(
    'action',
    array('list', 'add', 'edit', 'delete')
);
 
$date       = fRequest::get('date');
$old_date   = fRequest::get('old_date');
$manage_url = URL_ROOT . Meetup::makeURL('list');
 
 
// --------------------------------- //
if ('delete' == $action) {
    
    try {
        
        $meetup = new Meetup($date);
        
        if (fRequest::isPost()) {
        
            fCRUD::validateRequestToken(fRequest::get('token'));
            
            $meetup->delete();
            
            fMessaging::create('success', $manage_url, 'The meetup on ' . $meetup->getDate()->format('F j, Y') . ' was successfully deleted');
            fURL::redirect($manage_url);    
        
        }
    
    } catch (fNotFoundException $e) {
        fMessaging::create('error', $manage_url, 'The meetup requested, ' . fHTML::encode($date) . ', could not be found');
        fURL::redirect($manage_url);
    
    } catch (fExpectedException $e) {
        fMessaging::create('error', fURL::get(), $e->getMessage());    
    }
 
    include './views/delete.php';    
    
}
 
 
// --------------------------------- // 
if ('edit' == $action) {
    
    try {
        
        $meetup = new Meetup($old_date ? $old_date : $date);
        
        if (fRequest::isPost()) {
        
            $meetup->populate();
            
            fCRUD::validateRequestToken(fRequest::get('token'));
            
            $meetup->store();
            
            fMessaging::create('affected', $manage_url, $meetup->getDate()->__toString());
            fMessaging::create('success', $manage_url, 'The meetup on ' . $meetup->getDate()->format('F j, Y') . ' was successfully updated');
            fURL::redirect($manage_url);    
        
        }
    
    } catch (fNotFoundException $e) {
        fMessaging::create('error', $manage_url, 'The meetup requested, ' . fHTML::encode($date) . ', could not be found');    
        fURL::redirect($manage_url);
        
    } catch (fExpectedException $e) {
        fMessaging::create('error', fURL::get(), $e->getMessage());    
    }
 
    include './views/add_edit.php';
    
}
 
 
// --------------------------------- //
if ('add' == $action) {
    
    $meetup = new Meetup();
    
    if (fRequest::isPost()) {    
        
        try {
            $meetup->populate();
            
            fCRUD::validateRequestToken(fRequest::get('token'));
            
            $meetup->store();
            
            fMessaging::create('affected', $manage_url, $meetup->getDate()->__toString());
            fMessaging::create('success', $manage_url, 'The meetup on ' . $meetup->getDate()->format('F j, Y') . ' was successfully created');
            fURL::redirect($manage_url);
                
        } catch (fExpectedException $e) {
            fMessaging::create('error', fURL::get(), $e->getMessage());    
        }    
        
    } else {
        
        // Get the third thursday of this month, or next month if this month's has passed
        $date = fDate()->modify('Y-m-01')->adjust('+3 thursdays');
        if ($date->getSecondsDifference(fDate()) < 0) {
            $date = fDate('next month')->modify('Y-m-01')->adjust('+3 thursdays');
        }
        
        $meetup->setDate($date);
        $meetup->setVenue('The Grog');
        $meetup->setVenueWebsite('http://thegrog.com');    
        $meetup->setCity('Newburyport');
        $meetup->setState('MA');
        $meetup->setStartTime('7:00 pm');
        $meetup->setEndTime('10:30 pm');
        
    }
 
    include './views/add_edit.php';    
    
}
 
 
// --------------------------------- // 
if ('list' == $action) {
    
    $col = fCRUD::getSortColumn('date', 'location');
    $dir = fCRUD::getSortDirection('desc');
    fCRUD::redirectWithLoadedValues();
    
    $meetups = Meetup::findAll($col, $dir);
 
    include './views/list.php';
    
}

The ORM Class

Since this site only has a single table storing the meetup details, only a single fActiveRecord class is needed. This class models a single row in the meetups table and other related functionality.

<?php
/**
 * Handles storing and retrieving a single meetup
 *        
 * @copyright  Copyright (c) 2009 Will Bond
 * @author     Will Bond [wb] <will@flourishlib.com>
 */
class Meetup extends fActiveRecord
{
    /**
     * Returns all meetups on the system
     * 
     * @param  string  $sort_column  The column to sort by
     * @param  string  $sort_dir     The direction to sort the column
     * @return fRecordSet  An object containing all meetups
     */
    static function findAll($sort_column, $sort_dir)
    {
        if (!in_array($sort_column, array('date', 'location'))) {
            $sort_column = 'date';
        } 
        
        if (!in_array($sort_dir, array('asc', 'desc'))) {
            $sort_dir = 'desc';
        }
        
        if ($sort_column == 'location') {
            $sort_column = "venue || ' ' || city || ' ' || state";    
        }
        
        return fRecordSet::build(
            __CLASS__,
            array(),
            array($sort_column => $sort_dir)
        );    
    }
    
    
    /**
     * Returns all meetups on the system through next month
     * 
     * @return fRecordSet  An object containing all meetups
     */
    static function findCurrent()
    {
        return fRecordSet::build(
            __CLASS__,
            array('date<=' => fDate('+4 weeks')),
            array('date' => 'desc')
        );    
    }
    
    
    /**
     * Creates all Meetup related URLs for the site
     * 
     * @param  string $type  The type of URL to make: 'list', 'add', 'edit', 'delete'
     * @param  Meetup $obj   The Meetup object for the edit and delete URL types
     * @return string  The URL requested
     */
    static public function makeURL($type, $obj=NULL)
    {
        switch ($type)
        {
            case 'list':
                return 'manage';
            case 'add':
                return 'manage/add';
            case 'edit':
                return 'manage/' . $obj->prepareDate('Y-m-d') . '/edit';
            case 'delete':
                return 'manage/' . $obj->prepareDate('Y-m-d') . '/delete';
        }    
    }
    
    
    /**
     * Allows the programmer to set features for the class. Only called once per page load for each class.
     * 
     * @return void
     */
    protected function configure()
    {
        fORMColumn::configureLinkColumn($this, 'venue_website');
        fORMColumn::configureLinkColumn($this, 'yahoo_upcoming_url');
        
        fORMDate::configureDateCreatedColumn($this, 'date_posted');
    }    
    
    
    /**    
     * Sets the Google Maps HTML, removing some unwanted HTML attributes
     * 
     * @param  string $google_maps_html  The HTML for the embedded google map
     * @return void
     */
    public function setGoogleMapsHtml($google_maps_html)
    {
        $google_maps_html = preg_replace('#<iframe.*?src=#', '<iframe src=', $google_maps_html);
        $google_maps_html = preg_replace('#\s+style=".*?"#', '', $google_maps_html);
        $this->set('google_maps_html', $google_maps_html);
    }    
}

Other Files

Most of the other files in the demo site are simply display files with mostly HTML contents.

Live Instance

The site is currently running live on http://demo.flourishlib.com. You can log in with the following information:

  • Login: admin
  • Password: password

Right now the site will allow you to edit anything, however I may need to disabled saving changes if the site is spammed or inappropriate content is added.

Source Code

Feel free to grab flourish_demo_site_r745.zip and explore the source.

If you wish to run the demo site on your own server, simply extract the zip file and then recursively chmod the storage directory to 777 so that the web server can write to the SQLite database and session directory.

chmod -R 777 /path/to/flourish_demo_site/storage

All files included with the demo site, except for graphics and CSS files, are licensed under the MIT license. Graphics and CSS files are only provided for demo purposes. Please see the license.txt file in the zip for more details.


Comments

wbond at 12:56 pm on Jan 14, 2009

For those interested, Einars posted an example rewrite configuration in the forum if you want to use the demo site with lighttpd:

$HTTP["host"] == "flourish" {
    server.document-root = "/srv/www/flourish"
    url.rewrite-once += (
        "^/$"                    => "/index.php?type=html",
        "^/rss/?$"               => "/index.php?type=rss",
        "^/(log_in|log_out)$"    => "/login.php?action=$1",
        "^/manage/add$"          => "/manage.php?action=add",
        "^/manage/(\d{4}-\d{2}-\d{2})/(edit|delete)$"
                                 => "/manage.php?action=$2&date=$1",
        "^/manage/?(?:\?(.*))?$" => "/manage.php?$1",
    )
}

richard at 9:29 am on May 28, 2009

what does your mod rewrite look like?

wbond at 9:43 am on May 28, 2009

@richard

The mod_rewrite is as follows:

RewriteEngine   On

## the real rewrite rules
RewriteRule     ^favicon.ico                                    sup/img/favicon.ico             [L]

RewriteRule     ^$                                              index.php?type=html             [L]
RewriteRule     ^rss$                                           index.php?type=rss              [L]

RewriteRule     ^(log_in|log_out)$                              login.php?action=$1             [L]

RewriteRule     ^manage$                                        manage.php                      [L,QSA]
RewriteRule     ^manage/add$                                    manage.php?action=add           [L]
RewriteRule     ^manage/(\d{4}-\d{2}-\d{2})/(edit|delete)$      manage.php?action=$2&date=$1    [L]

# Prevent access to anything that didn't match so far and isn't in /sup/
RewriteCond     %{ENV:REDIRECT_STATUS}                          ^$ 
RewriteRule     ^(?!sup/|robots\.txt)                           views/404.php                   [L]

richard@guthnur.net at 10:03 am on May 28, 2009

@wbond Thanks. I'm a php developer with a company here in maine, I found your library and i have to say its really good, If you need good php developer let me know.

richard at 10:14 am on Jun 3, 2009

if i wanted to see a individual post how you handle that in the index.php

Mori at 11:01 am on Aug 8, 2009

Do you have a demo site that fully based on Flourish framework?

wbond at 8:44 pm on Aug 8, 2009

@Mori

This example site is fully based on Flourish, it uses only Flourish and built-in PHP functionality. Did you notice the ZIP download with all of the source files?

toln at 6:54 pm on Sep 25, 2009

RewriteCond     %{ENV:REDIRECT_STATUS}                          ^$ 
RewriteRule     ^(?!sup/|robots\.txt)                           views/404.php                   [L]

My set is a bit different I have

www.mysite.com

  • .htaccess
  • images
  • includes
    • flourish
    • init.php
    • config.php
  • index.php
  • templates
    • home.php
    • header.php
    • footer.php
    • 404.php

I am having a little issue with my 404 I figured i could change

RewriteRule     ^(?!sup/|robots\.txt)                           views/404.php                   [L]

to

RewriteRule     ^(?!templates/|robots\.txt)                           templates/404.php                   [L]

but no luck anyone? thanks

wbond at 1:45 pm on Oct 5, 2009

@toln

Since you renamed /views/ to /templates/ and /sup/ to /images/, the .htaccess should look like below. Notice how I changed sup/ to images/ and not templates/.

RewriteRule     ^(?!images/|robots\.txt)                           templates/404.php                   [L]
Add comment