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