Marius Cramer
2015-08-06 37b29231e47a0c4458dc1c15d98588f16f07e1e2
commit | author | age
bbc657 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 */
b1a6a5 30 require_once 'aps_base.inc.php';
bbc657 31
MC 32 @set_time_limit(0);
33 @ignore_user_abort(1);
34
35 class ApsInstaller extends ApsBase
36 {
b1a6a5 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 = '';
bbc657 47     private $putenv = array();
MC 48
b1a6a5 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      */
bbc657 55
b1a6a5 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
cc7a82 262         $main_domain = $app->db->queryOneRecord("SELECT value FROM aps_instances_settings WHERE name = 'main_domain' AND instance_id = ?", $task['instance_id']);
b1a6a5 263         $this->domain = $main_domain['value'];
bbc657 264
b1a6a5 265         // Get the document root
cc7a82 266         $domain_res = $app->db->queryOneRecord("SELECT document_root, web_folder, type FROM web_domain WHERE domain = ?", $this->domain);
b1a6a5 267         $this->document_root = $domain_res['document_root'];
MC 268
269         // Get the sub location
cc7a82 270         $location_res = $app->db->queryOneRecord("SELECT value FROM aps_instances_settings WHERE name = 'main_location' AND instance_id = ?", $task['instance_id']);
b1a6a5 271         $this->sublocation = $location_res['value'];
MC 272
273         // Make sure the document_root ends with /
274         if(substr($this->document_root, -1) != '/') $this->document_root .= '/';
275
276         // Attention: ISPConfig Special: web files are in subfolder 'web' -> append it:
511ba5 277         if(($domain_res['type'] == 'vhostsubdomain') || ($domain_res['type'] == 'vhostalias'))
DM 278             $this->document_root .= $domain_res['web_folder'] . '/';
b1a6a5 279         else $this->document_root .= 'web/';
MC 280
281         // If a subfolder is given, make sure it's path doesn't begin with / i.e. /phpbb
282         if(substr($this->sublocation, 0, 1) == '/') $this->sublocation = substr($this->sublocation, 1);
283
284         // If the package isn't installed to a subfolder, remove the / at the end of the document root
285         if(empty($this->sublocation)) $this->document_root = substr($this->document_root, 0, strlen($this->document_root) - 1);
286
287         // Set environment variables, later processed by the package install script
288         $this->putenv[] = 'BASE_URL_SCHEME=http';
289         // putenv('BASE_URL_PORT') -> omitted as it's 80 by default
290         $this->putenv[] = 'BASE_URL_HOST='.$this->domain;
291         $this->putenv[] = 'BASE_URL_PATH='.$this->sublocation.'/';
292     }
293
294
295
296     /**
297      * Setup a database (if needed) and the appropriate environment variables
298      *
299      * @param $task an array containing all install related data
300      * @param $sxe a SimpleXMLElement handle, holding APP-META.xml
301      */
302     private function prepareDatabase($task, $sxe)
303     {
304         global $app;
305
306         $db_id = parent::getXPathValue($sxe, '//db:id');
307         if(empty($db_id)) return; // No database needed
308
309         $mysqlver_res = $app->db->queryOneRecord('SELECT VERSION() as ver;');
310         $mysqlver = $mysqlver_res['ver'];
311
cc7a82 312         $tmp = $app->db->queryOneRecord("SELECT value FROM aps_instances_settings WHERE name = 'main_database_password' AND instance_id = ?", $task['instance_id']);
b1a6a5 313         $newdb_pw = $tmp['value'];
MC 314
cc7a82 315         $tmp = $app->db->queryOneRecord("SELECT value FROM aps_instances_settings WHERE name = 'main_database_host' AND instance_id = ?", $task['instance_id']);
b1a6a5 316         $newdb_host = $tmp['value'];
MC 317
cc7a82 318         $tmp = $app->db->queryOneRecord("SELECT value FROM aps_instances_settings WHERE name = 'main_database_name' AND instance_id = ?", $task['instance_id']);
b1a6a5 319         $newdb_name = $tmp['value'];
MC 320
cc7a82 321         $tmp = $app->db->queryOneRecord("SELECT value FROM aps_instances_settings WHERE name = 'main_database_login' AND instance_id = ?", $task['instance_id']);
b1a6a5 322         $newdb_login = $tmp['value'];
9938f6 323         
TB 324         /* Test if the new mysql connection is laready working to ensure that db servers in multiserver
325            setups get enough time to create the database */
99ca0f 326         if($this->handle_type == 'install') {
TB 327             for($n = 1; $n < 15; $n++) {
6bd166 328                 $link = mysqli_connect($newdb_host, $newdb_login, $newdb_pw);
99ca0f 329                 if (!$link) {
TB 330                     unset($link);
331                     sleep(5);
332                 } else {
333                     unset($link);
334                     break;
335                 }
9938f6 336             }
TB 337         }
bbc657 338
b1a6a5 339         $this->putenv[] = 'DB_'.$db_id.'_TYPE=mysql';
MC 340         $this->putenv[] = 'DB_'.$db_id.'_NAME='.$newdb_name;
341         $this->putenv[] = 'DB_'.$db_id.'_LOGIN='.$newdb_login;
342         $this->putenv[] = 'DB_'.$db_id.'_PASSWORD='.$newdb_pw;
343         $this->putenv[] = 'DB_'.$db_id.'_HOST='.$newdb_host;
344         $this->putenv[] = 'DB_'.$db_id.'_PORT=3306';
345         $this->putenv[] = 'DB_'.$db_id.'_VERSION='.$mysqlver;
346     }
bbc657 347
b1a6a5 348
MC 349
350     /**
351      * Extract all needed files from the package
352      *
353      * @param $task an array containing all install related data
354      * @param $sxe a SimpleXMLElement handle, holding APP-META.xml
355      * @return boolean
356      */
357     private function prepareFiles($task, $sxe)
358     {
359         global $app;
360
361         // Basically set the mapping for APS version 1.0, if not available -> newer way
362         $mapping = $sxe->mapping;
363         $mapping_path = $sxe->mapping['path'];
364         $mapping_url = $sxe->mapping['url'];
365         if(empty($mapping))
366         {
367             $mapping = $sxe->service->provision->{'url-mapping'}->mapping;
368             $mapping_path = $sxe->service->provision->{'url-mapping'}->mapping['path'];
369             $mapping_url = $sxe->service->provision->{'url-mapping'}->mapping['url'];
370         }
371
372         try
373         {
374             // Make sure we have a valid mapping path (at least /)
375             if(empty($mapping_path)) throw new Exception('Unable to determine a mapping path');
376
377             $this->local_installpath = $this->document_root.$this->sublocation.'/';
378
379             // Now delete an existing folder (affects install and removal in the same way)
380             @chdir($this->local_installpath);
381             if(file_exists($this->local_installpath)){
bbc657 382                 // make sure we don't delete error and stats folders
MC 383                 if($this->local_installpath == $this->document_root.'/'){
384                     if(is_dir($this->document_root)){
b1a6a5 385                         $files = array_diff(scandir($this->document_root), array('.', '..', 'error', 'stats'));
bbc657 386                         foreach($files as $file){
MC 387                             if(is_dir($this->document_root.'/'.$file)){
388                                 $app->file->removeDirectory($this->document_root.'/'.$file);
389                             } else {
390                                 @unlink($this->document_root.'/'.$file);
391                             }
392                         }
393                     } else {
394                         @unlink($this->document_root);
395                         mkdir($this->document_root, 0777, true);
396                     }
397                 } else {
398                     exec("rm -Rf ".escapeshellarg($this->local_installpath).'*');
399                 }
400             } else {
401                 mkdir($this->local_installpath, 0777, true);
402             }
403
b1a6a5 404             if($this->handle_type == 'install')
MC 405             {
406                 // Now check if the needed folder is there
407                 if(!file_exists($this->local_installpath))
408                     throw new Exception('Unable to create a new folder for the package '.$task['path']);
409
410                 // Extract all files and assign them a new owner
411                 if( ($this->extractZip($this->packages_dir.'/'.$task['path'], $mapping_path, $this->local_installpath) === false)
412                     || ($this->extractZip($this->packages_dir.'/'.$task['path'], 'scripts', $this->local_installpath.'install_scripts/') === false) )
413                 {
414                     // Clean already extracted data
415                     exec("rm -Rf ".escapeshellarg($this->local_installpath).'*');
416                     throw new Exception('Unable to extract the package '.$task['path']);
417                 }
418
419                 $this->processMappings($mapping, $mapping_url, $this->local_installpath);
420
421                 // Set the appropriate file owner
cc7a82 422                 $main_domain = $app->db->queryOneRecord("SELECT value FROM aps_instances_settings WHERE name = 'main_domain' AND instance_id = ?", $task['instance_id']);
MC 423                 $owner_res = $app->db->queryOneRecord("SELECT system_user, system_group FROM web_domain WHERE domain = ?", $main_domain['value']);
b1a6a5 424                 $this->file_owner_user = $owner_res['system_user'];
MC 425                 $this->file_owner_group = $owner_res['system_group'];
426                 exec('chown -R '.$this->file_owner_user.':'.$this->file_owner_group.' '.escapeshellarg($this->local_installpath));
427
146783 428                 //* Chown stats directory back
MC 429                 if(is_dir($this->local_installpath.'stats')) {
430                     exec('chown -R root:root '.escapeshellarg($this->local_installpath.'stats'));
431                 }
b1a6a5 432             }
MC 433         }
434         catch(Exception $e)
435         {
cc7a82 436             $app->dbmaster->query('UPDATE aps_instances SET instance_status = ? WHERE id = ?', INSTANCE_ERROR, $task['instance_id']);
b1a6a5 437             $app->log($e->getMessage(), 1);
MC 438             return false;
439         }
440
441         return true;
442     }
443
444
445
bbc657 446     /**
b1a6a5 447      * Get all user config variables and set them to environment variables
MC 448      *
449      * @param $task an array containing all install related data
450      */
451     private function prepareUserInputData($task)
452     {
453         global $app;
bbc657 454
cc7a82 455         $userdata = $app->db->queryAllRecords("SELECT name, value FROM aps_instances_settings WHERE instance_id = ?", $task['instance_id']);
b1a6a5 456         if(empty($userdata)) return false;
bbc657 457
b1a6a5 458         foreach($userdata as $data)
MC 459         {
460             // Skip unnecessary data
461             if($data['name'] == 'main_location'
462                 || $data['name'] == 'main_domain'
463                 || $data['name'] == 'main_database_password'
464                 || $data['name'] == 'main_database_name'
465                 || $data['name'] == 'main_database_host'
466                 || $data['name'] == 'main_database_login'
467                 || $data['name'] == 'license') continue;
468
469             $this->putenv[] = 'SETTINGS_'.$data['name'].'='.$data['value'];
470         }
471     }
472
473
474
475     /**
476      * Fetch binary data from a given array
477      * The data is retrieved in binary mode and
478      * then directly written to an output file
479      *
480      * @param $input a specially structed array
481      * @see $this->startUpdate()
482      */
483     private function fetchFiles($input)
484     {
485         $fh = array();
486         $url = array();
487         $conn = array();
488
489         // Build the single cURL handles and add them to a multi handle
490         $mh = curl_multi_init();
491
492         // Process each app
493         for($i = 0; $i < count($input); $i++)
494         {
495             $conn[$i] = curl_init($input[$i]['url']);
496             $fh[$i] = fopen($input[$i]['localtarget'], 'wb');
497
498             curl_setopt($conn[$i], CURLOPT_BINARYTRANSFER, true);
499             curl_setopt($conn[$i], CURLOPT_FILE, $fh[$i]);
500             curl_setopt($conn[$i], CURLOPT_TIMEOUT, 0);
501             curl_setopt($conn[$i], CURLOPT_FAILONERROR, 1);
502             curl_setopt($conn[$i], CURLOPT_FOLLOWLOCATION, 1);
27978b 503             curl_setopt($conn[$i], CURLOPT_SSL_VERIFYPEER, 0);
b1a6a5 504
MC 505             curl_multi_add_handle($mh, $conn[$i]);
506         }
507
508         $active = 0;
509         do curl_multi_exec($mh, $active);
510         while($active > 0);
511
512         // Close the handles
513         for($i = 0; $i < count($input); $i++)
514         {
515             fclose($fh[$i]);
516             curl_multi_remove_handle($mh, $conn[$i]);
517             curl_close($conn[$i]);
518         }
519         curl_multi_close($mh);
520     }
521
522
523
524     /**
525      * The installation script should be executed
526      *
527      * @param $task an array containing all install related data
528      * @param $sxe a SimpleXMLElement handle, holding APP-META.xml
529      * @return boolean
530      */
531     private function doInstallation($task, $sxe)
532     {
533         global $app;
534
535         try
536         {
537             // Check if the install directory exists
538             if(!is_dir($this->local_installpath.'install_scripts/'))
539                 throw new Exception('The install directory '.$this->local_installpath.' is not existing');
540
541             // Set the executable bit to the configure script
542             $cfgscript = @(string)$sxe->service->provision->{'configuration-script'}['name'];
543             if(!$cfgscript) $cfgscript = 'configure';
544             chmod($this->local_installpath.'install_scripts/'.$cfgscript, 0755);
545
546             // Change to the install folder (import for the exec() below!)
bbc657 547             //exec('chown -R '.$this->file_owner_user.':'.$this->file_owner_group.' '.escapeshellarg($this->local_installpath));
b1a6a5 548             chdir($this->local_installpath.'install_scripts/');
MC 549
bbc657 550             // Set the enviroment variables
MC 551             foreach($this->putenv as $var) {
552                 putenv($var);
553             }
b1a6a5 554
MC 555             $shell_retcode = true;
556             $shell_ret = array();
557             exec('php '.escapeshellarg($this->local_installpath.'install_scripts/'.$cfgscript).' install 2>&1', $shell_ret, $shell_retcode);
558             $shell_ret = array_filter($shell_ret);
559             $shell_ret_str = implode("\n", $shell_ret);
560
bbc657 561             // Although $shell_retcode might be 0, there can be PHP errors. Filter them:
b1a6a5 562             if(substr_count($shell_ret_str, 'Warning: ') > 0) $shell_retcode = 1;
MC 563
564             // If an error has occurred, the return code is != 0
565             if($shell_retcode != 0) throw new Exception($shell_ret_str);
566             else
567             {
568                 // The install succeeded, chown newly created files too
569                 exec('chown -R '.$this->file_owner_user.':'.$this->file_owner_group.' '.escapeshellarg($this->local_installpath));
570
bbc657 571                 //* Chown stats directory back
MC 572                 if(is_dir($this->local_installpath.'stats')) {
573                     exec('chown -R root:root '.escapeshellarg($this->local_installpath.'stats'));
574                 }
b1a6a5 575
cc7a82 576                 $app->dbmaster->query('UPDATE aps_instances SET instance_status = ? WHERE id = ?', INSTANCE_SUCCESS, $task['instance_id']);
b1a6a5 577             }
MC 578         }
579
580         catch(Exception $e)
581         {
cc7a82 582             $app->dbmaster->query('UPDATE aps_instances SET instance_status = ? WHERE id = ?', INSTANCE_ERROR, $task['instance_id']);
b1a6a5 583             $app->log($e->getMessage(), 1);
MC 584             return false;
585         }
586
587         return true;
588     }
589
590
591
592     /**
593      * Cleanup: Remove install scripts, remove tasks and update the database
594      *
595      * @param $task an array containing all install related data
596      * @param $sxe a SimpleXMLElement handle, holding APP-META.xml
597      */
598     private function cleanup($task, $sxe)
599     {
600         chdir($this->local_installpath);
601         exec("rm -Rf ".escapeshellarg($this->local_installpath).'install_scripts');
602     }
603
604
605
606     /**
607      * The main method which performs the actual package installation
608      *
609      * @param $instanceid the instanceID to install
610      * @param $type the type of task to perform (installation, removal)
611      */
612     public function installHandler($instanceid, $type)
613     {
614         global $app;
615
616         // Set the given handle type, currently supported: install, delete
617         if($type == 'install' || $type == 'delete') $this->handle_type = $type;
618         else return false;
619
620         // Get all instance metadata
cc7a82 621         $task = $app->db->queryOneRecord("SELECT * FROM aps_instances AS i INNER JOIN aps_packages AS p ON i.package_id = p.id WHERE i.id = ?", $instanceid);
b1a6a5 622         if(!$task) return false;  // formerly: throw new Exception('The InstanceID doesn\'t exist.');
MC 623         if(!isset($task['instance_id'])) $task['instance_id'] = $instanceid;
624
bbc657 625         // Download aps package
MC 626         if(!file_exists($this->packages_dir.'/'.$task['path']) || filesize($this->packages_dir.'/'.$task['path']) == 0) {
627             $ch = curl_init();
628             $fh = fopen($this->packages_dir.'/'.$task['path'], 'wb');
b1a6a5 629             curl_setopt($ch, CURLOPT_FILE, $fh);
MC 630             //curl_setopt($ch, CURLOPT_HEADER, 0);
bbc657 631             curl_setopt($ch, CURLOPT_URL, $task['package_url']);
MC 632             curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
633             curl_setopt($ch, CURLOPT_TIMEOUT, 0);
634             curl_setopt($ch, CURLOPT_FAILONERROR, 1);
b1a6a5 635             curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
6bdb10 636             curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
b1a6a5 637             if(curl_exec($ch) === false) $app->log(curl_error($ch), 1);
bbc657 638             fclose($fh);
MC 639             curl_close($ch);
640         }
b1a6a5 641
bbc657 642         /*
b1a6a5 643         $app_to_dl[] = array('name' => $task['path'],
MC 644                             'url' => $task['package_url'],
645                             'filesize' => 0,
bbc657 646                             'localtarget' => $this->packages_dir.'/'.$task['path']);
MC 647
648         $this->fetchFiles($app_to_dl);
649         */
650
b1a6a5 651         // Make sure the requirements are given so that this script can execute
MC 652         $req_ret = $this->checkRequirements();
653         if(!$req_ret) return false;
654
655         $metafile = $this->getContentFromZIP($this->packages_dir.'/'.$task['path'], 'APP-META.xml');
656         // Check if the meta file is existing
657         if(!$metafile)
658         {
cc7a82 659             $app->dbmaster->query('UPDATE aps_instances SET instance_status = ? WHERE id = ?', INSTANCE_ERROR, $task['instance_id']);
b1a6a5 660             $app->log('Unable to find the meta data file of package '.$task['path'], 1);
MC 661             return false;
662         }
663
664         // Rename namespaces and register them
665         $metadata = str_replace("xmlns=", "ns=", $metafile);
666         $sxe = new SimpleXMLElement($metadata);
667         $namespaces = $sxe->getDocNamespaces(true);
668         foreach($namespaces as $ns => $url) $sxe->registerXPathNamespace($ns, $url);
669
670         // Setup the environment with data for the install location
671         $this->prepareLocation($task);
672
673         // Create the database if necessary
674         $this->prepareDatabase($task, $sxe);
675
676         // Unpack the install scripts from the packages
677         if($this->prepareFiles($task, $sxe) && $this->handle_type == 'install')
678         {
679             // Setup the variables from the install script
680             $this->prepareUserInputData($task);
681
682             // Do the actual installation
683             $this->doInstallation($task, $sxe);
684
685             // Remove temporary files
686             $this->cleanup($task, $sxe);
687         }
688
689         // Finally delete the instance entry + settings
690         if($this->handle_type == 'delete')
691         {
cc7a82 692             $app->db->query('DELETE FROM aps_instances WHERE id = ?', $task['instance_id']);
MC 693             $app->db->query('DELETE FROM aps_instances_settings WHERE instance_id = ?', $task['instance_id']);
e92eda 694             if ($app->dbmaster != $app->db) {
cc7a82 695                 $app->dbmaster->query('DELETE FROM aps_instances WHERE id = ?', $task['instance_id']);
MC 696                 $app->dbmaster->query('DELETE FROM aps_instances_settings WHERE instance_id = ?', $task['instance_id']);
e92eda 697             }
b1a6a5 698         }
MC 699
700         unset($sxe);
701     }
702
bbc657 703 }
b1a6a5 704
bbc657 705 ?>