Browse Source

update phreeze to latest version
implement new tests
fix DB connection issue

Jason Hinkle 11 years ago
parent
commit
a90abbd161
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>