diff --git a/install/sql/incremental/upd_0034.sql b/install/sql/incremental/upd_0034.sql
index ea8457700f7c65bc3b824c285e875a4dfc9295c5..16d6a0dd2aeb76a7f2a9743bf80e236aa3890243 100644
--- a/install/sql/incremental/upd_0034.sql
+++ b/install/sql/incremental/upd_0034.sql
@@ -46,6 +46,7 @@ CREATE TABLE IF NOT EXISTS `aps_packages` (
   `category` varchar(255) NOT NULL,
   `version` varchar(20) NOT NULL,
   `release` int(4) NOT NULL,
+  `package_url` TEXT NOT NULL,
   `package_status` int(1) NOT NULL DEFAULT '2',
   PRIMARY KEY (`id`)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8 ;
diff --git a/install/sql/ispconfig3.sql b/install/sql/ispconfig3.sql
index 8a08f3164b57391fa279bf5473ae576760385c3b..91a52125264bc63bf7a725565ba829d0b78e619e 100644
--- a/install/sql/ispconfig3.sql
+++ b/install/sql/ispconfig3.sql
@@ -94,6 +94,7 @@ CREATE TABLE IF NOT EXISTS `aps_packages` (
   `category` varchar(255) NOT NULL,
   `version` varchar(20) NOT NULL,
   `release` int(4) NOT NULL,
+  `package_url` TEXT NOT NULL,
   `package_status` int(1) NOT NULL DEFAULT '2',
   PRIMARY KEY (`id`)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8 ;
diff --git a/interface/lib/classes/aps_crawler.inc.php b/interface/lib/classes/aps_crawler.inc.php
index 39375f57bd0b2c7ea862748a2f154afec407e033..e4ca565d3578eab62d67ce5ee0fed4f8c2c5c73c 100644
--- a/interface/lib/classes/aps_crawler.inc.php
+++ b/interface/lib/classes/aps_crawler.inc.php
@@ -34,6 +34,9 @@ require_once('aps_base.inc.php');
 
 class ApsCrawler extends ApsBase
 {
+   
+   public $app_download_url_list = array();
+   
    /**
     * Constructor
     *
@@ -290,6 +293,8 @@ class ApsCrawler extends ApsBase
                             $app_dl = parent::getXPathValue($sxe, "entry[position()=1]/link[@a:type='aps']/@href");
                             $app_filesize = parent::getXPathValue($sxe, "entry[position()=1]/link[@a:type='aps']/@length");
                             $app_metafile = parent::getXPathValue($sxe, "entry[position()=1]/link[@a:type='meta']/@href");
+							
+							$this->app_download_url_list[$app_name.'-'.$new_ver.'.app.zip'] = $app_dl;
 
                             // Skip ASP.net packages because they can't be used at all
                             $asp_handler = parent::getXPathValue($sxe, '//aspnet:handler');
@@ -476,12 +481,13 @@ class ApsCrawler extends ApsBase
             $path_query = $this->db->queryAllRecords('SELECT path AS Path FROM aps_packages;');
             foreach($path_query as $path) $existing_packages[] = $path['Path']; 
             $diff = array_diff($existing_packages, $pkg_list);
-            foreach($diff as $todelete)
+            foreach($diff as $todelete) {
                 /*$this->db->query("UPDATE aps_packages SET package_status = '".PACKAGE_ERROR_NOMETA."' 
                     WHERE path = '".$this->db->quote($todelete)."';");*/
 				$tmp = $this->db->queryOneRecord("SELECT id FROM aps_packages WHERE path = '".$this->db->quote($todelete)."';");
 				$this->db->datalogUpdate('aps_packages', "package_status = ".PACKAGE_ERROR_NOMETA, 'id', $tmp['id']);
 				unset($tmp);
+			}
             
             // Register all new packages
             $new_packages = array_diff($pkg_list, $existing_packages);
@@ -515,10 +521,10 @@ class ApsCrawler extends ApsBase
                     ".$this->db->quote($pkg_release).", ".PACKAGE_ENABLED.");");
 				*/
 				
-				$insert_data = "(`path`, `name`, `category`, `version`, `release`, `package_status`) VALUES 
+				$insert_data = "(`path`, `name`, `category`, `version`, `release`, `package_url`, `package_status`) VALUES 
                     ('".$this->db->quote($pkg)."', '".$this->db->quote($pkg_name)."',
                     '".$this->db->quote($pkg_category)."', '".$this->db->quote($pkg_version)."',
-                    ".$this->db->quote($pkg_release).", ".PACKAGE_ENABLED.");";
+                    ".$this->db->quote($pkg_release).", '".$this->db->quote($this->app_download_url_list[$pkg])."', ".PACKAGE_ENABLED.");";
 				
 				$this->app->db->datalogInsert('aps_packages', $insert_data, 'id');
             }
diff --git a/interface/lib/classes/aps_guicontroller.inc.php b/interface/lib/classes/aps_guicontroller.inc.php
index 0b4038fbddebeb1ad39ae647dcd0ee4ce1c1cee2..55d6db0487055db8926c0a0f347b93d4d22955c2 100644
--- a/interface/lib/classes/aps_guicontroller.inc.php
+++ b/interface/lib/classes/aps_guicontroller.inc.php
@@ -199,6 +199,8 @@ class ApsGUIController extends ApsBase
     {
 		global $app;
 		
+		include_once(ISPC_WEB_PATH.'/sites/tools.inc.php');
+		
 		$webserver_id = 0;
         $websrv = $this->db->queryOneRecord("SELECT * FROM web_domain WHERE domain = '".$this->db->quote($settings['main_domain'])."';");
         if(!empty($websrv)) $webserver_id = $websrv['server_id'];
@@ -256,9 +258,11 @@ class ApsGUIController extends ApsBase
 				if($tmp['number'] == 0) break;
 			}
 			
