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