tbrehm
2013-02-22 526b997c9891a796b152cdbab8e329b356b1f596
commit | author | age
7d4360 1 <?php
T 2 /*
3 Copyright (c) 2012, ISPConfig UG
4 Contributors: web wack creations,  http://www.web-wack.at
5 All rights reserved.
6
7 Redistribution and use in source and binary forms, with or without modification,
8 are permitted provided that the following conditions are met:
9
10     * Redistributions of source code must retain the above copyright notice,
11       this list of conditions and the following disclaimer.
12     * Redistributions in binary form must reproduce the above copyright notice,
13       this list of conditions and the following disclaimer in the documentation
14       and/or other materials provided with the distribution.
15     * Neither the name of ISPConfig nor the names of its contributors
16       may be used to endorse or promote products derived from this software without
17       specific prior written permission.
18
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
26 OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28 EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30 require_once('aps_base.inc.php');
31
32 @set_time_limit(0);
33 @ignore_user_abort(1);
34
35 class ApsInstaller extends ApsBase
36 {
37     private $handle_type = '';
38     private $domain = '';
39     private $document_root = '';
40     private $sublocation = '';
41     private $local_installpath = '';
42     private $dbhost = '';
43     private $newdb_name = '';
44     private $newdb_user = '';
45     private $file_owner_user = '';
46     private $file_owner_group = '';
47     private $putenv = array();
48     
49    /**
50     * Constructor
51     *
52     * @param $app the application instance (db handle + log method)
53     * @param $interface_mode act in interface (true) or server mode (false)
54     */
55     public function __construct($app, $interface_mode = false)
56     {
57         parent::__construct($app, 'APS installer: ', $interface_mode);
58     }
59     
60     /**
61      * Before the cron is executed, make sure all necessary options are set
62      * and all functions are available
63      */
64     private function checkRequirements()
65     {
bfcdef 66         global $app;
7d4360 67         try
T 68         {
69             // Check if exec() is not disabled
70             $disabled_func = explode(',', @ini_get('disable_functions'));
71             if(in_array('exec', $disabled_func)) throw new Exception('the call of exec() is disabled');
72             
73             // Check if safe_mode is disabled (needed for correct putenv, chmod, chown handling)
74             if(@ini_get('safe_mode')) throw new Exception('the safe_mode restriction is on');
75             
76             return true;
77         }
78         catch(Exception $e)
79         {
bfcdef 80             $app->log('Aborting execution because '.$e->getMessage(), 1);
7d4360 81             return false;
T 82         }
83     }
84     
85     /**
86      * Get a file from a ZIP archive and either return it's content or
87      * extract it to a given destination
88      * 
89      * @param $zipfile the ZIP file to work with
90      * @param $subfile the file from which to get the content
91      * @param $destfolder the optional extraction destination
92      * @param $destname the optional target file name when extracting
93      * @return string or boolean
94      */
95     private function getContentFromZIP($zipfile, $subfile, $destfolder = '', $destname = '')
96     {
97         try
98         {
99             $zip = new ZipArchive;
100             $res = $zip->open(realpath($zipfile));
101             if(!$res) throw new Exception('Cannot open ZIP file '.$zipfile);
102         
103             // If no destination is given, the content is returned, otherwise
104             // the $subfile is extracted to $destination
105             if($destfolder == '')
106             {
107                 $fh = $zip->getStream($subfile);
108                 if(!$fh) throw new Exception('Cannot read '.$subfile.' from '.$zipfile);
109                 
110                 $subfile_content = '';            
111                 while(!feof($fh)) $subfile_content .= fread($fh, 8192);
112                 
113                 fclose($fh);
114                 
115                 return $subfile_content;
116             }
117             else
118             {
119                 // extractTo would be suitable but has no target name parameter
120                 //$ind = $zip->locateName($subfile);
121                 //$ex = $zip->extractTo($destination, array($zip->getNameIndex($ind)));
122                 if($destname == '') $destname = basename($subfile);
123                 $ex = @copy('zip://'.$zipfile.'#'.$subfile, $destfolder.$destname);
124                 if(!$ex) throw new Exception('Cannot extract '.$subfile.' to '.$destfolder);
125             }
126             
127             $zip->close();
128             
129         }
130         catch(Exception $e)
131         {
132             // The exception message is only interesting for debugging reasons
133             // echo $e->getMessage();
134             return false;
135         }
136     }    
137     
138     /**
139      * Extract the complete directory of a ZIP file
140      * 
141      * @param $filename the file to unzip
142      * @param $directory the ZIP inside directory to unzip
143      * @param $destination the place where to extract the data
144      * @return boolean
145      */
146     private function extractZip($filename, $directory, $destination)
147     {
148         if(!file_exists($filename)) return false;
149         
150         // Fix the paths
151         if(substr($directory, -1) == '/') $directory = substr($directory, 0, strlen($directory) - 1);
152         if(substr($destination, -1) != '/') $destination .= '/';
153         
154         // Read and extract the ZIP file
155         $ziphandle = zip_open(realpath($filename));
156         if(is_resource($ziphandle))
157         {
158             while($entry = zip_read($ziphandle))
159             {
160                 if(substr(zip_entry_name($entry), 0, strlen($directory)) == $directory)
161                 {
162                     // Modify the relative ZIP file path
163                     $new_path = substr(zip_entry_name($entry), strlen($directory));
164                     
165                     if(substr($new_path, -1) == '/') // Identifier for directories
166                     {
167                         if(!file_exists($destination.$new_path)) mkdir($destination.$new_path, 0777, true);
168                     }
169                     else // Handle files
170                     {
171                         if(zip_entry_open($ziphandle, $entry))
172                         {
173                             $new_dir = dirname($destination.$new_path);
174                             if(!file_exists($new_dir)) mkdir($new_dir, 0777, true);
175                             
176                             $file = fopen($destination.$new_path, 'wb');
177                             if($file)
178                             {
179                                 while($line = zip_entry_read($entry)) fwrite($file, $line);
180                                 fclose($file);
181                             }
182                             else return false;
183                         }
184                     }
185                 }
186             }
187             
188             zip_close($ziphandle);
189             return true;
190         }
191         
192         return false;
193     }
194
195     /**
196      * Setup the path environment variables for the install script
197      * 
198      * @param $parent_mapping the SimpleXML instance with the current mapping position
199      * @param $url the relative path within the mapping tree
200      * @param $path the absolute path within the mapping tree
201      */
202     private function processMappings($parent_mapping, $url, $path)
203     {
204         if($parent_mapping && $parent_mapping != null)
205         {
206             $writable = parent::getXPathValue($parent_mapping, 'php:permissions/@writable');
207             $readable = parent::getXPathValue($parent_mapping, 'php:permissions/@readable');
208
209             // set the write permission            
210             if($writable == 'true')
211             {
212                 if(is_dir($path)) chmod($path, 0775);
213                 else chmod($path, 0664);
214             }
215             
216             // set non-readable permission
217             if($readable == 'false')
218             {
219                 if(is_dir($path)) chmod($path, 0333);
220                 else chmod($path, 0222);
221             }
222         }
223         
224         // Set the environment variables
225         $env = str_replace('/', '_', $url);
226         $this->putenv[] = 'WEB_'.$env.'_DIR='.$path;
227         
228         // Step recursively into further mappings
229         if($parent_mapping && $parent_mapping != null)
230         {
231             foreach($parent_mapping->mapping as $mapping)
232             {
233                 if($url == '/') $this->processMappings($mapping, $url.$mapping['url'], $path.$mapping['url']);
234                 else $this->processMappings($mapping, $url.'/'.$mapping['url'], $path.'/'.$mapping['url']);
235             }
236         }
237     }    
238     
239     /**
240      * Setup the environment with data for the install location
241      * 
242      * @param $task an array containing all install related data
243      */
244     private function prepareLocation($task)
245     {
bfcdef 246         global $app;
T 247         
7d4360 248         // Get the domain name to use for the installation
T 249         // Would be possible in one query too, but we use 2 for easier debugging
bfcdef 250         $main_domain = $app->db->queryOneRecord("SELECT value FROM aps_instances_settings  
T 251             WHERE name = 'main_domain' AND instance_id = '".$app->db->quote($task['instance_id'])."';");
7d4360 252         $this->domain = $main_domain['value'];
T 253         
254         // Get the document root
bfcdef 255         $domain_res = $app->db->queryOneRecord("SELECT document_root, web_folder, type FROM web_domain 
T 256             WHERE domain = '".$app->db->quote($this->domain)."';");
7d4360 257         $this->document_root = $domain_res['document_root'];
T 258         
259         // Get the sub location
bfcdef 260         $location_res = $app->db->queryOneRecord("SELECT value FROM aps_instances_settings 
T 261             WHERE name = 'main_location' AND instance_id = '".$app->db->quote($task['instance_id'])."';");
7d4360 262         $this->sublocation = $location_res['value'];
T 263         
264         // Make sure the document_root ends with /
265         if(substr($this->document_root, -1) != '/') $this->document_root .= '/';
266         
267         // Attention: ISPConfig Special: web files are in subfolder 'web' -> append it:
bfcdef 268         if($domain_res['type'] == 'vhostsubdomain') $this->document_root .= $domain_res['web_folder'] . '/';
T 269         else $this->document_root .= 'web/';
7d4360 270
T 271         // If a subfolder is given, make sure it's path doesn't begin with / i.e. /phpbb
272         if(substr($this->sublocation, 0, 1) == '/') $this->sublocation = substr($this->sublocation, 1);
273                 
274         // If the package isn't installed to a subfolder, remove the / at the end of the document root
275         if(empty($this->sublocation)) $this->document_root = substr($this->document_root, 0, strlen($this->document_root) - 1);
276         
277         // Set environment variables, later processed by the package install script
278         $this->putenv[] = 'BASE_URL_SCHEME=http';
279         // putenv('BASE_URL_PORT') -> omitted as it's 80 by default
280         $this->putenv[] = 'BASE_URL_HOST='.$this->domain;
281         $this->putenv[] = 'BASE_URL_PATH='.$this->sublocation.'/';
282     }    
283     
284     /**
285      * Setup a database (if needed) and the appropriate environment variables
286      * 
287      * @param $task an array containing all install related data
288      * @param $sxe a SimpleXMLElement handle, holding APP-META.xml
289      */
290     private function prepareDatabase($task, $sxe)
291     {
bfcdef 292         global $app;
T 293         
7d4360 294         $db_id = parent::getXPathValue($sxe, '//db:id');
T 295         if(empty($db_id)) return; // No database needed
296         
10b4c8 297         /* WARNING: if this will ever be uncommented please check the updated prefix handling for user and db names!!!
T 298          * 
7d4360 299         // Set the database owner to the domain owner
T 300         // ISPConfig identifies the owner by the sys_groupid (not sys_userid!)
301         // so sys_userid can be set to any value
bfcdef 302         $perm = $app->db->queryOneRecord("SELECT sys_groupid, server_id FROM web_domain 
7d4360 303             WHERE domain = '".$this->domain."';");
T 304         $task['sys_groupid'] = $perm['sys_groupid'];
305         $serverid = $perm['server_id'];
306                 
307         // Get the database prefix and db user prefix 
bfcdef 308         $app->uses('getconf');
T 309         $global_config = $app->getconf->get_global_config('sites');
7d4360 310         $dbname_prefix = str_replace('[CLIENTID]', '', $global_config['dbname_prefix']);
T 311         $dbuser_prefix = str_replace('[CLIENTID]', '', $global_config['dbuser_prefix']);
312         $this->dbhost = DB_HOST; // Taken from config.inc.php
313         if(empty($this->dbhost)) $this->dbhost = 'localhost'; // Just to ensure any hostname... ;)
314         
315         $this->newdb_name = $dbname_prefix.$task['CustomerID'].'aps'.$task['InstanceID'];
316         $this->newdb_user = $dbuser_prefix.$task['CustomerID'].'aps'.$task['InstanceID'];
bfcdef 317         $dbpw_res = $app->db->queryOneRecord("SELECT Value FROM aps_instances_settings  
T 318             WHERE Name = 'main_database_password' AND InstanceID = '".$app->db->quote($task['InstanceID'])."';");
7d4360 319         $newdb_pw = $dbpw_res['Value'];
T 320  
321         // In any case delete an existing database (install and removal procedure)
bfcdef 322         $app->db->query('DROP DATABASE IF EXISTS `'.$app->db->quote($this->newdb_name).'`;');
7d4360 323         // Delete an already existing database with this name
bfcdef 324         $app->db->query("DELETE FROM web_database WHERE database_name = '".$app->db->quote($this->newdb_name)."';");
7d4360 325         
T 326         
327         // Create the new database and assign it to a user
328         if($this->handle_type == 'install')
329         {
bfcdef 330             $app->db->query('CREATE DATABASE IF NOT EXISTS `'.$app->db->quote($this->newdb_name).'`;');
T 331             $app->db->query('GRANT ALL PRIVILEGES ON '.$app->db->quote($this->newdb_name).'.* TO '.$app->db->quote($this->newdb_user).'@'.$app->db->quote($this->dbhost).' IDENTIFIED BY \'password\';');
332             $app->db->query('SET PASSWORD FOR '.$app->db->quote($this->newdb_user).'@'.$app->db->quote($this->dbhost).' = PASSWORD(\''.$newdb_pw.'\');');
333             $app->db->query('FLUSH PRIVILEGES;');
7d4360 334         
T 335             // Add the new database to the customer databases
336             // Assumes: charset = utf8
bfcdef 337             $app->db->query('INSERT INTO web_database (sys_userid, sys_groupid, sys_perm_user, sys_perm_group, sys_perm_other, server_id, 
7d4360 338                 type, database_name, database_user, database_password, database_charset, remote_access, remote_ips, active) 
T 339                 VALUES ('.$task['sys_userid'].', '.$task['sys_groupid'].', "'.$task['sys_perm_user'].'", "'.$task['sys_perm_group'].'", 
bfcdef 340                 "'.$task['sys_perm_other'].'", '.$app->db->quote($serverid).', "mysql", "'.$app->db->quote($this->newdb_name).'", 
T 341                 "'.$app->db->quote($this->newdb_user).'", "'.$app->db->quote($newdb_pw).'", "utf8", "n", "", "y");');
7d4360 342         }
T 343         */
344         
bfcdef 345         $mysqlver_res = $app->db->queryOneRecord('SELECT VERSION() as ver;');
7d4360 346         $mysqlver = $mysqlver_res['ver'];
T 347         
bfcdef 348         $tmp = $app->db->queryOneRecord("SELECT value FROM aps_instances_settings WHERE name = 'main_database_password' AND instance_id = '".$app->db->quote($task['instance_id'])."';");
7d4360 349         $newdb_pw = $tmp['value'];
T 350         
bfcdef 351         $tmp = $app->db->queryOneRecord("SELECT value FROM aps_instances_settings WHERE name = 'main_database_host' AND instance_id = '".$app->db->quote($task['instance_id'])."';");
7d4360 352         $newdb_host = $tmp['value'];
T 353         
bfcdef 354         $tmp = $app->db->queryOneRecord("SELECT value FROM aps_instances_settings WHERE name = 'main_database_name' AND instance_id = '".$app->db->quote($task['instance_id'])."';");
7d4360 355         $newdb_name = $tmp['value'];
T 356         
bfcdef 357         $tmp = $app->db->queryOneRecord("SELECT value FROM aps_instances_settings WHERE name = 'main_database_login' AND instance_id = '".$app->db->quote($task['instance_id'])."';");
7d4360 358         $newdb_login = $tmp['value'];
T 359         
360         $this->putenv[] = 'DB_'.$db_id.'_TYPE=mysql';
361         $this->putenv[] = 'DB_'.$db_id.'_NAME='.$newdb_name;
362         $this->putenv[] = 'DB_'.$db_id.'_LOGIN='.$newdb_login;
363         $this->putenv[] = 'DB_'.$db_id.'_PASSWORD='.$newdb_pw;
364         $this->putenv[] = 'DB_'.$db_id.'_HOST='.$newdb_host;
365         $this->putenv[] = 'DB_'.$db_id.'_PORT=3306';
366         $this->putenv[] = 'DB_'.$db_id.'_VERSION='.$mysqlver;
367     }    
368     
369     /**
370      * Extract all needed files from the package
371      * 
372      * @param $task an array containing all install related data
373      * @param $sxe a SimpleXMLElement handle, holding APP-META.xml
374      * @return boolean
375      */
376     private function prepareFiles($task, $sxe)
377     {
bfcdef 378         global $app;
T 379         
7d4360 380         // Basically set the mapping for APS version 1.0, if not available -> newer way
T 381         $mapping = $sxe->mapping;
382         $mapping_path = $sxe->mapping['path'];
383         $mapping_url = $sxe->mapping['url'];
384         if(empty($mapping))
385         {
386             $mapping = $sxe->service->provision->{'url-mapping'}->mapping;
387             $mapping_path = $sxe->service->provision->{'url-mapping'}->mapping['path'];
388             $mapping_url = $sxe->service->provision->{'url-mapping'}->mapping['url'];
389         }
390
391         try
392         {
393             // Make sure we have a valid mapping path (at least /)
394             if(empty($mapping_path)) throw new Exception('Unable to determine a mapping path');
395             
396             $this->local_installpath = $this->document_root.$this->sublocation.'/';
397
398             // Now delete an existing folder (affects install and removal in the same way)
399             @chdir($this->local_installpath);
526b99 400             if(file_exists($this->local_installpath)){
T 401                 // make sure we don't delete error and stats folders
402                 if($this->local_installpath == $this->document_root.'/'){
403                     if(is_dir($this->document_root)){
404                         $files = array_diff(scandir($this->document_root), array('.','..','error','stats'));
405                         foreach($files as $file){
406                             if(is_dir($this->document_root.'/'.$file)){
407                                 $app->file->removeDirectory($this->document_root.'/'.$file);
408                             } else {
409                                 @unlink($this->document_root.'/'.$file);
410                             }
411                         }
412                     } else {
413                         @unlink($this->document_root);
414                         mkdir($this->document_root, 0777, true);
415                     }
416                 } else {
417                     exec("rm -Rf ".escapeshellarg($this->local_installpath).'*');
418                 }
419             } else {
420                 mkdir($this->local_installpath, 0777, true);
421             }
7d4360 422
T 423             if($this->handle_type == 'install')
424             {            
425                 // Now check if the needed folder is there
426                 if(!file_exists($this->local_installpath))
427                     throw new Exception('Unable to create a new folder for the package '.$task['path']);
428                 
429                 // Extract all files and assign them a new owner
430                 if( ($this->extractZip($this->packages_dir.'/'.$task['path'], $mapping_path, $this->local_installpath) === false)
431                  || ($this->extractZip($this->packages_dir.'/'.$task['path'], 'scripts', $this->local_installpath.'install_scripts/') === false) )
432                 {
433                     // Clean already extracted data
434                     exec("rm -Rf ".escapeshellarg($this->local_installpath).'*');
435                     throw new Exception('Unable to extract the package '.$task['path']);              
436                 }
437                 
438                 $this->processMappings($mapping, $mapping_url, $this->local_installpath);
439             
440                 // Set the appropriate file owner
bfcdef 441                 $main_domain = $app->db->queryOneRecord("SELECT value FROM aps_instances_settings  
T 442                     WHERE name = 'main_domain' AND instance_id = '".$app->db->quote($task['instance_id'])."';");        
443                 $owner_res = $app->db->queryOneRecord("SELECT system_user, system_group FROM web_domain  
444                         WHERE domain = '".$app->db->quote($main_domain['value'])."';");
7d4360 445                 $this->file_owner_user = $owner_res['system_user']; 
T 446                 $this->file_owner_group = $owner_res['system_group'];
447                 exec('chown -R '.$this->file_owner_user.':'.$this->file_owner_group.' '.escapeshellarg($this->local_installpath));
448             }
449         }
450         catch(Exception $e)
451         {
bfcdef 452             $app->dbmaster->query('UPDATE aps_instances SET instance_status = "'.INSTANCE_ERROR.'" 
T 453                 WHERE id = "'.$app->db->quote($task['instance_id']).'";');
454             $app->log($e->getMessage(), 1);
7d4360 455             return false;
T 456         }
457         
458         return true;
459     }
460     
461     /**
462      * Get all user config variables and set them to environment variables
463      * 
464      * @param $task an array containing all install related data
465      */    
466     private function prepareUserInputData($task)
467     {
bfcdef 468         global $app;
T 469         
470         $userdata = $app->db->queryAllRecords("SELECT name, value FROM aps_instances_settings 
471             WHERE instance_id = '".$app->db->quote($task['instance_id'])."';");
7d4360 472         if(empty($userdata)) return false;
T 473         
474         foreach($userdata as $data)
475         {
476             // Skip unnecessary data
477             if($data['name'] == 'main_location'
478             || $data['name'] == 'main_domain'
479             || $data['name'] == 'main_database_password'
480             || $data['name'] == 'main_database_name'
481             || $data['name'] == 'main_database_host'
482             || $data['name'] == 'main_database_login'
483             || $data['name'] == 'license') continue;
484             
485             $this->putenv[] = 'SETTINGS_'.$data['name'].'='.$data['value'];
486         }
487     }
488     
489     /**
490      * Fetch binary data from a given array
491      * The data is retrieved in binary mode and 
492      * then directly written to an output file
493      *
494      * @param $input a specially structed array
495      * @see $this->startUpdate()
496      */
497     private function fetchFiles($input)
498     {
499         $fh = array();
500         $url = array();
501         $conn = array();
502
503         // Build the single cURL handles and add them to a multi handle
504         $mh = curl_multi_init();
505         
506         // Process each app 
507         for($i = 0; $i < count($input); $i++)
508         {
509             $conn[$i] = curl_init($input[$i]['url']);
510             $fh[$i] = fopen($input[$i]['localtarget'], 'wb'); 
511             
512             curl_setopt($conn[$i], CURLOPT_BINARYTRANSFER, true);
513             curl_setopt($conn[$i], CURLOPT_FILE, $fh[$i]);
514             curl_setopt($conn[$i], CURLOPT_TIMEOUT, 0);
515             curl_setopt($conn[$i], CURLOPT_FAILONERROR, 1);
516             curl_setopt($conn[$i], CURLOPT_FOLLOWLOCATION, 1); 
517             
518             curl_multi_add_handle($mh, $conn[$i]);
519         }
520         
521         $active = 0;
522         do curl_multi_exec($mh, $active);
523         while($active > 0);
524
525         // Close the handles
526         for($i = 0; $i < count($input); $i++)
527         {
528             fclose($fh[$i]);
529             curl_multi_remove_handle($mh, $conn[$i]);
530             curl_close($conn[$i]);
531         }
532         curl_multi_close($mh);
533     }
534     
535     /**
536      * The installation script should be executed
537      * 
538      * @param $task an array containing all install related data
539      * @param $sxe a SimpleXMLElement handle, holding APP-META.xml
540      * @return boolean
541      */
542     private function doInstallation($task, $sxe)
543     {
bfcdef 544         global $app;
T 545         
7d4360 546         try
T 547         {
548             // Check if the install directory exists
549             if(!is_dir($this->local_installpath.'install_scripts/'))
550                 throw new Exception('The install directory '.$this->local_installpath.' is not existing');
551             
552             // Set the executable bit to the configure script
553             $cfgscript = @(string)$sxe->service->provision->{'configuration-script'}['name'];
554             if(!$cfgscript) $cfgscript = 'configure';
555             chmod($this->local_installpath.'install_scripts/'.$cfgscript, 0755);
556             
557             // Change to the install folder (import for the exec() below!)
558             //exec('chown -R '.$this->file_owner_user.':'.$this->file_owner_group.' '.escapeshellarg($this->local_installpath));
559             chdir($this->local_installpath.'install_scripts/');
560             
561             // Set the enviroment variables
562             foreach($this->putenv as $var) {
563                 putenv($var);
564             }
565             
566             $shell_retcode = true;
567             $shell_ret = array();
568              exec('php '.escapeshellarg($this->local_installpath.'install_scripts/'.$cfgscript).' install 2>&1', $shell_ret, $shell_retcode);
569             $shell_ret = array_filter($shell_ret);
570             $shell_ret_str = implode("\n", $shell_ret);
571             
572             // Although $shell_retcode might be 0, there can be PHP errors. Filter them:
573             if(substr_count($shell_ret_str, 'Warning: ') > 0) $shell_retcode = 1;
574             
575             // If an error has occurred, the return code is != 0 
576             if($shell_retcode != 0) throw new Exception($shell_ret_str);
577             else
578             {
579                 // The install succeeded, chown newly created files too
580                 exec('chown -R '.$this->file_owner_user.':'.$this->file_owner_group.' '.escapeshellarg($this->local_installpath));
581                 
bfcdef 582                 $app->dbmaster->query('UPDATE aps_instances SET instance_status = "'.INSTANCE_SUCCESS.'" 
T 583                     WHERE id = "'.$app->db->quote($task['instance_id']).'";');
7d4360 584             }
T 585         }
586         catch(Exception $e)
587         {
bfcdef 588             $app->dbmaster->query('UPDATE aps_instances SET instance_status = "'.INSTANCE_ERROR.'" 
T 589                 WHERE id = "'.$app->db->quote($task['instance_id']).'";');
590             $app->log($e->getMessage(), 1);
7d4360 591             return false;
T 592         }
593         
594         return true;
595     }
596     
597     /**
598      * Cleanup: Remove install scripts, remove tasks and update the database
599      * 
600      * @param $task an array containing all install related data
601      * @param $sxe a SimpleXMLElement handle, holding APP-META.xml
602      */
603     private function cleanup($task, $sxe)
604     {
605         chdir($this->local_installpath);
606         exec("rm -Rf ".escapeshellarg($this->local_installpath).'install_scripts');
607     }    
608     
609     /**
610      * The main method which performs the actual package installation
611      * 
612      * @param $instanceid the instanceID to install
613      * @param $type the type of task to perform (installation, removal)
614      */
615     public function installHandler($instanceid, $type)
616     {
bfcdef 617         global $app;
T 618         
7d4360 619         // Set the given handle type, currently supported: install, delete
T 620         if($type == 'install' || $type == 'delete') $this->handle_type = $type;
621         else return false;
622         
623         // Get all instance metadata
84b8c1 624         /*
bfcdef 625         $task = $app->db->queryOneRecord("SELECT * FROM aps_instances AS i 
7d4360 626             INNER JOIN aps_packages AS p ON i.package_id = p.id 
T 627             INNER JOIN client AS c ON i.customer_id = c.client_id
628             WHERE i.id = ".$instanceid.";");
84b8c1 629         */
bfcdef 630         $task = $app->db->queryOneRecord("SELECT * FROM aps_instances AS i 
84b8c1 631             INNER JOIN aps_packages AS p ON i.package_id = p.id
T 632             WHERE i.id = ".$instanceid.";");
7d4360 633         if(!$task) return false;  // formerly: throw new Exception('The InstanceID doesn\'t exist.');
T 634         if(!isset($task['instance_id'])) $task['instance_id'] = $instanceid;
635         
636         // Download aps package
526b99 637         if(!file_exists($this->packages_dir.'/'.$task['path']) || filesize($this->packages_dir.'/'.$task['path']) == 0) {
7d4360 638             $ch = curl_init();
T 639             $fh = fopen($this->packages_dir.'/'.$task['path'], 'wb');
640             curl_setopt($ch, CURLOPT_FILE, $fh); 
641             //curl_setopt($ch, CURLOPT_HEADER, 0); 
642             curl_setopt($ch, CURLOPT_URL, $task['package_url']);
643             curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
644             curl_setopt($ch, CURLOPT_TIMEOUT, 0);
645             curl_setopt($ch, CURLOPT_FAILONERROR, 1);
646             curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);     
bfcdef 647             if(curl_exec($ch) === false) $app->log(curl_error ($ch),1);
7d4360 648             fclose($fh);
T 649             curl_close($ch);
650         }
651         
652         /*
653         $app_to_dl[] = array('name' => $task['path'], 
654                             'url' => $task['package_url'], 
655                             'filesize' => 0, 
656                             'localtarget' => $this->packages_dir.'/'.$task['path']);
657
658         $this->fetchFiles($app_to_dl);
659         */
660         
661         // Make sure the requirements are given so that this script can execute
662         $req_ret = $this->checkRequirements();
663         if(!$req_ret) return false;
664         
665         $metafile = $this->getContentFromZIP($this->packages_dir.'/'.$task['path'], 'APP-META.xml');
666         // Check if the meta file is existing
667         if(!$metafile)
668         {
bfcdef 669             $app->dbmaster->query('UPDATE aps_instances SET instance_status = "'.INSTANCE_ERROR.'" 
T 670                 WHERE id = "'.$app->db->quote($task['instance_id']).'";');
671             $app->log('Unable to find the meta data file of package '.$task['path'], 1);
7d4360 672             return false;
T 673         }
674         
675         // Rename namespaces and register them 
676         $metadata = str_replace("xmlns=", "ns=", $metafile);
677         $sxe = new SimpleXMLElement($metadata);
678         $namespaces = $sxe->getDocNamespaces(true);
679         foreach($namespaces as $ns => $url) $sxe->registerXPathNamespace($ns, $url); 
680
681         // Setup the environment with data for the install location
682         $this->prepareLocation($task);
683         
684         // Create the database if necessary
685         $this->prepareDatabase($task, $sxe);
686         
687         // Unpack the install scripts from the packages
688         if($this->prepareFiles($task, $sxe) && $this->handle_type == 'install')
689         {
690             // Setup the variables from the install script
691             $this->prepareUserInputData($task);
692         
693             // Do the actual installation
694             $this->doInstallation($task, $sxe);
695             
696             // Remove temporary files
697             $this->cleanup($task, $sxe);
698         }
699         
700         // Finally delete the instance entry + settings
701         if($this->handle_type == 'delete')
702         {
bfcdef 703             $app->db->query('DELETE FROM aps_instances WHERE id = "'.$app->db->quote($task['instance_id']).'";');
T 704             $app->db->query('DELETE FROM aps_instances_settings WHERE instance_id = "'.$app->db->quote($task['instance_id']).'";');
7d4360 705         }
T 706         
707         unset($sxe);
708     }
709 }
710 ?>