+			$mysql_db_password = $settings['main_database_password'];
+			
 			//* Create the mysql database
 			$insert_data = "(`sys_userid`, `sys_groupid`, `sys_perm_user`, `sys_perm_group`, `sys_perm_other`, `server_id`, `parent_domain_id`, `type`, `database_name`, `database_user`, `database_password`, `database_charset`, `remote_access`, `remote_ips`, `backup_copies`, `active`, `backup_interval`) 
-					  VALUES( ".$websrv['sys_userid'].", ".$websrv['sys_groupid'].", 'riud', '".$websrv['sys_perm_group']."', '', $mysql_db_server_id, ".$websrv['domain_id'].", 'mysql', '$mysql_db_name', '$mysql_db_user', '$mysql_db_password', '', '$mysql_db_remote_access', '$mysql_db_remote_ips', ".$websrv['backup_copies'].", 'y', '".$websrv['backup_interval']."')";
+					  VALUES( ".$websrv['sys_userid'].", ".$websrv['sys_groupid'].", 'riud', '".$websrv['sys_perm_group']."', '', $mysql_db_server_id, ".$websrv['domain_id'].", 'mysql', '$mysql_db_name', '$mysql_db_user', PASSWORD('$mysql_db_password'), '', '$mysql_db_remote_access', '$mysql_db_remote_ips', ".$websrv['backup_copies'].", 'y', '".$websrv['backup_interval']."')";
 			$app->db->datalogInsert('web_database', $insert_data, 'database_id');
 			
 			//* Add db details to package settings
diff --git a/interface/web/sites/aps_cron_apscrawler_if.php b/interface/web/sites/aps_cron_apscrawler_if.php
index 32095d79deb17744a004ebec97b8d7d814baae66..6bfa89d6d69252ca56324c9bfa54f1892475a49e 100644
--- a/interface/web/sites/aps_cron_apscrawler_if.php
+++ b/interface/web/sites/aps_cron_apscrawler_if.php
@@ -32,6 +32,9 @@ require_once('../../lib/app.inc.php');
 //require_once('classes/class.crawler.php');
 $app->load('aps_crawler');
 
+if(!@ini_get('allow_url_fopen')) $app->error('allow_url_fopen is not enabled');
+if(!function_exists('curl_version')) $app->error('cURL is not available');
+
 $log_prefix = 'APS crawler cron: ';
 
 $aps = new ApsCrawler($app, true); // true = Interface mode, false = Server mode
