Browse Source

Merge branch 'master' of https://github.com/jasonhinkle/FrameworkBenchmarks into PR782

Mike Smith 11 years ago
parent
commit
af975bf0c9
41 changed files with 1376 additions and 266 deletions
  1. 1 35
      phreeze/.htaccess
  2. 6 3
      phreeze/benchmark_config
  3. 10 10
      phreeze/deploy/nginx.conf
  4. 9 5
      phreeze/index.php
  5. 69 0
      phreeze/libs/Controller/TestController.php
  6. 57 0
      phreeze/libs/Model/DAO/FortuneCriteriaDAO.php
  7. 33 0
      phreeze/libs/Model/DAO/FortuneDAO.php
  8. 59 0
      phreeze/libs/Model/DAO/FortuneMap.php
  9. 58 0
      phreeze/libs/Model/Fortune.php
  10. 62 0
      phreeze/libs/Model/FortuneCriteria.php
  11. 18 0
      phreeze/libs/verysimple/DB/DataDriver/IDataDriver.php
  12. 26 0
      phreeze/libs/verysimple/DB/DataDriver/MySQL.php
  13. 263 0
      phreeze/libs/verysimple/DB/DataDriver/MySQL_PDO.php
  14. 33 1
      phreeze/libs/verysimple/DB/DataDriver/MySQLi.php
  15. 23 0
      phreeze/libs/verysimple/DB/DataDriver/SQLite.php
  16. 27 23
      phreeze/libs/verysimple/DB/Reflection/DBConnection.php
  17. 3 1
      phreeze/libs/verysimple/DB/Reflection/DBConnectionString.php
  18. 21 0
      phreeze/libs/verysimple/Form.php
  19. 4 0
      phreeze/libs/verysimple/HTTP/HttpRequest.php
  20. 32 21
      phreeze/libs/verysimple/HTTP/RequestUtil.php
  21. 5 4
      phreeze/libs/verysimple/Phreeze/ActionRouter.php
  22. 5 1
      phreeze/libs/verysimple/Phreeze/CacheMemCache.php
  23. 1 1
      phreeze/libs/verysimple/Phreeze/CacheNoCache.php
  24. 1 1
      phreeze/libs/verysimple/Phreeze/CacheRam.php
  25. 92 10
      phreeze/libs/verysimple/Phreeze/Controller.php
  26. 22 3
      phreeze/libs/verysimple/Phreeze/Criteria.php
  27. 9 0
      phreeze/libs/verysimple/Phreeze/CriteriaFilter.php
  28. 36 3
      phreeze/libs/verysimple/Phreeze/DataAdapter.php
  29. 48 30
      phreeze/libs/verysimple/Phreeze/DataSet.php
  30. 69 29
      phreeze/libs/verysimple/Phreeze/Dispatcher.php
  31. 20 10
      phreeze/libs/verysimple/Phreeze/GenericRouter.php
  32. 3 3
      phreeze/libs/verysimple/Phreeze/ICache.php
  33. 1 1
      phreeze/libs/verysimple/Phreeze/PHPRenderEngine.php
  34. 1 1
      phreeze/libs/verysimple/Phreeze/Phreezable.php
  35. 64 8
      phreeze/libs/verysimple/Phreeze/Phreezer.php
  36. 1 1
      phreeze/libs/verysimple/Phreeze/Reporter.php
  37. 121 0
      phreeze/libs/verysimple/Phreeze/SimpleRouter.php
  38. 4 3
      phreeze/libs/verysimple/String/SimpleTemplate.php
  39. 13 13
      phreeze/libs/verysimple/String/VerySimpleStringUtil.php
  40. 28 45
      phreeze/libs/verysimple/Util/MemCacheProxy.php
  41. 18 0
      phreeze/templates/TestFortunes.php

+ 1 - 35
phreeze/.htaccess

@@ -1,37 +1,3 @@
 ##
-## PHREEZE ACCESS RULES FOR APACHE
-## VERSION 1.2
+## NO RULES NECESSARY FOR PHREEZE
 ##
-
-## PHP ERROR REPORTING
-# php_flag display_errors 1
-# php_value error_reporting 1 # (or 8191 for all errors)
-# php_flag asp_tags 0
-
-## PHP FILE UPLOAD LIMITS
-# php_value upload_max_filesize 3M
-# php_value post_max_size 3M
-# php_value max_execution_time 200
-# php_value max_input_time 200
-
-<IfModule mod_rewrite.c>
-	Options +FollowSymLinks
-	RewriteEngine On
-	
-	## TODO: some hosts require the app root must be specified
-	# RewriteBase /
-
-	# Redirect all requests to index.php unless the directory, file or link exists
-	RewriteCond %{REQUEST_FILENAME} !-f
-	RewriteCond %{REQUEST_FILENAME} !-d
-	RewriteCond %{REQUEST_FILENAME} !-l
-	RewriteRule (.*) index.php?_REWRITE_COMMAND=$1 [QSA,L]
-</IfModule>
-
-# Add correct mime type for web fonts to supress browser warnings
-<IfModule mod_mime.c>
-	AddType application/vnd.ms-fontobject eot
-	AddType font/opentype otf
-	AddType font/truetype ttf
-	AddType application/x-font-woff woff
-</IfModule>

+ 6 - 3
phreeze/benchmark_config

