tbrehm
2013-02-22 526b997c9891a796b152cdbab8e329b356b1f596
commit | author | age
477d4e 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 class ApsGUIController extends ApsBase
33 {
34     /**
35     * Constructor
36     *
37     * @param $app the application instance (db handle)
38     */
39     public function __construct($app)
40     {
41         parent::__construct($app);
42     }
43     
44     /**
45      * Reads in a package metadata file and registers it's namespaces
46      * 
47      * @param $filename the file to read
48      * @return $sxe a SimpleXMLElement handle
49      */
50     private function readInMetaFile($filename)
51     {
52         $metadata = file_get_contents($filename);
53         $metadata = str_replace("xmlns=", "ns=", $metadata);
54         $sxe = new SimpleXMLElement($metadata);
55         $namespaces = $sxe->getDocNamespaces(true);
56         foreach($namespaces as $ns => $url) $sxe->registerXPathNamespace($ns, $url);
57         
58         return $sxe; 
59     }
60     
61     /**
62      * Applies a RegEx pattern onto a location path in order to secure it against 
63      * code injections and invalid input
64      * 
65      * @param $location_unfiltered the file path to secure
66      * @return $location
67      */
68     private function secureLocation($location_unfiltered)
69     {
70         // Filter invalid slashes from string
71         $location = preg_replace(array('#/+#', '#\.+#', '#\0+#', '#\\\\+#'), 
72                                  array('/', '', '', '/'), 
73                                  $location_unfiltered);
74
75         // Remove a beginning or trailing slash
76         if(substr($location, -1) == '/') $location = substr($location, 0, strlen($location) - 1);
77         if(substr($location, 0, 1) == '/') $location = substr($location, 1);
78         
79         return $location;
80     }
81     
82     /**
83      * Gets the CustomerID (ClientID) which belongs to a specific domain
84      * 
85      * @param $domain the domain
86      * @return $customerid
87      */
88     private function getCustomerIDFromDomain($domain)
89     {
bfcdef 90         global $app;
84b8c1 91         $customerid = 0;
477d4e 92         
bfcdef 93         $customerdata = $app->db->queryOneRecord("SELECT client_id FROM sys_group, web_domain
477d4e 94             WHERE web_domain.sys_groupid = sys_group.groupid 
bfcdef 95             AND web_domain.domain = '".$app->db->quote($domain)."';");
477d4e 96         if(!empty($customerdata)) $customerid = $customerdata['client_id'];
T 97         
98         return $customerid;
99     }
100     
101     /**
102      * Returns the server_id for an already installed instance. Is actually 
103      * just a little helper method to avoid redundant code
104      * 
105      * @param $instanceid the instance to process
106      * @return $webserver_id the server_id
107      */
108     private function getInstanceDataForDatalog($instanceid)
109     {
bfcdef 110         global $app;
477d4e 111         $webserver_id = '';
T 112         
bfcdef 113         $websrv = $app->db->queryOneRecord("SELECT server_id FROM web_domain 
477d4e 114             WHERE domain = (SELECT value FROM aps_instances_settings 
bfcdef 115                 WHERE name = 'main_domain' AND instance_id = ".$app->db->quote($instanceid).");");
477d4e 116
T 117         // If $websrv is empty, an error has occured. Domain no longer existing? Settings table damaged?
118         // Anyhow, remove this instance record because it's not useful at all
119         if(empty($websrv)) 
120         {
bfcdef 121             $app->db->query("DELETE FROM aps_instances WHERE id = ".$app->db->quote($instanceid).";");
T 122             $app->db->query("DELETE FROM aps_instances_settings WHERE instance_id = ".$app->db->quote($instanceid).";");
477d4e 123         }
T 124         else $webserver_id = $websrv['server_id'];
125         
126         return $webserver_id;
127     } 
128     
129     /**
130      * Finds out if there is a newer package version for 
131      * a given (possibly valid) package ID
132      * 
133      * @param $id the ID to check
134      * @return $newer_pkg_id the newer package ID
135      */ 
136     public function getNewestPackageID($id)
137     {
bfcdef 138         global $app;
T 139         
477d4e 140         if(preg_match('/^[0-9]+$/', $id) != 1) return 0;
T 141         
bfcdef 142         $result = $app->db->queryOneRecord("SELECT id, name, 
477d4e 143             CONCAT(version, '-', CAST(`release` AS CHAR)) AS current_version 
T 144             FROM aps_packages 
bfcdef 145             WHERE name = (SELECT name FROM aps_packages WHERE id = ".$app->db->quote($id).")  
477d4e 146             ORDER BY REPLACE(version, '.', '')+0 DESC, `release` DESC");
T 147             
148         if(!empty($result) && ($id != $result['id'])) return $result['id'];
149         
150         return 0; 
151     }
152
153     /**
154      * Validates a given package ID
155      *
156      * @param $id the ID to check
157      * @param $is_admin a flag to allow locked IDs too (for admin calls)
158      * @return boolean
159      */
160     public function isValidPackageID($id, $is_admin = false)
161     {
bfcdef 162         global $app;
T 163         
477d4e 164          if(preg_match('/^[0-9]+$/', $id) != 1) return false;
T 165          
166          $sql_ext = (!$is_admin) ? 
167             'package_status = '.PACKAGE_ENABLED.' AND' :  
168             '(package_status = '.PACKAGE_ENABLED.' OR package_status = '.PACKAGE_LOCKED.') AND'; 
169
bfcdef 170          $result = $app->db->queryOneRecord("SELECT id FROM aps_packages WHERE ".$sql_ext." id = ".$app->db->quote($id).";");
477d4e 171          if(!$result) return false;
T 172          
173          return true;
174     }
175     
176     /**
177      * Validates a given instance ID
178      *
179      * @param $id the ID to check
180      * @param $client_id the calling client ID
181      * @param $is_admin a flag to ignore the client ID check for admins
182      * @return boolean
183      */
184     public function isValidInstanceID($id, $client_id, $is_admin = false)
185     {
bfcdef 186         global $app;
T 187         
477d4e 188          if(preg_match('/^[0-9]+$/', $id) != 1) return false;
T 189          
190          // Only filter if not admin
bfcdef 191          $sql_ext = (!$is_admin) ? 'customer_id = '.$app->db->quote($client_id).' AND' : ''; 
477d4e 192
bfcdef 193          $result = $app->db->queryOneRecord('SELECT id FROM aps_instances WHERE '.$sql_ext.' id = '.$app->db->quote($id).';');
477d4e 194          if(!$result) return false;
T 195          
196          return true;
197     }    
198     
199     /**
200      * Creates a new database record for the package instance and
201      * an install task
202      * 
203      * @param $settings the settings to enter into the DB
204      * @param $packageid the PackageID
205      */
206     public function createPackageInstance($settings, $packageid)
207     {
208         global $app;
209         
31f6ce 210         $app->uses('tools_sites');
M 211         
477d4e 212         $webserver_id = 0;
bfcdef 213         $websrv = $app->db->queryOneRecord("SELECT * FROM web_domain WHERE domain = '".$app->db->quote($settings['main_domain'])."';");
477d4e 214         if(!empty($websrv)) $webserver_id = $websrv['server_id'];
T 215         $customerid = $this->getCustomerIDFromDomain($settings['main_domain']);
216         
84b8c1 217         if(empty($settings) || empty($webserver_id)) return false;
477d4e 218         
T 219         //* Get server config of the web server
bfcdef 220         $app->uses("getconf");
T 221         $web_config = $app->getconf->get_server_config($app->functions->intval($websrv["server_id"]),'web');
477d4e 222             
526b99 223         //* Set PHP mode to php-fcgi and enable suexec in website on apache servers / set PHP mode to PHP-FPM on nginx servers
477d4e 224         if($web_config['server_type'] == 'apache') {
526b99 225             if(($websrv['php'] != 'fast-cgi' || $websrv['suexec'] != 'y') && $websrv['php'] != 'php-fpm') {
477d4e 226                 $app->db->datalogUpdate('web_domain', "php = 'fast-cgi', suexec = 'y'", 'domain_id', $websrv['domain_id']);
T 227             }
526b99 228         } else {
T 229             // nginx
230             if($websrv['php'] != 'php-fpm' && $websrv['php'] != 'fast-cgi') {
231                 $app->db->datalogUpdate('web_domain', "php = 'php-fpm'", 'domain_id', $websrv['domain_id']);
232             }
477d4e 233         }
T 234         
526b99 235         
477d4e 236         //* Create the MySQL database for the application
bfcdef 237         $pkg = $app->db->queryOneRecord('SELECT * FROM aps_packages WHERE id = '.$app->db->quote($packageid).';');
477d4e 238         $metafile = $this->interface_pkg_dir.'/'.$pkg['path'].'/APP-META.xml';
T 239         $sxe = $this->readInMetaFile($metafile);
240         
241         $db_id = parent::getXPathValue($sxe, '//db:id');
242         if (!empty($db_id)) {
243             $global_config = $app->getconf->get_global_config('sites');
244             
245             $tmp = array();
246             $tmp['parent_domain_id'] = $websrv['domain_id'];
247             $tmp['sys_groupid'] = $websrv['sys_groupid'];
31f6ce 248             $dbname_prefix = $app->tools_sites->replacePrefix($global_config['dbname_prefix'], $tmp);
M 249             $dbuser_prefix = $app->tools_sites->replacePrefix($global_config['dbuser_prefix'], $tmp);
477d4e 250             unset($tmp);
T 251             
bfcdef 252             // get information if the webserver is a db server, too
T 253             $web_server = $app->db->queryOneRecord("SELECT server_id,server_name,db_server FROM server WHERE server_id  = ".$websrv['server_id']);
254             if($web_server['db_server'] == 1) {
255                 // create database on "localhost" (webserver)
477d4e 256                 $mysql_db_server_id = $websrv['server_id'];
T 257                 $mysql_db_host = 'localhost';
258                 $mysql_db_remote_access = 'n';
259                 $mysql_db_remote_ips = '';
bfcdef 260             } else {
T 261                 //* get the default database server of the client
262                 $client = $app->db->queryOneRecord("SELECT default_dbserver FROM sys_group, client WHERE sys_group.client_id = client.client_id and sys_group.groupid = ".$websrv['sys_groupid']);
263                 if(is_array($client) && $client['default_dbserver'] > 0 && $client['default_dbserver'] != $websrv['server_id']) {
264                     $mysql_db_server_id =  $client['default_dbserver'];
265                     $dbserver_config = $web_config = $app->getconf->get_server_config($app->functions->intval($mysql_db_server_id),'server');
266                     $mysql_db_host = $dbserver_config['ip_address'];
267                     $mysql_db_remote_access = 'y';
268                     $webserver_config = $app->getconf->get_server_config($app->functions->intval($websrv['server_id']),'server');
269                     $mysql_db_remote_ips = $webserver_config['ip_address'];
270                 } else {
271                     /* I left this in place for a fallback that should NEVER! happen.
272                      * if we reach this point it means that there is NO default db server for the client
273                      * AND the webserver has NO db service enabled.
274                      * We have to abort the aps installation here... so I added a return false
275                      * although this does not present any error message to the user.
276                      */
277                     return false;
278                     
279                     /*$mysql_db_server_id = $websrv['server_id'];
280                     $mysql_db_host = 'localhost';
281                     $mysql_db_remote_access = 'n';
282                     $mysql_db_remote_ips = '';*/
283                 }
284             }
477d4e 285             
bfcdef 286             //* Find a free db name for the app
477d4e 287             for($n = 1; $n <= 1000; $n++) {
bfcdef 288                 $mysql_db_name = ($dbname_prefix != '' ? $dbname_prefix.'aps'.$n : uniqid('aps'));
91b004 289                 $tmp = $app->db->queryOneRecord("SELECT count(database_id) as number FROM web_database WHERE database_name = '".$app->db->quote($mysql_db_name)."'");
M 290                 if($tmp['number'] == 0) break;
291             }
292             //* Find a free db username for the app
293             for($n = 1; $n <= 1000; $n++) {
10b4c8 294                 $mysql_db_user = ($dbuser_prefix != '' ? $dbuser_prefix.'aps'.$n : uniqid('aps'));
91b004 295                 $tmp = $app->db->queryOneRecord("SELECT count(database_user_id) as number FROM web_database_user WHERE database_user = '".$app->db->quote($mysql_db_user)."'");
477d4e 296                 if($tmp['number'] == 0) break;
T 297             }
298             
7d4360 299             $mysql_db_password = $settings['main_database_password'];
T 300             
91b004 301             //* Create the mysql database user
10b4c8 302             $insert_data = "(`sys_userid`, `sys_groupid`, `sys_perm_user`, `sys_perm_group`, `sys_perm_other`, `server_id`, `database_user`, `database_user_prefix`, `database_password`) 
T 303                       VALUES( ".$websrv['sys_userid'].", ".$websrv['sys_groupid'].", 'riud', '".$websrv['sys_perm_group']."', '', 0, '$mysql_db_user', '".$app->db->quote($dbuser_prefix) . "', PASSWORD('$mysql_db_password'))";
91b004 304             $mysql_db_user_id = $app->db->datalogInsert('web_database_user', $insert_data, 'database_user_id');
M 305             
477d4e 306             //* Create the mysql database
10b4c8 307             $insert_data = "(`sys_userid`, `sys_groupid`, `sys_perm_user`, `sys_perm_group`, `sys_perm_other`, `server_id`, `parent_domain_id`, `type`, `database_name`, `database_name_prefix`, `database_user_id`, `database_ro_user_id`, `database_charset`, `remote_access`, `remote_ips`, `backup_copies`, `active`, `backup_interval`) 
T 308                       VALUES( ".$websrv['sys_userid'].", ".$websrv['sys_groupid'].", 'riud', '".$websrv['sys_perm_group']."', '', $mysql_db_server_id, ".$websrv['domain_id'].", 'mysql', '$mysql_db_name', '" . $app->db->quote($dbname_prefix) . "', '$mysql_db_user_id', 0, '', '$mysql_db_remote_access', '$mysql_db_remote_ips', ".$websrv['backup_copies'].", 'y', '".$websrv['backup_interval']."')";
477d4e 309             $app->db->datalogInsert('web_database', $insert_data, 'database_id');
T 310             
311             //* Add db details to package settings
312             $settings['main_database_host'] = $mysql_db_host;
313             $settings['main_database_name'] = $mysql_db_name;
314             $settings['main_database_login'] = $mysql_db_user;
315         
316         }
317         
318         //* Insert new package instance
bfcdef 319         $insert_data = "(`sys_userid`, `sys_groupid`, `sys_perm_user`, `sys_perm_group`, `sys_perm_other`, `server_id`, `customer_id`, `package_id`, `instance_status`) VALUES (".$websrv['sys_userid'].", ".$websrv['sys_groupid'].", 'riud', '".$websrv['sys_perm_group']."', '', ".$app->db->quote($webserver_id).",".$app->db->quote($customerid).", ".$app->db->quote($packageid).", ".INSTANCE_PENDING.")";
477d4e 320         $InstanceID = $app->db->datalogInsert('aps_instances', $insert_data, 'id');
T 321         
322         //* Insert all package settings
323         if(is_array($settings)) {
324             foreach($settings as $key => $value) {
bfcdef 325                 $insert_data = "(server_id, instance_id, name, value) VALUES (".$app->db->quote($webserver_id).",".$app->db->quote($InstanceID).", '".$app->db->quote($key)."', '".$app->db->quote($value)."')";
T 326                 $app->db->datalogInsert('aps_instances_settings', $insert_data, 'id');
477d4e 327             }
T 328         }
329         
330         //* Set package status to install afetr we inserted the settings
331         $app->db->datalogUpdate('aps_instances', "instance_status = ".INSTANCE_INSTALL, 'id', $InstanceID);
332     }
333     
334     /**
335      * Sets the status of an instance to "should be removed" and creates a 
336      * datalog entry to give the ISPConfig server a real removal advice 
337      * 
338      * @param $instanceid the instance to delete
339      */
340     public function deleteInstance($instanceid)
341     {
4bd960 342         global $app;
T 343         /*
bfcdef 344         $app->db->query("UPDATE aps_instances SET instance_status = ".INSTANCE_REMOVE." WHERE id = ".$instanceid.";");
477d4e 345         
T 346         $webserver_id = $this->getInstanceDataForDatalog($instanceid);
347         if($webserver_id == '') return;
348         
349         // Create a sys_datalog entry for deletion
350         $datalog = array('Instance_id' => $instanceid, 'server_id' => $webserver_id);
bfcdef 351         $app->db->datalogSave('aps', 'DELETE', 'id', $instanceid, array(), $datalog);
477d4e 352         */
84b8c1 353         
36bb81 354         $sql = "SELECT web_database.database_id as database_id, web_database.database_user_id as `database_user_id` FROM aps_instances_settings, web_database WHERE aps_instances_settings.value = web_database.database_name AND aps_instances_settings.value =  aps_instances_settings.name = 'main_database_name' AND aps_instances_settings.instance_id = ".$instanceid." LIMIT 0,1";
bfcdef 355         $tmp = $app->db->queryOneRecord($sql);
T 356         if($tmp['database_id'] > 0) $app->db->datalogDelete('web_database', 'database_id', $tmp['database_id']);
84b8c1 357         
36bb81 358         $database_user = $tmp['database_user_id'];
bfcdef 359         $tmp = $app->db->queryOneRecord("SELECT COUNT(*) as `cnt` FROM `web_database` WHERE `database_user_id` = '" . $app->functions->intval($database_user) . "' OR `database_ro_user_id` = '" . $app->functions->intval($database_user) . "'");
T 360         if($tmp['cnt'] < 1) $app->db->datalogDelete('web_database_user', 'database_user_id', $database_user);
36bb81 361         
4bd960 362         $app->db->datalogUpdate('aps_instances', "instance_status = ".INSTANCE_REMOVE, 'id', $instanceid);
T 363
477d4e 364     }
T 365     
366     /**
367      * Sets the status of an instance to "installation planned" and creates a 
368      * datalog entry to re-install the package. The existing package is simply overwritten. 
369      * 
370      * @param $instanceid the instance to delete
371      */
372     public function reinstallInstance($instanceid)
373     {
bfcdef 374         global $app;
T 375         
477d4e 376         /*
bfcdef 377         $app->db->query("UPDATE aps_instances SET instance_status = ".INSTANCE_INSTALL." WHERE id = ".$instanceid.";");
477d4e 378         
T 379         $webserver_id = $this->getInstanceDataForDatalog($instanceid);
380         if($webserver_id == '') return;
381         
382         // Create a sys_datalog entry for re-installation
383         $datalog = array('instance_id' => $instanceid, 'server_id' => $webserver_id);
bfcdef 384         $app->db->datalogSave('aps', 'INSERT', 'id', $instanceid, array(), $datalog);
477d4e 385         */
84b8c1 386         
T 387         $sql = "SELECT web_database.database_id as database_id FROM aps_instances_settings, web_database WHERE aps_instances_settings.value = web_database.database_name AND aps_instances_settings.value =  aps_instances_settings.name = 'main_database_name' AND aps_instances_settings.instance_id = ".$instanceid." LIMIT 0,1";
bfcdef 388         $tmp = $app->db->queryOneRecord($sql);
T 389         if($tmp['database_id'] > 0) $app->db->datalogDelete('web_database', 'database_id', $tmp['database_id']);
84b8c1 390         
bfcdef 391         $app->db->datalogUpdate('aps_instances', "instance_status = ".INSTANCE_INSTALL, 'id', $instanceid);
477d4e 392     }
T 393
394     /**
395      * Read the settings to be filled when installing
396      * 
397      * @param $id the internal ID of the package
398      * @return array
399      */
400     public function getPackageSettings($id)    
401     {
bfcdef 402         global $app;
T 403         
404         $pkg = $app->db->queryOneRecord('SELECT * FROM aps_packages WHERE id = '.$app->db->quote($id).';');
477d4e 405         
T 406         // Load in meta file if existing and register its namespaces
407         $metafile = $this->interface_pkg_dir.'/'.$pkg['path'].'/APP-META.xml';
408         if(!file_exists($metafile)) 
409             return array('error' => 'The metafile for '.$settings['Name'].' couldn\'t be found');
410         
411         $sxe = $this->readInMetaFile($metafile);
412         
413         $groupsettings = parent::getXPathValue($sxe, '//settings/group/setting', true);
414         if(empty($groupsettings)) return array();
415         
416         $settings = array();
417         foreach($groupsettings as $setting)
418         {
419             $setting_id = strval($setting['id']);
420             
421             if($setting['type'] == 'string' || $setting['type'] == 'email' || $setting['type'] == 'integer'
422             || $setting['type'] == 'float' || $setting['type'] == 'domain-name')
423             {
424                 $settings[] = array('SettingID' => $setting_id,
425                                     'SettingName' => $setting->name,
426                                     'SettingDescription' => $setting->description,
427                                     'SettingType' => $setting['type'],
428                                     'SettingInputType' => 'string',
429                                     'SettingDefaultValue' => strval($setting['default-value']),
430                                     'SettingRegex' => $setting['regex'],
431                                     'SettingMinLength' => $setting['min-length'],
432                                     'SettingMaxLength' => $setting['max-length']);
433             }
434             else if($setting['type'] == 'password')
435             {
436                 $settings[] = array('SettingID' => $setting_id,
437                                     'SettingName' => $setting->name,
438                                     'SettingDescription' => $setting->description,
439                                     'SettingType' => 'password',
440                                     'SettingInputType' => 'password',
441                                     'SettingDefaultValue' => '',
442                                     'SettingRegex' => $setting['regex'],
443                                     'SettingMinLength' => $setting['min-length'],
444                                     'SettingMaxLength' => $setting['max-length']);
445             }
446             else if($setting['type'] == 'boolean')
447             {
448                 $settings[] = array('SettingID' => $setting_id,
449                                     'SettingName' => $setting->name,
450                                     'SettingDescription' => $setting->description,
451                                     'SettingType' => 'boolean',
452                                     'SettingInputType' => 'checkbox',
453                                     'SettingDefaultValue' => strval($setting['default-value']));
454             }
455             else if($setting['type'] == 'enum')
456             {
457               $choices = array();
458               foreach($setting->choice as $choice)
459               {
460                 $choices[] = array('EnumID' => strval($choice['id']),
461                                    'EnumName' => $choice->name);
462               }
463               $settings[] = array('SettingID' => $setting_id,
464                                   'SettingName' => $setting->name,
465                                   'SettingDescription' => $setting->description,
466                                   'SettingType' => 'enum',
467                                   'SettingInputType' => 'select',
468                                   'SettingDefaultValue' => strval($setting['default-value']),
469                                   'SettingChoices' => $choices);
470             }
471         }
472
473         return $settings;
474     }
475     
476     /**
477      * Validates the user input according to the settings array and
478      * delivers errors if occurring
479      * 
480      * @param $input the user $_POST array
481      * @param $pkg_details the package details
482      * @param $settings the package settings array
483      * @return array in this structure:
484      *               array(2) {
485      *                  ["input"]=> ...
486      *                  ["errors"]=> ...
487      *               }
488      */
489     public function validateInstallerInput($postinput, $pkg_details, $domains, $settings = array())
490     {
65ea2e 491         global $app;
M 492         
477d4e 493         $ret = array();
T 494         $input = array(); 
495         $error = array();
496         
497         // Main domain (obligatory)
498         if(isset($postinput['main_domain']))
499         {
bfcdef 500             if(!in_array($postinput['main_domain'], $domains)) $error[] = $app->lng('error_main_domain');
477d4e 501             else $input['main_domain'] = $postinput['main_domain'];
T 502         }
bfcdef 503         else $error[] = $app->lng('error_main_domain'); 
477d4e 504         
T 505         // Main location (not obligatory but must be supplied)
506         if(isset($postinput['main_location']))
507         {
508             $temp_errstr = '';
509             // It can be empty but if the user did write something, check it
510             $userinput = false;
511             if(strlen($postinput['main_location']) > 0) $userinput = true; 
512             
513             // Filter invalid input slashes (twice!)
514             $main_location = $this->secureLocation($postinput['main_location']);
515             $main_location = $this->secureLocation($main_location);
516             // Only allow digits, words, / and -
517             $main_location = preg_replace("/[^\d\w\/\-]/i", "", $main_location);
bfcdef 518             if($userinput && (strlen($main_location) == 0)) $temp_errstr = $app->lng('error_inv_main_location');
477d4e 519             
T 520             // Find out document_root and make sure no apps are installed twice to one location
521             if(in_array($postinput['main_domain'], $domains))
522             {
bfcdef 523                 $docroot = $app->db->queryOneRecord("SELECT document_root FROM web_domain 
T 524                     WHERE domain = '".$app->db->quote($postinput['main_domain'])."';");
477d4e 525                 $new_path = $docroot['document_root'];
T 526                 if(substr($new_path, -1) != '/') $new_path .= '/';
527                 $new_path .= $main_location;
528                 
529                 // Get the $customerid which belongs to the selected domain
530                 $customerid = $this->getCustomerIDFromDomain($postinput['main_domain']);
531                 
532                 // First get all domains used for an install, then their loop them
533                 // and get the corresponding document roots as well as the defined
534                 // locations. If an existing doc_root + location matches with the
535                 // new one -> error
bfcdef 536                 $instance_domains = $app->db->queryAllRecords("SELECT instance_id, s.value AS domain 
477d4e 537                     FROM aps_instances AS i, aps_instances_settings AS s 
T 538                     WHERE i.id = s.instance_id AND s.name = 'main_domain' 
bfcdef 539                         AND i.customer_id = '".$app->db->quote($customerid)."';");
477d4e 540                 for($i = 0; $i < count($instance_domains); $i++)
T 541                 {
542                     $used_path = '';
543                     
bfcdef 544                     $doc_root = $app->db->queryOneRecord("SELECT document_root FROM web_domain 
T 545                         WHERE domain = '".$app->db->quote($instance_domains[$i]['domain'])."';");
477d4e 546
T 547                     // Probably the domain settings were changed later, so make sure the doc_root
548                     // is not empty for further validation
549                     if(!empty($doc_root))
550                     {
10b4c8 551                         $used_path = $doc_root['document_root'];
477d4e 552                         if(substr($used_path, -1) != '/') $used_path .= '/';
T 553                         
bfcdef 554                         $location_for_domain = $app->db->queryOneRecord("SELECT value 
477d4e 555                             FROM aps_instances_settings WHERE name = 'main_location' 
bfcdef 556                             AND instance_id = '".$app->db->quote($instance_domains[$i]['instance_id'])."';");
477d4e 557                         
T 558                         // The location might be empty but the DB return must not be false!
10b4c8 559                         if($location_for_domain) $used_path .= $location_for_domain['value'];                          
477d4e 560
T 561                         if($new_path == $used_path)
562                         {
bfcdef 563                             $temp_errstr = $app->lng('error_used_location');
477d4e 564                             break;
T 565                         }
566                     }
567                 }
568             }
bfcdef 569             else $temp_errstr = $app->lng('error_main_domain');
477d4e 570             
T 571             if($temp_errstr == '') $input['main_location'] = htmlspecialchars($main_location);
572             else $error[] = $temp_errstr;            
573         }
bfcdef 574         else $error[] = $app->lng('error_no_main_location');
477d4e 575         
T 576         // License (the checkbox must be set)
577         if(isset($pkg_details['License need agree']) 
578         && $pkg_details['License need agree'] == 'true')
579         {
580             if(isset($postinput['license']) && $postinput['license'] == 'on') $input['license'] = 'true';
bfcdef 581             else $error[] = $app->lng('error_license_agreement');
477d4e 582         } 
T 583         
584         // Database
585         if(isset($pkg_details['Requirements Database'])
586         && $pkg_details['Requirements Database'] != '')
587         {
588             if(isset($postinput['main_database_password']))
589             {
bfcdef 590                 if($postinput['main_database_password'] == '') $error[] = $app->lng('error_no_database_pw');
477d4e 591                 else if(strlen($postinput['main_database_password']) > 8) 
T 592                     $input['main_database_password'] = htmlspecialchars($postinput['main_database_password']);
bfcdef 593                 else $error[] = $app->lng('error_short_database_pw');
477d4e 594             }
bfcdef 595             else $error[] = $app->lng('error_no_database_pw');
477d4e 596         }
T 597         
598         // Validate the package settings 
599         foreach($settings as $setting)
600         {
601             $temp_errstr = '';
602             $setting_id = strval($setting['SettingID']); 
603             
604             // We assume that every setting must be set
605             if((isset($postinput[$setting_id]) && ($postinput[$setting_id] != ''))
606             || ($setting['SettingType'] == 'boolean'))
607             {
608                 if($setting['SettingType'] == 'string' || $setting['SettingType'] == 'password')
609                 {
65ea2e 610                     if($app->functions->intval($setting['SettingMinLength'], true) != 0 
M 611                     && strlen($postinput[$setting_id]) < $app->functions->intval($setting['SettingMinLength'], true))
bfcdef 612                         $temp_errstr = sprintf($app->lng('error_short_value_for'), $setting['setting_name']);
477d4e 613                         
65ea2e 614                     if($app->functions->intval($setting['SettingMaxLength'], true) != 0 
M 615                     && strlen($postinput[$setting_id]) > $app->functions->intval($setting['SettingMaxLength'], true))
bfcdef 616                         $temp_errstr = sprintf($app->lng('error_long_value_for'), $setting['setting_name']);
477d4e 617
T 618                     if(isset($setting['SettingRegex'])
619                     && !preg_match("/".$setting['SettingRegex']."/", $postinput[$setting_id]))
bfcdef 620                         $temp_errstr = sprintf($app->lng('error_inv_value_for'), $setting['setting_name']);
477d4e 621                 }
T 622                 else if($setting['SettingType'] == 'email')
623                 {
624                     if(filter_var(strtolower($postinput[$setting_id]), FILTER_VALIDATE_EMAIL) === false)
bfcdef 625                         $temp_errstr = sprintf($app->lng('error_inv_email_for'), $setting['setting_name']);
477d4e 626                 }
T 627                 else if($setting['SettingType'] == 'domain-name')
628                 {
629                     if(!preg_match("^(http|https)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(\:[0-9]+)*(/($|[a-zA-Z0-9\.\,\?\'\\\+&%\$#\=~_\-]+))*$", 
630                         $postinput[$setting_id]))
bfcdef 631                     $temp_errstr = sprintf($app->lng('error_inv_domain_for'), $setting['setting_name']);    
477d4e 632                 }
T 633                 else if($setting['SettingType'] == 'integer')
634                 {
635                     if(filter_var($postinput[$setting_id], FILTER_VALIDATE_INT) === false)
bfcdef 636                         $temp_errstr = sprintf($app->lng('error_inv_integer_for'), $setting['setting_name']);
477d4e 637                 }
T 638                 else if($setting['SettingType'] == 'float')
639                 {
640                     if(filter_var($postinput[$setting_id], FILTER_VALIDATE_FLOAT) === false)
bfcdef 641                         $temp_errstr = sprintf($app->lng('error_inv_float_for'), $setting['setting_name']);
477d4e 642                 }
T 643                 else if($setting['SettingType'] == 'boolean')
644                 {
645                     // If we have a boolean value set, it must be either true or false
646                     if(!isset($postinput[$setting_id])) $postinput[$setting_id] = 'false';
647                     else if(isset($postinput[$setting_id]) && $postinput[$setting_id] != 'true') 
648                         $postinput[$setting_id] = 'true';
649                 }
650                 else if($setting['SettingType'] == 'enum')
651                 {
652                     $found = false;
653                     for($i = 0; $i < count($setting['SettingChoices']); $i++)
654                     {
655                         if($setting['SettingChoices'][$i]['EnumID'] == $postinput[$setting_id])
656                             $found = true;
657                     }
bfcdef 658                     if(!$found) $temp_errstr = sprintf($app->lng('error_inv_value_for'), $setting['SettingName']);
477d4e 659                 }
T 660                 
661                 if($temp_errstr == '') $input[$setting_id] = $postinput[$setting_id];
662                 else $error[] = $temp_errstr;
663             }
bfcdef 664             else $error[] = sprintf($app->lng('error_no_value_for'), $setting['SettingName']);
477d4e 665         }
T 666         
667         $ret['input'] = $input;
668         $ret['error'] = array_unique($error);
669         
670         return $ret;
671     }
672     
673     /**
674      * Read the metadata of a package and returns some content
675      * 
676      * @param $id the internal ID of the package
677      * @return array
678      */
679     public function getPackageDetails($id)
680     {
bfcdef 681         global $app;
T 682         
683         $pkg = $app->db->queryOneRecord('SELECT * FROM aps_packages WHERE id = '.$app->db->quote($id).';');
477d4e 684         
T 685         // Load in meta file if existing and register its namespaces
686         $metafile = $this->interface_pkg_dir.'/'.$pkg['path'].'/APP-META.xml';
687         if(!file_exists($metafile)) 
688             return array('error' => 'The metafile for '.$pkg['name'].' couldn\'t be found');
689         
690         $metadata = file_get_contents($metafile);
691         $metadata = str_replace("xmlns=", "ns=", $metadata);
692         $sxe = new SimpleXMLElement($metadata);
693         $namespaces = $sxe->getDocNamespaces(true);
694         foreach($namespaces as $ns => $url) $sxe->registerXPathNamespace($ns, $url); 
695
696         $pkg['Summary'] = htmlspecialchars(parent::getXPathValue($sxe, '//summary'));
697         $pkg['Homepage'] = parent::getXPathValue($sxe, '//homepage');
698         $pkg['Description'] = nl2br(htmlspecialchars(trim(parent::getXPathValue($sxe, '//description'))));
699         $pkg['Config script'] = strtoupper(parent::getXPathValue($sxe, '//configuration-script-language'));
700         $installed_size = parent::getXPathValue($sxe, '//installed-size');
701         $pkg['Installed Size'] = (!empty($installed_size)) ? parent::convertSize((int)$installed_size) : '';        
702         
703         // License
704         $pkg['License need agree'] = parent::getXPathValue($sxe, '//license/@must-accept');
705         $pkg['License name'] = parent::getXPathValue($sxe, '//license/text/name'); // might be empty
706         $pkg['License type'] = 'file'; // default type
707         $pkg['License content'] = ''; // default license filename on local system
708         $license_url = parent::getXPathValue($sxe, '//license/text/url');
709         if(!empty($license_url)) 
710         {
711             $pkg['License type'] = 'url';
712             $pkg['License content'] = htmlspecialchars($license_url);
713         }
714         else
715         {
716             $lic = @file_get_contents($this->interface_pkg_dir.'/'.$pkg['path'].'/LICENSE');
717             $pkg['License content'] = htmlentities($lic, ENT_QUOTES, 'ISO-8859-1');
718         }  
719         
720         // Languages
721         $languages = parent::getXPathValue($sxe, '//languages/language', true);
722         $pkg['Languages'] = (is_array($languages)) ? implode(' ', $languages) : '';
723         
724         // Icon
725         $icon = parent::getXPathValue($sxe, '//icon/@path');
726         if(!empty($icon))
727         {
728             // Using parse_url() to filter malformed URLs
729             $path = dirname(parse_url($_SERVER['PHP_SELF'], PHP_URL_PATH)).'/'.
730                     basename($this->interface_pkg_dir).'/'.$pkg['path'].'/'.basename((string)$icon);
731             $pkg['Icon'] = $path;
732         }
733         else $pkg['Icon'] = '';
734         
735         // Screenshots
736         $screenshots = parent::getXPathValue($sxe, '//screenshot', true);
737         if(!empty($screenshots))
738         {
739             foreach($screenshots as $screen)
740             {
741                 // Using parse_url() to filter malformed URLs
742                 $path = dirname(parse_url($_SERVER['PHP_SELF'], PHP_URL_PATH)).'/'.
743                         basename($this->interface_pkg_dir).'/'.$pkg['path'].'/'.basename((string)$screen['path']);
744
745                 $pkg['Screenshots'][] = array('ScreenPath' => $path,
746                                               'ScreenDescription' => htmlspecialchars(trim((string)$screen->description)));
747             }
748         }
749         else $pkg['Screenshots'] = ''; // if no screenshots are available, set the variable though
750                 
751         // Changelog
752         $changelog = parent::getXPathValue($sxe, '//changelog/version', true);
753         if(!empty($changelog))
754         {
755             foreach($changelog as $change)
756             {
757                 $entries = array(); 
758                 foreach($change->entry as $entry) $entries[] = htmlspecialchars(trim((string)$entry)); 
759
760                 $pkg['Changelog'][] = array('ChangelogVersion' => (string)$change['version'], 
761                                             'ChangelogDescription' => implode('<br />', $entries));
762             }
763         }
764         
765         else $pkg['Changelog'] = '';
766         
767         // PHP extensions
768         $php_extensions = parent::getXPathValue($sxe, '//php:extension', true);
769         $php_ext = '';
770         if(!empty($php_extensions))
771         {
772             foreach($php_extensions as $extension)
773             {
774                 if(strtolower($extension) == 'php') continue;
775                 $php_ext .= $extension.' ';
776             }
777         }
778         $pkg['Requirements PHP extensions'] = trim($php_ext);
779         
780         // PHP bool options
781         $pkg['Requirements PHP settings'] = '';
782         $php_bool_options = array('allow-url-fopen', 'file-uploads', 'magic-quotes-gpc', 
783                                   'register-globals', 'safe-mode', 'short-open-tag');
784         foreach($php_bool_options as $option)
785         {
786             $value = parent::getXPathValue($sxe, '//php:'.$option);
787             if(!empty($value))
788             {
789                 $option = str_replace('-', '_', $option);
790                 $value = str_replace(array('false', 'true'), array('off', 'on'), $value);
791                 $pkg['Requirements PHP settings'][] = array('PHPSettingName' => $option,
792                                                             'PHPSettingValue' => $value);
793             }
794         }
795         
796         // PHP integer value settings
797         $memory_limit = parent::getXPathValue($sxe, '//php:memory-limit');
798         if(!empty($memory_limit))
799             $pkg['Requirements PHP settings'][] = array('PHPSettingName' => 'memory_limit',
800                                                         'PHPSettingValue' => parent::convertSize((int)$memory_limit));
801
802         $max_exec_time = parent::getXPathValue($sxe, '//php:max-execution-time');
803         if(!empty($max_exec_time))
804             $pkg['Requirements PHP settings'][] = array('PHPSettingName' => 'max-execution-time',
805                                                         'PHPSettingValue' => $max_exec_time);
806         
807         $post_max_size = parent::getXPathValue($sxe, '//php:post-max-size');
808         if(!empty($post_max_size))
809             $pkg['Requirements PHP settings'][] = array('PHPSettingName' => 'post_max_size',
810                                                         'PHPSettingValue' => parent::convertSize((int)$post_max_size));
811         
812         // Get supported PHP versions
813         $pkg['Requirements Supported PHP versions'] = '';
814         $php_min_version = parent::getXPathValue($sxe, '//php:version/@min');
815         $php_max_not_including = parent::getXPathValue($sxe, '//php:version/@max-not-including');
816         if(!empty($php_min_version) && !empty($php_max_not_including)) 
817             $pkg['Requirements Supported PHP versions'] = $php_min_version.' - '.$php_max_not_including;
818         else if(!empty($php_min_version)) 
819             $pkg['Requirements Supported PHP versions'] = '> '.$php_min_version;
820         else if(!empty($php_max_not_including))
821             $pkg['Requirements Supported PHP versions'] = '< '.$php_min_version;
822         
823         // Database
824         $db_id = parent::getXPathValue($sxe, '//db:id');
825         $db_server_type = parent::getXPathValue($sxe, '//db:server-type');
826         $db_min_version = parent::getXPathValue($sxe, '//db:server-min-version'); 
827         if(!empty($db_id))
828         {
829             $db_server_type = str_replace('postgresql', 'PostgreSQL', $db_server_type);
830             $db_server_type = str_replace('microsoft:sqlserver', 'MSSQL', $db_server_type);
831             $db_server_type = str_replace('mysql', 'MySQL', $db_server_type);
832             
833             $pkg['Requirements Database'] = $db_server_type;
834             if(!empty($db_min_version)) $pkg['Requirements Database'] .= ' > '.$db_min_version;
835         }
836         else $pkg['Requirements Database'] = '';
837         
838         return $pkg;
839     }
840 }
841 ?>