diff --git a/server/lib/classes/aps_base.inc.php b/server/lib/classes/aps_base.inc.php
new file mode 100644
index 0000000000000000000000000000000000000000..9822caeaa22092ca89669afa5a1ebd66aab3e2c0
--- /dev/null
+++ b/server/lib/classes/aps_base.inc.php
@@ -0,0 +1,109 @@
+<?php
+/*
+Copyright (c) 2012, ISPConfig UG
+Contributors: web wack creations,  http://www.web-wack.at
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+    * Neither the name of ISPConfig nor the names of its contributors
+      may be used to endorse or promote products derived from this software without
+      specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+// Constants describing instances
+define('INSTANCE_PENDING', 0);
+define('INSTANCE_INSTALL', 1);
+define('INSTANCE_ERROR', 2);
+define('INSTANCE_SUCCESS', 3);
+define('INSTANCE_REMOVE', 4);
+
+// Constants describing packages
+define('PACKAGE_LOCKED', 1);
+define('PACKAGE_ENABLED', 2);
+define('PACKAGE_OUTDATED', 3);
+define('PACKAGE_ERROR_NOMETA', 4);
+
+class ApsBase
+{
+    protected $app = null;
+    protected $db = null;
+    
+    protected $log_prefix = '';
+    protected $fetch_url = '';
+    protected $aps_version = '';
+    protected $packages_dir = '';
+    protected $temp_pkg_dir = '';
+    protected $interface_pkg_dir = '';
+    protected $interface_mode = false; // server mode by default
+
+    /**
+     * Constructor
+     *
+     * @param $app the application instance (db handle + log method)
+     * @param $interface_mode act in interface (true) or server mode (false)
+     * @param $log_prefix a prefix to set before all log entries
+     */
+    public function __construct($app, $log_prefix = 'APS: ', $interface_mode = false)
+    {
+        $this->db = $app->db;
+        $this->app = $app;
+        
+        $this->log_prefix = $log_prefix;
+        $this->interface_mode = $interface_mode;
+        $this->fetch_url = 'apscatalog.com';
+        $this->aps_version = '1';
+        $this->packages_dir = ISPC_ROOT_PATH.'/aps_packages';
+        $this->interface_pkg_dir = ISPC_ROOT_PATH.'/web/sites/aps_meta_packages';
+    }
+    
+    /**
+     * Converts a given value to it's native representation in 1024 units
+     * 
+     * @param $value the size to convert
+     * @return integer and string 
+     */
+    public function convertSize($value)
+    {
+        $unit = array('Bytes', 'KB', 'MB', 'GB', 'TB');
+        return @round($value/pow(1024, ($i = floor(log($value, 1024)))), 2).' '.$unit[$i];
+    }
+    
+    /**
+     * Determine a specific xpath from a given SimpleXMLElement handle. If the
+     * element is found, it's string representation is returned. If not,
+     * the return value will stay empty
+     *
+     * @param $xml_handle the SimpleXMLElement handle
+     * @param $query the XPath query
+     * @param $array define whether to return an array or a string
+     * @return $ret the return string
+     */
+    protected function getXPathValue($xml_handle, $query, $array = false)
+    {
+        $ret = '';
+        
+        $xp_result = @($xml_handle->xpath($query)) ? $xml_handle->xpath($query) : false;
+        if($xp_result !== false) $ret = (($array === false) ? (string)$xp_result[0] : $xp_result);
+        
+        return $ret;
+    }
+}
+?>
\ No newline at end of file
diff --git a/server/lib/classes/aps_installer.inc.php b/server/lib/classes/aps_installer.inc.php
new file mode 100644
index 0000000000000000000000000000000000000000..b567c8174dbb338ba349be7a5650322ce73fe7ac
--- /dev/null
+++ b/server/lib/classes/aps_installer.inc.php
@@ -0,0 +1,670 @@
+<?php
+/*
+Copyright (c) 2012, ISPConfig UG
+Contributors: web wack creations,  http://www.web-wack.at
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+    * Neither the name of ISPConfig nor the names of its contributors
+      may be used to endorse or promote products derived from this software without
+      specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+require_once('aps_base.inc.php');
+
+@set_time_limit(0);
+@ignore_user_abort(1);
+
+class ApsInstaller extends ApsBase
+{
+    private $handle_type = '';
+    private $domain = '';
+    private $document_root = '';
+    private $sublocation = '';
+    private $local_installpath = '';
+    private $dbhost = '';
+    private $newdb_name = '';
+    private $newdb_user = '';
+    private $file_owner_user = '';
+    private $file_owner_group = '';
+	private $putenv = array();
+    
+   /**
+    * Constructor
+    *
+    * @param $app the application instance (db handle + log method)
+    * @param $interface_mode act in interface (true) or server mode (false)
+    */
+    public function __construct($app, $interface_mode = false)
+    {
+        parent::__construct($app, 'APS installer: ', $interface_mode);
+    }
+    
+    /**
+     * Before the cron is executed, make sure all necessary options are set
+     * and all functions are available
+     */
+    private function checkRequirements()
+    {
+        try
+        {
+            // Check if exec() is not disabled
+            $disabled_func = explode(',', @ini_get('disable_functions'));
+            if(in_array('exec', $disabled_func)) throw new Exception('the call of exec() is disabled');
+            
+            // Check if safe_mode is disabled (needed for correct putenv, chmod, chown handling)
+            if(@ini_get('safe_mode')) throw new Exception('the safe_mode restriction is on');
+            
+            return true;
+        }
+        catch(Exception $e)
+        {
+            $this->app->log('Aborting execution because '.$e->getMessage());
+            return false;
+        }
+    }
+    
+    /**
+     * Get a file from a ZIP archive and either return it's content or
+     * extract it to a given destination
+     * 
+     * @param $zipfile the ZIP file to work with
+     * @param $subfile the file from which to get the content
+     * @param $destfolder the optional extraction destination
+     * @param $destname the optional target file name when extracting
+     * @return string or boolean
+     */
+    private function getContentFromZIP($zipfile, $subfile, $destfolder = '', $destname = '')
+    {
+        try
+        {
+            $zip = new ZipArchive;
+            $res = $zip->open(realpath($zipfile));
+            if(!$res) throw new Exception('Cannot open ZIP file '.$zipfile);
+        
+            // If no destination is given, the content is returned, otherwise
+            // the $subfile is extracted to $destination
+            if($destfolder == '')
+            {
+                $fh = $zip->getStream($subfile);
+                if(!$fh) throw new Exception('Cannot read '.$subfile.' from '.$zipfile);
+                
+                $subfile_content = '';            
+                while(!feof($fh)) $subfile_content .= fread($fh, 8192);
+                
+                fclose($fh);
+                
+                return $subfile_content;
+            }
+            else
+            {
+                // extractTo would be suitable but has no target name parameter
+                //$ind = $zip->locateName($subfile);
+                //$ex = $zip->extractTo($destination, array($zip->getNameIndex($ind)));
+                if($destname == '') $destname = basename($subfile);
+                $ex = @copy('zip://'.$zipfile.'#'.$subfile, $destfolder.$destname);
+                if(!$ex) throw new Exception('Cannot extract '.$subfile.' to '.$destfolder);
+            }
+            
+            $zip->close();
+            
+        }
+        catch(Exception $e)
+        {
+            // The exception message is only interesting for debugging reasons
+            // echo $e->getMessage();
+            return false;
+        }
+    }    
+    
+    /**
+     * Extract the complete directory of a ZIP file
+     * 
+     * @param $filename the file to unzip
+     * @param $directory the ZIP inside directory to unzip
+     * @param $destination the place where to extract the data
+     * @return boolean
+     */
+    private function extractZip($filename, $directory, $destination)
+    {
+        if(!file_exists($filename)) return false;
+        
+        // Fix the paths
+        if(substr($directory, -1) == '/') $directory = substr($directory, 0, strlen($directory) - 1);
+        if(substr($destination, -1) != '/') $destination .= '/';
+        
+        // Read and extract the ZIP file
+        $ziphandle = zip_open(realpath($filename));
+        if(is_resource($ziphandle))
+        {
+            while($entry = zip_read($ziphandle))
+            {
+                if(substr(zip_entry_name($entry), 0, strlen($directory)) == $directory)
+                {
+                    // Modify the relative ZIP file path
+                    $new_path = substr(zip_entry_name($entry), strlen($directory));
+                    
+                    if(substr($new_path, -1) == '/') // Identifier for directories
+                    {
+                        if(!file_exists($destination.$new_path)) mkdir($destination.$new_path, 0777, true);
+                    }
+                    else // Handle files
+                    {
+                        if(zip_entry_open($ziphandle, $entry))
+                        {
+                            $new_dir = dirname($destination.$new_path);
+                            if(!file_exists($new_dir)) mkdir($new_dir, 0777, true);
+                            
+                            $file = fopen($destination.$new_path, 'wb');
+                            if($file)
+                            {
+                                while($line = zip_entry_read($entry)) fwrite($file, $line);
+                                fclose($file);
+                            }
+                            else return false;
+                        }
+                    }
+                }
+            }
+            
+            zip_close($ziphandle);
+            return true;
+        }
+        
+        return false;
+    }
+
+    /**
+     * Setup the path environment variables for the install script
+     * 
+     * @param $parent_mapping the SimpleXML instance with the current mapping position
+     * @param $url the relative path within the mapping tree
+     * @param $path the absolute path within the mapping tree
+     */
+    private function processMappings($parent_mapping, $url, $path)
+    {
+        if($parent_mapping && $parent_mapping != null)
+        {
+            $writable = parent::getXPathValue($parent_mapping, 'php:permissions/@writable');
+            $readable = parent::getXPathValue($parent_mapping, 'php:permissions/@readable');
+
+            // set the write permission            
+            if($writable == 'true')
+            {
+                if(is_dir($path)) chmod($path, 0775);
+                else chmod($path, 0664);
+            }
+            
+            // set non-readable permission
+            if($readable == 'false')
+            {
+                if(is_dir($path)) chmod($path, 0333);
+                else chmod($path, 0222);
+            }
+        }
+        
+        // Set the environment variables
+        $env = str_replace('/', '_', $url);
+        $this->putenv[] = 'WEB_'.$env.'_DIR='.$path;
+        
+        // Step recursively into further mappings
+        if($parent_mapping && $parent_mapping != null)
+        {
+            foreach($parent_mapping->mapping as $mapping)
+            {
+                if($url == '/') $this->processMappings($mapping, $url.$mapping['url'], $path.$mapping['url']);
+                else $this->processMappings($mapping, $url.'/'.$mapping['url'], $path.'/'.$mapping['url']);
+            }
+        }
+    }    
+    
+    /**
+     * Setup the environment with data for the install location
+     * 
+     * @param $task an array containing all install related data
+     */
+    private function prepareLocation($task)
+    {
+        // Get the domain name to use for the installation
+        // Would be possible in one query too, but we use 2 for easier debugging
+        $main_domain = $this->app->db->queryOneRecord("SELECT value FROM aps_instances_settings  
+            WHERE name = 'main_domain' AND instance_id = '".$this->db->quote($task['instance_id'])."';");
+        $this->domain = $main_domain['value'];
+        
+        // Get the document root
+        $domain_res = $this->app->db->queryOneRecord("SELECT document_root FROM web_domain 
+            WHERE domain = '".$this->db->quote($this->domain)."';");
+        $this->document_root = $domain_res['document_root'];
+        
+        // Get the sub location
+        $location_res = $this->app->dbmaster->queryOneRecord("SELECT value FROM aps_instances_settings 
+            WHERE name = 'main_location' AND instance_id = '".$this->db->quote($task['instance_id'])."';");
+        $this->sublocation = $location_res['value'];
+        
+        // Make sure the document_root ends with /
+        if(substr($this->document_root, -1) != '/') $this->document_root .= '/';
+        
+        // Attention: ISPConfig Special: web files are in subfolder 'web' -> append it:
+        $this->document_root .= 'web/';
+
+        // If a subfolder is given, make sure it's path doesn't begin with / i.e. /phpbb
+        if(substr($this->sublocation, 0, 1) == '/') $this->sublocation = substr($this->sublocation, 1);
+                
+        // If the package isn't installed to a subfolder, remove the / at the end of the document root
+        if(empty($this->sublocation)) $this->document_root = substr($this->document_root, 0, strlen($this->document_root) - 1);
+        
+        // Set environment variables, later processed by the package install script
+        $this->putenv[] = 'BASE_URL_SCHEME=http';
+        // putenv('BASE_URL_PORT') -> omitted as it's 80 by default
+        $this->putenv[] = 'BASE_URL_HOST='.$this->domain;
+        $this->putenv[] = 'BASE_URL_PATH='.$this->sublocation.'/';
+    }    
+    
+    /**
+     * Setup a database (if needed) and the appropriate environment variables
+     * 
+     * @param $task an array containing all install related data
+     * @param $sxe a SimpleXMLElement handle, holding APP-META.xml
+     */
+    private function prepareDatabase($task, $sxe)
+    {
+        $db_id = parent::getXPathValue($sxe, '//db:id');
+        if(empty($db_id)) return; // No database needed
+        
+		/*
+        // Set the database owner to the domain owner
+        // ISPConfig identifies the owner by the sys_groupid (not sys_userid!)
+        // so sys_userid can be set to any value
+        $perm = $this->app->db->queryOneRecord("SELECT sys_groupid, server_id FROM web_domain 
+            WHERE domain = '".$this->domain."';");
+        $task['sys_groupid'] = $perm['sys_groupid'];
+        $serverid = $perm['server_id'];
+                
+        // Get the database prefix and db user prefix 
+        $this->app->uses('getconf');
+        $global_config = $this->app->getconf->get_global_config('sites');
+        $dbname_prefix = str_replace('[CLIENTID]', '', $global_config['dbname_prefix']);
+        $dbuser_prefix = str_replace('[CLIENTID]', '', $global_config['dbuser_prefix']);
+        $this->dbhost = DB_HOST; // Taken from config.inc.php
+        if(empty($this->dbhost)) $this->dbhost = 'localhost'; // Just to ensure any hostname... ;)
+        
+        $this->newdb_name = $dbname_prefix.$task['CustomerID'].'aps'.$task['InstanceID'];
+        $this->newdb_user = $dbuser_prefix.$task['CustomerID'].'aps'.$task['InstanceID'];
+        $dbpw_res = $this->app->dbmaster->queryOneRecord("SELECT Value FROM aps_instances_settings  
+            WHERE Name = 'main_database_password' AND InstanceID = '".$this->db->quote($task['InstanceID'])."';");
+        $newdb_pw = $dbpw_res['Value'];
+ 
+        // In any case delete an existing database (install and removal procedure)
+        $this->db->query('DROP DATABASE IF EXISTS `'.$this->db->quote($this->newdb_name).'`;');
+        // Delete an already existing database with this name
+        $this->app->dbmaster->query("DELETE FROM web_database WHERE database_name = '".$this->db->quote($this->newdb_name)."';");
+        
+        
+        // Create the new database and assign it to a user
+        if($this->handle_type == 'install')
+        {
+            $this->db->query('CREATE DATABASE IF NOT EXISTS `'.$this->db->quote($this->newdb_name).'`;');
+            $this->db->query('GRANT ALL PRIVILEGES ON '.$this->db->quote($this->newdb_name).'.* TO '.$this->db->quote($this->newdb_user).'@'.$this->db->quote($this->dbhost).' IDENTIFIED BY \'password\';');
+            $this->db->query('SET PASSWORD FOR '.$this->db->quote($this->newdb_user).'@'.$this->db->quote($this->dbhost).' = PASSWORD(\''.$newdb_pw.'\');');
+            $this->db->query('FLUSH PRIVILEGES;');
+        
+            // Add the new database to the customer databases
+            // Assumes: charset = utf8
+            $this->app->dbmaster->query('INSERT INTO web_database (sys_userid, sys_groupid, sys_perm_user, sys_perm_group, sys_perm_other, server_id, 
+                type, database_name, database_user, database_password, database_charset, remote_access, remote_ips, active) 
+                VALUES ('.$task['sys_userid'].', '.$task['sys_groupid'].', "'.$task['sys_perm_user'].'", "'.$task['sys_perm_group'].'", 
+                "'.$task['sys_perm_other'].'", '.$this->db->quote($serverid).', "mysql", "'.$this->db->quote($this->newdb_name).'", 
+                "'.$this->db->quote($this->newdb_user).'", "'.$this->db->quote($newdb_pw).'", "utf8", "n", "", "y");');
+        }
+		*/
+        
+        $mysqlver_res = $this->app->db->queryOneRecord('SELECT VERSION() as ver;');
+        $mysqlver = $mysqlver_res['ver'];
+		
+		$tmp = $this->app->dbmaster->queryOneRecord("SELECT value FROM aps_instances_settings WHERE name = 'main_database_password' AND instance_id = '".$this->db->quote($task['instance_id'])."';");
+        $newdb_pw = $tmp['value'];
+		
+		$tmp = $this->app->dbmaster->queryOneRecord("SELECT value FROM aps_instances_settings WHERE name = 'main_database_host' AND instance_id = '".$this->db->quote($task['instance_id'])."';");
+        $newdb_host = $tmp['value'];
+		
+		$tmp = $this->app->dbmaster->queryOneRecord("SELECT value FROM aps_instances_settings WHERE name = 'main_database_name' AND instance_id = '".$this->db->quote($task['instance_id'])."';");
+        $newdb_name = $tmp['value'];
+		
+		$tmp = $this->app->dbmaster->queryOneRecord("SELECT value FROM aps_instances_settings WHERE name = 'main_database_login' AND instance_id = '".$this->db->quote($task['instance_id'])."';");
+        $newdb_login = $tmp['value'];
+        
+        $this->putenv[] = 'DB_'.$db_id.'_TYPE=mysql';
+        $this->putenv[] = 'DB_'.$db_id.'_NAME='.$newdb_name;
+        $this->putenv[] = 'DB_'.$db_id.'_LOGIN='.$newdb_login;
+        $this->putenv[] = 'DB_'.$db_id.'_PASSWORD='.$newdb_pw;
+        $this->putenv[] = 'DB_'.$db_id.'_HOST='.$newdb_host;
+        $this->putenv[] = 'DB_'.$db_id.'_PORT=3306';
+        $this->putenv[] = 'DB_'.$db_id.'_VERSION='.$mysqlver;
+    }    
+    
+    /**
+     * Extract all needed files from the package
+     * 
+     * @param $task an array containing all install related data
+     * @param $sxe a SimpleXMLElement handle, holding APP-META.xml
+     * @return boolean
+     */
+    private function prepareFiles($task, $sxe)
+    {
+        // Basically set the mapping for APS version 1.0, if not available -> newer way
+        $mapping = $sxe->mapping;
+        $mapping_path = $sxe->mapping['path'];
+        $mapping_url = $sxe->mapping['url'];
+        if(empty($mapping))
+        {
+            $mapping = $sxe->service->provision->{'url-mapping'}->mapping;
+            $mapping_path = $sxe->service->provision->{'url-mapping'}->mapping['path'];
+            $mapping_url = $sxe->service->provision->{'url-mapping'}->mapping['url'];
+        }
+
+        try
+        {
+            // Make sure we have a valid mapping path (at least /)
+            if(empty($mapping_path)) throw new Exception('Unable to determine a mapping path');
+            
+            $this->local_installpath = $this->document_root.$this->sublocation.'/';
+
+            // Now delete an existing folder (affects install and removal in the same way)
+            @chdir($this->local_installpath);
+            if(file_exists($this->local_installpath)) exec("rm -Rf ".escapeshellarg($this->local_installpath).'*');
+            else mkdir($this->local_installpath, 0777, true);
+
+            if($this->handle_type == 'install')
+            {            
+                // Now check if the needed folder is there
+                if(!file_exists($this->local_installpath))
+                    throw new Exception('Unable to create a new folder for the package '.$task['path']);
+                
+                // Extract all files and assign them a new owner
+                if( ($this->extractZip($this->packages_dir.'/'.$task['path'], $mapping_path, $this->local_installpath) === false)
+                 || ($this->extractZip($this->packages_dir.'/'.$task['path'], 'scripts', $this->local_installpath.'install_scripts/') === false) )
+                {
+                    // Clean already extracted data
+                    exec("rm -Rf ".escapeshellarg($this->local_installpath).'*');
+                    throw new Exception('Unable to extract the package '.$task['path']);              
+                }
+                
+                $this->processMappings($mapping, $mapping_url, $this->local_installpath);
+            
+                // Set the appropriate file owner
+                $main_domain = $this->app->db->queryOneRecord("SELECT value FROM aps_instances_settings  
+                    WHERE name = 'main_domain' AND instance_id = '".$this->db->quote($task['instance_id'])."';");        
+                $owner_res = $this->db->queryOneRecord("SELECT system_user, system_group FROM web_domain  
+                        WHERE domain = '".$this->db->quote($main_domain['value'])."';");
+                $this->file_owner_user = $owner_res['system_user']; 
+                $this->file_owner_group = $owner_res['system_group'];
+                exec('chown -R '.$this->file_owner_user.':'.$this->file_owner_group.' '.escapeshellarg($this->local_installpath));
+            }
+        }
+        catch(Exception $e)
+        {
+            $this->app->dbmaster->query('UPDATE aps_instances SET instance_status = "'.INSTANCE_ERROR.'" 
+                WHERE id = "'.$this->db->quote($task['instance_id']).'";');
+            $this->app->log($e->getMessage());
+            return false;
+        }
+        
+        return true;
+    }
+    
+    /**
+     * Get all user config variables and set them to environment variables
+     * 
+     * @param $task an array containing all install related data
+     */    
+    private function prepareUserInputData($task)
+    {
+        $userdata = $this->app->dbmaster->queryAllRecords("SELECT name, value FROM aps_instances_settings 
+            WHERE instance_id = '".$this->db->quote($task['instance_id'])."';");
+        if(empty($userdata)) return false;
+        
+        foreach($userdata as $data)
+        {
+            // Skip unnecessary data
+            if($data['name'] == 'main_location'
+            || $data['name'] == 'main_domain'
+            || $data['name'] == 'main_database_password'
+			|| $data['name'] == 'main_database_name'
+			|| $data['name'] == 'main_database_host'
+			|| $data['name'] == 'main_database_login'
+            || $data['name'] == 'license') continue;
+            
+            $this->putenv[] = 'SETTINGS_'.$data['name'].'='.$data['value'];
+        }
+    }
+	
+	/**
+     * Fetch binary data from a given array
+     * The data is retrieved in binary mode and 
+     * then directly written to an output file
+     *
+     * @param $input a specially structed array
+     * @see $this->startUpdate()
+     */
+    private function fetchFiles($input)
+    {
+        $fh = array();
+        $url = array();
+        $conn = array();
+
+        // Build the single cURL handles and add them to a multi handle
+        $mh = curl_multi_init();
+        
+        // Process each app 
+        for($i = 0; $i < count($input); $i++)
+        {
+            $conn[$i] = curl_init($input[$i]['url']);
+            $fh[$i] = fopen($input[$i]['localtarget'], 'wb'); 
+            
+            curl_setopt($conn[$i], CURLOPT_BINARYTRANSFER, true);
+            curl_setopt($conn[$i], CURLOPT_FILE, $fh[$i]);
+            curl_setopt($conn[$i], CURLOPT_TIMEOUT, 0);
+            curl_setopt($conn[$i], CURLOPT_FAILONERROR, 1);
+            curl_setopt($conn[$i], CURLOPT_FOLLOWLOCATION, 1); 
+            
+            curl_multi_add_handle($mh, $conn[$i]);
+        }
+        
+        $active = 0;
+        do curl_multi_exec($mh, $active);
+        while($active > 0);
+
+        // Close the handles
+        for($i = 0; $i < count($input); $i++)
+        {
+            fclose($fh[$i]);
+            curl_multi_remove_handle($mh, $conn[$i]);
+            curl_close($conn[$i]);
+        }
+        curl_multi_close($mh);
+    }
+    
+    /**
+     * The installation script should be executed
+     * 
+     * @param $task an array containing all install related data
+     * @param $sxe a SimpleXMLElement handle, holding APP-META.xml
+     * @return boolean
+     */
+    private function doInstallation($task, $sxe)
+    {
+        try
+        {
+            // Check if the install directory exists
+            if(!is_dir($this->local_installpath.'install_scripts/'))
+                throw new Exception('The install directory '.$this->local_installpath.' is not existing');
+            
+            // Set the executable bit to the configure script
+            $cfgscript = @(string)$sxe->service->provision->{'configuration-script'}['name'];
+            if(!$cfgscript) $cfgscript = 'configure';
+            chmod($this->local_installpath.'install_scripts/'.$cfgscript, 0755);
+            
+            // Change to the install folder (import for the exec() below!)
+			//exec('chown -R '.$this->file_owner_user.':'.$this->file_owner_group.' '.escapeshellarg($this->local_installpath));
+            chdir($this->local_installpath.'install_scripts/');
+			
+			// Set the enviroment variables
+			foreach($this->putenv as $var) {
+				putenv($var);
+			}
+			
+            $shell_retcode = true;
+            $shell_ret = array();
+			 exec('php '.escapeshellarg($this->local_installpath.'install_scripts/'.$cfgscript).' install 2>&1', $shell_ret, $shell_retcode);
+            $shell_ret = array_filter($shell_ret);
+            $shell_ret_str = implode("\n", $shell_ret);
+            
+			// Although $shell_retcode might be 0, there can be PHP errors. Filter them:
+            if(substr_count($shell_ret_str, 'Warning: ') > 0) $shell_retcode = 1;
+            
+            // If an error has occurred, the return code is != 0 
+            if($shell_retcode != 0) throw new Exception($shell_ret_str);
+            else
+            {
+                // The install succeeded, chown newly created files too
+                exec('chown -R '.$this->file_owner_user.':'.$this->file_owner_group.' '.escapeshellarg($this->local_installpath));
+                
+                $this->app->dbmaster->query('UPDATE aps_instances SET instance_status = "'.INSTANCE_SUCCESS.'" 
+                    WHERE id = "'.$this->db->quote($task['instance_id']).'";');
+            }
+        }
+        catch(Exception $e)
+        {
+            $this->app->dbmaster->query('UPDATE aps_instances SET instance_status = "'.INSTANCE_ERROR.'" 
+                WHERE id = "'.$this->db->quote($task['instance_id']).'";');
+            $this->app->log($e->getMessage());
+            return false;
+        }
+        
+        return true;
+    }
+    
+    /**
+     * Cleanup: Remove install scripts, remove tasks and update the database
+     * 
+     * @param $task an array containing all install related data
+     * @param $sxe a SimpleXMLElement handle, holding APP-META.xml
+     */
+    private function cleanup($task, $sxe)
+    {
+        chdir($this->local_installpath);
+        exec("rm -Rf ".escapeshellarg($this->local_installpath).'install_scripts');
+    }    
+    
+    /**
+     * The main method which performs the actual package installation
+     * 
+     * @param $instanceid the instanceID to install
+     * @param $type the type of task to perform (installation, removal)
+     */
+    public function installHandler($instanceid, $type)
+    {
+        // Set the given handle type, currently supported: install, delete
+        if($type == 'install' || $type == 'delete') $this->handle_type = $type;
+        else return false;
+        
+        // Get all instance metadata
+        $task = $this->app->db->queryOneRecord("SELECT * FROM aps_instances AS i 
+            INNER JOIN aps_packages AS p ON i.package_id = p.id 
+            INNER JOIN client AS c ON i.customer_id = c.client_id
+            WHERE i.id = ".$instanceid.";");
+        if(!$task) return false;  // formerly: throw new Exception('The InstanceID doesn\'t exist.');
+        if(!isset($task['instance_id'])) $task['instance_id'] = $instanceid;
+		
+		// Download aps package
+		if(!file_exists($this->packages_dir.'/'.$task['path'])) {
+			$ch = curl_init();
+			$fh = fopen($this->packages_dir.'/'.$task['path'], 'wb');
+			curl_setopt($ch, CURLOPT_FILE, $fh); 
+			//curl_setopt($ch, CURLOPT_HEADER, 0); 
+			curl_setopt($ch, CURLOPT_URL, $task['package_url']);
+			curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
+			curl_setopt($ch, CURLOPT_TIMEOUT, 0);
+			curl_setopt($ch, CURLOPT_FAILONERROR, 1);
+			curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);     
+			if(curl_exec($ch) === false) $this->app->log(curl_error ($ch),LOGLEVEL_DEBUG);
+			fclose($fh);
+			curl_close($ch);
+		}
+		
+		/*
+		$app_to_dl[] = array('name' => $task['path'], 
+                            'url' => $task['package_url'], 
+                            'filesize' => 0, 
+                            'localtarget' => $this->packages_dir.'/'.$task['path']);
+
+        $this->fetchFiles($app_to_dl);
+		*/
+		
+		// Make sure the requirements are given so that this script can execute
+        $req_ret = $this->checkRequirements();
+        if(!$req_ret) return false;
+        
+        $metafile = $this->getContentFromZIP($this->packages_dir.'/'.$task['path'], 'APP-META.xml');
+        // Check if the meta file is existing
+        if(!$metafile)
+        {
+            $this->app->dbmaster->query('UPDATE aps_instances SET instance_status = "'.INSTANCE_ERROR.'" 
+                WHERE id = "'.$this->db->quote($task['instance_id']).'";');
+            $this->app->log('Unable to find the meta data file of package '.$task['path']);
+            return false;
+        }
+        
+        // Rename namespaces and register them 
+        $metadata = str_replace("xmlns=", "ns=", $metafile);
+        $sxe = new SimpleXMLElement($metadata);
+        $namespaces = $sxe->getDocNamespaces(true);
+        foreach($namespaces as $ns => $url) $sxe->registerXPathNamespace($ns, $url); 
+
+        // Setup the environment with data for the install location
+        $this->prepareLocation($task);
+        
+        // Create the database if necessary
+        $this->prepareDatabase($task, $sxe);
+        
+        // Unpack the install scripts from the packages
+        if($this->prepareFiles($task, $sxe) && $this->handle_type == 'install')
+        {
+            // Setup the variables from the install script
+            $this->prepareUserInputData($task);
+        
+            // Do the actual installation
+            $this->doInstallation($task, $sxe);
+            
+            // Remove temporary files
+            $this->cleanup($task, $sxe);
+        }
+        
+        // Finally delete the instance entry + settings
+        if($this->handle_type == 'delete')
+        {
+            $this->app->dbmaster->query('DELETE FROM aps_instances WHERE id = "'.$this->db->quote($task['instance_id']).'";');
+            $this->app->dbmaster->query('DELETE FROM aps_instances_settings WHERE instance_id = "'.$this->db->quote($task['instance_id']).'";');
+        }
+        
+        unset($sxe);
+    }
+}
+?>
\ No newline at end of file
diff --git a/server/mods-available/web_module.inc.php b/server/mods-available/web_module.inc.php
index 8d5681a47b5225cbcb83647e568addbb9682a956..dd7aba083424858978c0b7efd5cb8c53e5e15c66 100644
--- a/server/mods-available/web_module.inc.php
+++ b/server/mods-available/web_module.inc.php
@@ -52,7 +52,19 @@ class web_module {
 									'web_folder_user_delete',
 									'web_backup_insert',
 									'web_backup_update',
-									'web_backup_delete');
+									'web_backup_delete',
+									'aps_instance_insert',
+									'aps_instance_update',
+									'aps_instance_delete',
+									'aps_instance_setting_insert',
+									'aps_instance_setting_update',
+									'aps_instance_setting_delete',
+									'aps_package_insert',
+									'aps_package_update',
+									'aps_package_delete',
+									'aps_setting_insert',
+									'aps_setting_update',
+									'aps_setting_delete');
 	
 	//* This function is called during ispconfig installation to determine
 	//  if a symlink shall be created for this plugin.
@@ -98,6 +110,10 @@ class web_module {
 		$app->modules->registerTableHook('web_folder','web_module','process');
 		$app->modules->registerTableHook('web_folder_user','web_module','process');
 		$app->modules->registerTableHook('web_backup','web_module','process');
+		$app->modules->registerTableHook('aps_instances','web_module','process');
+		$app->modules->registerTableHook('aps_instances_settings','web_module','process');
+		$app->modules->registerTableHook('aps_packages','web_module','process');
+		$app->modules->registerTableHook('aps_settings','web_module','process');
 		
 		// Register service
 		$app->services->registerService('httpd','web_module','restartHttpd');
@@ -149,6 +165,26 @@ class web_module {
 				if($action == 'u') $app->plugins->raiseEvent('web_backup_update',$data);
 				if($action == 'd') $app->plugins->raiseEvent('web_backup_delete',$data);
 			break;
+			case 'aps_instances':
+				if($action == 'i') $app->plugins->raiseEvent('aps_instance_insert',$data);
+				if($action == 'u') $app->plugins->raiseEvent('aps_instance_update',$data);
+				if($action == 'd') $app->plugins->raiseEvent('aps_instance_delete',$data);
+			break;
+			case 'aps_instances_settings':
+				if($action == 'i') $app->plugins->raiseEvent('aps_instance_setting_insert',$data);
+				if($action == 'u') $app->plugins->raiseEvent('aps_instance_setting_update',$data);
+				if($action == 'd') $app->plugins->raiseEvent('aps_instance_setting_delete',$data);
+			break;
+			case 'aps_packages':
+				if($action == 'i') $app->plugins->raiseEvent('aps_package_insert',$data);
+				if($action == 'u') $app->plugins->raiseEvent('aps_package_update',$data);
+				if($action == 'd') $app->plugins->raiseEvent('aps_package_delete',$data);
+			break;
+			case 'aps_settings':
+				if($action == 'i') $app->plugins->raiseEvent('aps_setting_insert',$data);
+				if($action == 'u') $app->plugins->raiseEvent('aps_setting_update',$data);
+				if($action == 'd') $app->plugins->raiseEvent('aps_setting_delete',$data);
+			break;
 		} // end switch
 	} // end function
 	
diff --git a/server/plugins-available/aps_plugin.inc.php b/server/plugins-available/aps_plugin.inc.php
new file mode 100644
index 0000000000000000000000000000000000000000..26ae9bedee56e02d5fc3284a6df86a58f4476a19
--- /dev/null
+++ b/server/plugins-available/aps_plugin.inc.php
@@ -0,0 +1,108 @@
+<?php
+/*
+Copyright (c) 2012, ISPConfig UG
+Contributors: web wack creations,  http://www.web-wack.at
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+    * Neither the name of ISPConfig nor the names of its contributors
+      may be used to endorse or promote products derived from this software without
+      specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+require_once(ISPC_ROOT_PATH.'/lib/classes/aps_installer.inc.php');
+//require_once(ISPC_ROOT_PATH.'/lib/classes/class.installer.php');
+
+class aps_plugin
+{
+    public $plugin_name = 'aps_plugin';
+    public $class_name = 'aps_plugin';
+	
+	//* This function is called during ispconfig installation to determine
+	//  if a symlink shall be created for this plugin.
+	function onInstall() {
+		global $conf;
+
+		if($conf['services']['web'] == true) {
+			return true;
+		} else {
+			return false;
+		}
+
+	}
+    
+    /**
+     * This method gets called when the plugin is loaded
+     */
+    public function onLoad()
+    {
+        global $app;
+        
+        // Register the available events
+        $app->plugins->registerEvent('aps_instance_install', $this->plugin_name, 'install');
+        $app->plugins->registerEvent('aps_instance_update', $this->plugin_name, 'install');
+        $app->plugins->registerEvent('aps_instance_delete', $this->plugin_name, 'delete');
+    }
+    
+    /**
+     * (Re-)install a package
+     */
+    public function install($event_name, $data)
+    {
+        global $app, $conf;
+        
+		$app->log("Starting APS install",LOGLEVEL_DEBUG);
+        if(!isset($data['new']['id'])) return false;
+        $instanceid = $data['new']['id'];
+		
+		if($data['new']['instance_status'] == INSTANCE_INSTALL) {
+			$aps = new ApsInstaller($app);
+			$app->log("Running installHandler",LOGLEVEL_DEBUG);
+			$aps->installHandler($instanceid, 'install');
+		}
+    }
+    
+    /**
+     * Update an existing instance (currently unused)
+     */
+	 /*
+    public function update($event_name, $data)
+    {
+    }
+	*/
+    
+    /**
+     * Uninstall an instance
+     */
+    public function delete($event_name, $data)
+    {
+        global $app, $conf;
+        
+        if(!isset($data['new']['id'])) return false;
+        $instanceid = $data['new']['id'];
+		
+		if($data['new']['instance_status'] == INSTANCE_REMOVE) {
+			$aps = new ApsInstaller($app);
+			$aps->installHandler($instanceid, 'install');
+		}        
+    }
+}
+?>
\ No newline at end of file