Flourish Demo Site
Posted by Will Bond on 1/5/09 at 1:31 pm, 9 comments
Recent Blogs
- Prepared Statement Support Added posted 3/4/10
- Contributing posted 11/16/09
- New Oracle Support and More Tests posted 5/28/09
- Discussions and Comments posted 1/14/09
- Flourish Demo Site posted 1/5/09
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.
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:
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.
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.
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.
/** * 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
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]

RSS Feed
Twitter
Ohloh
GitHub
Bitbucket
Launchpad
IRC
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", ) }