Browse Source

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

Patrick Falls 12 years ago
parent
commit
8e074758d1
98 changed files with 13497 additions and 0 deletions
  1. 3 0
      .gitignore
  2. 37 0
      phreeze/.htaccess
  3. 32 0
      phreeze/README.md
  4. 13 0
      phreeze/benchmark_config
  5. 130 0
      phreeze/deploy/ngnix.conf
  6. 9 0
      phreeze/deploy/phreeze
  7. 35 0
      phreeze/index.php
  8. 61 0
      phreeze/libs/Controller/TestController.php
  9. 57 0
      phreeze/libs/Model/DAO/WorldCriteriaDAO.php
  10. 33 0
      phreeze/libs/Model/DAO/WorldDAO.php
  11. 59 0
      phreeze/libs/Model/DAO/WorldMap.php
  12. 50 0
      phreeze/libs/Model/World.php
  13. 62 0
      phreeze/libs/Model/WorldCriteria.php
  14. 47 0
      phreeze/libs/verysimple/Authentication/Auth401.php
  15. 23 0
      phreeze/libs/verysimple/Authentication/AuthenticationException.php
  16. 104 0
      phreeze/libs/verysimple/Authentication/Authenticator.php
  17. 158 0
      phreeze/libs/verysimple/Authentication/Bcrypt.php
  18. 40 0
      phreeze/libs/verysimple/Authentication/IAuthenticatable.php
  19. 53 0
      phreeze/libs/verysimple/Authentication/OAuthUtil.php
  20. 57 0
      phreeze/libs/verysimple/Authentication/PassPhrase.php
  21. 58 0
      phreeze/libs/verysimple/Authentication/SimpleAccount.php
  22. 136 0
      phreeze/libs/verysimple/DB/DataDriver/IDataDriver.php
  23. 213 0
      phreeze/libs/verysimple/DB/DataDriver/MySQL.php
  24. 215 0
      phreeze/libs/verysimple/DB/DataDriver/MySQLi.php
  25. 182 0
      phreeze/libs/verysimple/DB/DataDriver/SQLite.php
  26. 21 0
      phreeze/libs/verysimple/DB/DatabaseConfig.php
  27. 33 0
      phreeze/libs/verysimple/DB/DatabaseException.php
  28. 27 0
      phreeze/libs/verysimple/DB/ISqlFunction.php
  29. 239 0
      phreeze/libs/verysimple/DB/Reflection/DBColumn.php
  30. 209 0
      phreeze/libs/verysimple/DB/Reflection/DBConnection.php
  31. 42 0
      phreeze/libs/verysimple/DB/Reflection/DBConnectionString.php
  32. 58 0
      phreeze/libs/verysimple/DB/Reflection/DBConstraint.php
  33. 80 0
      phreeze/libs/verysimple/DB/Reflection/DBEventHandler.php
  34. 42 0
      phreeze/libs/verysimple/DB/Reflection/DBKey.php
  35. 78 0
      phreeze/libs/verysimple/DB/Reflection/DBSchema.php
  36. 54 0
      phreeze/libs/verysimple/DB/Reflection/DBServer.php
  37. 67 0
      phreeze/libs/verysimple/DB/Reflection/DBSet.php
  38. 303 0
      phreeze/libs/verysimple/DB/Reflection/DBTable.php
  39. 144 0
      phreeze/libs/verysimple/HTTP/BrowserDevice.php
  40. 59 0
      phreeze/libs/verysimple/HTTP/Context.php
  41. 85 0
      phreeze/libs/verysimple/HTTP/FileUpload.php
  42. 111 0
      phreeze/libs/verysimple/HTTP/FormValidator.php
  43. 342 0
      phreeze/libs/verysimple/HTTP/HttpRequest.php
  44. 23 0
      phreeze/libs/verysimple/HTTP/Request.php
  45. 591 0
      phreeze/libs/verysimple/HTTP/RequestUtil.php
  46. 35 0
      phreeze/libs/verysimple/HTTP/UrlWriter.php
  47. 42 0
      phreeze/libs/verysimple/IO/FileHelper.php
  48. 60 0
      phreeze/libs/verysimple/IO/FolderHelper.php
  49. 18 0
      phreeze/libs/verysimple/IO/IncludeException.php
  50. 98 0
      phreeze/libs/verysimple/IO/Includer.php
  51. 169 0
      phreeze/libs/verysimple/Phreeze/ActionRouter.php
  52. 177 0
      phreeze/libs/verysimple/Phreeze/AuthAccount.php
  53. 127 0
      phreeze/libs/verysimple/Phreeze/BladeRenderEngine.php
  54. 102 0
      phreeze/libs/verysimple/Phreeze/CacheMemCache.php
  55. 35 0
      phreeze/libs/verysimple/Phreeze/CacheNoCache.php
  56. 42 0
      phreeze/libs/verysimple/Phreeze/CacheRam.php
  57. 113 0
      phreeze/libs/verysimple/Phreeze/ConnectionSetting.php
  58. 968 0
      phreeze/libs/verysimple/Phreeze/Controller.php
  59. 500 0
      phreeze/libs/verysimple/Phreeze/Criteria.php
  60. 62 0
      phreeze/libs/verysimple/Phreeze/CriteriaFilter.php
  61. 437 0
      phreeze/libs/verysimple/Phreeze/DataAdapter.php
  62. 103 0
      phreeze/libs/verysimple/Phreeze/DataPage.php
  63. 542 0
      phreeze/libs/verysimple/Phreeze/DataSet.php
  64. 130 0
      phreeze/libs/verysimple/Phreeze/Dispatcher.php
  65. 182 0
      phreeze/libs/verysimple/Phreeze/ExportUtility.php
  66. 125 0
      phreeze/libs/verysimple/Phreeze/FieldMap.php
  67. 265 0
      phreeze/libs/verysimple/Phreeze/GenericRouter.php
  68. 44 0
      phreeze/libs/verysimple/Phreeze/ICache.php
  69. 23 0
      phreeze/libs/verysimple/Phreeze/IDaoMap.php
  70. 36 0
      phreeze/libs/verysimple/Phreeze/IObservable.php
  71. 25 0
      phreeze/libs/verysimple/Phreeze/IObserver.php
  72. 58 0
      phreeze/libs/verysimple/Phreeze/IRenderEngine.php
  73. 58 0
      phreeze/libs/verysimple/Phreeze/IRouter.php
  74. 51 0
      phreeze/libs/verysimple/Phreeze/KeyMap.php
  75. 95 0
      phreeze/libs/verysimple/Phreeze/MockRouter.php
  76. 18 0
      phreeze/libs/verysimple/Phreeze/NotFoundException.php
  77. 49 0
      phreeze/libs/verysimple/Phreeze/Observable.php
  78. 53 0
      phreeze/libs/verysimple/Phreeze/ObserveToBrowser.php
  79. 118 0
      phreeze/libs/verysimple/Phreeze/ObserveToFile.php
  80. 67 0
      phreeze/libs/verysimple/Phreeze/ObserveToSmarty.php
  81. 127 0
      phreeze/libs/verysimple/Phreeze/PHPRenderEngine.php
  82. 779 0
      phreeze/libs/verysimple/Phreeze/Phreezable.php
  83. 909 0
      phreeze/libs/verysimple/Phreeze/Phreezer.php
  84. 302 0
      phreeze/libs/verysimple/Phreeze/QueryBuilder.php
  85. 329 0
      phreeze/libs/verysimple/Phreeze/Reporter.php
  86. 112 0
      phreeze/libs/verysimple/Phreeze/SavantRenderEngine.php
  87. 93 0
      phreeze/libs/verysimple/Phreeze/SmartyRenderEngine.php
  88. 32 0
      phreeze/libs/verysimple/Phreeze/ValidationResponse.php
  89. 76 0
      phreeze/libs/verysimple/String/NameValue.php
  90. 170 0
      phreeze/libs/verysimple/String/SimpleTemplate.php
  91. 527 0
      phreeze/libs/verysimple/String/VerySimpleStringUtil.php
  92. 78 0
      phreeze/libs/verysimple/Util/ExceptionFormatter.php
  93. 82 0
      phreeze/libs/verysimple/Util/ExceptionThrower.php
  94. 100 0
      phreeze/libs/verysimple/Util/MemCacheProxy.php
  95. 73 0
      phreeze/libs/verysimple/Util/TextImageWriter.php
  96. 19 0
      phreeze/libs/verysimple/Util/UrlWriterMode.php
  97. 154 0
      phreeze/libs/verysimple/Util/VsDateUtil.php
  98. 33 0
      phreeze/setup.py

+ 3 - 0
.gitignore

@@ -9,3 +9,6 @@ target/
 *.out
 *.out
 *.class
 *.class
 mods/
 mods/
+/.settings
+/.buildpath
+/.project

+ 37 - 0
phreeze/.htaccess

@@ -0,0 +1,37 @@
+##
+## PHREEZE ACCESS RULES FOR APACHE
+## VERSION 1.2
+##
+
+## 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>

+ 32 - 0
phreeze/README.md

@@ -0,0 +1,32 @@
+#Phreeze Benchmarking Test
+
+This is the Phreeze portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
+
+### JSON Encoding Test
+
+* [JSON test source](libs/Controller/TestController.php)
+
+### Data-Store/Database Mapping Test
+
+* [Database test source](libs/Controller/TestController.php)
+
+## Infrastructure Software Versions
+The tests were run with:
+
+* [PHP Version 5.3.15](http://www.php.net/)
+* [Apache Version 2.2.22](http://httpd.apache.org/)
+* [MySQL 5.5.27](https://dev.mysql.com/)
+* [Phreeze 3.3.1](http://www.phreeze.com/)
+
+## Test URLs
+### JSON Encoding Test
+
+http://localhost/phreeze/json
+
+### Data-Store/Database Mapping Test
+
+http://localhost/phreeze/db
+
+### Variable Query Test
+
+http://localhost/phreeze/db?queries=5

+ 13 - 0
phreeze/benchmark_config

@@ -0,0 +1,13 @@
+{
+  "framework": "phreeze",
+  "tests": [{
+    "default": {
+      "setup_file": "setup",
+      "json_url": "/phreeze/json",
+      "db_url": "/phreeze/db",
+      "query_url": "/phreeze/db?queries=",
+      "port": 8080,
+      "sort": 1
+    }
+  }]
+}

+ 130 - 0
phreeze/deploy/ngnix.conf

@@ -0,0 +1,130 @@
+#user  nobody;
+worker_processes  8;
+
+#error_log  logs/error.log;
+#error_log  logs/error.log  notice;
+#error_log  logs/error.log  info;
+
+#pid        logs/nginx.pid;
+
+
+events {
+    worker_connections  1024;
+}
+
+
+http {
+    include       /usr/local/nginx/conf/mime.types;
+    default_type  application/octet-stream;
+
+    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
+    #                  '$status $body_bytes_sent "$http_referer" '
+    #                  '"$http_user_agent" "$http_x_forwarded_for"';
+
+    #access_log  logs/access.log  main;
+
+    sendfile        on;
+    #tcp_nopush     on;
+
+    #keepalive_timeout  0;
+    keepalive_timeout  65;
+
+    #gzip  on;
+
+    server {
+        listen       8080;
+        server_name  localhost;
+
+        #charset koi8-r;
+
+        #access_log  logs/host.access.log  main;
+
+        #location / {
+        #    root   html;
+        #    index  index.html index.htm;
+        #}
+
+        #error_page  404              /404.html;
+
+        # redirect server error pages to the static page /50x.html
+        #
+        #error_page   500 502 503 504  /50x.html;
+        #location = /50x.html {
+        #    root   html;
+        #}
+
+        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
+        #
+        #location ~ \.php$ {
+        #    proxy_pass   http://127.0.0.1;
+        #}
+
+        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
+        #
+        location ~ \.php$ {
+            root /home/ubuntu/FrameworkBenchmarks;
+            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;
+            include        /usr/local/nginx/conf/fastcgi_params;
+        }
+
+	    # url rewriting for a Phreeze application called "example" 
+	    #
+	    location ~ ^/phreeze/(.*) {
+	        try_files /phreeze/$1 /phreeze/$1/ /phreeze/index.php?_REWRITE_COMMAND=$1&args;
+	    }
+	    
+	    # url rewriting for phreeze app that is installed in the htdocs root
+	    #
+	    #location ~ ^/(.*) {
+	    #    try_files /$1 /$1/ /index.php?_REWRITE_COMMAND=$1&args;
+	    #}
+    
+        # deny access to .htaccess files, if Apache's document root
+        # concurs with nginx's one
+        #
+        #location ~ /\.ht {
+        #    deny  all;
+        #}
+    }
+
+
+    # another virtual host using mix of IP-, name-, and port-based configuration
+    #
+    #server {
+    #    listen       8000;
+    #    listen       somename:8080;
+    #    server_name  somename  alias  another.alias;
+
+    #    location / {
+    #        root   html;
+    #        index  index.html index.htm;
+    #    }
+    #}
+
+
+    # HTTPS server
+    #
+    #server {
+    #    listen       443;
+    #    server_name  localhost;
+
+    #    ssl                  on;
+    #    ssl_certificate      cert.pem;
+    #    ssl_certificate_key  cert.key;
+
+    #    ssl_session_timeout  5m;
+
+    #    ssl_protocols  SSLv2 SSLv3 TLSv1;
+    #    ssl_ciphers  HIGH:!aNULL:!MD5;
+    #    ssl_prefer_server_ciphers   on;
+
+    #    location / {
+    #        root   html;
+    #        index  index.html index.htm;
+    #    }
+    #}
+
+}

+ 9 - 0
phreeze/deploy/phreeze

@@ -0,0 +1,9 @@
+<VirtualHost *:8080>
+  Alias /phreeze/ "/home/ubuntu/FrameworkBenchmarks/phreeze/"
+  <Directory /home/ubuntu/FrameworkBenchmarks/phreeze/>
+          Options Indexes FollowSymLinks MultiViews
+          #AllowOverride None
+          Order allow,deny
+          allow from all
+  </Directory>
+</VirtualHost>

+ 35 - 0
phreeze/index.php

@@ -0,0 +1,35 @@
+<?php
+/** @package    HELLO WORLD */
+
+set_include_path('libs/' . PATH_SEPARATOR . get_include_path());
+
+/* 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/Phreezer.php';
+
+$cs = new ConnectionSetting();
+$cs->ConnectionString = "localhost:3306";
+$cs->DBName = "hello_world";
+$cs->Username = "benchmarkdbuser";
+$cs->Password = "benchmarkdbpass";
+$cs->Type = "MySQL";
+
+$phreezer = new Phreezer($cs);
+
+$route_map = array(
+		'GET:' => array('route' => 'Test.JSON'),
+		'GET:json' => array('route' => 'Test.JSON'),
+		'GET:db' => array('route' => 'Test.DB')
+);
+
+$router = new GenericRouter('./','Test.JSON',$route_map);
+
+Dispatcher::Dispatch(
+	$phreezer,
+	null,
+	'',
+	null,
+	$router
+);

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

@@ -0,0 +1,61 @@
+<?php
+/** @package    HELLO WORLD::Controller */
+
+/** import supporting libraries */
+require_once("verysimple/Phreeze/Controller.php");
+
+/**
+ *
+ * @package HELLO WORLD::Controller
+ * @author ClassBuilder
+ * @version 1.0
+ */
+class TestController extends Controller
+{
+	
+	/**
+	 * Not used but necessary to implement Controller
+	 * @see Controller::Init()
+	 */
+	protected function Init()
+	{
+	}
+	
+	/**
+	 * Test route that outputs a simple JSON object
+	 */
+	public function JSON()
+	{
+		$arr = array(
+		    "message" => "Hello, World!"
+		);
+		
+		$this->RenderJSON($arr);
+	}
+	
+	/**
+	 * Test route that connects to the database and outputs
+	 * the number of rows specified in the querystring argument "queries"
+	 */
+	public function DB()
+	{
+		// 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);
+			
+			// convert the Phreezable object into a simple structure for output
+			$arr[] = array('id'=>$world->Id,'randomNumber'=>$world->Randomnumber);
+		}
+		
+		$this->RenderJSON($arr);
+
+	}
+	
+}

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

@@ -0,0 +1,57 @@
+<?php
+/** @package    HelloWorld::Model::DAO */
+
+/** import supporting libraries */
+require_once("verysimple/Phreeze/Criteria.php");
+
+/**
+ * WorldCriteria allows custom querying for the World 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 WorldCriteriaDAO extends Criteria
+{
+
+	public $Id_Equals;
+	public $Id_NotEquals;
+	public $Id_IsLike;
+	public $Id_IsNotLike;
+	public $Id_BeginsWith;
+	public $Id_EndWith;
+	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 $Randomnumber_Equals;
+	public $Randomnumber_NotEquals;
+	public $Randomnumber_IsLike;
+	public $Randomnumber_IsNotLike;
+	public $Randomnumber_BeginsWith;
+	public $Randomnumber_EndWith;
+	public $Randomnumber_GreaterThan;
+	public $Randomnumber_GreaterThanOrEqual;
+	public $Randomnumber_LessThan;
+	public $Randomnumber_LessThanOrEqual;
+	public $Randomnumber_In;
+	public $Randomnumber_IsNotEmpty;
+	public $Randomnumber_IsEmpty;
+	public $Randomnumber_BitwiseOr;
+	public $Randomnumber_BitwiseAnd;
+
+}
+
+?>

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

@@ -0,0 +1,33 @@
+<?php
+/** @package HelloWorld::Model::DAO */
+
+/** import supporting libraries */
+require_once("verysimple/Phreeze/Phreezable.php");
+require_once("WorldMap.php");
+
+/**
+ * WorldDAO provides object-oriented access to the World 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 WorldDAO extends Phreezable
+{
+	/** @var int */
+	public $Id;
+
+	/** @var int */
+	public $Randomnumber;
+
+
+
+}
+?>

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

@@ -0,0 +1,59 @@
+<?php
+/** @package    HelloWorld::Model::DAO */
+
+/** import supporting libraries */
+require_once("verysimple/Phreeze/IDaoMap.php");
+
+/**
+ * WorldMap is a static class with functions used to get FieldMap and KeyMap information that
+ * is used by Phreeze to map the WorldDAO to the World 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 WorldMap implements IDaoMap
+{
+	/**
+	 * Returns a singleton array of FieldMaps for the World object
+	 *
+	 * @access public
+	 * @return array of FieldMaps
+	 */
+	public static function GetFieldMaps()
+	{
+		static $fm = null;
+		if ($fm == null)
+		{
+			$fm = Array();
+			$fm["Id"] = new FieldMap("Id","World","id",true,FM_TYPE_INT,10,null,true);
+			$fm["Randomnumber"] = new FieldMap("Randomnumber","World","randomNumber",false,FM_TYPE_INT,11,null,false);
+		}
+		return $fm;
+	}
+
+	/**
+	 * Returns a singleton array of KeyMaps for the World object
+	 *
+	 * @access public
+	 * @return array of KeyMaps
+	 */
+	public static function GetKeyMaps()
+	{
+		static $km = null;
+		if ($km == null)
+		{
+			$km = Array();
+		}
+		return $km;
+	}
+
+}
+
+?>

+ 50 - 0
phreeze/libs/Model/World.php

@@ -0,0 +1,50 @@
+<?php
+/** @package    HelloWorld::Model */
+
+/** import supporting libraries */
+require_once("DAO/WorldDAO.php");
+require_once("WorldCriteria.php");
+
+/**
+ * The World class extends WorldDAO which provides the access
+ * to the datastore.
+ *
+ * @package HelloWorld::Model
+ * @author ClassBuilder
+ * @version 1.0
+ */
+class World extends WorldDAO
+{
+
+	/**
+	 * 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 World: ' .  implode(', ', $this->GetValidationErrors()));
+
+		// OnSave must return true or eles Phreeze will cancel the save operation
+		return true;
+	}
+
+}
+
+?>

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

@@ -0,0 +1,62 @@
+<?php
+/** @package    HelloWorld::Model */
+
+/** import supporting libraries */
+require_once("DAO/WorldCriteriaDAO.php");
+
+/**
+ * The WorldCriteria class extends WorldDAOCriteria and is used
+ * to query the database for objects and collections
+ * 
+ * @inheritdocs
+ * @package HelloWorld::Model
+ * @author ClassBuilder
+ * @version 1.0
+ */
+class WorldCriteria extends WorldCriteriaDAO
+{
+	
+	/**
+	 * 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 ....";
+		}
+	}
+	*/
+
+}
+?>

+ 47 - 0
phreeze/libs/verysimple/Authentication/Auth401.php

@@ -0,0 +1,47 @@
+<?php
+/** @package    verysimple::Authentication */
+
+/**
+* Auth404 provided 401 authentication
+* @package    verysimple::Authentication
+* @author     VerySimple Inc.
+* @copyright  1997-2007 VerySimple, Inc.
+* @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+* @version    1.0
+*/
+class Auth401
+{
+
+	/**
+	* Send 401 headers to the browser
+	* @param string message to output as "Basic realm" text/message (default "Login Required")
+	* @param bool true to terminate php after outputting headers (default true)
+	*/
+	static function OutputHeaders($realm = "Login Required", $terminate = true)
+	{
+		header("WWW-Authenticate: Basic realm=\"".$realm."\"");
+		header("Status: 401 Unauthorized");
+		header("HTTP-Status: 401 Unauthorized");
+		if ($terminate) die();
+	}
+	
+	/**
+	 * Returns the server AUTH_USERNAME if provided or returns empty string
+	 * @return string
+	 */
+	static function GetUsername()
+	{
+		return isset($_SERVER["PHP_AUTH_USER"]) ? $_SERVER["PHP_AUTH_USER"] : "";
+	}
+
+	/**
+	 * Returns the server AUTH_PASSWORD if provided or returns empty string
+	 * @return string
+	 */
+	static function GetPassword()
+	{
+		return isset($_SERVER["PHP_AUTH_PW"]) ? $_SERVER["PHP_AUTH_PW"] : "";
+	}
+}
+
+?>

+ 23 - 0
phreeze/libs/verysimple/Authentication/AuthenticationException.php

@@ -0,0 +1,23 @@
+<?php
+/** @package    verysimple::Authentication */
+ 
+ /**
+ * AuthenticationException is thrown as a result of "RequirePermission" failing
+ * @package    verysimple::Authentication
+ * @author     VerySimple Inc.
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    1.0
+ */
+class AuthenticationException extends Exception
+{
+    // Redefine the exception so message isn't optional
+    public function __construct($message, $code = 0) 
+	{
+        // make sure everything is assigned properly
+        parent::__construct($message, $code);
+    }
+
+}
+
+?>

+ 104 - 0
phreeze/libs/verysimple/Authentication/Authenticator.php

@@ -0,0 +1,104 @@
+<?php
+/** @package    verysimple::Authentication */
+
+/** import supporting libraries */
+require_once("IAuthenticatable.php");
+require_once("AuthenticationException.php");
+
+/**
+ * Authenticator is a collection of static methods for storing a current user
+ * in the session and determining if the user has necessary permissions to 
+ * perform an action
+ * @package    verysimple::Authentication
+ * @author     VerySimple Inc.
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    1.0
+ */
+class Authenticator
+{
+	static $user = null;
+	static $is_initialized = false;
+	
+	public static function Init()
+	{
+		if (!self::$is_initialized)
+		{
+			self::$is_initialized = true;
+			
+			if (session_id() == '')
+			{
+				@session_start();
+			}
+		}
+	}
+	
+	/**
+	 * Returns the currently authenticated user or null
+	 *
+	 * @access public
+	 * @return IAuthenticatable || null
+	 */
+	public static function GetCurrentUser($guid = "CURRENT_USER")
+	{
+		if (self::$user == null)
+		{
+			self::Init();
+			
+			if (isset($_SESSION[$guid]))
+			{
+				self::$user = unserialize($_SESSION[$guid]);
+			}		
+		}
+		return self::$user;
+	}
+
+	
+	/**
+	 * Set the given IAuthenticable object as the currently authenticated user.
+	 * UnsetAllSessionVars will be called before setting the current user
+	 *
+	 * @param IAuthenticatable $user
+	 * @param mixed $guid a unique id for this session
+	 *
+	 */
+	public static function SetCurrentUser(IAuthenticatable $user, $guid = "CURRENT_USER")
+	{
+		self::UnsetAllSessionVars(); // this calls Init so we don't have to here
+		self::$user = $user;
+		$_SESSION[$guid] = serialize($user);
+	}
+
+	
+	/**
+	 * Unsets all session variables without destroying the session
+	 *
+	 */
+	public static function UnsetAllSessionVars()
+	{
+		self::Init();
+		foreach (array_keys($_SESSION) as $key)
+		{
+			unset($_SESSION[$key]);
+		}
+	}
+	
+	/**
+	 * Forcibly clear all _SESSION variables and destroys the session
+	 *
+	 * @param string $guid The GUID of this user
+	 */
+	public static function ClearAuthentication($guid = "CURRENT_USER")
+	{
+		self::Init();
+		self::$user = null;
+		unset($_SESSION[$guid]);
+		
+		self::UnsetAllSessionVars();
+		
+		@session_destroy();
+	}
+
+}
+
+?>

+ 158 - 0
phreeze/libs/verysimple/Authentication/Bcrypt.php

@@ -0,0 +1,158 @@
+<?php
+/** @package    verysimple::Authentication */
+
+/**
+ * Bcrypt abstracts the use of bcrypt for secure password hashing.
+ * The verify method is backwards compatible with any algorithm
+ * supported by php crypt function
+ *
+ * @author http://stackoverflow.com/questions/4795385/how-do-you-use-bcrypt-for-hashing-passwords-in-php
+ * @author J.Hinkle http://verysimple.com/
+ * @example <code>
+ * $bcrypt = new Bcrypt(15);
+ * $hash = $bcrypt->hash('password');
+ * $isGood = $bcrypt->verify('password', $hash);
+ * </code>
+ */
+class Bcrypt
+{
+	private $rounds;
+	private $randomState;
+
+	/**
+	 * Constructor
+	 * @param int number of crypt rounds
+	 * @throws Exception if bcrypt is not supported
+	 */
+	public function __construct($rounds = 12) {
+		if(CRYPT_BLOWFISH != 1) {
+			throw new Exception("bcrypt not supported in this installation. See http://php.net/crypt");
+		}
+
+		$this->rounds = $rounds;
+	}
+
+	/**
+	 * Return true if the given hash is crypted with the blowfish algorithm
+	 * @param string $hash
+	 */
+	static function isBlowfish($hash)
+	{
+		return substr($hash,0,4) == '$2a$';
+	}
+
+	/**
+	 * generate a hash
+	 * @param string plain text input
+	 */
+	public function hash($input) {
+		$hash = crypt($input, $this->getSalt());
+
+		if(strlen($hash) > 13)
+			return $hash;
+
+		return false;
+	}
+
+	/**
+	 * Verify if the input is equal to the hashed value
+	 * @param string plain text input
+	 * @param string hashed input
+	 */
+	public function verify($input, $existingHash) {
+		$hash = crypt($input, $existingHash);
+
+		return $hash === $existingHash;
+	}
+
+	/**
+	 * return an ascii-encoded 16 char salt
+	 */
+	private function getSalt() {
+		$salt = sprintf('$2a$%02d$', $this->rounds);
+
+		$bytes = $this->getRandomBytes(16);
+
+		$salt .= $this->encodeBytes($bytes);
+
+		return $salt;
+	}
+
+	/**
+	 * get random bytes to be used in random salts
+	 * @param int $count
+	 */
+	private function getRandomBytes($count) {
+		$bytes = '';
+
+		if(function_exists('openssl_random_pseudo_bytes') &&
+				(strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) { // OpenSSL slow on Win
+			$bytes = openssl_random_pseudo_bytes($count);
+		}
+
+		if($bytes === '' && is_readable('/dev/urandom') &&
+				($hRand = @fopen('/dev/urandom', 'rb')) !== FALSE) {
+			$bytes = fread($hRand, $count);
+			fclose($hRand);
+		}
+
+		if(strlen($bytes) < $count) {
+			$bytes = '';
+
+			if($this->randomState === null) {
+				$this->randomState = microtime();
+				if(function_exists('getmypid')) {
+					$this->randomState .= getmypid();
+				}
+			}
+
+			for($i = 0; $i < $count; $i += 16) {
+				$this->randomState = md5(microtime() . $this->randomState);
+
+				if (PHP_VERSION >= '5') {
+					$bytes .= md5($this->randomState, true);
+				} else {
+					$bytes .= pack('H*', md5($this->randomState));
+				}
+			}
+
+			$bytes = substr($bytes, 0, $count);
+		}
+
+		return $bytes;
+	}
+
+	/**
+	 * ascii-encode used for converting random salt into legit ascii value
+	 * @param string $input
+	 */
+	private function encodeBytes($input) {
+		// The following is code from the PHP Password Hashing Framework
+		$itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+
+		$output = '';
+		$i = 0;
+		do {
+			$c1 = ord($input[$i++]);
+			$output .= $itoa64[$c1 >> 2];
+			$c1 = ($c1 & 0x03) << 4;
+			if ($i >= 16) {
+				$output .= $itoa64[$c1];
+				break;
+			}
+
+			$c2 = ord($input[$i++]);
+			$c1 |= $c2 >> 4;
+			$output .= $itoa64[$c1];
+			$c1 = ($c2 & 0x0f) << 2;
+
+			$c2 = ord($input[$i++]);
+			$c1 |= $c2 >> 6;
+			$output .= $itoa64[$c1];
+			$output .= $itoa64[$c2 & 0x3f];
+		} while (1);
+
+		return $output;
+	}
+}
+?>

+ 40 - 0
phreeze/libs/verysimple/Authentication/IAuthenticatable.php

@@ -0,0 +1,40 @@
+<?php
+/** @package    verysimple::Authentication */
+
+/**
+ * Classes implementing IAuthenticatable can be used with Authenticator for checking permissions
+ * @package    verysimple::Authentication
+ * @author     VerySimple Inc.
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    1.0
+ */
+interface IAuthenticatable
+{
+	/**
+	 * Return true if the user is anonymous, meaning they have not
+	 * yet authenticated
+	 * @return bool
+	 */
+	public function IsAnonymous();
+	
+	/**
+	 * Verify if the IAuthenticable object has the requested permission
+	 * and return either true or false
+	 * @param variant $permission
+	 * @return bool
+	 */
+	public function IsAuthorized($permission);
+	
+	/**
+	 * Verify the login information and, if correct, this object will
+	 * populate itself and return a reference to $this.  If login fails
+	 * then return false/null.
+	 * @param string $username
+	 * @param string $password
+	 * @return IAuthenticatable or false/null
+	 */
+	public function Login($username,$password);
+}
+
+?>

+ 53 - 0
phreeze/libs/verysimple/Authentication/OAuthUtil.php

@@ -0,0 +1,53 @@
+<?php
+/** @package	verysimple::Authentication */
+
+require_once "oauth/OAuthStore.php";
+require_once "oauth/OAuthRequester.php";
+require_once "verysimple/String/VerySimpleStringUtil.php";
+
+/**
+ * A set of utility functions for working with OAuth
+ *
+ * @package	verysimple::String
+ * @author Jason Hinkle
+ * @copyright  1997-2012 VerySimple, Inc.
+ * @license	http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version 1.0
+ */
+class OAuthUtil
+{
+
+	/**
+	 * Given a URL return an OAuth signed URL.  This will handle creating a timestamp and nonce
+	 *
+	 * @param string $url the unsigned url
+	 * @param string $method request method GET, POST, PUT, DELETE
+	 * @param string $key oauth key
+	 * @param string $secret oauth secret
+	 * @param array $params querystring or post parameters
+	 * @param string $body the body contents of the request
+	 * @param string $signature_method method used for signature (default = 'HMAC_SHA1')
+	 */
+	public static function SignUrl($url,$method,$key,$secret,$params=null,$body=null,$signature_method='HMAC_SHA1')
+	{
+		$options = array('consumer_key' => $key, 'consumer_secret' => $secret);
+		$params = $params ? $params : array();
+
+		OAuthStore::instance("2Leg", $options);
+
+		// Obtain a request object for the request we want to make
+		$request = new OAuthRequester($url, $method, $params, $body);
+
+		$sig = $request->sign($key,null,'');
+
+		$data = $request->signatureBaseString();
+
+		$url = substr( urldecode($data . '&oauth_signature=' . $request->calculateDataSignature($data,$secret,'',$signature_method) ), strlen($method) + 1);
+
+		$url = VerySimpleStringUtil::ReplaceFirst('&', '?', $url);
+
+		return $url;
+	}
+}
+
+?>

+ 57 - 0
phreeze/libs/verysimple/Authentication/PassPhrase.php

@@ -0,0 +1,57 @@
+<?php
+/** @package    verysimple::Authentication */
+
+/**
+ * Passphrase is a utility class containing static functions for generating passwords
+ * @package    verysimple::Authentication
+ * @author     VerySimple Inc.
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    1.0
+ */
+class PassPhrase
+{
+
+/**
+	** 
+	* GetRandomPassPhrase returns a prounoucable password of the given length
+	* filtering out potential offensive words
+	** @public
+	** @param int $$pass_length
+	** @return string $password.
+	**/
+	static function GetRandomPassPhrase($length = 8)
+	{
+		srand((double)microtime()*1000000);
+		
+		$vowels = array("a", "e", "i", "o", "u");
+		$cons = array("b", "c", "d", "g", "h", "j", "k", "l", "m", "n", "p", "r", "s", "t", "u", "v", "w", "tr",
+			"cr", "br", "fr", "th", "dr", "ch", "ph", "wr", "st", "sp", "sw", "pr", "sl", "cl");
+		$badwords = array("fuc","shit","ass","pus","slut","dic","fag","coc","cun","cum","dam","nig","kik","spic","neg","bitc","gay","poo","twa","vag","peni","whor");
+		
+		$num_vowels = count($vowels);
+		$num_cons = count($cons);
+		$password = "";
+		
+		for($i = 0; $i < $length; $i++)
+		{
+			$password .= $cons[rand(0, $num_cons - 1)] . $vowels[rand(0, $num_vowels - 1)];
+		}
+		
+		$newpass = substr($password, 0, $length);
+		
+		// ensure this is not a potentially offensive password
+		foreach ($badwords as $badword)
+		{
+			if ( strpos($newpass,$badword) !== false)
+			{
+				return PassPhrase::GetRandomPassPhrase($length);
+			}
+		}
+		
+		return $newpass;
+	}
+}
+
+	
+?>

+ 58 - 0
phreeze/libs/verysimple/Authentication/SimpleAccount.php

@@ -0,0 +1,58 @@
+<?php
+/** @package    verysimple::Authentication */
+
+require_once("IAuthenticatable.php");
+
+/**
+ * simple implementation of IAuthenticatable for using a basic
+ * hard-coded username/password combination.
+ * @package    verysimple::Authentication
+ * @author     VerySimple Inc.
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    1.0
+ */
+class SimpleAccount implements IAuthenticatable
+{
+	private $_authenticated = false;
+	private $_username;
+	private $_password;
+	
+	public function __construct($required_username,$required_password)
+	{
+		$this->_username = $required_username;
+		$this->_password = $required_password;
+	}
+	
+	public function IsAnonymous()
+	{
+		return (!$this->_authenticated);
+	}
+		
+	public function IsAuthorized($permission)
+	{
+		return $this->_authenticated;
+	}
+	
+	public function Login($username,$password)
+	{
+		if ($this->_username == $username && $this->_password == $password)
+		{
+			$this->_authenticated = true;
+			return true;
+		}
+		
+		return false;
+	}
+	
+	/**
+	 * This is implemented only for Phreeze but is not utilized
+	 * @param object
+	 */
+	public function Refresh($obj)
+	{
+		
+	}
+}
+
+?>

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

@@ -0,0 +1,136 @@
+<?php
+/** @package verysimple::DB::DataDriver */
+
+/**
+ * IDataDriver is an interface that is used by Phreeze::DataAdapter
+ * to communicate with a database storage engine.  Any server
+ * that can implement these methods will be usable with 
+ * 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
+ */
+interface IDataDriver
+{
+	/**
+	 * returns a string to identify the type of server
+	 * supported in the implemenation
+	 * 
+	 * @return string
+	 */
+	function GetServerType();
+	
+	/**
+	 * Return true if the given connection is live
+	 * @param $connection
+	 * @return bool;
+	 */
+	function Ping($connection);
+	
+	/**
+	 * Open the database with the given parameters.  the implementation
+	 * must provide a protocol for the connection string that is relevant
+	 * 
+	 * @param string $connectionstring
+	 * @param string $database
+	 * @param string $username
+	 * @param string $password
+	 * @param string $charset the charset that will be used for the connection (example 'utf8')
+	 * @param string $bootstrap SQL that will be executed when the connection is first opened (example 'SET SQL_BIG_SELECTS=1')
+	 * @return connection
+	 */
+	function Open($connectionstring,$database,$username,$password,$charset='',$bootstrap='');
+	
+	/**
+	 * Close the given connection reference
+	 * @param connection
+	 */
+	function Close($connection);
+	
+	/**
+	 * Execute a SQL query that is expected to return a resultset
+	 * @param connection
+	 * @param string sql query
+	 * @return resultset
+	 */
+	function Query($connection,$sql);
+
+	/**
+	 * Executes a SQL query that does not return a resultset, such as an insert or update
+	 * 
+	 * @param connection
+	 * @param string sql statement
+	 * @return int number of affected records
+	 */
+	function Execute($connection,$sql);
+	
+	/**
+	 * Moves the database curser forward and returns the current row as an associative array
+	 * When no more data is available, null is returned
+	 * 
+	 * @param connection
+	 * @param resultset
+	 * @return array (or null)
+	 */
+	function Fetch($connection,$rs);
+
+	/**
+	 * Returns the last auto-insert id that was inserted for the
+	 * given connection reference
+	 * 
+	 * @param connection
+	 */
+	function GetLastInsertId($connection);
+	
+	/**
+	 * Returns the last error message that the server encountered
+	 * for the given connection reference
+	 * 
+	 * @param connection
+	 */
+	function GetLastError($connection);
+	
+	/**
+	 * Releases the resources for the given resultset.
+	 * 
+	 * @param connection
+	 * @param resultset
+	 */
+	function Release($connection,$rs);
+	
+	/**
+	 * Remove or escape any characters that will cause a SQL statment
+	 * to crash or cause an injection exploit
+	 * @param string value to escape
+	 * @return string value after escaping
+	 */
+	function Escape($val);
+	
+	/**
+	 * Return a stringified version of $val ready to insert with appropriate quoting and escaping
+	 * This method must handle at a minimum: strings, numbers, NULL and ISqlFunction objects
+	 * @param variant value to insert/update/query
+	 * @return string value ready to use in a SQL statement quoted and escaped if necessary
+	 */
+	function GetQuotedSql($val);
+	
+	/**
+	 * Returns an array of tablenames for the given database
+	 * @param mixed connection reference
+	 * @param string name of the database
+	 * @param $ommitEmptyTables (default false) set to true and tables with no data will be ommitted
+	 */
+ 	function GetTableNames($connection, $dbname, $ommitEmptyTables = false) ;
+	
+	/**
+	 * Optimize, clean, defrag or whatever action is relevant for the database server
+	 * @param mixes connection reference
+	 * @param string name of table to optimize
+	 */
+ 	function Optimize($connection,$table);
+}
+
+?>

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

@@ -0,0 +1,213 @@
+<?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 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 (!function_exists("mysql_connect")) throw new DatabaseException('mysql extension is not enabled on this server.',DatabaseException::$CONNECTION_ERROR);
+		
+		if ( !$connection = @mysql_connect($connectionstring, $username, $password) )
+		{
+			throw new DatabaseException("Error connecting to database: " . mysql_error(),DatabaseException::$CONNECTION_ERROR);
+		}
+
+		if (!@mysql_select_db($database, $connection))
+		{
+			throw new DatabaseException("Unable to select database " . $database,DatabaseException::$CONNECTION_ERROR);
+		}
+		
+		if ($charset && !@mysql_set_charset($charset,$connection))
+		{
+			throw new DatabaseException("Unable to set charset " . $charset,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) 
+	{
+		@mysql_close($connection); // ignore warnings
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function Query($connection,$sql) 
+	{
+		if ( !$rs = @mysql_query($sql, $connection) )
+		{
+			throw new DatabaseException(mysql_error(),DatabaseException::$ERROR_IN_QUERY);
+		}
+		
+		return $rs;
+	}
+
+	/**
+	 * @inheritdocs
+	 */
+	function Execute($connection,$sql) 
+	{
+		if ( !$result = @mysql_query($sql, $connection) )
+		{
+			throw new DatabaseException(mysql_error(),DatabaseException::$ERROR_IN_QUERY);
+		}
+		
+		return mysql_affected_rows($connection);
+	}
+
+	/**
+	 * @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 mysql_fetch_assoc($rs);
+	}
+
+	/**
+	 * @inheritdocs
+	 */
+	function GetLastInsertId($connection) 
+	{
+		return (mysql_insert_id($connection));
+	}
+
+	/**
+	 * @inheritdocs
+	 */
+	function GetLastError($connection)
+	{
+		return mysql_error($connection);
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function Release($connection,$rs) 
+	{
+		mysql_free_result($rs);	
+	}
+	
+	/**
+	 * @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;
+	}
+	
+}
+
+?>

+ 215 - 0
phreeze/libs/verysimple/DB/DataDriver/MySQLi.php

@@ -0,0 +1,215 @@
+<?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 DataDriverMySQLi 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 "MySQLi";
+	}
+	
+	function Ping($connection)
+	{
+		 return mysqli_ping($connection);
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function Open($connectionstring,$database,$username,$password,$charset='',$bootstrap='') 
+	{
+		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 ( mysqli_connect_errno() )
+		{
+			throw new DatabaseException("Error connecting to database: " . mysqli_connect_error(),DatabaseException::$CONNECTION_ERROR);
+		}
+		
+		if ($charset)
+		{
+			mysqli_set_charset($connection,$charset);
+			
+			if ( mysqli_connect_errno() )
+			{
+				throw new DatabaseException("Unable to set charset: " . mysqli_connect_error(),DatabaseException::$CONNECTION_ERROR);
+			}
+		}
+		
+		if ($bootstrap)
+		{
+			$statements = explode(';',$bootstrap);
+			foreach ($statements as $sql)
+			{
+				try 
+				{
+					$this->Execute($connection, $sql);
+				}
+				catch (Exception $ex)
+				{
+					throw new DatabaseException("problem with bootstrap sql: " . $ex->getMessage(),DatabaseException::$ERROR_IN_QUERY);
+				}
+			}
+		}
+		
+		return $connection;
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function Close($connection) 
+	{
+		@mysqli_close($connection); // ignore warnings
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function Query($connection,$sql) 
+	{
+		if ( !$rs = @mysqli_query($connection,$sql) )
+		{
+			throw new DatabaseException(mysqli_error($connection),DatabaseException::$ERROR_IN_QUERY);
+		}
+		
+		return $rs;
+	}
+
+	/**
+	 * @inheritdocs
+	 */
+	function Execute($connection,$sql) 
+	{
+		if ( !$result = @mysqli_query($connection,$sql) )
+		{
+			throw new DatabaseException(mysqli_error($connection),DatabaseException::$ERROR_IN_QUERY);
+		}
+		
+		return mysqli_affected_rows($connection);
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function Fetch($connection,$rs) 
+	{
+		return mysqli_fetch_assoc($rs);
+	}
+
+	/**
+	 * @inheritdocs
+	 */
+	function GetLastInsertId($connection) 
+	{
+		return (mysqli_insert_id($connection));
+	}
+
+	/**
+	 * @inheritdocs
+	 */
+	function GetLastError($connection)
+	{
+		return mysqli_error($connection);
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function Release($connection,$rs) 
+	{
+		mysqli_free_result($rs);	
+	}
+	
+	/**
+	 * @inheritdocs
+	 * this method currently uses replacement and not mysqli_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 mysqli_real_escape_string($val);
+ 	}
+
+ 	/**
+ 	 * @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 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;
+	}
+	
+}
+
+?>

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

@@ -0,0 +1,182 @@
+<?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 SQLite database file.  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 DataDriverSQLite implements IDataDriver
+{	
+	/**
+	 * @inheritdocs
+	 */
+	function GetServerType()
+	{
+		return "SQLite";
+	}
+	
+	function Ping($connection)
+	{
+		 throw new DatabaseException("Not Implemented");
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function Open($connectionstring,$database,$username,$password,$charset='',$bootstrap='') 
+	{
+		if (!class_exists("SQLite3")) throw new DatabaseException('SQLite3 extension is not enabled on this server.',DatabaseException::$CONNECTION_ERROR);
+		
+		if ( !$connection =  new SQLite3($connectionstring, SQLITE3_OPEN_READWRITE,$password) )
+		{
+			throw new DatabaseException("Error connecting to database: Unable to open the database file.",DatabaseException::$CONNECTION_ERROR);
+		}
+		
+		// charset is ignored with sqlite
+
+		if ($bootstrap)
+		{
+			$statements = explode(';',$bootstrap);
+			foreach ($statements as $sql)
+			{
+				try
+				{
+					$this->Execute($connection, $sql);
+				}
+				catch (Exception $ex)
+				{
+					throw new DatabaseException("problem with bootstrap sql: " . $ex->getMessage(),DatabaseException::$ERROR_IN_QUERY);
+				}
+			}
+		}
+		
+		return $connection;
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function Close($connection) 
+	{
+		@$connection->close(); // ignore warnings
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function Query($connection,$sql) 
+	{
+
+		if ( !$rs = $connection->query($sql) )
+		{
+			throw new DatabaseException($connection->lastErrorMsg(),DatabaseException::$ERROR_IN_QUERY);
+		}
+		
+		return $rs;
+	}
+
+	/**
+	 * @inheritdocs
+	 */
+	function Execute($connection,$sql) 
+	{
+		return $connection->exec($sql);
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function Fetch($connection,$rs) 
+	{
+		return $rs->fetchArray(SQLITE3_ASSOC);
+	}
+
+	/**
+	 * @inheritdocs
+	 */
+	function GetLastInsertId($connection) 
+	{
+		return $connection->lastInsertRowID();
+	}
+
+	/**
+	 * @inheritdocs
+	 */
+	function GetLastError($connection)
+	{
+		return $connection->lastErrorMsg();
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	function Release($connection,$rs) 
+	{
+		$rs->finalize();
+	}
+	
+	/**
+	 * @inheritdocs
+	 * @TODO: use SQLite
+	 */
+	function Escape($val) 
+	{
+		return str_replace("'","''",$val);
+ 	}
+	
+ 	/**
+ 	 * @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 GetTableNames($connection, $dbname, $ommitEmptyTables = false) 
+	{
+		if ($ommitEmptyTables) throw new DatabaseException("SQLite DataDriver doesn't support returning only non-empty tables.  Set ommitEmptyTables arg to false to use this method.");
+		
+		$rs = $this->Query($connection,"SELECT name FROM sqlite_master WHERE type='table' and name != 'sqlite_sequence' ORDER BY name");
+
+		$tables = array();
+		
+		while ( $row = $this->Fetch($connection,$rs) )
+		{
+			$tables[] = $row['name'];
+		}
+		
+		return $tables;
+		
+ 	}
+	
+	/**
+	 * @inheritdocs
+	 */
+ 	function Optimize($connection,$table) 
+	{
+		if ($table) throw new DatabaseException("SQLite optimization is database-wide.  Call Optimize() with a blank/null table arg to use this method.");
+		$this->Execute($connection,"VACUUM");
+	}
+	
+}
+
+?>

+ 21 - 0
phreeze/libs/verysimple/DB/DatabaseConfig.php

@@ -0,0 +1,21 @@
+<?php 
+/** @package verysimple::DB::DataDriver */
+
+
+/**
+ * A static class that holds global database configuration options.
+ *
+ * @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 DatabaseConfig
+{	
+	/** @var boolean set to true to convert NULL values to empty string when inserting/updating */
+	public static $CONVERT_NULL_TO_EMPTYSTRING = true;
+
+}
+
+?>

+ 33 - 0
phreeze/libs/verysimple/DB/DatabaseException.php

@@ -0,0 +1,33 @@
+<?php
+/** @package    verysimple::DB */
+ 
+ /**
+ * DatabaseException is thrown when an error occurs that involves the database
+ * @package    verysimple::DB
+ * @author     VerySimple Inc.
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    1.0
+ */
+class DatabaseException extends Exception
+{
+	
+	/** codes used to determine error sub-type */
+	static $UNKNOWN = 0;
+	static $CONNECTION_ERROR = 1;
+	static $ERROR_IN_QUERY = 2;
+	
+	public $data;
+	
+    // Redefine the constructor so message isn't optional
+    public function __construct($message, $code = 0, $data = "") 
+	{
+        // make sure everything is assigned properly
+        parent::__construct($message, $code);
+        
+        $this->data = $data;
+    }
+
+}
+
+?>

+ 27 - 0
phreeze/libs/verysimple/DB/ISqlFunction.php

@@ -0,0 +1,27 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/** import supporting libraries */
+
+/**
+ * ISqlFunction is an interface that defines a SQL function.  This can be used
+ * to insert/update or query a database with a value that is not quoted,
+ * for example sysdate().
+
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc.
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    2.0
+ */
+interface ISqlFunction
+{
+	/**
+	 * Return the quoted SQL that will be used for the insert/update/select
+	 * @param Phreezer
+	 * @return string
+	 */
+	public function GetQuotedSql($phreezer);
+}
+
+?>

+ 239 - 0
phreeze/libs/verysimple/DB/Reflection/DBColumn.php

@@ -0,0 +1,239 @@
+<?php
+/** @package    verysimple::DB::Reflection */
+
+require_once('verysimple/Phreeze/FieldMap.php');
+
+/**
+ * DBcolumn is an object representation of column
+ * @package    verysimple::DB::Reflection
+ * @author Jason Hinkle
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version 1.0
+ */
+ class DBColumn
+{
+	public $Table;
+	public $Name;
+	public $Type;
+	public $Unsigned;
+	public $Size;
+	public $Key;
+	public $Null;
+	public $Default;
+	public $Extra;
+	public $Comment;
+	public $NameWithoutPrefix; // populated by DBTable if there is a prefix
+	public $MaxSize;
+	public $Keys = array();
+	public $Constraints = array();
+
+	/**
+	 * Instantiate new DBColumn
+	 *
+	 * @access public
+	 * @param DBTable $table
+	 * @param Array $row result from "describe table" statement
+	 */
+	function __construct($table, $row)
+	{
+		// typical type is something like varchar(40)
+		$typesize = explode("(",$row["Type"]);
+
+		$tmp = isset($typesize[1]) ? str_replace(")","", $typesize[1]) : "" ;
+		$sizesign = explode(" ", $tmp);
+
+		$this->Table =& $table;
+		$this->Name = $row["Field"];
+		$this->NameWithoutPrefix = $row["Field"];
+		$this->Type = $typesize[0];
+		$this->Unsigned = isset($sizesign[1]);
+		$this->Null = $row["Null"];
+		$this->Key = $row["Key"];
+		$this->Default = $row["Default"];
+		$this->Extra = $row["Extra"];
+
+		// enums are a little different because they contain a list of legal values instead of a size limit
+		if ($this->IsEnum())
+		{
+			// enum size is in the format 'val1','val2',...
+			$this->Size = explode("','", substr($sizesign[0],1,-1) );
+			$this->MaxSize = 0;
+		}
+		else
+		{
+			$this->Size = $sizesign[0];
+			// size may be saved for decimals as "n,n" so we need to convert that to an int
+			$tmp = explode(",",$this->Size);
+			$this->MaxSize = count($tmp) > 1 ? ($tmp[0] + $tmp[1]) : $this->Size;
+		}
+		
+		// if ($this->Key == "MUL") print " ########################## " . print_r($row,1) . " ########################## ";
+	}
+	
+	/**
+	 * Return true if this column is an enum type
+	 * @return boolean
+	 */
+	function IsEnum()
+	{
+		return $this->Type == 'enum';
+	}
+	
+	/**
+	 * Return the enum values if this column type is an enum
+	 * @return array
+	 */
+	function GetEnumValues()
+	{
+		return $this->IsEnum() ? $this->Size : array();
+	}
+	
+	/**
+	 * Return the Phreeze column constant that most closely matches this column type
+	 * @return string
+	 */
+	function GetPhreezeType()
+	{
+		return FieldMap::GetConstantFromType($this->Type);
+	}
+
+	/**
+	 * Return the PHP variable type that most closely matches this column type
+	 * @return string
+	 */
+	function GetPhpType()
+	{
+		$rt = $this->Type;
+		switch ($this->Type)
+		{
+			case "smallint":
+			case "bigint":
+			case "tinyint":
+			case "mediumint":
+				$rt = "int";
+				break;
+			case "varchar":
+			case "text":
+			case "tinytext":
+				$rt = "string";
+				break;
+			case "date":
+			case "datetime":
+				$rt = "date";
+				break;
+			case "decimal":
+			case "float":
+				$rt = "float";
+				break;
+			default;
+				break;
+		}
+
+		return $rt;
+	}
+
+	/**
+	 * Get the SQLite type that most closely matches this column type
+	 * @return string
+	 */
+	function GetSqliteType()
+	{
+		$rt = $this->Type;
+		switch ($this->Type)
+		{
+			case "int":
+				$rt = "integer";
+				break;
+			case "smallint":
+				$rt = "integer";
+				break;
+			case "tinyint":
+				$rt = "integer";
+				break;
+			case "varchar":
+				$rt = "text";
+				break;
+			case "text":
+				$rt = "text";
+				break;
+			case "tinytext":
+				$rt = "text";
+				break;
+			case "date":
+				$rt = "datetime";
+				break;
+			case "datetime":
+				$rt = "datetime";
+				break;
+			case "mediumint":
+				$rt = "integer";
+				break;
+			case "bigint":
+				$rt = "integer";
+				break;
+			case "decimal":
+				$rt = "real";
+				break;
+			case "float":
+				$rt = "real";
+				break;
+			default;
+				break;
+		}
+
+		return $rt;
+	}
+
+	/**
+	 * Return the AS3/Flex type that most closely matches this column type
+	 * @return string
+	 */
+	function GetFlexType()
+	{
+		$rt = $this->Type;
+		switch ($this->Type)
+		{
+			case "int":
+				$rt = "int";
+				break;
+			case "smallint":
+				$rt = "int";
+				break;
+			case "tinyint":
+				$rt = $this->MaxSize > 1 ? "int" : "Boolean";
+				break;
+			case "varchar":
+				$rt = "String";
+				break;
+			case "text":
+				$rt = "String";
+				break;
+			case "tinytext":
+				$rt = "String";
+				break;
+			case "datetime":
+				$rt = "Date";
+				break;
+			case "mediumint":
+				$rt = "int";
+				break;
+			case "bigint":
+				$rt = "int";
+				break;
+			case "decimal":
+				$rt = "Number";
+				break;
+			case "float":
+				$rt = "Number";
+				break;
+			default;
+				break;
+		}
+
+		return $rt;
+	}
+
+}
+
+?>

+ 209 - 0
phreeze/libs/verysimple/DB/Reflection/DBConnection.php

@@ -0,0 +1,209 @@
+<?php
+/** @package    verysimple::DB::Reflection */
+
+/** import supporting libraries */
+require_once("DBEventHandler.php");
+require_once("DBConnectionString.php");
+require_once('verysimple/DB/DatabaseException.php');
+
+/**
+ * DBConnection provides connectivity to a MySQL Server
+ *
+ * @package    verysimple::DB::Reflection
+ * @author Jason Hinkle
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version 1.0
+ */
+class DBConnection
+{
+	public $Host;
+	public $Port;
+	public $Username;
+	public $Password;
+	public $DBName;
+
+	private $dbconn;
+	private $handler;
+	private $dbopen;
+
+	/**
+	 * Instantiate new DBConnection
+	 *
+	 * @access public
+	 * @param DBConnectionString $host
+	 * @param DBEventHandler $port
+	 */
+	function __construct($dbconnstring, $handler = null)
+	{
+
+        $this->dbopen = false;
+		$this->Host = $dbconnstring->Host;
+		$this->Port = $dbconnstring->Port;
+		$this->Username = $dbconnstring->Username;
+		$this->Password = $dbconnstring->Password;
+		$this->DBName = $dbconnstring->DBName;
+
+		if ($handler)
+		{
+			$this->handler =& $handler;
+		}
+		else
+		{
+			$this->handler = new DBEventHandler();
+		}
+
+		$this->handler->Log(DBH_LOG_INFO, "Connection Initialized");
+	}
+
+    /**
+     * Destructor closes the db connection.
+     *
+     * @access     public
+     */
+	function __destruct()
+    {
+        $this->Disconnect();
+    }
+
+    /**
+	 * Opens a connection to the MySQL Server and selects the specified database
+	 *
+	 * @access public
+	 * @param string $dbname
+	 */
+	function Connect()
+	{
+		$this->handler->Log(DBH_LOG_INFO, "Opening Connection...");
+		if ($this->dbopen)
+		{
+			$this->handler->Log(DBH_LOG_WARNING, "Connection Already Open");
+		}
+		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());
+			}
+
+			if (!mysql_select_db($this->DBName, $this->dbconn))
+			{
+				$this->handler->Crash(DatabaseException::$CONNECTION_ERROR,"Unable to select database " . $this->DBName);
+			}
+
+			$this->handler->Log(DBH_LOG_INFO, "Connection Open");
+			$this->dbopen = true;
+		}
+	}
+
+    /**
+	 * Checks that the connection is open and if not, crashes
+	 *
+	 * @access public
+	 * @param bool $auto Automatically try to connect if connection isn't already open
+	 */
+	private function RequireConnection($auto = false)
+	{
+		if (!$this->dbopen)
+		{
+			if ($auto)
+			{
+				$this->Connect();
+			}
+			else
+			{
+				$this->handler->Crash(DatabaseException::$CONNECTION_ERROR, "DB is not connected.  Please call DBConnection->Connect() first.");
+			}
+		}
+	}
+
+	/**
+	 * Closing the connection to the MySQL Server
+	 *
+	 * @access public
+	 */
+	function Disconnect()
+	{
+		$this->handler->Log(DBH_LOG_INFO, "Closing Connection...");
+
+		if ($this->dbopen)
+		{
+			mysql_close($this->dbconn);
+			$this->dbopen = false;
+			$this->handler->Log(DBH_LOG_INFO, "Connection closed");
+		}
+		else
+		{
+			$this->handler->Log(DBH_LOG_WARNING, "Connection Already Closed");
+		}
+	}
+
+	/**
+	 * Executes a SQL select statement and returns a MySQL resultset
+	 *
+	 * @access public
+	 * @param string $sql
+	 * @return mysql_query
+	 */
+	function Select($sql)
+	{
+		$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;
+	}
+
+	/**
+	 * Executes a SQL query that does not return a resultset
+	 *
+	 * @access public
+	 * @param string $sql
+	 */
+	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());
+		}
+	}
+
+	/**
+	 * Moves the database curser forward and returns the current row as an associative array
+	 *
+	 * @access public
+	 * @param mysql_query $rs
+	 * @return Array
+	 */
+	function Next($rs)
+	{
+		$this->RequireConnection();
+
+		$this->handler->Log(DBH_LOG_DEBUG, "Fetching next result as array");
+		return mysql_fetch_assoc($rs);
+	}
+
+	/**
+	 * Releases the resources for the given resultset
+	 *
+	 * @access public
+	 * @param mysql_query $rs
+	 */
+	function Release($rs)
+	{
+		$this->RequireConnection();
+
+		$this->handler->Log(DBH_LOG_DEBUG, "Releasing result resources");
+		mysql_free_result($rs);
+	}
+
+}
+
+?>

+ 42 - 0
phreeze/libs/verysimple/DB/Reflection/DBConnectionString.php

@@ -0,0 +1,42 @@
+<?php
+/** @package    verysimple::DB::Reflection */
+
+/**
+ * DBConnectionString specifies the connection information
+ *
+ * @package    verysimple::DB::Reflection
+ * @author Jason Hinkle
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version 1.0
+ */
+ class DBConnectionString
+{
+	public $Host;
+	public $Port;
+	public $Username;
+	public $Password;
+	public $DBName;
+	
+	/**
+	 * Create a new instance of a DBConnectionString
+	 *
+	 * @access public
+	 * @param string $host
+	 * @param string $port
+	 * @param string $username
+	 * @param string $password	 
+	 * @param string $$dbname	 
+	 */
+	function __construct($host = "", $port = "", $username = "", $password = "", $dbname = "")
+	{
+		$this->Host = $host;
+		$this->Port = $port;
+		$this->Username = $username;
+		$this->Password = $password;
+		$this->DBName = $dbname;
+	}
+	
+}
+
+?>

+ 58 - 0
phreeze/libs/verysimple/DB/Reflection/DBConstraint.php

@@ -0,0 +1,58 @@
+<?php
+/** @package    verysimple::DB::Reflection */
+
+/**
+ * DBSet is an object representation of foreign key constraint
+ *
+ * @package    verysimple::DB::Reflection
+ * @author Jason Hinkle
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version 1.0
+ */
+ class DBConstraint
+{
+	public $Table;
+	public $Name;
+	public $KeyColumn;
+	public $ReferenceTable;
+	public $ReferenceTableName;
+	public $ReferenceKeyColumn;
+	
+	public $NameNoPrefix;
+	public $KeyColumnNoPrefix;
+	public $ReferenceKeyColumnNoPrefix;
+	
+	public $GetterName;
+	
+	/**
+	 * Instantiate new DBConstraint
+	 *
+	 * @access public
+	 * @param DBTable $table that is the dependent/child table
+	 * @param Array $row array that is result from parsing show create table	
+	 */	
+	function __construct($table, $row)
+	{
+		$this->Table =& $table;
+		
+		$this->Name = $row[0];
+		$this->KeyColumn = $row[1];
+		$this->ReferenceTableName = $row[2];
+		$this->ReferenceKeyColumn = $row[3];
+
+		$this->ReferenceTable = $this->Table->Schema->Tables[$this->ReferenceTableName];
+		// print "<p><b>" . $this->Table->Name . " constraint references " . $reftable->Name . "</b></p>";
+
+		$this->NameNoPrefix = $this->Table->RemovePrefix($this->Name);
+		$this->KeyColumnNoPrefix = $this->Table->RemovePrefix($this->KeyColumn);
+		$this->ReferenceKeyColumnNoPrefix = $this->ReferenceTable->RemovePrefix($this->ReferenceKeyColumn);
+
+		// intelligently decide what a good name for this constraint might be
+		$tmp1 = str_replace("__","_",str_replace($this->ReferenceTableName,"",str_replace("_id","", $this->KeyColumnNoPrefix)) . "_");
+		$tmp2 = $this->ReferenceTableName;
+		$this->GetterName = ($tmp1 == "_") ? $tmp2 : ($tmp1 . $tmp2);
+	}
+}
+
+?>

+ 80 - 0
phreeze/libs/verysimple/DB/Reflection/DBEventHandler.php

@@ -0,0 +1,80 @@
+<?php
+/** @package    verysimple::DB::Reflection */
+
+/** import supporting libraries */
+
+require_once('verysimple/DB/DatabaseException.php');
+
+define("DBH_LOG_NONE",1);
+define("DBH_LOG_INFO",2);
+define("DBH_LOG_DEBUG",4);
+define("DBH_LOG_QUERY",8);
+define("DBH_LOG_WARNING",16);
+define("DBH_LOG_ERROR",32);
+ 
+/**
+ * DBEventHandler is an optional parameter that can be used to hook into events in the 
+ * DAO system for intercepting, debugging and observing
+ *
+ * @package    verysimple::DB::Reflection
+ * @author Jason Hinkle
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version 1.0
+ */
+ class DBEventHandler
+{
+	public $LogLevel;
+	
+	function __construct($level = DBH_LOG_NONE)
+	{
+		$this->LogLevel = $level;
+	}
+	
+	/**
+	 * Called by DB objects to report logging information
+	 *
+	 * @access public
+	 * @param int $level
+	 * @param string $message
+	 * @param string $data
+	 */	
+	function Log($level,$message, $data = "")
+	{
+		$data = $data != "" ? ": $data" : "";
+		switch ($level)
+		{
+			case DBH_LOG_DEBUG:
+				if ($this->LogLevel & DBH_LOG_DEBUG) print "<pre style='color: silver;'>$message</pre>\r\n";
+				break;
+			case DBH_LOG_INFO:
+				if ($this->LogLevel & DBH_LOG_INFO) print "<pre style='color: blue;'>$message $data</pre>\r\n";
+				break;
+			case DBH_LOG_QUERY:
+				if ($this->LogLevel & DBH_LOG_QUERY) print "<pre style='color: green;'>$message $data</pre>\r\n";
+				break;
+			case DBH_LOG_WARNING:
+				if ($this->LogLevel & DBH_LOG_WARNING) print "<pre style='color: orange;'>$message $data</pre>\r\n";
+				break;
+			case DBH_LOG_ERROR:
+				if ($this->LogLevel & DBH_LOG_ERROR) print "<pre style='color: red;'>$message $data</pre>\r\n";
+				break;
+		}
+		
+	}
+	
+	/**
+	 * Called by DB objects when a critical error occurs
+	 *
+	 * @access public
+	 * @param int $code unique numerical identifier for error
+	 * @param string $message human-readable error
+	 * @param string $data any additional information that may help with debugging
+	 */	
+	function Crash($code, $message = "", $data = "")
+	{
+		throw new DatabaseException($message,$code,$data);
+	}
+}
+
+?>

+ 42 - 0
phreeze/libs/verysimple/DB/Reflection/DBKey.php

@@ -0,0 +1,42 @@
+<?php
+/** @package    verysimple::DB::Reflection */
+
+/**
+ * DBSet is an object representation of foreign key
+ *
+ * @package    verysimple::DB::Reflection
+ * @author Jason Hinkle
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version 1.0
+ */
+class DBKey
+{
+	public $Table;
+	public $Name;
+	public $NameNoPrefix;
+	public $GetterName;
+	public $KeyColumn;
+	public $KeyComment;
+
+	/**
+	 * Instantiate new DBSet
+	 *
+	 * @access public
+	 * @param DBTable $table that is the dependent/child table
+	 * @param string $keyname	
+	 * @param string $columnname	
+	 */	
+	function __construct($table, $keyname, $columnname)
+	{
+		$this->Table =& $table;
+		$this->Name = $keyname;
+		$this->KeyColumn = str_replace("`","", $columnname);
+		$this->KeyComment = $this->Table->Columns[$this->KeyColumn]->Comment;
+
+		$this->NameNoPrefix = $this->Table->RemovePrefix($this->Name);
+		$this->GetterName = $this->NameNoPrefix;
+	}
+}
+
+?>

+ 78 - 0
phreeze/libs/verysimple/DB/Reflection/DBSchema.php

@@ -0,0 +1,78 @@
+<?php
+/** @package    verysimple::DB::Reflection */
+
+/** import supporting libraries */
+require_once("DBTable.php");
+
+/**
+ * DBSchema is an object representation of a MySQL Schema/Database
+ *
+ * @package    verysimple::DB::Reflection
+ * @author Jason Hinkle
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version 1.0
+ */
+class DBSchema
+{
+	public $Server;
+	public $Name;
+	public $Tables;
+	
+	/**
+	 * Instantiate new DBSchema
+	 *
+	 * @access public
+	 * @param DBServer $server
+	 * @param string $name name of schema to parse	
+	 */	
+	function __construct($server)
+	{
+		$this->Server =& $server;
+		$this->Name = $server->Connection->DBName;
+		$this->Tables = Array();
+		
+		$this->Load();
+		
+		// print "<pre>"; print_r($this->Tables["ticket"]); die();
+	}
+
+	/**
+	 * Inspects the current schema and loads all tables, keys, etc.
+	 *
+	 * @access public
+	 */	
+	private function Load()
+	{
+		$sql = "show tables";
+		$rs = $this->Server->Connection->Select($sql);
+		
+		// first pass load all the tables.  this will initialize each object.  we have to
+		// do this first so that we can correctly determine and store "Set" information
+		while ($row = $this->Server->Connection->Next($rs))
+		{
+			$this->Tables[$row["Tables_in_" . $this->Name]] = new DBTable($this,$row);
+		}
+		
+		// now load all the keys and constraints for each table
+		foreach ($this->Tables as $table)
+		{
+			$table->LoadKeys();
+		}
+
+		$this->Server->Connection->Release($rs);
+		
+		$sql = "show table status from `" . $this->Name . "`";
+		$rs2 = $this->Server->Connection->Select($sql);
+		
+		// load the extra data
+		while ($row = $this->Server->Connection->Next($rs2))
+		{
+			$this->Tables[$row["Name"]]->Engine = $row["Engine"]; 
+			$this->Tables[$row["Name"]]->Comment = $row["Comment"];
+		}
+		$this->Server->Connection->Release($rs2);
+	}
+}
+
+?>

+ 54 - 0
phreeze/libs/verysimple/DB/Reflection/DBServer.php

@@ -0,0 +1,54 @@
+<?php
+/** @package    verysimple::DB::Reflection */
+
+/** import supporting libraries */
+require_once("DBConnection.php");
+require_once("DBSchema.php");
+
+/**
+ * DBServer is an object representation of a MySQL Server
+ *
+ * @package    verysimple::DB::Reflection
+ * @author Jason Hinkle
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version 1.0
+ */
+class DBServer
+{
+	public $Connection;
+	public $SchemaName;
+	
+	/**
+	 * Instantiate new DBServer
+	 *
+	 * @access public
+	 * @param DBConnection $connection
+	 */	
+	function __construct($connection)
+	{
+		$this->Connection =& $connection;
+	}
+	
+
+	/**
+	 * Return the schema with the given name from this server
+	 *
+	 * @access public
+	 * @param string $name
+	 * @return DBSchema	
+	 */	
+	function GetSchema()
+	{
+		$this->Connection->Connect();
+		
+		$schema = new DBSchema($this);
+		
+		$this->Connection->Disconnect();
+		
+		return $schema;
+	}
+
+}
+
+?>

+ 67 - 0
phreeze/libs/verysimple/DB/Reflection/DBSet.php

@@ -0,0 +1,67 @@
+<?php
+/** @package    verysimple::DB::Reflection */
+
+/**
+ * DBSet is an object representation of table dependency relationship
+ *
+ * @package    verysimple::DB::Reflection
+ * @author Jason Hinkle
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version 1.0
+ */
+class DBSet
+{
+	public $Table;
+	public $Name;
+	public $KeyColumn;
+	public $KeyComment;
+	public $SetTableName;
+	public $SetKeyColumn;
+	public $SetKeyComment;
+
+	public $NameNoPrefix;
+	
+	public $KeyColumnNoPrefix;
+	public $SetKeyColumnNoPrefix;
+	public $SetPrimaryKey;
+	public $SetPrimaryKeyNoPrefix;
+	
+	public $GetterName;
+	
+	/**
+	 * Instantiate new DBSet
+	 *
+	 * @access public
+	 * @param DBTable $table that is the dependent/child table
+	 * @param Array $row array that is result from parsing show create table	
+	 */	
+	function __construct($table, $row)
+	{
+		$this->Table =& $table->Schema->Tables[$row[2]];
+		
+		$this->Name = $row[0];
+		$this->KeyColumn = $row[3];
+		$this->KeyComment = $this->Table->Columns[$this->KeyColumn]->Comment;
+		$this->SetTableName = $table->Name;
+		$this->SetKeyColumn = $row[1];
+		$this->SetKeyComment = $table->Columns[$this->SetKeyColumn]->Comment;
+		
+		$reftable = $this->Table->Schema->Tables[$this->SetTableName];
+		// print "<p><b>" . $this->Table->Name . " set references " . $reftable->Name . "</b></p>";
+		
+		$this->SetPrimaryKey = $reftable->GetPrimaryKeyName(false);
+		
+		$this->NameNoPrefix = $this->Table->RemovePrefix($this->Name);
+		$this->KeyColumnNoPrefix = $this->Table->RemovePrefix($this->KeyColumn);
+		$this->SetKeyColumnNoPrefix = $reftable->RemovePrefix($this->SetKeyColumn);
+		$this->SetPrimaryKeyNoPrefix = $reftable->RemovePrefix($this->SetPrimaryKey);
+		
+		// intelligently decide what a good name for this set would be
+		$tmp1 = str_replace("__","_",str_replace($this->Table->Name,"", str_replace("_id","", $this->SetKeyColumnNoPrefix)) . "_");
+		$tmp2 = $this->SetTableName . "s";
+		$this->GetterName = ($tmp1 == "_") ? $tmp2 : ($tmp1 . $tmp2);
+	}
+}
+
+?>

+ 303 - 0
phreeze/libs/verysimple/DB/Reflection/DBTable.php

@@ -0,0 +1,303 @@
+<?php
+/** @package    verysimple::DB::Reflection */
+
+/** import supporting libraries */
+require_once("DBColumn.php");
+require_once("DBConstraint.php");
+require_once("DBSet.php");
+require_once("DBKey.php");
+
+/**
+ * DBTable is an object representation of a MySQL Table
+ *
+ * @package    verysimple::DB::Reflection
+ * @author Jason Hinkle
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version 1.0
+ */
+class DBTable
+{
+	public $Schema;
+	public $Name;
+	public $Engine;
+	public $Comment;
+	public $DefaultCharacterSet;
+	public $Columns;
+	public $ColumnPrefix;
+	public $PrimaryKeys;
+	public $ForeignKeys;
+	public $Constraints;
+	public $Sets;
+	public $IsView = false;
+
+	/**
+	 * Instantiate new DBTable
+	 *
+	 * @access public
+	 * @param DBSchema $schema
+	 * @return Array $row array that is result from "show tables" statement
+	 */
+	function __construct($schema, $row)
+	{
+		$this->Schema = $schema;
+		$this->Name = $row["Tables_in_" . $this->Schema->Name];
+		$this->Columns = Array();
+		$this->PrimaryKeys = Array();
+		$this->ForeignKeys = Array();
+		$this->Constraints = Array();
+		$this->Sets = Array();
+
+		$this->LoadColumns();
+		$this->DiscoverColumnPrefix();
+
+
+	}
+
+	/**
+	 * Returns the number of columns involved in the primary key
+	 * @return number
+	 */
+	function NumberOfPrimaryKeyColumns()
+	{
+		return count($this->PrimaryKeys);
+	}
+
+	/**
+	 * Returns name of the primary key
+	 * TODO: If there are multiple keys, this is no accurate.  Only returns the first key found
+	 *
+	 * @access public
+	 * @param bool $remove_prefix
+	 * @return string
+	 */
+	function GetPrimaryKeyName($remove_prefix = true)
+	{
+		foreach ($this->PrimaryKeys as $key)
+		{
+			return ($remove_prefix) ? $this->RemovePrefix($key->KeyColumn) : $key->KeyColumn;
+		}
+		
+		// views don't technically have a primary key but we will return the first column if anybody asks
+		if ($this->IsView) return $this->GetColumnNameByIndex(0,$remove_prefix);
+	}
+	
+	/**
+	 * Returns the name of the column at the given index
+	 *
+	 * @access public
+	 * @param int $index (zero based)
+	 * @param bool $remove_prefix
+	 * @return string
+	 */
+	function GetColumnNameByIndex($index, $remove_prefix = true)
+	{
+		$count = 0;
+		foreach ($this->Columns as $column)
+		{
+			if ($count == $index) return ($remove_prefix) ? $column->NameWithoutPrefix : $column->Name;
+		}
+		
+		throw new Exception('Index out of bounds');
+	}
+
+	/**
+	 * Returns true if the primary key for this table is an auto_increment
+	 * TODO: Only checks the first key if there are multiple primary keys
+	 *
+	 * @access public
+	 * @return bool
+	 */
+	function PrimaryKeyIsAutoIncrement()
+	{
+		$pk = $this->GetPrimaryKeyName(false);
+		return $pk && $this->Columns[$pk]->Extra == "auto_increment";
+	}
+
+	/**
+	 * Returns name of the first varchar field which could be used as a "label"
+	 *
+	 * @access public
+	 * @return string
+	 */
+	function GetDescriptorName($remove_prefix = true)
+	{
+		foreach ($this->Columns as $column)
+		{
+			if ($column->Type == "varchar")
+			{
+				return ($remove_prefix) ? $this->RemovePrefix($column->Name) : $column->Name;
+			}
+		}
+
+		// give up because there are no varchars in this table
+		return $this->GetPrimaryKeyName($remove_prefix);
+	}
+
+	/**
+	 * Inspects all columns to see if there is a common prefix in the format: XXX_
+	 *
+	 * @access private
+	 */
+	private function DiscoverColumnPrefix()
+	{
+		$prev_prefix = "";
+		$has_prefix = true;
+		foreach ($this->Columns as $column)
+		{
+			$curr_prefix = substr($column->Name, 0, strpos($column->Name,"_")+1);
+
+			if ($prev_prefix == "")
+			{
+				// first time through the loop
+				$prev_prefix = $curr_prefix ? $curr_prefix : "#NONE#";
+			}
+			elseif ($prev_prefix != $curr_prefix)
+			{
+				$has_prefix = false;
+			}
+		}
+
+		if ($has_prefix)
+		{
+			// set the table column prefix property
+			$this->ColumnPrefix = $curr_prefix;
+
+			// update the columns to reflect the prefix as well
+			foreach ($this->Columns as $column)
+			{
+				$column->NameWithoutPrefix = substr($column->Name,strlen($curr_prefix));
+			}
+		}
+	}
+
+	/**Given a column name, removes the prefix
+	 *
+	 * @access private
+	 */
+	public function RemovePrefix($name)
+	{
+		// print "remove prefix $name: " . $this->ColumnPrefix . "<br>";
+		return substr($name,strlen($this->ColumnPrefix));
+	}
+
+
+	/**
+	 * Inspects the current table and loads all Columns
+	 *
+	 * @access private
+	 */
+	private function LoadColumns()
+	{
+		// get the colums
+		$sql = "describe `".$this->Name."`";
+
+		$rs = $this->Schema->Server->Connection->Select($sql);
+
+		while ($row = $this->Schema->Server->Connection->Next($rs))
+		{
+			$this->Columns[$row["Field"]] = new DBColumn($this,$row);
+		}
+
+		$this->Schema->Server->Connection->Release($rs);
+	}
+
+	/**
+	 * Load the keys and constraints for this table and populate the sets for
+	 * all tables on which this table is dependents
+	 *
+	 * @access public
+	 */
+	public function LoadKeys()
+	{
+
+		// get the keys and constraints
+		$sql = "show create table `".$this->Name."`";
+
+		$create_table = "";
+
+		$rs = $this->Schema->Server->Connection->Select($sql);
+
+		if ($row = $this->Schema->Server->Connection->Next($rs))
+		{
+			if (isset($row["Create Table"]))
+			{
+				$create_table = $row["Create Table"];
+			}
+			else if (isset($row["Create View"]))
+			{
+				$this->IsView = true;
+				$create_table = $row["Create View"];
+				
+				// treat the 1st column in a view as the primary key
+				$this->Columns[$this->GetColumnNameByIndex(0,false)]->Key = 'PRI';
+			}
+			else
+			{
+				throw new Exception("Unknown Table Type");
+			}
+		}
+
+		$this->Schema->Server->Connection->Release($rs);
+
+		$lines = explode("\n", $create_table);
+
+		foreach ($lines as $line)
+		{
+			$line = trim($line);
+			if (substr($line,0,11) == "PRIMARY KEY")
+			{
+				preg_match_all("/`(\w+)`/",$line, $matches, PREG_PATTERN_ORDER);
+				// print "<pre>";  print_r($matches);  die(); // DEBUG
+				$this->PrimaryKeys[$matches[1][0]] = new DBKey($this,"PRIMARY KEY",$matches[0][0]);
+			}
+			elseif (substr($line,0,3) == "KEY")
+			{
+				preg_match_all("/`(\w+)`/",$line, $matches, PREG_PATTERN_ORDER);
+				// print "<pre>";  print_r($matches);  die(); // DEBUG
+				$this->ForeignKeys[$matches[1][0]] = new DBKey($this,$matches[1][0],$matches[1][1]);
+
+				// Add keys to the column for convenience
+				$this->Columns[$matches[1][1]]->Keys[] = $matches[1][0];
+			}
+			elseif (substr($line,0,10) == "CONSTRAINT")
+			{
+				preg_match_all("/`(\w+)`/",$line, $matches, PREG_PATTERN_ORDER);
+				//print "<pre>";  print_r($matches);  die(); // DEBUG
+				$this->Constraints[$matches[1][0]] = new DBConstraint($this,$matches[1]);
+
+				// the set is basically the reverse of the constraint, but we want to add it to the
+				// constraining table so we don't have to do reverse-lookup looking for child relationships
+				$this->Schema->Tables[$matches[1][2]]->Sets[$matches[1][0]] = new DBSet($this,$matches[1]);
+
+				//print "<pre>##########################\r\n" . print_r($matches,1) . "\r\n##########################\r\n";
+
+				// Add constraints to the column for convenience
+				$this->Columns[$matches[1][1]]->Constraints[] = $matches[1][0];
+			}
+			elseif ( strstr( $line, "COMMENT ") )
+			{
+				// TODO: this is pretty fragile... ?
+				// table comments and column comments are seemingly differentiated by "COMMENT="  vs "COMMENT "
+				$parts = explode("`",$line);
+				$column = $parts[1];
+				$comment = strstr( $line, "COMMENT ");
+				$comment = substr($comment,9,strlen($comment)-11);
+				$comment = str_replace("''","'",$comment);
+				$this->Columns[$column]->Comment = $comment;
+
+				if ($this->Columns[$column]->Default == "" && substr($comment,0,8) == "default=")
+				{
+					$this->Columns[$column]->Default = substr($comment,9, strlen($comment)-10 );
+				}
+
+				// print "<pre>" . $column . "=" . htmlspecialchars( $this->Columns[$column]->Default );
+
+			}
+			// TODO: look for COMMENT
+		}
+	}
+
+}
+
+?>

+ 144 - 0
phreeze/libs/verysimple/HTTP/BrowserDevice.php

@@ -0,0 +1,144 @@
+<?php
+/** @package    verysimple::HTTP */
+
+/**
+ * BrowserDevice represents a device used when browsing (or executing)
+ * the current code.  This is a Singleton pattern, cannot be 
+ * instantiated directly.
+ * 
+ * TODO: this only has minimal support and basically only supplies
+ * information about whether the device is mobile or not, and if so
+ * it supplies the major vendor name.
+ * 
+ * Usage:
+ * $browser = BrowserDevice::GetInstance();
+ * if ($device->IsMobile()) dosomething();
+ *
+ * @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 BrowserDevice
+{
+
+	/**
+	 * patters to search for devices
+	 * @var Array
+	 */
+	static $DESKTOP_DEVICE_PATTERNS = Array(
+		'AdobeAIR'=>'AdobeAIR',
+		'chrome'=>'google',
+		'firefox'=>'mozilla',
+		'MSIE'=>'microsoft',
+		'safari'=>'apple'
+	);
+	
+	/**
+	 * patters to search for devices
+	 * @var Array
+	 */
+	static $MOBILE_DEVICE_PATTERNS = Array(
+		'(ipad|ipod|iphone)'=>'apple',
+		'android'=>'android',
+		'opera mini'=>'opera',
+		'blackberry'=>'blackberry',
+		'(pre\/|palm os|palm|hiptop|avantgo|plucker|xiino|blazer|elaine)'=>'palm',
+		'(iris|3g_t|windows ce|opera mobi|windows ce; smartphone;|windows ce; iemobile)'=>'windows',
+		'(mini 9.5|vx1000|lge |m800|e860|u940|ux840|compal|wireless| mobi|ahong|lg380|lgku|lgu900|lg210|lg47|lg920|lg840|lg370|sam-r|mg50|s55|g83|t66|vx400|mk99|d615|d763|el370|sl900|mp500|samu3|samu4|vx10|xda_|samu5|samu6|samu7|samu9|a615|b832|m881|s920|n210|s700|c-810|_h797|mob-x|sk16d|848b|mowser|s580|r800|471x|v120|rim8|c500foma:|160x|x160|480x|x640|t503|w839|i250|sprint|w398samr810|m5252|c7100|mt126|x225|s5330|s820|htil-g1|fly v71|s302|-x113|novarra|k610i|-three|8325rc|8352rc|sanyo|vx54|c888|nx250|n120|mtk |c5588|s710|t880|c5005|i;458x|p404i|s210|c5100|teleca|s940|c500|s590|foma|samsu|vx8|vx9|a1000|_mms|myx|a700|gu1100|bc831|e300|ems100|me701|me702m-three|sd588|s800|8325rc|ac831|mw200|brew |d88|htc\/|htc_touch|355x|m50|km100|d736|p-9521|telco|sl74|ktouch|m4u\/|me702|8325rc|kddi|phone|lg |sonyericsson|samsung|240x|x320|vx10|nokia|sony cmd|motorola|up.browser|up.link|mmp|symbian|smartphone|midp|wap|vodafone|o2|pocket|kindle|mobile|psp|treo)'=>'unknown'
+	);
+	
+    private static $instance;
+    
+    public $UserAgent;
+    public $IsWAP;
+    public $IsMobile;
+    public $IsTablet;
+	public $IsConsole;
+    public $Vendor;
+	public $SupportsJS;
+	public $SupportsCSS;
+	public $IsNativeDevice;
+	public $NativeClientVersion;
+   
+    /**
+     * Private constructor enforces the Singleton pattern
+     */
+    private function __construct() 
+    {
+    	// do nothing
+    }
+    
+    /**
+     * Parse the user agent and detect the browser, populating
+     * all internal properties accordingly
+     */
+    public function Detect()
+    {
+    	// RESET DEFAULT VALUES
+    	$this->IsWAP = false;
+	    $this->IsMobile = false;
+	    $this->IsTablet = false;
+		$this->IsConsole = false;
+	    $this->Vendor = 'unknown';
+		$this->SupportsJS = true;
+		$this->SupportsCSS = true;
+	
+    	$this->UserAgent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : "";
+    	$wap = isset($_SERVER['HTTP_X_WAP_PROFILE']) ? $_SERVER['HTTP_X_WAP_PROFILE'] : "";
+    
+
+    	if (!$this->UserAgent)
+    	{
+    		$this->IsConsole = true;
+    	}
+    	else
+    	{
+    		foreach (BrowserDevice::$MOBILE_DEVICE_PATTERNS as $key => $val )
+    		{
+    			if (preg_match('/'.$key.'/i',$this->UserAgent))
+    			{
+    				$this->IsMobile = true;
+    				$this->Vendor = $val;
+    				break;
+    			}
+    		}
+				
+			if ($this->IsMobile == false) {
+				
+				foreach (BrowserDevice::$DESKTOP_DEVICE_PATTERNS as $key => $val )
+	    		{
+	    			if (preg_match('/'.$key.'/i',$this->UserAgent))
+	    			{
+	    				$this->Vendor = $val;
+	    				break;
+	    			}
+	    		}
+	
+			}
+
+
+    	}
+    }
+
+	/**
+	 * Returns an instance of a BrowserDevice populated based on the
+	 * server variables provided
+	 * @return BrowserDevice
+	 */
+    public static function GetInstance() 
+    {
+        if (!isset(self::$instance)) 
+        {
+            $c = __CLASS__;
+            self::$instance = new $c;
+            self::$instance->Detect();
+        }
+
+		return self::$instance;
+    }
+	
+}
+
+?>

+ 59 - 0
phreeze/libs/verysimple/HTTP/Context.php

@@ -0,0 +1,59 @@
+<?php
+/** @package    verysimple::HTTP */
+
+/**
+ * Context Persistance Storage
+ * 
+ * The context provides an object that can be uses globally for
+ * dependency injection when passing information that should be 
+ * available to an entire application
+ *
+ * @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 Context
+{
+	public $GUID;
+	
+	/**
+	 * Constructor initializes the session
+	 */
+	public function __construct()
+	{
+		if (session_id() == '')
+		{
+			@session_start();
+		}
+	}
+	
+	/**
+	 * Returns a persisted object or value
+	 *
+	 * @param var
+	 * @param default value (default = null)
+	 * @return value of var (or default)
+	 */
+	public function Get($var,$default = null)
+	{
+		return (isset($_SESSION[$this->GUID . "_" . $var])) ? unserialize($_SESSION[$this->GUID . "_" . $var]) : null;
+	}
+
+	/**
+	 * Persists an object or value
+	 *
+	 * @access public
+	 * @param var
+	 * @param value
+	 * @return object || null
+	 */
+	public function Set($var,$val)
+	{
+		$_SESSION[$this->GUID . "_" . $var] = serialize($val);
+	}
+
+}
+
+?>

+ 85 - 0
phreeze/libs/verysimple/HTTP/FileUpload.php

@@ -0,0 +1,85 @@
+<?php
+/** @package    verysimple::HTTP */
+
+/**
+ * Encapsulates a file upload.
+ *
+ * Utility class for dealing with file uploads and converting them into
+ * a string that is easily insertable into a database
+ *
+ * @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 FileUpload
+{
+	public $Name;
+	public $Size;
+	public $Type;
+	public $Extension;
+	public $Data;
+	
+	/**
+	 * Returns a file upload as xml that is ready to save to a file or database
+	 *
+	 * @param	string	$fieldname
+	 * @return	string
+	 */
+	public function ToXML($base64 = true)
+	{
+		return "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\r\n"
+			. "<file>\r\n"
+			. "<name>".$this->Name."</name>\r\n"
+			. "<size>".$this->Size."</size>\r\n"
+			. "<type>".$this->Type."</type>\r\n"
+			. "<Extension>".$this->Extension."</Extension>\r\n"
+			. "<encoding>" . ($base64 ? "base64" : "none") . "</encoding>\r\n"
+			. "<data>" . ($base64 ? base64_encode($this->Data) : $this->Data) . "</data>\r\n"
+			. "</file>";
+	}
+	
+	/**
+	 * Loads this FileUpload object from previously obtained from ToXML()
+	 *
+	 * @param	string	$xml
+	 */
+	public function FromXML($xml)
+	{
+		$sxo = new SimpleXMLElement($xml);
+		
+		if($sxo->encoding == "base64")
+		{
+			$this->Data = base64_decode($sxo->data);
+		}
+		else
+		{
+			$this->Data = $sxo->data;
+		}
+		
+		$this->Name = $attachment->name;
+		$this->Type = $attachment->type;
+		$this->Size = $attachment->size;
+		$this->Extension = $attachment->extension;
+	}
+	
+	/**
+	 * Saves the file upload to the given path
+	 * @param string $path full path to save directory (trailing slash required)
+	 * @param string $alternate_name (optional) if not provided, $this->Name is used
+	 * @param bool $chmod (default=true) set file permission to 666
+	 */
+	public function SaveTo($path, $alternate_name="", $chmod=true)
+	{
+		$name = $alternate_name ? $alternate_name : $this->Name;
+		
+		$fullpath = $path . $name;
+		$handle = fopen($fullpath, "w");
+		fwrite($handle, $this->Data);
+		fclose($handle);
+		
+		if ($chmod) @chmod($fullpath, 0666);
+	}
+}
+?>

+ 111 - 0
phreeze/libs/verysimple/HTTP/FormValidator.php

@@ -0,0 +1,111 @@
+<?php
+/** @package    verysimple::HTTP */
+
+/** import supporting libraries */
+
+
+/**
+ * Static utility class for validating form input
+ *
+ * Contains various methods for validating standard information such
+ * as email, dates, credit card numbers, etc
+ *
+ * @package    verysimple::HTTP 
+ * @author     VerySimple Inc.
+ * @copyright  1997-2008 VerySimple, Inc. http://www.verysimple.com
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    1.0
+ */
+class FormValidator
+{
+
+	/** 
+	 * Returns true if the provided email is valid
+	 * 
+	 * @param string email address
+	 * @return bool
+	 */
+	static function IsValidEmail($email)
+	{
+		return (
+			eregi("^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,3})$", $email)
+			);
+	}
+	
+	/** 
+	 * Returns true if the provided credit card appears to be valid.  If type is
+	 * provided, then the validation makes sure it is valid for that specific
+	 * type, otherwise it just makes sure it is valid for any type
+	 * 
+	 * @param string credit card number
+	 * @param string type [optional] (American, Dinners, Discover, Master, Visa)
+	 * @return bool
+	 */
+	static function IsValidCreditCard($cc_num, $type = "") 
+	{
+		
+		if($type == "American") {
+			$denum = "American Express";
+		} elseif($type == "Dinners") {
+			$denum = "Diner's Club";
+		} elseif($type == "Discover") {
+			$denum = "Discover";
+		} elseif($type == "Master") {
+			$denum = "Master Card";
+		} elseif($type == "Visa") {
+			$denum = "Visa";
+		}
+		
+		$verified = false;
+		
+		if($type == "American" || $type == "") {
+			$pattern = "/^([34|37]{2})([0-9]{13})$/";//American Express
+			if (preg_match($pattern,$cc_num)) {
+				$verified = true;
+			}
+			
+			
+		}
+		
+		if($type == "Dinners" || $type == "") {
+			$pattern = "/^([30|36|38]{2})([0-9]{12})$/";//Diner's Club
+			if (preg_match($pattern,$cc_num)) {
+				$verified = true;
+			}
+			
+			
+		}
+		
+		if($type == "Discover" || $type == "") {
+			$pattern = "/^([6011]{4})([0-9]{12})$/";//Discover Card
+			if (preg_match($pattern,$cc_num)) {
+				$verified = true;
+			}
+			
+			
+		}
+		
+		if($type == "Master" || $type == "") {
+			$pattern = "/^([51|52|53|54|55]{2})([0-9]{14})$/";//Mastercard
+			if (preg_match($pattern,$cc_num)) {
+				$verified = true;
+			}
+			
+			
+		} 
+		
+		if($type == "Visa" || $type == "") {
+			$pattern = "/^([4]{1})([0-9]{12,15})$/";//Visa
+			if (preg_match($pattern,$cc_num)) {
+				$verified = true;
+			}
+			
+		}
+		
+		return $verified;
+		
+	}
+	
+}
+
+?>

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

@@ -0,0 +1,342 @@
+<?php
+/** @package    verysimple::HTTP */
+
+/**
+ * HttpRequest is a utility method for makine HTTP requests
+ *
+ * @package    verysimple::HTTP
+ * @author     VerySimple Inc.
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    2.0
+ */
+class HttpRequest
+{
+
+	static $METHOD_GET = "GET";
+	static $METHOD_POST = "POST";
+	static $METHOD_PUT = "PUT";
+	static $METHOD_DELETE = "DELETE";
+
+	static $USER_AGENT = "verysimple::HttpRequest";
+	static $VERIFY_CERT = false;
+
+	/**
+	 *
+	 * @param $method
+	 * @param $params
+	 */
+	static function RestRequest($endpoint, $method, $params = Array())
+	{
+		$qs = HttpRequest::ArrayToQueryString($params);
+		$ch = null;
+
+		switch ($method)
+		{
+			case HttpRequest::$METHOD_GET:
+				$ch = curl_init($endpoint . ($qs ? "?" . $qs : ""));
+				break;
+			case HttpRequest::$METHOD_POST:
+				$ch = curl_init($endpoint);
+				curl_setopt($ch, CURLOPT_POST, 1);
+				curl_setopt($ch, CURLOPT_POSTFIELDS, $qs);
+				break;
+			case HttpRequest::$METHOD_PUT:
+				$ch = curl_init($endpoint);
+				// curl_setopt($ch, CURLOPT_PUT, 1); // <- this method requires CURLOPT_INFILE
+				curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
+				curl_setopt($ch, CURLOPT_POSTFIELDS, $qs);
+				break;
+			case HttpRequest::$METHOD_DELETE:
+				$ch = curl_init($endpoint);
+				curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
+				curl_setopt($ch, CURLOPT_POSTFIELDS, $qs);
+				break;
+		}
+
+		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
+		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+		curl_setopt($ch, CURLOPT_VERBOSE, 0); // <- ENABLE DEBUGGING
+		curl_setopt($ch, CURLOPT_USERAGENT, HttpRequest::$USER_AGENT);
+		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, HttpRequest::$VERIFY_CERT);
+		curl_setopt($ch, CURLOPT_NOPROGRESS, 1);
+
+		// make the request
+		$response = curl_exec ($ch);
+
+		// if error is not empty, then a network error occured
+		$error = curl_error($ch);
+		if ($error) {$response .= $error;}
+
+		curl_close ($ch);
+
+		return $response;
+	}
+
+	/**
+	* Make an HTTP POST request using the best method available on the server
+	*
+	* @param string $url
+	* @param array $data (array of field/value pairs)
+	* @param bool true to require verification of SSL cert
+	* @return string
+	*/
+	static function Post($url, $data, $verify_cert = false, $timeout = 30)
+	{
+		if (function_exists("curl_init"))
+		{
+			return HttpRequest::CurlPost($url, $data, $verify_cert, $timeout);
+		}
+		else
+		{
+			return HttpRequest::FilePost($url, $data, $verify_cert, $timeout);
+		}
+	}
+
+	/**
+	 * Make an HTTP GET request using the best method available on the server
+	 *
+	 * @param string $url
+	 * @param array $data (array of field/value pairs)
+	 * @param bool true to require verification of SSL cert
+	 * @return string
+	 */
+	static function Get($url, $data = "", $verify_cert = false, $timeout = 30)
+	{
+		if (function_exists("curl_init"))
+		{
+			return HttpRequest::CurlPost($url, $data, $verify_cert, $timeout);
+		}
+		else
+		{
+			return HttpRequest::FilePost($url, $data, $verify_cert, $timeout);
+		}
+	}
+
+	/**
+	* Make an HTTP PUT reequest using the best method available on the server
+	*
+	* @param string $url
+	* @param array $data (array of field/value pairs)
+	* @param bool true to require verification of SSL cert
+	* @param int timeout (in seconds)
+	* @return string
+	*/
+	static function Put($url, $data = "", $verify_cert = false, $timeout = 30)
+	{
+		if (function_exists("curl_init"))
+		{
+			return HttpRequest::CurlPut($url, $data, $verify_cert, $timeout);
+		}
+		else
+		{
+			throw new Exception('PUT request is not supported on systems without curl installed');
+		}
+	}
+
+	/**
+	 * Make an HTTP GET request using file_get_contents
+	 *
+	 * @param string $url
+	 * @param array $data (array of field/value pairs)
+	 * @param bool true to require verification of SSL cert
+	 * @return string
+	 */
+	static function FileGet($url, $data = "", $verify_cert = false, $timeout = 30)
+	{
+		$qs = HttpRequest::ArrayToQueryString($data);
+		$full_url = $url . ($qs ? "?" . $qs : "");
+		return file_get_contents( $full_url );
+	}
+
+	/**
+	 * Make an HTTP POST request using file_get_contents
+	 *
+	 * @param string $url
+	 * @param array $data (array of field/value pairs)
+	 * @param bool true to require verification of SSL cert
+	 * @return string
+	 */
+	static function FilePost($url, $data = "", $verify_cert = false, $timeout = 30)
+	{
+		$qs = HttpRequest::ArrayToQueryString($data);
+		$url = $url . ($qs ? "?" . $qs : "");
+
+		$show_headers = false;
+		$url = parse_url($url);
+
+		if (!isset($url['port'])) {
+			if ($url['scheme'] == 'http') { $url['port']=80; }
+			elseif ($url['scheme'] == 'https') { $url['port']=443; }
+		}
+		$url['query']=isset($url['query'])?$url['query']:'';
+
+		$url['protocol']=$url['scheme'].'://';
+		$eol="\r\n";
+
+		$headers =  "POST ".$url['protocol'].$url['host'].$url['path']." HTTP/1.0".$eol.
+			"Host: ".$url['host'].$eol.
+			"Referer: ".$url['protocol'].$url['host'].$url['path'].$eol.
+			"Content-Type: application/x-www-form-urlencoded".$eol.
+			"Content-Length: ".strlen($url['query']).$eol.
+			$eol.$url['query'];
+		$fp = fsockopen($url['host'], $url['port'], $errno, $errstr, 30);
+		if($fp)
+		{
+			fputs($fp, $headers);
+			$result = '';
+			while(!feof($fp)) { $result .= fgets($fp, 128); }
+			fclose($fp);
+			if (!$show_headers)
+			{
+				//removes headers
+				$match = preg_split("/\r\n\r\n/s",$result,2);
+				$result = $match[1];
+			}
+
+			return $result;
+		}
+	}
+
+	/**
+	 * Make an HTTP GET request using CURL
+	 *
+	 * @param string $url
+	 * @param variant $data querystring or array of field/value pairs
+	 * @param bool true to require verification of SSL cert
+	 * @return string
+	 */
+	static function CurlGet($url, $data = "", $verify_cert = false, $timeout = 30)
+	{
+		return HttpRequest::CurlRequest("GET",$url, $data, $verify_cert, $timeout);
+	}
+
+	/**
+	 * Make an HTTP POST request using CURL
+	 *
+	 * @param string $url
+	 * @param variant $data querystring or array of field/value pairs
+	 * @param bool true to require verification of SSL cert
+	 * @return string
+	 */
+	static function CurlPost($url, $data, $verify_cert = false, $timeout = 30)
+	{
+		return HttpRequest::CurlRequest("POST",$url, $data, $verify_cert, $timeout);
+	}
+
+	/**
+	 * Make an HTTP PUT request using CURL
+	 *
+	 * @param string $url
+	 * @param variant $data querystring or array of field/value pairs
+	 * @param bool true to require verification of SSL cert
+	 * @return string
+	 */
+	static function CurlPut($url, $data, $verify_cert = false, $timeout = 30)
+	{
+		return HttpRequest::CurlRequest("PUT",$url, $data, $verify_cert, $timeout);
+	}
+
+	/**
+	 * Make an HTTP request using CURL
+	 *
+	 * @param string "POST" or "GET"
+	 * @param string $url
+	 * @param variant $data querystring or array of field/value pairs
+	 * @param bool true to require verification of SSL cert
+	 * @return string
+	 */
+	static function CurlRequest($method, $url, $data, $verify_cert = false, $timeout = 30)
+	{
+		// if the data provided is in array format, convert it to a querystring
+		$qs = HttpRequest::ArrayToQueryString($data);
+
+		$agent = "verysimple::HttpRequest";
+
+		// $header[] = "Accept: text/vnd.wap.wml,*.*";
+
+		$fp = null;
+
+		if ($method == "POST")
+		{
+			$ch = curl_init($url);
+			curl_setopt($ch,		CURLOPT_POST, 1);
+			curl_setopt($ch,		CURLOPT_POSTFIELDS, $qs);
+		}
+		elseif ($method == 'PUT')
+		{
+			$ch = curl_init($url);
+			curl_setopt($ch, CURLOPT_PUT, true);
+
+			if ($data)
+			{
+				// with a PUT request the body must be written to a file stream
+				$fp = fopen('php://temp/maxmemory:256000', 'w');
+				if (!$fp) {
+					throw new Exception('Unable to write to php://temp for PUT request');
+				}
+				fwrite($fp, $data);
+				fseek($fp, 0);
+
+				// if the PUT request contains JSON data then add the content type header
+				if (json_encode($data)) curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: application/json") );
+
+				curl_setopt($ch, CURLOPT_INFILE, $fp);
+				curl_setopt($ch, CURLOPT_INFILESIZE, strlen($data));
+				curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
+			}
+
+		}
+		else
+		{
+			$full_url = $url . ($qs ? "?" . $qs : "");
+			$ch = curl_init($full_url);
+		}
+
+		curl_setopt($ch,		CURLOPT_FOLLOWLOCATION, 1);
+		curl_setopt($ch,		CURLOPT_RETURNTRANSFER, 1);
+		curl_setopt($ch,		CURLOPT_VERBOSE, 0); ########### debug
+		curl_setopt($ch,	    CURLOPT_USERAGENT, $agent);
+		curl_setopt($ch,		CURLOPT_SSL_VERIFYPEER, $verify_cert);
+		curl_setopt($ch,		CURLOPT_NOPROGRESS, 1);
+		// curl_setopt($ch,	    CURLOPT_HTTPHEADER, $header);
+		// curl_setopt($ch,		CURLOPT_COOKIEJAR, "curl_cookie");
+		// curl_setopt($ch,		CURLOPT_COOKIEFILE, "curl_cookie");
+		curl_setopt($ch,		CURLOPT_TIMEOUT,$timeout);
+
+		$tmp = curl_exec ($ch);
+		$error = curl_error($ch);
+
+		if ($fp) @fclose($fp); // if a PUT request had body data, close the file stream
+
+		if ($error != "") {$tmp .= $error;}
+		curl_close ($ch);
+
+		return $tmp;
+	}
+
+	/**
+	 * Converts an array into a URL querystring
+	 * @param array key/value pairs
+	 * @return string
+	 */
+	static function ArrayToQueryString($arr)
+	{
+		$qs = $arr;
+
+		if (is_array($arr))
+		{
+			// convert the data array into a url querystring
+			$qs = "";
+			$delim = "";
+			foreach (array_keys($arr) as $key)
+			{
+				$qs .= $delim . $key ."=" . $arr[$key];
+				$delim = "&";
+			}
+		}
+
+		return $qs;
+	}
+}
+?>

+ 23 - 0
phreeze/libs/verysimple/HTTP/Request.php

@@ -0,0 +1,23 @@
+<?php
+/** @package    verysimple::HTTP */
+
+/** import supporting libraries */
+require_once("RequestUtil.php");
+
+/**
+ * Static utility class for processing form post/request data
+ *
+ * Contains various methods for retrieving user input from forms
+ *
+ * @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
+ * @deprecated User RequestUtil instead to avoid namespace conflicts
+ */
+class Request extends RequestUtil
+{
+}
+
+?>

+ 591 - 0
phreeze/libs/verysimple/HTTP/RequestUtil.php

@@ -0,0 +1,591 @@
+<?php
+/** @package	verysimple::HTTP */
+
+/** import supporting libraries */
+require_once("FileUpload.php");
+require_once("verysimple/String/VerySimpleStringUtil.php");
+
+/**
+ * Static utility class for processing form post/request data
+ *
+ * Contains various methods for retrieving user input from forms
+ *
+ * @package	verysimple::HTTP
+ * @author	 VerySimple Inc.
+ * @copyright  1997-2011 VerySimple, Inc. http://www.verysimple.com
+ * @license	http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version	1.3
+ */
+class RequestUtil
+{
+
+
+	/** @var bool set to true and all non-ascii characters in request variables will be html encoded */
+	static $ENCODE_NON_ASCII = false;
+
+	/** @var bool set to false to skip is_uploaded_file.  This allows for simulated file uploads during unit testing */
+	static $VALIDATE_FILE_UPLOAD = true;
+
+	/** @var body contents, only read once in case GetBody is called multiple times */
+	private static $bodyCache = '';
+
+	/** @var true if the request body has already been read */
+	private static $bodyCacheIsReady = false;
+
+	/**
+	 * @var bool
+	 * @deprecated use $VALIDATE_FILE_UPLOAD instead
+	 */
+	static $TestMode = false;
+
+	/**
+	 * Returns the remote host IP address, attempting to locate originating
+	 * IP of the requester in the case of proxy/load balanced requests.
+	 *
+	 * @see http://en.wikipedia.org/wiki/X-Forwarded-For
+	 * @return string
+	 */
+	static function GetRemoteHost()
+	{
+		if (array_key_exists('HTTP_X_CLUSTER_CLIENT_IP',$_SERVER)) return $_SERVER['HTTP_X_CLUSTER_CLIENT_IP'];
+		if (array_key_exists('HTTP_X_FORWARDED_FOR',$_SERVER)) return $_SERVER['HTTP_X_FORWARDED_FOR'];
+		if (array_key_exists('X_FORWARDED_FOR',$_SERVER)) return $_SERVER['X_FORWARDED_FOR'];
+		if (array_key_exists('REMOTE_ADDR',$_SERVER)) return $_SERVER['REMOTE_ADDR'];
+		return "0.0.0.0";
+	}
+
+	/**
+	 * Returns true if the current session is running in SSL
+	 */
+	static function IsSSL()
+	{
+		return isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != "" && $_SERVER['HTTPS'] != "off";
+	}
+
+	/** In the case of URL re-writing, sometimes querystrings appended to a URL can get
+	 * lost.  This function examines the original request URI and updates $_REQUEST
+	 * superglobal to ensure that it contains all of values in the qeurtystring
+	 *
+	 */
+	public static function NormalizeUrlRewrite()
+	{
+		$uri = array();
+		if (isset($_SERVER["REQUEST_URI"]))
+		{
+			$uri = parse_url($_SERVER["REQUEST_URI"]);
+		}
+		elseif (isset($_SERVER["QUERY_STRING"]))
+		{
+			$uri['query'] = $_SERVER["QUERY_STRING"];
+		}
+
+		if (isset($uri['query']))
+		{
+			$parts = explode("&",$uri['query']);
+			foreach ($parts as $part)
+			{
+				$keyval = explode("=",$part,2);
+				$_REQUEST[$keyval[0]] = isset($keyval[1]) ? urldecode($keyval[1]) : "";
+			}
+		}
+	}
+
+	/**
+	* Returns the root url of the server without any subdirectories
+	* @return string URL path with trailing slash
+	*/
+	public static function GetServerRootUrl()
+	{
+		$url = self::GetCurrentURL(false);
+		$parts = explode('/', $url);
+		if (count($parts) < 2) throw new Exception('RequestUtil is unable to determine the server root');
+		return $parts[0] . '//' . $parts[2] . '/';
+	}
+
+	/**
+	 * Returns the base url of the currently executing script.  For example
+	 * the script http://localhost/myapp/index.php would return http://localhost/myapp/
+	 * @return string URL path with trailing slash
+	 */
+	public static function GetBaseURL()
+	{
+		$url = self::GetCurrentURL(false);
+		$slash = strripos($url,"/");
+		return substr($url,0,$slash+1);
+	}
+
+	/**
+	 * Returns the parts of the url as deliminated by forward slashes for example /this/that/other
+	 * will be returned as an array [this,that,other]
+	 * @param string root folder for the app (ex. 'myapp' or 'myapp/subdir1')
+	 * @return array
+	 */
+	public static function GetUrlParts($appRoot = '')
+	{
+		$urlqs = explode("?", self::GetCurrentURL(),2);
+		$url = $urlqs[0];
+
+		// if a root folder was provided, then we need to strip that out as well
+		if ($appRoot) $url = str_replace($appRoot.'/','',$url);
+
+		$parts = explode("/", $url);
+		// we only want the parts starting with #3 (after http://server/)
+
+		array_shift($parts);
+		array_shift($parts);
+		array_shift($parts);
+
+		// if there is no action specified then we don't want to return an array with an empty string
+		while (count($parts) && $parts[0] == '')
+		{
+			array_shift($parts);
+		}
+
+		return $parts;
+	}
+
+	/**
+	 * Returns the request method (GET, POST, PUT, DELETE).  This is detected based
+	 * on the HTTP request method, a special URL parameter, or a request header
+	 * with the name 'X-HTTP-Method-Override'
+	 *
+	 * For clients or servers that don't support PUT/DELETE requests, the emulated
+	 * param can be used or the override header
+	 *
+	 * @param string name of the querystring parameter that has the overridden request method
+	 */
+	public static function GetMethod($emulateHttpParamName = '_method')
+	{
+
+		if (array_key_exists($emulateHttpParamName, $_REQUEST)) return $_REQUEST[$emulateHttpParamName];
+
+		$headers = self::GetRequestHeaders();
+
+		// this is used by backbone
+		if (array_key_exists('X-HTTP-Method-Override', $headers))
+		{
+			return $headers['X-HTTP-Method-Override'];
+		}
+
+		return array_key_exists('REQUEST_METHOD', $_SERVER) ? $_SERVER['REQUEST_METHOD'] : '';
+	}
+
+	/**
+	 * Return all request headers using the best method available for the server environment
+	 * @return array
+	 */
+	public static function GetRequestHeaders()
+	{
+
+		if (function_exists('getallheaders')) return getallheaders();
+
+		$headers = array();
+		foreach ($_SERVER as $k => $v)
+		{
+			if (substr($k, 0, 5) == "HTTP_")
+			{
+				$k = str_replace('_', ' ', substr($k, 5));
+				$k = str_replace(' ', '-', ucwords(strtolower($k)));
+				$headers[$k] = $v;
+			}
+		}
+		return $headers;
+	}
+
+	/**
+	 * Returns the body/payload of a request.  this is cached so that this method
+	 * may be called multiple times.
+	 *
+	 * Note: if this is a PUT request and the body is not returning data, then
+	 * you must look in other code an libraries that may read from php://input,
+	 * which can only be read one time for PUT requests
+	 *
+	 * @return string
+	 */
+	public static function GetBody()
+	{
+		if (!self::$bodyCacheIsReady)
+		{
+			self::$bodyCache = @file_get_contents('php://input');
+			self::$bodyCacheIsReady = true;
+		}
+
+ 		return self::$bodyCache;
+	}
+
+	/**
+	 * Used primarily for unit testing.  Set the contents of the request body
+	 * @param string $contents
+	 */
+	public static function SetBody($contents)
+	{
+		self::$bodyCache = $contents;
+		self::$bodyCacheIsReady = true;
+	}
+
+	/**
+	 * Return the HTTP headers sent along with the request.  This will attempt
+	 * to use apache_request_headers if available in the environment, otherwise
+	 * will manually build the headers using $_SERVER superglobal
+	 * @return array
+	 */
+	public static function GetHeaders()
+	{
+		$headers = false;
+
+		if (function_exists('apache_request_headers')) $headers = apache_request_headers();
+
+		if ($headers === false)
+		{
+			// apache_request_headers is not supported in this environment
+
+			$headers = array();
+			foreach ($_SERVER as $key => $value)
+			{
+				if (substr($key, 0, 5) <> 'HTTP_')
+				{
+					continue;
+				}
+				$header = str_replace(' ', '-', ucwords(str_replace('_', ' ', strtolower(substr($key, 5)))));
+				$headers[$header] = $value;
+			}
+		}
+
+		return $headers;
+	}
+
+	/** Returns the full URL of the PHP page that is currently executing
+	 *
+	 * @param bool $include_querystring (optional) Specify true/false to include querystring. Default is true.
+	 * @param bool $append_post_vars true to append post variables to the querystring as GET parameters Default is false
+	 * @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"] : "";
+
+		$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);
+
+
+		if (isset($_SERVER['REQUEST_URI']))
+		{
+			// REQUEST_URI is more accurate but isn't always defined on windows
+			// in particular for the format http://www.domain.com/?var=val
+			$pq = explode("?",$_SERVER['REQUEST_URI'],2);
+			$path = $pq[0];
+			$qs = isset($pq[1]) ? "?" . $pq[1] : "";
+		}
+		else
+		{
+			// otherwise use SCRIPT_NAME & QUERY_STRING
+			$path = isset($_SERVER['SCRIPT_NAME']) ? $_SERVER['SCRIPT_NAME'] : "";
+			$qs = isset($_SERVER['QUERY_STRING']) ? "?" . $_SERVER['QUERY_STRING'] : "";
+		}
+
+		// if we also want the post variables appended we can get them as a querystring from php://input
+		if ($append_post_vars && isset($_POST))
+		{
+			$post = self::GetBody();
+			$qs .= $qs ? "&$post" : "?$post";
+		}
+
+		$url = strtolower($protocol) . "://" . $domain . $port . $path . ($include_querystring ? $qs : "");
+
+		return $url;
+	}
+
+
+
+	/**
+	* Returns a form upload as a FileUpload object.  This function throws an exeption on fail
+	* with details, so it is recommended to use try/catch when calling this function
+	*
+	* @param	string $fieldname name of the html form field
+	* @param	bool $b64encode true to base64encode file data (default false)
+	* @param	bool $ignore_empty true to not throw exception if form fields doesn't contain a file (default false)
+	* @param	int $max_kb maximum size allowed for upload (default unlimited)
+	* @param	array $ok_types if array is provided, only files with those Extensions will be allowed (default all)
+	* @return   FileUpload object (or null if $ignore_empty = true and there is no file data)
+	*/
+	public static function GetFileUpload($fieldname, $ignore_empty = false, $max_kb = 0, $ok_types = null)
+	{
+		// make sure there is actually a file upload
+		if (!isset($_FILES[$fieldname]))
+		{
+			// this means the form field wasn't present which is generally an error
+			// however if ignore is specified, then return empty string
+			if ($ignore_empty)
+			{
+				return null;
+			}
+			throw new Exception("\$_FILES['".$fieldname."'] is empty.  Did you forget to add enctype='multipart/form-data' to your form code?");
+		}
+
+		// make sure a file was actually uploaded, otherwise return null
+		if($_FILES[$fieldname]['error'] == 4)
+		{
+			return;
+		}
+
+		// get the upload ref
+		$upload = $_FILES[$fieldname];
+
+		// make sure there were no errors during upload, but ignore case where
+		if ($upload['error'])
+		{
+			$error_codes[0] = "The file uploaded with success.";
+			$error_codes[1] = "The uploaded file exceeds the upload_max_filesize directive in php.ini.";
+			$error_codes[2] = "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the html form.";
+			$error_codes[3] = "The uploaded file was only partially uploaded.";
+			$error_codes[4] = "No file was uploaded.";
+			throw new Exception("Error uploading file: " . $error_codes[$upload['error']]);
+		}
+
+		// backwards compatibility
+		if (self::$TestMode) self::$VALIDATE_FILE_UPLOAD = false;
+
+		// make sure this is a legit file request
+		if ( self::$VALIDATE_FILE_UPLOAD && is_uploaded_file($upload['tmp_name']) == false )
+		{
+			throw new Exception("Unable to access this upload: " . $fieldname);
+		}
+
+		// get the filename and Extension
+		$tmp_path = $upload['tmp_name'];
+		$info = pathinfo($upload['name']);
+
+		$fupload = new FileUpload();
+		$fupload->Name = $info['basename'];
+		$fupload->Size = $upload['size'];
+		$fupload->Type = $upload['type'];
+		$fupload->Extension = strtolower($info['extension']);
+
+
+		if ($ok_types && !in_array($fupload->Extension, $ok_types) )
+		{
+			throw new Exception("The file '".htmlentities($fupload->Name)."' is not a type that is allowed.  Allowed file types are: " . (implode(", ",$ok_types)) . ".");
+		}
+
+		if ($max_kb && ($fupload->Size/1024) > $max_kb)
+		{
+			throw new Exception("The file '".htmlentities($fupload->Name)."' is to large.  Maximum allowed size is " . number_format($max_kb/1024,2) . "Mb");
+		}
+
+		// open the file and read the entire contents
+		$fh = fopen($tmp_path,"r");
+		$fupload->Data = fread($fh, filesize($tmp_path));
+		fclose($fh);
+
+		return $fupload;
+	}
+
+	/**
+	 * Returns a form upload as an xml document with the file data base64 encoded.
+	 * suitable for storing in a clob or blob
+	 *
+	* @param	string $fieldname name of the html form field
+	* @param	bool $b64encode true to base64encode file data (default true)
+	* @param	bool $ignore_empty true to not throw exception if form fields doesn't contain a file (default false)
+	* @param	int $max_kb maximum size allowed for upload (default unlimited)
+	* @param	array $ok_types if array is provided, only files with those Extensions will be allowed (default all)
+	* @return   string or null
+	 */
+	public static function GetFile($fieldname, $b64encode = true, $ignore_empty = false, $max_kb = 0, $ok_types = null)
+	{
+		$fupload = self::GetFileUpload($fieldname, $ignore_empty, $max_kb, $ok_types);
+		return ($fupload) ? $fupload->ToXML($b64encode) : null;
+	}
+
+	/**
+	* Sets a value as if it was sent from the browser - primarily used for unit testing
+	*
+	* @param	string $key
+	* @param	variant $val
+	*/
+	public static function Set($key, $val)
+	{
+		$_REQUEST[$key] = $val;
+	}
+
+	/**
+	* Clears all browser input - primarily used for unit testing
+	*
+	*/
+	public static function ClearAll()
+	{
+		$_REQUEST = array();
+		$_FILES = array();
+
+		self::$bodyCache = "";
+		self::$bodyCacheIsReady = false;
+	}
+
+	/**
+	* Returns a form parameter as a string, handles null values.  Note that if
+	* $ENCODE_NON_ASCII = true then the value will be passed through VerySimpleStringUtil::EncodeToHTML
+	* before being returned.
+	*
+	* If the form field is a multi-value type (checkbox, etc) then an array may be returned
+	*
+	* @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)
+	* @return   string | array
+	*/
+	public static function Get($fieldname, $default = "", $escape = false)
+	{
+		$val = (isset($_REQUEST[$fieldname]) && $_REQUEST[$fieldname] != "") ? $_REQUEST[$fieldname] : $default;
+
+		if ($escape)
+		{
+			$val = htmlspecialchars($val, ENT_COMPAT, null, false);
+		}
+
+		if (self::$ENCODE_NON_ASCII)
+		{
+			if (is_array($val))
+			{
+				foreach ($val as $k=>$v)
+				{
+					$val[$k] = VerySimpleStringUtil::EncodeToHTML($v);
+				}
+			}
+			else
+			{
+				$val = VerySimpleStringUtil::EncodeToHTML($val);
+			}
+		}
+
+		return $val;
+	}
+
+	/**
+	 * Returns true if the given form field has non-ascii characters
+	 * @param string $fieldname
+	 * @return bool
+	 */
+	public static function HasNonAsciiChars($fieldname)
+	{
+		$val = isset($_REQUEST[$fieldname]) ? $_REQUEST[$fieldname] : '';
+		return VerySimpleStringUtil::EncodeToHTML($val) != $val;
+	}
+
+	/**
+	* Returns a form parameter and persists it in the session.  If the form parameter was not passed
+	* again, then it returns the session value.  if the session value doesn't exist, then it returns
+	* the default setting
+	*
+	* @param	string $fieldname
+	* @param	string $default
+	* @return   string
+	*/
+	public static function GetPersisted($fieldname, $default = "",$escape = false)
+	{
+		if ( isset($_REQUEST[$fieldname]) )
+		{
+			$_SESSION["_PERSISTED_".$fieldname] = self::Get($fieldname, $default, $escape);
+		}
+
+		if ( !isset($_SESSION["_PERSISTED_".$fieldname]) )
+		{
+			$_SESSION["_PERSISTED_".$fieldname] = $default;
+		}
+
+		return $_SESSION["_PERSISTED_".$fieldname];
+	}
+
+	/**
+	* Returns a form parameter as a date formatted for mysql YYYY-MM-DD,
+	* expects some type of date format.  if default value is not provided,
+	* will return today.  if default value is empty string "" will return
+	* empty string.
+	*
+	* @param	string $fieldname
+	* @param	string $default default value = today
+	* @param	bool $includetime whether to include the time in addition to date
+	* @return   string
+	*/
+	public static function GetAsDate($fieldname, $default = "date('Y-m-d')", $includetime = false)
+	{
+		$returnVal = self::Get($fieldname,$default);
+
+		if ($returnVal == "date('Y-m-d')")
+		{
+			return date('Y-m-d');
+		}
+		elseif ($returnVal == "date('Y-m-d H:i:s')")
+		{
+			return date('Y-m-d H:i:s');
+		}
+		elseif ($returnVal == "")
+		{
+			return "";
+		}
+		else
+		{
+			if ($includetime)
+			{
+				if (self::Get($fieldname."Hour"))
+				{
+					$hour = self::Get($fieldname."Hour",date("H"));
+					$minute = self::Get($fieldname."Minute",date("i"));
+					$ampm = self::Get($fieldname."AMPM","AM");
+
+					if ($ampm == "PM")
+					{
+						$hour = ($hour*1)+12;
+					}
+					$returnVal .= " " . $hour . ":" . $minute . ":" . "00";
+				}
+
+				return date("Y-m-d H:i:s",strtotime($returnVal));
+			}
+			else
+			{
+				return date("Y-m-d",strtotime($returnVal));
+			}
+		}
+	}
+
+	/**
+	* Returns a form parameter as a date formatted for mysql YYYY-MM-DD HH:MM:SS,
+	* expects some type of date format.  if default value is not provided,
+	* will return now.  if default value is empty string "" will return
+	* empty string.
+	*
+	* @param	string $fieldname
+	* @param	string $default default value = today
+	* @return   string
+	*/
+	public static function GetAsDateTime($fieldname, $default = "date('Y-m-d H:i:s')")
+	{
+		return self::GetAsDate($fieldname,$default,true);
+	}
+
+	/**
+	 * Returns a form parameter minus currency symbols
+	 *
+	 * @param	string	$fieldname
+	 * @return	string
+	 */
+	public static function GetCurrency($fieldname)
+	{
+		return str_replace(array(',','$'),'',self::Get($fieldname));
+	}
+
+
+}
+
+?>

+ 35 - 0
phreeze/libs/verysimple/HTTP/UrlWriter.php

@@ -0,0 +1,35 @@
+<?php
+/** @package    verysimple::HTTP */
+
+require_once("verysimple/Phreeze/ActionRouter.php");
+require_once("verysimple/HTTP/RequestUtil.php");
+
+/**
+ * class for dealing with URLs
+ *
+ * @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 UrlWriter extends ActionRouter
+{
+	/** Returns a url for the given controller, method and parameters
+	 *
+	 * @param string $controller
+	 * @param string $method
+	 * @param string $params in the format param1=val1&param2=val2
+	 * @param bool $strip_api set to true to strip virtual part of the url in a rest call
+	 * @param string $delim the querystring variable delimiter (& or &amp; for generating valid html)
+	 * @return string URL
+	 */
+	public function Get($controller, $method, $params = "", $strip_api = true, $delim="&")
+	{
+		$this->stripApi = $strip_api;
+		$this->delim = $delim;
+		return $this->GetUrl($controller, $method, $params);
+	}
+}
+
+?>

+ 42 - 0
phreeze/libs/verysimple/IO/FileHelper.php

@@ -0,0 +1,42 @@
+<?php
+/** @package    verysimple::IO */
+
+/**
+ * Provides helper functions for dealing with a file
+ *
+ * @package    verysimple::IO
+ * @author Jason Hinkle
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version 1.0
+ */
+ class FileHelper
+{
+	public $Name;
+	public $Path;
+	public $FolderPath;
+	public $Extention;
+	public $Prefix;
+	public $MiddleBit;
+	
+	/**
+	 * Creates a new instance of a FileHelper object
+	 *
+	 * @access public
+	 * @param $path The full path to the file
+	 */
+	function __construct($path)
+	{
+		//TODO: user build-in php functions to extract these properties
+		
+		$this->Path = str_replace("\\","/", $path); // normalize any directory paths
+		
+		$this->Name = substr($this->Path, strrpos($this->Path,"/")+1);
+		$this->Extention = substr($this->Path, strrpos($this->Path,".")+1);
+		$this->Prefix = substr($this->Name, 0, strpos($this->Name,"."));
+		$this->MiddleBit = substr($this->Name, strpos($this->Name,".")+1, strrpos($this->Name,".")-strpos($this->Name,".")-1);
+		$this->FolderPath = substr($this->Path, 0, strrpos($this->Path,"/")+1);
+	}
+	
+}
+?>

+ 60 - 0
phreeze/libs/verysimple/IO/FolderHelper.php

@@ -0,0 +1,60 @@
+<?php
+/** @package    verysimple::IO */
+
+/** import supporting libraries */
+require_once("FileHelper.php");
+
+/**
+ * Provided object oriented access to a file system directory
+ *
+ * @package    verysimple::IO
+ * @author Jason Hinkle
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version 1.0
+ */
+class FolderHelper
+{
+	private $Path;
+
+	/**
+	 * Constructor
+	 *
+	 * @access public
+	 * @param string $path uri to directory to manipulate
+	 */	
+	function __construct($path)
+	{
+		$this->Path = $path;
+	}
+	
+	/**
+	 * Returns an array of FileHelper objects
+	 *
+	 * @access public
+	 * @param string $pattern (not yet implemented)
+	 * @return array
+	 */	
+	public function GetFiles($pattern = "")
+	{
+		$files = Array();
+		$dh = opendir($this->Path);
+
+		while ($fname = readdir($dh)) 
+		{
+			if (is_file($this->Path.$fname))
+			{
+				if ($pattern == "" || preg_match($pattern,$fname) > 0)
+				{
+					$files[] = new FileHelper($this->Path.$fname);
+				}
+			}
+		}
+		
+		closedir($dh);
+	    
+		return $files;
+	}
+
+}
+?>

+ 18 - 0
phreeze/libs/verysimple/IO/IncludeException.php

@@ -0,0 +1,18 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/**
+ * NotFoundExeption is thrown when a persisted object is requsted by primary key
+ * but does not exist in the data store
+ *
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc.
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    2.0
+ */
+class IncludeException extends Exception
+{
+}
+
+?>

+ 98 - 0
phreeze/libs/verysimple/IO/Includer.php

@@ -0,0 +1,98 @@
+<?php
+/** @package    verysimple::IO */
+
+/** import supporting libraries */
+require_once("IncludeException.php");
+
+/**
+ * Provides helper functions for including classes and files dynamically
+ * so that Exceptions are thrown instead of PHP errors and warnings
+ *
+ * @package    verysimple::IO
+ * @author Jason Hinkle
+ * @copyright  1997-2008 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version 1.0
+ */
+class Includer
+{
+
+	/**
+	 * Includes a file with the given path.  If PHP is unable to include the file,
+	 * an IncludeException is thrown instead of a PHP warning
+	 * @param string path to file passed to the include_once statement
+	 */
+	public static function IncludeFile($path)
+	{
+		// re-route error handling temporarily so we can catch errors
+		// use include instead of require so we can catch runtime exceptions
+		// reset error handling back to whatever it was
+		//*
+		set_error_handler(array("Includer", "IncludeException"), E_WARNING);
+		include_once($path);
+		restore_error_handler();
+		//*/
+		
+		// this doesn't work but it seems like it should
+		// if (@include_once($path) === false) throw new IncludeException("Unable to include file: " . $path);
+
+	}
+
+	/**
+	 * Ensures that a class is defined.  If not, attempts to include the file
+	 * using the provided path. If unable to locate the class, an IncludeException
+	 * will be thrown.  The path that will be used for include_once is
+	 * $classpath . "/" . $classname . ".php"
+	 * 
+	 * @param string name of class (ex Phreeze)
+	 * @param string or array [optional] the relative path(s) where the file would be found
+	 */
+	public static function RequireClass($classname, $classpath = "")
+	{
+		if (class_exists($classname)) return true;
+		
+		// normalize this as an array
+		$classpaths = is_array($classpath) ? $classpath : array($classpath);
+		$attempts = "";
+		
+		foreach ($classpaths as $path)
+		{
+			if (class_exists($classname)) break;
+
+			try
+			{
+				// append a directory separater if necessary
+				if ($path && substr($path,-1) != "/") $path .= "/";
+				Includer::IncludeFile($path . $classname . ".php");
+			}
+			catch (IncludeException $ex) {$attempts .= " " . $ex->getMessage();}
+
+		}
+		
+		if (!class_exists($classname))
+		{
+			// the class still isn't defined so there was a problem including the model
+			throw new IncludeException("Unable to locate class '$classname': " . $attempts);
+		}
+	}	
+	
+	/**
+	* Handler for catching file-not-found errors and throwing an IncludeException
+	*/
+	public static function IncludeException($code, $string, $file, $line, $context)
+	{
+		// check for repressed errors
+		if (error_reporting() == 0) return;
+		
+		$tmp1 = explode(")",$string);
+		$tmp2 = explode("(",$tmp1[0]);
+		$mfile = isset($tmp2[1]) ? $tmp2[1] : "";
+		
+		$msg = "Error $code: " .  ($mfile ? "Unable to include file: '" . $mfile . "'" : $string);
+		
+		throw new IncludeException($msg,$code);
+	}
+	
+}
+
+?>

+ 169 - 0
phreeze/libs/verysimple/Phreeze/ActionRouter.php

@@ -0,0 +1,169 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+require_once("verysimple/HTTP/RequestUtil.php");
+require_once("verysimple/Util/UrlWriterMode.php");
+require_once("verysimple/Phreeze/IRouter.php");
+
+/**
+ * class for dealing with URLs
+ *
+ * @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 ActionRouter implements IRouter
+{
+	private $_mode;
+	private $_appRoot;
+	private $_defaultRoute;
+
+	protected $stripApi = true;
+	protected $delim = '&';
+
+	protected static $_format;
+
+	/**
+	 * Constructor allows a rewriting pattern to be specified
+	 *
+	 * @param string $format sprintf compatible format
+	 */
+	public function __construct($format = "%s.%s.page?%s", $mode = UrlWriterMode::WEB, $appRoot = '', $defaultRoute = '')
+	{
+		$this->_format = $format;
+		$this->_mode = $mode;
+		$this->_appRoot = $appRoot;
+		$this->_defaultRoute = $defaultRoute;
+	}
+
+	/**
+	 * @inheritdocs
+	 */
+	public function GetUri()
+	{
+		return implode('/',RequestUtil::GetUrlParts($this->_appRoot));
+	}
+
+	/**
+	* @inheritdocs
+	*/
+	public function GetUrlParams()
+	{
+		return $_REQUEST;
+	}
+
+	/**
+	* @inheritdocs
+	*/
+	public function GetUrlParam($key, $default = '')
+	{
+		return RequestUtil::Get($key,$default);
+	}
+
+	/**
+	* @inheritdocs
+	*/
+	public function GetUrl($controller,$method,$params='',$requestMethod='')
+	{
+		$format = str_replace("{delim}",$this->delim,$this->_format);
+
+		$qs = "";
+		$d = "";
+		if (is_array($params))
+		{
+			foreach ($params as $key => $val)
+			{
+				// if no val, the omit the equal sign (this might be used in rest-type requests)
+				$qs .= $d . $key . (strlen($val) ? ("=" . urlencode($val)) : "");
+				$d = $this->delim;
+			}
+		}
+		else
+		{
+			$qs = $params;
+		}
+
+		$url = sprintf($format,$controller,$method,$qs);
+
+		// strip off trailing delimiters from the url
+		$url = (substr($url,-5) == "&amp;") ? substr($url,0,strlen($url)-5) : $url;
+		$url = (substr($url,-1) == "&" || substr($url,-1) == "?") ? substr($url,0,strlen($url)-1) : $url;
+
+		$api_check = explode("/api/",RequestUtil::GetCurrentUrl());
+		if ($this->stripApi && count($api_check) > 1)
+		{
+			$url = $api_check[0] . "/" . $url;
+		}
+
+		return $url;
+	}
+
+	/**
+	 * @inheritdocs
+	 */
+	public function GetRoute($uri = "")
+	{
+
+		if( $uri == "" )
+		{
+			$action = RequestUtil::Get('action');
+			if (!$action) $action = $this->_defaultRoute;
+			$uri = $action ? $action : RequestUtil::GetCurrentURL();
+		}
+
+		// get the action requested
+		$params = explode(".", str_replace("/",".", $uri) );
+		$controller_param = isset($params[0]) && $params[0] ? $params[0] : "";
+		$controller_param = str_replace(array(".","/","\\"),array("","",""),$controller_param);
+
+		if ( !$controller_param )
+		{
+			throw new Exception("Invalid or missing Controller parameter");
+		}
+
+		$method_param = isset($params[1]) && $params[1] ? $params[1] : "";
+		if ( !$method_param ) $method_param = "DefaultAction";
+		
+		return array($controller_param,$method_param);
+	}
+
+	/**
+	* Returns true or false based on the $value passed in as to whether or not the
+	* URL Writer is currently in that mode.
+	*
+	* @param $value	String mode to check against the current mode
+	* @return	boolean TRUE if arg passed in is the current mode
+	*/
+	public function ModeIs( $value )
+	{
+		if( strcmp($this->_mode,$value) == 0 )
+			return true;
+		else
+			return false;
+	}
+
+	/**
+	 * Returns how the Dispatcher plucks it's controller and method from the URL.
+	 *
+	 * @param $default_action	The Default action in case the argument hasn't been supplied
+	 */
+	public function GetAction( $url_param = "action", $default_action = "Account.DefaultAction" )
+	{
+		switch( $this->_mode )
+		{
+			// TODO: Determine mobile/joomla URL action (if different from default)
+			/*
+			*	case UrlWriterMode::JOOMLA:
+			*		break;
+			*	case UrlWriterMode::MOBILE:
+			*		break;
+			*/
+			default:
+				// default is to return the standard browser-based action=%s.%s&%s:
+				return RequestUtil::Get($url_param, $default_action);
+			break;
+		}
+	}
+}

+ 177 - 0
phreeze/libs/verysimple/Phreeze/AuthAccount.php

@@ -0,0 +1,177 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/** import supporting libraries */
+require_once("Model/DAO/AccountDAO.php");
+require_once("verysimple/Authentication/IAuthenticatable.php");
+
+// these are some generic permission settings.  You should set your own in
+// your account object
+define("ACCOUNT_PERMISSION_NONE",0);
+define("ACCOUNT_PERMISSION_READ",1);
+define("ACCOUNT_PERMISSION_WRITE",2);
+define("ACCOUNT_PERMISSION_ADMIN",4);
+
+/**
+ * @package    verysimple::Phreeze
+ */
+
+/**
+ * This is a sample account object that can  be extended under the condition that your
+ * account object meets the following criteria:
+ * 
+ * The Model is name Account with the following properties/methods:
+ * - Id (int/primary key)
+ * - Username (string)
+ * - Password (string)
+ * - Modifed (datetime)
+ * - GetRole (returns a role object with a 'Permission' property that contains a bit-wise integer)
+ *
+ * Extending your account object from this base class will provide the following features:
+ * - Your class implements IAuthenticatable for use with Controller authentication
+ * - Login method will load this object
+ * - Password changes will be detected on save and passwords will be one-way crypted
+ *
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc.
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    2.0
+ */
+class AuthAccount extends AccountDAO implements IAuthenticatable
+{
+	/** @var string this is public for serialization */
+	public $_original_password = "";
+
+	/**
+	 * Checks if the current user is "anonymous" meaning they have not authenticated
+	 * @return bool true if user is anonymous
+	 */
+	function IsAnonymous()
+	{
+		return (!$this->Id);
+	}
+	
+	function PasswordWasChanged()
+	{
+		return ($this->Password != $this->_original_password && $this->Password != "");
+	}
+	
+	/**
+	 * Returns true if the current account has the specified permission
+	 * @param int $permission a bitwise integer representing a unique permission in the application
+	 * @return bool true if the current account is authorize for the given permission
+	 */
+	function IsAuthorized($permission)
+	{
+		if ($this->IsAnonymous())
+		{
+			return false;
+		}
+		
+		return (($this->GetRole()->Permission & $permission) > 0);
+	}
+	
+	/**
+	 * Attempts to authenticate based on the provided username/password.  if
+	 * successful, the object is populated with data from the data store
+	 *
+	 * @param string $username
+	 * @param string $password
+	 * @return bool true if login was successful
+	 */
+	function Login($username, $password)
+	{
+		// for backwards compatibility with Phreeze 2x, look in multiple places for the AccountCriteria class
+		if (!class_exists("AccountCriteria")) @include_once("Model/AccountCriteria.php");
+		if (!class_exists("AccountCriteria")) @include_once("Model/DAO/AccountCriteria.php");
+		if (!class_exists("AccountCriteria")) throw new Exception("Unable to locate AccountCriteria class.");
+		
+		if ($username == "" || $password == "")
+		{
+			return false;
+		}
+		
+		$this->_phreezer->Observe("AuthAccount.Login Searching For Matching Account...");
+
+		$criteria = new AccountCriteria();
+		// set both the name and the _Equals properties for backwards compatibility
+		$criteria->Username = $username;
+		$criteria->Username_Equals = $username;
+		$criteria->Password = base64_encode(crypt($password,$username));
+		$criteria->Password_Equals = base64_encode(crypt($password,$username));
+		
+		$ds = $this->_phreezer->Query("Account", $criteria);
+		
+		// we have to clear the cache, this resolves an issue where logging in repeatedly 
+		// will retain the same cached child objects
+		$this->ClearCache();
+		
+		if ($account = $ds->Next())
+		{
+			// we can't do $this = $account, so instead just clone all the properties:
+			$this->LoadFromObject($account);
+			$this->GetRole(); // this triggers the role to load so it will be cached
+			
+			// we need to update the login date and count
+			//$this->LastLogin = date('Y-m-d H:i:s');
+			//$this->LoginCount++;
+			//$this->Save();
+
+			return true;
+		}
+		else
+		{
+			return false;
+		}
+	}
+	
+	/** 
+	 * if the password has changed since load, then we want to crypt it
+	 * otherwise we don't want to touch it because it is already crypted
+	 *
+	 * @param bool $is_insert
+	 * @return bool
+	 */
+	function OnSave($is_insert)
+	{
+		// if the password has changed since load, then we want to crypt it
+		// otherwise we don't want to touch it because it is already crypted
+		if ($is_insert || $this->PasswordWasChanged() )
+		{
+			$this->_phreezer->Observe("Account-&gt;OnSave: The password has changed");
+			$this->Password = base64_encode(crypt($this->Password,$this->Username));
+		}
+		else
+		{
+			$this->Password = $this->_original_password;
+			$this->_phreezer->Observe("Account->OnSave: The password was not changed");
+		}		
+
+		// update the modified date
+		$this->Modified = date('Y-m-d H:i:s');
+		return true;
+	}
+	
+	/**
+	 * stores the original password so we can detect if it has been changed
+	 */
+	function OnLoad()
+	{
+		$this->_original_password = $this->Password;
+	}
+	
+	/**
+	 * Updates the password for this account
+	 * @param string $new_pass
+	 */
+	function UpdatePassword($new_pass)
+	{
+		$this->_original_password = ""; // force Save to crypt the password
+		$this->Password = $new_pass; //base64_encode(crypt($this->Password,$this->Username));
+		$this->Save();
+	}
+	
+}
+
+?>

+ 127 - 0
phreeze/libs/verysimple/Phreeze/BladeRenderEngine.php

@@ -0,0 +1,127 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+require_once("IRenderEngine.php");
+
+define('EXT', '.php');
+define('BLADE_EXT', '.blade.php');
+define('CRLF', "\r\n");
+define('DEFAULT_BUNDLE', 'application');
+define('MB_STRING', (int) function_exists('mb_get_info'));
+define('DS',DIRECTORY_SEPARATOR);
+
+require_once("laravel/paths.php");
+require_once("laravel/helpers.php");
+require_once("laravel/blade.php");
+require_once("laravel/view.php");
+require_once("laravel/event.php");
+require_once("laravel/bundle.php");
+require_once("laravel/session.php");
+require_once("laravel/messages.php");
+require_once("laravel/section.php");
+
+/**
+ * PHPRenderEngine is an implementation of IRenderEngine
+ * that uses PHP as the template language
+ *
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc.
+ * @copyright  1997-2010 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    1.0
+ */
+class BladeRenderEngine implements IRenderEngine
+{
+	/** the file path to the template director */
+	static $TEMPLATE_PATH;
+	
+	static $COMPILE_PATH;
+
+	/** stores the assigned vars */
+	public $model = Array();
+
+	/**
+	 * @param string $templatePath
+	 * @param string $compilePath (not used for this render engine)
+	 */
+	function __construct($templatePath = '',$compilePath = '')
+	{
+		self::$TEMPLATE_PATH = $templatePath;
+		self::$COMPILE_PATH = $compilePath;
+
+		// blade will look for this path to store compiled templates
+		$GLOBALS['laravel_paths']['storage'] = self::$COMPILE_PATH;
+		
+		// attach a handler to the 'View::loader' event so we can tweak the file paths to fit with Phreeze
+		Laravel\Event::listen(Laravel\View::loader, function($bundle,$view)
+		{
+			return BladeRenderEngine::$TEMPLATE_PATH . $view . '.blade.php';
+		});
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	public function assign($key,$value)
+	{
+		$this->model[$key] = $value;
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	public function display($template)
+	{
+		//die('template = ' . $template);
+		$template = str_replace('.tpl', '', $template); // normalize any old smarty template paths
+		echo $this->fetch($template);
+	}
+
+	/**
+	 * Returns the specified model value
+	 */
+	public function get($key)
+	{
+		return $this->model[$key];
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	public function fetch($template)
+	{
+		$view = Laravel\View::make($template, $this->model);
+		
+		Laravel\Blade::sharpen();
+		
+		$responses = Laravel\Event::fire(Laravel\View::engine, array($view));
+		
+		return $responses[0];
+	}
+
+	/**
+	 * @see IRenderEngine::clear()
+	 */
+	function clear($key)
+	{
+		if (array_key_exists($key,$this->model)) unset($this->model[$key]);
+	}
+
+	/**
+	 * @see IRenderEngine::clearAll()
+	 */
+	function clearAll()
+	{
+		$this->model == array();
+	}
+
+	/**
+	 * @see IRenderEngine::getAll()
+	 */
+	function getAll()
+	{
+		return $this->model;
+	}
+}
+
+?>

+ 102 - 0
phreeze/libs/verysimple/Phreeze/CacheMemCache.php

@@ -0,0 +1,102 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/** import supporting libraries */
+require_once("ICache.php");
+require_once("verysimple/Util/ExceptionThrower.php");
+
+/**
+ * CacheRam is an implementation of a Cache that persists to ram for the current page load only
+ *
+ * @package    verysimple::Phreeze 
+ * @author     VerySimple Inc.
+ * @copyright  1997-2008 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    2.0
+ */
+class CacheMemCache implements ICache
+{
+	private $_memcache = null;
+	private $_prefix = "";
+	private $_suppressServerErrors = false;
+	private $_lockFilePath = "";
+	
+	/**
+	 * Constructor requires a reference to a MemCache object
+	 * @param Memcache memcache object
+	 * @param string a unique prefix to use so this app doesn't conflict with any others that may use the same memcache pool
+	 * @param bool set to true to ignore errors if a connection can't be made to the cache server
+	 */
+	public function __construct($memcache,$uniquePrefix = "CACHE-",$suppressServerErrors=false)
+	{
+		$this->_memcache = $memcache;
+		$this->_prefix = $uniquePrefix ? $uniquePrefix . "-" : "";
+		$this->_suppressServerErrors = $suppressServerErrors;
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	public function Get($key,$flags=null)
+	{
+		$obj = null;
+		try
+		{
+			ExceptionThrower::Start();
+			$obj = $this->_memcache->get($this->_prefix . $key);
+			ExceptionThrower::Stop();
+		}
+		catch (Exception $ex)
+		{
+			ExceptionThrower::Stop();
+			if (!$this->_suppressServerErrors) throw $ex;
+		}
+
+		return $obj;
+	}
+	
+	/**
+	 * @inheritdocs
+	 */
+	public function Set($key,$val,$flags=null,$timeout=null)
+	{
+		$result = null;
+		try
+		{
+			ExceptionThrower::Start();
+			$result = $this->_memcache->set($this->_prefix . $key,$val,$flags,$timeout);
+			ExceptionThrower::Stop();
+		}
+		catch (Exception $ex)
+		{
+			ExceptionThrower::Stop();
+			if (!$this->_suppressServerErrors) throw $ex;
+		}
+		
+		return $result;
+	}
+
+	/**
+	 * @inheritdocs
+	 */
+	public function Delete($key)
+	{
+		$result = null;
+		try
+		{
+			ExceptionThrower::Start();
+			$result = $this->_memcache->delete($this->_prefix . $key);
+			ExceptionThrower::Stop();
+		}
+		catch (Exception $ex)
+		{
+			ExceptionThrower::Stop();
+			if (!$this->_suppressServerErrors) throw $ex;
+		}
+		
+		return $result;
+	}
+	
+	}
+
+?>

+ 35 - 0
phreeze/libs/verysimple/Phreeze/CacheNoCache.php

@@ -0,0 +1,35 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/** import supporting libraries */
+require_once("ICache.php");
+
+/**
+ * CacheRam is an implementation of a Cache that doesn't actually cache at all
+ *
+ * @package    verysimple::Phreeze 
+ * @author     VerySimple Inc.
+ * @copyright  1997-2008 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    2.0
+ */
+class CacheNoCache implements ICache
+{
+	private $ram = array();
+	
+	public function Get($key,$flags=null)
+	{
+		return null;
+	}
+	
+	public function Set($key,$val,$flags=null,$timeout=null)
+	{
+	}
+
+	public function Delete($key)
+	{
+	}
+	
+}
+
+?>

+ 42 - 0
phreeze/libs/verysimple/Phreeze/CacheRam.php

@@ -0,0 +1,42 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/** import supporting libraries */
+require_once("ICache.php");
+
+/**
+ * CacheRam is an implementation of a Cache that persists to ram for the current page load only
+ *
+ * @package    verysimple::Phreeze 
+ * @author     VerySimple Inc.
+ * @copyright  1997-2008 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    2.0
+ */
+class CacheRam implements ICache
+{
+	private $ram = array();
+	
+	public function Get($key,$flags=null)
+	{
+		return isset($this->ram[$key]) ? $this->ram[$key] : null;
+	}
+	
+	public function GetKeys()
+	{
+		return array_keys($this->ram);
+	}
+	
+	public function Set($key,$val,$flags=null,$timeout=null)
+	{
+		$this->ram[$key] = $val;
+	}
+
+	public function Delete($key)
+	{
+		if (isset($this->ram[$key])) unset($this->ram[$key]);
+	}
+	
+}
+
+?>

+ 113 - 0
phreeze/libs/verysimple/Phreeze/ConnectionSetting.php

@@ -0,0 +1,113 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/**
+ * ConnectionSetting object contains information about the data store used for object persistance.
+ *
+ * @package    verysimple::Phreeze 
+ * @author     VerySimple Inc.
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    2.0
+ */
+class ConnectionSetting
+{
+	/** @var string database type, for example mysql, mysqli, sqlite */
+    var $Type = "mysql";
+    
+    /** @var string connection string used to connect to the database, for example  localhost:3306 */
+    var $ConnectionString;
+    
+    /** @var string name of the database/schema */
+    var $DBName;
+    
+    /** @var string database username used to connect */
+    var $Username;
+    
+    /** @var string database password used to connect */
+    var $Password;
+    
+    /** @var string if all tables share a common prefix, this can be used so object names do not include the prefix */
+    var $TablePrefix;
+    
+    /** @var string any arbitrary SQL that should be run upon first opening the connection, for example SET SQL_BIG_SELECTS=1 */
+    var $BootstrapSQL;
+    
+    /** @var string characterset used for the database, for example 'utf8' */
+    var $Charset;
+    
+    /** @var boolean set to true and multi-byte functions will be used when evaluating strings */
+    var $Multibyte = false;
+
+     /**
+     * Constructor
+     *
+     */
+    function __construct($connection_code = "")
+    {
+        if ($connection_code != "")
+        {
+            $this->Unserialize($connection_code);
+        }
+    }
+    
+     /**
+     * Returns an DSN array compatible with PEAR::DB
+     *
+     */
+    function GetDSN()
+    {
+	    return array(
+		    'phptype'  => $this->Type,
+		    'username' => $this->Username,
+		    'password' => $this->Password,
+		    'hostspec' => $this->ConnectionString,
+		    'database' => $this->DBName,
+	    );
+	}
+    
+    /**
+     * Returns an options array compatible with PEAR::DB
+     *
+     */
+    function GetOptions()
+    {
+        return array(
+	        'debug'          => 2,
+	        // 'portability' => DB_PORTABILITY_NONE,
+        );
+    }
+    
+    /**
+     * Serialize to string
+     *
+     */
+    function Serialize()
+    {
+        return base64_encode(serialize($this));
+    }
+
+    /**
+     * Populate info from serialized string
+     *
+     */
+    function Unserialize(&$serialized)
+    {
+        // load the util from the serialized code
+        $tmp = unserialize(base64_decode($serialized));
+        $this->Type = $tmp->Type;
+        $this->Username = $tmp->Username;
+        $this->Password = $tmp->Password;
+        $this->ConnectionString = $tmp->ConnectionString;
+        $this->DBName = $tmp->DBName;
+        $this->Type = $tmp->Type;
+        $this->TablePrefix = $tmp->TablePrefix;
+   		$this->Charset = $tmp->Charset;
+   		$this->BootstrapSQL = $tmp->BootstrapSQL;
+   		$this->Multibyte = $tmp->Multibyte;
+        }
+    
+}
+
+
+?>

+ 968 - 0
phreeze/libs/verysimple/Phreeze/Controller.php

@@ -0,0 +1,968 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/** import supporting libraries */
+require_once("verysimple/HTTP/RequestUtil.php");
+require_once("verysimple/HTTP/Context.php");
+require_once("verysimple/HTTP/BrowserDevice.php");
+require_once("verysimple/Authentication/Authenticator.php");
+require_once("verysimple/Authentication/Auth401.php");
+require_once("verysimple/Authentication/IAuthenticatable.php");
+require_once("verysimple/String/VerySimpleStringUtil.php");
+require_once("DataPage.php");
+require_once("Phreezer.php");
+require_once("Criteria.php");
+require_once("IRouter.php");
+require_once("GenericRouter.php");
+
+/**
+ * Controller is a base controller object used for an MVC pattern
+ * This controller uses Phreeze ORM and RenderEngine Template Engine
+ * This controller could be extended to use a differente ORM and
+ * Rendering engine as long as they implement compatible functions.
+ *
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc.
+ * @copyright  1997-2011 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    3.1
+ */
+abstract class Controller
+{
+	protected $Phreezer;
+	protected $RenderEngine;
+
+	/**
+	 * @var string ModelName is used by the base Controller class for certain functions in which
+	 * require knowledge of what Model is being used.  For example, when validating user input.
+	 * This may be defined in Init() if any of thes base Controller features will be used.
+	 */
+	protected $ModelName;
+
+	protected $Context;
+
+	/** @deprecated use RenderEngine */
+	protected $Smarty;
+
+	private $_router;
+	private $_cu;
+	public $GUID;
+	public $DebugOutput = "";
+	public $UnitTestMode = false;
+	public $CaptureOutputMode = false;
+	
+	private $_terminate = false;
+
+	/** Prefix this to the name of the view templates */
+	static $SmartyViewPrefix = "View";
+
+	/** the default mode used when calling 'Redirect' */
+	static $DefaultRedirectMode = "client";
+
+	/**
+	 * Constructor initializes the controller.  This method cannot be overriden.  If you need
+	 * to do something during construction, add it to Init
+	 *
+	 * @param Phreezer $phreezer Object persistance engine
+	 * @param IRenderEngine $renderEngine rendering engine
+	 * @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)
+	{
+		$this->Phreezer =& $phreezer;
+		$this->RenderEngine =& $renderEngine;
+
+		// for backwards compatibility
+		$this->Smarty =& $renderEngine;
+
+		$ra = RequestUtil::GetRemoteHost();
+		$this->GUID = $this->Phreezer->DataAdapter->GetDBName() . "_" . str_replace(".","_", $ra);
+
+		$this->_router = $router ? $router : new GenericRouter();
+
+		if ($context)
+		{
+			$this->Context =& $context;
+		}
+		else
+		{
+			$this->Context = new Context();
+			$this->Context->GUID = "CTX_" . $this->GUID;
+		}
+
+		if ($this->RenderEngine)
+		{
+			// assign some variables globally for the views
+			$this->Assign("CURRENT_USER",$this->GetCurrentUser());
+			$this->Assign("URL",$this->GetRouter());
+			$this->Assign("BROWSER_DEVICE",$this->GetDevice());
+	
+			// if feedback was persisted, set it
+			$this->Assign("feedback",$this->Context->Get("feedback"));
+			$this->Context->Set("feedback",null);
+		}
+
+		$this->Init();
+	}
+	
+	/**
+	 * Calling Terminate in Init will tell the dispatcher to halt execution
+	 * without calling the requested method/route
+	 */
+	protected function Terminate()
+	{
+		$this->_terminate = true;
+	}
+	
+	/**
+	 * Returns true if Terminate() has been fired
+	 * @return bool
+	 */
+	public function IsTerminated()
+	{
+		return $this->_terminate;
+	}
+
+	/**
+	 * Returns the router object used to convert url/uri to controller method
+	 * @return IRouter
+	 */
+	protected function GetRouter()
+	{
+		return $this->_router;
+	}
+
+	/**
+	 * Init is called by the base constructor immediately after construction.
+	 * This method must be implemented and provided an oportunity to
+	 * set any class-wide variables such as ModelName, implement
+	 * authentication for this Controller or any other class-wide initialization
+	 */
+	abstract protected function Init();
+
+	/**
+	 * Requires 401 Authentication.  If authentication fails, this function
+	 * terminates with a 401 header.  If success, sets CurrentUser and returns null.
+	 * @param IAuthenticatable any IAuthenticatable object
+	 * @param string http realm (basically the login message shown to the user)
+	 * @param string username querystring field (optional) if provided, the username can be passed via querystring instead of through the auth headers
+	 * @param string password querystring field (optional) if provided, the password can be passed via querystring instead of through the auth headers
+	 */
+	protected function Require401Authentication(IAuthenticatable $authenticatable, $realm = "Login Required", $qs_username_field = "", $qs_password_field = "")
+	{
+		$user = $this->Get401Authentication($authenticatable,$qs_username_field, $qs_password_field);
+
+		// we only want to output 401 headers if the user is not already authenticated
+		if (!$user)
+		{
+			if( $this->Get401AuthUsername($qs_username_field) )
+			{
+				// a username was provided, which means login failed
+				Auth401::OutputHeaders("Invalid Login");
+			}
+			else
+			{
+				// no username provided, which means prompt for username
+				Auth401::OutputHeaders($realm);
+			}
+		}
+	}
+	
+	/**
+	 * 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
+	 * @param IObserver $observer
+	 * @param bool $with_styles if true then basic styles will be output to the browser
+	 */
+	protected function StartObserving($observer = null, $with_styles = true)
+	{
+		if ($observer == null)
+		{
+			require_once "ObserveToBrowser.php";
+			$observer = new ObserveToBrowser();
+		}
+		
+		if ($with_styles)
+		{
+			$this->PrintOut("<style>.debug, .query, .info {font-family: courier new; border-bottom: solid 1px #999;} .debug {color: blue;} .query {color: green;}</style>");
+		}
+		
+		$this->Phreezer->AttachObserver($observer);
+	}
+
+	/**
+	 * accept username passed in either headers or querystring.  if a querystring parameter name is
+	 * provided, that will be checked first before the 401 auth headers
+	 * @param string $qs_username_field the querystring parameter to check for username (optional)
+	 */
+	protected function Get401AuthUsername($qs_username_field = "")
+	{
+		$qsv = $qs_username_field ? RequestUtil::Get($qs_username_field) : '';
+		return $qsv ? $qsv : Auth401::GetUsername();
+	}
+
+	/**
+	* accept password passed in either headers or querystring.  if a querystring parameter name is
+	* provided, that will be checked first before the 401 auth headers
+	* @param string $qs_password_field the querystring parameter to check for password (optional)
+	*/
+	protected function Get401AuthPassword($qs_password_field = "")
+	{
+		$qsv = $qs_password_field ? RequestUtil::Get($qs_password_field) : '';
+		return $qsv ? $qsv : Auth401::GetPassword();
+	}
+
+	/**
+	* Gets the user from 401 auth headers (or optionally querystring).  There are three scenarios
+	*   - The user is already logged authenticated = IAuthenticatable is returned
+	*   - The user was not logged in and valid login credentials were provided = SetCurrentUser is called and IAuthenticatable is returned
+	*   - The user was not logged in and invalid (or no) credentials were provided = NULL is returned
+	* @param IAuthenticatable any IAuthenticatable object
+	* @param string username querystring field (optional) if provided, the username can be passed via querystring instead of through the auth headers
+	* @param string password querystring field (optional) if provided, the password can be passed via querystring instead of through the auth headers
+	* @return IAuthenticatable or NULL
+	*/
+	protected function Get401Authentication(IAuthenticatable $authenticatable, $qs_username_field = "", $qs_password_field = "")
+	{
+		$user = null;
+		$username = $this->Get401AuthUsername($qs_username_field);
+
+		if( $username )
+		{
+			// username was provided so let's attempt a login
+			$password = $this->Get401AuthPassword($qs_password_field);
+
+			if ( $authenticatable->Login($username,$password) )
+			{
+				$user = $authenticatable;
+				$this->SetCurrentUser($authenticatable);
+			}
+		}
+		else
+		{
+			// no login info was provided so return whatever is in the session
+			// (which will be null if the user is not authenticated)
+			$user = $this->GetCurrentUser();
+		}
+
+		return $user;
+
+	}
+
+	/**
+	 * LoadFromForm should load the object specified by primary key = $pk, or
+	 * create a new instance of the object.  Then should overwrite any applicable
+	 * properties with user input.
+	 *
+	 * This method is used by ValidateInput for automation AJAX server-side validation.
+	 *
+	 * @param variant $pk the primary key (optional)
+	 * @return Phreezable a phreezable object
+	 */
+	protected function LoadFromForm($pk = null)
+	{
+		return null;
+	}
+
+	/**
+	 * Use as an alterative to print in order to capture debug output
+	 * @param string text to print
+	 * @param mime content type (example text/plain)
+	 */
+	protected function PrintOut($text,$contentType = null)
+	{
+		if ($this->CaptureOutputMode)
+		{
+			$this->DebugOutput .= $text;
+		}
+		else
+		{
+			if ($contentType) header("Content-type: " . $contentType);
+			print $text;
+		}
+	}
+
+	/**
+	 * Returns a BrowserDevice object with information about the browser
+	 * that is being used to view/execute this code.
+	 * @return BrowserDevice
+	 */
+	public function GetDevice()
+	{
+		return BrowserDevice::GetInstance();
+	}
+
+	/**
+	 * Displays the ListAll view for the primary model object.  Because the
+	 * datagrid is populated via ajax, no model data is populated here
+	 */
+	public function ListAll()
+	{
+		if (!$this->ModelName)
+		{
+			throw new Exception("ModelName must be defined in " . get_class($this) . "::ListAll");
+		}
+
+		// capture output instead of rendering if specified
+		if ($this->CaptureOutputMode)
+		{
+			$this->DebugOutput = $this->RenderEngine->fetch("View" . $this->ModelName .  "ListAll.tpl");
+		}
+		else
+		{
+			$this->RenderEngine->display("View" . $this->ModelName .  "ListAll.tpl");
+		}
+		//$this->_ListAll(null, Request::Get("page",1), Request::Get("limit",20));
+	}
+
+	/**
+	 * Displays the ListAll view for the primary model object in the event that
+	 * ajax will not be used.  The model data is populated
+	 *
+	 * @param Criteria $criteria
+	 * @param int $current_page number of the current page (for pagination)
+	 * @param int $limit size of the page (for pagination)
+	 */
+	protected function _ListAll(Criteria $criteria, $current_page, $limit)
+	{
+		if (!$this->ModelName)
+		{
+			throw new Exception("ModelName must be defined in " . get_class($this) . "::_ListAll.");
+		}
+
+		$page = $this->Phreezer->Query($this->ModelName,$criteria)->GetDataPage($current_page,$limit);
+		$this->RenderEngine->assign($this->ModelName . "DataPage", $page);
+		$this->RenderEngine->display("View" . $this->ModelName .  "ListAll.tpl");
+	}
+
+	/**
+	 * Renders a datapage as XML for use with a datagrid.  The optional $additionalProps allows
+	 * retrieval of properties from foreign relationships
+	 *
+	 * @param DataPage $page
+	 * @param Array $additionalProps (In the format Array("GetObjName1"=>"PropName","GetObjName2"=>"PropName1,PropName2")
+	 * @param Array $supressProps (In the format Array("PropName1","PropName2")
+	 * @param bool noMap set to true to render this DataPage regardless of whether there is a FieldMap
+	 */
+	protected function RenderXML(DataPage $page,$additionalProps = null, $supressProps = null, $noMap = false)
+	{
+		if (!is_array($supressProps)) $supressProps = array();
+
+		// never include these props
+		$suppressProps[] = "NoCache";
+		$suppressProps[] = "CacheLevel";
+		$suppressProps[] = "IsLoaded";
+		$suppressProps[] = "IsPartiallyLoaded";
+
+
+		$xml = "";
+		$xml .= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n";
+
+		$xml .= "<DataPage>\r\n";
+		$xml .= "<ObjectName>".htmlspecialchars($page->ObjectName)."</ObjectName>\r\n";
+		$xml .= "<ObjectKey>".htmlspecialchars($page->ObjectKey)."</ObjectKey>\r\n";
+		$xml .= "<TotalRecords>".htmlspecialchars($page->TotalResults)."</TotalRecords>\r\n";
+		$xml .= "<TotalPages>".htmlspecialchars($page->TotalPages)."</TotalPages>\r\n";
+		$xml .= "<CurrentPage>".htmlspecialchars($page->CurrentPage)."</CurrentPage>\r\n";
+		$xml .= "<PageSize>".htmlspecialchars($page->PageSize)."</PageSize>\r\n";
+
+		$xml .= "<Records>\r\n";
+
+		// get the fieldmap for this object type unless not specified
+		if ($noMap)
+		{
+			$fms = array();
+		}
+		else
+		{
+			try
+			{
+				$fms = $this->Phreezer->GetFieldMaps($page->ObjectName);
+			}
+			catch (exception $ex)
+			{
+				throw new Exception("The objects contained in this DataPage do not have a FieldMap.  Set noMap argument to true to supress this error: " . $ex->getMessage());
+			}
+		}
+
+		foreach ($page->Rows as $obj)
+		{
+			$xml .= "<" . htmlspecialchars($page->ObjectName) . ">\r\n";
+			foreach (get_object_vars($obj) as $var => $val)
+			{
+				if (!in_array($var,$supressProps))
+				{
+					// depending on what type of field this is, do some special formatting
+					$fm = isset($fms[$var]) ? $fms[$var]->FieldType : FM_TYPE_UNKNOWN;
+
+					if ($fm == FM_TYPE_DATETIME)
+					{
+						$val = strtotime($val) ? date("m/d/Y h:i A",strtotime($val)) : $val;
+					}
+					elseif ($fm == FM_TYPE_DATE)
+					{
+						$val = strtotime($val) ? date("m/d/Y",strtotime($val)) : $val;
+					}
+
+					// if the developer has added a property that is not a simple type
+					// we need to serialize it
+					if (is_array($val) || is_object($val))
+					{
+						$val = serialize($val);
+					}
+
+					$val = VerySimpleStringUtil::EncodeSpecialCharacters($val, true, true);
+
+					$xml .= "<" . htmlspecialchars($var) . ">" . $val . "</" . htmlspecialchars($var) . ">\r\n";
+				}
+			}
+
+
+			// Add any properties that we want from child objects
+			if ($additionalProps)
+			{
+				foreach ($additionalProps as $meth => $propPair)
+				{
+					$props = explode(",",$propPair);
+					foreach ($props as $prop)
+					{
+						$xml .= "<" . htmlspecialchars($meth . $prop) . ">" . htmlspecialchars($obj->$meth()->$prop) . "</" . htmlspecialchars($meth . $prop) . ">\r\n";
+					}
+				}
+		}
+
+			$xml .= "</" . htmlspecialchars($page->ObjectName) . ">\r\n";
+		}
+		$xml .= "</Records>\r\n";
+
+		$xml .= "</DataPage>\r\n";
+
+		// capture output instead of rendering if specified
+		if ($this->CaptureOutputMode)
+		{
+			$this->DebugOutput = $xml;
+		}
+		else
+		{
+			header('Content-type: text/xml');
+			print $xml;
+		}
+
+	}
+
+	/**
+	 * Render an array of IRSSFeedItem objects as an RSS feed
+	 * @param array $feedItems array of IRSSFeedItem objects
+	 * @param string $feedTitle
+	 * @param string $feedDescription
+	 */
+	protected function RenderRSS(Array $feedItems, $feedTitle = "RSS Feed", $feedDescription = "RSS Feed")
+	{
+		require_once('verysimple/RSS/Writer.php');
+		require_once('verysimple/RSS/IRSSFeedItem.php');
+
+		$baseUrl = RequestUtil::GetBaseURL();
+		$rssWriter = new RSS_Writer($feedTitle,$baseUrl,$feedDescription);
+		$rssWriter->setLanguage('us-en');
+		$rssWriter->addCategory("Items");
+
+		if (count($feedItems))
+		{
+			$count = 0;
+			foreach ($feedItems as $item)
+			{
+				$count++;
+
+				if ($item instanceof IRSSFeedItem)
+				{
+					$rssWriter->addItem(
+						$item->GetRSSTitle(), 						// title
+						$item->GetRSSLink($baseUrl), 				// link
+						$item->GetRSSDescription(), 				// description
+						$item->GetRSSAuthor(), 						// author
+						date(DATE_RSS, $item->GetRSSPublishDate()), // date
+						null, 										// source
+						$item->GetRSSGUID() 						// guid
+					);
+				}
+				else
+				{
+					$rssWriter->addItem("Item $count doesn't implment IRSSFeedItem","about:blank",'','Error',date(DATE_RSS) );
+				}
+			}
+		}
+		else
+		{
+			$rssWriter->addItem("No Items","about:blank",'','No Author',date(DATE_RSS) );
+		}
+
+		$rssWriter->writeOut();
+	}
+
+	/**
+	 * @deprecated use Controller->Context->Set instead
+	 */
+	protected function Set($var,$val)
+	{
+		return $this->Context->Set($var,$val);
+	}
+
+	/**
+	 * @deprecated use Controller->Context->Get instead
+	 */
+	protected function Get($var,$default=null)
+	{
+		return $this->Context->Get($var,$default);
+	}
+
+	/**
+	 * This method calls LoadFromForm to retrieve a model object populated with user
+	 * input.  The input is validated and a ValidationResponse is rendered in JSON format
+	 *
+	 * if Request::Get("SaveInline") is set then validate will call Save instead of
+	 * rendering JSON.  In which case, your Save method should render the ValidationResponse
+	 */
+	function ValidateInput()
+	{
+		require_once("ValidationResponse.php");
+		$vr = new ValidationResponse();
+
+		$save = RequestUtil::Get("SaveInline");
+
+		$obj = $this->LoadFromForm();
+
+		if (!is_object($obj))
+		{
+			$vr->Success = false;
+			$vr->Errors = array("Unknown"=>"LoadFromForm does not appear to be implemented.  Unable to validate");
+			$vr->Message = "LoadFromForm does not appear to be implemented.  Unable to validate";
+		}
+		elseif ($obj->Validate())
+		{
+			$vr->Success = true;
+		}
+		else
+		{
+			$vr->Success = false;
+			$vr->Errors = $obj->GetValidationErrors();
+			$vr->Message = "Validation Errors Occured";
+		}
+
+		// if the user requested to save inline, their Save method will take over from here
+		if ($vr->Success && $save)
+		{
+			$this->Save();
+		}
+		else
+		{
+			$this->RenderJSON($vr);
+		}
+	}
+
+
+	/**
+	 * Stub method
+	 */
+	function Save()
+	{
+		if ( !RequestUtil::Get("SaveInline") )
+		{
+			throw new Exception("Save is not implemented by this controller");
+		}
+
+		require_once("ValidationResponse.php");
+		$vr = new ValidationResponse();
+		$vr->Success = false;
+		$vr->Errors = array();
+		$vr->Message = "SaveInline is not implemented by this controller";
+		$this->RenderJSON($vr);
+	}
+
+	/**
+	 * Returns an array of all property names in the primary model
+	 *
+	 * @return array
+	 */
+	protected function GetColumns()
+	{
+		if (!$this->ModelName)
+		{
+			throw new Exception("ModelName must be defined in " . get_class($this) . "::GetColumns");
+		}
+
+		$counter = 0;
+		$props = array();
+		foreach (get_class_vars($this->ModelName)as $var => $val)
+		{
+			$props[$counter++] = $var;
+		}
+		return $props;
+	}
+
+	/**
+	 * Returns a unique ID for this session based on connection string and remote IP
+	 * This is a reasonable variable to use as a session variable because it ensures
+	 * that if other applications on the same server are running phreeze, there won't
+	 * be cross-application authentication issues.  Additionally, the remote ip
+	 * helps to make session hijacking more difficult
+	 *
+	 * @deprecated use $controller->GUID instead
+	 * @return string
+	 */
+	private function GetGUID()
+	{
+		return $this->GUID;
+	}
+
+	/**
+	 * Clears the current authenticated user from the session
+	 */
+	public function ClearCurrentUser()
+	{
+		$this->_cu = null;
+		Authenticator::ClearAuthentication($this->GUID);
+	}
+
+	/**
+	 * Sets the given user as the authenticatable user for this session.
+	 *
+	 * @param IAuthenticatable The user object that has authenticated
+	 */
+	protected function SetCurrentUser(IAuthenticatable $user)
+	{
+		$this->_cu = $user;
+		Authenticator::SetCurrentUser($user,$this->GUID);
+
+		// assign some global variables to the view
+		$this->Assign("CURRENT_USER",$this->GetCurrentUser());
+	}
+
+	/**
+	 * Returns the currently authenticated user, or null if a user has not been authenticated.
+	 *
+	 * @return IAuthenticatable || null
+	 */
+	protected function GetCurrentUser()
+	{
+		if (!$this->_cu)
+		{
+			$this->Phreezer->Observe("Loading CurrentUser from Session");
+			$this->_cu = Authenticator::GetCurrentUser($this->GUID);
+
+			if ($this->_cu )
+			{
+				if (get_class($this->_cu) == "__PHP_Incomplete_Class")
+				{
+					// this happens if the class used for authentication was not included before the session was started
+					$tmp = print_r($this->_cu,1);
+					$parts1 = explode("__PHP_Incomplete_Class_Name] => ",$tmp);
+					$parts2 = explode("[",$parts1[1]);
+					$name = trim($parts2[0]);
+
+					Authenticator::ClearAuthentication($this->GUID);
+					throw new Exception("The class definition used for authentication '$name' must be defined (included) before the session is started, for example in _app_config.php.");
+				}
+				else
+				{
+					// refresh the current user if the object supports it
+					if (method_exists($this->_cu, 'Refresh')) $this->_cu->Refresh($this->Phreezer);
+				}
+			}
+		}
+		else
+		{
+			$this->Phreezer->Observe("Using previously loaded CurrentUser");
+		}
+
+		return $this->_cu;
+	}
+
+	/**
+	 * 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
+	 *
+	 * @param int $permission Permission ID requested
+	 * @param string $on_fail_action (optional) The action to redirect if require fails
+	 * @param string $not_authenticated_feedback (optional) Feedback to forward to the on_fail_action if user is not logged in
+	 * @param string $permission_denied_feedback (optional) Feedback to forward to the on_fail_action if user is logged in but does not have permission
+	 * @throws AuthenticationException
+	 */
+	protected function RequirePermission($permission, $on_fail_action = "", $not_authenticated_feedback = "Please login to access this page", $permission_denied_feedback = "You are not authorized to view this page and/or your session has expired")
+	{
+		$this->Phreezer->Observe("Checking For Permission '$permission'");
+		$cu = $this->GetCurrentUser();
+
+		if (!$cu || !$cu->IsAuthorized($permission))
+		{
+			$message = !$cu || $cu->IsAnonymous()
+				? $not_authenticated_feedback
+				: $permission_denied_feedback;
+			
+			if ($on_fail_action)
+			{
+
+				$this->Redirect($on_fail_action,$message);
+			}
+			else
+			{
+				$ex = new AuthenticationException($message,500);
+				$this->Crash("Permission Denied",500,$ex);
+			}
+		}
+	}
+
+	/**
+	 * Assigns a variable to the view
+	 *
+	 * @param string $varname
+	 * @param variant $varval
+	 */
+	protected function Assign($varname,$varval)
+	{
+		$this->RenderEngine->assign($varname,$varval);
+	}
+
+	/**
+	 * Renders the specified view
+	 *
+	 * @param string $view (optional) if not provided, the view is automatically bound using the class and method name
+	 * @param string $format (optional) defaults to $self::SmartyViewPrefix
+	 */
+	protected function Render($view="",$format = null)
+	{
+		$isSmarty = (strpos(get_class($this->RenderEngine),"Smarty") > -1);
+
+		if ($isSmarty && $format == null) $format = self::$SmartyViewPrefix;
+
+		if ($format == null) $format = '';
+
+		if ($view == "")
+		{
+			// automatic binding
+			$backtrace = debug_backtrace();
+			$view = str_replace("Controller","", $backtrace[1]['class']) . $backtrace[1]['function'];
+		}
+
+		// if the render engine is Smarty then add the '.tpl' suffix
+		$viewPath = $isSmarty ? $format.$view.".tpl" : $format.$view;
+
+		// capture output instead of rendering if specified
+		if ($this->CaptureOutputMode)
+		{
+			$this->DebugOutput = $this->RenderEngine->fetch($viewPath);
+		}
+		else
+		{
+			$this->RenderEngine->display($viewPath);
+		}
+	}
+
+	/**
+	 * Renders the given value as JSON
+	 *
+	 * @param variant the variable, array, object, etc to be rendered as JSON
+	 * @param string if a callback is provided, this will be rendered as JSONP
+	 * @param bool if true then objects will be returned ->GetObject() (only supports ObjectArray or individual Phreezable or Reporter object)
+	 * @param array  (only relvant if useSimpleObject is true) options array passed through to Phreezable->ToString()
+	 * @param bool set to 0 to leave data untouched.  set to 1 to always force value to UTF8. set to 2 to only force UTF8 if an encoding error occurs (WARNING: options 1 or 2 will likely result in unreadable characters.  The recommended fix is to set your database charset to utf8)
+	 */
+	protected function RenderJSON($var, $callback = "",$useSimpleObject = false, $options = null, $forceUTF8 = 0)
+	{
+		$obj = null;
+
+		if (is_a($var,'DataSet') || is_a($var,'DataPage'))
+		{
+			// if a dataset or datapage can be converted directly into an array without enumerating twice
+			$obj = $var->ToObjectArray($useSimpleObject,$options);
+		}
+		else if ($useSimpleObject)
+		{
+			// we need to figure out what type
+			if (is_array($var) || is_a($var,'SplFixedArray')  )
+			{
+				$obj = array();
+				foreach ($var as $item)
+				{
+					$obj[] = $item->ToObject($options);
+				}
+			}
+			elseif (is_a($var,'Phreezable') || is_a($var,'Reporter'))
+			{
+				$obj = $var->ToObject($options);
+			}
+			else
+			{
+				throw new Exception('RenderJSON could not determine the type of object to render');
+			}
+		}
+		else
+		{
+			$obj = $var;
+		}
+
+		if ($forceUTF8 == 1) $this->UTF8Encode($obj);
+
+		try
+		{
+			$output = json_encode($obj);
+		}
+		catch (Exception $ex)
+		{
+			if (strpos($ex->getMessage(),'Invalid UTF-8') !== false)
+			{
+				// a UTF encoding problem has been encountered
+				if ($forceUTF8 == 2) 
+				{
+					$this->UTF8Encode($obj);
+					$output = json_encode($obj);
+				}
+				else
+				{
+					throw new Exception('The object to be encoded contains invalid UTF-8 data.  Please verify your database character encoding or alternatively set the Controller::RenderJSON $forceUTF8 parameter to 1 or 2.');
+				}
+			}
+			else
+			{
+				// we don't know what this is so don't try to handle it here
+				throw $ex;
+			}
+		}
+
+		if ($callback) $output = "$callback(" . $output . ")";
+
+		// capture output instead of rendering if specified
+		if ($this->CaptureOutputMode)
+		{
+			$this->DebugOutput = $output;
+		}
+		else
+		{
+			@header($callback ? 'Content-type: text/plain' : 'Content-type: application/json');
+			print $output;
+		}
+	}
+
+
+	/**
+	 * Send a crash message to the browser and terminate
+	 *
+	 * @param string $errmsg text message to display
+	 * @param int $code used for support for this error
+	 * @param Exception $exception if exception was thrown, can be provided for more info
+	 */
+	protected function Crash($errmsg = "Unknown Error", $code = 0, $exception = null)
+	{
+		$ex = $exception ? $exception : new Exception($errmsg, $code);
+		throw $ex;
+	}
+
+	/**
+	 * Redirect to the appropriate page based on the action.  This function will
+	 * 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 array $params
+	 * @param string $mode (client | header) default = Controller::$DefaultRedirectMode
+	 */
+	protected function Redirect($action, $feedback = "", $params = "", $mode = "")
+	{
+		if (!$mode) $mode = self::$DefaultRedirectMode;
+
+		$params = is_array($params) ? $params : array();
+
+		if ($feedback)
+		{
+			// $params["feedback"] = $feedback;
+			$this->Context->Set("feedback",$feedback);
+		}
+
+		// support for deprecated Controller/Method format
+		list($controller,$method) = explode(".", str_replace("/",".",$action));
+
+		$url = $this->GetRouter()->GetUrl($controller,$method,$params);
+
+		// capture output instead of rendering if specified
+		if ($this->CaptureOutputMode)
+		{
+			if ($mode == 'client')
+			{
+				$this->RenderEngine->assign("url",$url);
+				$this->DebugOutput = $this->RenderEngine->fetch("_redirect.tpl");
+			}
+			else
+			{
+				$this->DebugOutput = 'Location: ' . $url;
+			}
+		}
+		else
+		{
+
+			if ($mode == 'client')
+			{
+				$this->RenderEngine->assign("url",$url);
+				$this->RenderEngine->display("_redirect.tpl");
+			}
+			else
+			{
+				header('Location: ' . $url) ;
+			}
+
+		}
+
+		// don't exit if we are unit testing because it will stop all further tests
+		if (!$this->UnitTestMode) exit;
+
+	}
+
+	/**
+	 * Does a recursive UTF8 encoding on a string/array/object.  This is used
+	 * when json encoding fails due to non UTF8 in the database that cannot
+	 * be repaired with any other means.
+	 * 
+	 * NOTE: this does not have a return value.  value is passed by reference and updated
+	 * 
+	 * @param variant $input
+	 */
+	private function UTF8Encode(&$input) 
+	{
+		if (is_string($input)) 
+		{
+			// pop recursion here
+			$input = utf8_encode($input);
+		} 
+		else if (is_array($input)) 
+		{
+			foreach ($input as &$value) 
+			{
+				$this->UTF8Encode($value);
+			}
+			unset($value);
+		} 
+		else if (is_object($input)) 
+		{
+			$vars = array_keys(get_object_vars($input));
+			foreach ($vars as $var) 
+			{
+				$this->UTF8Encode($input->$var);
+			}
+		}
+	}
+
+    /**
+    * Throw an exception if an undeclared method is accessed
+    *
+	* @access     public
+	* @param      string $name
+	* @param      variant $vars
+	* @throws     Exception
+	*/
+	function __call($name,$vars = null)
+	{
+		throw new Exception(get_class($this) . "::" . $name . " is not implemented");
+	}
+}
+
+?>

+ 500 - 0
phreeze/libs/verysimple/Phreeze/Criteria.php

@@ -0,0 +1,500 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/** import supporting libraries */
+require_once("DataAdapter.php");
+require_once("CriteriaFilter.php");
+require_once("verysimple/IO/Includer.php");
+
+/**
+ * Criteria is a base object that is passed into Phreeze->Query for retreiving
+ * records based on specific criteria
+ *
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc.
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    2.2
+ */
+class Criteria
+{
+	protected $_join;
+	protected $_where;
+	protected $_where_delim;
+	protected $_order;
+	protected $_order_delim;
+	protected $_is_prepared;
+
+	protected $_map_object_class;
+	
+	private $_fieldmaps;
+	private $_keymaps;
+	
+	private $_constructor_where;
+	private $_constructor_order;
+	private $_set_order;
+	
+	private $_and = array();
+	private $_or = array();
+	
+	public $PrimaryKeyField;
+	public $PrimaryKeyValue;
+	
+	/**
+	 * @var $Filters a CriteriaFilter or array of CriteriaFilters to be applied to the query
+	 */
+	public $Filters;
+	
+	public function __construct($where = "", $order = "")
+	{
+		$this->_constructor_where = $where;
+		$this->_constructor_order = $order;
+		
+		$this->_where = $where;
+		$this->_order = $order;
+		
+		$this->Init();
+	}
+	
+	/**
+	 * Init is called directly after construction and can be overridden.  If the
+	 * name of the Criteria class is not ObjectClassCriteria, then this method
+	 * must be overriden and _map_object_class should be set to the correct
+	 * name of the DAO Map class
+	 */
+	protected function Init()
+	{
+		$this->_map_object_class = str_replace("Criteria","Map",get_class($this));
+	}
+	
+	/**
+	 * Add a CriteriaFilter to the criteria for custom filtering of results
+	 * @param CriteriaFilter $filter
+	 */
+	public function AddFilter(CriteriaFilter $filter)
+	{
+		if (!$this->Filters) $this->Filters = array();
+		$this->Filters[] = $filter;
+	}
+
+	/**
+	 * Return an array of CriteriaFilters that have been added to this criteria
+	 * @return array
+	 */
+	public function GetFilters()
+	{
+		return $this->Filters;
+	}
+	
+	/**
+	 * Remove all filters that are currently attached
+	 */
+	public function ClearFilters()
+	{
+		$this->Filters = null;
+	}
+	
+	/**
+	 * Adds a criteria to be joined w/ an "and" statement.
+	 * Criterias to foreign objects may be added as long as they
+	 * have an immediate relationship to the foreign table
+	 * 
+	 * @param Criteria
+	 * @param string [optional] id of the foreign key map. If the same table is joined
+	 * multiple times, then you should specify which keymap to use
+	 */
+	public function AddAnd(Criteria $criteria, $keymap_id = null)
+	{
+		$this->_and[] = $criteria;
+	}
+		
+	/**
+	 * Return any and criterias that have been added to this criteria
+	 * @return array
+	 */
+	public function GetAnds()
+	{
+		return $this->_and;
+	}
+	
+	/**
+	 * Escape values for insertion into a SQL query string
+	 * @return string
+	 */
+	public function Escape($val)
+	{
+		return DataAdapter::Escape($val);
+	}
+
+	/**
+	 * Returns DataAdapter::GetQuotedSql($val)
+	 * @param variant $val to be quoted
+	 * @return string
+	 */
+	public function GetQuotedSql($val)
+	{
+		return DataAdapter::GetQuotedSql($val);
+	}
+	
+	/**
+	 * Adds a criteria to be joined w/ an "or" statement.
+	 * Criterias to foreign objects may be added as long as they
+	 * have an immediate relationship to the foreign table
+	 * 
+	 * @param Criteria
+	 * @param string [optional] id of the foreign key map. If the same table is joined
+	 * multiple times, then you should specify which keymap to use
+	 */
+	public function AddOr(Criteria $criteria, $keymap_id = null)
+	{
+		$this->_or[] = $criteria;
+	}
+	
+	/**
+	 * Return any 'or' criterias that have been added to this criteria
+	 * @return array
+	 */
+	public function GetOrs()
+	{
+		return $this->_or;
+	}
+	
+	/**
+	 * Reset the Criteria for re-use.  This is called by querybuilder after the criteria has been used
+	 * to generate SQL.  It can be called manually as well.
+	 */
+	public function Reset()
+	{
+		$this->_is_prepared = false;
+		$this->_where = $this->_constructor_where;
+		$this->_order = $this->_constructor_order;
+	}
+	
+	/** Prepare is called just prior to execution and will fire OnPrepare after it completes
+	 * If this is a base Criteria class, then we can only do a lookup by PrimaryKeyField or
+	 * else raw SQL must be provided during construction.  _Equals, _BeginsWith can only be
+	 * used by inherited Criteria classes because we don't know what table this is associated
+	 * with, so we can't translate property names to column names.
+	 *
+	 */
+	private final function Prepare()
+	{
+		if (!$this->_is_prepared)
+		{
+		
+			if (get_class($this) == "Criteria")
+			{
+				if ($this->PrimaryKeyField)
+				{
+					// PrimaryKeyField property was specified. this might be coming from $phreezer->Get
+					$this->_where = " " . $this->PrimaryKeyField ." = '". $this->Escape($this->PrimaryKeyValue) . "'";
+				}
+				// else {raw SQL was likely provided in the constructor. this might be coming from $phreezer->GetOneToMany}
+			}
+			else
+			{
+				// loop through all of the properties and attempt to 
+				// build a query based on any values that have been set
+				$this->_where = '';
+				$this->_where_delim = '';
+				
+				$props = get_object_vars($this);
+				foreach ($props as $prop => $val)
+				{
+					// TODO: tighten this up a bit to reduce redundant code
+					if ($prop == "Filters" && isset($val) && (is_array($val) || is_a($val, 'CriteriaFilter')) )
+					{
+						// a filter object will take care of generating it's own where statement
+						
+						// normalize the input to accept either an individual filter or multiple filters
+						$filters = (is_array($val)) ? $val : array($val);
+						
+						foreach ($filters as $filter)
+						{
+							$this->_where .= $this->_where_delim . ' ' . $filter->GetWhere($this);
+							$this->_where_delim = " and";
+						}
+					}
+					elseif (substr($prop,-7) == "_Equals" && strlen($this->$prop))
+					{
+						$dbfield = $this->GetFieldFromProp(str_replace("_Equals","",$prop));
+						$this->_where .= $this->_where_delim . " " . $dbfield ." = ". $this->GetQuotedSql($val) . "";
+						$this->_where_delim = " and";
+					}
+					elseif (substr($prop,-10) == "_NotEquals" && strlen($this->$prop))
+					{
+						$dbfield = $this->GetFieldFromProp(str_replace("_NotEquals","",$prop));
+						$this->_where .= $this->_where_delim . " " . $dbfield ." != ". $this->GetQuotedSql($val) . "";
+						$this->_where_delim = " and";
+					}
+					elseif (substr($prop,-8) == "_IsEmpty" && $this->$prop)
+					{
+						$dbfield = $this->GetFieldFromProp(str_replace("_IsEmpty","",$prop));
+						$this->_where .= $this->_where_delim . " " . $dbfield ." = ''";
+						$this->_where_delim = " and";
+					}
+					elseif (substr($prop,-11) == "_IsNotEmpty" && $this->$prop)
+					{
+						$dbfield = $this->GetFieldFromProp(str_replace("_IsNotEmpty","",$prop));
+						$this->_where .= $this->_where_delim . " " . $dbfield ." != ''";
+						$this->_where_delim = " and";
+					}
+					elseif (substr($prop,-7) == "_IsLike" && strlen($this->$prop))
+					{
+						$dbfield = $this->GetFieldFromProp(str_replace("_IsLike","",$prop));
+						$this->_where .= $this->_where_delim . " " . $dbfield ." like '%". $this->Escape($val) . "%'";
+						$this->_where_delim = " and";
+					}
+					elseif (substr($prop,-10) == "_IsNotLike" && strlen($this->$prop))
+					{
+						$dbfield = $this->GetFieldFromProp(str_replace("_IsNotLike","",$prop));
+						$this->_where .= $this->_where_delim . " " . $dbfield ." not like '%". $this->Escape($val) . "%'";
+						$this->_where_delim = " and";
+					}
+					elseif (substr($prop,-11) == "_BeginsWith" && strlen($this->$prop))
+					{
+						$dbfield = $this->GetFieldFromProp(str_replace("_BeginsWith","",$prop));
+						$this->_where .= $this->_where_delim . " " . $dbfield ." like '". $this->Escape($val) . "%'";
+						$this->_where_delim = " and";
+					}
+					elseif (substr($prop,-9) == "_EndsWith" && strlen($this->$prop))
+					{
+						$dbfield = $this->GetFieldFromProp(str_replace("_EndsWith","",$prop));
+						$this->_where .= $this->_where_delim . " " . $dbfield ." like '%". $this->Escape($val) . "'";
+						$this->_where_delim = " and";
+					}
+					elseif (substr($prop,-12) == "_GreaterThan" && strlen($this->$prop))
+					{
+						$dbfield = $this->GetFieldFromProp(str_replace("_GreaterThan","",$prop));
+						$this->_where .= $this->_where_delim . " " . $dbfield ." > ". $this->GetQuotedSql($val) . "";
+						$this->_where_delim = " and";
+					}
+					elseif (substr($prop,-19) == "_GreaterThanOrEqual" && strlen($this->$prop))
+					{
+						$dbfield = $this->GetFieldFromProp(str_replace("_GreaterThanOrEqual","",$prop));
+						$this->_where .= $this->_where_delim . " " . $dbfield ." >= ". $this->GetQuotedSql($val) . "";
+						$this->_where_delim = " and";
+					}
+					elseif (substr($prop,-9) == "_LessThan" && strlen($this->$prop))
+					{
+						$dbfield = $this->GetFieldFromProp(str_replace("_LessThan","",$prop));
+						$this->_where .= $this->_where_delim . " " . $dbfield ." < ". $this->GetQuotedSql($val) . "";
+						$this->_where_delim = " and";
+					}
+					elseif (substr($prop,-16) == "_LessThanOrEqual" && strlen($this->$prop))
+					{
+						$dbfield = $this->GetFieldFromProp(str_replace("_LessThanOrEqual","",$prop));
+						$this->_where .= $this->_where_delim . " " . $dbfield ." <= ". $this->GetQuotedSql($val) . "";
+						$this->_where_delim = " and";
+					}
+					elseif (substr($prop,-10) == "_BitwiseOr" && strlen($this->$prop))
+					{
+						$dbfield = $this->GetFieldFromProp(str_replace("_BitwiseOr","",$prop));
+						$this->_where .= $this->_where_delim . " (" . $dbfield ." | '". $this->Escape($val) . ")";
+						$this->_where_delim = " and";
+					}
+					elseif (substr($prop,-11) == "_BitwiseAnd" && strlen($this->$prop))
+					{
+						$dbfield = $this->GetFieldFromProp(str_replace("_BitwiseAnd","",$prop));
+						$this->_where .= $this->_where_delim . " (" . $dbfield ." & ". $this->Escape($val) . ")";
+						$this->_where_delim = " and";
+					}
+					elseif (substr($prop,-16) == "_LiteralFunction" && strlen($this->$prop))
+					{
+						$dbfield = $this->GetFieldFromProp(str_replace("_LiteralFunction","",$prop));
+						$this->_where .= $this->_where_delim . " (" . $dbfield ." ". $val . ")";
+						$this->_where_delim = " and";
+					}
+					elseif (substr($prop,-3) == "_In" && isset($val) && is_array($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
+						// 100% guarantee this, though, we can choose a highly unlikely value
+						// that will never return a match under ordinary circumstances
+						if (count($val) == 0)
+						{
+							array_push($val,"$prop EMPTY PHREEZE CRITERIA ARRAY");
+						}
+						
+						$dbfield = $this->GetFieldFromProp(str_replace("_In","",$prop));
+						$this->_where .= $this->_where_delim . " " . $dbfield ." in (";
+						$indelim = "";
+						foreach ($val as $n)
+						{ 
+							$this->_where .= $indelim . "'" . $this->Escape($n) . "'";
+							$indelim = ",";
+						}
+						$this->_where .= ")";
+						$this->_where_delim = " and";
+					}
+					elseif (substr($prop,-6) == "_NotIn" && isset($val) && is_array($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
+						// 100% guarantee this, though, we can choose a highly unlikely value
+						// that will never return a match under ordinary circumstances
+						if (count($val) == 0)
+						{
+							array_push($val,"$prop EMPTY PHREEZE CRITERIA ARRAY");
+						}
+						
+						$dbfield = $this->GetFieldFromProp(str_replace("_NotIn","",$prop));
+						$this->_where .= $this->_where_delim . " " . $dbfield ." not in (";
+						$indelim = "";
+						foreach ($val as $n)
+						{ 
+							$this->_where .= $indelim . "'" . $this->Escape($n) . "'";
+							$indelim = ",";
+						}
+						$this->_where .= ")";
+						$this->_where_delim = " and";
+					}
+				}
+			}
+
+			// prepend the sql so the statement will work correctly
+			if ($this->_where)
+			{
+				$this->_where = " where " . $this->_where;
+			}
+			
+			// if the user has called SetOrder then use that for the order
+			if ($this->_set_order)
+			{
+				$this->_order = $this->_set_order;	
+			}
+
+			if ($this->_order)
+			{
+				$this->_order = " order by " . $this->_order;
+			}
+
+			$this->OnPrepare();
+			$this->_is_prepared = true;
+		}
+	}
+	
+	public function OnPrepare() {}
+
+	public final function GetWhere()
+	{
+		$this->Prepare();
+		return $this->_where;
+	}
+	
+
+	public final function GetJoin()
+	{
+		$this->Prepare();
+		return $this->_join;
+	}
+	
+	public final function GetOrder()
+	{
+		$this->Prepare();
+		return $this->_order;
+	}
+
+	/**
+	 * Adds an object property to the order by clause.  If any sorting needs to be done
+	 * on foreign tables, then for the moment, you need to override this method and
+	 * handle it manually.  You can call this method repeatedly to add more than
+	 * one property for sorting.
+	 *
+	 * @param string $property the name of the object property (or '?' for random order)
+	 * @param bool $desc (optional) set to true to sort in descending order (default false)
+	 */
+	public function SetOrder($property,$desc = false)
+	{
+		if (!$property)
+		{
+			// no property was specified.
+			return;
+		}
+		
+		$this->_order_delim = ($this->_set_order) ? "," : "";
+		
+		if($property == '?')
+		{
+			$this->_set_order = "RAND()" . $this->_order_delim . $this->_set_order;
+		}
+		else
+		{
+			$colname = $this->GetFieldFromProp($property);
+			$this->_set_order .= $this->_order_delim . $colname . ($desc ? " desc" : "");	
+		}
+
+	}
+	
+	private function InitMaps()
+	{
+		if (!$this->_fieldmaps)
+		{
+			// we have to open the file to get the fieldmaps
+			$mapname = $this->_map_object_class;
+			$this->IncludeMap($mapname);
+			
+			$this->_fieldmaps = call_user_func(array($mapname,"GetFieldMaps"));
+			$this->_keymaps = call_user_func(array($mapname,"GetKeyMaps"));
+			
+		}
+	}
+	
+	
+	/**
+	* If the map class is not already defined, attempts to require_once the definition.
+	* If the Map file cannot be located, an exception is thrown
+	*
+	* @access public
+	* @param string $objectclass The name of the object map class
+	*/
+	public function IncludeMap($objectclass)
+	{
+		try
+		{
+			Includer::RequireClass($objectclass,"Model/DAO/");
+		}
+		catch (IncludeException $ex)
+		{
+			throw new Exception($ex->getMessage() . '.  If a map file does not exist then ' . get_class($this) . ' can implement GetFieldFromProp instead.');
+		}
+	}
+	
+	protected function GetFieldMaps()
+	{
+		$this->InitMaps();
+		return $this->_fieldmaps;
+	}
+	
+	protected function GetKeyMaps()
+	{
+		$this->InitMaps();
+		return $this->_keymaps;
+	}
+	
+
+	public function GetFieldFromProp($propname)
+	{
+		if (get_class($this) == "Criteria")
+		{
+			throw new Exception("Phreeze is unable to determine field mapping.  The base Criteria class should only be used to query by primary key without sorting");
+		}
+
+		$fms = $this->GetFieldMaps();
+				
+		// make sure this property is defined
+		if (!isset($fms[$propname]))
+		{
+			throw new Exception(get_class($this) . " is unable to determine the database column for the property: '$propname'");
+		}
+		//print_r($this->_fieldmaps);
+		$fm = $fms[$propname];
+		
+		return $fm->FieldType == FM_CALCULATION ? "(" . $fm->ColumnName . ")" : "`" . $fm->TableName . "`.`" . $fm->ColumnName . "`";
+
+	}
+}
+
+?>

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

@@ -0,0 +1,62 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/** import supporting libraries */
+
+
+/**
+ * CriteriaFilter allows arbitrary filtering based on one or more fields
+ *
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc.
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    2.2
+ */
+class CriteriaFilter
+{
+	static $TYPE_SEARCH = 1;
+	
+	public $propertyNames;
+	public $Value;
+	public $Type;
+	
+	/**
+	 * 
+	 * @param variant $propertyNames comma-delimited string or array of property names (ie haystack)
+	 * @param string $value search term (ie needle)
+	 * @param int $type (default CriteriaFilter::TYPE_SEARCH)
+	 */
+	public function __construct($propertyNames,$value,$type = null)
+	{
+		$this->PropertyNames = $propertyNames;
+		$this->Value = $value;
+		$this->Type = ($type == null) ? self::$TYPE_SEARCH : $type;
+	}
+	
+	/**
+	 * Return the "where" portion of the SQL statement (without the where prefix)
+	 * @param Criteria $criteria the Criteria object to which this filter has been added
+	 */
+	public function GetWhere($criteria)
+	{
+		if ($this->Type != self::$TYPE_SEARCH) throw new Exception('Unsupported Filter Type');
+		
+		// normalize property names as an array
+		$propertyNames = (is_array($this->PropertyNames)) ? $this->PropertyNames : explode(',', $this->PropertyNames);
+		
+		$where = ' (';
+		$orDelim = '';
+		foreach ($propertyNames as $propName)
+		{
+			$dbfield = $criteria->GetFieldFromProp($propName);
+			$where .= $orDelim . $criteria->Escape($dbfield) ." like ". $criteria->GetQuotedSql($this->Value) . "";
+			$orDelim = ' or ';
+		}
+		$where .= ') ';
+		
+		return $where;
+	}
+}
+
+?>

+ 437 - 0
phreeze/libs/verysimple/Phreeze/DataAdapter.php

@@ -0,0 +1,437 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/** import supporting libraries */
+require_once("IObservable.php");
+require_once("ConnectionSetting.php");
+require_once("DataPage.php");
+require_once("DataSet.php");
+require_once("QueryBuilder.php");
+require_once("verysimple/DB/DataDriver/IDataDriver.php");
+require_once("verysimple/IO/Includer.php");
+
+/**
+ * DataAdapter abstracts and provides access to the data store
+ *
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc. <[email protected]>
+ * @copyright  1997-2005 VerySimple Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    2.0
+ */
+class DataAdapter implements IObservable
+{
+    
+	/**
+	 * @var ConnectionSetting
+	 */
+    public $ConnectionSetting;
+	
+    private $_observers = Array();
+	private $_dbconn;
+	private $_dbopen;
+	private $_driver;
+	
+	/** @var used internally to keep track of communication error re-tries */
+	private $_num_retries = 0;
+	
+	/** @var instance of the driver class, used for escaping */
+	static $DRIVER_INSTANCE = null;
+	
+	/** @var bool if true the data adapter attempt one retry when a communication error occurs */
+	static $RETRY_ON_COMMUNICATION_ERROR = false;
+	
+    /**
+    * Contructor initializes the object
+    *
+    * @access public
+    * @param ConnectionSetting $csetting
+    * @param Observable $listener
+    * @param IDataDriver (optional) if not provided, then DataAdapter will attempt to instantiate one based on ConnectionSetting->Type
+    */
+    function __construct($csetting, $listener = null, IDataDriver $driver = null)
+    {
+    	$this->_driver = $driver;
+    	
+    	
+    	if ($this->_driver == null) 
+    	{
+    		// the driver was not explicitly provided so we will try to create one from 
+    		// the connection setting based on the database types that we do know about
+    		switch($csetting->Type)
+    		{
+    			case "mysql":
+					include_once("verysimple/DB/DataDriver/MySQL.php");
+    				$this->_driver  = new DataDriverMySQL();
+    				break;
+    			case "mysqli":
+					include_once("verysimple/DB/DataDriver/MySQLi.php");
+    				$this->_driver  = new DataDriverMySQLi();
+    				break;
+    			case "sqlite":
+					include_once("verysimple/DB/DataDriver/SQLite.php");
+    				$this->_driver  = new DataDriverSQLite();
+    				break;
+    			default:
+    				try
+    				{
+    					Includer::IncludeFile("verysimple/DB/DataDriver/".$csetting->Type.".php");
+						$classname = "DataDriver" . $csetting->Type;
+    					$this->_driver  = new $classname();
+    				}
+    				catch (IncludeException $ex)
+		    		{
+		    			throw new Exception('Unknown DataDriver "' . $csetting->Type . '" specified in connection settings');
+		    		}
+    				break;
+    		}
+
+    	}
+    	
+    	DataAdapter::$DRIVER_INSTANCE = $this->_driver;
+    	
+		$this->AttachObserver($listener);
+		$this->ConnectionSetting =& $csetting;
+		$this->Observe("DataAdapter Instantiated", OBSERVE_DEBUG);
+	}
+	
+	
+	/**
+     * Destructor closes the db connection.
+     *
+     * @access     public
+     */    
+	function __destruct()
+	{
+		$this->Observe("DataAdapter Destructor Firing...",OBSERVE_DEBUG);
+		$this->Close();
+	}
+	
+    /**
+	 * Returns name of the DB currently in use
+	 *
+	 * @access public
+	 * @return string
+	 */	
+	function GetDBName()
+	{
+		return $this->ConnectionSetting->DBName;
+	}
+	
+    /**
+	 * Opens a connection to the data server and selects the specified database
+	 *
+	 * @access public
+	 */	
+	function Open()
+	{
+		$this->Observe("Opening Connection...",OBSERVE_DEBUG);
+		
+		if ($this->_dbopen)
+		{
+			$this->Observe("Connection Already Open",OBSERVE_WARN);
+		}
+		else
+		{
+			try
+			{
+				$this->_dbconn = $this->_driver->Open(
+					$this->ConnectionSetting->ConnectionString, 
+					$this->ConnectionSetting->DBName, 
+					$this->ConnectionSetting->Username, 
+					$this->ConnectionSetting->Password,
+					$this->ConnectionSetting->Charset,
+					$this->ConnectionSetting->BootstrapSQL);
+				
+				$this->_num_retries = 0;
+			}
+			catch (Exception $ex)
+			{
+				// retry one time a communication error occurs
+				if ($this->_num_retries == 0 && DataAdapter::$RETRY_ON_COMMUNICATION_ERROR && $this->IsCommunicationError($ex))
+				{
+					$this->_num_retries++;
+					$this->Observe("Communication error.  Retry attempt " . $this->_num_retries, OBSERVE_WARN);
+					sleep(2); // slight delay to prevent throttling
+					return $this->Open();
+				}
+
+				$msg = 'Error Opening DB: ' . $ex->getMessage() . ' (retry attempts: '.$this->_num_retries.')';
+					
+				$this->Observe($msg,OBSERVE_FATAL);
+				throw new Exception($msg,$ex->getCode(),$ex->getPrevious());
+			}
+			
+			$this->_dbopen = true;
+			$this->Observe("Connection Open",OBSERVE_DEBUG);
+		}
+	}
+	
+	/**
+	 * Closing the connection to the data Server
+	 *
+	 * @access public
+	 */	
+	function Close()
+	{
+		$this->Observe("Closing Connection...",OBSERVE_DEBUG);
+		
+		if ($this->_dbopen)
+		{
+			$this->_driver->Close($this->_dbconn); // ignore warnings
+			$this->_dbopen = false;
+			$this->Observe("Connection Closed",OBSERVE_DEBUG);
+		}
+		else
+		{
+			$this->Observe("Connection Not Open",OBSERVE_DEBUG);
+		}
+	}
+    
+    /**
+	 * Checks that the connection is open and if not, crashes
+	 *
+	 * @access public
+	 * @param bool $auto Automatically try to connect if connection isn't already open
+	 */	
+	private function RequireConnection($auto = false)
+	{
+		if ($this->_dbopen)
+		{
+			// $this->_driver->Ping($this->_dbconn);
+		}
+		else
+		{
+			if ($auto)
+			{
+				$this->Open();
+			}
+			else
+			{
+				$this->Observe("DB is not connected.  Please call DBConnection->Open() first.",OBSERVE_FATAL);
+				throw new Exception("DB is not connected.  Please call DBConnection->Open() first.");
+			}
+		}
+	}
+	
+	/**
+	 * Executes a SQL select statement and returns a resultset that can be read
+	 * using Fetch
+	 *
+	 * @access public
+	 * @param string $sql
+	 * @return resultset (dependent on the type of driver used)
+	 */	
+	function Select($sql)
+	{
+		$this->RequireConnection(true);
+		$this->Observe("(DataAdapter.Select) " . $sql, OBSERVE_QUERY);
+		
+		try
+		{
+			$rs = $this->_driver->Query($this->_dbconn,$sql);
+			$this->_num_retries = 0;
+		}
+		catch (Exception $ex)
+		{
+			// retry one time a communication error occurs
+			if ($this->_num_retries == 0 && DataAdapter::$RETRY_ON_COMMUNICATION_ERROR && $this->IsCommunicationError($ex))
+			{
+				$this->_num_retries++;
+				$this->Observe("Communication error.  Retry attempt " . $this->_num_retries, OBSERVE_WARN);
+				sleep(2); // slight delay to prevent throttling
+				return $this->Select($sql);
+			}
+			
+			$msg = 'Error Selecting SQL: ' . $ex->getMessage() . ' (retry attempts: '.$this->_num_retries.')';
+			
+			$this->Observe($msg,OBSERVE_FATAL);
+			throw new Exception($msg,$ex->getCode(),$ex->getPrevious());
+		}
+		
+		return $rs;
+	}
+	
+	/**
+	 * Executes a SQL query that does not return a resultset
+	 *
+	 * @access public
+	 * @param string $sql
+	 * @return int number of records affected
+	 */	
+	function Execute($sql)
+	{
+		$this->RequireConnection(true);
+		$this->Observe("(DataAdapter.Execute) " . $sql, OBSERVE_QUERY);
+		$result = -1;
+		
+		try
+		{
+			$result = $this->_driver->Execute($this->_dbconn, $sql);
+			$this->_num_retries = 0;
+		}
+		catch (Exception $ex)
+		{
+			// retry one time a communication error occurs
+			if ($this->_num_retries == 0 && DataAdapter::$RETRY_ON_COMMUNICATION_ERROR && $this->IsCommunicationError($ex))
+			{
+				$this->_num_retries++;
+				$this->Observe("Communication error.  Retry attempt " . $this->_num_retries, OBSERVE_WARN);
+				sleep(2); // slight delay to prevent throttling
+				return $this->Execute($sql);
+			}
+			
+			$msg = 'Error Executing SQL: ' . $ex->getMessage() . ' (retry attempts: '.$this->_num_retries.')';
+			
+			$this->Observe($msg,OBSERVE_FATAL);
+			throw new Exception($msg,$ex->getCode(),$ex->getPrevious());
+		}
+		
+		return $result;
+	}
+	
+	/**
+	 * Return true if the error with the given message is a communication/network error
+	 * @param variant string or Exception $msg
+	 * @return bool
+	 */
+	public function IsCommunicationError($error)
+	{
+		$msg = is_a($error, 'Exception') ? $error->getMessage() : $error;
+		return strpos(strtolower($msg),'lost connection') !== false;
+	}
+	
+	/**
+	 * Returns an array of all table names in the current database
+	 * @param bool true to ommit tables that are empty (default = false)
+	 * @return array
+	 */
+	public function GetTableNames($ommitEmptyTables = false)
+	{
+		return $this->_driver->GetTableName($this->_dbconn,$this->GetDBName(),$ommitEmptyTables);
+	}
+	
+	/**
+	 * Runs OPTIMIZE TABLE on all tables in the current database
+	 * @return array results for each table
+	 */
+	public function OptimizeTables()
+	{
+		$results = array();
+		$table_names = $this->_driver->GetTableNames($this->_dbconn,$this->GetDBName());
+		
+		foreach ($table_names as $table_name)
+		{
+			$results[$table_name] = $this->_driver->Optimize($this->_dbconn,$table_name);
+		}
+		
+		return $results;
+		
+	}
+	/**
+	 * Returns last auto-inserted Id
+	 *
+	 * @access public
+	 * @return int
+	 */	
+	function GetLastInsertId()
+	{
+		$this->RequireConnection();
+		$this->Observe("GetLastInsertId", OBSERVE_QUERY);
+		return $this->_driver->GetLastInsertId($this->_dbconn);
+	}
+	
+	/**
+	 * Moves the database curser forward and returns the current row as an associative array
+	 * the resultset passed in must have been created by the same database driver that
+	 * was connected when Select was called
+	 *
+	 * @access public
+	 * @param resultset $rs
+	 * @return Array
+	 */	
+	function Fetch($rs)
+	{
+		$this->RequireConnection();
+
+		$this->Observe("Fetching next result as array",OBSERVE_DEBUG);
+		return $this->_driver->Fetch($this->_dbconn,$rs);
+	}
+	
+	/**
+	 * Releases the resources for the given resultset.  the resultset must have 
+	 * been created by the same database driver
+	 *
+	 * @access public
+	 * @param resultset $rs
+	 */	
+	function Release($rs)
+	{
+		$this->RequireConnection();
+
+		$this->Observe("Releasing result resources",OBSERVE_DEBUG);
+		$this->_driver->Release($this->_dbconn,$rs);
+	}
+	
+	/**
+	 * Removes any illegal chars from a value to prepare it for use in SQL
+	 *
+	 * @access public
+	 * @param string $val
+	 * @return string
+	 */	
+    public static function Escape($val)
+    {
+		// this is an unfortunate leftover from poor design of making this function static
+		// we cannon use the driver's escape method without a static reference
+		if (!DataAdapter::$DRIVER_INSTANCE) throw new Exception("DataAdapter must be instantiated before Escape can be called");
+
+		// if magic quotes are enabled, then we need to stip the slashes that php added
+		if (get_magic_quotes_runtime() || get_magic_quotes_gpc()) $val = stripslashes($val);
+		
+		// $driver->RequireConnection(true);
+		return DataAdapter::$DRIVER_INSTANCE->Escape($val);
+	}
+	
+	/**
+	 * Quote and escape value to prepare it for use in SQL
+	 *
+	 * @access public
+	 * @param string $val
+	 * @return string
+	 */
+	public static function GetQuotedSql($val)
+	{
+		// this is an unfortunate leftover from poor design of making this function static
+		// we cannon use the driver's escape method without a static reference
+		if (!DataAdapter::$DRIVER_INSTANCE) throw new Exception("DataAdapter must be instantiated before Escape can be called");
+	
+		// $driver->RequireConnection(true);
+		return DataAdapter::$DRIVER_INSTANCE->GetQuotedSql($val);
+	}
+	
+	
+    /**
+    * Registers/attaches an IObserver to this object
+    *
+    * @access public
+	* @param IObserver $observer
+    */
+	public function AttachObserver($listener)
+	{
+		if ($listener) $this->_observers[] =& $listener;
+	}
+	
+    /**
+    * Fires the Observe event on all registered observers
+    *
+    * @access public
+    * @param variant $obj the $obj or message that you want to log/listen to, etc.
+    * @param int $ltype the type/level
+    */
+	public function Observe($obj, $ltype = OBSERVE_INFO)
+	{
+		foreach ($this->_observers as $observer) @$observer->Observe($obj, $ltype);
+	}
+}
+?>

+ 103 - 0
phreeze/libs/verysimple/Phreeze/DataPage.php

@@ -0,0 +1,103 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/**
+ * DataPage is a container for one "page" of data in a DataSet
+ * This is used for displaying results in small chunks.  A DataPage
+ * is returned by DataSet::GetDataPage
+ *
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc. <[email protected]>
+ * @copyright  1997-2007 VerySimple Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    1.1
+ */
+class DataPage implements Iterator
+{
+    /**
+     * The Rows property is an array of objects retreived from the data store
+     */
+    public $Rows = null;
+
+	/**
+	 * ObjectName is the classname of the object that is stored
+	 */
+    public $ObjectName = "";
+
+	/**
+	 * ObjectInstance is an instance of the class that is stored in Rows
+	 * @var Phreezable
+	 */
+    public $ObjectInstance = null;
+
+	/**
+	 * ObjectKey is the name of the primary key property for the objects in Rows
+	 */
+    public $ObjectKey = "";
+
+    public $TotalResults = 0;
+    public $TotalPages = 0;
+    public $CurrentPage = 0;
+    public $PageSize = 0;
+
+
+    /**
+     * @return Phreezable
+     */
+    public function Next()
+    {
+		return next($this->Rows);
+	}
+
+	public function rewind() {
+		reset($this->Rows);
+	}
+
+	/**
+	 * @return Phreezable
+	 */
+	public function current() {
+		return current($this->Rows);
+	}
+
+	public function key() {
+		return key($this->Rows);
+	}
+
+	public function valid() {
+		return $this->current() !== false;
+	}
+
+	/**
+	* Returns the entire page as an array of objects.  if the asSimpleObject is false
+	* then the stateful Phreezable objects will be returned.  If asSimpleObject is true
+	* then the objects returned will be whatever is returned by ToObject()
+	* Phreezable object (the default is a stdClass with all public properties)
+	*
+	* @access public
+    * @param bool asSimpleObject if true then populate the array with ToObject on each item in the array
+    * @param array options (only relevant if asSimpleObject is true) passed through to ToObject
+	* @return array
+	    */
+	function ToObjectArray($asSimpleObject = false, $options = null)
+	{
+		$arr = null;
+
+		if ($asSimpleObject)
+		{
+			$arr = array();
+			foreach ($this->Rows as $row)
+			{
+				$arr[] = $row->ToObject($options);
+			}
+		}
+		else
+		{
+			$arr = $this->Rows;
+		}
+
+		return $arr;
+	}
+}
+
+?>

+ 542 - 0
phreeze/libs/verysimple/Phreeze/DataSet.php

@@ -0,0 +1,542 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/** import supporting libraries */
+require_once("DataPage.php");
+
+/**
+ * DataSet stores zero or more Loadable objects
+ * The DataSet is the object that is returned by every Phreezer Query operation.
+ * The DataSet contains various methods to enumerate through , or retrieve all
+ * results all at once.
+ *
+ * The DataSet executes queries lazily, only when the first result is retrieved.
+ * Using GetDataPage will allow retreival of sub-sets of large amounts of data without
+ * querying the entire database
+ *
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc. <[email protected]>
+ * @copyright  1997-2007 VerySimple Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    1.1
+ */
+class DataSet implements Iterator // @TODO implement Countable, ArrayAccess
+{
+    protected $_phreezer;
+    protected $_rs;
+    protected $_objectclass;
+    protected $_counter;
+
+    private $_sql;
+    private $_current; // the current object in the set
+	private $_last; // the previous object in the set
+	private $_totalcount;
+	private $_no_exception;  // used during iteration to suppress exception on the final Next call
+	private $_cache_timeout;  // length of time to cache query results
+
+	public $UnableToCache = true;
+
+	/**
+	 * A custom SQL query may be provided to count the results of the query.
+	 * This query should return one column "counter" which is the number of rows
+	 * and must take into account all criteria parameters.
+	 * If no value is provided, the counter query will be generated (which is likely less efficient)
+	 *
+	 * @var string
+	 */
+	public $CountSQL = "";
+
+    /**
+    * Contructor initializes the object
+    *
+    * @access     public
+    * @param Phreezer
+    * @param string class of object this DataSet contains
+    * @param string $sql code
+    * @param int cache timeout (in seconds).  Default is Phreezer->ValueCacheTimeout.  Set to 0 for no cache
+    */
+    function __construct(&$preezer, $objectclass, $sql, $cache_timeout = null)
+    {
+        $this->_counter = -1;
+		$this->_totalcount = -1;
+		$this->_eof = false;
+        $this->_objectclass = $objectclass;
+        $this->_phreezer =& $preezer;
+        $this->_rs = null;
+        $this->_sql = $sql;
+        $this->_cache_timeout = is_null($cache_timeout) ? $preezer->ValueCacheTimeout : $cache_timeout;
+    }
+
+    /**
+    * _getObject must be overridden and returns the type of object that
+    * this collection will contain.
+    *
+    * @access     private
+    * @param      array $row array to use for populating a single object
+    * @return     Preezable
+    */
+    private function _getObject(&$row)
+    {
+        $obj = new $this->_objectclass($this->_phreezer, $row);
+        return $obj;
+    }
+
+    /**
+    * Next returns the next object in the collection.
+    *
+    * @access     public
+    * @return     Preezable
+    */
+    function Next()
+    {
+    	if ($this->UnableToCache)
+    	{
+    		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);
+
+    		// use this line to discover where an uncachable query is coming from
+    		// throw new Exception("WTF");
+
+    		// stop this warning from repeating on every next call for this dataset
+    	    $this->UnableToCache = false;
+    	}
+
+        $this->_verifyRs();
+
+		$this->_current = null;
+		$this->_counter++;
+
+        if ($this->_eof)
+        {
+			if (!$this->_no_exception)
+				throw new Exception("EOF: This is a forward-only dataset.");
+        }
+
+        if ($row = $this->_phreezer->DataAdapter->Fetch($this->_rs))
+        {
+            $this->_current = $this->_getObject($row);
+			$this->_last = $this->_current;
+		}
+        else
+        {
+            $this->_eof = true;
+        }
+
+		return $this->_current;
+    }
+
+
+    /**
+    * Executes the sql statement and fills the resultset if necessary
+    */
+    private function _verifyRs()
+    {
+        if ($this->_rs == null)
+        {
+			$this->_phreezer->IncludeModel($this->_objectclass);
+			$this->_rs = $this->_phreezer->DataAdapter->Select($this->_sql);
+        }
+    }
+
+    /**
+     * If a reporter query does not return data (insert/update/delete) then
+     * calling Execute will execute the sql without expecting return data
+     */
+    public function Execute()
+    {
+    	return $this->_phreezer->DataAdapter->Execute($this->_sql);
+    }
+
+	public function rewind() {
+		$this->_rs = null;
+		$this->_counter = 0;
+		$this->_no_exception = true;
+		$this->_total = $this->Count();
+		$this->_verifyRs();
+		$this->Next(); // we have to get the party started for php iteration
+	}
+
+	public function current() {
+		// php iteration calls next then gets the current record.  The DataSet
+		// Next return the current object.  so, we have to fudge a little on the
+		// laster iteration to make it work properly
+		return ($this->key() == $this->Count()) ? $this->_last : $this->_current;
+	}
+
+	public function key() {
+		return $this->_counter;
+	}
+
+	public function valid() {
+		return $this->key() <= $this->Count();
+	}
+
+	/**
+	 * Returns true if the total number of records is known.  Because calling "Count"
+	 * directly may fire a database query, this method can be used to tell if
+	 * the number of records is known without actually firing any queries
+	 * @return boolean
+	 */
+	function CountIsKnown()
+	{
+		return $this->_totalcount > -1;
+	}
+
+    /**
+    * Count returns the number of objects in the collection.  If the
+    * count is not available, a count statement will be executed to determine the total
+    * number of rows available
+    *
+    * Note: if you get an "Unknown Column" error during a query, it may be due to tables being
+    * joined in the wrong order.  To fix this, simply include references in your FieldMap to
+    * the foreign tables in the same order that you wish them to be included in the query
+    *
+    * @access     public
+    * @return     int
+    */
+    function Count()
+    {
+		if (!$this->CountIsKnown())
+		{
+			// check the cache
+			$cachekey = $this->_sql . " COUNT";
+			$this->_totalcount = $this->GetDelayedCache($cachekey);
+
+			// 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);
+			}
+			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)
+				{
+					$sql = $this->CountSQL;
+				}
+				else
+				{
+					$this->_phreezer->Observe("(DataSet.Count: CountSQL was not provided so a counter query will be generated.  Implement GetCustomCountQuery in the reporter class to improve performance.)",OBSERVE_WARN);
+					$sql = "select count(1) as counter from (" . $this->_sql . ") tmptable" . rand(1000,9999);
+				}
+
+				$rs = $this->_phreezer->DataAdapter->Select($sql);
+				$row = $this->_phreezer->DataAdapter->Fetch($rs);
+				$this->_phreezer->DataAdapter->Release($rs);
+				$this->_totalcount = $row["counter"];
+
+				$this->_phreezer->SetValueCache($cachekey, $this->_totalcount, $this->_cache_timeout);
+
+				$this->UnlockCache($cachekey);
+			}
+		}
+
+		return $this->_totalcount;
+    }
+
+    /**
+    * Returns the entire collection as an array of objects.  if the asSimpleObject is false
+	* then the stateful Phreezable objects will be returned.  If asSimpleObject is true
+	* then the objects returned will be whatever is returned by ToObject() on each
+	* Phreezable object (the default is a stdClass with all public properties)
+    *
+    * @access public
+    * @param bool asSimpleObject if true then populate the array with ToObject()
+    * @param array options (only relevant if asSimpleObject is true) passed through to ToObject
+    * @return array
+    */
+    function ToObjectArray($asSimpleObject = false, $options = null)
+    {
+		$cachekey = $this->_sql . " OBJECTARRAY" . ($asSimpleObject ? '-AS-OBJECT-' .  serialize($options) : '');
+
+		$arr = $this->GetDelayedCache($cachekey);
+
+		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);
+			if (!$asSimpleObject)
+			{
+				foreach ($arr as $obj)
+				{
+					$obj->Refresh($this->_phreezer);
+				}
+			}
+		}
+		else
+		{
+			// there is nothing in the cache so we have to reload it
+
+			$this->LockCache($cachekey);
+
+			$this->UnableToCache = false;
+
+			// use a fixed count array if the count is known for performance
+			$arr = $this->CountIsKnown()
+				? $this->GetEmptyArray($this->Count())
+				: Array();
+
+			$i = 0;
+			while ($object = $this->Next())
+			{
+				$arr[$i++] = $asSimpleObject ? $object->ToObject($options) : $object;
+			}
+
+			$this->_phreezer->SetValueCache($cachekey, $arr, $this->_cache_timeout);
+
+			$this->UnlockCache($cachekey);
+		}
+
+		return $arr;
+    }
+
+
+    /**
+    * @deprecated Use GetLabelArray instead
+    */
+    function ToLabelArray($val_prop, $label_prop)
+    {
+        return $this->GetLabelArray($val_prop, $label_prop);
+    }
+
+    /**
+     * Returns an empty array structure, determining which is appropriate
+     * based on the system capabilities and whether a count is known.
+     * If the count parameter is provided then the returned array may be
+     * a fixed-size array (depending on php version)
+     *
+     * @param int count (if known)
+     * @return Array or SplFixedArray
+     */
+    private function GetEmptyArray($count = 0)
+    {
+    	return ($count && class_exists('SplFixedArray'))
+    		? new SplFixedArray($count)
+    		: array();
+    }
+
+	/**
+    * Returns the entire collection as an associative array that can be easily used
+    * for Smarty dropdowns
+    *
+    * @access     public
+    * @param      string $val_prop the object property to be used for the dropdown value
+    * @param      string $label_prop the object property to be used for the dropdown label
+    * @return     array
+    */
+    function GetLabelArray($val_prop, $label_prop)
+    {
+		// check the cache
+		// $cachekey = md5($this->_sql . " VAL=".$val_prop." LABEL=" . $label_prop);
+		$cachekey = $this->_sql . " VAL=".$val_prop." LABEL=" . $label_prop;
+
+		$arr = $this->GetDelayedCache($cachekey);
+
+		// if no cache, go to the db
+		if ($arr != null)
+		{
+			$this->_phreezer->Observe("(DataSet.GetLabelArray: skipping query because cache exists) " . $this->_sql,OBSERVE_QUERY);
+		}
+		else
+		{
+			$this->LockCache($cachekey);
+
+			$arr = Array();
+			$this->UnableToCache = false;
+
+			while ($object = $this->Next())
+			{
+				$arr[$object->$val_prop] = $object->$label_prop;
+			}
+
+			$this->_phreezer->SetValueCache($cachekey, $arr, $this->_cache_timeout);
+
+			$this->UnlockCache($cachekey);
+		}
+
+        return $arr;
+    }
+
+	/**
+	* Release the resources held by this DataSet
+	*
+	* @access     public
+	*/
+	function Clear()
+    {
+         $this->_phreezer->DataAdapter->Release($this->_rs);
+    }
+
+    /**
+     * Returns a DataPage object suitable for binding to the smarty PageView plugin
+     *
+     * @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).
+     * @return DataPage
+     */
+    function GetDataPage($pagenum, $pagesize)
+    {
+		// check the cache
+		// $cachekey = md5($this->_sql . " PAGE=".$pagenum." SIZE=" . $pagesize);
+		$cachekey = $this->_sql . " PAGE=".$pagenum." SIZE=" . $pagesize;
+
+		$page = $this->GetDelayedCache($cachekey);
+
+		// if no cache, go to the db
+		if ($page != null)
+		{
+			$this->_phreezer->Observe("(DataSet.GetDataPage: skipping query because cache exists) " . $this->_sql,OBSERVE_QUERY);
+
+			foreach ($page->Rows as $obj)
+			{
+				$obj->Refresh($this->_phreezer);
+			}
+		}
+		else
+		{
+			$this->LockCache($cachekey);
+
+			$this->UnableToCache = false;
+
+			$page = new DataPage();
+			$page->ObjectName = $this->_objectclass;
+			$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)
+			{
+				$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 );
+			}
+
+			// now enumerate through the rows in the page that we want.
+			// decrement the requested pagenum here so that we will be
+			// using a zero-based array - which saves us from having to
+			// decrement on every iteration
+			$pagenum--;
+
+			$start = $pagesize * $pagenum;
+
+			// @TODO the limit statement should come from the DataAdapter
+			// ~~~ more efficient method where we limit the data queried ~~~
+			// since we are doing paging, we want to get only the records that we
+			// want from the database, so we wrap the original query with a
+			// limit query.
+			// $sql = "select * from (" . $this->_sql . ") page limit $start,$pagesize";
+			$sql = $this->_sql . ($pagesize == 0 ? "" : " limit $start,$pagesize");
+			$this->_rs = $this->_phreezer->DataAdapter->Select($sql);
+
+	        // if we know the number of rows we have, then use SplFixedArray for performance
+			$page->Rows = ($page->TotalPages > $page->CurrentPage)
+				? $this->GetEmptyArray($pagesize)
+				: Array();
+
+			// transfer all of the results into the page object
+			$i = 0;
+			while ( $obj = $this->Next() )
+			{
+				$page->Rows[$i++] = $obj;
+			}
+
+			$this->_phreezer->SetValueCache($cachekey, $page, $this->_cache_timeout);
+
+			$this->Clear();
+
+			$this->UnlockCache($cachekey);
+
+		}
+
+		return $page;
+	}
+
+
+    /**
+     *
+     * @param string $cachekey
+     */
+    private function GetDelayedCache($cachekey)
+    {
+    	// if no cache then don't return anything
+    	if ($this->_cache_timeout == 0) return null;
+
+        $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) )
+		{
+			$this->_phreezer->Observe("(DataSet.GetDelayedCache: flood prevention. delayed attempt ".$counter." of 3...) " . $cachekey,OBSERVE_DEBUG);
+			usleep(50000); // 5/100th of a second
+			$obj = $this->_phreezer->GetValueCache($cachekey);
+			$counter++;
+		}
+
+		return $obj;
+    }
+
+    /**
+     *
+     * @param $cachekey
+     */
+    private function IsLocked($cachekey)
+    {
+		return $this->_phreezer->LockFilePath && file_exists($this->_phreezer->LockFilePath . md5($cachekey) . ".lock" );
+    }
+
+    /**
+     *
+     * @param $cachekey
+     */
+    private function LockCache($cachekey)
+    {
+		if ($this->_phreezer->LockFilePath)
+		{
+			touch($this->_phreezer->LockFilePath . md5($cachekey) . ".lock");
+		}
+
+    }
+
+    /**
+     *
+     * @param $cachekey
+     */
+    private function UnlockCache($cachekey)
+    {
+		if ($this->_phreezer->LockFilePath)
+		{
+			$lockfile = $this->_phreezer->LockFilePath . md5($cachekey) . ".lock";
+			if (file_exists($lockfile)) @unlink($lockfile);
+		}
+	}
+
+}
+
+?>

+ 130 - 0
phreeze/libs/verysimple/Phreeze/Dispatcher.php

@@ -0,0 +1,130 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/** import supporting libraries */
+require_once("verysimple/HTTP/RequestUtil.php");
+require_once("verysimple/Util/ExceptionThrower.php");
+
+/**
+ * Dispatcher direct a web request to the correct controller & method
+ *
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc.
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    2.4
+ */
+class Dispatcher
+{
+	/**
+	 * Set to true and Phreeze will not try to handle deprecated function warnings
+	 * @var boolean default = true
+	 */
+	static $IGNORE_DEPRECATED = true;
+
+	/**
+	 * Processes user input and executes the specified controller method, ensuring
+	 * that the controller dependencies are all injected properly
+	 *
+	 * @param Phreezer $phreezer Object persistance engine
+	 * @param IRenderEngine $renderEngine rendering engine
+	 * @param string (optional) $action the user requested action (if not provided will use router->GetRoute())
+	 * @param Context (optional) a context object for persisting state
+	 * @param IRouter (optional) router object for reading/writing URLs (if not provided, GenericRouter will be used)
+	 */
+	static function Dispatch($phreezer,$renderEngine,$action='',$context=null,$router=null)
+	{
+		if ($router == null)
+		{
+			require_once('GenericRouter.php');
+			$router = new GenericRouter();
+		}
+
+		list($controller_param,$method_param) = $router->GetRoute( $action );
+
+		// 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)) )
+		{
+			// go to plan be, search the include path for the controller
+			$paths = explode(PATH_SEPARATOR,get_include_path());
+			$found = false;
+			foreach ($paths as $path)
+			{
+				if (file_exists($path ."/".$controller_file))
+				{
+					$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");
+		}
+
+
+		// create an instance of the controller class
+		$controller = new $controller_class($phreezer,$renderEngine,$context,$router);
+		
+		// we have a valid instance, just verify there is a matching method
+		if (!is_callable(array($controller, $method_param)))
+		{
+			throw new Exception("'".$controller_class.".".$method_param."' is not a valid action");
+		}
+
+		// do not call the requested method/route if the controller request has been cancelled
+		if (!$controller->IsTerminated())
+		{
+			// file, class and method all are ok, go ahead and call it
+			call_user_func(array(&$controller, $method_param));
+		}
+
+		// reset error handling back to whatever it was
+		//restore_exception_handler();
+		ExceptionThrower::Stop();
+
+		return true;
+	}
+
+	/**
+	 * Fired by the PHP error handler function.  Calling this function will
+	 * always throw an exception unless error_reporting == 0.  If the
+	 * PHP command is called with @ preceeding it, then it will be ignored
+	 * here as well.
+	 *
+	 * @deprecated use ExceptionThrower::HandleError instead
+	 * @param string $code
+	 * @param string $string
+	 * @param string $file
+	 * @param string $line
+	 * @param string $context
+	 */
+	static function HandleException($code, $string, $file, $line, $context)
+	{
+		ExceptionThrower::HandleError($code, $string, $file, $line, $context);
+	}
+}
+
+?>

+ 182 - 0
phreeze/libs/verysimple/Phreeze/ExportUtility.php

@@ -0,0 +1,182 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/**
+ * ExportUtility Class
+ *
+ * This contains various utility functions for exporting Phreezable objects into other formats
+ * such as Excel, CSV, tab-delimited, XML, etc
+ *
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc. <[email protected]>
+ * @copyright  1997-2005 VerySimple Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    1.0
+ */
+class ExportUtility
+{
+	/**
+	 * Streams to the browser the provided array of objects as a basic Excel document
+	 * with headers.  if the objects have an associated Map class, then footers will be
+	 * added to sum any numeric fields.  otherwise no footers are added
+	 * 
+	 * Note that PEAR Spreadsheet_Excel_Writer must be installed
+	 * @link http://pear.php.net/package/Spreadsheet_Excel_Writer
+	 * 
+	 * @param Array an array of Phreezable objects, obtained for example, using DataSet->ToObjectArray
+	 * @param Phreezer $phreezer is needed to get field maps
+	 * @param string (optional) The title of the report
+	 */
+	static function OutputAsExcel(Array $objects, Phreezer $phreezer, $reportTitle = "Data Export", $fileName = "export.xls")
+	{
+		require_once 'Spreadsheet/Excel/Writer.php';
+		
+		// create the workbook and worksheet
+		$workbook = new Spreadsheet_Excel_Writer();
+		$worksheet = $workbook->addWorksheet("Export");
+		
+		$BOLD_MED =& $workbook->addFormat();
+		$BOLD_MED->setSize(16);
+		$BOLD_MED->SetBold();
+		
+		$BOLD_REG =& $workbook->addFormat();
+		$BOLD_REG->setSize(11);
+		$BOLD_REG->SetBold();
+		
+		$NORMAL =& $workbook->addFormat();
+		$NORMAL->setSize(11);
+		
+		$CURRENCY =& $workbook->addFormat();
+		$CURRENCY->setNumFormat('0.00');
+		$CURRENCY->setSize(11);
+		$CURRENCY->setAlign('right');
+
+		$worksheet->writeString(0, 0, $reportTitle, $BOLD_MED);
+
+		// default to no columns
+		$fields = Array();
+		$columns = Array();
+		$is_numeric = Array();
+		$fieldmap_exists = false;
+		
+		// print the headers
+		// while we're looping, also parse the fields so we don't have to do 
+		// it repeatedly when looping through data
+		if (isset($objects[0]))
+		{
+			try
+			{
+				// see if there is a fieldmap for this object
+				$fields = $phreezer->GetFieldMaps( get_class($objects[0]) );
+				$fieldmap_exists = true;
+
+				// these are the columns we'll use for enumeration from here on
+				$columns = array_keys($fields);
+			}
+			catch (Exception $ex)
+			{
+				// no fieldmaps exist, so use the reflection class instead
+				$reflect = new ReflectionClass($objects[0]);
+				$publicAttributes = $reflect->getProperties(ReflectionProperty::IS_PUBLIC);
+				$staticAttributes = $reflect->getStaticProperties();
+				// only include non-static public properties
+				$props = array_diff($publicAttributes,$staticAttributes);
+
+				foreach ($props as $prop) 
+				{
+					$column = $prop->getName();
+				    $columns[] = $column;
+				}
+			}
+			
+			$current_column = 0;
+			foreach ($columns as $column) 
+			{
+				// save this so we don't check it every time when looping through data
+				$is_numeric[$column] = $fieldmap_exists ? $fields[$column]->IsNumeric() : false;
+
+    			$worksheet->writeString(2, $current_column, $column, $BOLD_REG);
+    			$current_column++;
+			}
+
+		}
+		
+		$current_row = 3;
+		
+		// loop through all of the data
+		foreach ($objects as $object)
+		{
+			$current_column = 0;
+			foreach ($columns as $column) 
+			{
+				if ($fieldmap_exists == false || $is_numeric[$column] == true)
+				{
+					$worksheet->write($current_row, $current_column, $object->$column, $NORMAL);
+				}
+				else
+				{
+					$worksheet->writeString($current_row, $current_column, $object->$column, $NORMAL);
+				}
+				
+    			$current_column++;
+			}
+			$current_row++;
+		}
+		
+		// lastly write to the footer to sum the numeric columns
+		$current_column = 0;
+		foreach ($columns as $column) 
+		{
+			if ($is_numeric[$column])
+			{
+				$columnLetter = ExportUtility::GetColumnLetter($current_column);
+				$formula = "=SUM(".$columnLetter."3:".$columnLetter.($current_row-1).")";
+				
+				// notice the @ sign in front because this will fire a deprecated warning due to use of "split"
+				@$worksheet->write($current_row, $current_column, $formula, $BOLD_REG);
+			}
+			
+    		$current_column++;
+		}
+		
+		$workbook->send($fileName);
+		
+		// this has errors suppressed due to strict mode
+		@$workbook->close();
+	}
+	
+	/**
+	 * Given a zero-based column number, the approriate Excel column letter is 
+	 * returned, ie A, B, AB, CJ, etc.  max supported is ZZ, higher than that will
+	 * throw an exception.
+	 * 
+	 * @param int $columnNumber
+	 */
+	static function GetColumnLetter($columnNumber)
+	{
+		// work with 1-based number
+		$colNum = $columnNumber + 1;
+		$code = "";
+		
+		if ($colNum > 26)
+		{
+			// greater than 26 means the column will be AA, AB, AC, etc.
+			$left = floor($columnNumber / 26);
+			$right = 1 + ($columnNumber % 26);
+
+			if ($left > 26) throw new Exception("Columns exceed supported amount");
+			
+			$code = chr($left + 64) . chr($right + 64);
+		}
+		else
+		{
+			$code = chr($colNum + 64);
+		}
+		
+		return $code;
+		
+		
+	}
+}
+
+?>

+ 125 - 0
phreeze/libs/verysimple/Phreeze/FieldMap.php

@@ -0,0 +1,125 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/** import supporting libraries */
+define("FM_TYPE_UNKNOWN",0);
+define("FM_TYPE_DECIMAL",1);
+define("FM_TYPE_INT",2);
+define("FM_TYPE_SMALLINT",3);
+define("FM_TYPE_TINYINT",4);
+define("FM_TYPE_MEDIUMINT",15);
+define("FM_TYPE_BIGINT",16);
+define("FM_TYPE_FLOAT",20);
+define("FM_TYPE_VARCHAR",5);
+define("FM_TYPE_BLOB",6);
+define("FM_TYPE_DATE",7);
+define("FM_TYPE_DATETIME",8);
+define("FM_TYPE_TEXT",9);
+define("FM_TYPE_SMALLTEXT",10);
+define("FM_TYPE_MEDIUMTEXT",11);
+define("FM_TYPE_CHAR",12);
+define("FM_TYPE_LONGBLOB",13);
+define("FM_TYPE_LONGTEXT",14);
+
+define("FM_TYPE_TIMESTAMP",17);
+define("FM_TYPE_ENUM",18);
+define("FM_TYPE_TINYTEXT",19);
+
+define("FM_TYPE_YEAR",21);
+define("FM_TYPE_TIME",22);
+
+define("FM_CALCULATION",99); // not to be used during save
+
+/**
+ * FieldMap is a base object for mapping a Phreezable object to a database table
+ *
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc.
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    2.0
+ */
+class FieldMap
+{
+
+	public $PropertyName;
+	public $TableName;
+	public $ColumnName;
+	public $FieldType;
+	public $IsPrimaryKey;
+	public $DefaultValue;
+	public $IsAutoInsert;
+
+	/** @variant either an int or an array to indicate max size or acceptable values */
+	public $FieldSize;
+	
+	/**
+	 * Given a MySQL column type, return the Phreeze constant name
+	 * @param string $type
+	 */
+	static function GetConstantFromType($type)
+	{
+		$const = 'FM_TYPE_' . strtoupper($type);
+		return (defined($const)) ? $const : 'FM_TYPE_UNKNOWN';
+	}
+
+	/**
+	* Initializes the FieldMap
+	*
+	* @param string $pn Model property name
+	* @param string $tn DB table name
+	* @param string $cb DB column name
+	* @param bool $pk True if column is a primary key  (optional default = false)
+	* @param int $ft Field type FM_TYPE_VARCHAR | FM_TYPE_INT | etc...  (optional default = FM_TYPE_UNKNOWN)
+	* @param variant $fs Field size, 0 for unlimited.  for enums, an array of acceptable values  (default = 0)
+	* @param variant $dv Default value  (optional default = null)
+	* @param bool $iai True if column is auto insert column  (optional default = null)
+	*/
+	public function __construct($pn, $tn, $cn, $pk = false, $ft = FM_TYPE_UNKNOWN, $fs = 0, $dv = null, $iai = null)
+	{
+		$this->PropertyName = $pn;
+		$this->TableName = $tn;
+		$this->ColumnName = $cn;
+		$this->IsPrimaryKey = $pk;
+		$this->FieldType = $ft;
+		$this->FieldSize = $fs;
+		$this->DefaultValue = $dv;
+		$this->IsAutoInsert = $iai;
+	}
+	
+	/**
+	 * Return true if this column is an enum type
+	 * @return boolean
+	 */
+	function IsEnum()
+	{
+		return $this->FieldType == FM_TYPE_ENUM;
+	}
+	
+	/**
+	 * Return the enum values if this column type is an enum
+	 * @return array
+	 */
+	function GetEnumValues()
+	{
+		return $this->IsEnum() ? $this->FieldSize : array();
+	}
+
+	/**
+	 * Return true if this column is a numeric type
+	 */
+	public function IsNumeric()
+	{
+		return (
+			$this->FieldType == FM_TYPE_DECIMAL
+			|| $this->FieldType == FM_TYPE_INT
+			|| $this->FieldType == FM_TYPE_SMALLINT
+			|| $this->FieldType == FM_TYPE_TINYINT
+			|| $this->FieldType == FM_TYPE_MEDIUMINT
+			|| $this->FieldType == FM_TYPE_BIGINT
+			|| $this->FieldType == FM_TYPE_FLOAT
+		);
+	}
+}
+
+?>

+ 265 - 0
phreeze/libs/verysimple/Phreeze/GenericRouter.php

@@ -0,0 +1,265 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/** import supporting libraries */
+require_once('IRouter.php');
+
+/**
+ * Generic Router is an implementation of IRouter that uses patterns to connect
+ * routes to a Controller/Method
+ *
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc.
+ * @copyright  1997-2012 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    1.0
+ */
+class GenericRouter implements IRouter
+{
+	private static $routes = array();
+
+	private $defaultAction = 'Default.Home';
+	private $uri = '';
+	private $appRootUrl = '';
+
+	// cached route from last run:
+	private $cachedRoute;
+
+	/**
+	 * Instantiate the GenericRouter
+	 *
+	 * @param string $appRootUrl the root url of the application including trailing slash (ex http://localhost/)
+	 * @param string $defaultAction action to call if no route is provided (ex Default.DefaultAction)
+	 * @param array $mapping the associative array of maps Example: <pre>
+	 * 		$routes = array(
+	 * 			"GET:" => array("route" => "Default.Home"),
+	 * 			"GET:user/(:num)/packages" => array("route" => "Package.Query", "params" => array("userId" => 1)),
+	 * 			"POST:user/(:num)/package/(:num)" => array("route" => "Package.Update", "params" => array("userId" => 1, "packageId" => 3)),
+	 * 			"GET:automation/(:any)" => array("route" => "Automation.DoAutomation", "params" => array("action" => 1))
+	 * 		);
+	 * </pre>
+	 */
+	public function __construct($appRootUrl = '', $defaultAction = '', $mapping = array())
+	{
+		if ($defaultAction) $this->defaultAction = $defaultAction;
+		if ($appRootUrl) $this->appRootUrl = $appRootUrl;
+
+		$this->mapRoutes($mapping);
+
+		$this->cachedRoute = null;
+	}
+
+	/**
+	 * Return the root url for this application as provided at construction
+	 */
+	public function GetRootUrl()
+	{
+		return $this->appRootUrl;
+	}
+
+	/**
+	 * Adds router mappings to our routes array.
+	 *
+	 * @param array $src
+	 */
+	private static function mapRoutes( $src )
+	{
+		foreach ( $src as $key => $val )
+			self::$routes[ $key ] = $val;
+	}
+
+	/**
+	 * @inheritdocs
+	 */
+	public function GetRoute( $uri = "" )
+	{
+		if( $uri == "" )
+			$uri = RequestUtil::GetMethod() . ":" . $this->GetUri();
+
+		// literal match check
+		if ( isset(self::$routes[ $uri ]) )
+		{
+			// expects mapped values to be in the form: Controller.Model
+			list($controller,$method) = explode(".",self::$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()
+			);
+
+			return array($controller,$method);
+		}
+
+		// loop through the route map for wild cards:
+		foreach( self::$routes as $key => $value)
+		{
+			$unalteredKey = $key;
+
+			// convert wild cards to RegEx.
+			// currently only ":any" and ":num" are supported wild cards
+			$key = str_replace( ':any', '.+', $key );
+			$key = str_replace( ':num', '[0-9]+', $key );
+
+			// check for RegEx match
+			if ( preg_match( '#^' . $key . '$#', $uri ) )
+			{
+				$this->cachedRoute = array(
+					"key" => $unalteredKey
+					,"route" => $value["route"]
+					,"params" => isset($value["params"]) ? $value["params"] : array()
+				);
+
+				// expects mapped values to be in the form: Controller.Model
+				list($controller,$method) = explode(".",$value["route"]);
+				return array($controller,$method);
+			}
+		}
+
+		// if we haven't returned by now, we've found no match:
+		return array("Default","Error404");
+	}
+
+	/**
+	 * @see IRouter::GetUri()
+	 */
+	public function GetUri()
+	{
+		if (!$this->uri)
+		{
+			$this->uri = array_key_exists('_REWRITE_COMMAND', $_REQUEST) ? $_REQUEST['_REWRITE_COMMAND'] : '';
+
+			// if a root folder was provided, then we need to strip that out as well
+			if ($this->appRootUrl)
+			{
+				$prefix = $this->appRootUrl.'/';
+				while (substr($this->uri,0,strlen($prefix)) == $prefix)
+				{
+					$this->uri = substr($this->uri,strlen($prefix));
+				}
+			}
+
+			// strip trailing slash
+			while (substr($this->uri,-1) == '/')
+			{
+				$this->uri = substr($this->uri,0,-1);
+			}
+		}
+		return $this->uri;
+	}
+
+	/**
+	 * @inheritdocs
+	 */
+	public function GetUrl( $controller, $method, $params = '', $requestMethod = "" )
+	{
+		$found = false;
+		// $requestMethod = RequestUtil::GetMethod();
+
+		if ($params == '') $params = array();
+		
+		if (!is_array($params))
+		{
+			$pairs = explode('&',$params);
+			$params = array();
+			foreach ($pairs as $pair)
+			{
+				$keyval = explode('=',$pair);
+				$params[$keyval[0]] = count($keyval) > 1 ? $keyval[1] : '';
+			}
+		}
+
+		// if an appRootUrl was provided then use that, otherwise figure it out based on the root url
+		$url = $this->appRootUrl ? $this->appRootUrl : RequestUtil::GetBaseURL();
+
+		// normalize url by stripping trailing slash
+		while (substr($url,-1) == '/')
+		{
+			$url = substr($url,0,-1);
+		}
+
+		foreach( self::$routes as $key => $value)
+		{
+			list($routeController,$routeMethod) = explode(".",$value["route"]);
+
+			$keyRequestMethodArr = explode(":",$key,2);
+			$keyRequestMethod = $keyRequestMethodArr[0];
+
+			// echo "$routeController:$controller $routeMethod:$method $keyRequestMethod:$requestMethod)<br/>";
+			if( ($routeController == $controller) && ($routeMethod == $method) && ($requestMethod == "" || ($keyRequestMethod == $requestMethod)) &&
+			    (! array_key_exists("params",$value) || count($params) == count($value["params"]) )
+			  )
+			{
+				$keyArr = explode('/',$key);
+
+				// strip the request method off the key:
+				// we can safely access 0 here, as there has to be params to get here:
+				$reqMethodAndController = explode(":",$keyArr[0]);
+				$keyArr[0] = (count($reqMethodAndController) == 2 ? $reqMethodAndController[1] : $reqMethodAndController[0]);
+
+				// merge the parameters passed in with the routemap's path
+				// example: path is user/(:num)/events and parameters are [userCode]=>111
+				// this would yield an array of [0]=>user, [1]=>111, [2]=>events
+				if( array_key_exists("params",$value) )
+				{
+					foreach( $value["params"] as $rKey => $rVal )
+					{
+						if (!array_key_exists($rKey, $params))
+							throw new Exception('Missing parameter "' . $rKey . "' for route " . $controller.'.'.$method);
+						$keyArr[$value["params"][$rKey]] = $params[$rKey];
+					}
+				}
+
+				// put the url together:
+				foreach( $keyArr as $urlPiece )
+				{
+					$url = $url . ($urlPiece != '' ? "/$urlPiece" : '');
+				}
+				
+				// no route, just a request method? RESTful to add a trailing slash:
+				if( $keyRequestMethodArr[1] == "")
+					$url = $url . "/";
+
+				$found = true;
+				break;
+			}
+		}
+
+		// we stripped this at the beginning, need to add it back
+		if( ! $found ) // $url = $url . "/";
+			throw new Exception("No route found for " . $controller.'.'.$method. ($params ? '?' . implode('&',$params) : '' ));
+
+		return $url;
+	}
+
+	/**
+	 * @inheritdocs
+	 */
+	public function GetUrlParams()
+	{
+		return explode('/',$this->GetUri());
+	}
+
+	/**
+	 * @inheritdocs
+	 */
+	public function GetUrlParam($paramKey, $default = '')
+	{
+		if( $this->cachedRoute == null )
+			throw new Exception("Call GetRoute before accessing GetUrlParam");
+
+		$params = $this->GetUrlParams();
+		$uri = $this->GetUri();
+		$count = 0;
+		$routeMap = $this->cachedRoute["key"];
+
+		if( isset($this->cachedRoute["params"][$paramKey]) )
+		{
+			$indexLocation = $this->cachedRoute["params"][$paramKey];
+			return $params[$indexLocation];
+		}
+		else
+			return RequestUtil::Get($paramKey,"");
+	}
+}
+?>

+ 44 - 0
phreeze/libs/verysimple/Phreeze/ICache.php

@@ -0,0 +1,44 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/**
+ * ICache defines an interface for a caching mechanism
+ *
+ * @package    verysimple::Phreeze 
+ * @author     VerySimple Inc.
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    2.0
+ */
+interface ICache
+{
+	/**
+	* Retreives a value from the cache
+	*
+	* @access     public
+	* @param string $key
+	*/
+	public function Get($key);
+	
+	/**
+	* Stores a value in the cache
+	*
+	* @access     public
+	* @param string $key
+	* @param variant $val
+	* @param int $flags
+	* @param int $timout in miliseconds
+	* @return variant
+	*/
+	public function Set($key,$val,$flags=null,$timeout=null);
+
+	/**
+	* Removes a value from the cache
+	*
+	* @access     public
+	* @param string $key
+	*/
+	public function Delete($key);
+}
+
+?>

+ 23 - 0
phreeze/libs/verysimple/Phreeze/IDaoMap.php

@@ -0,0 +1,23 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/** import supporting libraries */
+require_once("FieldMap.php");
+require_once("KeyMap.php");
+
+/**
+ * IDaoMap is an interface for a mapped object that can be persisted by Phreezer
+ *
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc.
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    2.0
+ */
+interface IDaoMap
+{
+	static function GetFieldMaps();
+	static function GetKeyMaps();
+}
+
+?>

+ 36 - 0
phreeze/libs/verysimple/Phreeze/IObservable.php

@@ -0,0 +1,36 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/** import supporting libraries */
+require_once("IObserver.php");
+
+/**
+ * IObservable defines an interface for an object that can have listeners attached 
+ *
+ * @package    verysimple::Phreeze 
+ * @author     VerySimple Inc.
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    2.0
+ */
+interface IObservable
+{
+    /**
+    * Registers/attaches an IObserver to this object
+    *
+    * @access     public
+	* @param IObserver $observer
+    */
+	public function AttachObserver($observer);
+
+    /**
+    * Fires the Observe event on all registered observers
+    *
+    * @access     public
+    * @param variant $obj the $obj or message that you want to log/listen to, etc.
+    * @param int $ltype the type/level
+    */
+	public function Observe($obj, $ltype = OBSERVE_INFO);
+}
+
+?>

+ 25 - 0
phreeze/libs/verysimple/Phreeze/IObserver.php

@@ -0,0 +1,25 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/** import supporting libraries */
+define("OBSERVE_DEBUG",1);
+define("OBSERVE_QUERY",2);
+define("OBSERVE_INFO",4);
+define("OBSERVE_WARN",8);
+define("OBSERVE_FATAL",16);
+
+/**
+ * IObserver is an interface that defines an object that can Observe IObservable objects
+ *
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc.
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    2.0
+ */
+interface IObserver
+{
+	public function Observe($obj, $ltype = OBSERVE_INFO);
+}
+
+?>

+ 58 - 0
phreeze/libs/verysimple/Phreeze/IRenderEngine.php

@@ -0,0 +1,58 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/**
+ * IRenderEngine is an interface that can be implemented and passed
+ * into Phreeze Controller constructor and will be used to
+ * render views.
+ *
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc.
+ * @copyright  1997-2010 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    1.0
+ */
+interface IRenderEngine
+{
+
+	function __construct($templatePath='',$compilePath='');
+
+	/**
+	 * Assigns a value which will be available to the view
+	 * @param string $key
+	 * @param variant $value
+	 */
+	public function assign($key,$value);
+
+	/**
+	 * Renders and outputs the given template file to the browser
+	 * @param string $template
+	 */
+	public function display($template);
+
+	/**
+	 * Renders and returns the given template file as a string
+	 * @param string $template
+	 * @return string
+	 */
+	public function fetch($template);
+
+	/**
+	 * Unassign a value
+	 * @param string $key
+	 */
+	public function clear($key);
+
+	/**
+	 * Clear all assigned variables
+	 */
+	public function clearAll();
+
+	/**
+	 * Return all assigned variables
+	 * @return array
+	 */
+	public function getAll();
+}
+
+?>

+ 58 - 0
phreeze/libs/verysimple/Phreeze/IRouter.php

@@ -0,0 +1,58 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/**
+ * IRouter is an interface used for routing URLs to a Controller/Method
+ *
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc.
+ * @copyright  1997-2012 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    1.0
+ */
+interface IRouter
+{
+	/**
+	 * 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 = '');
+
+	/**
+	 * 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 = "");
+
+	/**
+	 * 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 = '');
+
+	/**
+	 * 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();
+
+	/**
+	 * Returns the RESTful part of the url
+	 * For example, localhost/users/5 will return users/5
+	 * @return string
+	 */
+	public function GetUri();
+}
+?>

+ 51 - 0
phreeze/libs/verysimple/Phreeze/KeyMap.php

@@ -0,0 +1,51 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/** import supporting libraries */
+define("KM_LOAD_LAZY",1);
+define("KM_LOAD_EAGER",2);
+define("KM_LOAD_INNER",4);
+define("KM_TYPE_ONETOMANY",1);
+define("KM_TYPE_MANYTOONE",2);
+
+/**
+ * KeyMap is a class for storing mapping information for Foreign Keys.  KeyMaps are
+ * used by Phreeze to map DB relationships to their respective model objects
+ *
+ * @package    verysimple::Phreeze 
+ * @author     VerySimple Inc.
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    1.0
+ */
+class KeyMap
+{
+	public $KeyName;
+	public $KeyProperty;
+	public $ForeignObject;
+	public $ForeignKeyProperty;
+	public $KeyType;
+	public $LoadType;
+	
+	/**
+	 * Initializes the KeyMap
+	 *
+	 * @param string $kn KeyName a unique name for this key
+	 * @param string $kp KeyProperty the foreign key property
+	 * @param string $fo ForeignObject the name of the foreign model object
+	 * @param string $fkp ForeignKeyProperty the primary key property of the foreign model object
+	 * @param int $kt Key Type (optional default = KM_TYPE_ONETOMANY)
+	 * @param int $lt Load Type KM_LOAD_LAZY | KM_LOAD_EAGER (optional default = KM_LOAD_LAZY)
+	 */
+	public function __construct($kn, $kp, $fo, $fkp, $kt = KM_TYPE_ONETOMANY, $lt = KM_LOAD_LAZY)
+	{
+		$this->KeyName = $kn;
+		$this->KeyProperty = $kp;
+		$this->ForeignObject = $fo;
+		$this->ForeignKeyProperty = $fkp;
+		$this->KeyType = $kt;
+		$this->LoadType = $lt;
+	}
+}
+
+?>

+ 95 - 0
phreeze/libs/verysimple/Phreeze/MockRouter.php

@@ -0,0 +1,95 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/** import supporting libraries */
+require_once('IRouter.php');
+
+/**
+ * Mock router for unit testing purposes
+ *
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc.
+ * @copyright  1997-2012 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    1.0
+ */
+class MockRouter implements IRouter
+{
+	private $_params = array();
+	private $_uri;
+	private $_url;
+
+	/**
+	 *
+	 * @param string $paramName
+	 * @param string $paramName
+	 */
+	public function SetUrlParam( $paramName, $value )
+	{
+		$this->_params[$paramName] = $value;
+	}
+
+	/**
+	 * @inheritdocs
+	 */
+	public function GetRoute( $uri = "" )
+	{
+
+	}
+
+	/**
+	 * @see IRouter::GetUri()
+	 */
+	public function GetUri()
+	{
+		return $this->_uri;
+	}
+
+	/**
+	 * @inheritdocs
+	 */
+	public function GetUrl( $controller, $method, $params = '', $requestMethod = '' )
+	{
+		return $this->_url;
+	}
+
+	/**
+	 * @inheritdocs
+	 */
+	public function GetUrlParams()
+	{
+		return $this->_params;
+	}
+
+	/**
+	 * @inheritdocs
+	 */
+	public function GetUrlParam($paramKey, $default = '')
+	{
+		return array_key_exists($paramKey, $this->_params) ? $this->_params[$paramKey] : "";
+	}
+
+	/**
+	 *
+	 * @param unknown_type $value
+	 */
+	public function SetUri( $value )
+	{
+		$this->_uri = $value;
+	}
+
+	/**
+	 *
+	 * @param unknown_type $value
+	 */
+	public function SetUrl( $value )
+	{
+		$this->_url = $value;
+	}
+
+	public function ClearUrlParams()
+	{
+		$this->_params = array();
+	}
+}
+?>

+ 18 - 0
phreeze/libs/verysimple/Phreeze/NotFoundException.php

@@ -0,0 +1,18 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/**
+ * NotFoundExeption is thrown when a persisted object is requsted by primary key
+ * but does not exist in the data store
+ *
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc.
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    2.0
+ */
+class NotFoundException extends Exception
+{
+}
+
+?>

+ 49 - 0
phreeze/libs/verysimple/Phreeze/Observable.php

@@ -0,0 +1,49 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/** import supporting libraries */
+require_once("IObservable.php");
+
+/**
+ * Observable is an abstract implementation of IObservable 
+ *
+ * @package    verysimple::Phreeze 
+ * @author     VerySimple Inc.
+ * @copyright  1997-2005 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    2.0
+ */
+abstract class Observable implements IObservable
+{
+
+    private $_observers = Array();
+    
+    /**
+    * Registers an observer with this object
+    *
+    * @param IObserver $observer
+    */
+	public function AttachObserver($observer)
+	{
+		if ($observer)
+		{
+			$this->_observers[] =& $observer;
+		}
+	}
+	
+	/**
+	* Fires and observable event.  All registered observers will be notified
+	*
+	* @param variant $obj a string, numeric or object that contains the observable message
+	* @param int $ltype specified the "level" as defined in IObservable
+	*/
+	public function Observe($obj, $ltype = OBSERVE_INFO)
+	{
+		foreach ($this->_observers as $observer)
+		{
+			$observer->Observe($obj, $ltype);
+		}
+	}
+}
+
+?>

+ 53 - 0
phreeze/libs/verysimple/Phreeze/ObserveToBrowser.php

@@ -0,0 +1,53 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/** import supporting libraries */
+require_once("IObserver.php");
+ 
+/**
+ * ObserverToBrowser is an implementation of IObserver that prints all
+ * messages to the browser
+ *
+ * @package    verysimple::Phreeze 
+ * @author     VerySimple Inc.
+ * @copyright  1997-2005 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    2.0
+ */
+class ObserveToBrowser implements IObserver
+{
+	
+	public function Observe($obj, $ltype = OBSERVE_INFO)
+	{
+		if (is_object($obj) || is_array($obj))
+		{
+			$msg = "<pre>" . print_r($obj, 1) . "</pre>";
+		}
+		else
+		{
+			$msg = $obj;
+		}
+		
+		switch ($ltype)
+		{
+			case OBSERVE_DEBUG:
+				print "<div class='debug'>$msg</div>\n";
+				break;
+			case OBSERVE_QUERY:
+				print "<div class='query'>$msg</div>\n";
+				break;
+			case OBSERVE_FATAL:
+				print "<div class='fatal'>$msg</div>\n";
+				break;
+			case OBSERVE_INFO:
+				print "<div class='info'>$msg</div>\n";
+				break;
+			case OBSERVE_WARN:
+				print "<div class='warn'>$msg</div>\n";
+				break;
+		}
+	}
+
+}
+
+?>

+ 118 - 0
phreeze/libs/verysimple/Phreeze/ObserveToFile.php

@@ -0,0 +1,118 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/** import supporting libraries */
+require_once("IObserver.php");
+require_once("verysimple/HTTP/RequestUtil.php");
+/**
+ * ObserverToBrowser is an implementation of IObserver that writes all
+ * messages to a file
+ *
+ * @package    verysimple::Phreeze 
+ * @author     VerySimple Inc.
+ * @copyright  1997-2005 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    2.0
+ */
+class ObserveToFile implements IObserver
+{
+	private $filepath;
+	private $eventtype;
+	private $fh;
+	private $fileIsOpen = false;
+	
+	public function __construct($filepath, $eventtype = null)
+	{
+		$this->filepath = $filepath;
+		$this->eventtype = $eventtype;
+		
+		$this->Init();
+	}
+	
+	public function __destruct()
+	{
+		@fclose($this->fh);
+		$this->fileIsOpen = false;
+	}
+	
+	function Init()
+	{
+		$this->fh = fopen($this->filepath,"a");
+		$this->fileIsOpen = true;
+		fwrite($this->fh,"DEBUG:\t". date("Y-m-d H:i:s:u") . "\t" . getmypid() . "\t########## ObserveToFile Initialized: " . RequestUtil::GetCurrentURL() . " ##########\r\n");
+	}
+	
+	public function Observe($obj, $ltype = OBSERVE_INFO)
+	{
+		if (is_object($obj) || is_array($obj))
+		{
+			$msg = "<pre>" . print_r($obj, 1) . "</pre>";
+		}
+		else
+		{
+			$msg = $obj;
+		}
+		
+		$msg = date("Y-m-d H:i:s:u") . "\t" . getmypid() . "\t" . str_replace(array("\t","\r","\n"),array(" "," "," "), $msg); 
+		
+		if ($this->eventtype == null || $this->eventtype & $ltype)
+		{
+			// this can occur if the file has been closed due to the php script terminating
+			if (!$this->fileIsOpen) 
+			{
+				$this->Init();
+				fwrite($this->fh, "WARN:\t". date("Y-m-d H:i:s:u") . "\tfilehandle was re-opened due to Observe being called after destruction\r\n");
+			}
+			
+			switch ($ltype)
+			{
+				case OBSERVE_DEBUG:
+					fwrite($this->fh, "DEBUG:\t$msg\r\n");
+					break;
+				case OBSERVE_QUERY:
+					//fwrite($this->fh, "QUERY:\t" . $this->FormatTrace(debug_backtrace()) . " " . $msg . "\r\n");
+					fwrite($this->fh, "QUERY:\t" . $msg . "\r\n");
+					break;
+				case OBSERVE_FATAL:
+					fwrite($this->fh, "FATAL:\t$msg\r\n");
+					break;
+				case OBSERVE_INFO:
+					fwrite($this->fh, "INFO:\t$msg\r\n");
+					break;
+				case OBSERVE_WARN:
+					fwrite($this->fh, "WARN:\t$msg\r\n");
+					break;
+			}
+			
+		}
+	}
+	
+	private function FormatTrace($tb, $join = " :: ", $show_lines = false)
+	{
+		$msg = "";
+		$delim = "";
+		
+		$calling_function = "";
+		$calling_line = "[?]";
+		for ($x = count($tb); $x > 0; $x--)
+		{
+			$stack = $tb[$x-1];
+			$s_file = isset($stack['file']) ? basename($stack['file']) : "[?]";
+			$s_line = isset($stack['line']) ? $stack['line'] : "[?]";
+			$s_function = isset($stack['function']) ? $stack['function'] : "";
+			$s_class = isset($stack['class']) ? $stack['class'] : "";
+			$s_type = isset($stack['type']) ? $stack['type'] : "";
+			
+			$msg .= $delim . "$calling_function" . ($show_lines ? " ($s_file Line $s_line)" : "");
+			$calling_function = $s_class . $s_type . $s_function;
+			
+			$delim = $join;
+		}
+		
+		return $msg;
+		
+	}
+	
+}
+
+?>

+ 67 - 0
phreeze/libs/verysimple/Phreeze/ObserveToSmarty.php

@@ -0,0 +1,67 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/** import supporting libraries */
+require_once("IObserver.php");
+ 
+/**
+ * ObserverToBrowser is an implementation of IObserver that outputs all
+ * messages to the smarty debug console
+ *
+ * @package    verysimple::Phreeze 
+ * @author     VerySimple Inc.
+ * @copyright  1997-2005 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    2.0
+ */
+class ObserveToSmarty implements IObserver
+{
+	private $_smarty = null;
+	private $_counter = 0;
+	
+	public function __construct($smarty)
+	{
+		$this->_smarty = $smarty;
+		$this->_smarty->debugging = true;
+	}
+	
+	public function Observe($obj, $ltype = OBSERVE_INFO)
+	{
+		
+		if (is_object($obj) || is_array($obj))
+		{
+			$msg = "<pre>" . print_r($obj, 1) . "</pre>";
+		}
+		else
+		{
+			$msg = $obj;
+		}
+		
+		$desc = "";
+
+		switch ($ltype)
+		{
+			case OBSERVE_DEBUG:
+				$desc = "DEBUG";
+				break;
+			case OBSERVE_QUERY:
+				$desc = "QUERY";
+				$msg = $desc . " " . $msg;
+				break;
+			case OBSERVE_FATAL:
+				$desc = "FATAL";
+				break;
+			case OBSERVE_INFO:
+				$desc = "INFO";
+				break;
+			case OBSERVE_WARN:
+				$desc = "WARN";
+				break;
+		}
+		
+		$this->_smarty->assign(str_pad($this->_counter++,3,"0",STR_PAD_LEFT) . "_" . $desc ,$msg);
+	}
+
+}
+
+?>

+ 127 - 0
phreeze/libs/verysimple/Phreeze/PHPRenderEngine.php

@@ -0,0 +1,127 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+require_once("IRenderEngine.php");
+
+/**
+ * PHPRenderEngine is an implementation of IRenderEngine
+ * that uses PHP as the template language
+ *
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc.
+ * @copyright  1997-2010 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    1.0
+ */
+class PHPRenderEngine implements IRenderEngine
+{
+	/** the file path to the template director */
+	public $tempatePath;
+
+	/** if this is true, all views will have .php appended the last 4 chars are not .php */
+	public $verifyExtension = true;
+
+	/** stores the assigned vars */
+	public $model = Array();
+
+	/**
+	 * @param string $templatePath
+	 * @param string $compilePath (not used for this render engine)
+	 */
+	function __construct($templatePath = '',$compilePath = '')
+	{
+		$this->templatePath = $templatePath;
+
+		if (substr($path,-1) != '/' && substr($path,-1) != '\\') $this->templatePath .= "/";
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	public function assign($key,$value)
+	{
+		$this->model[$key] = $value;
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	public function display($template)
+	{
+		// these two are special templates used by the Phreeze controller and dispatcher
+		if ($template == "_redirect.tpl")
+		{
+			header("Location: " . $this->model['url']);
+			die();
+		}
+		elseif ($template == "_error.tpl")
+		{
+			die("<h4>" . $this->model['message'] . "</h4>" . $this->model['stacktrace']);
+		}
+		else
+		{
+			if ($this->verifyExtension && substr($template,-4) != '.php') $template .= ".php";
+
+			$path = $this->templatePath . $template;
+
+			if (!is_readable($path))
+			{
+				throw new Exception("The template file '" . htmlspecialchars($path) . "' was not found.");
+			}
+
+			// make this available at the scope of the included file
+			$engine = $this;
+			$model = $this->model;
+			include_once($path);
+		}
+	}
+
+	/**
+	 * Returns the specified model value
+	 */
+	public function get($key)
+	{
+		return $this->model[$key];
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	public function fetch($template)
+	{
+		ob_start();
+
+		$this->display($template);
+		$buffer = ob_get_contents();
+
+		ob_end_clean();
+
+		return $buffer;
+	}
+
+	/**
+	 * @see IRenderEngine::clear()
+	 */
+	function clear($key)
+	{
+		if (array_key_exists($key,$this->model)) unset($this->model[$key]);
+	}
+
+	/**
+	 * @see IRenderEngine::clearAll()
+	 */
+	function clearAll()
+	{
+		$this->model == array();
+	}
+
+	/**
+	 * @see IRenderEngine::getAll()
+	 */
+	function getAll()
+	{
+		return $this->model;
+	}
+}
+
+?>

+ 779 - 0
phreeze/libs/verysimple/Phreeze/Phreezable.php

@@ -0,0 +1,779 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/**
+ * Phreezable Class
+ *
+ * Abstract base class for object that are persistable by Phreeze
+ *
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc. <[email protected]>
+ * @copyright  1997-2005 VerySimple Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    1.3
+ */
+abstract class Phreezable implements Serializable
+{
+    private $_cache = Array();
+    protected $_phreezer;
+	protected $_val_errors = Array();
+	protected $_base_validation_complete = false;
+
+    private $_isLoaded;
+	private $_isPartiallyLoaded;
+	private $_cacheLevel = 0;
+	private $_noCache = false;
+
+	/** @var these properties will never be cached */
+	private static $NoCacheProperties = array("_cache","_phreezer","_val_errors","_base_validation_complete");
+
+	/** @var cache of public properties for each type for improved performance when enumerating */
+	private static $PublicPropCache = array();
+
+	/**
+	* Returns true if the current object has been loaded
+	* @access     public
+	* @param      bool (optional) if provided will change the value
+	* @return     bool
+	*/
+	public function IsLoaded($value = null)
+	{
+		if ($value != null) $this->_isLoaded = $value;
+		return $this->_isLoaded;
+	}
+
+	/**
+	* Returns true if the current object has been partially loaded
+	* @access     public
+	* @param      bool (optional) if provided will change the value
+	* @return     bool
+	*/
+	public function IsPartiallyLoaded($value = null)
+	{
+		if ($value != null) $this->_isPartiallyLoaded = $value;
+		return $this->_isPartiallyLoaded;
+	}
+
+	/**
+	* Returns 0 if this was loaded from the DB, 1 if from 1st level cache and 2 if 2nd level cache
+	* @access     public
+	* @param      bool (optional) if provided will change the value
+	* @return     bool
+	*/
+	public function CacheLevel($value = null)
+	{
+		if ($value != null) $this->_cacheLevel = $value;
+		return $this->_cacheLevel;
+	}
+
+	/**
+	* Returns true if the current object should never be cached
+	* @access     public
+	* @param      bool (optional) if provided will change the value
+	* @return     bool
+	*/
+	public function NoCache($value = null)
+	{
+		if ($value != null) $this->_noCache = $value;
+		return $this->_noCache;
+	}
+
+	/**
+	 * Returns an array with all public properties, excluding any internal
+	 * properties used by the Phreeze framework.  This is cached for performance
+	 * when enumerating through large numbers of the same class
+	 * @return array
+	 */
+	public function GetPublicProperties()
+	{
+		$className = get_class($this);
+
+		if (!array_key_exists($className, self::$PublicPropCache))
+		{
+
+			$props = array();
+			$ro = new ReflectionObject($this);
+
+			foreach ($ro->getProperties() as $rp )
+			{
+				$propname = $rp->getName();
+
+				if (!in_array($propname,self::$NoCacheProperties))
+				{
+					if (!($rp->isPrivate() || $rp->isStatic()))
+					{
+						$props[] = $propname;
+					}
+				}
+			}
+
+			self::$PublicPropCache[$className] = $props;
+		}
+
+		return self::$PublicPropCache[$className];
+	}
+
+	/**
+	 * When serializing, make sure that we ommit certain properties that
+	 * should never be cached or serialized.
+	 */
+	function serialize()
+	{
+
+		$propvals = array();
+		$ro = new ReflectionObject($this);
+
+		foreach ($ro->getProperties() as $rp )
+		{
+			$propname = $rp->getName();
+
+			if (!in_array($propname,self::$NoCacheProperties))
+			{
+				if (method_exists($rp,"setAccessible"))
+				{
+					$rp->setAccessible(true);
+					$propvals[$propname] = $rp->getValue($this);
+				}
+				elseif (!$rp->isPrivate())
+				{
+					// if < php 5.3 we can't serialize private vars
+					$propvals[$propname] = $rp->getValue($this);
+				}
+
+			}
+		}
+
+		return serialize($propvals);
+	}
+
+	/**
+	* @deprecated use ToObject
+	*/
+	function GetObject($props = null, $camelCase = false)
+	{
+		return $this->ToObject(array('props'=>$props,'camelCase'=>$camelCase));
+	}
+
+	/**
+	 * Return an object with a limited number of properties from this Phreezable object.
+	 * This can be used if not all properties are necessary, for example rendering as JSON
+	 *
+	 * This can be overriden per class for custom JSON output.  the overridden method may accept
+	 * additional option parameters that are not supported by the base Phreezable calss
+	 *
+	 * @param array assoc array of options. This is passed through from Controller->RenderJSON
+	 * 		props (array) array of props to return (if null then use all public props)
+	 * 		omit (array) array of props to omit
+	 * 		camelCase (bool) if true then first letter of each property is made lowercase
+	 * @return stdClass
+	 */
+	function ToObject($options = null)
+	{
+		if ($options === null) $options = array();
+		$props = array_key_exists('props', $options) ? $options['props'] : $this->GetPublicProperties();
+		$omit = array_key_exists('omit', $options) ? $options['omit'] : array();
+		$camelCase = array_key_exists('camelCase', $options) ? $options['camelCase'] : false;
+
+		$obj = new stdClass();
+
+		foreach ($props as $prop)
+		{
+			if (!in_array($prop, $omit))
+			{
+				$newProp = ($camelCase) ? lcfirst($prop) : $prop;
+				$obj->$newProp = $this->$prop;
+			}
+		}
+
+		return $obj;
+	}
+
+	/**
+	 * Reload the object when it awakes from serialization
+	 * @param $data
+	 */
+	function unserialize($data)
+	{
+		$propvals = unserialize($data);
+		$ro = new ReflectionObject($this);
+
+		foreach ($ro->getProperties() as $rp )
+		{
+			$propname = $rp->name;
+			if ( array_key_exists($propname,$propvals) )
+			{
+				if (method_exists($rp,"setAccessible"))
+				{
+					$rp->setAccessible(true);
+					$rp->setValue($this,$propvals[$propname]);
+				}
+				elseif (!$rp->isPrivate())
+				{
+					// if < php 5.3 we can't serialize private vars
+					$rp->setValue($this,$propvals[$propname]);
+				}
+
+			}
+		}
+	}
+
+
+    /**
+    * constructor
+    *
+    * @access     public
+    * @param      Phreezer $phreezer
+    * @param      Array $row
+    */
+	final function __construct(Phreezer &$phreezer, $row = null)
+    {
+		$this->_phreezer = $phreezer;
+		$this->_cache = Array();
+
+        if ($row)
+        {
+			$this->Init();
+            $this->Load($row);
+        }
+		else
+		{
+			$this->LoadDefaults();
+			$this->Init();
+		}
+    }
+
+    /**
+    * Init is called after contruction.  When loading, Init is called prior to Load().
+	* When creating a blank object, Init is called immediately after LoadDefaults()
+    *
+    * @access     public
+    */
+    public function Init()
+    {
+	}
+
+    /**
+    * LoadDefaults is called during construction if this object is not instantiated with
+	* a DB row.  The default values as specified in the fieldmap are loaded
+    *
+    * @access     public
+    */
+	public function LoadDefaults()
+	{
+		$fms = $this->_phreezer->GetFieldMaps(get_class($this));
+
+		foreach ($fms as $fm)
+		{
+			$prop = $fm->PropertyName;
+			$this->$prop = $fm->DefaultValue;
+		}
+	}
+
+	/**
+	* LoadFromObject allows this class to be populated from another class, so long as
+	* the properties are compatible.  This is useful when using reporters so that you
+	* can easily convert them to phreezable objects.  Be sure to check that IsLoaded
+	* is true before attempting to save this object.
+	*
+	* @access     public
+	* @param $src the object to populate from, which must contain compatible properties
+	*/
+	public function LoadFromObject($src)
+	{
+		$this->IsLoaded(true);
+		$src_cls = get_class($src);
+
+		foreach (get_object_vars($this) as $key => $val)
+		{
+			if (substr($key,0,1) != "_")
+			{
+				if (property_exists($src_cls ,$key))
+				{
+					$this->$key = $src->$key;
+					$this->IsPartiallyLoaded(true);
+				}
+				else
+				{
+					$this->IsLoaded(false);
+				}
+			}
+		}
+
+		$this->OnLoad();
+	}
+
+    /**
+    * Validate returns true if the properties all contain valid values.  If not,
+	* use GetValidationErrors to see which fields have invalid values.
+    *
+    * @access     public
+    */
+	public function Validate()
+	{
+		// force re-validation
+		$this->ResetValidationErrors();
+
+		$is_valid = (!$this->HasValidationErrors());
+
+		// if validation fails, remove this object from the cache otherwise invalid values can
+		// hang around and cause troubles.
+		if (!$is_valid)
+		{
+			$this->_phreezer->DeleteCache(get_class($this), $this->GetPrimaryKeyValue());
+		}
+
+		return $is_valid;
+	}
+
+	/**
+	 * Add a validation error to the error array
+	 * @param string property name
+	 * @param string error message
+	 */
+	protected function AddValidationError($prop,$msg)
+	{
+		$this->_val_errors[$prop] = $msg;
+	}
+
+	/**
+	 * Returns true if this object has validation errors
+	 * @return bool
+	 */
+	protected function HasValidationErrors()
+	{
+		$this->_DoBaseValidation();
+		return count($this->_val_errors) > 0;
+	}
+
+	/**
+	* Returns the error array - containing an array of fields with invalid values.
+	*
+	* @access     public
+	* @return     array
+	*/
+	public function GetValidationErrors()
+	{
+		$this->_DoBaseValidation();
+		return $this->_val_errors;
+	}
+
+	/**
+	 * Clears all previous validation errors
+	 */
+	protected function ResetValidationErrors()
+	{
+		$this->_val_errors = Array();
+		$this->_base_validation_complete = false;
+	}
+
+    /**
+    * populates the _val_errors array w/ phreezer
+    *
+    * @access     private
+    */
+	private function _DoBaseValidation()
+	{
+		$lenfunction = $this->_phreezer->DataAdapter->ConnectionSetting->Multibyte
+			? 'mb_strlen'
+			: 'strlen';
+		
+		if (!$this->_base_validation_complete)
+		{
+			$fms = $this->_phreezer->GetFieldMaps(get_class($this));
+
+			foreach ($fms as $fm)
+			{
+				$prop = $fm->PropertyName;
+
+				if (is_numeric($fm->FieldSize) && ($lenfunction($this->$prop) > $fm->FieldSize))
+				{
+					$this->AddValidationError($prop,"$prop exceeds the maximum length of " . $fm->FieldSize . "");
+				}
+
+				if ($this->$prop == "" && ($fm->DefaultValue || $fm->IsAutoInsert) )
+				{
+					// these fields are auto-populated so we don't need to validate them unless
+					// a specific value was provided
+				}
+				else
+				{
+					switch ($fm->FieldType)
+					{
+						case FM_TYPE_INT:
+						case FM_TYPE_SMALLINT:
+						case FM_TYPE_TINYINT:
+						case FM_TYPE_MEDIUMINT:
+						case FM_TYPE_BIGINT:
+						case FM_TYPE_DECIMAL:
+							if (!is_numeric($this->$prop))
+							{
+								$this->AddValidationError($prop,"$prop is not a valid number");
+							}
+							break;
+						case FM_TYPE_DATE:
+						case FM_TYPE_DATETIME:
+							if (strtotime($this->$prop) === '')
+							{
+								$this->AddValidationError($prop,"$prop is not a valid date/time value.");
+							}
+							break;
+						case FM_TYPE_ENUM:
+							if ( !in_array($this->$prop, $fm->GetEnumValues()) )
+							{
+								$this->AddValidationError($prop,"$prop is not valid value. Allowed values: " . implode(', ',$fm->GetEnumValues()) );
+							}
+							break;
+						default:
+							break;
+					}
+				}
+			}
+		}
+
+		// print_r($this->_val_errors);
+
+		$this->_base_validation_complete = true;
+	}
+
+    /**
+    * This static function can be overridden to populate this object with
+    * results of a custom query
+    *
+    * @access     public
+    * @param      Criteria $criteria
+    * @return     string or null
+    */
+    public static function GetCustomQuery($criteria)
+    {
+		return null;
+	}
+
+    /**
+    * Refresh the object in the event that it has been saved to the session or serialized
+    *
+    * @access     public
+    * @param      Phreezer $phreezer
+    * @param      Array $row
+    */
+    final function Refresh(&$phreezer, $row = null)
+    {
+		$this->_phreezer = $phreezer;
+
+		// also refresh any children in the cache in case they are accessed
+		foreach ($this->_cache as $child)
+		{
+			if ( in_array("Phreezable", class_parents($child)) )
+			{
+				$child->Refresh($phreezer, $row);
+			}
+		}
+
+        if ($row)
+        {
+            $this->Load($row);
+        }
+
+        $this->OnRefresh();
+	}
+
+    /**
+     * Serialized string representation of this object.  For sorting
+     * purposes it is recommended to override this method
+     */
+    function ToString()
+    {
+		return serialize($this);
+	}
+
+    /**
+    * Returns the name of the primary key property.
+    * TODO: does not support multiple primary keys.
+    *
+    * @access     public
+    * @return     string
+    */
+    function GetPrimaryKeyName()
+    {
+        $fms = $this->_phreezer->GetFieldMaps(get_class($this));
+        foreach ($fms as $fm)
+        {
+            if ($fm->IsPrimaryKey)
+            {
+				return $fm->PropertyName;
+			}
+        }
+
+		/*
+		print "<pre>";
+		$this->Data = "";
+		$this->_phreezer = null;
+		$this->_cache = null;
+		print_r($this);
+
+		print_r($fms);
+		die();
+		*/
+
+		throw new Exception("No Primary Key found for " . get_class($this));
+    }
+
+    /**
+    * Returns the value of the primary key property.
+    * TODO: does not support multiple primary keys.
+    *
+    * @access     public
+    * @return     string
+    */
+    function GetPrimaryKeyValue()
+    {
+        $prop = $this->GetPrimaryKeyName();
+		return $this->$prop;
+    }
+
+    /**
+    * Returns this object as an associative array with properties as keys and
+    * values as values
+    *
+    * @access     public
+    * @return     array
+    */
+    function GetArray()
+    {
+		$fms = $this->_phreezer->GetFieldMaps(get_class($this));
+		$cols = Array();
+
+        foreach ($fms as $fm)
+        {
+			$prop = $fm->PropertyName;
+			$cols[$fm->ColumnName] = $this->$prop;
+        }
+
+        return $cols;
+	}
+
+	/**
+	 * Persist this object to the data store
+	 *
+	 * @access public
+	 * @param bool $force_insert (default = false)
+	 * @return int auto_increment or number of records affected
+	 */
+	function Save($force_insert = false)
+	{
+		return $this->_phreezer->Save($this,$force_insert);
+	}
+
+	/**
+	 * Delete this object from the data store
+	 *
+	 * @access public
+	 * @return int number of records affected
+	 */
+	function Delete()
+	{
+		return $this->_phreezer->Delete($this);
+	}
+
+    /**
+    * Loads the object with data given in the row array.
+    *
+    * @access     public
+    * @param      Array $row
+    */
+    function Load(&$row)
+    {
+
+        $fms = $this->_phreezer->GetFieldMaps(get_class($this));
+		$this->_phreezer->Observe("Loading " . get_class($this),OBSERVE_DEBUG);
+
+        $this->IsLoaded(true); // assume true until fail occurs
+		$this->IsPartiallyLoaded(false); // at least we tried
+
+		// in order to prevent collisions on fields, QueryBuilder appends __tablename__rand to the
+		// sql statement.  We need to strip that out so we can match it up to the property names
+		$rowlocal = array();
+		foreach ($row as $key => $val)
+		{
+			$info = explode("___",$key);
+
+			// we prefer to use tablename.colname if we have it, but if not
+			// just use the colname
+			$newkey = isset($info[1]) ? ($info[1] . "." . $info[0]) : $info[0];
+			if (isset($rowlocal[$newkey]))
+			{
+				throw new Exception("The column `$newkey` was selected twice in the same query, causing a data collision");
+			}
+			$rowlocal[$newkey] = $val;
+
+		}
+
+        foreach ($fms as $fm)
+        {
+            if ( array_key_exists($fm->TableName . "." . $fm->ColumnName, $rowlocal) )
+            {
+				// first try to locate the field by tablename.colname
+				$prop = $fm->PropertyName;
+				$this->$prop = $rowlocal[$fm->TableName . "." . $fm->ColumnName];
+            }
+			elseif ( array_key_exists($fm->ColumnName, $rowlocal) )
+			{
+				// if we can't locate the field by tablename.colname, then just look for colname
+				$prop = $fm->PropertyName;
+				$this->$prop = $rowlocal[$fm->ColumnName];
+			}
+			else
+            {
+                // there is a required column missing from this $row array - mark as partially loaded
+                $this->_phreezer->Observe("Missing column '".$fm->ColumnName."' while loading " . get_class($this), OBSERVE_WARN);
+                $this->IsLoaded(false);
+				$this->IsPartiallyLoaded(true);
+            }
+        }
+
+		// now look for any eagerly loaded children - their fields should be available in this query
+		$kms = $this->_phreezer->GetKeyMaps(get_class($this));
+
+		foreach ($kms as $km)
+		{
+			if ($km->LoadType == KM_LOAD_EAGER || $km->LoadType == KM_LOAD_INNER)
+			{
+				// load the child object that was obtained eagerly and cache so we
+				// won't ever grab the same object twice in one page load
+				$this->_phreezer->IncludeModel($km->ForeignObject);
+				$foclass = $km->ForeignObject;
+				$fo = new $foclass($this->_phreezer,$row);
+
+				$this->_phreezer->SetCache(
+					$foclass,
+					$fo->GetPrimaryKeyValue(),
+					$fo,
+					$this->_phreezer->CacheQueryObjectLevel2
+				);
+			}
+		}
+		$this->_phreezer->Observe("Firing " . get_class($this) . "->OnLoad()",OBSERVE_DEBUG);
+		$this->OnLoad();
+    }
+
+	/**
+	* Returns a value from the local cache
+	*
+	* @access     public
+	* @deprecated this is handled internally by Phreezer now
+	* @param      string $key
+	* @return     object
+	*/
+	public function GetCache($key)
+    {
+		return (array_key_exists($key, $this->_cache) ? $this->_cache[$key] : null);
+	}
+
+	/**
+	* Sets a value from in local cache
+	*
+	* @access     public
+	* @deprecated this is handled internally by Phreezer now
+	* @param      string $key
+	* @param      object $obj
+	*/
+	public function SetCache($key, $obj)
+    {
+		$this->_cache[$key] = $obj;
+	}
+
+	/**
+	* Clears all values in the local cache
+	*
+	* @access     public
+	* @deprecated this is handled internally by Phreezer now
+	*/
+	public function ClearCache()
+    {
+		$this->_cache = Array();
+	}
+
+    /**
+    * Called after object is loaded, may be overridden
+    *
+    * @access     protected
+    */
+    protected function OnLoad(){}
+
+	/**
+	* Called by Phreezer prior to saving the object, may be overridden.
+	* If this function returns any non-true value, then the save operation
+	* will be cancelled.  This allows you to perform custom insert/update queries
+	* if necessary
+	*
+	* @access     protected
+	* @param      boolean $is_insert true if Phreezer considers this a new record
+	* @return     boolean
+	*/
+	public function OnSave($is_insert) {return true;}
+
+	/**
+    * Called by Phreezer after object is updated, may be overridden
+    *
+    * @access     public
+    */
+    public function OnUpdate(){}
+
+    /**
+    * Called by Phreezer after object is inserted, may be overridden
+    *
+    * @access     public
+    */
+    public function OnInsert(){}
+
+    /**
+    * Called by Phreezer after object is deleted, may be overridden
+    *
+    * @access     public
+    */
+    public function OnDelete(){}
+
+	/**
+	* Called by Phreezer before object is deleted, may be overridden.
+	* if a true value is not returned, the delete operation will be aborted
+	*
+	* @access     public
+	* @return	  bool
+	*/
+	public function OnBeforeDelete(){return true;}
+
+	/**
+    * Called after object is refreshed, may be overridden
+    *
+    * @access     public
+    */
+    public function OnRefresh(){}
+
+
+    /**
+    * Throw an exception if an undeclared property is accessed
+    *
+    * @access     public
+    * @param      string $key
+    * @throws     Exception
+    */
+    public function __get($key)
+    {
+        throw new Exception("Unknown property: $key");
+    }
+
+    /**
+    * Throw an exception if an undeclared property is accessed
+    *
+    * @access     public
+    * @param      string $key
+    * @param      string $val
+    * @throws     Exception
+    */
+    public function __set($key,$val)
+    {
+        throw new Exception("Unknown property: $key");
+    }
+
+}
+
+?>

+ 909 - 0
phreeze/libs/verysimple/Phreeze/Phreezer.php

@@ -0,0 +1,909 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/** import supporting libraries */
+require_once("Observable.php");
+require_once("Criteria.php");
+require_once("KeyMap.php");
+require_once("FieldMap.php");
+require_once("DataAdapter.php");
+require_once("NotFoundException.php");
+require_once("CacheRam.php");
+require_once("CacheNoCache.php");
+require_once("verysimple/IO/Includer.php");
+
+/**
+ * The Phreezer class is a factory for obtaining and working with Phreezable (persistable)
+ * objects.  The Phreezer is generally the starting point for the application where you
+ * will obtain one or more objects.
+ *
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc.
+ * @copyright  1997-2008 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    3.3.3
+ */
+class Phreezer extends Observable
+{
+	public $DataAdapter;
+
+	/**
+	 * Render engine can hold any arbitrary object used to render views
+	 * @var Smarty
+	 */
+	public $RenderEngine;
+
+	public static $Version = '3.3.3 HEAD';
+
+	/**
+	 * @var int expiration time for query & value cache (in seconds) default = 5
+	 * The default is a low value which will help only with floods of traffic, but
+	 * will prevent stale data from appearing
+	 */
+	public $ValueCacheTimeout = 5;
+
+	/**
+	 * @var int expiration time for single objects cache (in seconds)
+	 * All individual save operations will update the cache so this can be a higher value
+	 * as long as other non-phreeze applications are not also editing the database
+	 * and you are not doing bulk query updates.
+	 */
+	public $ObjectCacheTimeout = 300; // 5 minutes
+
+	/**
+	 * @var set to true to save each individual query object in the level-2 cache
+	 * this can lead to a lot of save operations on the level-2 cahce that don't
+	 * ever get read, so enable only if you know it will improve performance
+	 */
+	public $CacheQueryObjectLevel2 = false;
+
+	/**
+	 * @var string path used for saving lock files to prevent cache stampedes
+	 */
+	public $LockFilePath;
+	
+	
+	/**
+	 * @var array
+	 */
+	private $_mapCache;
+
+	/**
+	 * @var ICache
+	 */
+	private $_level1Cache;
+
+	/**
+	 * @var ICache
+	 */
+	private $_level2Cache;	
+	
+	/**
+	 * If Phreeze is loaded from a .phar file, return the path of that file
+	 * otherwise return empty string
+	 * @return string
+	 */
+	static function PharPath()
+	{
+		return 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
+    *
+    * @access public
+    * @param ConnectionSetting $csetting
+    * @param Observable $observer
+    */
+    public function __construct($csetting, $observer = null)
+	{
+		$this->_mapCache = new CacheRam();
+		$this->_level1Cache = new CacheRam();
+		$this->_level2Cache = new CacheNoCache();
+
+		parent::AttachObserver($observer);
+		$this->Observe("Phreeze Instantiated", OBSERVE_DEBUG);
+
+		$this->DataAdapter = new DataAdapter($csetting, $observer);
+	}
+
+	/**
+	* Sets a cache provider for the level 1 cache
+	* @param ICache $cache
+	*/
+	private function SetLevel1CacheProvider(ICache $cache)
+	{
+		$this->_level1Cache = $cache;
+	}
+
+	/**
+	* Sets a cache provider for the level 1 cache
+	* @param ICache $cache
+	*/
+	public function SetLevel2CacheProvider(ICache $cache, $lockFilePath = "")
+	{
+		$this->_level2Cache = $cache;
+		$this->LockFilePath = $lockFilePath;
+	}
+
+	/**
+	* ValueCache is a utility method allowing any object or value to
+	* be stored in the cache.  The timout is specified by
+	* ValueCacheTimeout.  This
+	*
+	* @param string $sql
+	* @param variant $val
+	* @param int cache timeout (in seconds) default = Phreezer->ValueCacheTimeout.  set to zero for no cache
+	* @return bool true if cache was set, false if not
+	*/
+	public function SetValueCache($key, $val, $timeout = null)
+	{
+		if (is_null($timeout)) $timeout = $this->ValueCacheTimeout;
+
+		if ($timeout <= 0) return false;
+
+		if (strlen($key) > 250) $key = substr($key,0,150) . md5($key);
+
+		$this->_level1Cache->Set(md5($key),$val,0,$timeout);
+		return $this->_level2Cache->Set($key,$val,0,$timeout);
+	}
+
+	/**
+	* Retreives an object or value that was persisted using SetValueCache
+	*
+	* @param string $key
+	* @return variant
+	*/
+	public function GetValueCache($key)
+	{
+		// save the trouble of retrieving the cache if it is not enabled
+		if ($this->ValueCacheTimeout <= 0) return null;
+
+		if (strlen($key) > 250) $key = substr($key,0,150) . md5($key);
+		$obj = $this->_level1Cache->Get(md5($key));
+		return $obj ? $obj : $this->_level2Cache->Get($key);
+	}
+
+	/**
+	* Deletes a value from the cache
+	* @param string $objectclass
+	* @param string $id
+	*/
+	public function DeleteCache($objectclass,$id)
+	{
+		$this->_level1Cache->Delete($objectclass . "_" . $id);
+		$this->_level2Cache->Delete($objectclass . "_" . $id);
+		$this->Observe("Deleted TYPE='$objectclass' ID='$id' from Cache",OBSERVE_DEBUG);
+	}
+
+	/**
+	* Sets value in the cache
+	* @param string $objectclass
+	* @param string $id
+	* @param Phreezable $val
+	* @param bool $includeCacheLevel2 true = cache both level 1 and 2.  false = cache only level 1. (default true)
+	* @param int optionally override the default cache timeout of Phreezer->ObjectCacheTimeout (in seconds)
+	*/
+	public function SetCache($objectclass,$id, Phreezable $val, $includeCacheLevel2 = true, $timeout = null)
+	{
+		if (is_null($timeout)) $timeout = $this->ObjectCacheTimeout;
+
+		if ($val->NoCache() || $timeout <= 0) return false;
+
+		// if the object hasn't changed at level 1, then supress the cache update
+		$obj = $this->_level1Cache->Get($objectclass . "_" . $id);
+
+		if ($obj && $obj->serialize() == $val->serialize())
+		{
+			$this->Observe("TYPE='$objectclass' ID='$id' level 1 cache has not changed.  SetCache was supressed",OBSERVE_DEBUG);
+			return false;
+		}
+
+		$this->_level1Cache->Set($objectclass . "_" . $id,$val, $timeout);
+
+		// cache level 2 only if specified
+		if ($includeCacheLevel2) $this->_level2Cache->Set($objectclass . "_" . $id,$val, $timeout);
+	}
+
+	/**
+	* Retrieves a value from the cache
+	* @param string $objectclass
+	* @param string $id
+	* @return Phreezable
+	*/
+	public function GetCache($objectclass,$id)
+	{
+		if ($this->ObjectCacheTimeout <= 0) return null;
+
+		$cachekey = $objectclass . "_" . $id;
+
+		// include the model so any serialized classes will not throw an exception
+		$this->IncludeModel($objectclass);
+
+		// see if this object was cached in the level 1 cache
+		$obj = $this->_level1Cache->Get($cachekey);
+
+		if ($obj)
+		{
+			$this->Observe("Retrieved TYPE='$objectclass' ID='$id' from 1st Level Cache",OBSERVE_DEBUG);
+			$obj->CacheLevel(1);
+			if (!$obj->IsLoaded()) $obj->Refresh($this);
+			return $obj;
+		}
+
+		// try the level 2 cahce
+		$obj = $this->_level2Cache->Get($cachekey);
+
+		if ($obj)
+		{
+			$this->Observe("Retrieved TYPE='$objectclass' ID='$id' from 2nd Level Cache",OBSERVE_DEBUG);
+			$obj->Refresh($this);
+			$obj->CacheLevel(2);
+
+			// we just got this from level 2, but it wasn't in level 1 so let's save it in level 1 for
+			$this->_level1Cache->Set($cachekey,$obj);
+
+			return $obj;
+		}
+
+		$this->Observe("No L1/L2 Cache for TYPE='$objectclass' ID='$id'",OBSERVE_DEBUG);
+		// $this->Observe("KEYS =" . serialize($this->_level1Cache->GetKeys()) ,OBSERVE_DEBUG);
+		return null;
+	}
+
+	/**
+    * Override of the base AttachObserver so that when an observer is attached, it
+	* will also be attached to all child objects.  Note that some initialization
+	* messages won't be observed unless you provide it in the Phreezer constructor
+    */
+	public function AttachObserver($observer)
+	{
+		parent::AttachObserver($observer);
+		$this->DataAdapter->AttachObserver($observer);
+	}
+
+	/**
+	* Phreezer::Compare is used internally by Phreezer::Sort
+	* @param object
+	* @param object
+	* @return bool
+	*/
+	static function Compare($a, $b)
+	{
+		return strcmp($a->ToString(), $b->ToString());
+	}
+
+
+	/**
+	* Sort an array of Phreezable objects.  ToString() is used as the sort
+	* key.  You must implmement ToString on your sortable objects in order
+	* for Phreezer::Sort to be effective
+	*
+	* @param array $objects array of objects
+	*/
+	static function Sort(&$objects)
+	{
+		usort($objects,array("Phreezer","Compare"));
+	}
+
+	/**
+	* Get one instance of an object based on criteria.  If multiple records
+	* are found, only the first is returned.  If no matches are found,
+	* an exception is thrown
+	*
+	* @access public
+	* @param string $objectclass the type of object that will be queried
+    * @param Criteria $criteria a Criteria object to limit results
+	* @param bool $crash_if_multiple_found default value = true
+	* @param int cache timeout (in seconds).  Default is Phreezer->ValueCacheTimeout.  Set to 0 for no cache
+	* @return Phreezable
+	*/
+	public function GetByCriteria($objectclass, $criteria, $crash_if_multiple_found = true, $cache_timeout = null)
+	{
+		if (is_null($cache_timeout)) $cache_timeout = $this->ValueCacheTimeout;
+
+		if (strlen($objectclass) < 1)
+		{
+			throw new Exception("\$objectclass argument is required");
+		}
+
+		$obj = null;
+		$ds = $this->Query($objectclass, $criteria, $cache_timeout);
+
+		$ds->UnableToCache = false;
+
+		if (!$obj = $ds->Next())
+		{
+			throw new NotFoundException("$objectclass with specified criteria not found");
+		}
+
+		if ($crash_if_multiple_found && $ds->Next())
+		{
+			throw new Exception("More than one $objectclass with specified criteria was found");
+		}
+
+		return $obj;
+	}
+
+    /**
+    * Query for a specific type of object
+    *
+    * @access public
+    * @param string $objectclass the type of object that your DataSet will contain
+    * @param Criteria $criteria a Criteria object to limit results
+    * @param int cache timeout (in seconds).  Default is Phreezer->ValueCacheTimeout.  Set to 0 for no cache
+    * @return DataSet
+    */
+ 	public function Query($objectclass, $criteria = null, $cache_timeout = null)
+	{
+		if (is_null($cache_timeout)) $cache_timeout = $this->ValueCacheTimeout;
+
+		if (strlen($objectclass) < 1)
+		{
+			throw new Exception("\$objectclass argument is required");
+		}
+
+		// if criteria is null, then create a generic one
+		if (is_null($criteria))
+		{
+			$criteria = new Criteria();
+		}
+
+		// see if this object has a custom query designated
+		$custom = $this->GetCustomQuery($objectclass, $criteria);
+
+		$sql = "";
+		$count_sql = "";
+
+		if ($custom)
+		{
+			$this->Observe("Using Custom Query",OBSERVE_DEBUG);
+			$sql = $custom;
+
+			// the counter query may be blank, in which case DataSet will generate one
+			$count_sql = $this->GetCustomCountQuery($objectclass, $criteria);
+		}
+		else
+		{
+			// the first-level fieldmaps should be from the primary table
+			$fms = $this->GetFieldMaps($objectclass);
+
+			// the query builder will handle creating the SQL for us
+			$builder = new QueryBuilder($this);
+			$builder->RecurseFieldMaps($objectclass, $fms);
+
+			$sql = $builder->GetSQL($criteria);
+
+			$count_sql = $builder->GetCountSQL($criteria);
+		}
+
+		$ds = new DataSet($this, $objectclass, $sql, $cache_timeout);
+		$ds->CountSQL = $count_sql;
+
+		return $ds;
+
+	}
+
+    /**
+    * Get one instance of an object based on it's primary key value
+    *
+    * @access public
+    * @param string $objectclass the type of object that your DataSet will contain
+    * @param variant $id the value of the primary key
+    * @param int cache timeout (in seconds).  Default is Phreezer->ObjectCacheTimeout.  Set to 0 for no cache
+    * @return Phreezable
+    */
+	public function Get($objectclass, $id, $cache_timeout = null)
+	{
+		if (is_null($cache_timeout)) $cache_timeout = $this->ObjectCacheTimeout;
+
+		if (strlen($objectclass) < 1)
+		{
+			throw new Exception("\$objectclass argument is required");
+		}
+		if (strlen($id) < 1)
+		{
+			throw new Exception("\$id argument is required for $objectclass");
+		}
+
+		// see if this object was cached & if so return it
+		$obj = $cache_timeout == 0 ? null : $this->GetCache($objectclass,$id);
+		if ($obj) return $obj;
+
+		$pkm = $this->GetPrimaryKeyMap($objectclass);
+
+		if (!$pkm) throw new Exception("Table for '$objectclass' has no primary key");
+
+		$criteria = new Criteria();
+		$criteria->PrimaryKeyField = "`" . $pkm->TableName . "`.`" . $pkm->ColumnName . "`";
+		$criteria->PrimaryKeyValue = $id;
+
+		$ds = $this->Query($objectclass, $criteria);
+
+		// tell the dataset that we will be able to cache this query
+		$ds->UnableToCache = false;
+
+		if (!$obj = $ds->Next())
+		{
+			throw new NotFoundException("$objectclass with primary key of $id not found");
+		}
+
+		// cache the object for future use
+		$this->SetCache($objectclass,$id,$obj,$cache_timeout);
+
+		return $obj;
+	}
+
+	/**
+	 * Persist an object to the data store.  An insert or update will be executed based
+	 * on whether the primary key has a value.  use $form_insert to override this
+	 * in the case of a primary key that is not an auto_increment
+	 *
+	 * @access public
+	 * @param Object $obj the object to persist
+	 * @param bool $force_insert (default = false)
+	 * @return int the auto_increment id (insert) or the number of records updated (update)
+	 */
+	public function Save($obj, $force_insert = false)
+	{
+		$objectclass = get_class($obj);
+		$fms = $this->GetFieldMaps($objectclass);
+
+		$pk = $obj->GetPrimaryKeyName();
+		$id = $obj->$pk;
+		$table = $fms[$pk]->TableName;
+		$pkcol = $fms[$pk]->ColumnName;
+		$returnval = "";
+
+		$pk_is_auto_insert = strlen($id) == 0;
+
+		// if there is no value for the primary key, this is an insert
+		$is_insert = $force_insert || $pk_is_auto_insert;
+
+		// fire the OnSave event in case the object needs to prepare itself
+		// if OnSave returns false, then don't proceed with the save
+		$this->Observe("Firing ".get_class($obj)."->OnSave($is_insert)",OBSERVE_DEBUG);
+		if (!$obj->OnSave($is_insert))
+		{
+			$this->Observe("".get_class($obj)."->OnSave($is_insert) returned FALSE.  Exiting without saving",OBSERVE_WARN);
+			return false;
+		}
+
+		$sql = "";
+
+		if (!$is_insert)
+		{
+			// this is an update
+
+			// remove this class from the cache before saving
+			$this->DeleteCache($objectclass,$id);
+
+			$sql = "update `$table` set ";
+			$delim = "";
+			foreach ($fms as $fm)
+			{
+				if ((!$fm->IsPrimaryKey) && $fm->FieldType != FM_CALCULATION)
+				{
+					$prop = $fm->PropertyName;
+					$val = $obj->$prop;
+
+					try
+					{
+						$sql .= $delim . "`" . $fm->ColumnName . "` = " . $this->GetQuotedSql($val);
+					}
+					catch (Exception $ex)
+					{
+						throw new Exception("Error escaping property '$prop'. value could not be converted to string");
+					}
+
+					$delim = ", ";
+				}
+			}
+			$sql .= " where $pkcol = '" . $this->Escape($id) . "'";
+
+			$returnval = $this->DataAdapter->Execute($sql);
+
+			$obj->OnUpdate(); // fire OnUpdate event
+		}
+		else
+		{
+			// this is an insert
+			$sql = "insert into `$table` (";
+			$delim = "";
+			foreach ($fms as $fm)
+			{
+				// we don't want to include the primary key if this is an auto-increment table
+				if ( (!$fm->IsPrimaryKey) || $force_insert)
+				{
+					// calculated fields are not directly bound to a column and do not get persisted
+					if ($fm->FieldType != FM_CALCULATION)
+					{
+						$prop = $fm->PropertyName;
+						$val = $obj->$prop;
+						$sql .= $delim . "`" . $fm->ColumnName . "`";
+						$delim = ", ";
+					}
+				}
+			}
+
+			$sql .= ") values (";
+
+			$delim = "";
+			foreach ($fms as $fm)
+			{
+				// use the save logic inserting values as with the column names above
+				if ((!$fm->IsPrimaryKey) || $force_insert)
+				{
+					if ($fm->FieldType != FM_CALCULATION)
+					{
+						$prop = $fm->PropertyName;
+						$val = $obj->$prop;
+
+						try
+						{
+							$sql .= $delim . ' ' . $this->GetQuotedSql($val);
+						}
+						catch (Exception $ex)
+						{
+							throw new Exception("Error escaping property '$prop'. value could not be converted to string");
+						}
+
+						$delim = ", ";
+					}
+				}
+			}
+			$sql .= ")";
+
+			// for the insert we also need to get the insert id of the primary key
+			$returnval = $this->DataAdapter->Execute($sql);
+			if ($pk_is_auto_insert)
+			{
+				$returnval = $this->DataAdapter->GetLastInsertId();
+				$obj->$pk = $returnval;
+			}
+			$obj->OnInsert(); // fire OnInsert event
+		}
+
+		return $returnval;
+	}
+
+    /**
+    * Delete the given object from the data store
+    *
+    * @access public
+    * @param Object $obj the object to delete
+    */
+	public function Delete($obj)
+	{
+		$objectclass = get_class($obj);
+
+		if (!$obj->OnBeforeDelete())
+		{
+			$this->Observe("Delete was cancelled because OnBeforeDelete did not return true");
+			return 0;
+		}
+
+		$fms = $this->GetFieldMaps($objectclass);
+
+		$pk = $obj->GetPrimaryKeyName();
+		$id = $obj->$pk;
+		$table = $fms[$pk]->TableName;
+		$pkcol = $fms[$pk]->ColumnName;
+
+		$sql = "delete from `$table` where `$pkcol` = '" . $this->Escape($id) . "'";
+		$returnval = $this->DataAdapter->Execute($sql);
+
+		// remove from cache
+		$this->DeleteCache($objectclass,$id);
+
+		$obj->OnDelete(); // fire OnDelete event
+
+		return $returnval;
+
+	}
+
+    /**
+    * Delete all objects from the datastore used by the given object
+    *
+    * @access public
+    * @param Object $obj the object to delete
+    */
+	public function DeleteAll($obj)
+	{
+		$fms = $this->GetFieldMaps(get_class($obj));
+		$pk = $obj->GetPrimaryKeyName();
+		$table = $fms[$pk]->TableName;
+
+		$sql = "delete from `$table`";
+		$returnval = $this->DataAdapter->Execute($sql);
+		$obj->OnDelete(); // fire OnDelete event
+		return $returnval;
+
+	}
+
+    /**
+    * Returns all FieldMaps for the given object class
+    *
+    * @access public
+    * @param string $objectclass the type of object that your DataSet will contain
+    * @return Array of FieldMap objects
+    */
+	public function GetFieldMaps($objectclass)
+	{
+		// this is a temporary ram cache
+		$fms = $this->_mapCache->Get($objectclass."FieldMaps");
+		if ($fms) return $fms;
+
+		$this->IncludeModel($objectclass);
+
+		if (!class_exists($objectclass."Map")) throw new Exception($objectclass . " must either implement GetCustomQuery or '" . $objectclass."Map' class must exist in the include path.");
+		$fms = call_user_func( array($objectclass."Map","GetFieldMaps") );
+
+		$this->_mapCache->Set($objectclass."FieldMaps",$fms);
+		return $fms;
+	}
+
+    /**
+    * Returns the custom query for the given object class if it is defined
+    *
+    * @access public
+    * @param string $objectclass the type of object that your DataSet will contain
+    * @return Array of FieldMap objects
+    */
+	public function GetCustomQuery($objectclass, $criteria)
+	{
+		$this->IncludeModel($objectclass);
+		$sql = call_user_func( array($objectclass,"GetCustomQuery"),$criteria );
+		return $sql;
+	}
+
+	/**
+	* Returns the custom "counter" query for the given object class if it is defined
+	*
+	* @access public
+	* @param string $objectclass the type of object that your DataSet will contain
+	* @return Array of FieldMap objects
+	*/
+	public function GetCustomCountQuery($objectclass, $criteria)
+	{
+		$this->IncludeModel($objectclass);
+		$sql = call_user_func( array($objectclass,"GetCustomCountQuery"),$criteria );
+		return $sql;
+	}
+
+	static $cnt = 0; // used for debugging php memory errors due to circular references
+
+    /**
+    * Returns all KeyMaps for the given object class
+    *
+    * @access public
+    * @param string $objectclass the type of object
+    * @return Array of KeyMap objects
+    */
+	public function GetKeyMaps($objectclass)
+	{
+		// TODO: if a php memory error occurs within this method, uncomment this block to debug
+		/*
+		if (Phreezer::$cnt++ > 500)
+		{
+			throw new Exception("A sanity limit was exceeded when recursing KeyMaps for `$objectclass`.  Please check your Map for circular joins.");
+		}
+		//*/
+
+		// this is a temporary ram cache
+		$kms = $this->_mapCache->Get($objectclass."KeyMaps");
+		if ($kms) return $kms;
+
+		$this->IncludeModel($objectclass);
+		if (!class_exists($objectclass."Map")) throw new Exception("Class '" . $objectclass."Map' is not defined.");
+		$kms = call_user_func( array($objectclass."Map","GetKeyMaps") );
+
+		$this->_mapCache->Set($objectclass."KeyMaps",$kms);
+		return $kms;
+	}
+
+    /**
+    * Return specific FieldMap for the given object class with the given name
+    *
+    * @access public
+    * @param string $objectclass the type of object
+    * @param string $propertyname the name of the property
+    * @return Array of FieldMap objects
+    */
+	public function GetFieldMap($objectclass, $propertyname)
+	{
+		$fms = $this->GetFieldMaps($objectclass);
+		return $fms[$propertyname];
+	}
+
+    /**
+    * Return specific KeyMap for the given object class with the given name
+    *
+    * @access public
+    * @param string $objectclass the type of object
+    * @param string $keyname the name of the key
+    * @return Array of KeyMap objects
+    */
+	public function GetKeyMap($objectclass, $keyname)
+	{
+		$kms = $this->GetKeyMaps($objectclass);
+		return $kms[$keyname];
+	}
+
+    /**
+    * Returns the name of the DB column associted with the given property
+    *
+    * @access public
+    * @param string $objectclass the type of object
+    * @param string $propertyname the name of the property
+    * @return string name of the DB Column
+    */
+	public function GetColumnName($objectclass, $propertyname)
+	{
+		$fm = $this->GetFieldMap($objectclass, $propertyname);
+		return $fm->ColumnName;
+	}
+
+	/**
+	* Returns the name of the DB Table associted with the given property
+	*
+	* @access public
+	* @param string $objectclass the type of object
+	* @param string $propertyname the name of the property
+	* @return string name of the DB Column
+	*/
+	public function GetTableName($objectclass, $propertyname)
+	{
+		$fm = $this->GetFieldMap($objectclass, $propertyname);
+		return $fm->TableName;
+	}
+
+	/**
+    * Return the KeyMap for the primary key for the given object class
+    *
+    * @access public
+    * @param string $objectclass the type of object
+    * @return KeyMap object
+    */
+	public function GetPrimaryKeyMap($objectclass)
+	{
+		$fms = $this->GetFieldMaps($objectclass);
+		foreach ($fms as $fm)
+		{
+			if ($fm->IsPrimaryKey)
+			{
+				return $fm;
+			}
+		}
+	}
+
+
+    /**
+    * Query for a child objects in a one-to-many relationship
+    *
+    * @access public
+    * @param Phreezable $parent the parent object
+    * @param string $keyname The name of the key representing the relationship
+    * @return Criteria $criteria a Criteria object to limit the results
+    */
+    public function GetOneToMany($parent, $keyname, $criteria)
+    {
+
+		// get the keymap for this child relationship
+		$km = $this->GetKeyMap(get_class($parent), $keyname);
+
+		// we need the value of the foreign key.  (ex. to get all orders for a customer, we need Customer.Id)
+		$parent_prop = $km->KeyProperty;
+		$key_value = $parent->$parent_prop;
+
+		if (!$criteria)
+		{
+			// if no criteria was specified, then create a generic one.  we can specify SQL
+			// code in the constructor, but we have to translate the properties into column names
+			$foreign_table = $this->GetTableName($km->ForeignObject,$km->ForeignKeyProperty);
+			$foreign_column = $this->GetColumnName($km->ForeignObject,$km->ForeignKeyProperty);
+			$criteria = new Criteria("`" . $foreign_table . "`.`" . $foreign_column . "` = '" . $this->Escape($key_value) . "'");
+		}
+		else
+		{
+			// ensure that the criteria passed in will filter correctly by foreign key
+			$foreign_prop = $km->ForeignKeyProperty;
+
+			// this is only for backwards compatibility, but it should be ignored by current criteria objects
+			$criteria->$foreign_prop = $key_value;
+
+			// the current criteria "Equals" format "FieldName_Equals"
+			$foreign_prop .= "_Equals";
+			$criteria->$foreign_prop = $key_value;
+
+			// if this criteria has any or criterias attached, we need to set the foreign key to these
+			// as well or else we'll get unexpected results
+			foreach ($criteria->GetOrs() as $oc)
+			{
+				$oc->$foreign_prop = $key_value;
+			}
+		}
+
+		return $this->Query($km->ForeignObject,$criteria);
+	}
+
+    /**
+    * Query for a parent object in a many-to-one relationship
+    *
+    * @access public
+    * @param Phreezable $parent the parent object
+    * @param string $keyname The name of the key representing the relationship
+    * @return Phreezable object an object of the type specified by the KeyMap
+    */
+	public function GetManyToOne($parent, $keyname)
+	{
+		// get the keymap for this child relationship
+		$km = $this->GetKeyMap(get_class($parent), $keyname);
+
+		// we need the value of the foreign key.  (ex. to get all orders for a customer, we need Customer.Id)
+		// we also need to know the class of the object we're retrieving because if it's cached, we need to
+		// make sure the model file is loaded
+		$objectclass = $km->ForeignObject;
+		$parent_prop = $km->KeyProperty;
+		$key_value = $parent->$parent_prop;
+
+		// get this object  Get uses caching so we don't need to bother
+		$obj = $this->Get($km->ForeignObject,$key_value);
+
+		return $obj;
+
+	}
+
+	/**
+	* Dynamically override the LoadType for a KeyMap.  This is useful for
+	* eager fetching for a particular query.  One set, this configuration
+	* will be used until the end of the page context, or it is changed.
+	*
+	* @access public
+	* @param string $objectclass The name of the object class
+	* @param string $keyname The unique id of the KeyMap in the objects KeyMaps collection
+	* @param int $load_type (optional) KM_LOAD_INNER | KM_LOAD_EAGER | KM_LOAD_LAZY  (default is KM_LOAD_EAGER)
+	*/
+	public function SetLoadType($objectclass, $keyname, $load_type = KM_LOAD_EAGER)
+	{
+		$this->GetKeyMap($objectclass, $keyname)->LoadType = $load_type;
+	}
+
+
+	/**
+	 * Utility method that calls DataAdapter::Escape($val)
+	 * @param variant $val to be escaped
+	 * @return string
+	 */
+	public function Escape($val)
+	{
+		return DataAdapter::Escape($val);
+	}
+
+	/**
+	 * Utility method that calls DataAdapter::GetQuotedSql($val)
+	 * @param variant $val to be quoted
+	 * @return string
+	 */
+	private function GetQuotedSql($val)
+	{
+		return DataAdapter::GetQuotedSql($val);
+	}
+	
+	/**
+	* If the type is not already defined, attempts to require_once the definition.
+	* If the Model file cannot be located, an exception is thrown
+	*
+	* @access public
+	* @param string $objectclass The name of the object class
+	*/
+	public function IncludeModel($objectclass)
+	{
+		Includer::RequireClass($objectclass, array("Model/","Reporter/") );
+	}
+
+}
+
+?>

+ 302 - 0
phreeze/libs/verysimple/Phreeze/QueryBuilder.php

@@ -0,0 +1,302 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/**
+ * QueryBuilder generates the actual SQL that is executed by Phreezer
+ *
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc.
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    2.01
+ */
+ class QueryBuilder
+{
+	private $_phreezer;
+	private $_counter = 0;
+	public $Columns;
+	public $Tables;
+	public $Joins;
+
+	/**
+	 * Constructor
+	 *
+	 * @param Phreezer $phreezer persistance engine
+	 */
+	public function __construct($phreezer)
+	{
+		$this->_phreezer =& $phreezer;
+		$this->Columns = Array();
+		$this->Tables = Array();
+		$this->Joins = Array();
+	}
+	
+	/**
+	 * Adds a field map to the queue, which will be used later when the SQL is generated
+	 *
+	 * @param FieldMap $fm
+	 */
+	public function AddFieldMap($fm)
+	{
+		$tablealias = $fm->TableName;
+		if (!array_key_exists($tablealias, $this->Tables)) $this->Tables[$tablealias] = $fm->TableName;
+		
+		// debugging sequence of loading tables
+		// print "<div>QueryBuilder->AddFieldMap:" . $tablealias . "-&gt;" . $fm->ColumnName . "</div>";
+		
+		$this->Columns[$tablealias ."-". $fm->ColumnName] = $fm->FieldType == FM_CALCULATION 
+			? $fm->ColumnName 
+			: "`" . $tablealias . "`.`" . $fm->ColumnName . "` as `" . $fm->ColumnName . "___" . $fm->TableName . "___" . $this->_counter++ . "`";
+	}
+	
+	private $_keymapcache = array();  // used to check for recursive eager fetching
+	private $_prevkeymap;  // used to check for recursive eager fetching
+	
+	/**
+	 * Each Model that is to be included in this query is recursed, where all fields are added
+	 * to the queue and then looks for eager joins to recurse through
+	 *
+	 * @param string $typename name of the Model object to be recursed
+	 * @param FieldMap $fm
+	 */
+	public function RecurseFieldMaps($typename, $fms)
+	{
+
+		// if we see the same table again, we have an infinite loop
+		if (isset($this->_keymapcache[$typename]))
+		{
+			// return;  // TODO: why doesn't this work..?
+			throw new Exception("A circular EAGER join was detected while parsing `$typename`.  This is possibly due to an EAGER join with `".$this->_prevkeymap."`  Please edit your Map so that at least one side of the join is LAZY.");
+		}
+
+		// first we just add the basic columns of this object
+		foreach ($fms as $fm)
+		{
+			$this->AddFieldMap($fm);
+		}
+		
+		// get the keymaps for the requested object
+		$kms = $this->_phreezer->GetKeyMaps($typename);
+		$this->_keymapcache[$typename] = $kms;  // save this to prevent infinite loop
+		$this->_prevkeymap = $typename;
+		
+		// each keymap in this object that is eagerly loaded, we want to join into the query.
+		// each of these tables, then might have eagerly loaded keymaps as well, so we'll use
+		// recursion to eagerly load as much of the graph as is desired
+		foreach ($kms as $km)
+		{
+			if ($km->LoadType == KM_LOAD_EAGER || $km->LoadType == KM_LOAD_INNER)
+			{
+				// check that we didn't eagerly join this already.
+				// TODO: use aliases to support multiple eager joins to the same table
+				if (isset($this->Joins[$km->ForeignObject . "_is_joined"]))
+				{
+					//print_r($typename);
+					throw new Exception($typename ."Map has multiple EAGER joins to `" . $km->ForeignObject . "` which is not yet supported by Phreeze.");
+				}
+				
+				$ffms = $this->_phreezer->GetFieldMaps($km->ForeignObject);
+				
+				$this->RecurseFieldMaps($km->ForeignObject, $ffms);
+				
+				// lastly we need to add the join information for this foreign field map
+				$jointype = $km->LoadType == KM_LOAD_INNER ? "inner" : "left";
+				
+				foreach ($ffms as $ffm)
+				{
+					if (!isset($this->Joins[$ffm->TableName]))
+					{
+						$this->Joins[$ffm->TableName] = " ".$jointype." join `".$ffm->TableName."` on `" . $fms[$km->KeyProperty]->TableName . "`.`" .  $fms[$km->KeyProperty]->ColumnName . "` = `" . $ffms[$km->ForeignKeyProperty]->TableName . "`.`" . $ffms[$km->ForeignKeyProperty]->ColumnName . "`";
+					}
+
+				}
+				
+				// keep track of what we have eagerly joined already
+				$this->Joins[$km->ForeignObject . "_is_joined"] = 1;
+			}
+		}
+	}
+	
+	/**
+	 * Returns an array of column names that will be included in this query
+	 *
+	 * @return string comma-separated list of escaped DB column names
+	 */
+	public function GetColumnNames()
+	{
+		return implode(", ",array_values($this->Columns));
+	}
+	
+	/**
+	 * Determines what tables need to be included in the query 
+	 * based on the mapping
+	 * @param Criteria $criteria
+	 * @return string "from" sql
+	 */
+	private function GetTableJoinSQL($criteria)
+	{
+		$sql = "";
+		
+		$tablenames = array_keys($this->Tables);
+		
+		if (count($tablenames) > 1)
+		{
+			// we're selecting from multiple tables so we have to do an outer join
+			$sql .= " from `" . $tablenames[0] . "`";
+				
+			// WARNING: if tables are being joined in the incorrect order, it is likely
+			// caused by a query that goes across more than one foreign relationship.
+			// you can force tables to be joined in a specific order by adding a field
+			// reference in the outermost Phreezable FieldMap.  QueryBuilder will
+			// include tables in whatever order they are references, so you can control
+			// this by simply adding in referenced to foreign field in the order you want
+			// the tables to be joined
+			//for ($i = count($tablenames) -1; $i > 0 ; $i--) // this iterates backwards
+			for ($i = 1; $i < count($tablenames); $i++)      // this iterates forwards
+			{
+				try
+				{
+					$sql .= $this->Joins[$tablenames[$i]];
+				}
+				catch (Exception $ex)
+				{
+					// if 'undefined index' occurs here, there is likely a foreign field in the fieldmap that does not have it's related keymap set to KM_LOAD_EAGER
+					throw new Exception("An invalid join was attempted from table '" . $tablenames[$i] 
+						. "'. Please verify that the KeyMap fetching strategy for table '" . $tablenames[0]
+						. "' has been properly configured.");
+				}
+			}
+		}
+		else
+		{
+			// we are only selecting from one table
+			// (LL) added backticks here
+			$sql .= " from `" . $tablenames[0] . "` ";
+		}
+		
+		return $sql;
+	}
+	
+	/**
+	 * Removes the "where" from the beginning of a statement
+	 * @param string partial query to parse
+	 * @return string query without the preceeding "where"
+	 */
+	private function RemoveWherePrefix($sql)
+	{
+		$sql = trim($sql);
+		
+		// remove if the query is surrounded by parenths
+		while (substr($sql,0,1) == "(" && substr($sql,0,-1) == ")")
+		{
+			$sql = trim( substr($sql,1,-1) );
+		}
+		
+		while (strtolower( substr($sql, 0, 5) ) == "where")
+		{
+			$sql = trim(substr($sql,5));
+		}
+		
+		return $sql;
+	}
+	
+	/**
+	 * Returns the "where" part of the query based on the criteria parameters
+	 * @param Criteria $criteria
+	 * @return String "where" part of the sql query
+	 */
+	private function GetWhereSQL($criteria)
+	{
+		$ands = $criteria->GetAnds();
+		$ors = $criteria->GetOrs();
+		
+		// TODO: this all needs to move to the criteria object so it will recurse properly  ....
+		$where = $this->RemoveWherePrefix($criteria->GetWhere());
+		
+		if (count($ands))
+		{
+			$wdelim = ($where) ? " and " : "";
+			foreach($ands as $c)
+			{
+				$tmp = $c->GetWhere();
+				$buff = $this->RemoveWherePrefix($tmp);
+				if ($buff)
+				{
+					$where .= $wdelim . $buff;
+					$wdelim = " and ";
+				}
+			}
+		}
+		
+		if (count($ors))
+		{
+			$where = trim($where) ? "(" . $where . ")" : ""; // no primary criteria.  kinda strange
+			$wdelim = $where ? " or " : "";
+				
+			foreach($ors as $c)
+			{
+				$tmp = $c->GetWhere();
+				$buff = $this->RemoveWherePrefix($tmp);
+				if ($buff)
+				{
+					$where .= $wdelim . "(" . $buff . ")";
+					$wdelim = " or ";
+				}
+			}
+		}
+		
+		// .. end of stuff that should be in criteria	
+
+		// prepend the "where" onto the statement
+		if ($where) $where = " where (" . trim($where) . ") ";
+		
+		return $where;
+	}
+	
+	/**
+	 * Builds a SQL statement from the given criteria object and resets the Criteria
+	 *
+	 * @param Criteria $criteria
+	 * @return string fully formed SQL statement
+	 */
+	public function GetSQL($criteria)
+	{
+		// start building the sql statement
+		$sql = "select " . $this->GetColumnNames() . "";
+
+		$sql .= $this->GetTableJoinSQL($criteria);		
+
+		$sql .= $criteria->GetJoin();
+				
+		$sql .= $this->GetWhereSQL($criteria);
+		
+		$sql .= $criteria->GetOrder();
+		
+		$criteria->Reset();
+		
+		return $sql;
+	}
+	
+	/**
+	* Builds a SQL statement from the given criteria object to count the results and resets the Criteria
+	*
+	* @param Criteria $criteria
+	* @return string fully formed SQL statement
+	*/
+	public function GetCountSQL($criteria)
+	{
+		$sql = "select count(1) as counter ";
+	
+		$sql .= $this->GetTableJoinSQL($criteria);
+	
+		$sql .= $criteria->GetJoin();
+	
+		$sql .= $this->GetWhereSQL($criteria);
+		
+		$criteria->Reset();
+	
+		return $sql;
+	}
+}
+
+?>

+ 329 - 0
phreeze/libs/verysimple/Phreeze/Reporter.php

@@ -0,0 +1,329 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/**
+ * Reporter allows creating dynamic objects that do not necessarily reflect
+ * the structure of the datastore table.  This is often useful for reporting
+ * or returning aggregate data
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc. <[email protected]>
+ * @copyright  1997-2005 VerySimple Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    1.0
+ */
+abstract class Reporter
+{
+    protected $_phreezer;
+
+	private $_isLoaded;
+	private $_isPartiallyLoaded;
+	private $_cacheLevel = 0;
+	private $_noCache = false;
+
+	/** @var these properties will never be cached */
+	private static $NoCacheProperties = array("_cache","_phreezer","_val_errors","_base_validation_complete");
+
+	/** @var cache of public properties for each type for improved performance when enumerating */
+	private static $PublicPropCache = array();
+
+	/**
+	* Returns true if the current object has been loaded
+	* @access     public
+	* @param      bool (optional) if provided will change the value
+	* @return     bool
+	*/
+	public function IsLoaded($value = null)
+	{
+		if ($value != null) $this->_isLoaded = $value;
+		return $this->_isLoaded;
+	}
+
+	/**
+	* Returns true if the current object has been partially loaded
+	* @access     public
+	* @param      bool (optional) if provided will change the value
+	* @return     bool
+	*/
+	public function IsPartiallyLoaded($value = null)
+	{
+		if ($value != null) $this->_isPartiallyLoaded = $value;
+		return $this->_isPartiallyLoaded;
+	}
+
+	/**
+	* Returns 0 if this was loaded from the DB, 1 if from 1st level cache and 2 if 2nd level cache
+	* @access     public
+	* @param      bool (optional) if provided will change the value
+	* @return     bool
+	*/
+	public function CacheLevel($value = null)
+	{
+		if ($value != null) $this->_cacheLevel = $value;
+		return $this->_cacheLevel;
+	}
+
+	/**
+	* Returns true if the current object should never be cached
+	* @access     public
+	* @param      bool (optional) if provided will change the value
+	* @return     bool
+	*/
+	public function NoCache($value = null)
+	{
+		if ($value != null) $this->_noCache = $value;
+		return $this->_noCache;
+	}
+
+
+    /**
+    * constructor
+    *
+    * @access     public
+    * @param      Phreezer $phreezer
+    * @param      Array $row
+    */
+    final function __construct(&$phreezer, $row = null)
+    {
+		$this->_phreezer = $phreezer;
+
+        if ($row)
+        {
+            $this->Load($row);
+        }
+    }
+
+    /**
+	 * When serializing, make sure that we ommit certain properties that
+	 * should never be cached or serialized.
+	 */
+	function serialize()
+	{
+		$propvals = array();
+		$ro = new ReflectionObject($this);
+
+		foreach ($ro->getProperties() as $rp )
+		{
+			$propname = $rp->getName();
+
+			if (!in_array($propname,self::$NoCacheProperties))
+			{
+				if (method_exists($rp,"setAccessible"))
+				{
+					$rp->setAccessible(true);
+					$propvals[$propname] = $rp->getValue($this);
+				}
+				elseif (!$rp->isPrivate())
+				{
+					// if < php 5.3 we can't serialize private vars
+					$propvals[$propname] = $rp->getValue($this);
+				}
+
+			}
+		}
+
+		return serialize($propvals);
+	}
+
+	/**
+	 * Reload the object when it awakes from serialization
+	 * @param $data
+	 */
+	function unserialize($data)
+	{
+		$propvals = unserialize($data);
+
+		$ro = new ReflectionObject($this);
+
+		foreach ($ro->getProperties() as $rp )
+		{
+			$propname = $rp->name;
+			if ( array_key_exists($propname,$propvals) )
+			{
+				if (method_exists($rp,"setAccessible"))
+				{
+					$rp->setAccessible(true);
+					$rp->setValue($this,$propvals[$propname]);
+				}
+				elseif (!$rp->isPrivate())
+				{
+					// if < php 5.3 we can't serialize private vars
+					$rp->setValue($this,$propvals[$propname]);
+				}
+
+			}
+		}
+	}
+
+	/**
+	* Returns an array with all public properties, excluding any internal
+	* properties used by the Phreeze framework.  This is cached for performance
+	* when enumerating through large numbers of the same class
+	* @return array
+	*/
+	public function GetPublicProperties()
+	{
+		$className = get_class($this);
+
+		if (!array_key_exists($className, self::$PublicPropCache))
+		{
+
+			$props = array();
+			$ro = new ReflectionObject($this);
+
+			foreach ($ro->getProperties() as $rp )
+			{
+				$propname = $rp->getName();
+
+				if (!in_array($propname,self::$NoCacheProperties))
+				{
+					if (!($rp->isPrivate() || $rp->isStatic()))
+					{
+						$props[] = $propname;
+					}
+				}
+			}
+
+			self::$PublicPropCache[$className] = $props;
+		}
+
+		return self::$PublicPropCache[$className];
+	}
+
+	/**
+	* Return an object with a limited number of properties from this Phreezable object.
+	* This can be used if not all properties are necessary, for example rendering as JSON
+	*
+	* This can be overriden per class for custom JSON output.  the overridden method may accept
+	* additional option parameters that are not supported by the base Phreezable calss
+	*
+	* @param array assoc array of options. This is passed through from Controller->RenderJSON
+	* 		props (array) array of props to return (if null then use all public props)
+	* 		omit (array) array of props to omit
+	* 		camelCase (bool) if true then first letter of each property is made lowercase
+	* @return stdClass
+	*/
+	function ToObject($options = null)
+	{
+		if ($options === null) $options = array();
+		$props = array_key_exists('props', $options) ? $options['props'] : $this->GetPublicProperties();
+		$omit = array_key_exists('omit', $options) ? $options['omit'] : array();
+		$camelCase = array_key_exists('camelCase', $options) ? $options['camelCase'] : false;
+
+		$obj = new stdClass();
+
+		foreach ($props as $prop)
+		{
+			if (!in_array($prop, $omit))
+			{
+				$newProp = ($camelCase) ? lcfirst($prop) : $prop;
+				$obj->$newProp = $this->$prop;
+			}
+		}
+
+		return $obj;
+	}
+
+	/**
+	 * Restores the object's connection to the datastore, for example after serialization
+	 * @param $phreezer
+	 * @param $row
+	 */
+	function Refresh(Phreezer $phreezer, $row = null)
+	{
+		$this->_phreezer = $phreezer;
+
+		if ($row)
+        {
+            $this->Load($row);
+        }
+
+         $this->OnRefresh();
+	}
+
+	/**
+    * Called after object is refreshed, may be overridden
+    *
+    * @access     public
+    */
+    public function OnRefresh(){}
+
+    /**
+    * This static function can be overridden to populate this object with
+    * results of a custom query
+    *
+    * @access     public
+    * @param      Criteria $criteria
+    * @return     string
+    */
+    static function GetCustomQuery($criteria)
+    {
+    	return "";
+    }
+
+    /**
+	 * This may be overridden to return SQL used for counting the number of rows
+	 * in a result.  This method is not required, however it will allow 
+	 * Phreeze to use an efficient query for counting results.  This query 
+	 * must return the correct number of results that GetCustomQuery would, 
+	 * given the same criteria
+	 * 
+	 * The resultant SQL must return only one row with one column named 'counter'
+    *
+    * @access     public
+    * @param      Criteria $criteria
+    * @return     string
+    */
+    static function GetCustomCountQuery($criteria)
+    {
+    	return "";
+    }
+
+	/**
+    * Returns this object as an associative array with properties as keys and
+    * values as values
+    *
+    * @access     public
+    * @return     array
+    */
+    function GetArray()
+    {
+		$fms = $this->_phreezer->GetFieldMaps(get_class($this));
+		$cols = Array();
+
+        foreach ($fms as $fm)
+        {
+			$prop = $fm->PropertyName;
+			$cols[$fm->ColumnName] = $this->$prop;
+        }
+
+        return $cols;
+	}
+
+    /**
+    * Loads the object with data given in the row array.
+    *
+    * @access     public
+    * @param      Array $row
+    */
+    function Load(&$row)
+    {
+		$this->_phreezer->Observe("Loading " . get_class($this),OBSERVE_DEBUG);
+
+		foreach (array_keys($row) as $prop)
+		{
+			$this->$prop = $row[$prop];
+		}
+
+        $this->OnLoad();
+    }
+
+    /**
+    * Called after object is loaded, may be overridden
+    *
+    * @access     protected
+    */
+    protected function OnLoad(){}
+
+}
+
+?>

+ 112 - 0
phreeze/libs/verysimple/Phreeze/SavantRenderEngine.php

@@ -0,0 +1,112 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+require_once("IRenderEngine.php");
+require_once('savant/Savant3.php');
+
+/**
+ * Implementation of IRenderEngine that uses Savant as the template language
+ *
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc.
+ * @copyright  1997-2010 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    1.0
+ */
+class SavantRenderEngine implements IRenderEngine
+{
+
+	static $TEMPLATE_EXTENSION = ".tpl.php";
+
+	/** @var Savant3 */
+	public $savant;
+
+
+	/**
+	 * @param string $templatePath
+	 * @param string $compilePath (not used for this render engine)
+	 */
+	function __construct($templatePath = '',$compilePath = '')
+	{
+		$this->savant = new Savant3(array('exceptions'=>true));
+
+		// normalize the path
+		if (substr($templatePath,-1) != '/' && substr($templatePath,-1) != '\\') $templatePath .= "/";
+
+		if ($templatePath) $this->savant->setPath('template',$templatePath);
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	public function assign($key,$value)
+	{
+		$this->savant->$key = $value;
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	public function display($template)
+	{
+		// strip off .tpl from the end for backwards compatibility with older apps
+		if (substr($template, -4) == '.tpl') $template = substr($template,0,-4);
+
+		// these two are special templates used by the Phreeze controller and dispatcher
+		if ($template == "_redirect")
+		{
+			header("Location: " . $this->savant->url);
+			die();
+		}
+		elseif ($template == "_error")
+		{
+			$this->savant->display('_error' . $TEMPLATE_EXTENSION);
+		}
+		else
+		{
+			$this->savant->display($template . self::$TEMPLATE_EXTENSION);
+		}
+	}
+
+	/**
+	 * Returns the specified model value
+	 */
+	public function get($key)
+	{
+		return $this->savant->$key;
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	public function fetch($template)
+	{
+		return $this->savant->fetch($template . self::$TEMPLATE_EXTENSION);
+	}
+
+	/**
+	 * @see IRenderEngine::clear()
+	 */
+	function clear($key)
+	{
+		if (array_key_exists($key,$this->savant)) unset($this->savant[$key]);
+	}
+
+	/**
+	 * @see IRenderEngine::clearAll()
+	 */
+	function clearAll()
+	{
+		throw new Exception('clearAll not implemented for SavantRenderEngine');
+	}
+
+	/**
+	 * @see IRenderEngine::getAll()
+	 */
+	function getAll()
+	{
+		return get_object_vars($this->savant);
+	}
+}
+
+?>

+ 93 - 0
phreeze/libs/verysimple/Phreeze/SmartyRenderEngine.php

@@ -0,0 +1,93 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/** import supporting libraries */
+require_once("smarty/Smarty.class.php");
+require_once("verysimple/Phreeze/IRenderEngine.php");
+
+/**
+ * SmartyRenderEngine is an implementation of IRenderEngine that uses
+ * the Smarty template engine to render views
+ *
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc.
+ * @copyright  1997-2011 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    1.0
+ */
+class SmartyRenderEngine implements IRenderEngine
+{
+
+	/** @var Smarty */
+	public $smarty;
+
+	/**
+	 * @param string $templatePath
+	 * @param string $compilePath
+	 */
+	function __construct($templatePath = '',$compilePath = '')
+	{
+		$this->smarty = new Smarty();
+
+		if ($templatePath) $this->smarty->template_dir = $templatePath;
+
+		if ($compilePath)
+		{
+			$this->smarty->compile_dir = $compilePath;
+			$this->smarty->config_dir = $compilePath;
+			$this->smarty->cache_dir = $compilePath;
+		}
+	}
+
+	/**
+	 * @see IRenderEngine::assign()
+	 */
+	function assign($key, $value)
+	{
+		return $this->smarty->assign($key,$value);
+	}
+
+	/**
+	 * @see IRenderEngine::display()
+	 */
+	function display($template)
+	{
+		return $this->smarty->display($template);
+	}
+
+	/**
+	 * @see IRenderEngine::fetch()
+	 */
+	function fetch($template)
+	{
+		return $this->smarty->fetch($template);
+	}
+
+	/**
+	 * @see IRenderEngine::clear()
+	 */
+	function clear($key)
+	{
+		$this->smarty->clearAssign($key);
+	}
+
+	/**
+	 * @see IRenderEngine::clearAll()
+	 */
+	function clearAll()
+	{
+		$this->smarty->clearAllAssign();
+	}
+
+	/**
+	 * @see IRenderEngine::getAll()
+	 */
+	function getAll()
+	{
+		return $this->smarty->getTemplateVars();
+	}
+
+
+}
+
+?>

+ 32 - 0
phreeze/libs/verysimple/Phreeze/ValidationResponse.php

@@ -0,0 +1,32 @@
+<?php
+/** @package    verysimple::Phreeze */
+
+/**
+ * A validation response is an object that stores the results when an
+ * object is validated.  This code can be serialized as JSON and used
+ * in AJAX validation
+ * @package    verysimple::Phreeze
+ * @author     VerySimple Inc.
+ * @copyright  1997-2007 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    2.0
+ */
+class ValidationResponse
+{
+
+	public function __construct($success = false, $message = "", $primaryKey = "", $errors = null)
+	{
+		$this->Success = $success;
+		$this->Message = $message;
+		$this->Errors = $errors ? $errors : Array();
+		$this->PrimaryKeyValue = $primaryKey;
+	}
+
+	public $PrimaryKeyValue;
+	public $Success = false;
+	public $Errors = Array();
+	public $Message = "";
+	public $Data;
+}
+
+?>

+ 76 - 0
phreeze/libs/verysimple/String/NameValue.php

@@ -0,0 +1,76 @@
+<?php
+/** @package    verysimple::String */
+
+/**
+ * A abstraction of NameValue pairs with static functions for parsing simple text structures
+ *
+ * @package    verysimple::String
+ * @author Jason Hinkle
+ * @copyright  1997-2008 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version 1.0
+ */
+class NameValue
+{
+	public $Code;
+	public $Total;
+	
+	/**
+	 * Constructor optionally accepts a line that will be parsed into a name/value
+	 * @access public
+	 * @param $line the line to be parsed
+	 * @param $delim (default "=")
+	 */
+	function __construct($line = "", $delim = "=", $nameonly=false)
+	{
+		$keyval = explode($delim,$line);
+		$this->Name = $keyval[0];
+		$this->Value = $nameonly == false && isset($keyval[1]) ? $keyval[1] : $keyval[0];
+	}
+	
+	/**
+	 * Parses a string into an array of NameValue objects.
+	 * @access public
+	 * @param $lines string in the format name1=val1\nname2=val2 etc...
+	 * @param $delim the delimiter between name and value (default "=")
+	 * @param $nameonly returns only the name in the name/value pair
+	 * @return Array of NameValue objects
+	 */
+	static function Parse($lines, $delim="=", $nameonly=false)
+	{
+		$return = array();
+		
+		$lines = str_replace("\r\n","\n",$lines);
+		$lines = str_replace("\r","\n",$lines);
+		$arr = explode("\n", $lines );
+		
+		if ($lines=="") return $return;
+		
+		foreach ($arr as $line)
+		{
+			$return[] = new NameValue($line,$delim,$nameonly);
+		}
+		
+		return $return;
+	}
+	
+	/**
+	 * Converts an array of NameValue objects into a simple 1 dimensional array.
+	 * WARNING: if there are duplicate Names in your array, they will be overwritten
+	 * @access public
+	 * @param $nvArray Array of NameValue objects (as returned from Parse)
+	 * @return array
+	 */
+	static function ToSimpleArray($nvArray)
+	{
+		$sa = array();
+		foreach ($nvArray as $nv)
+		{
+			$sa[$nv->Name] = $nv->Value;
+		}
+		return $sa;
+	}
+	
+}
+
+?>

+ 170 - 0
phreeze/libs/verysimple/String/SimpleTemplate.php

@@ -0,0 +1,170 @@
+<?php
+/** @package	verysimple::String */
+
+/**
+ * A set of utility functions for working with simple template files
+ *
+ * @package	verysimple::String
+ * @author Jason Hinkle
+ * @copyright  1997-2011 VerySimple, Inc.
+ * @license	http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version 1.0
+ */
+class SimpleTemplate
+{
+	
+	/** @var used internally for merging. */
+	static $_MERGE_TEMPLATE_VALUES = null;
+	
+	/**
+	 * Transforms HTML into formatted plain text.
+	 * 
+	 * @param string HTML
+	 * @return string plain text
+	 */
+	static function HtmlToText($html)
+	{
+		require_once("class.html2text.php");
+		$h2t = new html2text($html);
+		return $h2t->get_text(); 
+	}
+	
+	/**
+	 * Transforms plain text into formatted HTML.
+	 * 
+	 * @param string plain text
+	 * @return string HTML
+	 */
+	static function TextToHtml($txt) 
+	{
+		//Kills double spaces and spaces inside tags.
+		while( !( strpos($txt,'  ') === FALSE ) ) $txt = str_replace('  ',' ',$txt);
+		$txt = str_replace(' >','>',$txt);
+		$txt = str_replace('< ','<',$txt);
+	
+		//Transforms accents in html entities.
+		$txt = htmlentities($txt);
+	
+		//We need some HTML entities back!
+		$txt = str_replace('&quot;','"',$txt);
+		$txt = str_replace('&lt;','<',$txt);
+		$txt = str_replace('&gt;','>',$txt);
+		$txt = str_replace('&amp;','&',$txt);
+	
+		//Ajdusts links - anything starting with HTTP opens in a new window
+		//$txt = str_ireplace("<a href=\"http://","<a target=\"_blank\" href=\"http://",$txt);
+		//$txt = str_ireplace("<a href=http://","<a target=\"_blank\" href=http://",$txt);
+
+		
+		//Basic formatting
+		$eol = ( strpos($txt,"\r") === FALSE ) ? "\n" : "\r\n";
+		$html = '<p>'.str_replace("$eol$eol","</p><p>",$txt).'</p>';
+		$html = str_replace("$eol","<br />\n",$html);
+		$html = str_replace("</p>","</p>\n\n",$html);
+		$html = str_replace("<p></p>","<p>&nbsp;</p>",$html);
+	
+		//Wipes <br> after block tags (for when the user includes some html in the text).
+		$wipebr = Array("table","tr","td","blockquote","ul","ol","li");
+	
+		for($x = 0; $x < count($wipebr); $x++) 
+		{
+			$tag = $wipebr[$x];
+			$html = str_ireplace("<$tag><br />","<$tag>",$html);
+			$html = str_ireplace("</$tag><br />","</$tag>",$html);
+		}
+	
+		return $html;
+	}
+	
+	/**
+	 * Merges data into a template with placeholder variables 
+	 * (for example "Hello {{NAME}}").  Useful for simple templating
+	 * needs such as email, form letters, etc.
+	 * 
+	 * If a placeholder is in the template but there is no matching value,
+	 * then the placeholder will be left alone and will appear in the output.
+	 * 
+	 * Note that there is no escape character so ensure the right and
+	 * left delimiters do not appear as normal text within the template
+	 * 
+	 * @param string $template string with placeholder variables
+	 * @param mixed (array or object) $values an associative array or object with key/value pairs
+	 * @param bool true to strip out placeholders with missing value, false to leave them as-is in the output (default true)
+	 * @param string the left (opening) delimiter for placeholders. default = {{
+	 * @param string the right (closing) delimiter for placeholders. default = }}
+	 * @return string merged template
+	 */
+	static function Merge($template, $values, $stripMissingValues = true, $ldelim = "{{", $rdelim = "}}")
+	{
+		return $stripMissingValues 
+			? self::MergeRegEx($template, $values, $ldelim, $rdelim)
+			: self::MergeSimple($template, $values, $ldelim, $rdelim);
+	}
+
+	/**
+	 * Used internally by Merge, or may be called directly.
+	 * If a placeholder is in the template but there is no matching value,
+	 * it will be left alone and appear in the template, for example: {{PLACEHOLDER}}.
+	 * 
+	 * @param string $template string with placeholder variables
+	 * @param mixed (array or object) $values an associative array or object with key/value pairs
+	 * @param string the left (opening) delimiter for placeholders. default = {{
+	 * @param string the right (closing) delimiter for placeholders. default = }}
+	 * @return string merged template
+	 */
+	static function MergeSimple($template, $values, $ldelim = "{{", $rdelim = "}}")
+	{
+		$replacements = array();
+		
+		foreach ($values as $key => $val)
+		{
+			$replacements[$ldelim.$key.$rdelim] = $val;
+		}
+
+		return strtr($template, $replacements);
+	}
+	
+	/**
+	 * Used internally by Merge, or may be called directly.
+	 * If a placeholder is in the template but there is no matching value,
+	 * it will be replaced with empty string and will NOT appear in the output.
+	 * 
+	 * @param string $template string with placeholder variables
+	 * @param mixed (array or object) $values an associative array or object with key/value pairs
+	 * @param string the left (opening) delimiter for placeholders. default = {{
+	 * @param string the right (closing) delimiter for placeholders. default = }}
+	 * @return string merged template
+	 */
+	static function MergeRegEx($template, $values, $ldelim = "{{", $rdelim = "}}")
+	{
+		self::$_MERGE_TEMPLATE_VALUES = $values;
+		
+		if ($ldelim != "{{" || $rdelim != "}}") throw new Exception("Custom delimiters are not yet implemented. Sorry!");
+		
+		$results = preg_replace_callback('!\{\{(\w+)\}\}!', 'SimpleTemplate::_MergeRegExCallback', $template);
+		
+		self::$_MERGE_TEMPLATE_VALUES = null;
+		
+		return $results;
+		
+	}
+	
+	/**
+	 * called internally by preg_replace_callback
+	 * @param array $matches
+	 */
+	static function _MergeRegExCallback($matches)
+	{
+		if (isset(self::$_MERGE_TEMPLATE_VALUES[$matches[1]]))
+		{
+			return self::$_MERGE_TEMPLATE_VALUES[$matches[1]];
+		}
+		else
+		{
+			return "";
+		}
+	}
+	
+}
+
+?>

+ 527 - 0
phreeze/libs/verysimple/String/VerySimpleStringUtil.php

@@ -0,0 +1,527 @@
+<?php
+/** @package	verysimple::String */
+
+/**
+ * A set of utility functions for working with strings
+ *
+ * @package	verysimple::String
+ * @author Jason Hinkle
+ * @copyright  1997-2008 VerySimple, Inc.
+ * @license	http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version 1.0
+ */
+class VerySimpleStringUtil
+{
+	/** @var the character set used when converting non ascii characters */
+	static $DEFAULT_CHARACTER_SET = 'UTF-8';
+
+	/** @var list of fancy/smart quote characters plus emdash w/ generic replacements */
+	static $SMART_QUOTE_CHARS;
+
+	/** @var list of xml reserved characters */
+	static $XML_SPECIAL_CHARS;
+
+	/** @var associative array containing the html translation for special characters with their numeric equivilant */
+	static $HTML_ENTITIES_TABLE;
+
+	/** @var common characters, especially on windows systems, that are technical not valid */
+	static $INVALID_CODE_CHARS;
+
+	/** @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;
+	}
+
+	/**
+	 * VerySimpleStringUtil::InitStaticVars(); is called at the bottom of this file
+	 */
+	static function InitStaticVars()
+	{
+
+		self::$HTML_ENTITIES_TABLE = array();
+		foreach (get_html_translation_table(HTML_ENTITIES, ENT_QUOTES) as $char => $entity)
+		{
+			self::$HTML_ENTITIES_TABLE[$entity] = '&#' . ord($char) . ';';
+		}
+
+		self::$SMART_QUOTE_CHARS =
+			array(
+				"Ô" => "'",
+				"Õ" => "'",
+				"Ò" => "\"",
+				"Ó" => "\"",
+				chr(145) => "'",
+				chr(146) => "'",
+				chr(147) => "\"",
+				chr(148) => "\"",
+				chr(151) => "-"
+			);
+
+		self::$CONTROL_CODE_CHARS =
+			array(
+				chr(0) => "&#0;",
+				chr(1) => "&#1;",
+				chr(2) => "&#2;",
+				chr(3) => "&#3;",
+				chr(4) => "&#4;",
+				chr(5) => "&#5;",
+				chr(6) => "&#6;",
+				chr(7) => "&#7;",
+				chr(8) => "&#8;",
+				chr(14) => "&#14;",
+				chr(15) => "&#15;",
+				chr(16) => "&#16;",
+				chr(17) => "&#17;",
+				chr(18) => "&#18;",
+				chr(19) => "&#19;",
+				chr(20) => "&#20;",
+				chr(21) => "&#21;",
+				chr(22) => "&#22;",
+				chr(23) => "&#23;",
+				chr(24) => "&#24;",
+				chr(25) => "&#25;",
+				chr(26) => "&#26;",
+				chr(27) => "&#27;",
+				chr(28) => "&#28;",
+				chr(29) => "&#29;",
+				chr(30) => "&#30;",
+				chr(31) => "&#31;"
+			);
+
+		self::$INVALID_CODE_CHARS = array(
+			chr(128) => '&#8364;',
+			chr(130) => '&#8218;',
+			chr(131) => '&#402;',
+			chr(132) => '&#8222;',
+			chr(133) => '&#8230;',
+			chr(134) => '&#8224;',
+			chr(135) => '&#8225;',
+			chr(136) => '&#710;',
+			chr(137) => '&#8240;',
+			chr(138) => '&#352;',
+			chr(139) => '&#8249;',
+			chr(140) => '&#338;',
+			chr(142) => '&#381;',
+			chr(145) => '&#8216;',
+			chr(146) => '&#8217;',
+			chr(147) => '&#8220;',
+			chr(148) => '&#8221;',
+			chr(149) => '&#8226;',
+			chr(150) => '&#8211;',
+			chr(151) => '&#8212;',
+			chr(152) => '&#732;',
+			chr(153) => '&#8482;',
+			chr(154) => '&#353;',
+			chr(155) => '&#8250;',
+			chr(156) => '&#339;',
+			chr(158) => '&#382;',
+			chr(159) => '&#376;');
+
+		self::$XML_SPECIAL_CHARS = array(
+			"&"   =>"&amp;",
+			"<"   =>"&lt;"
+			,">"  =>"&gt;"
+			,"\"" =>"&quot;"
+			,"'"  =>"&apos;"
+		);
+
+	}
+
+	/**
+	 * Takes the given text and converts any email address into mailto links,
+	 * returning HTML content.
+	 *
+	 * @param string $text
+	 * @param bool true to sanitize the text before parsing for display security
+	 * @return string HTML
+	 */
+	static function ConvertEmailToMailTo($text,$sanitize = false)
+	{
+		if ($sanitize) $text = VerySimpleStringUtil::Sanitize($text);
+		$regex = "/([a-z0-9_\-\.]+)". "@" . "([a-z0-9-]{1,64})" . "\." . "([a-z]{2,10})/i";
+		return preg_replace($regex, '<a href="mailto:\\1@\\2.\\3">\\1@\\2.\\3</a>', $text);
+	}
+
+	/**
+	 * Takes the given text and converts any URLs into links,
+	 * returning HTML content.
+	 *
+	 * @param string $text
+	 * @param bool true to sanitize the text before parsing for display security
+	 * @return string HTML
+	 */
+	static function ConvertUrlToLink($text,$sanitize = false)
+	{
+		if ($sanitize) $text = VerySimpleStringUtil::Sanitize($text);
+		$regex = "/[[:alpha:]]+://[^<>[:space:]]+[[:alnum:]/]/i";
+		return preg_replace($regex, '<a href=\"\\0\">\\0</a>', $text);
+	}
+
+	/**
+	 * Sanitize any text so that it can be safely displayed as HTML without
+	 * allowing XSS or other injection attacks
+	 * @param string $text
+	 * @return string
+	 */
+	static function Sanitize($text)
+	{
+		return htmlspecialchars($text);
+	}
+
+	/**
+	 * @param string $string
+	 * @param bool $numericEncodingOnly set to true to only use numeric html encoding.  warning, setting to false may be slower performance (default true)
+	 * @param bool $encodeControlCharacters (only relevant if $numericEncodingOnly = false) false = wipe control chars.  true = encode control characters (default false)
+	 * @return string
+	 */
+	static function EncodeToHTML($string, $numericEncodingOnly = true, $encodeControlCharacters = false)
+	{
+		if (strlen($string) == 0) return "";
+
+		$result = $numericEncodingOnly
+			? self::UTF8ToHtml($string)
+			: self::UTFToNamedHTML($string, $encodeControlCharacters);
+
+		return $result;
+	}
+
+	/**
+	 * Decode string that has been encoded using EncodeToHTML
+	 * used in combination with utf8_decode can be helpful
+	 * @TODO: warning, this function is BETA!
+	 *
+	 * @param string $string
+	 * @param destination character set (default = $DEFAULT_CHARACTER_SET (UTF-8))
+	 */
+	static function DecodeFromHTML($string, $charset = null)
+	{
+		// this only gets named characters
+		// return html_entity_decode($string);
+
+		// this is a complex method that appears to be the reverse of UTF8ToHTML
+		// taken from http://www.php.net/manual/en/function.html-entity-decode.php#68491
+//		$string = self::ReplaceNonNumericEntities($string);
+//		$string = preg_replace_callback('~&(#(x?))?([^;]+);~', 'self::html_entity_replace', $string);
+//        return $string;
+
+		// this way at least somebody could specify a character set.  UTF-8 will work most of the time
+		if ($charset == null) $charset = VerySimpleStringUtil::$DEFAULT_CHARACTER_SET;
+		return mb_convert_encoding($string, $charset, 'HTML-ENTITIES');
+	}
+
+	/**
+	 * This HTML encodes special characters and returns an ascii safe version.
+	 * This function extends EncodeToHTML to additionally strip
+	 * out characters that may be disruptive when used in HTML or XML data
+	 *
+	 * @param string value to parse
+	 * @param bool $escapeQuotes true to additionally escape ENT_QUOTE characters <>&"' (default = true)
+	 * @param bool $numericEncodingOnly set to true to only use numeric html encoding.  warning, setting to false may be slower performance (default true)
+	 * @param bool $replaceSmartQuotes true to replace "smart quotes" with standard ascii ones, can be useful for stripping out windows-only codes (default = false)
+	 * @return string
+	 */
+	static function EncodeSpecialCharacters($string, $escapeQuotes = true, $numericEncodingOnly = true, $replaceSmartQuotes = false)
+	{
+		if (strlen($string) == 0) return "";
+
+		$result = $string;
+
+		// do this first before encoding
+		if ($replaceSmartQuotes) $result = self::ReplaceSmartQuotes($result);
+
+		// this method does not double-encode, but replaces single-quote with a numeric entity
+		if ($escapeQuotes) $result = htmlspecialchars($result, ENT_QUOTES, null, false);
+
+		// this method double-encodes values but uses the special character entity for single quotes
+		// if ($escapeQuotes) $result = self::ReplaceXMLSpecialChars($result);
+
+		// for special chars we don't need to insist on numeric encoding only
+		return self::EncodeToHTML($result,$numericEncodingOnly);
+
+	}
+
+	/**
+	 * Converts a string into a character array
+	 * @param string $string
+	 * @return array
+	 */
+	static function GetCharArray($string)
+	{
+		return preg_split("//", $string, -1, PREG_SPLIT_NO_EMPTY);
+	}
+
+	/**
+	 * This replaces XML special characters with HTML encoding
+	 * @param string $string
+	 * @return string
+	 */
+	static function ReplaceXMLSpecialChars($string)
+	{
+		return strtr($string,self::$XML_SPECIAL_CHARS);
+	}
+
+	/**
+	 * This replaces smart (fancy) quote characters with generic ascii versions
+	 * @param string $string
+	 * @return string
+	 */
+	static function ReplaceSmartQuotes($string)
+	{
+		return strtr($string,self::$SMART_QUOTE_CHARS);
+	}
+
+	/**
+	 * This replaces control characters characters with generic ascii versions
+	 * @param string $string
+	 * @return string
+	 */
+	static function ReplaceControlCodeChars($string)
+	{
+		return strtr($string,self::$CONTROL_CODE_CHARS);
+	}
+
+	/**
+	 * This replaces all non-numeric html entities with the numeric equivilant
+	 * @param string $string
+	 * @return string
+	 */
+	static function ReplaceNonNumericEntities($string)
+	{
+		return strtr($string,self::$HTML_ENTITIES_TABLE);
+	}
+
+	/**
+	 * This replaces illegal ascii code values $INVALID_CODE_CHARS
+	 * @param string $string
+	 * @return string
+	 */
+	static function ReplaceInvalidCodeChars($string)
+	{
+		return strtr($string,self::$INVALID_CODE_CHARS);
+	}
+
+	/**
+	 * This is The same as UTFToHTML except it utilizes htmlentities, which will return the Named
+	 * HTML code when possible (ie &pound; &sect;, etc).  It is preferrable in all cases to use
+	 * UTFToHTML instead unless you absolutely have to have named entities
+	 *
+	 * @param string $string
+	 * @param bool $encodeControlCharacters false = wipe control chars.  true = encode control characters (default false)
+	 * @return string
+	 */
+	static function UTFToNamedHTML($string, $encodeControlCharacters = false)
+	{
+
+		$utf8 = $string;
+		$result = '';
+		for ($i = 0; $i < strlen($utf8); $i++) {
+			$char = $utf8[$i];
+			$ascii = ord($char);
+			if ($ascii < 128) {
+				// one-byte character
+				$result .= $char;
+			} else if ($ascii < 192) {
+				// non-utf8 character or not a start byte
+				$result .= ($encodeControlCharacters) ? htmlentities($char) : '';
+			} else if ($ascii < 224) {
+				// two-byte character
+				$encoded = htmlentities(substr($utf8, $i, 2), ENT_QUOTES, 'UTF-8');
+
+				// @hack if htmlentities didn't encode it, then we need to do a charset conversion
+			   if ($encoded != '' && substr($encoded,0,1) != '&') $encoded = mb_convert_encoding($encoded, 'HTML-ENTITIES', self::$DEFAULT_CHARACTER_SET);
+
+				$result .= $encoded;
+				$i++;
+			} else if ($ascii < 240) {
+				// three-byte character
+				$ascii1 = ord($utf8[$i+1]);
+				$ascii2 = ord($utf8[$i+2]);
+				$unicode = (15 & $ascii) * 4096 +
+						   (63 & $ascii1) * 64 +
+						   (63 & $ascii2);
+				$result .= "&#$unicode;" ;
+				$i += 2;
+			} else if ($ascii < 248) { // (TODO: should this be 245 or 248 ??)
+				// four-byte character
+				$ascii1 = ord($utf8[$i+1]);
+				$ascii2 = ord($utf8[$i+2]);
+				$ascii3 = ord($utf8[$i+3]);
+				$unicode = (15 & $ascii) * 262144 +
+						   (63 & $ascii1) * 4096 +
+						   (63 & $ascii2) * 64 +
+						   (63 & $ascii3);
+				$result .= "&#$unicode;";
+				$i += 3;
+			}
+		}
+
+		return $result;
+	}
+
+
+	/**
+	 * Converts UTF-8 character set into html encoded goodness
+	 *
+	 * @author montana
+	 * @link http://www.php.net/manual/en/function.htmlentities.php#92105
+	 * @param string $content
+	 */
+	static function UTF8ToHTML($content="")
+	{
+		$contents = self::unicode_string_to_array($content);
+		$swap = "";
+		$iCount = count($contents);
+		for ($o=0;$o<$iCount;$o++) {
+			$contents[$o] = self::unicode_entity_replace($contents[$o]);
+			$swap .= $contents[$o];
+		}
+		return mb_convert_encoding($swap,"UTF-8"); //not really necessary, but why not.
+	}
+
+	/**
+	 * takes a unicode string and turns it into an array
+	 * of UTF-8 characters
+	 *
+	 * @author adjwilli
+	 * @param string $string
+	 * @return array
+	 */
+	static function unicode_string_to_array( $string )
+	{
+		$array = array();
+		$strlen = mb_strlen($string);
+		while ($strlen) {
+			$array[] = mb_substr( $string, 0, 1, "UTF-8" );
+			$string = mb_substr( $string, 1, $strlen, "UTF-8" );
+			$strlen = mb_strlen( $string );
+		}
+		return $array;
+	}
+
+	/**
+	 * Uses scary binary math to replace a character with
+	 * it's html entity
+	 *
+	 * @author m. perez
+	 * @param char $c
+	 * @return string
+	 */
+	static function unicode_entity_replace($c)
+	{
+		$h = ord($c{0});
+		if ($h <= 0x7F) { // 127
+			return $c;
+		} else if ($h < 0xC2) { // 194
+			return $c;
+		}
+
+		if ($h <= 0xDF) { // 0xDF = 223
+			$h = ($h & 0x1F) << 6 | (ord($c{1}) & 0x3F);  // 0x0F = 15, 0x1F = 31, 0x3F = 63
+			$h = "&#" . $h . ";";
+			return $h;
+		} else if ($h <= 0xEF) { // 0xEF = 239
+			$h = ($h & 0x0F) << 12 | (ord($c{1}) & 0x3F) << 6 | (ord($c{2}) & 0x3F);
+			$h = "&#" . $h . ";";
+			return $h;
+		} else if ($h <= 0xF4) { // 0xF4 = 244 (TODO: should this be 244 or 247 ??)
+			$h = ($h & 0x0F) << 18 | (ord($c{1}) & 0x3F) << 12 | (ord($c{2}) & 0x3F) << 6 | (ord($c{3}) & 0x3F);
+			$h = "&#" . $h . ";";
+			return $h;
+		}
+	}
+
+	/**
+	 * Used for decoding entities that started as UTF-8
+	 * converts a character that is likely non ascii into the correct UTF-8 char value
+	 * @link http://www.php.net/manual/en/function.html-entity-decode.php#68491
+	 * @param $code
+	 */
+	function chr_utf8($code)
+    {
+        if ($code < 0) return false;
+        elseif ($code < 128) return chr($code);
+        elseif ($code < 160) // Remove Windows Illegals Cars
+        {
+            if ($code==128) $code=8364;
+            elseif ($code==129) $code=160; // not affected
+            elseif ($code==130) $code=8218;
+            elseif ($code==131) $code=402;
+            elseif ($code==132) $code=8222;
+            elseif ($code==133) $code=8230;
+            elseif ($code==134) $code=8224;
+            elseif ($code==135) $code=8225;
+            elseif ($code==136) $code=710;
+            elseif ($code==137) $code=8240;
+            elseif ($code==138) $code=352;
+            elseif ($code==139) $code=8249;
+            elseif ($code==140) $code=338;
+            elseif ($code==141) $code=160; // not affected
+            elseif ($code==142) $code=381;
+            elseif ($code==143) $code=160; // not affected
+            elseif ($code==144) $code=160; // not affected
+            elseif ($code==145) $code=8216;
+            elseif ($code==146) $code=8217;
+            elseif ($code==147) $code=8220;
+            elseif ($code==148) $code=8221;
+            elseif ($code==149) $code=8226;
+            elseif ($code==150) $code=8211;
+            elseif ($code==151) $code=8212;
+            elseif ($code==152) $code=732;
+            elseif ($code==153) $code=8482;
+            elseif ($code==154) $code=353;
+            elseif ($code==155) $code=8250;
+            elseif ($code==156) $code=339;
+            elseif ($code==157) $code=160; // not affected
+            elseif ($code==158) $code=382;
+            elseif ($code==159) $code=376;
+        }
+        if ($code < 2048) return chr(192 | ($code >> 6)) . chr(128 | ($code & 63));
+        elseif ($code < 65536) return chr(224 | ($code >> 12)) . chr(128 | (($code >> 6) & 63)) . chr(128 | ($code & 63));
+        else return chr(240 | ($code >> 18)) . chr(128 | (($code >> 12) & 63)) . chr(128 | (($code >> 6) & 63)) . chr(128 | ($code & 63));
+    }
+
+    /**
+     * Callback for preg_replace_callback('~&(#(x?))?([^;]+);~', 'html_entity_replace', $str);
+     * used internally by decode
+     * @link http://www.php.net/manual/en/function.html-entity-decode.php#68491
+     * @param array
+     */
+    function html_entity_replace($matches)
+    {
+        if ($matches[2])
+        {
+            return self::chr_utf8(hexdec($matches[3]));
+        }
+        elseif ($matches[1])
+        {
+            return self::chr_utf8($matches[3]);
+        }
+        elseif ($matches[3])
+        {
+        	// return "((&" . $matches[3] . ";))";
+        	// return mb_convert_encoding('&'.$matches[3].';', 'UTF-8', 'HTML-ENTITIES');
+        	return html_entity_decode('&'.$matches[3].';');
+        }
+
+        return false;
+    }
+}
+
+// this will be executed only once
+VerySimpleStringUtil::InitStaticVars();
+
+
+?>

+ 78 - 0
phreeze/libs/verysimple/Util/ExceptionFormatter.php

@@ -0,0 +1,78 @@
+<?php
+/** @package    verysimple::Util */
+
+/**
+ * Formatter for formatting Exceptions and stack traces
+ *
+ * @package    verysimple::String
+ * @author Jason Hinkle
+ * @copyright  1997-2008 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version 1.0
+ */
+class ExceptionFormatter
+{
+	
+	/**
+	 * This is a utility function for tracing errors.  It will return a string that
+	 * displys the current execution stack
+	 * 
+	 * @param string $msg a debugging message to include
+	 * @param int $depth how far to go back in the stack (default = unlimited)
+	 * @param string $join the delimiter between lines
+	 * @param bool $show_lines true to include line numbers
+	 */
+	static function GetTraceAsString($msg = "DEBUG", $depth = 0, $join = " :: ", $show_lines = true)
+	{
+		$error = new Exception($msg);
+		return self::FormatTrace($error->getTrace(), $depth, $join, $show_lines);
+	}
+	
+	/**
+	 * Formats the debug_backtrace array into a printable string.  
+	 * You can create a debug traceback using $exception->getTrace() 
+	 * or using the php debug_backtrace() function
+	 *
+	 * @access public
+	 * @param array debug_backtrace.  For example: debug_backtrace() -or- $exception->getTrace()
+	 * @param int $depth how far to go back in the stack (default = unlimited)
+	 * @param string $join the delimiter between lines
+	 * @param bool $show_lines true to include line numbers
+	 */
+	static function FormatTrace($tb, $depth = 0, $join = " :: ", $show_lines = true)
+	{
+		$msg = "";
+		$delim = "";
+		
+		$calling_function = "";
+		$calling_line = "[?]";
+		$levels = count($tb);
+		
+		if ($depth == 0) $depth = $levels;
+		
+		for ($x = $levels; $x > 0; $x--)
+		{
+			$stack = $tb[$x-1];
+			$s_file = isset($stack['file']) ? basename($stack['file']) : "[?]";
+			$s_line = isset($stack['line']) ? $stack['line'] : "[?]";
+			$s_function = isset($stack['function']) ? $stack['function'] : "";
+			$s_class = isset($stack['class']) ? $stack['class'] : "";
+			$s_type = isset($stack['type']) ? $stack['type'] : "";
+			
+			if ($depth >= $x)
+			{ 
+				$msg .= $delim . "$calling_function" . ($show_lines ? " ($s_file Line $s_line)" : "");
+				$delim = $join;
+			}
+
+			$calling_function = $s_class . $s_type . $s_function;
+
+		}
+		
+		return $msg;
+		
+	}
+	
+}
+
+?>

+ 82 - 0
phreeze/libs/verysimple/Util/ExceptionThrower.php

@@ -0,0 +1,82 @@
+<?php
+/** @package    verysimple::Util */
+
+/**
+ * Utility for catching PHP errors and converting them to an exception
+ * that can be caught at runtime
+ * 
+ * @example <pre>
+ * try
+ * {
+ *   ExceptionThrower::Start();
+ *   // @TODO PHP command here
+ *   ExceptionThrower::Stop();
+ * }
+ * catch (Exception $ex)
+ * {
+ *   ExceptionThrower::Stop();
+ *   // handle or re-throw exception
+ * }</pre>
+ * @package    verysimple::Util
+ * @author Jason Hinkle
+ * @copyright  1997-2008 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version 1.0
+ */
+class ExceptionThrower
+{
+	
+	static $IGNORE_DEPRECATED = true;
+	
+	/**
+	 * Start redirecting PHP errors
+	 * @param int $level PHP Error level to catch (Default = E_ALL & ~E_DEPRECATED)
+	 */
+	static function Start($level = null)
+	{
+		
+		if ($level == null) 
+		{
+			if (defined("E_DEPRECATED"))
+			{
+				$level = E_ALL & ~E_DEPRECATED ;
+			}
+			else
+			{
+				// php 5.2 and earlier don't support E_DEPRECATED
+				$level = E_ALL;
+				self::$IGNORE_DEPRECATED = true;
+			}
+		}
+		set_error_handler(array("ExceptionThrower", "HandleError"), $level);
+	}
+	
+	/**
+	 * Stop redirecting PHP errors
+	 */
+	static function Stop()
+	{
+		restore_error_handler();
+	}
+	
+	/**
+	 * Fired by the PHP error handler function.  Calling this function will
+	 * always throw an exception unless error_reporting == 0.  If the
+	 * PHP command is called with @ preceeding it, then it will be ignored
+	 * here as well.
+	 * 
+	 * @param string $code
+	 * @param string $string
+	 * @param string $file
+	 * @param string $line
+	 * @param string $context
+	 */
+	static function HandleError($code, $string, $file, $line, $context)
+	{
+		// ignore supressed errors
+		if (error_reporting() == 0) return;
+		if (self::$IGNORE_DEPRECATED && strpos($string,"deprecated") === true) return true;
+		
+		throw new Exception($string . " in ".basename($file)." at line $line",$code);
+	}
+}

+ 100 - 0
phreeze/libs/verysimple/Util/MemCacheProxy.php

@@ -0,0 +1,100 @@
+<?php
+/** @package    verysimple::Util */
+
+/**
+* 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
+* @author     VerySimple Inc.
+* @copyright  1997-2007 VerySimple, Inc.
+* @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+* @version    1.0
+*/
+class MemCacheProxy
+{
+	private $_memcache;
+	public $ServerOffline = false;
+	public $LastServerError = '';
+
+	/**
+	 * 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')
+	 */
+	public function __construct($server_array = array('localhost'=>'11211'))
+	{
+		if (class_exists('Memcache'))
+		{
+			$this->_memcache = new Memcache();
+			foreach (array_keys($server_array) as $host)
+			{
+				// print "adding server $host " . $server_array[$host];
+				$this->_memcache->addServer($host, $server_array[$host]);
+			}
+		}
+		else
+		{
+			$this->LastServerError = 'Memcache client module not installed';
+			$this->ServerOffline = true;
+		}
+	}
+
+
+	/**
+	 * This is method get
+	 * @param string $key The key of the item to retrieve
+	 * @return mixed cache value or null
+	 */
+	public function get($key)
+	{
+		// 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;
+	}
+
+
+	/**
+	 * 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
+	 *
+	 */
+	public function set($key, $var, $flags = false, $expire = 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;
+		}
+	}
+
+
+}
+
+?>

+ 73 - 0
phreeze/libs/verysimple/Util/TextImageWriter.php

@@ -0,0 +1,73 @@
+<?php
+/** @package    verysimple::Util */
+
+/**
+ * Utility to stream an image containing text to the browser
+ *
+ * @package    verysimple::Util
+ * @author Jason Hinkle
+ * @copyright  1997-2011 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version 1.0
+ */
+class TextImageWriter
+{
+	
+	/**
+	 * 
+	 * @param $message
+	 */
+	
+	
+	/**
+	 * Output a png image to the browser, including headers
+	 * @param string $message
+	 * @param int $width
+	 * @param int $height
+	 * @param array $backgroundColor RGB values from 0-255.  example: array(0,0,0);
+	 * @param array $fontColor RGB values from 0-255.  example: array(0,0,0);
+	 * @param int $fontId (number between 1 and 5);
+	 */
+	static function Write($message, $width = 250, $height = 150, $backgroundColor = null, $fontColor = null, $fontId = 1)
+	{
+		if ($backgroundColor == null) $backgroundColor = array(255,255,255);
+		if ($backgroundColor == null) $backgroundColor = array(0,0,0);
+		
+		$im = self::GetErrorImage($message, $width, $height, $backgroundColor, $fontColor, $fontId);
+		header('Content-type: image/png');
+		imagepng($im);
+		imagedestroy($im);
+	}
+	
+	/**
+	 * Given text, returns an image reference with the text included in the image
+	 * @param string $message
+	 * @param int $width
+	 * @param int $height
+	 * @param array $backgroundColor RGB values from 0-255.  example: array(0,0,0);
+	 * @param array $fontColor RGB values from 0-255.  example: array(0,0,0);
+	 * @param int $fontId (number between 1 and 5);
+	 * @return int image reference
+	 */
+	static function GetErrorImage($message, $width = 250, $height = 150, $backgroundColor = null, $fontColor = null, $fontId = 1)
+	{
+		
+		if ($backgroundColor == null) $backgroundColor = array(255,255,255);
+		if ($backgroundColor == null) $backgroundColor = array(0,0,0);
+
+		$msg = str_replace("\n","",$message);
+		$im = imagecreate($width, $height);
+		$bgColor = imagecolorallocate($im, $backgroundColor[0],$backgroundColor[1],$backgroundColor[2]);
+		$fontColor = imagecolorallocate($im, $fontColor[0],$fontColor[1],$fontColor[2]);
+		$lines = explode( "\r", wordwrap($msg,($width/5),"\r"));
+		$count = 0;
+		foreach ($lines as $line)
+		{
+			imagestring($im, $fontId, 2, 2 + ($count * 12) , $line, $fontColor);
+			$count++;
+		}
+		return $im;
+	}
+}
+
+?>

+ 19 - 0
phreeze/libs/verysimple/Util/UrlWriterMode.php

@@ -0,0 +1,19 @@
+<?php
+/** @package    verysimple::Util */
+
+/**
+ * String Util for determining what mode the URL Writer needs to format for
+ *
+ * @package    verysimple::Util
+ * @author 	   Christian Dawson
+ * @copyright  1997-2010 VerySimple, Inc.
+ * @license    http://www.gnu.org/licenses/lgpl.html  LGPL
+ * @version    1.0
+ */
+class UrlWriterMode
+{
+	const WEB = "WEB";
+	const JOOMLA = "JOOMLA";
+	const MOBILE = "MOBILE";
+}
+?>

+ 154 - 0
phreeze/libs/verysimple/Util/VsDateUtil.php

@@ -0,0 +1,154 @@
+<?php
+/** @package    verysimple::Util */
+
+/**
+ * Static utility class for working with Dates
+ *
+ * @package		verysimple::Util
+ * @author		Jason Hinkle
+ * @copyright	1997-2011 VerySimple, Inc.
+ * @license		LGPL http://www.gnu.org/licenses/lgpl.html
+ * @version		1.0
+ */
+class VsDateUtil
+{
+	/** @var int one day in milliseconds */
+	static $ONE_DAY = 86400000;
+	
+	/** @var int one hour in milliseconds */
+	static $ONE_HOUR = 3600000;
+	
+	/** @var int one minute in milliseconds */
+	static $ONE_MINUTE = 6000;
+	
+	/** @var int one second in milliseconds */
+	static $ONE_SECOND = 1000;
+	
+	/**
+	 * Return current date as string in the specified format
+	 * @param string $format
+	 */
+	static function Today($format = "Y-m-d")
+	{
+		return self::Now($format);
+	}
+	
+	/**
+	 * Returns the timestamp for the beginning of DST for specified year
+	 * @param int the 4-digit year (if not provide then the current year is used)
+	 */
+	static function DstStartNorthAmerica($year = null)
+	{
+		if (!$year) $year = date('Y');
+		return strtotime('03/01/' . $year . ' second sunday');
+	}
+	
+	/**
+	 * Returns the timestamp for the end of DST for the specified year
+	 * @param int the 4-digit year (if not provide then the current year is used)
+	 */
+	static function DstEndNorthAmerica($year = null)
+	{
+		if (!$year) $year = date('Y');
+		return strtotime('11/01/' . $year . ' first sunday');
+	}
+	
+	/**
+	 * Return true if the date is within the DST observation range for North America
+	 * @param int $timestamp (if not provided then the current server time will be used)
+	 */
+	static function IsDstNorthAmerica($timestamp = null)
+	{
+		if (!$timestamp) $timestamp = time();
+		return $timestamp > self::DstStartNorthAmerica() && $timestamp < self::DstEndNorthAmerica();
+	}
+	
+	/**
+	 * Return current date/time as string in the specified format
+	 * @param string $format
+	 */
+	static function Now($format = "Y-m-d H:i:s")
+	{
+		return date($format);
+	}
+	
+	/**
+	 * Return yesterday's date as string in the specified format
+	 * @param string $format
+	 */
+	static function Yesterday($format = "Y-m-d")
+	{
+		return self::DaysAgo(1,$format);
+	}
+
+	/**
+	 * Return tomorrow's date as string in the specified format
+	 * @param string $format
+	 */
+	static function Tomorrow($format = "Y-m-d")
+	{
+		return self::DaysFromNow(1,$format);
+	}
+	
+	/**
+	 * Return the date/time 24 hours ago as string in the specified format
+	 * @param string $format
+	 */
+	static function TwentyFourHoursAgo($format = "Y-m-d H:i:s")
+	{
+		return self::HoursAgo(24,$format);
+	}
+
+	/**
+	 * Return the date/time 24 hours from now as string in the specified format
+	 * @param string $format
+	 */
+	static function TwentyFourHoursFromNow($format = "Y-m-d H:i:s")
+	{
+		return self::HoursFromNow(24,$format);
+	}
+	
+	/**
+	 * Return date as a string the specified number of days ago
+	 * @param int $days
+	 * @param string $format
+	 */
+	static function DaysAgo($days, $format = "Y-m-d")
+	{
+		return date($format,strtotime(self::Now() . " - $days days"));
+	}
+	
+	/**
+	 * Return date as a string the specified number of days from now
+	 * @param int $days
+	 * @param string $format
+	 */
+	static function DaysFromNow($days, $format = "Y-m-d")
+	{
+		return date($format,strtotime(self::Now() . " + $days days"));
+	}
+
+	/**
+	 * Return date/time as a string the specified number of hours ago
+	 * @param int $hours
+	 * @param string $format
+	 */
+	static function HoursAgo($hours, $format = "Y-m-d H:i:s")
+	{
+		return date($format,strtotime(self::Now() . " - $hours hours"));
+	}
+	
+	/**
+	 * Return date/time as a string the specified number of hours from now
+	 * @param int $hours
+	 * @param string $format
+	 */
+	static function HoursFromNow($hours, $format = "Y-m-d H:i:s")
+	{
+		return date($format,strtotime(self::Now() . " - $hours hours"));
+	}
+	
+	
+}
+
+?>

+ 33 - 0
phreeze/setup.py

@@ -0,0 +1,33 @@
+import subprocess
+import sys
+import setup_util
+import os
+from os.path import expanduser
+
+home = expanduser("~")
+
+def start(args):
+  setup_util.replace_text("phreeze/index.php", "localhost:3306", "" + args.database_host + ":3306")
+  setup_util.replace_text("phreeze/deploy/phreeze", "\".*\/FrameworkBenchmarks", "\"" + home + "/FrameworkBenchmarks")
+  setup_util.replace_text("phreeze/deploy/phreeze", "Directory .*\/FrameworkBenchmarks", "Directory " + home + "/FrameworkBenchmarks")
+  setup_util.replace_text("phreeze/deploy/nginx.conf", "root .*\/FrameworkBenchmarks", "root " + home + "/FrameworkBenchmarks")
+  
+  try:
+    #subprocess.check_call("sudo cp php/deploy/phreeze /etc/apache2/sites-available/", shell=True)
+    #subprocess.check_call("sudo a2ensite php", shell=True)
+    #subprocess.check_call("sudo chown -R www-data:www-data php", shell=True)
+    #subprocess.check_call("sudo /etc/init.d/apache2 start", shell=True)
+    subprocess.check_call("sudo php-fpm --fpm-config config/php-fpm.conf -g " + home + "/FrameworkBenchmarks/phreeze/deploy/php-fpm.pid", shell=True)
+    subprocess.check_call("sudo /usr/local/nginx/sbin/nginx -c " + home + "/FrameworkBenchmarks/phreeze/deploy/nginx.conf", shell=True)
+    
+    return 0
+  except subprocess.CalledProcessError:
+    return 1
+def stop():
+  try:
+    subprocess.call("sudo /usr/local/nginx/sbin/nginx -s stop", shell=True)
+    subprocess.call("sudo kill -QUIT $( cat phreeze/deploy/php-fpm.pid )", shell=True)
+    
+    return 0
+  except subprocess.CalledProcessError:
+    return 1