@@ -3,9 +3,12 @@
   "tests": [{
     "default": {
       "setup_file": "setup",
-      "json_url": "/json",
-      "db_url": "/db",
-      "query_url": "/db?queries=",
+      "json_url": "/index.php?json",
+      "db_url": "/index.php?db",
+      "query_url": "/index.php?db&queries=",
+      "fortune_url": "/index.php?fortunes",
+      "update_url": "/index.php?updates&queries=",
+      "plaintext_url": "/index.php?plaintext",
       "port": 8080,
       "approach": "Realistic",
       "classification": "Micro",

+ 10 - 10
phreeze/deploy/nginx.conf

@@ -33,6 +33,11 @@ http {
 
     #gzip  on;
 
+    upstream fastcgi_backend {
+        server 127.0.0.1:9001;
+        keepalive 32;
+    }
+
     server {
         listen       8080;
         server_name  localhost;
@@ -60,25 +65,20 @@ http {
         #location ~ \.php$ {
         #    proxy_pass   http://127.0.0.1;
         #}
-        
-        root /home/ubuntu/FrameworkBenchmarks/phreeze/;
-        index  index.php;
-        
-        location / {
-            try_files $uri $uri/ /index.php?_REWRITE_COMMAND=$uri&$args;
-        }
 
         # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
         #
         location ~ \.php$ {
-            try_files $uri =404;
-            fastcgi_pass   127.0.0.1:9001;
+            root /var/www/FrameworkBenchmarks/phreeze;
+            fastcgi_pass   fastcgi_backend;
+#            fastcgi_pass 127.0.0.1:9001;
             fastcgi_index  index.php;
 #            fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
             fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
+            fastcgi_keep_conn on;
             include        /usr/local/nginx/conf/fastcgi_params;
         }
-    
+
         # deny access to .htaccess files, if Apache's document root
         # concurs with nginx's one
         #

+ 9 - 5
phreeze/index.php

@@ -1,16 +1,17 @@
 <?php
 /** @package    HELLO WORLD */
 
-set_include_path('libs/' . PATH_SEPARATOR . get_include_path());
+set_include_path('libs/');
 
 /* require framework libs */
 require_once 'verysimple/Phreeze/Dispatcher.php';
 require_once 'verysimple/Phreeze/ConnectionSetting.php';
-require_once 'verysimple/Phreeze/GenericRouter.php';
+require_once 'verysimple/Phreeze/SimpleRouter.php';
 require_once 'verysimple/Phreeze/Phreezer.php';
+require_once 'Controller/TestController.php';
 
 $cs = new ConnectionSetting();
-$cs->ConnectionString = "172.16.98.98:3306";
+$cs->ConnectionString = "localhost:3306";
 $cs->DBName = "hello_world";
 $cs->Username = "benchmarkdbuser";
 $cs->Password = "benchmarkdbpass";
@@ -21,10 +22,13 @@ $phreezer = new Phreezer($cs);
 $route_map = array(
 		'GET:' => array('route' => 'Test.JSON'),
 		'GET:json' => array('route' => 'Test.JSON'),
-		'GET:db' => array('route' => 'Test.DB')
+		'GET:db' => array('route' => 'Test.DB'),
+		'GET:fortunes' => array('route' => 'Test.Fortunes'),
+		'GET:updates' => array('route' => 'Test.Updates'),
+		'GET:plaintext' => array('route' => 'Test.PlainText')
 );
 
-$router = new GenericRouter('/','Test.JSON',$route_map);
+$router = new SimpleRouter('/','Test.JSON',$route_map);
 
 Dispatcher::Dispatch(
 	$phreezer,

+ 69 - 0
phreeze/libs/Controller/TestController.php

@@ -39,6 +39,8 @@ class TestController extends Controller
 	 */
 	public function DB()
 	{
+		require_once("Model/World.php");
+		
 		// Read number of queries to run from URL parameter
 		$query_count = RequestUtil::Get('queries',1);
 
@@ -58,4 +60,71 @@ class TestController extends Controller
 
 	}
 	
+	/**
+	 * Output the Fortunes test template
+	 */
+	public function Fortunes()
+	{
+		require_once("Model/Fortune.php");
+		require_once("verysimple/Phreeze/PHPRenderEngine.php");
+		
+		// charset must be set to UTF8 to support multi-byte chars
+		$this->Phreezer->DataAdapter->ConnectionSetting->Charset = "utf8";
+		
+		// obtain fortunes without using 'order by'
+		$fortunes = $this->Phreezer->Query('Fortune')->ToObjectArray();
+		
+		// dynamically add a new, non-persisted Fortune object
+		$newFortune = new Fortune($this->Phreezer);
+		$newFortune->Id = 0;
+		$newFortune->Message = 'Additional fortune added at request time.';
+		$fortunes[] = $newFortune;
+		
+		// sort (will use Fortune->ToString)
+		Phreezer::Sort($fortunes);
+		
+		// Render using a template
+		$this->RenderEngine = new PHPRenderEngine('templates');
+		$this->Assign('fortunes',$fortunes);
+		$this->Render('TestFortunes.php');
+	}
+	
+	/**
+	 * Test for performing updates
+	 */
+	public function Updates()
+	{
+		require_once("Model/World.php");
+		
+		// Read number of queries to run from URL parameter
+		$query_count = RequestUtil::Get('queries',1);
+		
+		$arr = array();
+		
+		for ($i = 0; $i < $query_count; $i++) {
+		
+			$id = mt_rand(1, 10000);
+				
+			$world = $this->Phreezer->Get("World",$id);
+			
+			// update the random number and persist the record
+			$world->Randomnumber = mt_rand(1, 10000);
+			$world->Save();
+				
+			// convert the Phreezable object into a simple structure for output
+			$arr[] = array('id'=>$world->Id,'randomNumber'=>$world->Randomnumber);
+		}
+		
+		$this->RenderJSON($arr);
+	}
+	
+	/**
+	 * Test for outputting plaintext
+	 */
+	public function PlainText()
+	{
+		header('Content-type: text/plain');
+		echo 'Hello, World!';
+	}
+	
 }

+ 57 - 0
phreeze/libs/Model/DAO/FortuneCriteriaDAO.php

@@ -0,0 +1,57 @@
+<?php
+/** @package    HelloWorld::Model::DAO */
+
+/** import supporting libraries */
+require_once("verysimple/Phreeze/Criteria.php");
+
+/**
+ * FortuneCriteria allows custom querying for the Fortune object.
+ *
+ * WARNING: THIS IS AN AUTO-GENERATED FILE
+ *
+ * This file should generally not be edited by hand except in special circumstances.
+ * Add any custom business logic to the ModelCriteria class which is extended from this class.
+ * Leaving this file alone will allow easy re-generation of all DAOs in the event of schema changes
+ *
+ * @inheritdocs
+ * @package HelloWorld::Model::DAO
+ * @author ClassBuilder
+ * @version 1.0
+ */
+class FortuneCriteriaDAO extends Criteria
+{
+
+	public $Id_Equals;
+	public $Id_NotEquals;
+	public $Id_IsLike;
+	public $Id_IsNotLike;
+	public $Id_BeginsWith;
+	public $Id_EndsWith;
+	public $Id_GreaterThan;
+	public $Id_GreaterThanOrEqual;
+	public $Id_LessThan;
+	public $Id_LessThanOrEqual;
+	public $Id_In;
+	public $Id_IsNotEmpty;
+	public $Id_IsEmpty;
+	public $Id_BitwiseOr;
+	public $Id_BitwiseAnd;
+	public $Message_Equals;
+	public $Message_NotEquals;
+	public $Message_IsLike;
+	public $Message_IsNotLike;
+	public $Message_BeginsWith;
+	public $Message_EndsWith;
+	public $Message_GreaterThan;
+	public $Message_GreaterThanOrEqual;
+	public $Message_LessThan;
+	public $Message_LessThanOrEqual;
+	public $Message_In;
+	public $Message_IsNotEmpty;
+	public $Message_IsEmpty;
+	public $Message_BitwiseOr;
+	public $Message_BitwiseAnd;
+
+}
+
+?>

+ 33 - 0
phreeze/libs/Model/DAO/FortuneDAO.php

@@ -0,0 +1,33 @@
+<?php
+/** @package HelloWorld::Model::DAO */
+
+/** import supporting libraries */
+require_once("verysimple/Phreeze/Phreezable.php");
+require_once("FortuneMap.php");
+
+/**
+ * FortuneDAO provides object-oriented access to the Fortune table.  This
+ * class is automatically generated by ClassBuilder.
+ *
+ * WARNING: THIS IS AN AUTO-GENERATED FILE
+ *
+ * This file should generally not be edited by hand except in special circumstances.
+ * Add any custom business logic to the Model class which is extended from this DAO class.
+ * Leaving this file alone will allow easy re-generation of all DAOs in the event of schema changes
+ *
+ * @package HelloWorld::Model::DAO
+ * @author ClassBuilder
+ * @version 1.0
+ */
+class FortuneDAO extends Phreezable
+{
+	/** @var int */
+	public $Id;
+
+	/** @var string */
+	public $Message;
+
+
+
+}
+?>

+ 59 - 0
phreeze/libs/Model/DAO/FortuneMap.php

@@ -0,0 +1,59 @@
+<?php
+/** @package    HelloWorld::Model::DAO */
+
+/** import supporting libraries */
+require_once("verysimple/Phreeze/IDaoMap.php");
+
+/**
+ * FortuneMap is a static class with functions used to get FieldMap and KeyMap information that
+ * is used by Phreeze to map the FortuneDAO to the Fortune datastore.
+ *
+ * WARNING: THIS IS AN AUTO-GENERATED FILE
+ *
+ * This file should generally not be edited by hand except in special circumstances.
+ * You can override the default fetching strategies for KeyMaps in _config.php.
+ * Leaving this file alone will allow easy re-generation of all DAOs in the event of schema changes
+ *
+ * @package HelloWorld::Model::DAO
+ * @author ClassBuilder
+ * @version 1.0
+ */
+class FortuneMap implements IDaoMap
+{
+	/**
+	 * Returns a singleton array of FieldMaps for the Fortune object
+	 *
+	 * @access public
+	 * @return array of FieldMaps
+	 */
+	public static function GetFieldMaps()
+	{
+		static $fm = null;
+		if ($fm == null)
+		{
+			$fm = Array();
+			$fm["Id"] = new FieldMap("Id","Fortune","id",true,FM_TYPE_INT,10,null,true);
+			$fm["Message"] = new FieldMap("Message","Fortune","message",false,FM_TYPE_VARCHAR,2048,null,false);
+		}
+		return $fm;
+	}
+
+	/**
+	 * Returns a singleton array of KeyMaps for the Fortune object
+	 *
+	 * @access public
+	 * @return array of KeyMaps
+	 */
+	public static function GetKeyMaps()
+	{
+		static $km = null;
+		if ($km == null)
+		{
+			$km = Array();
+		}
+		return $km;
+	}
+
+}
+
+?>

+ 58 - 0
phreeze/libs/Model/Fortune.php

@@ -0,0 +1,58 @@
+<?php
+/** @package    HelloWorld::Model */
+
+/** import supporting libraries */
+require_once("DAO/FortuneDAO.php");
+require_once("FortuneCriteria.php");
+
+/**
+ * The Fortune class extends FortuneDAO which provides the access
+ * to the datastore.
+ *
+ * @package HelloWorld::Model
+ * @author ClassBuilder
+ * @version 1.0
+ */
+class Fortune extends FortuneDAO
+{
+
+	/**
+	 * Override default validation
+	 * @see Phreezable::Validate()
+	 */
+	public function Validate()
+	{
+		// example of custom validation
+		// $this->ResetValidationErrors();
+		// $errors = $this->GetValidationErrors();
+		// if ($error == true) $this->AddValidationError('FieldName', 'Error Information');
+		// return !$this->HasValidationErrors();
+
+		return parent::Validate();
+	}
+
+	/**
+	 * @see Phreezable::OnSave()
+	 */
+	public function OnSave($insert)
+	{
+		// the controller create/update methods validate before saving.  this will be a
+		// redundant validation check, however it will ensure data integrity at the model
+		// level based on validation rules.  comment this line out if this is not desired
+		if (!$this->Validate()) throw new Exception('Unable to Save Fortune: ' .  implode(', ', $this->GetValidationErrors()));
+
+		// OnSave must return true or eles Phreeze will cancel the save operation
+		return true;
+	}
+	
+	/**
+	 * Override ToString for sorting purposes
+	 */
+	public function ToString()
+	{
+		return $this->Message;
+	}
+
+}
+
+?>

+ 62 - 0
phreeze/libs/Model/FortuneCriteria.php

@@ -0,0 +1,62 @@
+<?php
+/** @package    HelloWorld::Model */
+
+/** import supporting libraries */
+require_once("DAO/FortuneCriteriaDAO.php");
+
+/**
+ * The FortuneCriteria class extends FortuneDAOCriteria and is used
+ * to query the database for objects and collections
+ * 
+ * @inheritdocs
+ * @package HelloWorld::Model
+ * @author ClassBuilder
+ * @version 1.0
+ */
+class FortuneCriteria extends FortuneCriteriaDAO
+{
+	
+	/**
+	 * GetFieldFromProp returns the DB column for a given class property
+	 * 
+	 * If any fields that are not part of the table need to be supported
+	 * by this Criteria class, they can be added inside the switch statement
+	 * in this method
+	 * 
+	 * @see Criteria::GetFieldFromProp()
+	 */
+	/*
+	public function GetFieldFromProp($propname)
+	{
+		switch($propname)
+		{
+			 case 'CustomProp1':
+			 	return 'my_db_column_1';
+			 case 'CustomProp2':
+			 	return 'my_db_column_2';
+			default:
+				return parent::GetFieldFromProp($propname);
+		}
+	}
+	*/
+	
+	/**
+	 * For custom query logic, you may override OnProcess and set the $this->_where to whatever
+	 * sql code is necessary.  If you choose to manually set _where then Phreeze will not touch
+	 * your where clause at all and so any of the standard property names will be ignored
+	 *
+	 * @see Criteria::OnPrepare()
+	 */
+	/*
+	function OnPrepare()
+	{
+		if ($this->MyCustomField == "special value")
+		{
+			// _where must begin with "where"
+			$this->_where = "where db_field ....";
+		}
+	}
+	*/
+
+}
+?>

+ 18 - 0
phreeze/libs/verysimple/DB/DataDriver/IDataDriver.php

@@ -131,6 +131,24 @@ interface IDataDriver
 	 * @param string name of table to optimize
 	 */
  	function Optimize($connection,$table);
+ 	
+ 	/**
+ 	 * Start a database transaction and disable auto-commit if necessary
+ 	 * @param mixed connection reference
+ 	 */
+ 	function StartTransaction($connection);
+ 	
+ 	/**
+ 	 * Commit the current database transaction and re-enable auto-commit
+ 	 * @param mixed connection reference
+ 	 */
+ 	function CommitTransaction($connection);
+ 	
+ 	/**
+ 	 * Rollback the current database transaction and re-enable auto-commit
+ 	 * @param mixed connection reference
+ 	 */
+ 	function RollbackTransaction($connection);
 }
 
 ?>

+ 26 - 0
phreeze/libs/verysimple/DB/DataDriver/MySQL.php

@@ -208,6 +208,32 @@ class DataDriverMySQL implements IDataDriver
 		return $result;
 	}
 	
+	/**
+	 * @inheritdocs
+	 */
+	function StartTransaction($connection)
+	{
+		$this->Execute($connection, "SET AUTOCOMMIT=0");
+		$this->Execute($connection, "START TRANSACTION");
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function CommitTransaction($connection)
+	{
+		$this->Execute($connection, "COMMIT");
+		$this->Execute($connection, "SET AUTOCOMMIT=1");
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function RollbackTransaction($connection)
+	{
+		$this->Execute($connection, "ROLLBACK");
+		$this->Execute($connection, "SET AUTOCOMMIT=1");
+	}
 }
 
 ?>

+ 263 - 0
phreeze/libs/verysimple/DB/DataDriver/MySQL_PDO.php

@@ -0,0 +1,263 @@
+<?php 
+/** @package verysimple::DB::DataDriver */
+
+require_once("IDataDriver.php");
+require_once("verysimple/DB/ISqlFunction.php");
+require_once("verysimple/DB/DatabaseException.php");
+require_once("verysimple/DB/DatabaseConfig.php");
+
+/**
+ * An implementation of IDataDriver that communicates with
+ * a MySQL server.  This is one of the native drivers
+ * supported by Phreeze
+ *
+ * @package    verysimple::DB::DataDriver
+ * @author     VerySimple Inc. <[email protected]>
+ * @copyright  1997-2010 VerySimple Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    1.0
+ */
+class DataDriverMySQL_PDO implements IDataDriver
+{	
+	/** @var characters that will be escaped */
+	static $BAD_CHARS = array("\\","\0","\n","\r","\x1a","'",'"');
+	
+	/** @var characters that will be used to replace bad chars */
+	static $GOOD_CHARS = array("\\\\","\\0","\\n","\\r","\Z","\'",'\"');
+	
+	/**
+	 * @inheritdocs
+	 */
+	function GetServerType()
+	{
+		return "MySQL";
+	}
+	
+	function Ping($connection)
+	{
+		 return mysql_ping($connection);
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function Open($connectionstring,$database,$username,$password,$charset='',$bootstrap='') 
+	{
+		if (!class_exists("PDO")) throw new DatabaseException('PDO extension is not enabled on this server.',DatabaseException::$CONNECTION_ERROR);
+		
+		$connection = null;
+		
+		try 
+		{
+			// if the port is provided in the connection string then strip it out and provide it as a separate param
+			$hostAndPort = explode(":",$connectionstring);
+			$host = $hostAndPort[0];
+			$port = count($hostAndPort) > 1 ? $hostAndPort[1] : null;
+			
+			$dsn = 'mysql:dbname='. $this->Escape($database)  .';host=' . $this->Escape($host);
+			
+			if ($port) {
+				$dsn .= ";port=" . $this->Escape($port);
+			}
+			
+			if ($charset) {
+				$dsn .= ";charset=" . $this->Escape($charset);
+			}
+			
+			$connection = new PDO($dsn, $username, $password);
+		} 
+		catch (Exception $e) 
+		{
+			throw new DatabaseException("Error connecting to database: " . $e->getMessage(),DatabaseException::$CONNECTION_ERROR);
+		}
+		
+		if ($bootstrap) 
+		{
+			$statements = explode(';',$bootstrap);
+			foreach ($statements as $sql)
+			{
+				try 
+				{
+					$this->Execute($connection, $sql);
+				}
+				catch (Exception $ex)
+				{
+					throw new DatabaseException("Connection Bootstrap Error: " . $ex->getMessage(),DatabaseException::$ERROR_IN_QUERY);
+				}
+			}
+		}
+		
+		return $connection;
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function Close($connection) 
+	{
+		$connection = null; // ignore warnings
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function Query($connection,$sql) 
+	{
+		
+		if (!$stmt = $connection->query($sql)) {
+			throw new DatabaseException($this->GetErrorDescription($connection),DatabaseException::$ERROR_IN_QUERY);
+		}
+		
+		return $stmt;
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function Execute($connection,$sql) 
+	{
+		$stmt = $connection->prepare($sql);
+		
+		if (!$stmt) {
+			throw new DatabaseException($this->GetErrorDescription($connection),DatabaseException::$ERROR_IN_QUERY);
+		}
+		
+		if (!$numRows = $stmt->execute())
+		{
+			throw new DatabaseException($this->GetErrorDescription($sth),DatabaseException::$ERROR_IN_QUERY);
+		}
+		
+		return $numRows;
+	}
+
+	/**
+	 * Given a PDO object, return the last error
+	 * @param PDO:errorInfo $errorInfo
+	 */
+	private function GetErrorDescription($obj)
+	{
+		$errorInfo = $obj->errorInfo();
+		return $errorInfo[2];
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	public function GetQuotedSql($val)
+	{
+		if ($val === null) return DatabaseConfig::$CONVERT_NULL_TO_EMPTYSTRING ? "''" : 'NULL';
+	
+		if ($val instanceof ISqlFunction) return $val->GetQuotedSql($this);
+	
+		return "'" . $this->Escape($val) . "'";
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function Fetch($connection,$rs) 
+	{
+		return $rs->fetch(PDO::FETCH_ASSOC);
+	}
+
+	/**
+	 * @inheritdocs
+	 */
+	function GetLastInsertId($connection) 
+	{
+		return $connection->lastInsertId(); 
+	}
+
+	/**
+	 * @inheritdocs
+	 */
+	function GetLastError($connection)
+	{
+		return $this->GetErrorDescription($connection);
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function Release($connection,$rs) 
+	{
+		$rs = null;
+	}
+	
+	/**
+	 * @inheritdocs
+	 * this method currently uses replacement and not mysql_real_escape_string
+	 * so that a database connection is not necessary in order to escape.
+	 * this way cached queries can be used without connecting to the DB server
+	 */
+	function Escape($val) 
+	{
+		return str_replace(self::$BAD_CHARS, self::$GOOD_CHARS, $val);
+		// return mysql_real_escape_string($val);
+ 	}
+	
+	/**
+	 * @inheritdocs
+	 */
+ 	function GetTableNames($connection, $dbname, $ommitEmptyTables = false) 
+	{
+		$sql = "SHOW TABLE STATUS FROM `" . $this->Escape($dbname) . "`";
+		$rs = $this->Query($connection,$sql);
+		
+		$tables = array();
+		
+		while ( $row = $this->Fetch($connection,$rs) )
+		{
+			if ( $ommitEmptyTables == false || $rs['Data_free'] > 0 )
+			{
+				$tables[] = $row['Name'];
+			}
+		}
+		
+		return $tables;
+ 	}
+	
+	/**
+	 * @inheritdocs
+	 */
+ 	function Optimize($connection,$table) 
+	{
+		$result = "";
+		$rs = $this->Query($connection,"optimize table `". $this->Escape($table)."`");
+
+		while ( $row = $this->Fetch($connection,$rs) )
+		{
+			$tbl = $row['Table'];
+			if (!isset($results[$tbl])) $results[$tbl] = "";
+			$result .= trim($results[$tbl] . " " . $row['Msg_type'] . "=\"" . $row['Msg_text'] . "\"");	
+		}
+		
+		return $result;
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function StartTransaction($connection)
+	{
+		$connection->beginTransaction();
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function CommitTransaction($connection)
+	{
+		$connection->commit();
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function RollbackTransaction($connection)
+	{
+		$connection->rollBack();
+	}
+}
+
+?>

+ 33 - 1
phreeze/libs/verysimple/DB/DataDriver/MySQLi.php

@@ -45,7 +45,12 @@ class DataDriverMySQLi implements IDataDriver
 	{
 		if (!function_exists("mysqli_connect")) throw new DatabaseException('mysqli extension is not enabled on this server.',DatabaseException::$CONNECTION_ERROR);
 		
-		$connection = mysqli_connect($connectionstring, $username, $password, $database);
+		// if the port is provided in the connection string then strip it out and provide it as a separate param
+		$hostAndPort = explode(":",$connectionstring);
+		$host = $hostAndPort[0];
+		$port = count($hostAndPort) > 1 ? $hostAndPort[1] : null;
+		
+		$connection = mysqli_connect($host, $username, $password, $database, $port);
 		
 		if ( mysqli_connect_errno() )
 		{
@@ -209,6 +214,33 @@ class DataDriverMySQLi implements IDataDriver
 		
 		return $result;
 	}
+
+	/**
+	 * @inheritdocs
+	 */
+	function StartTransaction($connection)
+	{
+		$this->Execute($connection, "SET AUTOCOMMIT=0");
+		$this->Execute($connection, "START TRANSACTION");
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function CommitTransaction($connection)
+	{
+		$this->Execute($connection, "COMMIT");
+		$this->Execute($connection, "SET AUTOCOMMIT=1");
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function RollbackTransaction($connection)
+	{
+		$this->Execute($connection, "ROLLBACK");
+		$this->Execute($connection, "SET AUTOCOMMIT=1");
+	}
 	
 }
 

+ 23 - 0
phreeze/libs/verysimple/DB/DataDriver/SQLite.php

@@ -177,6 +177,29 @@ class DataDriverSQLite implements IDataDriver
 		$this->Execute($connection,"VACUUM");
 	}
 	
+	/**
+	 * @inheritdocs
+	 */
+	function StartTransaction($connection)
+	{
+		throw new Exception('Transaction support is not implemented for this DataDriver');
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function CommitTransaction($connection)
+	{
+		throw new Exception('Transaction support is not implemented for this DataDriver');
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function RollbackTransaction($connection)
+	{
+		throw new Exception('Transaction support is not implemented for this DataDriver');
+	}
 }
 
 ?>

+ 27 - 23
phreeze/libs/verysimple/DB/Reflection/DBConnection.php

@@ -5,6 +5,7 @@
 require_once("DBEventHandler.php");
 require_once("DBConnectionString.php");
 require_once('verysimple/DB/DatabaseException.php');
+require_once('verysimple/Phreeze/DataAdapter.php');
 
 /**
  * DBConnection provides connectivity to a MySQL Server
@@ -24,6 +25,8 @@ class DBConnection
 	public $DBName;
 
 	private $dbconn;
+	private $csetting;
+	private $adapter;
 	private $handler;
 	private $dbopen;
 
@@ -38,11 +41,20 @@ class DBConnection
 	{
 
         $this->dbopen = false;
+        
 		$this->Host = $dbconnstring->Host;
 		$this->Port = $dbconnstring->Port;
 		$this->Username = $dbconnstring->Username;
 		$this->Password = $dbconnstring->Password;
 		$this->DBName = $dbconnstring->DBName;
+		
+		// TODO: this is redundant after switching to the DataAdapter
+		$this->csetting = new ConnectionSetting();
+		$this->csetting->ConnectionString = $dbconnstring->Host . ($dbconnstring->Port ? ':' . $dbconnstring->Port : '');
+		$this->csetting->DBName = $dbconnstring->DBName;
+		$this->csetting->Username = $dbconnstring->Username;
+		$this->csetting->Password = $dbconnstring->Password;
+		$this->csetting->Type = $dbconnstring->Type;
 
 		if ($handler)
 		{
@@ -81,14 +93,13 @@ class DBConnection
 		}
 		else
 		{
-			if ( !$this->dbconn = mysql_connect($this->Host . ":" . $this->Port, $this->Username, $this->Password) )
-			{
-				$this->handler->Crash(DatabaseException::$CONNECTION_ERROR,"Error connecting to database: " . mysql_error());
+			$this->adapter = new DataAdapter($this->csetting);
+			
+			try {
+				$this->adapter->Open();
 			}
-
-			if (!mysql_select_db($this->DBName, $this->dbconn))
-			{
-				$this->handler->Crash(DatabaseException::$CONNECTION_ERROR,"Unable to select database " . $this->DBName);
+			catch (Exception $ex) {
+				$this->handler->Crash(DatabaseException::$CONNECTION_ERROR,$ex->getMessage());
 			}
 
 			$this->handler->Log(DBH_LOG_INFO, "Connection Open");
@@ -128,7 +139,7 @@ class DBConnection
 
 		if ($this->dbopen)
 		{
-			mysql_close($this->dbconn);
+			$this->adapter->Close();
 			$this->dbopen = false;
 			$this->handler->Log(DBH_LOG_INFO, "Connection closed");
 		}
@@ -150,13 +161,8 @@ class DBConnection
 		$this->RequireConnection(true);
 
 		$this->handler->Log(DBH_LOG_QUERY, "Executing Query", $sql);
-
-		if ( !$rs = mysql_query($sql, $this->dbconn) )
-		{
-		   $this->handler->Crash(DatabaseException::$ERROR_IN_QUERY, 'Error executing SQL: ' . mysql_error());
-		}
-
-		return $rs;
+		
+		return $this->adapter->Select($sql);
 	}
 
 	/**
@@ -168,11 +174,8 @@ class DBConnection
 	function Update($sql)
 	{
 		$this->RequireConnection(true);
-
-		if ( !$result = mysql_query($sql, $this->dbconn) )
-		{
-		   $this->handler->Crash(DatabaseException::$ERROR_IN_QUERY,'Error executing SQL: ' . mysql_error());
-		}
+		
+		return $this->adapter->Escape($sql);
 	}
 
 	/**
@@ -185,9 +188,10 @@ class DBConnection
 	function Next($rs)
 	{
 		$this->RequireConnection();
-
+		
 		$this->handler->Log(DBH_LOG_DEBUG, "Fetching next result as array");
-		return mysql_fetch_assoc($rs);
+		return $this->adapter->Fetch($rs);
+
 	}
 
 	/**
@@ -201,7 +205,7 @@ class DBConnection
 		$this->RequireConnection();
 
 		$this->handler->Log(DBH_LOG_DEBUG, "Releasing result resources");
-		mysql_free_result($rs);
+		return $this->adapter->Release($rs);
 	}
 
 }

+ 3 - 1
phreeze/libs/verysimple/DB/Reflection/DBConnectionString.php

@@ -17,6 +17,7 @@
 	public $Username;
 	public $Password;
 	public $DBName;
+	public $Type;
 	
 	/**
 	 * Create a new instance of a DBConnectionString
@@ -28,13 +29,14 @@
 	 * @param string $password	 
 	 * @param string $$dbname	 
 	 */
-	function __construct($host = "", $port = "", $username = "", $password = "", $dbname = "")
+	function __construct($host = "", $port = "", $username = "", $password = "", $dbname = "", $type = "mysql")
 	{
 		$this->Host = $host;
 		$this->Port = $port;
 		$this->Username = $username;
 		$this->Password = $password;
 		$this->DBName = $dbname;
+		$this->Type = $type;
 	}
 	
 }

+ 21 - 0
phreeze/libs/verysimple/Form.php

@@ -0,0 +1,21 @@
+<?php
+/** @package    verysimple */
+
+/** import supporting libraries */
+require_once("verysimple/HTTP/Request.php");
+
+/**
+ * Static utility class for processing form input
+ *
+ * @package verysimple
+ * @link http://www.verysimple.com/
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version 1.0
+ * @deprecated use verysimple::HTTP::Request instead
+ */
+class Form extends Request 
+{
+}
+
+?>

+ 4 - 0
phreeze/libs/verysimple/HTTP/HttpRequest.php

@@ -53,6 +53,8 @@ class HttpRequest
 				curl_setopt($ch, CURLOPT_POSTFIELDS, $qs);
 				break;
 		}
+		
+		curl_setopt($ch,		CURLOPT_HTTPHEADER, array("Expect:  ") );  //Fixes the HTTP/1.1 417 Expectation Failed Bug
 
 		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
 		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
@@ -293,6 +295,8 @@ class HttpRequest
 			$ch = curl_init($full_url);
 		}
 
+		curl_setopt($ch,		CURLOPT_HTTPHEADER, array("Expect:  ") );  //Fixes the HTTP/1.1 417 Expectation Failed Bug
+		
 		curl_setopt($ch,		CURLOPT_FOLLOWLOCATION, 1);
 		curl_setopt($ch,		CURLOPT_RETURNTRANSFER, 1);
 		curl_setopt($ch,		CURLOPT_VERBOSE, 0); ########### debug

+ 32 - 21
phreeze/libs/verysimple/HTTP/RequestUtil.php

@@ -12,12 +12,11 @@
  * @author	 VerySimple Inc.
  * @copyright  1997-2011 VerySimple, Inc. http://www.verysimple.com
  * @license	http://www.gnu.org/licenses/lgpl.html  LGPL
- * @version	1.3
+ * @version	1.4
  */
 class RequestUtil
 {
 
-
 	/** @var bool set to true and all non-ascii characters in request variables will be html encoded */
 	static $ENCODE_NON_ASCII = false;
 
@@ -259,23 +258,23 @@ class RequestUtil
 	 * @return string URL
 	 */
 	public static function GetCurrentURL($include_querystring = true, $append_post_vars = false)
-	{
-		$server_protocol = isset($_SERVER["SERVER_PROTOCOL"]) ? $_SERVER["SERVER_PROTOCOL"] : "";
-		$http_host = isset($_SERVER["HTTP_HOST"]) ? $_SERVER["HTTP_HOST"] : "";
-		$server_port = isset($_SERVER["SERVER_PORT"]) ? $_SERVER["SERVER_PORT"] : "";
+	{
+		$server_protocol = isset($_SERVER["SERVER_PROTOCOL"]) ? $_SERVER["SERVER_PROTOCOL"] : "";
+		$http_host = isset($_SERVER["HTTP_HOST"]) ? $_SERVER["HTTP_HOST"] : "";
+		$server_port = isset($_SERVER["SERVER_PORT"]) ? $_SERVER["SERVER_PORT"] : "";
 
 		$protocol = substr($server_protocol, 0, strpos($server_protocol, "/"))
 			. (isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on" ? "S" : "");
-		$port = "";
-
-		$domainport = explode(":",$http_host);
-		$domain = $domainport[0];
-
-		$port = (isset($domainport[1])) ? $domainport[1] : $server_port;
-
-		// ports 80 and 443 are generally not included in the url
-		$port = ($port == "" || $port == "80" || $port == "443") ? "" : (":" . $port);
-
+		$port = "";
+
+		$domainport = explode(":",$http_host);
+		$domain = $domainport[0];
+
+		$port = (isset($domainport[1])) ? $domainport[1] : $server_port;
+
+		// ports 80 and 443 are generally not included in the url
+		$port = ($port == "" || $port == "80" || $port == "443") ? "" : (":" . $port);
+
 
 		if (isset($_SERVER['REQUEST_URI']))
 		{
@@ -299,8 +298,8 @@ class RequestUtil
 			$qs .= $qs ? "&$post" : "?$post";
 		}
 
-		$url = strtolower($protocol) . "://" . $domain . $port . $path . ($include_querystring ? $qs : "");
-
+		$url = strtolower($protocol) . "://" . $domain . $port . $path . ($include_querystring ? $qs : "");
+
 		return $url;
 	}
 
@@ -441,12 +440,24 @@ class RequestUtil
 	* @param	string $fieldname
 	* @param	string $default value returned if $_REQUEST[$fieldname] is blank or null (default = empty string)
 	* @param	bool $escape if true htmlspecialchars($val) is returned (default = false)
+	* @param    bool $ignorecase if true then request fieldname will not be case sensitive (default = false)
 	* @return   string | array
 	*/
-	public static function Get($fieldname, $default = "", $escape = false)
+	public static function Get($fieldname, $default = "", $escape = false, $ignorecase = false)
 	{
-		$val = (isset($_REQUEST[$fieldname]) && $_REQUEST[$fieldname] != "") ? $_REQUEST[$fieldname] : $default;
-
+		
+		$val = null;
+		
+		if ($ignorecase)
+		{
+			$_REQUEST_LOWER = array_change_key_case($_REQUEST, CASE_LOWER);
+			$val = (isset($_REQUEST_LOWER[strtolower($fieldname)]) && $_REQUEST_LOWER[strtolower($fieldname)] != "") ? $_REQUEST_LOWER[strtolower($fieldname)] : $default;
+		}
+		else
+		{
+			$val = (isset($_REQUEST[$fieldname]) && $_REQUEST[$fieldname] != "") ? $_REQUEST[$fieldname] : $default;
+		}
+		
 		if ($escape)
 		{
 			$val = htmlspecialchars($val, ENT_COMPAT, null, false);

+ 5 - 4
phreeze/libs/verysimple/Phreeze/ActionRouter.php

@@ -32,7 +32,7 @@ class ActionRouter implements IRouter
 	 */
 	public function __construct($format = "%s.%s.page?%s", $mode = UrlWriterMode::WEB, $appRoot = '', $defaultRoute = '')
 	{
-		$this->_format = $format;
+		self::$_format = $format;
 		$this->_mode = $mode;
 		$this->_appRoot = $appRoot;
 		$this->_defaultRoute = $defaultRoute;
@@ -59,7 +59,8 @@ class ActionRouter implements IRouter
 	*/
 	public function GetUrlParam($key, $default = '')
 	{
-		return RequestUtil::Get($key,$default);
+		// make the route params case insensitive
+		return RequestUtil::Get($key,$default,false,true);
 	}
 
 	/**
@@ -67,7 +68,7 @@ class ActionRouter implements IRouter
 	*/
 	public function GetUrl($controller,$method,$params='',$requestMethod='')
 	{
-		$format = str_replace("{delim}",$this->delim,$this->_format);
+		$format = str_replace("{delim}",$this->delim,self::$_format);
 
 		$qs = "";
 		$d = "";
@@ -125,7 +126,7 @@ class ActionRouter implements IRouter
 
 		$method_param = isset($params[1]) && $params[1] ? $params[1] : "";
 		if ( !$method_param ) $method_param = "DefaultAction";
-		
+
 		return array($controller_param,$method_param);
 	}
 

+ 5 - 1
phreeze/libs/verysimple/Phreeze/CacheMemCache.php

@@ -32,6 +32,7 @@ class CacheMemCache implements ICache
 		$this->_memcache = $memcache;
 		$this->_prefix = $uniquePrefix ? $uniquePrefix . "-" : "";
 		$this->_suppressServerErrors = $suppressServerErrors;
+		$this->LastServerError;
 	}
 	
 	/**
@@ -49,6 +50,7 @@ class CacheMemCache implements ICache
 		catch (Exception $ex)
 		{
 			ExceptionThrower::Stop();
+			$this->LastServerError = $ex->getMessage();
 			if (!$this->_suppressServerErrors) throw $ex;
 		}
 
@@ -58,7 +60,7 @@ class CacheMemCache implements ICache
 	/**
 	 * @inheritdocs
 	 */
-	public function Set($key,$val,$flags=null,$timeout=null)
+	public function Set($key,$val,$flags=null,$timeout=0)
 	{
 		$result = null;
 		try
@@ -70,6 +72,7 @@ class CacheMemCache implements ICache
 		catch (Exception $ex)
 		{
 			ExceptionThrower::Stop();
+			$this->LastServerError = $ex->getMessage();
 			if (!$this->_suppressServerErrors) throw $ex;
 		}
 		
@@ -91,6 +94,7 @@ class CacheMemCache implements ICache
 		catch (Exception $ex)
 		{
 			ExceptionThrower::Stop();
+			$this->LastServerError = $ex->getMessage();
 			if (!$this->_suppressServerErrors) throw $ex;
 		}
 		

+ 1 - 1
phreeze/libs/verysimple/Phreeze/CacheNoCache.php

@@ -22,7 +22,7 @@ class CacheNoCache implements ICache
 		return null;
 	}
 	
-	public function Set($key,$val,$flags=null,$timeout=null)
+	public function Set($key,$val,$flags=null,$timeout=0)
 	{
 	}
 

+ 1 - 1
phreeze/libs/verysimple/Phreeze/CacheRam.php

@@ -27,7 +27,7 @@ class CacheRam implements ICache
 		return array_keys($this->ram);
 	}
 	
-	public function Set($key,$val,$flags=null,$timeout=null)
+	public function Set($key,$val,$flags=null,$timeout=0)
 	{
 		$this->ram[$key] = $val;
 	}

+ 92 - 10
phreeze/libs/verysimple/Phreeze/Controller.php

@@ -50,6 +50,9 @@ abstract class Controller
 
 	/** Prefix this to the name of the view templates */
 	static $SmartyViewPrefix = "View";
+	
+	/** search string to look for to determine if this is an API request or not */
+	static $ApiIdentifier = "api/";
 
 	/** the default mode used when calling 'Redirect' */
 	static $DefaultRedirectMode = "client";
@@ -63,7 +66,7 @@ abstract class Controller
 	 * @param Context (optional) a context object for persisting the state of the current page
 	 * @param Router (optional) a custom writer for URL formatting
 	 */
-	final function __construct(Phreezer &$phreezer, &$renderEngine, &$context = null, IRouter &$router = null)
+	final function __construct(Phreezer $phreezer, $renderEngine, $context = null, IRouter $router = null)
 	{
 		$this->Phreezer =& $phreezer;
 		$this->RenderEngine =& $renderEngine;
@@ -94,7 +97,18 @@ abstract class Controller
 			$this->Assign("BROWSER_DEVICE",$this->GetDevice());
 	
 			// if feedback was persisted, set it
-			$this->Assign("feedback",$this->Context->Get("feedback"));
+			$feedback = $this->Context->Get("feedback");
+			
+			// print_r($feedback); die('feedback');
+			
+			if (is_array($feedback)) {
+				foreach ($feedback as $key => $val) {
+					$this->Assign($key,$val);
+				}
+			}
+			else {
+				$this->Assign("feedback",$feedback);
+			}
 			$this->Context->Set("feedback",null);
 		}
 
@@ -166,6 +180,63 @@ abstract class Controller
 		}
 	}
 	
+	/**
+	 * Assign the current CSRFToken to the view layer
+	 * @param string $varname the view varname to use for assignment
+	 */
+	protected function AssignCSRFToken($varname = 'CSRFToken')
+	{
+		$this->Assign($varname, $this->GetCSRFToken());
+	}
+	
+	/**
+	 * Returns a stored CSRF Token from the session.  If no token exists, then generate
+	 * one and save it to the session.
+	 *
+	 * @return string
+	 */
+	protected function GetCSRFToken()
+	{
+		$token = $this->Context->Get('X-CSRFToken');
+	
+		if (!$token)
+		{
+			$token = md5(rand(1111111111,9999999999).microtime());
+			$this->Context->Set('X-CSRFToken',$token);
+		}
+	
+		return $token;
+	}
+	
+	/**
+	 * Verify that X-CSRFToken was sent in the request headers and matches the session token
+	 * If not an Exception with be thrown.  If no exception is thrown then the token
+	 * is verified.
+	 * @param string the name of the header variable that contains the token
+	 * @throws Exception if token is not provided or does not match
+	 */
+	protected function VerifyCSRFToken($headerName = 'X-CSRFToken')
+	{
+		// check that a CSRF token is present in the request
+		$headers = RequestUtil::GetHeaders();
+		
+		// make this case-insensitive (IE changes all headers to lower-case)
+		$headers = array_change_key_case($headers, CASE_LOWER);
+		$headerName = strtolower($headerName);
+	
+		if (array_key_exists($headerName, $headers))
+		{
+			if ($this->GetCSRFToken() != $headers[$headerName])
+			{
+				throw new Exception('Invalid CSRFToken');
+			}
+		}
+		else
+		{
+			throw new Exception('Missing CSRFToken');
+		}
+	}
+	
 	/**
 	 * Start observing messages from Phreeze.  If no observer is provided, then
 	 * an ObserveToBrowser will be used and debug messages will be output to the browser
@@ -681,11 +752,24 @@ abstract class Controller
 
 		return $this->_cu;
 	}
+	
+	/**
+	 * Returns true if this request is an API request.  This examines the URL to 
+	 * see if the string Controller::$ApiIdentifier is in the URL
+	 * @return bool
+	 */
+	public function IsApiRequest()
+	{
+		$url = RequestUtil::GetCurrentURL();
+		return (strpos($url, self::$ApiIdentifier ) !== false);
+	}
 
 	/**
 	 * Check the current user to see if they have the requested permission.
 	 * If so then the function does nothing.  If not, then the user is redirected
-	 * to $on_fail_action (if provided) or an AuthenticationException is thrown
+	 * to $on_fail_action (if provided) or an AuthenticationException is thrown.
+	 * if Controller->IsApiRequest() returns true then an AuthenticationException will
+	 * be thrown regardless of the fail_action.
 	 *
 	 * @param int $permission Permission ID requested
 	 * @param string $on_fail_action (optional) The action to redirect if require fails
@@ -704,10 +788,9 @@ abstract class Controller
 				? $not_authenticated_feedback
 				: $permission_denied_feedback;
 			
-			if ($on_fail_action)
+			if ($on_fail_action && $this->IsApiRequest() == false)
 			{
-
-				$this->Redirect($on_fail_action,$message);
+				$this->Redirect($on_fail_action,array('feedback'=>$message,'warning'=>$message));
 			}
 			else
 			{
@@ -867,19 +950,18 @@ abstract class Controller
 	 * call "exit" so do not put any code that you wish to execute after Redirect
 	 *
 	 * @param string $action in the format Controller.Method
-	 * @param string $feedback
+	 * @param mixed $feedback string which will be assigne to the template as "feedback" or an array of values to assign
 	 * @param array $params
 	 * @param string $mode (client | header) default = Controller::$DefaultRedirectMode
 	 */
-	protected function Redirect($action, $feedback = "", $params = "", $mode = "")
+	protected function Redirect($action, $feedback =  null, $params = "", $mode = "")
 	{
 		if (!$mode) $mode = self::$DefaultRedirectMode;
 
 		$params = is_array($params) ? $params : array();
 
-		if ($feedback)
+		if ($feedback != null)
 		{
-			// $params["feedback"] = $feedback;
 			$this->Context->Set("feedback",$feedback);
 		}
 

+ 22 - 3
phreeze/libs/verysimple/Phreeze/Criteria.php

@@ -14,7 +14,7 @@ require_once("verysimple/IO/Includer.php");
  * @author     VerySimple Inc.
  * @copyright  1997-2007 VerySimple, Inc.
  * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
- * @version    2.2
+ * @version    2.3
  */
 class Criteria
 {
@@ -305,8 +305,11 @@ class Criteria
 						$this->_where .= $this->_where_delim . " (" . $dbfield ." ". $val . ")";
 						$this->_where_delim = " and";
 					}
-					elseif (substr($prop,-3) == "_In" && isset($val) && is_array($val))
+					elseif (substr($prop,-3) == "_In" && isset($val))
 					{
+						// if a string was passed in then treat it as comma-delimited
+						if  (!is_array($val)) $val = explode(',', $val);
+						
 						// if the count is zero, technically the user is saying that they don't
 						// want any results.  the only way to do that is to make the criteria
 						// something that will for sure not match any existing records.  we cannot
@@ -328,8 +331,11 @@ class Criteria
 						$this->_where .= ")";
 						$this->_where_delim = " and";
 					}
-					elseif (substr($prop,-6) == "_NotIn" && isset($val) && is_array($val))
+					elseif (substr($prop,-6) == "_NotIn" && isset($val))
 					{
+						// if a string was passed in then treat it as comma-delimited
+						if  (!is_array($val)) $val = explode(',', $val);
+						
 						// if the count is zero, technically the user is saying that they don't
 						// want any results.  the only way to do that is to make the criteria
 						// something that will for sure not match any existing records.  we cannot
@@ -366,6 +372,19 @@ class Criteria
 				$this->_order = $this->_set_order;	
 			}
 
+			// if any of the filters have an order by then add those
+			if (is_array($this->Filters)) {
+				$orderDelim = $this->_order ? ',' : '';
+				foreach ($this->Filters as $filter)
+				{
+					$filterOrder = $filter->GetOrder($this);
+					if ($filterOrder) {
+						$this->_order .= $orderDelim . $filterOrder;
+						$orderDelim = ', ';
+					}
+				}
+			}
+			
 			if ($this->_order)
 			{
 				$this->_order = " order by " . $this->_order;

+ 9 - 0
phreeze/libs/verysimple/Phreeze/CriteriaFilter.php

@@ -57,6 +57,15 @@ class CriteriaFilter
 		
 		return $where;
 	}
+	
+	/**
+	 * Return the "order by" portion of the SQL statement (without the order by prefix)
+	 * @param Criteria $criteria the Criteria object to which this filter has been added
+	 */
+	public function GetOrder($criteria)
+	{
+		return "";
+	}
 }
 
 ?>

+ 36 - 3
phreeze/libs/verysimple/Phreeze/DataAdapter.php

@@ -174,7 +174,7 @@ class DataAdapter implements IObservable
 				$msg = 'Error Opening DB: ' . $ex->getMessage() . ' (retry attempts: '.$this->_num_retries.')';
 					
 				$this->Observe($msg,OBSERVE_FATAL);
-				throw new Exception($msg,$ex->getCode(),$ex->getPrevious());
+				throw new Exception($msg,$ex->getCode());
 			}
 			
 			$this->_dbopen = true;
@@ -261,7 +261,7 @@ class DataAdapter implements IObservable
 			$msg = 'Error Selecting SQL: ' . $ex->getMessage() . ' (retry attempts: '.$this->_num_retries.')';
 			
 			$this->Observe($msg,OBSERVE_FATAL);
-			throw new Exception($msg,$ex->getCode(),$ex->getPrevious());
+			throw new Exception($msg,$ex->getCode());
 		}
 		
 		return $rs;
@@ -299,12 +299,45 @@ class DataAdapter implements IObservable
 			$msg = 'Error Executing SQL: ' . $ex->getMessage() . ' (retry attempts: '.$this->_num_retries.')';
 			
 			$this->Observe($msg,OBSERVE_FATAL);
-			throw new Exception($msg,$ex->getCode(),$ex->getPrevious());
+			throw new Exception($msg,$ex->getCode());
 		}
 		
 		return $result;
 	}
 	
+	/**
+	 * Start a DB transaction, disabling auto-commit if necessar)
+	 * @access public
+	 */
+	function StartTransaction()
+	{
+		$this->RequireConnection(true);
+		$this->Observe("(DataAdapter.StartTransaction)", OBSERVE_QUERY);
+		return $this->_driver->StartTransaction($this->_dbconn);
+	}
+	
+	/**
+	 * Commit the current DB transaction and re-enable auto-commit if necessary
+	 * @access public
+	 */
+	function CommitTransaction()
+	{
+		$this->RequireConnection(true);
+		$this->Observe("(DataAdapter.CommitTransaction)", OBSERVE_QUERY);
+		return $this->_driver->CommitTransaction($this->_dbconn);
+	}
+	
+	/**
+	 * Rollback the current DB transaction and re-enable auto-commit if necessary
+	 * @access public
+	 */
+	function RollbackTransaction()
+	{
+		$this->RequireConnection(true);
+		$this->Observe("(DataAdapter.RollbackTransaction)", OBSERVE_QUERY);
+		return $this->_driver->RollbackTransaction($this->_dbconn);
+	}
+	
 	/**
 	 * Return true if the error with the given message is a communication/network error
 	 * @param variant string or Exception $msg

+ 48 - 30
phreeze/libs/verysimple/Phreeze/DataSet.php

@@ -18,7 +18,7 @@ require_once("DataPage.php");
  * @author     VerySimple Inc. <[email protected]>
  * @copyright  1997-2007 VerySimple Inc.
  * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
- * @version    1.1
+ * @version    1.2
  */
 class DataSet implements Iterator // @TODO implement Countable, ArrayAccess
 {
@@ -93,7 +93,7 @@ class DataSet implements Iterator // @TODO implement Countable, ArrayAccess
     	{
     		require_once("verysimple/Util/ExceptionFormatter.php");
     		$info = ExceptionFormatter::FormatTrace(debug_backtrace());
-    		$this->_phreezer->Observe("(DataSet.Next: unable to cache query with cursor) " . $info . "  " . $this->_sql,OBSERVE_QUERY);
+    		$this->_phreezer->Observe("(DataSet.Next: unable to cache query with cursor) " . $info . "  " . $this->_sql,OBSERVE_DEBUG);
 
     		// use this line to discover where an uncachable query is coming from
     		// throw new Exception("WTF");
@@ -206,18 +206,19 @@ class DataSet implements Iterator // @TODO implement Countable, ArrayAccess
 			// if no cache, go to the db
 			if ($this->_totalcount != null)
 			{
-				$this->_phreezer->Observe("(DataSet.Count: skipping query because cache exists) " . $this->_sql,OBSERVE_QUERY);
+				$this->_phreezer->Observe("DataSet.Count: skipping count query because cache exists",OBSERVE_DEBUG);
 			}
 			else
 			{
 				$this->LockCache($cachekey);
 
-				$this->_phreezer->Observe("(DataSet.Count: query does not exist in cache) " . $this->_sql,OBSERVE_QUERY);
+				
 				$sql = "";
 
 				// if a custom counter sql query was provided, use that because it should be more efficient
 				if ($this->CountSQL)
 				{
+					$this->_phreezer->Observe("DataSet.Count: using CountSQL to obtain total number of records",OBSERVE_DEBUG);
 					$sql = $this->CountSQL;
 				}
 				else
@@ -260,7 +261,7 @@ class DataSet implements Iterator // @TODO implement Countable, ArrayAccess
 		if ($arr != null)
 		{
 			// we have a cache value, so we will repopulate from that
-			$this->_phreezer->Observe("(DataSet.ToObjectArray: skipping query because cache exists) " . $this->_sql,OBSERVE_QUERY);
+			$this->_phreezer->Observe("(DataSet.ToObjectArray: skipping query because cache exists) " . $this->_sql,OBSERVE_DEBUG);
 			if (!$asSimpleObject)
 			{
 				foreach ($arr as $obj)
@@ -374,18 +375,25 @@ class DataSet implements Iterator // @TODO implement Countable, ArrayAccess
     }
 
     /**
-     * Returns a DataPage object suitable for binding to the smarty PageView plugin
+     * Returns a DataPage object suitable for binding to the smarty PageView plugin.
+     * If $countrecords is true then the total number of records will be eagerly fetched
+     * using a count query.  This is necessary in order to calculate the total number of
+     * results and total number of pages.  If you do not care about pagination and simply
+     * want to limit the results, then this can be set to false to supress the count 
+     * query.  However, the pagination settings will not be correct and the total number
+     * of rows will be -1
      *
      * @access     public
      * @param int $pagenum which page of the results to view
      * @param int $pagesize the size of the page (or zero to disable paging).
+     * @param bool $countrecords will eagerly fetch the total number of records with a count query
      * @return DataPage
      */
-    function GetDataPage($pagenum, $pagesize)
+    function GetDataPage($pagenum, $pagesize, $countrecords = true)
     {
 		// check the cache
 		// $cachekey = md5($this->_sql . " PAGE=".$pagenum." SIZE=" . $pagesize);
-		$cachekey = $this->_sql . " PAGE=".$pagenum." SIZE=" . $pagesize;
+		$cachekey = $this->_sql . " PAGE=".$pagenum." SIZE=" . $pagesize." COUNT=" . ($countrecords ? '1' : '0');
 
 		$page = $this->GetDelayedCache($cachekey);
 
@@ -410,28 +418,36 @@ class DataSet implements Iterator // @TODO implement Countable, ArrayAccess
 			$page->ObjectInstance = new $this->_objectclass($this->_phreezer);
 			$page->PageSize = $pagesize;
 			$page->CurrentPage = $pagenum;
-			$page->TotalResults = $this->Count();
-
-
-			// first check if we have less than or exactly the same number of
-			// results as the pagesize.  if so, don't bother doing the math.
-			// we know we just have one page
-			if ($page->TotalPages > 0 && $page->TotalPages <= $page->PageSize)
+			
+			if ($countrecords) 
 			{
-				$page->TotalPages = 1;
+				$page->TotalResults = $this->Count();
+	
+				// first check if we have less than or exactly the same number of
+				// results as the pagesize.  if so, don't bother doing the math.
+				// we know we just have one page
+				if ($page->TotalPages > 0 && $page->TotalPages <= $page->PageSize)
+				{
+					$page->TotalPages = 1;
+				}
+				else if ($pagesize == 0)
+				{
+					// we don't want paging to occur in this case
+					$page->TotalPages = 1;
+				}
+				else
+				{
+					// we have more than one page.  we always need to round up
+					// here because 5.1 pages means we are spilling out into
+					// a 6th page.  (this will also handle zero results properly)
+					$page->TotalPages = ceil( $page->TotalResults / $pagesize );
+				}
 			}
-			else if ($pagesize == 0)
+			else 
 			{
-				// we don't want paging to occur in this case
+				$page->TotalResults = $pagesize; // this will get adjusted after we run the query
 				$page->TotalPages = 1;
 			}
-			else
-			{
-				// we have more than one page.  we always need to round up
-				// here because 5.1 pages means we are spilling out into
-				// a 6th page.  (this will also handle zero results properly)
-				$page->TotalPages = ceil( $page->TotalResults / $pagesize );
-			}
 
 			// now enumerate through the rows in the page that we want.
 			// decrement the requested pagenum here so that we will be
@@ -461,6 +477,12 @@ class DataSet implements Iterator // @TODO implement Countable, ArrayAccess
 			{
 				$page->Rows[$i++] = $obj;
 			}
+			
+			if (!$countrecords) 
+			{
+				// we don't know the total count so just set it to the total number of rows in this page
+				$page->TotalResults = $i;
+			}
 
 			$this->_phreezer->SetValueCache($cachekey, $page, $this->_cache_timeout);
 
@@ -485,13 +507,9 @@ class DataSet implements Iterator // @TODO implement Countable, ArrayAccess
 
         $obj = $this->_phreezer->GetValueCache($cachekey);
 
-        $lockfile = $this->_phreezer->LockFilePath
-			? $this->_phreezer->LockFilePath . md5($cachekey) . ".lock"
-			: "";
-
     	// no cache, so try three times with a delay to prevent a cache stampede
     	$counter = 1;
-		while ( $counter < 4 && $obj == null && $lockfile && file_exists($lockfile) )
+		while ( $counter < 4 && $obj == null && $this->IsLocked($cachekey) )
 		{
 			$this->_phreezer->Observe("(DataSet.GetDelayedCache: flood prevention. delayed attempt ".$counter." of 3...) " . $cachekey,OBSERVE_DEBUG);
 			usleep(50000); // 5/100th of a second

+ 69 - 29
phreeze/libs/verysimple/Phreeze/Dispatcher.php

@@ -12,7 +12,7 @@ require_once("verysimple/Util/ExceptionThrower.php");
  * @author     VerySimple Inc.
  * @copyright  1997-2007 VerySimple, Inc.
  * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
- * @version    2.4
+ * @version    2.6
  */
 class Dispatcher
 {
@@ -22,6 +22,31 @@ class Dispatcher
 	 */
 	static $IGNORE_DEPRECATED = true;
 
+	/**
+	 * This is a case-insensitive version of file_exists
+	 * @param string $fileName
+	 */
+	static function ControllerFileExists($fileName) 
+	{
+	
+		if (file_exists($fileName)) return $fileName;
+
+		$directoryName = dirname($fileName);
+		$fileArray = glob($directoryName . '/*', GLOB_NOSORT);
+		$fileNameLowerCase = strtolower($fileName);
+		
+		// TODO: if not an array then this path isn't readable, should we ignore or crash...?
+		// if (!is_array($fileArray)) throw new Exception('Unreadable include path "'.$directoryName.'" for controller "' . $fileName . '"');
+		if (!is_array($fileArray)) return false;
+		
+		foreach($fileArray as $file) 
+		{
+			if (strtolower($file) == $fileNameLowerCase) return $file;
+		}
+		
+		return false;
+	}
+	
 	/**
 	 * Processes user input and executes the specified controller method, ensuring
 	 * that the controller dependencies are all injected properly
@@ -44,44 +69,59 @@ class Dispatcher
 
 		// normalize the input
 		$controller_class = $controller_param."Controller";
-		$controller_file = "Controller/" . $controller_param . "Controller.php";
-
-		// look for the file in the expected places, hault if not found
-		if ( !(file_exists($controller_file) || file_exists("libs/".$controller_file)) )
+		
+		// if the controller was in a sub-directory, get rid of the directory path
+		$slashPos = strpos($controller_class,'/');
+		while ($slashPos !== false)
+		{
+			$controller_class = substr($controller_class,$slashPos+1);
+			$slashPos = strpos($controller_class,'/');
+		}
+		
+		if (!class_exists($controller_class))
 		{
-			// go to plan be, search the include path for the controller
-			$paths = explode(PATH_SEPARATOR,get_include_path());
+			// attempt to locate the controller file
+			$controller_file = "Controller/" . $controller_param . "Controller.php";
+			$controller_filepath = null;
+			
+			// search for the controller file in the default locations, then the include path
+			$paths = array_merge(
+				array('./libs/','./'),
+				explode(PATH_SEPARATOR,get_include_path())
+			);
+			
 			$found = false;
 			foreach ($paths as $path)
 			{
-				if (file_exists($path ."/".$controller_file))
+				$controller_filepath = self::ControllerFileExists($path ."/".$controller_file);
+				if ($controller_filepath)
 				{
 					$found = true;
 					break;
 				}
 			}
-
+	
 			if (!$found) throw new Exception("File ~/libs/".$controller_file." was not found in include path");
-		}
-
-		// convert any php errors into an exception
-		if (self::$IGNORE_DEPRECATED)
-		{
-			ExceptionThrower::Start();
-		}
-		else
-		{
-			ExceptionThrower::Start(E_ALL);
-			ExceptionThrower::$IGNORE_DEPRECATED = false;
-		}
-
-		// we should be fairly certain the file exists at this point
-		include_once($controller_file);
-
-		// we found the file but the expected class doesn't appear to be defined
-		if (!class_exists($controller_class))
-		{
-			throw new Exception("Controller file was found, but class '".$controller_class."' is not defined");
+	
+			// convert any php errors into an exception
+			if (self::$IGNORE_DEPRECATED)
+			{
+				ExceptionThrower::Start();
+			}
+			else
+			{
+				ExceptionThrower::Start(E_ALL);
+				ExceptionThrower::$IGNORE_DEPRECATED = false;
+			}
+	
+			// we should be fairly certain the file exists at this point
+			include_once($controller_filepath);
+	
+			// we found the file but the expected class doesn't appear to be defined
+			if (!class_exists($controller_class))
+			{
+				throw new Exception("Controller file was found, but class '".$controller_class."' is not defined");
+			}
 		}
 
 

+ 20 - 10
phreeze/libs/verysimple/Phreeze/GenericRouter.php

@@ -17,7 +17,7 @@ require_once('verysimple/HTTP/RequestUtil.php');
  */
 class GenericRouter implements IRouter
 {
-	private static $routes = array();
+	private $routes = array();
 
 	private $defaultAction = 'Default.Home';
 	private $uri = '';
@@ -65,10 +65,10 @@ class GenericRouter implements IRouter
 	 *
 	 * @param array $src
 	 */
-	private static function mapRoutes( $src )
+	private function mapRoutes( $src )
 	{
 		foreach ( $src as $key => $val )
-			self::$routes[ $key ] = $val;
+			$this->routes[ $key ] = $val;
 	}
 
 	/**
@@ -76,26 +76,29 @@ class GenericRouter implements IRouter
 	 */
 	public function GetRoute( $uri = "" )
 	{
+		// reset the uri cache
+		$this->uri = '';
+		
 		if( $uri == "" )
 			$uri = RequestUtil::GetMethod() . ":" . $this->GetUri();
 
 		// literal match check
-		if ( isset(self::$routes[ $uri ]) )
+		if ( isset($this->routes[ $uri ]) )
 		{
 			// expects mapped values to be in the form: Controller.Model
-			list($controller,$method) = explode(".",self::$routes[ $uri ]["route"]);
+			list($controller,$method) = explode(".",$this->routes[ $uri ]["route"]);
 
 			$this->cachedRoute = array(
-				"key" => self::$routes[ $uri ]
-				,"route" => self::$routes[ $uri ]["route"]
-				,"params" => isset(self::$routes[ $uri ]["params"]) ? self::$routes[ $uri ]["params"] : array()
+				"key" => $this->routes[ $uri ]
+				,"route" => $this->routes[ $uri ]["route"]
+				,"params" => isset($this->routes[ $uri ]["params"]) ? $this->routes[ $uri ]["params"] : array()
 			);
 
 			return array($controller,$method);
 		}
 
 		// loop through the route map for wild cards:
-		foreach( self::$routes as $key => $value)
+		foreach( $this->routes as $key => $value)
 		{
 			$unalteredKey = $key;
 
@@ -119,6 +122,13 @@ class GenericRouter implements IRouter
 			}
 		}
 
+		// this is a page-not-found route
+		$this->cachedRoute = array(
+				"key" => ''
+				,"route" => ''
+				,"params" => array()
+		);
+		
 		// if we haven't returned by now, we've found no match:
 		return explode('.', self::$ROUTE_NOT_FOUND,2);
 	}
@@ -181,7 +191,7 @@ class GenericRouter implements IRouter
 			$url = substr($url,0,-1);
 		}
 
-		foreach( self::$routes as $key => $value)
+		foreach( $this->routes as $key => $value)
 		{
 			list($routeController,$routeMethod) = explode(".",$value["route"]);
 

+ 3 - 3
phreeze/libs/verysimple/Phreeze/ICache.php

@@ -18,7 +18,7 @@ interface ICache
 	* @access     public
 	* @param string $key
 	*/
-	public function Get($key);
+	public function Get($key,$flags=null);
 	
 	/**
 	* Stores a value in the cache
@@ -27,10 +27,10 @@ interface ICache
 	* @param string $key
 	* @param variant $val
 	* @param int $flags
-	* @param int $timout in miliseconds
+	* @param int $timout in seconds
 	* @return variant
 	*/
-	public function Set($key,$val,$flags=null,$timeout=null);
+	public function Set($key,$val,$flags=null,$timeout=0);
 
 	/**
 	* Removes a value from the cache

+ 1 - 1
phreeze/libs/verysimple/Phreeze/PHPRenderEngine.php

@@ -32,7 +32,7 @@ class PHPRenderEngine implements IRenderEngine
 	{
 		$this->templatePath = $templatePath;
 
-		if (substr($path,-1) != '/' && substr($path,-1) != '\\') $this->templatePath .= "/";
+		if (substr($this->templatePath,-1) != '/' && substr($this->templatePath,-1) != '\\') $this->templatePath .= "/";
 	}
 
 	/**

+ 1 - 1
phreeze/libs/verysimple/Phreeze/Phreezable.php

@@ -225,7 +225,7 @@ abstract class Phreezable implements Serializable
     * @param      Phreezer $phreezer
     * @param      Array $row
     */
-	final function __construct(Phreezer &$phreezer, $row = null)
+	final function __construct(Phreezer $phreezer, $row = null)
     {
 		$this->_phreezer = $phreezer;
 		$this->_cache = Array();

+ 64 - 8
phreeze/libs/verysimple/Phreeze/Phreezer.php

@@ -18,10 +18,21 @@ require_once("verysimple/IO/Includer.php");
  * @author     VerySimple Inc.
  * @copyright  1997-2008 VerySimple, Inc.
  * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
- * @version    3.3.4
+ * @version    3.3.7
  */
 class Phreezer extends Observable
 {
+	/**
+	 * An associative array of DataAdapter objects, which can be 
+	 * specified using SelectAdapter
+	 * @var Array
+	 */
+	public $DataAdapters;
+	
+	/**
+	 * The currently selected DataAdapter
+	 * @var DataAdapter
+	 */
 	public $DataAdapter;
 
 	/**
@@ -30,7 +41,7 @@ class Phreezer extends Observable
 	 */
 	public $RenderEngine;
 
-	public static $Version = '3.3.4 HEAD';
+	public static $Version = '3.3.7 HEAD';
 
 	/**
 	 * @var int expiration time for query & value cache (in seconds) default = 5
@@ -82,16 +93,26 @@ class Phreezer extends Observable
 	 */
 	static function PharPath()
 	{
-		return Phar::running();
+		return class_exists("Phar") && Phar::running();
 	}
 
     /**
-    * Contructor initializes the object.  The database connection is opened upon instantiation
-    * and an exception will be thrown if db connectivity fails, so it is advisable to
-    * surround the instantiation with a try/catch
+    * Contructor initializes the object.  The database connection is opened only when
+    * a DB call is made.
+    * 
+    * The ConnectionSetting parameter can be either a single connection setting, or
+    * an associative array.  This allows switching among different database connections 
+    * which can be referred to by their array key using Phreezer->SelectAdapter.
+    * Multiple connections can be used for example to read from a slave database
+    * and write to a master.
+    * 
+    * One instantiate the DataAdapter will be set to whichever is the first
+    * connection in the list.
+    * 
+    * If a single ConnectionSetting is supplied, it will be assigned the key "default"
     *
     * @access public
-    * @param ConnectionSetting $csetting
+    * @param ConnectionSetting || Associative Array of ConnectionSetting objects
     * @param Observable $observer
     */
     public function __construct($csetting, $observer = null)
@@ -102,8 +123,42 @@ class Phreezer extends Observable
 
 		if ($observer) parent::AttachObserver($observer);
 		$this->Observe("Phreeze Instantiated", OBSERVE_DEBUG);
+		
+		$csettings = is_array($csetting) ? $csetting : array('default'=>$csetting);
 
-		$this->DataAdapter = new DataAdapter($csetting, $observer);
+		$this->DataAdapters = array();
+		foreach ($csettings as $key=>$connection) {
+			$this->DataAdapters[$key] = new DataAdapter($connection, $observer);
+		}
+		
+		$this->SelectAdapter();
+	}
+	
+	/**
+	 * SelectAdapter will change the DataAdapter, allowing the application
+	 * to query from multiple data sources.  The connection strings for
+	 * each database should be passed in an array during construction of
+	 * the Phreezer object.
+	 * 
+	 * Once this method is called, all DB calls will be made to this connection
+	 * until another adapter is selected.
+	 * 
+	 * @param string $key
+	 * @return DataAdapter the selected DataAdapter
+	 */
+	public function SelectAdapter($key = null)
+	{
+		if ($key) {
+			if (!array_key_exists($key, $this->DataAdapters))
+				throw new Exception("No DataAdapter with key '$key' is available");
+			$this->DataAdapter = $this->DataAdapters[$key];
+		}
+		else {
+			$adapters = array_values($this->DataAdapters);
+			$this->DataAdapter = $adapters[0];
+		}
+		
+		return $this->DataAdapter;
 	}
 
 	/**
@@ -381,6 +436,7 @@ class Phreezer extends Observable
 		require_once("DataSet.php");
 		$ds = new DataSet($this, $objectclass, $sql, $cache_timeout);
 		$ds->CountSQL = $count_sql;
+		$ds->UnableToCache = $cache_timeout === 0;
 
 		return $ds;
 

+ 1 - 1
phreeze/libs/verysimple/Phreeze/Reporter.php

@@ -11,7 +11,7 @@
  * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
  * @version    1.0
  */
-abstract class Reporter
+abstract class Reporter implements Serializable
 {
     protected $_phreezer;
 

+ 121 - 0
phreeze/libs/verysimple/Phreeze/SimpleRouter.php

@@ -0,0 +1,121 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+require_once("verysimple/HTTP/RequestUtil.php");
+require_once("verysimple/Phreeze/IRouter.php");
+
+/**
+ * SimpleRouter is a URL router that parses URLs in the following format:
+ * http://server/index.php?ROUTE
+ * This router can be used in a situation where URL re-writing is not 
+ * available or wanted on the host server
+ * 
+ * The route must be the first param in the querysting and does not
+ * have a key.  Additional querystring parameters can be appended
+ * and will have no effect on the route.
+ * 
+ * Note that this router does not currently support different routes
+ * based on params passed through nor is wildcard matching supported
+ *
+ * @package    verysimple::HTTP
+ * @author     VerySimple Inc.
+ * @copyright  1997-2007 VerySimple, Inc. http://www.verysimple.com
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    1.0
+ */
+class SimpleRouter implements IRouter
+{
+	public static $ROUTE_NOT_FOUND = "Default.Error404";
+	
+	private $routes = array();
+	private $defaultAction = 'Default.Home';
+	private $appRootUrl = '';
+	
+	/**
+	 * 
+	 * @param string $appRootUrl
+	 * @param string $defaultAction
+	 * @param array $mapping routeMap
+	 */
+	public function __construct($appRootUrl = '', $defaultAction = '', $mapping = array())
+	{
+		if ($defaultAction) $this->defaultAction = $defaultAction;
+		$this->routes = $mapping;
+	}
+	
+	/**
+	 * Given a controller, method and params, returns a url that points
+	 * to the correct location
+	 *
+	 * @param string $controller
+	 * @param string $method
+	 * @param array $params in the format param1=val1&param2=val2
+	 * @return string URL
+	 */
+	public function GetUrl($controller, $method, $params = '', $requestMethod = '')
+	{
+		throw new Exception('Not yet implemented');
+	}
+	
+	/**
+	 * Returns the controller and method for the given URI
+	 *
+	 * @param string the url, if not provided will be obtained using the current URL
+	 * @return array($controller,$method)
+	 */
+	public function GetRoute($uri = "")
+	{
+		$match = '';
+		
+		$qs = $uri ? $uri : (array_key_exists('QUERY_STRING', $_SERVER) ? $_SERVER['QUERY_STRING'] : '');
+		$parsed = explode('&', $qs,2);
+		$action = $parsed[0];
+		
+		if (strpos($action, '=') > -1 || !$action) {
+			// the querystring is empty or the first param is a named param, which we ignore
+			$match = $this->defaultAction;
+		}
+		else {
+			// otherwise we have a route.  see if we have a match in the routemap, otherwise return the 'not found' route
+			$method = RequestUtil::GetMethod();
+			$route = $method.':'.$action;
+			$match = array_key_exists($route, $this->routes) ? $this->routes[$route]['route'] : self::$ROUTE_NOT_FOUND;
+		}
+		
+		return explode('.',$match,2);
+	}
+	
+	/**
+	 * In the case of a rewrite url, the url itself contains the parameter
+	 * for example http://server/param1/param2/param3.  These params
+	 * are parsed and the param with the given index is returned
+	 * @return string (or $default if not provided)
+	 * @param string default value to return if parameter is empty
+	 */
+	public function GetUrlParam($key, $default = '')
+	{
+		return array_key_exists($key, $_REQUEST) ? $_REQUEST[$key] : $default;
+	}
+	
+	/**
+	 * In the case of a rewrite url, the url itself contains the parameter
+	 * for example http://server/param1/param2/param3.  These params
+	 * are parsed and returned as an array
+	 * @return array
+	 */
+	public function GetUrlParams()
+	{
+		return $_REQUEST;
+	}
+	
+	/**
+	 * Returns the RESTful part of the url
+	 * For example, localhost/users/5 will return users/5
+	 * @return string
+	 */
+	public function GetUri()
+	{
+		
+	}
+	
+}

+ 4 - 3
phreeze/libs/verysimple/String/SimpleTemplate.php

@@ -1,6 +1,9 @@
 <?php
 /** @package	verysimple::String */
 
+
+require_once("util/html2text.php");
+
 /**
  * A set of utility functions for working with simple template files
  *
@@ -24,9 +27,7 @@ class SimpleTemplate
 	 */
 	static function HtmlToText($html)
 	{
-		require_once("class.html2text.php");
-		$h2t = new html2text($html);
-		return $h2t->get_text(); 
+		return convert_html_to_text($html);
 	}
 	
 	/**

+ 13 - 13
phreeze/libs/verysimple/String/VerySimpleStringUtil.php

@@ -30,19 +30,19 @@ class VerySimpleStringUtil
 	/** @var characters used as control characters such as escape, backspace, etc */
 	static $CONTROL_CODE_CHARS;
 
-	/**
-	 * replace the first occurrance only within a string
-	 * @param string needle
-	 * @param string replacement
-	 * @param string haystack
-	 */
-	static function ReplaceFirst($s,$r,$str)
-	{
-		$l = strlen($str);
-		$a = strpos($str,$s);
-		$b = $a + strlen($s);
-		$temp = substr($str,0,$a) . $r . substr($str,$b,($l-$b));
-		return $temp;
+	/**
+	 * replace the first occurrance only within a string
+	 * @param string needle
+	 * @param string replacement
+	 * @param string haystack
+	 */
+	static function ReplaceFirst($s,$r,$str)
+	{
+		$l = strlen($str);
+		$a = strpos($str,$s);
+		$b = $a + strlen($s);
+		$temp = substr($str,0,$a) . $r . substr($str,$b,($l-$b));
+		return $temp;
 	}
 
 	/**

+ 28 - 45
phreeze/libs/verysimple/Util/MemCacheProxy.php

@@ -1,37 +1,41 @@
 <?php
 /** @package    verysimple::Util */
 
+require_once("verysimple/Phreeze/CacheMemCache.php");
+
 /**
 * MemCacheProxy provides simple access to memcache pool but ignores
 * if the server is down instead of throwing an error.  if the server
 * could not be contacted, ServerOffline will be set to true.
-* @package    verysimple::Authentication
+* 
+* @package    verysimple::Util
 * @author     VerySimple Inc.
 * @copyright  1997-2007 VerySimple, Inc.
 * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
 * @version    1.0
 */
-class MemCacheProxy
+class MemCacheProxy extends CacheMemCache
 {
-	private $_memcache;
 	public $ServerOffline = false;
 	public $LastServerError = '';
 
 	/**
-	 * Acts as a proxy for a MemCache server and fails gracefull if the pool
-	 * cannot be contacted
+	 * Acts as a proxy for a MemCache server and fails gracefull if the pool cannot be contacted
 	 * @param array in host/port format: array('host1'=>'11211','host2'=>'11211')
+	 * @param string a unique string.  prevents conflicts in case multiple apps are using the same memcached server bank
 	 */
-	public function __construct($server_array = array('localhost'=>'11211'))
+	public function __construct($server_array = array('localhost'=>'11211'),$uniquePrefix = "CACHE-")
 	{
 		if (class_exists('Memcache'))
 		{
-			$this->_memcache = new Memcache();
+			$memcache = new Memcache();
 			foreach (array_keys($server_array) as $host)
 			{
 				// print "adding server $host " . $server_array[$host];
-				$this->_memcache->addServer($host, $server_array[$host]);
+				$memcache->addServer($host, $server_array[$host]);
 			}
+			
+			parent::__construct($memcache,$uniquePrefix,true);
 		}
 		else
 		{
@@ -42,59 +46,38 @@ class MemCacheProxy
 
 
 	/**
-	 * This is method get
-	 * @param string $key The key of the item to retrieve
-	 * @return mixed cache value or null
+	 * @inheritdocs
 	 */
-	public function get($key)
+	public function Get($key,$flags=null)
 	{
 		// prevent hammering the server if it is down
 		if ($this->ServerOffline) return null;
 
-		$val = null;
-		try
-		{
-
-			$val = $this->_memcache->get($key);
-		}
-		catch (Exception $ex)
-		{
-			// memcache is not working
-			$this->LastServerError = $ex->getMessage();
-			$this->ServerOffline = true;
-		}
-		return $val;
+		return parent::Get($key,$flags);
 	}
 
 
 	/**
-	 * This is method set
-	 *
-	 * @param mixed $key The key that will be associated with the item
-	 * @param mixed $var The variable to store. Strings and integers are stored as is, other types are stored serialized.
-	 * @param mixed $flags Use MEMCACHE_COMPRESSED to store the item compressed (uses zlib).
-	 * @param mixed $expire Expiration time (in seconds)
-	 * @return mixed This is the return value description
-	 *
+	 * @inheritdocs
 	 */
-	public function set($key, $var, $flags = false, $expire = 0)
+	public function Set($key, $val, $flags = null, $timeout = 0)
 	{
 		// prevent hammering the server if it is down
 		if ($this->ServerOffline) return null;
 
-		try
-		{
-			$this->_memcache->set($key, $var, $flags, $expire);
-		}
-		catch (Exception $ex)
-		{
-			// memcache is not working
-			$this->LastServerError = $ex->getMessage();
-			$this->ServerOffline = true;
-		}
+		return parent::Set($key, $val, $flags, $timeout);
 	}
 
-
+	/**
+	 * @inheritdocs
+	 */
+	public function Delete($key)
+	{
+		// prevent hammering the server if it is down
+		if ($this->ServerOffline) return null;
+		
+		return parent::Delete($key);
+	}
 }
 
 ?>

+ 18 - 0
phreeze/templates/TestFortunes.php

@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title>Fortunes</title>
+</head>
+<body>
+<table>
+<tr>
+<th>id</th>
+<th>message</th>
+</tr>
+<?php foreach ($model['fortunes'] as $fortune) {
+	echo '<tr><td>' . $fortune->Id . '</td><td>' . htmlspecialchars($fortune->Message) . '</td></tr>' . "\n";
+} ?>
+</table>
+</body>
+</html>