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