diff --git a/webtools/uninstall_survey/.htaccess b/webtools/uninstall_survey/.htaccess
new file mode 100644
index 00000000000..e129ef0e173
--- /dev/null
+++ b/webtools/uninstall_survey/.htaccess
@@ -0,0 +1,5 @@
+
+ RewriteEngine on
+ RewriteRule ^$ webroot/ [L]
+ RewriteRule (.*) webroot/$1 [L]
+
diff --git a/webtools/uninstall_survey/app_controller.php b/webtools/uninstall_survey/app_controller.php
new file mode 100644
index 00000000000..abe07867cde
--- /dev/null
+++ b/webtools/uninstall_survey/app_controller.php
@@ -0,0 +1,69 @@
+
+ * Copyright (c) 2006, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.cake
+ * @since CakePHP v 0.2.9
+ * @version $Revision: 1.1 $
+ * @modifiedby $LastChangedBy: phpnut $
+ * @lastmodified $Date: 2006-05-24 19:14:24 $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+
+/**
+ * Short description for class.
+ *
+ * Add your application-wide methods in the class below, your controllers
+ * will inherit them.
+ *
+ * @package cake
+ * @subpackage cake.cake
+ */
+
+uses('Sanitize');
+
+class AppController extends Controller {
+
+ /**
+ * This function is intended to be used with url parameters when passing them to
+ * a view. (This is useful when echoing values out in tags, etc.
+ * Note that the keys to the arrays are escaped as well.
+ *
+ * @param array dirty parameters
+ * @return array cleaned values
+ */
+ function decodeAndSanitize($params)
+ {
+ $clean = array();
+
+ foreach ($params as $var => $val) {
+ $var = $this->Sanitize->html(urldecode($var));
+ $val = $this->Sanitize->html(urldecode($val));
+
+ $clean[$var] = $val;
+ }
+
+ return $clean;
+ }
+}
+
+?>
diff --git a/webtools/uninstall_survey/app_model.php b/webtools/uninstall_survey/app_model.php
new file mode 100644
index 00000000000..3475d279e80
--- /dev/null
+++ b/webtools/uninstall_survey/app_model.php
@@ -0,0 +1,72 @@
+
+ * Copyright (c) 2006, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.cake
+ * @since CakePHP v 0.2.9
+ * @version $Revision: 1.1 $
+ * @modifiedby $LastChangedBy: phpnut $
+ * @lastmodified $Date: 2006-05-24 19:14:24 $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+
+/**
+ * Application model for Cake.
+ *
+ * Add your application-wide methods in the class below, your models
+ * will inherit them.
+ *
+ * @package cake
+ * @subpackage cake.cake
+ */
+
+uses('sanitize');
+
+class AppModel extends Model {
+
+ /**
+ * Will clean arrays for input into SQL.
+ * Note that the array keys are getting cleaned here as well. If you're using strings
+ * (with escapable characters in them) as keys to your array, be extra careful.
+ *
+ * @access public
+ * @param array to be cleaned
+ * @return array with sql escaped
+ */
+ function cleanArrayForSql($array)
+ {
+ $sanitize = new Sanitize();
+ $clean = array();
+
+ foreach ($array as $var => $val)
+ {
+ $var = $sanitize->sql($var);
+ $val = $sanitize->sql($val);
+ $clean[$var] = $val;
+ }
+
+ return $clean;
+ }
+
+}
+
+?>
diff --git a/webtools/uninstall_survey/config/acl.ini.php b/webtools/uninstall_survey/config/acl.ini.php
new file mode 100644
index 00000000000..366a7add6f2
--- /dev/null
+++ b/webtools/uninstall_survey/config/acl.ini.php
@@ -0,0 +1,76 @@
+;
+; SVN FILE: $Id: acl.ini.php,v 1.1 2006-05-24 19:14:24 uid815 Exp $
+;/**
+; * Short description for file.
+; *
+; *
+; * PHP versions 4 and 5
+; *
+; * CakePHP : Rapid Development Framework
+; * Copyright (c) 2006, Cake Software Foundation, Inc.
+; * 1785 E. Sahara Avenue, Suite 490-204
+; * Las Vegas, Nevada 89104
+; *
+; * Licensed under The MIT License
+; * Redistributions of files must retain the above copyright notice.
+; *
+; * @filesource
+; * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+; * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+; * @package cake
+; * @subpackage cake.app.config
+; * @since CakePHP v 0.10.0.1076
+; * @version $Revision: 1.1 $
+; * @modifiedby $LastChangedBy: phpnut $
+; * @lastmodified $Date: 2006-05-24 19:14:24 $
+; * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+; */
+
+; acl.ini.php - Cake ACL Configuration
+; ---------------------------------------------------------------------
+; Use this file to specify user permissions.
+; aco = access control object (something in your application)
+; aro = access request object (something requesting access)
+;
+; User records are added as follows:
+;
+; [uid]
+; groups = group1, group2, group3
+; allow = aco1, aco2, aco3
+; deny = aco4, aco5, aco6
+;
+; Group records are added in a similar manner:
+;
+; [gid]
+; allow = aco1, aco2, aco3
+; deny = aco4, aco5, aco6
+;
+; The allow, deny, and groups sections are all optional.
+; NOTE: groups names *cannot* ever be the same as usernames!
+;
+; ACL permissions are checked in the following order:
+; 1. Check for user denies (and DENY if specified)
+; 2. Check for user allows (and ALLOW if specified)
+; 3. Gather user's groups
+; 4. Check group denies (and DENY if specified)
+; 5. Check group allows (and ALLOW if specified)
+; 6. If no aro, aco, or group information is found, DENY
+;
+; ---------------------------------------------------------------------
+
+;-------------------------------------
+;Users
+;-------------------------------------
+
+[username-goes-here]
+groups = group1, group2
+deny = aco1, aco2
+allow = aco3, aco4
+
+;-------------------------------------
+;Groups
+;-------------------------------------
+
+[groupname-goes-here]
+deny = aco5, aco6
+allow = aco7, aco8
\ No newline at end of file
diff --git a/webtools/uninstall_survey/config/bootstrap.php b/webtools/uninstall_survey/config/bootstrap.php
new file mode 100644
index 00000000000..0d9bed2ed97
--- /dev/null
+++ b/webtools/uninstall_survey/config/bootstrap.php
@@ -0,0 +1,50 @@
+
+ * Copyright (c) 2006, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.app.config
+ * @since CakePHP v 0.10.8.2117
+ * @version $Revision: 1.1 $
+ * @modifiedby $LastChangedBy: phpnut $
+ * @lastmodified $Date: 2006-05-24 19:14:24 $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+
+/**
+ *
+ * This file is loaded automatically by the app/webroot/index.php file after the core bootstrap.php is loaded
+ * This is an application wide file to load any function that is not used within a class define.
+ * You can also use this to include or require any files in your application.
+ *
+ */
+
+/**
+ * The settings below can be used to set additional paths to models, views and controllers.
+ * This is related to Ticket #470 (https://trac.cakephp.org/ticket/470)
+ *
+ * $modelPaths = array('full path to models', 'second full path to models', 'etc...');
+ * $viewPaths = array('this path to views', 'second full path to views', 'etc...');
+ * $controllerPaths = array('this path to controllers', 'second full path to controllers', 'etc...');
+ *
+ */
+
+//EOF
+?>
diff --git a/webtools/uninstall_survey/config/core.php b/webtools/uninstall_survey/config/core.php
new file mode 100644
index 00000000000..42a07481c59
--- /dev/null
+++ b/webtools/uninstall_survey/config/core.php
@@ -0,0 +1,153 @@
+
+ * Copyright (c) 2006, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.app.config
+ * @since CakePHP v 0.2.9
+ * @version $Revision: 1.1 $
+ * @modifiedby $LastChangedBy: phpnut $
+ * @lastmodified $Date: 2006-05-24 19:14:24 $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+
+/**
+ * If you do not have mod rewrite on your system
+ * or if you prefer to use CakePHP pretty urls.
+ * uncomment the line below.
+ * Note: If you do have mod rewrite but prefer the
+ * CakePHP pretty urls, you also have to remove the
+ * .htaccess files
+ * release/.htaccess
+ * release/app/.htaccess
+ * release/app/webroot/.htaccess
+ */
+//define ('BASE_URL', env('SCRIPT_NAME'));
+
+/**
+ * Set debug level here:
+ * - 0: production
+ * - 1: development
+ * - 2: full debug with sql
+ * - 3: full debug with sql and dump of the current object
+ *
+ * In production, the "flash messages" redirect after a time interval.
+ * With the other debug levels you get to click the "flash message" to continue.
+ *
+ */
+define('DEBUG', 0);
+/**
+ * Turn of caching checking wide.
+ * You must still use the controller var cacheAction inside you controller class.
+ * You can either set it controller wide, or in each controller method.
+ * use var $cacheAction = true; or in the controller method $this->cacheAction = true;
+ */
+define ('CACHE_CHECK', false);
+/**
+ * Error constant. Used for differentiating error logging and debugging.
+ * Currently PHP supports LOG_DEBUG
+ */
+define ('LOG_ERROR', 2);
+/**
+ * CakePHP includes 3 types of session saves
+ * database or file. Set this to your preferred method.
+ * If you want to use your own save handler place it in
+ * app/config/name.php DO NOT USE file or database as the name.
+ * and use just the name portion below.
+ *
+ * Setting this to cake will save files to /cakedistro/tmp directory
+ * Setting it to php will use the php default save path
+ * Setting it to database will use the database
+ *
+ *
+ */
+define('CAKE_SESSION_SAVE', 'php');
+/**
+ * Set a random string of used in session.
+ *
+ */
+define('CAKE_SESSION_STRING', 'DYhG93b0qyJfIxfs2guVoUubWwvniR2G0FgaC9mi');
+/**
+ * Set the name of session cookie
+ *
+ */
+define('CAKE_SESSION_COOKIE', 'CAKEPHP');
+
+/**
+ * Set level of Cake security.
+ *
+ */
+define('CAKE_SECURITY', 'high');
+
+/**
+ * Set Cake Session time out.
+ * If CAKE_SECURITY define is set
+ * high: multiplied by 10
+ * medium: is multiplied by 100
+ * low is: multiplied by 300
+ *
+ * Number below is seconds.
+ */
+define('CAKE_SESSION_TIMEOUT', '120');
+
+/**
+ * Uncomment the define below to use cake built in admin routes.
+ * You can set this value to anything you want.
+ * All methods related to the admin route should be prefixed with the
+ * name you set CAKE_ADMIN to.
+ * For example: admin_index, admin_edit
+ */
+//define('CAKE_ADMIN', 'admin');
+
+/**
+ * The define below is used to turn cake built webservices
+ * on or off. Default setting is off.
+ */
+define('WEBSERVICES', 'off');
+
+/**
+ * Compress output CSS (removing comments, whitespace, repeating tags etc.)
+ * This requires a/var/cache directory to be writable by the web server (caching).
+ * To use, prefix the CSS link URL with '/ccss/' instead of '/css/' or use Controller::cssTag().
+ */
+define('COMPRESS_CSS', false);
+
+/**
+ * If set to true, helpers would output data instead of returning it.
+ */
+define('AUTO_OUTPUT', false);
+
+/**
+ * If set to false, session would not automatically be started.
+ */
+define('AUTO_SESSION', true);
+
+/**
+ * Set the max size of file to use md5() .
+ */
+define('MAX_MD5SIZE', (5*1024)*1024 );
+
+/**
+ * To use Access Control Lists with Cake...
+ */
+define('ACL_CLASSNAME', 'DB_ACL');
+define('ACL_FILENAME', 'dbacl'.DS.'db_acl');
+
+?>
diff --git a/webtools/uninstall_survey/config/database.php.default b/webtools/uninstall_survey/config/database.php.default
new file mode 100644
index 00000000000..85ef0dd07a8
--- /dev/null
+++ b/webtools/uninstall_survey/config/database.php.default
@@ -0,0 +1,79 @@
+
+ * Copyright (c) 2006, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.app.config
+ * @since CakePHP v 0.2.9
+ * @version $Revision: 1.1 $
+ * @modifiedby $LastChangedBy: phpnut $
+ * @lastmodified $Date: 2006-05-24 19:14:24 $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+
+/**
+ * In this file you set up your database connection details.
+ *
+ * @package cake
+ * @subpackage cake.config
+ */
+
+
+/**
+ * Database configuration class.
+ * You can specify multiple configurations for production, development and testing.
+ *
+ * driver =>
+ * mysql, postgres, sqlite, adodb-drivername, pear-drivername
+ *
+ * connect =>
+ * MySQL set the connect to either mysql_pconnect of mysql_connect
+ * PostgreSQL set the connect to either pg_pconnect of pg_connect
+ * SQLite set the connect to sqlite_popen sqlite_open
+ * ADOdb set the connect to one of these
+ * (http://phplens.com/adodb/supported.databases.html) and
+ * append it '|p' for persistent connection. (mssql|p for example, or just mssql for not persistent)
+ *
+ * host =>
+ * the host you connect to the database
+ * MySQL 'localhost' to add a port number use 'localhost:port#'
+ * PostgreSQL 'localhost' to add a port number use 'localhost port=5432'
+ *
+ */
+class DATABASE_CONFIG
+{
+ var $default = array('driver' => 'mysql',
+ 'connect' => 'mysql_connect',
+ 'host' => 'localhost',
+ 'login' => 'user',
+ 'password' => 'password',
+ 'database' => 'project_name',
+ 'prefix' => '');
+
+ var $test = array('driver' => 'mysql',
+ 'connect' => 'mysql_connect',
+ 'host' => 'localhost',
+ 'login' => 'user',
+ 'password' => 'password',
+ 'database' => 'project_name-test',
+ 'prefix' => '');
+}
+
+?>
\ No newline at end of file
diff --git a/webtools/uninstall_survey/config/inflections.php b/webtools/uninstall_survey/config/inflections.php
new file mode 100644
index 00000000000..be906a719ff
--- /dev/null
+++ b/webtools/uninstall_survey/config/inflections.php
@@ -0,0 +1,74 @@
+
+ * Copyright (c) 2006, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.app.config
+ * @since CakePHP v 1.0.0.2312
+ * @version $Revision: 1.1 $
+ * @modifiedby $LastChangedBy: phpnut $
+ * @lastmodified $Date: 2006-05-24 19:14:24 $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+
+/**
+ * This is a key => value array of regex used to match words.
+ * If key matches then the value is returned.
+ *
+ * $pluralRules = array('/(s)tatus$/i' => '\1\2tatuses', '/^(ox)$/i' => '\1\2en', '/([m|l])ouse$/i' => '\1ice');
+ */
+$pluralRules = array();
+/**
+ * This is a key only array of plural words that should not be inflected.
+ * Notice the last comma
+ *
+ * $uninflectedPlural = array('.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox');
+ */
+$uninflectedPlural = array();
+/**
+ * This is a key => value array of plural irregular words.
+ * If key matches then the value is returned.
+ *
+ * $irregularPlural = array('atlas' => 'atlases', 'beef' => 'beefs', 'brother' => 'brothers')
+ */
+$irregularPlural = array();
+/**
+ * This is a key => value array of regex used to match words.
+ * If key matches then the value is returned.
+ *
+ * $singularRules = array('/(s)tatuses$/i' => '\1\2tatus', '/(matr)ices$/i' =>'\1ix','/(vert|ind)ices$/i')
+ */
+$singularRules = array();
+/**
+ * This is a key only array of singular words that should not be inflected.
+ * You should not have to change this value below if you do change it use same format
+ * as the $uninflectedPlural above.
+ */
+$uninflectedSingular = $uninflectedPlural;
+/**
+ * This is a key => value array of singular irregular words.
+ * Most of the time this will be a reverse of the above $irregularPlural array
+ * You should not have to change this value below if you do change it use same format
+ *
+ * $irregularSingular = array('atlases' => 'atlas', 'beefs' => 'beef', 'brothers' => 'brother')
+ */
+$irregularSingular = array_flip($irregularPlural);
+?>
\ No newline at end of file
diff --git a/webtools/uninstall_survey/config/routes.php b/webtools/uninstall_survey/config/routes.php
new file mode 100644
index 00000000000..4c4013b9892
--- /dev/null
+++ b/webtools/uninstall_survey/config/routes.php
@@ -0,0 +1,51 @@
+
+ * Copyright (c) 2006, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.app.config
+ * @since CakePHP v 0.2.9
+ * @version $Revision: 1.1 $
+ * @modifiedby $LastChangedBy: phpnut $
+ * @lastmodified $Date: 2006-05-24 19:14:24 $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+
+/**
+ * Here, we are connecting '/' (base path) to controller called 'Pages',
+ * its action called 'display', and we pass a param to select the view file
+ * to use (in this case, /app/views/pages/home.thtml)...
+ */
+$Route->connect ('/', array('controller'=>'results', 'action'=>'', ''));
+
+/**
+ * ...and connect the rest of 'Pages' controller's urls.
+ */
+$Route->connect ('/pages/*', array('controller'=>'pages', 'action'=>'display'));
+
+/**
+ * Then we connect url '/test' to our test controller. This is helpfull in
+ * developement.
+ */
+$Route->connect ('/tests', array('controller'=>'tests', 'action'=>'index'));
+
+?>
diff --git a/webtools/uninstall_survey/config/sql/db_acl.sql b/webtools/uninstall_survey/config/sql/db_acl.sql
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/webtools/uninstall_survey/config/sql/sessions.sql b/webtools/uninstall_survey/config/sql/sessions.sql
new file mode 100644
index 00000000000..3488faf8e3a
--- /dev/null
+++ b/webtools/uninstall_survey/config/sql/sessions.sql
@@ -0,0 +1,11 @@
+-- @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+-- @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+-- @since CakePHP v 0.10.8.1997
+-- @version $Revision: 1.1 $
+
+CREATE TABLE cake_sessions (
+ id varchar(255) NOT NULL default '',
+ data text,
+ expires int(11) default NULL,
+ PRIMARY KEY (id)
+);
\ No newline at end of file
diff --git a/webtools/uninstall_survey/controllers/applications_controller.php b/webtools/uninstall_survey/controllers/applications_controller.php
new file mode 100644
index 00000000000..40c9bf29bef
--- /dev/null
+++ b/webtools/uninstall_survey/controllers/applications_controller.php
@@ -0,0 +1,7 @@
+
diff --git a/webtools/uninstall_survey/controllers/intentions_controller.php b/webtools/uninstall_survey/controllers/intentions_controller.php
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/webtools/uninstall_survey/controllers/issues_controller.php b/webtools/uninstall_survey/controllers/issues_controller.php
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/webtools/uninstall_survey/controllers/results_controller.php b/webtools/uninstall_survey/controllers/results_controller.php
new file mode 100644
index 00000000000..22eecb82ae9
--- /dev/null
+++ b/webtools/uninstall_survey/controllers/results_controller.php
@@ -0,0 +1,122 @@
+Sanitize = new Sanitize();
+
+ // Pagination Stuff
+ $this->pagination_parameters['show'] = empty($_GET['show'])? '10' : $this->Sanitize->paranoid($_GET['show']);
+ $this->pagination_parameters['sortBy'] = empty($_GET['sort'])? 'created' : $this->Sanitize->paranoid($_GET['sort']);
+ $this->pagination_parameters['direction'] = empty($_GET['direction'])? 'desc': $this->Sanitize->paranoid($_GET['direction']);
+ $this->pagination_parameters['page'] = empty($_GET['page'])? '1': $this->Sanitize->paranoid($_GET['page']);
+ $this->pagination_parameters['order'] = $this->modelClass.'.'.$this->pagination_parameters['sortBy'].' '.strtoupper($this->pagination_parameters['direction']);
+ }
+
+ /**
+ * Front page will show the graph
+ */
+ function index()
+ {
+ // Products dropdown
+ $this->set('products', $this->Application->getApplications());
+
+ // Fill in all the data passed in $_GET
+ $this->set('url_params',$this->decodeAndSanitize($this->params['url']));
+
+ // We'll need to include the graphing libraries
+ $this->set('include_graph_libraries', true);
+
+ // Core data to show on page
+ $this->set('descriptionAndTotalsData',$this->Result->getDescriptionAndTotalsData($this->params['url']));
+ }
+
+ /**
+ * Display a table of user comments
+ */
+ function comments()
+ {
+ // Products dropdown
+ $this->set('products', $this->Application->getApplications());
+
+ // Fill in all the data passed in $_GET
+ $this->set('url_params',$this->decodeAndSanitize($this->params['url']));
+
+ // Pagination settings
+ $paging['style'] = 'html';
+ $paging['link'] = "/results/comments/?product=".urlencode($this->params['url']['product'])."&start_date=".urlencode($this->params['url']['start_date'])."&end_date=".urlencode($this->params['url']['end_date'])."&show={$this->pagination_parameters['show']}&sort={$this->pagination_parameters['sortBy']}&direction={$this->pagination_parameters['direction']}&page=";
+ $paging['count'] = $this->Result->getCommentCount($this->params['url']);
+ $paging['page'] = $this->pagination_parameters['page'];
+ $paging['limit'] = $this->pagination_parameters['show'];
+ $paging['show'] = array('10','25','50');
+
+ // No point in showing them an error if they click on "show 50" but they are
+ // already on the last page.
+ if ($paging['count'] < ($this->pagination_parameters['page'] * ($this->pagination_parameters['show']/2))) {
+ $this->pagination_parameters['page'] = $paging['page'] = 1;
+ }
+
+ // Set pagination array
+ $this->set('paging',$paging);
+
+ // Core data to show on page
+ $this->set('commentsData',$this->Result->getComments($this->params['url'], $this->pagination_parameters));
+
+
+ }
+
+ /**
+ * Display a csv
+ */
+ function csv()
+ {
+ // Get rid of the header/footer/etc.
+ $this->layout = null;
+
+ // Our CSV library sends headers and everything. Keep the view empty!
+ csv_send_csv($this->Result->getCsvExportData($this->params['url']));
+
+ // I'm not exiting here in case someone is going to use post callback stuff.
+ // In development, that means extra lines get added to our CSVs, but in
+ // production it should be clean.
+ }
+
+
+}
+?>
diff --git a/webtools/uninstall_survey/index.php b/webtools/uninstall_survey/index.php
new file mode 100644
index 00000000000..6c3696d8ed3
--- /dev/null
+++ b/webtools/uninstall_survey/index.php
@@ -0,0 +1,27 @@
+
+ * Copyright (c) 2006, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.app
+ * @since CakePHP v 0.10.0.1076
+ * @version $Revision: 1.1 $
+ * @modifiedby $LastChangedBy: phpnut $
+ * @lastmodified $Date: 2006-05-24 19:14:24 $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+require 'webroot'.DIRECTORY_SEPARATOR.'index.php';
+?>
\ No newline at end of file
diff --git a/webtools/uninstall_survey/models/application.php b/webtools/uninstall_survey/models/application.php
new file mode 100644
index 00000000000..e282e8a60e0
--- /dev/null
+++ b/webtools/uninstall_survey/models/application.php
@@ -0,0 +1,24 @@
+ array('className' => 'Intention'),
+ 'Issue' => array('className' => 'Issue')
+ );
+
+ /**
+ * This was added because running findAll() on this model does a left join on the
+ * results table which takes around 10 seconds to grab all the data. All I want
+ * is a list of the applications...
+ *
+ * @return array rows representing each application
+ */
+ function getApplications()
+ {
+ return $this->query('SELECT * FROM `applications` ORDER BY `id`');
+ }
+}
+?>
diff --git a/webtools/uninstall_survey/models/intention.php b/webtools/uninstall_survey/models/intention.php
new file mode 100644
index 00000000000..374750fc3b7
--- /dev/null
+++ b/webtools/uninstall_survey/models/intention.php
@@ -0,0 +1,12 @@
+
+ array('className' => 'Application')
+ );
+
+}
+?>
diff --git a/webtools/uninstall_survey/models/issue.php b/webtools/uninstall_survey/models/issue.php
new file mode 100644
index 00000000000..008a3b8933f
--- /dev/null
+++ b/webtools/uninstall_survey/models/issue.php
@@ -0,0 +1,10 @@
+ array('className' => 'Application'),
+ 'Result' => array('className' => 'Result')
+ );
+}
+?>
diff --git a/webtools/uninstall_survey/models/result.php b/webtools/uninstall_survey/models/result.php
new file mode 100644
index 00000000000..fac4d348d75
--- /dev/null
+++ b/webtools/uninstall_survey/models/result.php
@@ -0,0 +1,339 @@
+
+ array('className' => 'Issue')
+ );
+
+ /**
+ * Count's all the comments, according to the parameters.
+ * @param array URL parameters
+ * @return Cake's findCount() value
+ */
+ function getCommentCount($params)
+ {
+ // Clean parameters
+ $params = $this->cleanArrayForSql($params);
+
+ // We only want to see rows with comments
+ $_conditions = array("comments NOT LIKE ''");
+
+ if (!empty($params['start_date'])) {
+ $_timestamp = strtotime($params['start_date']);
+
+ if (!($_timestamp == -1) || $_timestamp == false) {
+ $_date = date('Y-m-d H:i:s', $_timestamp);//sql format
+ array_push($_conditions, "`created` >= '{$_date}'");
+ }
+ }
+ if (!empty($params['end_date'])) {
+ $_timestamp = strtotime($params['end_date']);
+
+ if (!($_timestamp == -1) || $_timestamp == false) {
+ $_date = date('Y-m-d H:i:s', $_timestamp);//sql format
+ array_push($_conditions, "`created` <= '{$_date}'");
+ }
+ }
+
+ if (!empty($params['product'])) {
+ // product's come in looking like:
+ // Mozilla Firefox 1.5.0.1
+ $_exp = explode(' ',urldecode($params['product']));
+
+ if(count($_exp) == 3) {
+ $_product = $_exp[0].' '.$_exp[1];
+
+ $_version = $_exp[2];
+
+ /* Note that 'Application' is not the actual name of the table! You can
+ * thank cake for that.*/
+ array_push($_conditions, "`Application`.`name` LIKE '%{$_product}%'");
+ array_push($_conditions, "`Application`.`version` LIKE '%{$_version}%'");
+ } else {
+ // defaults I guess?
+ array_push($_conditions, "`Application`.`name` LIKE 'Mozilla Firefox'");
+ array_push($_conditions, "`Application`.`version` LIKE '1.5'");
+ }
+
+ } else {
+ // I'm providing a default here, because otherwise all results will be
+ // returned (across all applications) and that is not desired
+ array_push($_conditions, "`Application`.`name` LIKE 'Mozilla Firefox'");
+ array_push($_conditions, "`Application`.`version` LIKE '1.5'");
+ }
+
+ // Do the actual query
+ $comments = $this->findCount($_conditions);
+
+ return $comments;
+ }
+
+ /**
+ * Will retrieve all the comments within param's and pagination's parameters
+ * @param array URL parameters
+ * @param array pagination values from the controller
+ * @param boolean if privacy is true phone numbers and email addresses will be
+ * masked
+ * @return cake result set
+ */
+ function getComments($params, $pagination, $privacy=true)
+ {
+ $params = $this->cleanArrayForSql($params);
+
+ // We only want to see rows with comments
+ $_conditions = array("comments NOT LIKE ''");
+
+ if (!empty($params['start_date'])) {
+ $_timestamp = strtotime($params['start_date']);
+
+ if (!($_timestamp == -1) || $_timestamp == false) {
+ $_date = date('Y-m-d H:i:s', $_timestamp);//sql format
+ array_push($_conditions, "`created` >= '{$_date}'");
+ }
+ }
+ if (!empty($params['end_date'])) {
+ $_timestamp = strtotime($params['end_date']);
+
+ if (!($_timestamp == -1) || $_timestamp == false) {
+ $_date = date('Y-m-d H:i:s', $_timestamp);//sql format
+ array_push($_conditions, "`created` <= '{$_date}'");
+ }
+ }
+
+ if (!empty($params['product'])) {
+ // product's come in looking like:
+ // Mozilla Firefox 1.5.0.1
+ $_exp = explode(' ',urldecode($params['product']));
+
+ if(count($_exp) == 3) {
+ $_product = $_exp[0].' '.$_exp[1];
+
+ $_version = $_exp[2];
+
+ /* Note that 'Application' is not the actual name of the table! You can
+ * thank cake for that.*/
+ array_push($_conditions, "`Application`.`name` LIKE '%{$_product}%'");
+ array_push($_conditions, "`Application`.`version` LIKE '%{$_version}%'");
+ } else {
+ // defaults I guess?
+ array_push($_conditions, "`Application`.`name` LIKE 'Mozilla Firefox'");
+ array_push($_conditions, "`Application`.`version` LIKE '1.5'");
+ }
+
+ } else {
+ // I'm providing a default here, because otherwise all results will be
+ // returned (across all applications) and that is not desired
+ array_push($_conditions, "`Application`.`name` LIKE 'Mozilla Firefox'");
+ array_push($_conditions, "`Application`.`version` LIKE '1.5'");
+ }
+
+ $comments = $this->findAll($_conditions, null, $pagination['order'], $pagination['show'], $pagination['page']);
+
+ if ($privacy) {
+ // Pull out all the email addresses and phone numbers
+ foreach ($comments as $var => $val) {
+
+ // Handle foo@bar.com
+ $_email_regex = '/\ ?(.+)?@(.+)?\.(.+)?\ ?/';
+ $comments[$var]['Result']['comments'] = preg_replace($_email_regex,'$1@****.$3',$comments[$var]['Result']['comments']);
+ $comments[$var]['Result']['intention_text'] = preg_replace($_email_regex,'$1@****.$3',$comments[$var]['Result']['intention_text']);
+
+ // Handle xxx-xxx-xxxx
+ $_phone_regex = '/([0-9]{3})[ .-]?[0-9]{4}/';
+ $comments[$var]['Result']['comments'] = preg_replace($_phone_regex,'$1-****',$comments[$var]['Result']['comments']);
+ $comments[$var]['Result']['intention_text'] = preg_replace($_phone_regex,'$1-****',$comments[$var]['Result']['intention_text']);
+ }
+ }
+
+
+ return $comments;
+ }
+
+
+ /**
+ * This function runs the query to get the export data for the CSV file.
+ *
+ * @param array URL parameters
+ * @param boolean if privacy is true phone numbers and email addresses will be
+ * masked
+ * @return array two dimensional array that should be pretty easy to transform
+ * into a CSV.
+ */
+ function getCsvExportData($params, $privacy=true)
+ {
+ $params = $this->cleanArrayForSql($params);
+
+ // We have to use a left join here because there isn't always an intention
+ $_query = "
+ SELECT
+ `results`.`id`,
+ `results`.`created`,
+ `results`.`intention_text` as `intention_other`,
+ `results`.`comments`,
+ `intentions`.`description` as `intention`
+ FROM `results`
+ LEFT JOIN `intentions` ON `results`.`intention_id`=`intentions`.`id`
+ INNER JOIN `applications` ON `applications`.`id` = `results`.`application_id`
+ WHERE
+ 1=1
+ ";
+
+ if (!empty($params['start_date'])) {
+ $_timestamp = strtotime($params['start_date']);
+
+ if (!($_timestamp == -1) || $_timestamp == false) {
+ $_date = date('Y-m-d H:i:s', $_timestamp);//sql format
+ $_query .= " AND `results`.`created` >= '{$_date}'";
+ }
+ }
+
+ if (!empty($params['end_date'])) {
+ $_timestamp = strtotime($params['end_date']);
+
+ if (!($_timestamp == -1) || $_timestamp == false) {
+ $_date = date('Y-m-d H:i:s', $_timestamp);//sql format
+ $_query .= " AND `results`.`created` <= '{$_date}'";
+ }
+ }
+
+ if (!empty($params['product'])) {
+ // product's come in looking like:
+ // Mozilla Firefox 1.5.0.1
+ $_exp = explode(' ',urldecode($params['product']));
+
+ if(count($_exp) == 3) {
+ $_product = $_exp[0].' '.$_exp[1];
+
+ $_version = $_exp[2];
+
+ $_query .= " AND `applications`.`name` LIKE '{$_product}'";
+ $_query .= " AND `applications`.`version` LIKE '{$_version}'";
+ } else {
+ // defaults I guess?
+ $_query .= " AND `applications`.`name` LIKE 'Mozilla Firefox'";
+ $_query .= " AND `applications`.`version` LIKE '1.5'";
+ }
+ } else {
+ // I'm providing a default here, because otherwise all results will be
+ // returned (across all applications) and that is not desired
+ $_query .= " AND `applications`.`name` LIKE 'Mozilla Firefox'";
+ $_query .= " AND `applications`.`version` LIKE '1.5'";
+ }
+
+ $_query .= " ORDER BY `results`.`created` ASC";
+
+ $res = $this->query($_query);
+
+ // Since we're exporting to a CSV, we need to flatten the results into a 2
+ // dimensional table array
+ $newdata = array();
+
+ foreach ($res as $result) {
+
+ $newdata[] = array_merge($result['results'], $result['intentions']);
+ }
+
+ if ($privacy) {
+ // Pull out all the email addresses and phone numbers
+ foreach ($newdata as $var => $val) {
+
+ // Handle foo@bar.com
+ $_email_regex = '/\ ?(.+)?@(.+)?\.(.+)?\ ?/';
+ $newdata[$var]['comments'] = preg_replace($_email_regex,'$1@****.$3',$newdata[$var]['comments']);
+ $newdata[$var]['intention_other'] = preg_replace($_email_regex,'$1@****.$3',$newdata[$var]['intention_other']);
+
+ // Handle xxx-xxx-xxxx
+ $_phone_regex = '/([0-9]{3})[ .-]?[0-9]{4}/';
+ $newdata[$var]['comments'] = preg_replace($_phone_regex,'$1-****',$newdata[$var]['comments']);
+ $newdata[$var]['intention_other'] = preg_replace($_phone_regex,'$1-****',$newdata[$var]['intention_other']);
+ }
+ }
+
+ // Our CSV library just prints out everything in order, so we have to put the
+ // column labels on here ourselves
+ $newdata = array_merge(array(array_keys($newdata[0])), $newdata);
+
+ return $newdata;
+ }
+
+ /**
+ * Will retrieve the information used for graphing.
+ * @param the url parameters (unescaped)
+ * @return a result set
+ */
+ function getDescriptionAndTotalsData($params)
+ {
+ // Clean parameters for inserting into SQL
+ $params = $this->cleanArrayForSql($params);
+
+ /* It would be nice to drop something like this in the SELECT:
+ *
+ * CONCAT(COUNT(*)/(SELECT COUNT(*) FROM our_giant_query_all_over_again)*100,'%') AS `percentage`
+ */
+
+ $_query = "
+ SELECT
+ issues.description,
+ COUNT( DISTINCT results.id ) AS total
+ FROM
+ issues
+ LEFT JOIN
+ issues_results ON issues_results.issue_id=issues.id
+ LEFT JOIN results ON results.id=issues_results.result_id AND results.application_id=applications.id
+ JOIN applications_issues ON applications_issues.issue_id=issues.id
+ JOIN applications ON applications.id=applications_issues.application_id
+ WHERE 1=1
+ ";
+
+ if (!empty($params['start_date'])) {
+ $_timestamp = strtotime($params['start_date']);
+
+ if (!($_timestamp == -1) || $_timestamp == false) {
+ $_date = date('Y-m-d H:i:s', $_timestamp);//sql format
+ $_query.= " AND `results`.`created` >= '{$_date}'";
+ }
+ }
+
+ if (!empty($params['end_date'])) {
+ $_timestamp = strtotime($params['end_date']);
+
+ if (!($_timestamp == -1) || $_timestamp == false) {
+ $_date = date('Y-m-d H:i:s', $_timestamp);//sql format
+ $_query .= " AND `results`.`created` <= '{$_date}'";
+ }
+ }
+
+ if (!empty($params['product'])) {
+ // product's come in looking like:
+ // Mozilla Firefox 1.5.0.1
+ $_exp = explode(' ',urldecode($params['product']));
+
+ if(count($_exp) == 3) {
+ $_product = $_exp[0].' '.$_exp[1];
+
+ $_version = $_exp[2];
+
+ $_query .= " AND `applications`.`name` LIKE '{$_product}'";
+ $_query .= " AND `applications`.`version` LIKE '{$_version}'";
+ } else {
+ // defaults I guess?
+ $_query .= " AND `applications`.`name` LIKE 'Mozilla Firefox'";
+ $_query .= " AND `applications`.`version` LIKE '1.5'";
+ }
+ } else {
+ // I'm providing a default here, because otherwise all results will be
+ // returned (across all applications) and that is not desired
+ $_query .= " AND `applications`.`name` LIKE 'Mozilla Firefox'";
+ $_query .= " AND `applications`.`version` LIKE '1.5'";
+ }
+
+ $_query .= " GROUP BY `issues`.`description`
+ ORDER BY `issues`.`description` DESC";
+
+ return $this->query($_query);
+ }
+}
+?>
diff --git a/webtools/uninstall_survey/vendors/csv/csv.php b/webtools/uninstall_survey/vendors/csv/csv.php
new file mode 100644
index 00000000000..309d78bac3f
--- /dev/null
+++ b/webtools/uninstall_survey/vendors/csv/csv.php
@@ -0,0 +1,188 @@
+
+ * if ($_GET['csv'])
+ * {
+ * $res=db_query("SELECT * FROM fic_courses");
+ * csv_send_csv($res);
+ * exit;
+ * }
+ *
+ * @package libs
+ * @subpackage csv
+ * @author Richard Faaberg
+ * @author Mike Morgan
+ */
+
+/**
+ * Use a resource or two dimensional array, then send the CSV results to user.
+ * @param mixed $res MySQL resource / result, or a two dimensional array
+ * @param string $name name of the export file
+ * @return bool true if file sent, false otherwise
+ */
+function csv_send_csv($res,$name=null)
+{
+ // set name of the export file
+ $filename=(is_null($name))?'export-'.date('Y-m-d').'.csv':$name.'.csv';
+ // check for valid resource
+ if ( is_resource($res) )
+ {
+ $csv=csv_export_to_csv($res);
+ }
+ elseif( is_array($res) && !empty($res) )
+ {
+ foreach ($res as $row)
+ {
+ if ( !is_array($row) )
+ ;
+ else
+ $csv[] = csv_array_to_csv($row)."\n";
+ }
+ }
+
+ if ( is_array($csv) )
+ {
+ // stream csv to user
+ header("Content-type: application/x-csv");
+ header('Content-disposition: inline; filename="'.$filename.'"');
+ header('Cache-Control: private');
+ header('Pragma: public');
+ foreach ($csv as $row)
+ {
+ echo $row;
+ }
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Replace quotes inside of a field with double quotes, which is something CSV requires.
+ * @param string $string unquoted quotes
+ * @return string $string quoted quotes
+ */
+function csv_fix_quotes($string)
+{
+ return preg_replace('/"/','""',$string);
+}
+
+/**
+ * Replace line breaks with commas trailed by a space.
+ * @param string $string string containing line breaks
+ * @param string string without line breaks
+ */
+function csv_fix_line_breaks($string)
+{
+ return preg_replace('/(\n\r|\r)/','\n',$string);
+}
+
+/**
+ * Replaces instances of double quotes in a string with a single quote.
+ * @param string $string the string to perform the replacement on
+ * @return string the string with "" replaced by "
+ */
+function csv_unfix_quotes($string)
+{
+ return preg_replace('/""/', '"', $string);
+}
+
+/**
+ * Place quotes outside of every field, which inherently solves space, line break issues.
+ * @param string $string
+ * @return string $string with quotes around it
+ */
+function csv_add_quotes($string)
+{
+ return '"'.$string.'"';
+}
+
+/**
+ * Removes quotes from the beginning and the end of a string.
+ * @param string $string the string to remove the quotes from
+ * @return string the string, sans quotes at the beginning and end
+ */
+function csv_remove_quotes($string)
+{
+ $pattern = "/^\"(.*)\"$/";
+ $replacement = "$1";
+ return preg_replace($pattern, $replacement, $string);
+}
+
+/**
+ * Convert an array into a CSV string with quotes around each value.
+ * @param array $array
+ * @return string the values in $array surrounded by quotes and separated by commas
+ */
+function csv_array_to_csv($array)
+{
+ $csv_arr = array();
+ foreach ($array as $value)
+ {
+ $csv_arr[]=csv_add_quotes(csv_fix_quotes(csv_fix_line_breaks($value)));
+ }
+ $csv_string=implode(',',$csv_arr);
+
+ return $csv_string;
+}
+
+/**
+ * Convert a CSV string into an array.
+ * Please use sparingly - this creates temp files
+ * @param string $string the CSV string
+ * @return array the elements from the CSV string in an array
+ */
+function csv_csv_to_array($string)
+{
+ $return = array();
+ $length = strlen($string);
+
+ // create a temp file and write the string to it
+ $tmpfname = tempnam('/tmp', 'csvlib');
+ $fh = fopen($tmpfname, 'w');
+ fwrite($fh, $string);
+ fclose($fh);
+
+ // open the file for csv parsing
+ $csvh = fopen($tmpfname, 'r');
+ while (($arraydata = fgetcsv($csvh, $length, ',')) !== false)
+ {
+ $return = array_merge($return, $arraydata);
+ }
+
+ fclose($csvh);
+ unlink($tmpfname);
+
+ return $return;
+}
+
+/**
+ * Read a CSV file into a two dimensional array
+ * It returns all the rows in the file, so if the first row are headers, you'd need to take care of that in the returned array
+ * @param string $filepath the path to the csv file
+ * @param string $delimiter delimiter, default to ','
+ * @param string $enclosure enclosure character, default to '"'
+ * @return &array the two dimensional array with the csv file content, or an empty if an error occured
+ */
+function &csv_csv_file_to_array($filepath, $delimiter=',', $enclosure='"')
+{
+ $return = array();
+
+ if (!file_exists($filepath) || !is_readable($filepath))
+ return $return;
+
+ $fh =& fopen($filepath, 'r');
+ $size = filesize($filepath)+1;
+
+ while ($data =& fgetcsv($fh, $size, $delimiter, $enclosure))
+ {
+ $return[] = $data;
+ }
+
+ fclose($fh);
+
+ return $return;
+}
+?>
diff --git a/webtools/uninstall_survey/views/elements/footer.thtml b/webtools/uninstall_survey/views/elements/footer.thtml
new file mode 100644
index 00000000000..83a8a338e44
--- /dev/null
+++ b/webtools/uninstall_survey/views/elements/footer.thtml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+