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