From 477d4e981a4171dcc8b7886b2130abbee0442067 Mon Sep 17 00:00:00 2001 From: tbrehm Date: Thu, 24 May 2012 11:26:50 +0000 Subject: [PATCH] Initial commit of the interface part of the APS installer. --- install/dist/lib/fedora.lib.php | 4 + install/dist/lib/gentoo.lib.php | 4 + install/dist/lib/opensuse.lib.php | 4 + install/lib/installer_base.lib.php | 4 + install/sql/incremental/upd_0034.sql | 76 ++ install/sql/ispconfig3.sql | 71 ++ interface/lib/classes/aps_base.inc.php | 109 +++ interface/lib/classes/aps_crawler.inc.php | 534 ++++++++++++ .../lib/classes/aps_guicontroller.inc.php | 770 ++++++++++++++++++ interface/web/js/scrigo.js.php | 5 +- .../web/sites/aps_availablepackages_list.php | 56 ++ .../web/sites/aps_cron_apscrawler_if.php | 59 ++ interface/web/sites/aps_do_operation.php | 110 +++ interface/web/sites/aps_install_package.php | 198 +++++ .../web/sites/aps_installedpackages_list.php | 127 +++ .../web/sites/aps_packagedetails_show.php | 99 +++ interface/web/sites/lib/lang/en_aps.lng | 57 ++ .../sites/lib/lang/en_aps_instances_list.lng | 13 + .../sites/lib/lang/en_aps_packages_list.lng | 8 + interface/web/sites/lib/module.conf.php | 28 + .../sites/list/aps_availablepackages.list.php | 86 ++ .../sites/list/aps_installedpackages.list.php | 81 ++ .../sites/templates/aps_install_package.htm | 54 ++ .../sites/templates/aps_instances_list.htm | 60 ++ .../templates/aps_packagedetails_show.htm | 122 +++ .../web/sites/templates/aps_packages_list.htm | 48 ++ .../default/css/screen/content_ispc.css | 5 +- .../web/themes/default/images/ajax-loader.gif | Bin 0 -> 3208 bytes 28 files changed, 2790 insertions(+), 2 deletions(-) create mode 100644 install/sql/incremental/upd_0034.sql create mode 100644 interface/lib/classes/aps_base.inc.php create mode 100644 interface/lib/classes/aps_crawler.inc.php create mode 100644 interface/lib/classes/aps_guicontroller.inc.php create mode 100644 interface/web/sites/aps_availablepackages_list.php create mode 100644 interface/web/sites/aps_cron_apscrawler_if.php create mode 100644 interface/web/sites/aps_do_operation.php create mode 100644 interface/web/sites/aps_install_package.php create mode 100644 interface/web/sites/aps_installedpackages_list.php create mode 100644 interface/web/sites/aps_packagedetails_show.php create mode 100644 interface/web/sites/lib/lang/en_aps.lng create mode 100644 interface/web/sites/lib/lang/en_aps_instances_list.lng create mode 100644 interface/web/sites/lib/lang/en_aps_packages_list.lng create mode 100644 interface/web/sites/list/aps_availablepackages.list.php create mode 100644 interface/web/sites/list/aps_installedpackages.list.php create mode 100644 interface/web/sites/templates/aps_install_package.htm create mode 100644 interface/web/sites/templates/aps_instances_list.htm create mode 100644 interface/web/sites/templates/aps_packagedetails_show.htm create mode 100644 interface/web/sites/templates/aps_packages_list.htm create mode 100644 interface/web/themes/default/images/ajax-loader.gif diff --git a/install/dist/lib/fedora.lib.php b/install/dist/lib/fedora.lib.php index ba1c285fa..f6ed41855 100644 --- a/install/dist/lib/fedora.lib.php +++ b/install/dist/lib/fedora.lib.php @@ -865,6 +865,10 @@ class installer_dist extends installer_base { } } + //* Make the APS directories group writable + exec("chmod -R 770 $install_dir/interface/web/sites/aps_meta_packages"); + exec("chmod -R 770 $install_dir/server/aps_packages"); + //* make sure that the server config file (not the interface one) is only readable by the root user exec("chmod 600 $install_dir/server/lib/$configfile"); exec("chown root:root $install_dir/server/lib/$configfile"); diff --git a/install/dist/lib/gentoo.lib.php b/install/dist/lib/gentoo.lib.php index 5e5c93c2a..ad94bad61 100644 --- a/install/dist/lib/gentoo.lib.php +++ b/install/dist/lib/gentoo.lib.php @@ -858,6 +858,10 @@ class installer extends installer_base } } + //* Make the APS directories group writable + exec("chmod -R 770 $install_dir/interface/web/sites/aps_meta_packages"); + exec("chmod -R 770 $install_dir/server/aps_packages"); + //* make sure that the server config file (not the interface one) is only readable by the root user chmod($install_dir.'/server/lib/'.$configfile, 0600); chown($install_dir.'/server/lib/'.$configfile, 'root'); diff --git a/install/dist/lib/opensuse.lib.php b/install/dist/lib/opensuse.lib.php index 88f508a5c..2dfcd70a9 100644 --- a/install/dist/lib/opensuse.lib.php +++ b/install/dist/lib/opensuse.lib.php @@ -903,6 +903,10 @@ class installer_dist extends installer_base { } } + //* Make the APS directories group writable + exec("chmod -R 770 $install_dir/interface/web/sites/aps_meta_packages"); + exec("chmod -R 770 $install_dir/server/aps_packages"); + //* make sure that the server config file (not the interface one) is only readable by the root user exec("chmod 600 $install_dir/server/lib/$configfile"); exec("chown root:root $install_dir/server/lib/$configfile"); diff --git a/install/lib/installer_base.lib.php b/install/lib/installer_base.lib.php index 94a176a78..b472844d1 100644 --- a/install/lib/installer_base.lib.php +++ b/install/lib/installer_base.lib.php @@ -1767,6 +1767,10 @@ class installer_base { } } } + + //* Make the APS directories group writable + exec("chmod -R 770 $install_dir/interface/web/sites/aps_meta_packages"); + exec("chmod -R 770 $install_dir/server/aps_packages"); //* make sure that the server config file (not the interface one) is only readable by the root user chmod($install_dir.'/server/lib/'.$configfile, 0600); diff --git a/install/sql/incremental/upd_0034.sql b/install/sql/incremental/upd_0034.sql new file mode 100644 index 000000000..ea8457700 --- /dev/null +++ b/install/sql/incremental/upd_0034.sql @@ -0,0 +1,76 @@ +-- -------------------------------------------------------- + +-- +-- Table structure for table `aps_instances` +-- + +CREATE TABLE IF NOT EXISTS `aps_instances` ( + `id` int(4) NOT NULL AUTO_INCREMENT, + `sys_userid` int(11) unsigned NOT NULL DEFAULT '0', + `sys_groupid` int(11) unsigned NOT NULL DEFAULT '0', + `sys_perm_user` varchar(5) DEFAULT NULL, + `sys_perm_group` varchar(5) DEFAULT NULL, + `sys_perm_other` varchar(5) DEFAULT NULL, + `server_id` int(11) NOT NULL DEFAULT '0', + `customer_id` int(4) NOT NULL, + `package_id` int(4) NOT NULL, + `instance_status` int(4) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 ; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `aps_instances_settings` +-- + +CREATE TABLE IF NOT EXISTS `aps_instances_settings` ( + `id` int(4) NOT NULL AUTO_INCREMENT, + `server_id` int(11) NOT NULL DEFAULT '0', + `instance_id` int(4) NOT NULL, + `name` varchar(255) NOT NULL, + `value` text NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 ; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `aps_packages` +-- + +CREATE TABLE IF NOT EXISTS `aps_packages` ( + `id` int(4) NOT NULL AUTO_INCREMENT, + `path` varchar(255) NOT NULL, + `name` varchar(255) NOT NULL, + `category` varchar(255) NOT NULL, + `version` varchar(20) NOT NULL, + `release` int(4) NOT NULL, + `package_status` int(1) NOT NULL DEFAULT '2', + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 ; + +-- -------------------------------------------------------- + +-- +-- Tabellenstruktur für Tabelle `aps_settings` +-- + +CREATE TABLE IF NOT EXISTS `aps_settings` ( + `id` int(4) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `value` text NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 ; + +-- +-- Dumping data for table `aps_settings` +-- + +INSERT INTO `aps_settings` (`id`, `name`, `value`) VALUES(1, 'ignore-php-extension', ''); +INSERT INTO `aps_settings` (`id`, `name`, `value`) VALUES(2, 'ignore-php-configuration', ''); +INSERT INTO `aps_settings` (`id`, `name`, `value`) VALUES(3, 'ignore-webserver-module', ''); + +ALTER TABLE `client` ADD `limit_aps` int(11) NOT NULL DEFAULT '0' AFTER `limit_webdav_user`; +ALTER TABLE `client_template` ADD `limit_aps` int(11) NOT NULL DEFAULT '0' AFTER `limit_webdav_user`; \ No newline at end of file diff --git a/install/sql/ispconfig3.sql b/install/sql/ispconfig3.sql index 842d61888..8a08f3164 100644 --- a/install/sql/ispconfig3.sql +++ b/install/sql/ispconfig3.sql @@ -53,6 +53,67 @@ SET FOREIGN_KEY_CHECKS = 0; -- -------------------------------------------------------- -- -------------------------------------------------------- +-- +-- Table structure for table `aps_instances` +-- + +CREATE TABLE IF NOT EXISTS `aps_instances` ( + `id` int(4) NOT NULL AUTO_INCREMENT, + `server_id` int(11) NOT NULL DEFAULT '0', + `customer_id` int(4) NOT NULL, + `package_id` int(4) NOT NULL, + `instance_status` int(4) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 ; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `aps_instances_settings` +-- + +CREATE TABLE IF NOT EXISTS `aps_instances_settings` ( + `id` int(4) NOT NULL AUTO_INCREMENT, + `server_id` int(11) NOT NULL DEFAULT '0', + `instance_id` int(4) NOT NULL, + `name` varchar(255) NOT NULL, + `value` text NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 ; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `aps_packages` +-- + +CREATE TABLE IF NOT EXISTS `aps_packages` ( + `id` int(4) NOT NULL AUTO_INCREMENT, + `path` varchar(255) NOT NULL, + `name` varchar(255) NOT NULL, + `category` varchar(255) NOT NULL, + `version` varchar(20) NOT NULL, + `release` int(4) NOT NULL, + `package_status` int(1) NOT NULL DEFAULT '2', + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 ; + +-- -------------------------------------------------------- + +-- +-- Tabellenstruktur für Tabelle `aps_settings` +-- + +CREATE TABLE IF NOT EXISTS `aps_settings` ( + `id` int(4) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `value` text NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 ; + +-- -------------------------------------------------------- + -- -- Table structure for table `attempts_login` -- @@ -123,6 +184,7 @@ CREATE TABLE `client` ( `limit_shell_user` int(11) NOT NULL DEFAULT '0', `ssh_chroot` varchar(255) NOT NULL DEFAULT 'no,jailkit,ssh-chroot', `limit_webdav_user` int(11) NOT NULL DEFAULT '0', + `limit_aps` int(11) NOT NULL DEFAULT '0', `default_dnsserver` int(11) unsigned NOT NULL DEFAULT '1', `limit_dns_zone` int(11) NOT NULL DEFAULT '-1', `limit_dns_slave_zone` int(11) NOT NULL DEFAULT '-1', @@ -208,6 +270,7 @@ CREATE TABLE `client_template` ( `limit_shell_user` int(11) NOT NULL default '0', `ssh_chroot` varchar(255) NOT NULL DEFAULT 'no', `limit_webdav_user` int(11) NOT NULL default '0', + `limit_aps` int(11) NOT NULL DEFAULT '0', `limit_dns_zone` int(11) NOT NULL default '-1', `limit_dns_slave_zone` int(11) NOT NULL default '-1', `limit_dns_record` int(11) NOT NULL default '-1', @@ -1670,6 +1733,14 @@ CREATE TABLE `web_traffic` ( -- -------------------------------------------------------- -- -------------------------------------------------------- +-- +-- Dumping data for table `aps_settings` +-- + +INSERT INTO `aps_settings` (`id`, `name`, `value`) VALUES(1, 'ignore-php-extension', ''); +INSERT INTO `aps_settings` (`id`, `name`, `value`) VALUES(2, 'ignore-php-configuration', ''); +INSERT INTO `aps_settings` (`id`, `name`, `value`) VALUES(3, 'ignore-webserver-module', ''); + -- -------------------------------------------------------- -- diff --git a/interface/lib/classes/aps_base.inc.php b/interface/lib/classes/aps_base.inc.php new file mode 100644 index 000000000..9822caeaa --- /dev/null +++ b/interface/lib/classes/aps_base.inc.php @@ -0,0 +1,109 @@ +db = $app->db; + $this->app = $app; + + $this->log_prefix = $log_prefix; + $this->interface_mode = $interface_mode; + $this->fetch_url = 'apscatalog.com'; + $this->aps_version = '1'; + $this->packages_dir = ISPC_ROOT_PATH.'/aps_packages'; + $this->interface_pkg_dir = ISPC_ROOT_PATH.'/web/sites/aps_meta_packages'; + } + + /** + * Converts a given value to it's native representation in 1024 units + * + * @param $value the size to convert + * @return integer and string + */ + public function convertSize($value) + { + $unit = array('Bytes', 'KB', 'MB', 'GB', 'TB'); + return @round($value/pow(1024, ($i = floor(log($value, 1024)))), 2).' '.$unit[$i]; + } + + /** + * Determine a specific xpath from a given SimpleXMLElement handle. If the + * element is found, it's string representation is returned. If not, + * the return value will stay empty + * + * @param $xml_handle the SimpleXMLElement handle + * @param $query the XPath query + * @param $array define whether to return an array or a string + * @return $ret the return string + */ + protected function getXPathValue($xml_handle, $query, $array = false) + { + $ret = ''; + + $xp_result = @($xml_handle->xpath($query)) ? $xml_handle->xpath($query) : false; + if($xp_result !== false) $ret = (($array === false) ? (string)$xp_result[0] : $xp_result); + + return $ret; + } +} +?> \ No newline at end of file diff --git a/interface/lib/classes/aps_crawler.inc.php b/interface/lib/classes/aps_crawler.inc.php new file mode 100644 index 000000000..640bb349f --- /dev/null +++ b/interface/lib/classes/aps_crawler.inc.php @@ -0,0 +1,534 @@ +interface_mode) + { + if(!is_writable($this->interface_pkg_dir)) + throw new Exception('the folder '.basename($this->interface_pkg_dir).' is not writable'); + } + else + { + if(!is_writable($this->packages_dir)) + throw new Exception('the folder '.basename($this->packages_dir).' is not writable'); + } + + return true; + } + catch(Exception $e) + { + $this->app->log($this->log_prefix.'Aborting execution because '.$e->getMessage(), LOGLEVEL_ERROR); + return false; + } + } + + /** + * Remove a directory recursively + * In case of error be silent + * + * @param $dir the directory to remove + */ + private function removeDirectory($dir) + { + if(is_dir($dir)) + { + $files = scandir($dir); + foreach($files as $file) + { + if($file != '.' && $file != '..') + if(filetype($dir.'/'.$file) == 'dir') rrmdir($dir.'/'.$file); + else @unlink($dir.'/'.$file); + } + reset($files); + @rmdir($dir); + } + } + + + /** + * Fetch HTML data from one or more given URLs + * If a string is given, a string is returned, if an array of URLs should + * be fetched, the responses of the parallel queries are returned as array + * + * @param $input the string or array to fetch + * @return $ret a query response string or array + */ + private function fetchPage($input) + { + $ret = array(); + $url = array(); + $conn = array(); + + // Make sure we are working with an array, further on + if(!is_array($input)) $url[] = $input; + else $url = $input; + + // Build the single cURL handles and add them to a multi handle + $mh = curl_multi_init(); + for($i = 0; $i < count($url); $i++) + { + $conn[$i] = curl_init('http://'.$this->fetch_url.$url[$i]); + curl_setopt($conn[$i], CURLOPT_RETURNTRANSFER, true); + curl_multi_add_handle($mh, $conn[$i]); + } + + $active = 0; + do curl_multi_exec($mh, $active); + while($active > 0); + + // Get the response(s) + for($i = 0; $i < count($url); $i++) + { + $ret[$i] = curl_multi_getcontent($conn[$i]); + curl_multi_remove_handle($mh, $conn[$i]); + curl_close($conn[$i]); + } + curl_multi_close($mh); + + if(count($url) == 1) $ret = $ret[0]; + + return $ret; + } + + /** + * Fetch binary data from a given array + * The data is retrieved in binary mode and + * then directly written to an output file + * + * @param $input a specially structed array + * @see $this->startUpdate() + */ + private function fetchFiles($input) + { + $fh = array(); + $url = array(); + $conn = array(); + + // Build the single cURL handles and add them to a multi handle + $mh = curl_multi_init(); + + // Process each app + for($i = 0; $i < count($input); $i++) + { + $conn[$i] = curl_init($input[$i]['url']); + $fh[$i] = fopen($input[$i]['localtarget'], 'wb'); + + curl_setopt($conn[$i], CURLOPT_BINARYTRANSFER, true); + curl_setopt($conn[$i], CURLOPT_FILE, $fh[$i]); + curl_setopt($conn[$i], CURLOPT_TIMEOUT, 0); + curl_setopt($conn[$i], CURLOPT_FAILONERROR, 1); + curl_setopt($conn[$i], CURLOPT_FOLLOWLOCATION, 1); + + curl_multi_add_handle($mh, $conn[$i]); + } + + $active = 0; + do curl_multi_exec($mh, $active); + while($active > 0); + + // Close the handles + for($i = 0; $i < count($input); $i++) + { + fclose($fh[$i]); + curl_multi_remove_handle($mh, $conn[$i]); + curl_close($conn[$i]); + } + curl_multi_close($mh); + } + + /** + * A method to build query URLs out of a list of vendors + * + */ + private function formatVendorCallback(&$array_item, $key) + { + $array_item = str_replace(' ', '%20', $array_item); + $array_item = str_replace('http://', '', $array_item); + $array_item = '/'.$this->aps_version.'.atom?vendor='.$array_item.'&pageSize=100'; + } + + /** + * The main method which performs the actual crawling + */ + public function startCrawler() + { + try + { + // Make sure the requirements are given so that this script can execute + $req_ret = $this->checkRequirements(); + if(!$req_ret) return false; + + // Execute the open task and first fetch all vendors (APS catalog API 1.1, p. 12) + $this->app->log($this->log_prefix.'Fetching data from '.$this->fetch_url); + + $vendor_page = $this->fetchPage('/all-app/'); //$vendor_page = $this->fetchPage('/'.$this->aps_version.'/'); + preg_match_all("/\ /1.atom?vendor=typo3.org&pageSize=100 + array_walk($vendors, array($this, 'formatVendorCallback')); + + // Process all vendors in chunks of 50 entries + $vendor_chunks = array_chunk($vendors, 50); + //var_dump($vendor_chunks); + + // Get all known apps from the database and the highest known version + // Note: A dirty hack is used for numerical sorting of the VARCHAR field Version: +0 -> cast + // A longer but typesafe way would be: ORDER BY CAST(REPLACE(Version, '.', '') AS UNSIGNED) DESC + $existing_apps = $this->db->queryAllRecords("SELECT * FROM ( + SELECT name AS Name, CONCAT(version, '-', CAST(`release` AS CHAR)) AS CurrentVersion + FROM aps_packages ORDER BY REPLACE(version, '.', '')+0 DESC, `release` DESC + ) as Versions GROUP BY name"); + //var_dump($existing_apps); + + // Used for statistics later + $apps_in_repo = 0; + $apps_updated = 0; + $apps_downloaded = 0; + + $apps_to_dl = array(); + + for($i = 0; $i < count($vendor_chunks); $i++) + { + // Fetch all apps for the current chunk of vendors + $apps = $this->fetchPage($vendor_chunks[$i]); + + for($j = 0; $j < count($apps); $j++) + { + // Before parsing, make sure it's worth the work by checking if at least one app exists + $apps_count = substr_count($apps[$j], '0'); + if($apps_count == 0) // obviously this vendor provides one or more apps + { + // Rename namespaces and register them + $xml = str_replace("xmlns=", "ns=", $apps[$j]); + $sxe = new SimpleXMLElement($xml); + $namespaces = $sxe->getDocNamespaces(true); + foreach($namespaces as $ns => $url) $sxe->registerXPathNamespace($ns, $url); + + // Fetching values of interest + $app_name = parent::getXPathValue($sxe, 'entry[position()=1]/a:name'); + $app_version = parent::getXPathValue($sxe, 'entry[position()=1]/a:version'); + $app_release = parent::getXPathValue($sxe, 'entry[position()=1]/a:release'); + + // Find out a (possibly) existing package version + $ex_ver = ''; + array_walk($existing_apps, + create_function('$v, $k, $ex_ver', 'if($v["Name"] == "'.$app_name.'") $ex_ver = $v["CurrentVersion"];'), &$ex_ver); + + $new_ver = $app_version.'-'.$app_release; + $local_intf_folder = $this->interface_pkg_dir.'/'.$app_name.'-'.$new_ver.'.app.zip/'; + + // Proceed if a newer or at least equal version has been found with server mode or + // interface mode is activated and there's no valid APP-META.xml existing yet + if((!$this->interface_mode && version_compare($new_ver, $ex_ver) >= 0) + || ($this->interface_mode + && (!file_exists($local_intf_folder.'APP-META.xml') || filesize($local_intf_folder.'APP-META.xml') == 0) + ) + ) + { + // Check if we already have an old version of this app + if(!empty($ex_ver) && version_compare($new_ver, $ex_ver) == 1) $apps_updated++; + + $app_dl = parent::getXPathValue($sxe, "entry[position()=1]/link[@a:type='aps']/@href"); + $app_filesize = parent::getXPathValue($sxe, "entry[position()=1]/link[@a:type='aps']/@length"); + $app_metafile = parent::getXPathValue($sxe, "entry[position()=1]/link[@a:type='meta']/@href"); + + // Skip ASP.net packages because they can't be used at all + $asp_handler = parent::getXPathValue($sxe, '//aspnet:handler'); + $asp_permissions = parent::getXPathValue($sxe, '//aspnet:permissions'); + $asp_version = parent::getXPathValue($sxe, '//aspnet:version'); + if(!empty($asp_handler) || !empty($asp_permissions) || !empty($asp_version)) continue; + + // Interface mode (download only parts) + if($this->interface_mode) + { + // Delete an obviously out-dated version from the system and DB + if(!empty($ex_ver) && version_compare($new_ver, $ex_ver) == 1) + { + $old_folder = $this->interface_pkg_dir.'/'.$app_name.'-'.$ex_ver.'.app.zip'; + if(file_exists($old_folder)) $this->removeDirectory($old_folder); + + /* + $this->db->query("UPDATE aps_packages SET package_status = '".PACKAGE_OUTDATED."' WHERE name = '". + $this->db->quote($app_name)."' AND CONCAT(version, '-', CAST(`release` AS CHAR)) = '". + $this->db->quote($ex_ver)."';"); + */ + $tmp = $this->db->queryOneRecord("SELECT id FROM aps_packages WHERE name = '". + $this->db->quote($app_name)."' AND CONCAT(version, '-', CAST(`release` AS CHAR)) = '". + $this->db->quote($ex_ver)."';"); + $this->db->datalogUpdate('aps_packages', "package_status = ".PACKAGE_OUTDATED, 'id', $tmp['id']); + unset($tmp); + } + + // Create the local folder if not yet existing + if(!file_exists($local_intf_folder)) @mkdir($local_intf_folder, 0777, true); + + // Download the meta file + $local_metafile = $local_intf_folder.'APP-META.xml'; + if(!file_exists($local_metafile) || filesize($local_metafile) == 0) + { + $apps_to_dl[] = array('name' => 'APP-META.xml', + 'url' => $app_metafile, + 'filesize' => 0, + 'localtarget' => $local_metafile); + $apps_downloaded++; + } + + // Download package license + $license = parent::getXPathValue($sxe, "entry[position()=1]/link[@a:type='eula']/@href"); + if($license != '') + { + $local_license = $local_intf_folder.'LICENSE'; + if(!file_exists($local_license) || filesize($local_license) == 0) + { + $apps_to_dl[] = array('name' => basename($license), + 'url' => $license, + 'filesize' => 0, + 'localtarget' => $local_license); + } + } + + // Download package icon + $icon = parent::getXPathValue($sxe, "entry[position()=1]/link[@a:type='icon']/@href"); + if($icon != '') + { + $local_icon = $local_intf_folder.basename($icon); + if(!file_exists($local_icon) || filesize($local_icon) == 0) + { + $apps_to_dl[] = array('name' => basename($icon), + 'url' => $icon, + 'filesize' => 0, + 'localtarget' => $local_icon); + } + } + + // Download available screenshots + $screenshots = parent::getXPathValue($sxe, "entry[position()=1]/link[@a:type='screenshot']", true); + if(!empty($screenshots)) + { + foreach($screenshots as $screen) + { + $local_screen = $local_intf_folder.basename($screen['href']); + if(!file_exists($local_screen) || filesize($local_screen) == 0) + { + $apps_to_dl[] = array('name' => basename($screen['href']), + 'url' => $screen['href'], + 'filesize' => 0, + 'localtarget' => $local_screen); + } + } + } + } + else // Server mode (download whole ZIP archive) + { + // Delete an obviously out-dated version from the system + if(!empty($ex_ver) && version_compare($new_ver, $ex_ver) == 1) + { + $old_file = $this->packages_dir.'/'.$app_name.'-'.$ex_ver.'.app.zip'; + if(file_exists($old_file)) $this->removeDirectory($old_file); + } + + // Attention: $new_ver can also be == $ex_ver (according to version_compare >= 0) + $local_zip = $this->packages_dir.'/'.$app_name.'-'.$new_ver.'.app.zip'; + + // Before re-downloading a file, make sure it's not yet existing on HDD (due to DB inconsistency) + if((file_exists($local_zip) && (filesize($local_zip) == $app_filesize)) === false) + { + $apps_to_dl[] = array('name' => $app_name, + 'url' => $app_dl, + 'filesize' => $app_filesize, + 'localtarget' => $local_zip); + $apps_downloaded++; + } + } + } + + unset($sxe); + $apps_in_repo++; + } + } + //var_dump($apps); + + // For memory reasons, unset the current vendor and his apps + unset($apps); + } + + // Shuffle the download array (in order to compensate unexpected php aborts) + shuffle($apps_to_dl); + + // After collecting all provisioned apps, download them + $apps_to_dl_chunks = array_chunk($apps_to_dl, 10); + + for($i = 0; $i < count($apps_to_dl_chunks); $i++) + { + $this->fetchFiles($apps_to_dl_chunks[$i]); + + // Check the integrity of all downloaded files + // but exclude cases where no filesize is available (i.e. screenshot or metafile download) + for($j = 0; $j < count($apps_to_dl_chunks[$i]); $j++) + { + if($apps_to_dl_chunks[$i][$j]['filesize'] != 0 && + $apps_to_dl_chunks[$i][$j]['filesize'] != filesize($apps_to_dl_chunks[$i][$j]['localtarget'])) + { + $this->app->log($this->log_prefix.' The filesize of the package "'. + $apps_to_dl_chunks[$i][$j]['name'].'" is wrong. Download failure?', LOGLEVEL_WARN); + } + } + } + + $this->app->log($this->log_prefix.'Processed '.$apps_in_repo. + ' apps from the repo. Downloaded '.$apps_updated. + ' updates, '.$apps_downloaded.' new apps'); + } + catch(Exception $e) + { + $this->app->log($this->log_prefix.$e->getMessage(), LOGLEVEL_ERROR); + return false; + } + } + + /** + * Read in all possible packages from the interface packages folder and + * check if they are not ASP.net code (as this can't be processed). + * + * Note: There's no need to check if the packages to register are newer + * than those in the database because this already happended in startCrawler() + */ + public function parseFolderToDB() + { + try + { + // This method must be used in server mode + if(!$this->interface_mode) return false; + + $pkg_list = array(); + + // Read in every package having a correct filename + $temp_handle = @dir($this->interface_pkg_dir); + if(!$temp_handle) throw new Exception('The temp directory is not accessible'); + while($folder = $temp_handle->read()) + if(substr($folder, -8) == '.app.zip') $pkg_list[] = $folder; + $temp_handle->close(); + + // If no packages are available -> exception (because at this point there should exist packages) + if(empty($pkg_list)) throw new Exception('No packages to read in'); + + // Get registered packages and mark non-existant packages with an error code to omit the install + $existing_packages = array(); + $path_query = $this->db->queryAllRecords('SELECT path AS Path FROM aps_packages;'); + foreach($path_query as $path) $existing_packages[] = $path['Path']; + $diff = array_diff($existing_packages, $pkg_list); + foreach($diff as $todelete) + /*$this->db->query("UPDATE aps_packages SET package_status = '".PACKAGE_ERROR_NOMETA."' + WHERE path = '".$this->db->quote($todelete)."';");*/ + $tmp = $this->db->queryOneRecord("SELECT id FROM aps_packages WHERE path = '".$this->db->quote($todelete)."';"); + $this->db->datalogUpdate('aps_packages', "package_status = ".PACKAGE_ERROR_NOMETA, 'id', $tmp['id']); + unset($tmp); + + // Register all new packages + $new_packages = array_diff($pkg_list, $existing_packages); + foreach($new_packages as $pkg) + { + // Load in meta file if existing and register its namespaces + $metafile = $this->interface_pkg_dir.'/'.$pkg.'/APP-META.xml'; + if(!file_exists($metafile)) + { + $this->app->log($this->log_prefix.'Cannot read metadata from '.$pkg, LOGLEVEL_ERROR); + continue; + } + + $metadata = file_get_contents($metafile); + $metadata = str_replace("xmlns=", "ns=", $metadata); + $sxe = new SimpleXMLElement($metadata); + $namespaces = $sxe->getDocNamespaces(true); + foreach($namespaces as $ns => $url) $sxe->registerXPathNamespace($ns, $url); + + // Insert the new package + $pkg_name = parent::getXPathValue($sxe, 'name'); + $pkg_category = parent::getXPathValue($sxe, '//category'); + $pkg_version = parent::getXPathValue($sxe, 'version'); + $pkg_release = parent::getXPathValue($sxe, 'release'); + + /* + $this->db->query("INSERT INTO `aps_packages` + (`path`, `name`, `category`, `version`, `release`, `package_status`) VALUES + ('".$this->db->quote($pkg)."', '".$this->db->quote($pkg_name)."', + '".$this->db->quote($pkg_category)."', '".$this->db->quote($pkg_version)."', + ".$this->db->quote($pkg_release).", ".PACKAGE_ENABLED.");"); + */ + + $insert_data = "(`path`, `name`, `category`, `version`, `release`, `package_status`) VALUES + ('".$this->db->quote($pkg)."', '".$this->db->quote($pkg_name)."', + '".$this->db->quote($pkg_category)."', '".$this->db->quote($pkg_version)."', + ".$this->db->quote($pkg_release).", ".PACKAGE_ENABLED.");"; + + $app->db->datalogInsert('aps_packages', $insert_data, 'id'); + } + } + catch(Exception $e) + { + $this->app->log($this->log_prefix.$e->getMessage(), LOGLEVEL_ERROR); + $this->app->error($e->getMessage()); + return false; + } + } +} +?> \ No newline at end of file diff --git a/interface/lib/classes/aps_guicontroller.inc.php b/interface/lib/classes/aps_guicontroller.inc.php new file mode 100644 index 000000000..0b4038fbd --- /dev/null +++ b/interface/lib/classes/aps_guicontroller.inc.php @@ -0,0 +1,770 @@ +getDocNamespaces(true); + foreach($namespaces as $ns => $url) $sxe->registerXPathNamespace($ns, $url); + + return $sxe; + } + + /** + * Applies a RegEx pattern onto a location path in order to secure it against + * code injections and invalid input + * + * @param $location_unfiltered the file path to secure + * @return $location + */ + private function secureLocation($location_unfiltered) + { + // Filter invalid slashes from string + $location = preg_replace(array('#/+#', '#\.+#', '#\0+#', '#\\\\+#'), + array('/', '', '', '/'), + $location_unfiltered); + + // Remove a beginning or trailing slash + if(substr($location, -1) == '/') $location = substr($location, 0, strlen($location) - 1); + if(substr($location, 0, 1) == '/') $location = substr($location, 1); + + return $location; + } + + /** + * Gets the CustomerID (ClientID) which belongs to a specific domain + * + * @param $domain the domain + * @return $customerid + */ + private function getCustomerIDFromDomain($domain) + { + $customerid = ''; + + $customerdata = $this->db->queryOneRecord("SELECT client_id FROM sys_group, web_domain + WHERE web_domain.sys_groupid = sys_group.groupid + AND web_domain.domain = '".$this->db->quote($domain)."';"); + if(!empty($customerdata)) $customerid = $customerdata['client_id']; + + return $customerid; + } + + /** + * Returns the server_id for an already installed instance. Is actually + * just a little helper method to avoid redundant code + * + * @param $instanceid the instance to process + * @return $webserver_id the server_id + */ + private function getInstanceDataForDatalog($instanceid) + { + $webserver_id = ''; + + $websrv = $this->db->queryOneRecord("SELECT server_id FROM web_domain + WHERE domain = (SELECT value FROM aps_instances_settings + WHERE name = 'main_domain' AND instance_id = ".$this->db->quote($instanceid).");"); + + // If $websrv is empty, an error has occured. Domain no longer existing? Settings table damaged? + // Anyhow, remove this instance record because it's not useful at all + if(empty($websrv)) + { + $this->db->query("DELETE FROM aps_instances WHERE id = ".$this->db->quote($instanceid).";"); + $this->db->query("DELETE FROM aps_instances_settings WHERE instance_id = ".$this->db->quote($instanceid).";"); + } + else $webserver_id = $websrv['server_id']; + + return $webserver_id; + } + + /** + * Finds out if there is a newer package version for + * a given (possibly valid) package ID + * + * @param $id the ID to check + * @return $newer_pkg_id the newer package ID + */ + public function getNewestPackageID($id) + { + if(preg_match('/^[0-9]+$/', $id) != 1) return 0; + + $result = $this->db->queryOneRecord("SELECT id, name, + CONCAT(version, '-', CAST(`release` AS CHAR)) AS current_version + FROM aps_packages + WHERE name = (SELECT name FROM aps_packages WHERE id = ".$this->db->quote($id).") + ORDER BY REPLACE(version, '.', '')+0 DESC, `release` DESC"); + + if(!empty($result) && ($id != $result['id'])) return $result['id']; + + return 0; + } + + /** + * Validates a given package ID + * + * @param $id the ID to check + * @param $is_admin a flag to allow locked IDs too (for admin calls) + * @return boolean + */ + public function isValidPackageID($id, $is_admin = false) + { + if(preg_match('/^[0-9]+$/', $id) != 1) return false; + + $sql_ext = (!$is_admin) ? + 'package_status = '.PACKAGE_ENABLED.' AND' : + '(package_status = '.PACKAGE_ENABLED.' OR package_status = '.PACKAGE_LOCKED.') AND'; + + $result = $this->db->queryOneRecord("SELECT id FROM aps_packages WHERE ".$sql_ext." id = ".$this->db->quote($id).";"); + if(!$result) return false; + + return true; + } + + /** + * Validates a given instance ID + * + * @param $id the ID to check + * @param $client_id the calling client ID + * @param $is_admin a flag to ignore the client ID check for admins + * @return boolean + */ + public function isValidInstanceID($id, $client_id, $is_admin = false) + { + if(preg_match('/^[0-9]+$/', $id) != 1) return false; + + // Only filter if not admin + $sql_ext = (!$is_admin) ? 'customer_id = '.$this->db->quote($client_id).' AND' : ''; + + $result = $this->db->queryOneRecord('SELECT id FROM aps_instances WHERE '.$sql_ext.' id = '.$this->db->quote($id).';'); + if(!$result) return false; + + return true; + } + + /** + * Creates a new database record for the package instance and + * an install task + * + * @param $settings the settings to enter into the DB + * @param $packageid the PackageID + */ + public function createPackageInstance($settings, $packageid) + { + global $app; + + $webserver_id = 0; + $websrv = $this->db->queryOneRecord("SELECT * FROM web_domain WHERE domain = '".$this->db->quote($settings['main_domain'])."';"); + if(!empty($websrv)) $webserver_id = $websrv['server_id']; + $customerid = $this->getCustomerIDFromDomain($settings['main_domain']); + + if(empty($settings) || empty($customerid) || empty($webserver_id)) return false; + + //* Get server config of the web server + $this->app->uses("getconf"); + $web_config = $this->app->getconf->get_server_config(intval($websrv["server_id"]),'web'); + + //* Set mysql mode to php-fcgi and enable suexec in website on apache servers + if($web_config['server_type'] == 'apache') { + if($websrv['php'] != 'fast-cgi' || $websrv['suexec'] != 'y') { + $app->db->datalogUpdate('web_domain', "php = 'fast-cgi', suexec = 'y'", 'domain_id', $websrv['domain_id']); + } + } + + //* Create the MySQL database for the application + $pkg = $this->db->queryOneRecord('SELECT * FROM aps_packages WHERE id = '.$this->db->quote($packageid).';'); + $metafile = $this->interface_pkg_dir.'/'.$pkg['path'].'/APP-META.xml'; + $sxe = $this->readInMetaFile($metafile); + + $db_id = parent::getXPathValue($sxe, '//db:id'); + if (!empty($db_id)) { + $global_config = $app->getconf->get_global_config('sites'); + + $tmp = array(); + $tmp['parent_domain_id'] = $websrv['domain_id']; + $tmp['sys_groupid'] = $websrv['sys_groupid']; + $dbname_prefix = replacePrefix($global_config['dbname_prefix'], $tmp); + $dbuser_prefix = replacePrefix($global_config['dbuser_prefix'], $tmp); + unset($tmp); + + //* get the default database server of the client + $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']); + if(is_array($client) && $client['default_dbserver'] > 0 && $client['default_dbserver'] != $websrv['server_id']) { + $mysql_db_server_id = $client['default_dbserver']; + $dbserver_config = $web_config = $app->getconf->get_server_config(intval($mysql_db_server_id),'server'); + $mysql_db_host = $dbserver_config['ip_address']; + $mysql_db_remote_access = 'y'; + $mysql_db_remote_ips = $dbserver_config['ip_address']; + } else { + $mysql_db_server_id = $websrv['server_id']; + $mysql_db_host = 'localhost'; + $mysql_db_remote_access = 'n'; + $mysql_db_remote_ips = ''; + } + + //* Find a free db name for the app + for($n = 1; $n <= 1000; $n++) { + $mysql_db_name = $dbname_prefix.'aps'.$n; + $mysql_db_user = $dbuser_prefix.'aps'.$n; + $tmp = $app->db->queryOneRecord("SELECT count(database_id) as number FROM web_database WHERE database_name = '".$app->db->quote($mysql_db_user)."' OR database_user = '".$app->db->quote($mysql_db_name)."'"); + if($tmp['number'] == 0) break; + } + + //* Create the mysql database + $insert_data = "(`sys_userid`, `sys_groupid`, `sys_perm_user`, `sys_perm_group`, `sys_perm_other`, `server_id`, `parent_domain_id`, `type`, `database_name`, `database_user`, `database_password`, `database_charset`, `remote_access`, `remote_ips`, `backup_copies`, `active`, `backup_interval`) + VALUES( ".$websrv['sys_userid'].", ".$websrv['sys_groupid'].", 'riud', '".$websrv['sys_perm_group']."', '', $mysql_db_server_id, ".$websrv['domain_id'].", 'mysql', '$mysql_db_name', '$mysql_db_user', '$mysql_db_password', '', '$mysql_db_remote_access', '$mysql_db_remote_ips', ".$websrv['backup_copies'].", 'y', '".$websrv['backup_interval']."')"; + $app->db->datalogInsert('web_database', $insert_data, 'database_id'); + + //* Add db details to package settings + $settings['main_database_host'] = $mysql_db_host; + $settings['main_database_name'] = $mysql_db_name; + $settings['main_database_login'] = $mysql_db_user; + + } + + //* Insert new package instance + $insert_data = "(`sys_userid`, `sys_groupid`, `sys_perm_user`, `sys_perm_group`, `sys_perm_other`, `server_id`, `customer_id`, `package_id`, `instance_status`) VALUES (".$websrv['sys_userid'].", ".$websrv['sys_groupid'].", 'riud', '".$websrv['sys_perm_group']."', '', ".$this->db->quote($webserver_id).",".$this->db->quote($customerid).", ".$this->db->quote($packageid).", ".INSTANCE_PENDING.")"; + $InstanceID = $app->db->datalogInsert('aps_instances', $insert_data, 'id'); + + //* Insert all package settings + if(is_array($settings)) { + foreach($settings as $key => $value) { + $insert_data = "(server_id, instance_id, name, value) VALUES (".$this->db->quote($webserver_id).",".$this->db->quote($InstanceID).", '".$this->db->quote($key)."', '".$this->db->quote($value)."')"; + $this->db->datalogInsert('aps_instances_settings', $insert_data, 'id'); + } + } + + //* Set package status to install afetr we inserted the settings + $app->db->datalogUpdate('aps_instances', "instance_status = ".INSTANCE_INSTALL, 'id', $InstanceID); + } + + /** + * Sets the status of an instance to "should be removed" and creates a + * datalog entry to give the ISPConfig server a real removal advice + * + * @param $instanceid the instance to delete + */ + public function deleteInstance($instanceid) + { + /* + $this->db->query("UPDATE aps_instances SET instance_status = ".INSTANCE_REMOVE." WHERE id = ".$instanceid.";"); + + $webserver_id = $this->getInstanceDataForDatalog($instanceid); + if($webserver_id == '') return; + + // Create a sys_datalog entry for deletion + $datalog = array('Instance_id' => $instanceid, 'server_id' => $webserver_id); + $this->db->datalogSave('aps', 'DELETE', 'id', $instanceid, array(), $datalog); + */ + $this->db->datalogUpdate('aps_instances', "instance_status = ".INSTANCE_REMOVE, 'id', $instanceid); + } + + /** + * Sets the status of an instance to "installation planned" and creates a + * datalog entry to re-install the package. The existing package is simply overwritten. + * + * @param $instanceid the instance to delete + */ + public function reinstallInstance($instanceid) + { + /* + $this->db->query("UPDATE aps_instances SET instance_status = ".INSTANCE_INSTALL." WHERE id = ".$instanceid.";"); + + $webserver_id = $this->getInstanceDataForDatalog($instanceid); + if($webserver_id == '') return; + + // Create a sys_datalog entry for re-installation + $datalog = array('instance_id' => $instanceid, 'server_id' => $webserver_id); + $this->db->datalogSave('aps', 'INSERT', 'id', $instanceid, array(), $datalog); + */ + $this->db->datalogUpdate('aps_instances', "instance_status = ".INSTANCE_INSTALL, 'id', $instanceid); + } + + /** + * Read the settings to be filled when installing + * + * @param $id the internal ID of the package + * @return array + */ + public function getPackageSettings($id) + { + $pkg = $this->db->queryOneRecord('SELECT * FROM aps_packages WHERE id = '.$this->db->quote($id).';'); + + // Load in meta file if existing and register its namespaces + $metafile = $this->interface_pkg_dir.'/'.$pkg['path'].'/APP-META.xml'; + if(!file_exists($metafile)) + return array('error' => 'The metafile for '.$settings['Name'].' couldn\'t be found'); + + $sxe = $this->readInMetaFile($metafile); + + $groupsettings = parent::getXPathValue($sxe, '//settings/group/setting', true); + if(empty($groupsettings)) return array(); + + $settings = array(); + foreach($groupsettings as $setting) + { + $setting_id = strval($setting['id']); + + if($setting['type'] == 'string' || $setting['type'] == 'email' || $setting['type'] == 'integer' + || $setting['type'] == 'float' || $setting['type'] == 'domain-name') + { + $settings[] = array('SettingID' => $setting_id, + 'SettingName' => $setting->name, + 'SettingDescription' => $setting->description, + 'SettingType' => $setting['type'], + 'SettingInputType' => 'string', + 'SettingDefaultValue' => strval($setting['default-value']), + 'SettingRegex' => $setting['regex'], + 'SettingMinLength' => $setting['min-length'], + 'SettingMaxLength' => $setting['max-length']); + } + else if($setting['type'] == 'password') + { + $settings[] = array('SettingID' => $setting_id, + 'SettingName' => $setting->name, + 'SettingDescription' => $setting->description, + 'SettingType' => 'password', + 'SettingInputType' => 'password', + 'SettingDefaultValue' => '', + 'SettingRegex' => $setting['regex'], + 'SettingMinLength' => $setting['min-length'], + 'SettingMaxLength' => $setting['max-length']); + } + else if($setting['type'] == 'boolean') + { + $settings[] = array('SettingID' => $setting_id, + 'SettingName' => $setting->name, + 'SettingDescription' => $setting->description, + 'SettingType' => 'boolean', + 'SettingInputType' => 'checkbox', + 'SettingDefaultValue' => strval($setting['default-value'])); + } + else if($setting['type'] == 'enum') + { + $choices = array(); + foreach($setting->choice as $choice) + { + $choices[] = array('EnumID' => strval($choice['id']), + 'EnumName' => $choice->name); + } + $settings[] = array('SettingID' => $setting_id, + 'SettingName' => $setting->name, + 'SettingDescription' => $setting->description, + 'SettingType' => 'enum', + 'SettingInputType' => 'select', + 'SettingDefaultValue' => strval($setting['default-value']), + 'SettingChoices' => $choices); + } + } + + return $settings; + } + + /** + * Validates the user input according to the settings array and + * delivers errors if occurring + * + * @param $input the user $_POST array + * @param $pkg_details the package details + * @param $settings the package settings array + * @return array in this structure: + * array(2) { + * ["input"]=> ... + * ["errors"]=> ... + * } + */ + public function validateInstallerInput($postinput, $pkg_details, $domains, $settings = array()) + { + $ret = array(); + $input = array(); + $error = array(); + + // Main domain (obligatory) + if(isset($postinput['main_domain'])) + { + if(!in_array($postinput['main_domain'], $domains)) $error[] = $this->app->lng('error_main_domain'); + else $input['main_domain'] = $postinput['main_domain']; + } + else $error[] = $this->app->lng('error_main_domain'); + + // Main location (not obligatory but must be supplied) + if(isset($postinput['main_location'])) + { + $temp_errstr = ''; + // It can be empty but if the user did write something, check it + $userinput = false; + if(strlen($postinput['main_location']) > 0) $userinput = true; + + // Filter invalid input slashes (twice!) + $main_location = $this->secureLocation($postinput['main_location']); + $main_location = $this->secureLocation($main_location); + // Only allow digits, words, / and - + $main_location = preg_replace("/[^\d\w\/\-]/i", "", $main_location); + if($userinput && (strlen($main_location) == 0)) $temp_errstr = $this->app->lng('error_inv_main_location'); + + // Find out document_root and make sure no apps are installed twice to one location + if(in_array($postinput['main_domain'], $domains)) + { + $docroot = $this->db->queryOneRecord("SELECT document_root FROM web_domain + WHERE domain = '".$this->db->quote($postinput['main_domain'])."';"); + $new_path = $docroot['document_root']; + if(substr($new_path, -1) != '/') $new_path .= '/'; + $new_path .= $main_location; + + // Get the $customerid which belongs to the selected domain + $customerid = $this->getCustomerIDFromDomain($postinput['main_domain']); + + // First get all domains used for an install, then their loop them + // and get the corresponding document roots as well as the defined + // locations. If an existing doc_root + location matches with the + // new one -> error + $instance_domains = $this->db->queryAllRecords("SELECT instance_id, s.value AS domain + FROM aps_instances AS i, aps_instances_settings AS s + WHERE i.id = s.instance_id AND s.name = 'main_domain' + AND i.customer_id = '".$this->db->quote($customerid)."';"); + for($i = 0; $i < count($instance_domains); $i++) + { + $used_path = ''; + + $doc_root = $this->db->queryOneRecord("SELECT document_root FROM web_domain + WHERE domain = '".$this->db->quote($instance_domains[$i]['domain'])."';"); + + // Probably the domain settings were changed later, so make sure the doc_root + // is not empty for further validation + if(!empty($doc_root)) + { + $used_path = $docroot['document_root']; + if(substr($used_path, -1) != '/') $used_path .= '/'; + + $location_for_domain = $this->db->queryOneRecord("SELECT value + FROM aps_instances_settings WHERE name = 'main_location' + AND instance_id = '".$this->db->quote($instance_domains[$i]['instance_id'])."';"); + + // The location might be empty but the DB return must not be false! + if($location_for_domain) $used_path .= $location_for_domain['value']; + + if($new_path == $used_path) + { + $temp_errstr = $this->app->lng('error_used_location'); + break; + } + } + } + } + else $temp_errstr = $this->app->lng('error_main_domain'); + + if($temp_errstr == '') $input['main_location'] = htmlspecialchars($main_location); + else $error[] = $temp_errstr; + } + else $error[] = $this->app->lng('error_no_main_location'); + + // License (the checkbox must be set) + if(isset($pkg_details['License need agree']) + && $pkg_details['License need agree'] == 'true') + { + if(isset($postinput['license']) && $postinput['license'] == 'on') $input['license'] = 'true'; + else $error[] = $this->app->lng('error_license_agreement'); + } + + // Database + if(isset($pkg_details['Requirements Database']) + && $pkg_details['Requirements Database'] != '') + { + if(isset($postinput['main_database_password'])) + { + if($postinput['main_database_password'] == '') $error[] = $this->app->lng('error_no_database_pw'); + else if(strlen($postinput['main_database_password']) > 8) + $input['main_database_password'] = htmlspecialchars($postinput['main_database_password']); + else $error[] = $this->app->lng('error_short_database_pw'); + } + else $error[] = $this->app->lng('error_no_database_pw'); + } + + // Validate the package settings + foreach($settings as $setting) + { + $temp_errstr = ''; + $setting_id = strval($setting['SettingID']); + + // We assume that every setting must be set + if((isset($postinput[$setting_id]) && ($postinput[$setting_id] != '')) + || ($setting['SettingType'] == 'boolean')) + { + if($setting['SettingType'] == 'string' || $setting['SettingType'] == 'password') + { + if(intval($setting['SettingMinLength']) != 0 + && strlen($postinput[$setting_id]) < intval($setting['SettingMinLength'])) + $temp_errstr = sprintf($this->app->lng('error_short_value_for'), $setting['setting_name']); + + if(intval($setting['SettingMaxLength']) != 0 + && strlen($postinput[$setting_id]) > intval($setting['SettingMaxLength'])) + $temp_errstr = sprintf($this->app->lng('error_long_value_for'), $setting['setting_name']); + + if(isset($setting['SettingRegex']) + && !preg_match("/".$setting['SettingRegex']."/", $postinput[$setting_id])) + $temp_errstr = sprintf($this->app->lng('error_inv_value_for'), $setting['setting_name']); + } + else if($setting['SettingType'] == 'email') + { + if(filter_var(strtolower($postinput[$setting_id]), FILTER_VALIDATE_EMAIL) === false) + $temp_errstr = sprintf($this->app->lng('error_inv_email_for'), $setting['setting_name']); + } + else if($setting['SettingType'] == 'domain-name') + { + 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\.\,\?\'\\\+&%\$#\=~_\-]+))*$", + $postinput[$setting_id])) + $temp_errstr = sprintf($this->app->lng('error_inv_domain_for'), $setting['setting_name']); + } + else if($setting['SettingType'] == 'integer') + { + if(filter_var($postinput[$setting_id], FILTER_VALIDATE_INT) === false) + $temp_errstr = sprintf($this->app->lng('error_inv_integer_for'), $setting['setting_name']); + } + else if($setting['SettingType'] == 'float') + { + if(filter_var($postinput[$setting_id], FILTER_VALIDATE_FLOAT) === false) + $temp_errstr = sprintf($this->app->lng('error_inv_float_for'), $setting['setting_name']); + } + else if($setting['SettingType'] == 'boolean') + { + // If we have a boolean value set, it must be either true or false + if(!isset($postinput[$setting_id])) $postinput[$setting_id] = 'false'; + else if(isset($postinput[$setting_id]) && $postinput[$setting_id] != 'true') + $postinput[$setting_id] = 'true'; + } + else if($setting['SettingType'] == 'enum') + { + $found = false; + for($i = 0; $i < count($setting['SettingChoices']); $i++) + { + if($setting['SettingChoices'][$i]['EnumID'] == $postinput[$setting_id]) + $found = true; + } + if(!$found) $temp_errstr = sprintf($this->app->lng('error_inv_value_for'), $setting['SettingName']); + } + + if($temp_errstr == '') $input[$setting_id] = $postinput[$setting_id]; + else $error[] = $temp_errstr; + } + else $error[] = sprintf($this->app->lng('error_no_value_for'), $setting['SettingName']); + } + + $ret['input'] = $input; + $ret['error'] = array_unique($error); + + return $ret; + } + + /** + * Read the metadata of a package and returns some content + * + * @param $id the internal ID of the package + * @return array + */ + public function getPackageDetails($id) + { + $pkg = $this->db->queryOneRecord('SELECT * FROM aps_packages WHERE id = '.$this->db->quote($id).';'); + + // Load in meta file if existing and register its namespaces + $metafile = $this->interface_pkg_dir.'/'.$pkg['path'].'/APP-META.xml'; + if(!file_exists($metafile)) + return array('error' => 'The metafile for '.$pkg['name'].' couldn\'t be found'); + + $metadata = file_get_contents($metafile); + $metadata = str_replace("xmlns=", "ns=", $metadata); + $sxe = new SimpleXMLElement($metadata); + $namespaces = $sxe->getDocNamespaces(true); + foreach($namespaces as $ns => $url) $sxe->registerXPathNamespace($ns, $url); + + $pkg['Summary'] = htmlspecialchars(parent::getXPathValue($sxe, '//summary')); + $pkg['Homepage'] = parent::getXPathValue($sxe, '//homepage'); + $pkg['Description'] = nl2br(htmlspecialchars(trim(parent::getXPathValue($sxe, '//description')))); + $pkg['Config script'] = strtoupper(parent::getXPathValue($sxe, '//configuration-script-language')); + $installed_size = parent::getXPathValue($sxe, '//installed-size'); + $pkg['Installed Size'] = (!empty($installed_size)) ? parent::convertSize((int)$installed_size) : ''; + + // License + $pkg['License need agree'] = parent::getXPathValue($sxe, '//license/@must-accept'); + $pkg['License name'] = parent::getXPathValue($sxe, '//license/text/name'); // might be empty + $pkg['License type'] = 'file'; // default type + $pkg['License content'] = ''; // default license filename on local system + $license_url = parent::getXPathValue($sxe, '//license/text/url'); + if(!empty($license_url)) + { + $pkg['License type'] = 'url'; + $pkg['License content'] = htmlspecialchars($license_url); + } + else + { + $lic = @file_get_contents($this->interface_pkg_dir.'/'.$pkg['path'].'/LICENSE'); + $pkg['License content'] = htmlentities($lic, ENT_QUOTES, 'ISO-8859-1'); + } + + // Languages + $languages = parent::getXPathValue($sxe, '//languages/language', true); + $pkg['Languages'] = (is_array($languages)) ? implode(' ', $languages) : ''; + + // Icon + $icon = parent::getXPathValue($sxe, '//icon/@path'); + if(!empty($icon)) + { + // Using parse_url() to filter malformed URLs + $path = dirname(parse_url($_SERVER['PHP_SELF'], PHP_URL_PATH)).'/'. + basename($this->interface_pkg_dir).'/'.$pkg['path'].'/'.basename((string)$icon); + $pkg['Icon'] = $path; + } + else $pkg['Icon'] = ''; + + // Screenshots + $screenshots = parent::getXPathValue($sxe, '//screenshot', true); + if(!empty($screenshots)) + { + foreach($screenshots as $screen) + { + // Using parse_url() to filter malformed URLs + $path = dirname(parse_url($_SERVER['PHP_SELF'], PHP_URL_PATH)).'/'. + basename($this->interface_pkg_dir).'/'.$pkg['path'].'/'.basename((string)$screen['path']); + + $pkg['Screenshots'][] = array('ScreenPath' => $path, + 'ScreenDescription' => htmlspecialchars(trim((string)$screen->description))); + } + } + else $pkg['Screenshots'] = ''; // if no screenshots are available, set the variable though + + // Changelog + $changelog = parent::getXPathValue($sxe, '//changelog/version', true); + if(!empty($changelog)) + { + foreach($changelog as $change) + { + $entries = array(); + foreach($change->entry as $entry) $entries[] = htmlspecialchars(trim((string)$entry)); + + $pkg['Changelog'][] = array('ChangelogVersion' => (string)$change['version'], + 'ChangelogDescription' => implode('
', $entries)); + } + } + + else $pkg['Changelog'] = ''; + + // PHP extensions + $php_extensions = parent::getXPathValue($sxe, '//php:extension', true); + $php_ext = ''; + if(!empty($php_extensions)) + { + foreach($php_extensions as $extension) + { + if(strtolower($extension) == 'php') continue; + $php_ext .= $extension.' '; + } + } + $pkg['Requirements PHP extensions'] = trim($php_ext); + + // PHP bool options + $pkg['Requirements PHP settings'] = ''; + $php_bool_options = array('allow-url-fopen', 'file-uploads', 'magic-quotes-gpc', + 'register-globals', 'safe-mode', 'short-open-tag'); + foreach($php_bool_options as $option) + { + $value = parent::getXPathValue($sxe, '//php:'.$option); + if(!empty($value)) + { + $option = str_replace('-', '_', $option); + $value = str_replace(array('false', 'true'), array('off', 'on'), $value); + $pkg['Requirements PHP settings'][] = array('PHPSettingName' => $option, + 'PHPSettingValue' => $value); + } + } + + // PHP integer value settings + $memory_limit = parent::getXPathValue($sxe, '//php:memory-limit'); + if(!empty($memory_limit)) + $pkg['Requirements PHP settings'][] = array('PHPSettingName' => 'memory_limit', + 'PHPSettingValue' => parent::convertSize((int)$memory_limit)); + + $max_exec_time = parent::getXPathValue($sxe, '//php:max-execution-time'); + if(!empty($max_exec_time)) + $pkg['Requirements PHP settings'][] = array('PHPSettingName' => 'max-execution-time', + 'PHPSettingValue' => $max_exec_time); + + $post_max_size = parent::getXPathValue($sxe, '//php:post-max-size'); + if(!empty($post_max_size)) + $pkg['Requirements PHP settings'][] = array('PHPSettingName' => 'post_max_size', + 'PHPSettingValue' => parent::convertSize((int)$post_max_size)); + + // Get supported PHP versions + $pkg['Requirements Supported PHP versions'] = ''; + $php_min_version = parent::getXPathValue($sxe, '//php:version/@min'); + $php_max_not_including = parent::getXPathValue($sxe, '//php:version/@max-not-including'); + if(!empty($php_min_version) && !empty($php_max_not_including)) + $pkg['Requirements Supported PHP versions'] = $php_min_version.' - '.$php_max_not_including; + else if(!empty($php_min_version)) + $pkg['Requirements Supported PHP versions'] = '> '.$php_min_version; + else if(!empty($php_max_not_including)) + $pkg['Requirements Supported PHP versions'] = '< '.$php_min_version; + + // Database + $db_id = parent::getXPathValue($sxe, '//db:id'); + $db_server_type = parent::getXPathValue($sxe, '//db:server-type'); + $db_min_version = parent::getXPathValue($sxe, '//db:server-min-version'); + if(!empty($db_id)) + { + $db_server_type = str_replace('postgresql', 'PostgreSQL', $db_server_type); + $db_server_type = str_replace('microsoft:sqlserver', 'MSSQL', $db_server_type); + $db_server_type = str_replace('mysql', 'MySQL', $db_server_type); + + $pkg['Requirements Database'] = $db_server_type; + if(!empty($db_min_version)) $pkg['Requirements Database'] .= ' > '.$db_min_version; + } + else $pkg['Requirements Database'] = ''; + + return $pkg; + } +} +?> \ No newline at end of file diff --git a/interface/web/js/scrigo.js.php b/interface/web/js/scrigo.js.php index 4a33f9671..75200ab6c 100644 --- a/interface/web/js/scrigo.js.php +++ b/interface/web/js/scrigo.js.php @@ -203,6 +203,9 @@ function loadContent(pagename) { var pageContentObject2 = jQuery.ajax({ type: "GET", url: pagename, dataType: "html", + beforeSend: function() { + jQuery('#pageContent').html('
'); + }, success: function(data, textStatus, jqXHR) { if(jqXHR.responseText.indexOf('HEADER_REDIRECT:') > -1) { var parts = jqXHR.responseText.split(':'); @@ -215,9 +218,9 @@ function loadContent(pagename) { //var reponse = jQuery(jqXHR.responseText); //var reponseScript = reponse.filter("script"); //jQuery.each(reponseScript, function(idx, val) { eval(val.text); } ); + jQuery('#pageContent').html(jqXHR.responseText); } - }, error: function() { reportError('Ajax Request was not successful. 113'); diff --git a/interface/web/sites/aps_availablepackages_list.php b/interface/web/sites/aps_availablepackages_list.php new file mode 100644 index 000000000..3e3b83bf3 --- /dev/null +++ b/interface/web/sites/aps_availablepackages_list.php @@ -0,0 +1,56 @@ +load('aps_base'); + +// Path to the list definition file +$list_def_file = "list/aps_availablepackages.list.php"; + +// Check the module permissions +$app->auth->check_module_permissions('sites'); + +// Load needed classes +$app->uses('tpl,listform_actions'); + +$app->listform_actions->SQLOrderBy = 'ORDER BY name, version'; +// Show only unlocked packages to clients and (un-)lockable packages to admins +if($_SESSION['s']['user']['typ'] != 'admin') $app->listform_actions->SQLExtWhere = 'package_status = '.PACKAGE_ENABLED; +else $app->listform_actions->SQLExtWhere = '(package_status = '.PACKAGE_ENABLED.' OR package_status = '.PACKAGE_LOCKED.')'; + +// Get package amount +$pkg_count = $app->db->queryOneRecord("SELECT COUNT(*) FROM aps_packages"); +$app->tpl->setVar("package_count", $pkg_count['COUNT(*)']); + +// Start the form rendering and action handling +$app->listform_actions->onLoad(); +?> \ No newline at end of file diff --git a/interface/web/sites/aps_cron_apscrawler_if.php b/interface/web/sites/aps_cron_apscrawler_if.php new file mode 100644 index 000000000..e40b74692 --- /dev/null +++ b/interface/web/sites/aps_cron_apscrawler_if.php @@ -0,0 +1,59 @@ +load('aps_crawler'); + +$log_prefix = 'APS crawler cron: '; + +$aps = new ApsCrawler($app, true); // true = Interface mode, false = Server mode + +$app->log($log_prefix.'Used mem at begin: '.$aps->convertSize(memory_get_usage(true))); + +$time_start = microtime(true); +$aps->startCrawler(); +$aps->parseFolderToDB(); +$time = microtime(true) - $time_start; + +$app->log($log_prefix.'Used mem at end: '.$aps->convertSize(memory_get_usage(true))); +$app->log($log_prefix.'Mem peak during execution: '.$aps->convertSize(memory_get_peak_usage(true))); +$app->log($log_prefix.'Execution time: '.round($time, 3).' seconds'); + +// Load the language file +$lngfile = 'lib/lang/'.$_SESSION['s']['language'].'_aps.lng'; +require_once($lngfile); +$app->load_language_file('web/sites/'.$lngfile); + +echo '

'.$app->lng('packagelist_update_finished_txt').'

'; + + + +?> \ No newline at end of file diff --git a/interface/web/sites/aps_do_operation.php b/interface/web/sites/aps_do_operation.php new file mode 100644 index 000000000..493cde417 --- /dev/null +++ b/interface/web/sites/aps_do_operation.php @@ -0,0 +1,110 @@ +auth->check_module_permissions('aps'); + +$gui = new ApsGUIController($app); + +// An action and ID are required in any case +if(!isset($_GET['action'])) die; + +// List of operations which can be performed +if($_GET['action'] == 'change_status') +{ + // Only admins can perform this operation + if($_SESSION['s']['user']['typ'] != 'admin') die; + + // Make sure a valid package ID is given + if(!$gui->isValidPackageID($_GET['id'], true)) die($app->lng('Invalid ID')); + + // Change the existing status to the opposite + $get_status = $app->db->queryOneRecord("SELECT PackageStatus FROM aps_packages WHERE ID = '".intval($_GET['id'])."';"); + if($get_status['PackageStatus'] == strval(PACKAGE_LOCKED)) + { + $app->db->query("UPDATE aps_packages SET PackageStatus = ".PACKAGE_ENABLED." WHERE ID = '".intval($_GET['id'])."';"); + echo '
'.$app->lng('Yes').'
'; + } + else + { + $app->db->query("UPDATE aps_packages SET PackageStatus = ".PACKAGE_LOCKED." WHERE ID = '".intval($_GET['id'])."';"); + echo '
'.$app->lng('No').'
'; + } +} +else if($_GET['action'] == 'delete_instance') +{ + // Make sure a valid package ID is given (also corresponding to the calling user) + $client_id = 0; + $is_admin = ($_SESSION['s']['user']['typ'] == 'admin') ? true : false; + if(!$is_admin) + { + $cid = $app->db->queryOneRecord("SELECT client_id FROM client WHERE username = '".$app->db->quote($_SESSION['s']['user']['username'])."';"); + $client_id = $cid['client_id']; + } + // Assume that the given instance belongs to the currently calling client_id. Unimportant if status is admin + if(!$gui->isValidInstanceID($_GET['id'], $client_id, $is_admin)) die($app->lng('Invalid ID')); + + // Only delete the instance if the status is "installed" or "flawed" + $check = $app->db->queryOneRecord("SELECT ID FROM aps_instances + WHERE ID = ".$app->db->quote($_GET['id'])." AND + (InstanceStatus = ".INSTANCE_SUCCESS." OR InstanceStatus = ".INSTANCE_ERROR.");"); + if(!empty($check)) $gui->deleteInstance($_GET['id']); + + echo $app->lng('Installation_remove'); +} +else if($_GET['action'] == 'reinstall_instance') +{ + // Make sure a valid package ID is given (also corresponding to the calling user) + $client_id = 0; + $is_admin = ($_SESSION['s']['user']['typ'] == 'admin') ? true : false; + if(!$is_admin) + { + $cid = $app->db->queryOneRecord("SELECT client_id FROM client WHERE username = '".$app->db->quote($_SESSION['s']['user']['username'])."';"); + $client_id = $cid['client_id']; + } + // Assume that the given instance belongs to the currently calling client_id. Unimportant if status is admin + if(!$gui->isValidInstanceID($_GET['id'], $client_id, $is_admin)) die($app->lng('Invalid ID')); + + // We've an InstanceID, so make sure the package is no enabled and InstanceStatus is still "installed" + $check = $app->db->queryOneRecord("SELECT aps_instances.ID FROM aps_instances, aps_packages + WHERE aps_instances.PackageID = aps_packages.ID + AND aps_instances.InstanceStatus = ".INSTANCE_SUCCESS." + AND aps_packages.PackageStatus = ".PACKAGE_ENABLED." + AND aps_instances.ID = ".$app->db->quote($_GET['id']).";"); + if(!$check) die; // normally this might not happen at all, so just die + + $gui->reinstallInstance($_GET['id']); + echo $app->lng('Installation_task'); +} +?> diff --git a/interface/web/sites/aps_install_package.php b/interface/web/sites/aps_install_package.php new file mode 100644 index 000000000..be6012108 --- /dev/null +++ b/interface/web/sites/aps_install_package.php @@ -0,0 +1,198 @@ +load('aps_guicontroller'); + +// Check the module permissions +$app->auth->check_module_permissions('sites'); + +// Load needed classes +$app->uses('tpl'); +$app->tpl->newTemplate("form.tpl.htm"); +$app->tpl->setInclude('content_tpl', 'templates/aps_install_package.htm'); + +// Load the language file +$lngfile = 'lib/lang/'.$_SESSION['s']['language'].'_aps.lng'; +require_once($lngfile); +$app->tpl->setVar($wb); +$app->load_language_file('web/sites/'.$lngfile); + +$adminflag = ($_SESSION['s']['user']['typ'] == 'admin') ? true : false; +$gui = new ApsGUIController($app); +$pkg_id = (isset($_GET['id'])) ? $app->db->quote($_GET['id']) : ''; + +// Check if a newer version is available for the current package +// Note: It's intended that here is no strict ID check (see below) +if(isset($pkg_id)) +{ + $newest_pkg_id = $gui->getNewestPackageID($pkg_id); + if($newest_pkg_id != 0) $pkg_id = $newest_pkg_id; +} + +// Make sure an integer ID is given +if(!isset($pkg_id) || !$gui->isValidPackageID($pkg_id, $adminflag)) + $app->error($app->lng('Invalid ID')); + +// Get package details +$details = $gui->getPackageDetails($pkg_id); +if(isset($details['error'])) $app->error($details['error']); +$settings = $gui->getPackageSettings($pkg_id); +if(isset($settings['error'])) $app->error($settings['error']); + +// Get domain list +$domains = array(); +$domain_for_user = ''; +if(!$adminflag) $domain_for_user = "AND (sys_userid = '".$app->db->quote($_SESSION['s']['user']['userid'])."' + OR sys_groupid = '".$app->db->quote($_SESSION['s']['user']['userid'])."' )"; +$domains_assoc = $app->db->queryAllRecords("SELECT domain FROM web_domain WHERE document_root != '' ".$domain_for_user." ORDER BY domain;"); +if(!empty($domains_assoc)) foreach($domains_assoc as $domain) $domains[] = $domain['domain']; + +// If data has been submitted, validate it +$result['input'] = array(); +if(count($_POST) > 1) +{ + $result = $gui->validateInstallerInput($_POST, $details, $domains, $settings); + if(empty($result['error'])) + { + $gui->createPackageInstance($result['input'], $pkg_id); + @header('Location:aps_installedpackages_list.php'); + } + else + { + $app->tpl->setVar('error', implode('
', $result['error'])); + + // Set memorized values (license, db password, install location) + if(!empty($result['input'])) + foreach($result['input'] as $key => $value) $app->tpl->setVar('inp_'.$key, $value); + } +} +else $app->tpl->setVar('inp_main_database_password', ucfirst(substr(md5(crypt(rand(0, 10))), 0, 16))); + +// Pass the package details to the template +foreach($details as $key => $value) +{ + if(!is_array($value)) $app->tpl->setVar('pkg_'.str_replace(' ', '_', strtolower($key)), $value); + else if($key == 'Requirements PHP settings') $app->tpl->setLoop('pkg_requirements_php_settings', $details['Requirements PHP settings']); +} + +// Parse the template as far as possible, then do the rest manually +$app->tpl_defaults(); +$parsed_tpl = $app->tpl->grab(); + + +// ISPConfig has a very old and functionally limited template engine. We have to style parts on our own... + +// Print the domain list +$domains_tpl = ''; +if(!empty($domains)) +{ + $set = array(); + $set[] = ''; + + $domains_tpl = implode("\n", $set); +} +$parsed_tpl = str_replace('DOMAIN_LIST_SPACE', $domains_tpl, $parsed_tpl); + +// Print the packgae settings +$settings_tpl = ''; +if(!empty($settings)) +{ + $set = array(); + $set[] = ''.$app->lng('package_settings_txt').''; + foreach($settings as $setting) + { + $set[] = '
'; + $set[] = ''; + if($setting['SettingInputType'] == 'string' || $setting['SettingInputType'] == 'password') + { + $input_type = ($setting['SettingInputType'] == 'string') ? 'text' : 'password'; + + $input_value = ''; + if((count($_POST) > 1) + && (isset($result['input'][$setting['SettingID']]))) + $input_value = $result['input'][$setting['SettingID']]; + else $input_value = @$setting['SettingDefaultValue']; + + $set[] = ' +

'.$setting['SettingDescription'].'

'; + } + else if($setting['SettingInputType'] == 'checkbox') + { + $checked = ''; + if((count($_POST) > 1) + && (isset($result['input'][$setting['SettingID']]) + && ($result['input'][$setting['SettingID']] == 'true'))) + $checked = 'checked '; + else if($setting['SettingDefaultValue'] == '1') $checked = 'checked '; + + $set[] = ' +

'.$setting['SettingDescription'].'

'; + } + else if($setting['SettingInputType'] == 'select') + { + $set[] = ' +

'.$setting['SettingDescription'].'

'; + } + + $set[] = '
'; + } + $settings_tpl = implode("\n", $set); +} +$parsed_tpl = str_replace('PKG_SETTINGS_SPACE', $settings_tpl, $parsed_tpl); + +echo $parsed_tpl; +?> \ No newline at end of file diff --git a/interface/web/sites/aps_installedpackages_list.php b/interface/web/sites/aps_installedpackages_list.php new file mode 100644 index 000000000..43b60538b --- /dev/null +++ b/interface/web/sites/aps_installedpackages_list.php @@ -0,0 +1,127 @@ +load('aps_base'); + +// Path to the list definition file +$list_def_file = "list/aps_installedpackages.list.php"; + +// Check the module permissions +$app->auth->check_module_permissions('sites'); + +// Load needed classes +$app->uses('tpl,tform,listform,listform_actions'); + +// Show further information only to admins or resellers +if($_SESSION['s']['user']['typ'] == 'admin' || $app->auth->has_clients($_SESSION['s']['user']['userid'])) + $app->tpl->setVar('is_noclient', 1); + +// Show each user the own packages (if not admin) +$client_ext = ''; +$is_admin = ($_SESSION['s']['user']['typ'] == 'admin') ? true : false; +if(!$is_admin) +{ + $cid = $app->db->queryOneRecord('SELECT client_id FROM client WHERE username = "'.$app->db->quote($_SESSION['s']['user']['username']).'";'); + $client_ext = ' AND aps_instances.customer_id = '.$cid['client_id']; +} +$app->listform_actions->SQLExtWhere = 'aps_instances.package_id = aps_packages.id'.$client_ext; +$app->listform_actions->SQLOrderBy = 'ORDER BY package_name'; + +// We are using parts of listform_actions because ISPConfig doesn't allow +// queries over multiple tables so we construct them ourselves +$_SESSION['s']['form']['return_to'] = ''; + +// Load the list template +$app->listform->loadListDef($list_def_file); +if(!is_file('templates/'.$app->listform->listDef["name"].'_list.htm')) +{ +$app->uses('listform_tpl_generator'); +$app->listform_tpl_generator->buildHTML($app->listform->listDef); +} +$app->tpl->newTemplate("listpage.tpl.htm"); +$app->tpl->setInclude('content_tpl', 'templates/'.$app->listform->listDef["name"].'_list.htm'); + +// Build the WHERE query for search +$sql_where = ''; +if($app->listform_actions->SQLExtWhere != '') + $sql_where .= ' '.$app->listform_actions->SQLExtWhere.' and'; +$sql_where = $app->listform->getSearchSQL($sql_where); +$app->tpl->setVar($app->listform->searchValues); + +// Paging +$limit_sql = $app->listform->getPagingSQL($sql_where); +$app->tpl->setVar('paging', $app->listform->pagingHTML); + +// Our query over multiple tables +$query = "SELECT aps_instances.id AS id, aps_instances.package_id AS package_id, + aps_instances.customer_id AS customer_id, client.username AS customer_name, + aps_instances.instance_status AS instance_status, aps_packages.name AS package_name, + aps_packages.version AS package_version, aps_packages.release AS package_release, + aps_packages.package_status AS package_status, + CONCAT ((SELECT value FROM aps_instances_settings WHERE name='main_domain' AND instance_id = aps_instances.id), + '/', (SELECT value FROM aps_instances_settings WHERE name='main_location' AND instance_id = aps_instances.id)) + AS install_location + FROM aps_instances, aps_packages, client + WHERE client.client_id = aps_instances.Customer_id AND ".$sql_where." ".$app->listform_actions->SQLOrderBy." ".$limit_sql; + +$records = $app->db->queryAllRecords($query); +$app->listform_actions->DataRowColor = '#FFFFFF'; + +// Re-form all result entries and add extra entries +$records_new = ''; +if(is_array($records)) +{ + $app->listform_actions->idx_key = $app->listform->listDef["table_idx"]; + foreach($records as $rec) + { + // Set an abbreviated install location to beware the page layout + $ils = ''; + if(strlen($rec['Install_location']) >= 38) $ils = substr($rec['Install_location'], 0, 35).'...'; + else $ils = $rec['install_location']; + $rec['install_location_short'] = $ils; + + // Also set a boolean-like variable for the reinstall button (vlibTemplate doesn't allow variable comparisons) + // For a reinstall, the package must be already installed successfully and (still be) enabled + if($rec['instance_status'] == INSTANCE_SUCCESS && $rec['package_status'] == PACKAGE_ENABLED) + $rec['reinstall_possible'] = 'true'; + // Of course an instance can only then be removed when it's not already tagged for removal + if($rec['instance_status'] != INSTANCE_REMOVE && $rec['instance_status'] != INSTANCE_INSTALL) + $rec['delete_possible'] = 'true'; + + $records_new[] = $app->listform_actions->prepareDataRow($rec); + } +} +$app->tpl->setLoop('records', $records_new); + +$app->listform_actions->onShow(); +?> \ No newline at end of file diff --git a/interface/web/sites/aps_packagedetails_show.php b/interface/web/sites/aps_packagedetails_show.php new file mode 100644 index 000000000..cff22afc6 --- /dev/null +++ b/interface/web/sites/aps_packagedetails_show.php @@ -0,0 +1,99 @@ +load('aps_guicontroller'); + +// Check the module permissions +$app->auth->check_module_permissions('sites'); + +// Load needed classes +$app->uses('tpl'); +$app->tpl->newTemplate("listpage.tpl.htm"); +$app->tpl->setInclude('content_tpl', 'templates/aps_packagedetails_show.htm'); + +// Load the language file +$lngfile = 'lib/lang/'.$_SESSION['s']['language'].'_aps.lng'; +require_once($lngfile); +$app->tpl->setVar($wb); + +$gui = new ApsGUIController($app); +$pkg_id = (isset($_GET['id'])) ? $app->db->quote($_GET['id']) : ''; + +// Check if a newer version is available for the current package +// Note: It's intended that here is no strict ID check (see below) +if(isset($pkg_id)) +{ + $newest_pkg_id = $gui->getNewestPackageID($pkg_id); + if($newest_pkg_id != 0) $pkg_id = $newest_pkg_id; +} + +// Make sure an integer ID is given +$adminflag = ($_SESSION['s']['user']['typ'] == 'admin') ? true : false; +if(!isset($pkg_id) || !$gui->isValidPackageID($pkg_id, $adminflag)) + $app->error($app->lng('Invalid ID')); + +// Get package details +$details = $gui->getPackageDetails($pkg_id); +if(isset($details['error'])) $app->error($details['error']); + +// Set the active and default tab +$next_tab = 'details'; +if(isset($_POST['next_tab'])) +{ + switch($_POST['next_tab']) + { + case 'details': $next_tab = 'details'; break; + case 'settings': $next_tab = 'settings'; break; + case 'changelog': $next_tab = 'changelog'; break; + case 'screenshots': $next_tab = 'screenshots'; break; + default: $next_tab = 'details'; + } +} +$app->tpl->setVar('next_tab', $next_tab); + +// Parse the package details to the template +foreach($details as $key => $value) +{ + if(!is_array($value)) $app->tpl->setVar('pkg_'.str_replace(' ', '_', strtolower($key)), $value); + else // Special cases + { + if($key == 'Changelog') $app->tpl->setLoop('pkg_changelog', $details['Changelog']); + elseif($key == 'Screenshots') $app->tpl->setLoop('pkg_screenshots', $details['Screenshots']); + elseif($key == 'Requirements PHP settings') $app->tpl->setLoop('pkg_requirements_php_settings', $details['Requirements PHP settings']); + } +} +//print_r($details['Requirements PHP settings']); + +$app->tpl_defaults(); +$app->tpl->pparse(); +?> \ No newline at end of file diff --git a/interface/web/sites/lib/lang/en_aps.lng b/interface/web/sites/lib/lang/en_aps.lng new file mode 100644 index 000000000..d0ecb771f --- /dev/null +++ b/interface/web/sites/lib/lang/en_aps.lng @@ -0,0 +1,57 @@ + \ No newline at end of file diff --git a/interface/web/sites/lib/lang/en_aps_instances_list.lng b/interface/web/sites/lib/lang/en_aps_instances_list.lng new file mode 100644 index 000000000..3b9c7e943 --- /dev/null +++ b/interface/web/sites/lib/lang/en_aps_instances_list.lng @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/interface/web/sites/lib/lang/en_aps_packages_list.lng b/interface/web/sites/lib/lang/en_aps_packages_list.lng new file mode 100644 index 000000000..12cc30d02 --- /dev/null +++ b/interface/web/sites/lib/lang/en_aps_packages_list.lng @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/interface/web/sites/lib/module.conf.php b/interface/web/sites/lib/module.conf.php index cbcc62bd9..a253dcd74 100644 --- a/interface/web/sites/lib/module.conf.php +++ b/interface/web/sites/lib/module.conf.php @@ -153,6 +153,34 @@ if($app->auth->get_client_limit($userid,'cron') != 0) 'items' => $items); } +//*** APS menu +$items = array(); + +$items[] = array('title' => 'Available packages', + 'target' => 'content', + 'link' => 'sites/aps_availablepackages_list.php', + 'html_id' => 'aps_availablepackages_list'); + +$items[] = array('title' => 'Installed packages', + 'target' => 'content', + 'link' => 'sites/aps_installedpackages_list.php', + 'html_id' => 'aps_installedpackages_list'); + + +// Second menu group, available only for admins +if($_SESSION['s']['user']['typ'] == 'admin') +{ + $items[] = array('title' => 'Update Packagelist', + 'target' => 'content', + 'link' => 'sites/aps_cron_apscrawler_if.php', + 'html_id' => 'aps_packagedetails_show'); +} + +$module['nav'][] = array('title' => 'APS Installer', + 'open' => 1, + 'items' => $items); + + //**** Statistics menu $items = array(); diff --git a/interface/web/sites/list/aps_availablepackages.list.php b/interface/web/sites/list/aps_availablepackages.list.php new file mode 100644 index 000000000..d07b85a4f --- /dev/null +++ b/interface/web/sites/list/aps_availablepackages.list.php @@ -0,0 +1,86 @@ + 'name', + 'datatype' => 'VARCHAR', + 'formtype' => 'TEXT', + 'op' => 'like', + 'prefix' => '%', + 'suffix' => '%', + 'width' => '', + 'value' => ''); + +$liste["item"][] = array('field' => 'version', + 'datatype' => 'VARCHAR', + 'formtype' => 'TEXT', + 'op' => 'like', + 'prefix' => '%', + 'suffix' => '%', + 'width' => '', + 'value' => ''); + +$liste["item"][] = array('field' => 'category', + 'datatype' => 'VARCHAR', + 'formtype' => 'SELECT', + 'op' => '=', + 'prefix' => '', + 'suffix' => '', + 'datasource' => array('type' => 'SQL', + 'querystring' => 'SELECT category FROM aps_packages ORDER BY category', + 'keyfield' => 'category', + 'valuefield' => 'category'), + 'width' => '', + 'value' => ''); + +if($_SESSION['s']['user']['typ'] == 'admin') +{ +$liste['item'][] = array('field' => 'package_status', + 'datatype' => 'VARCHAR', + 'formtype' => 'SELECT', + 'op' => '=', + 'prefix' => '', + 'suffix' => '', + 'width' => '', + 'value' => array(PACKAGE_ENABLED => '
'.$app->lng('Yes').'
', + PACKAGE_LOCKED => '
'.$app->lng('No').'
')); +} +?> \ No newline at end of file diff --git a/interface/web/sites/list/aps_installedpackages.list.php b/interface/web/sites/list/aps_installedpackages.list.php new file mode 100644 index 000000000..573df2a55 --- /dev/null +++ b/interface/web/sites/list/aps_installedpackages.list.php @@ -0,0 +1,81 @@ + 'name', + 'datatype' => 'VARCHAR', + 'formtype' => 'TEXT', + 'op' => 'LIKE', + 'prefix' => '%', + 'suffix' => '%', + 'width' => '', + 'value' => ''); + +$liste["item"][] = array('field' => 'version', + 'datatype' => 'VARCHAR', + 'formtype' => 'TEXT', + 'op' => 'like', + 'prefix' => '%', + 'suffix' => '%', + 'width' => '', + 'value' => ''); + +$liste["item"][] = array('field' => 'customer_name', + 'datatype' => 'VARCHAR', + 'formtype' => 'TEXT', + 'op' => 'LIKE', + 'prefix' => '%', + 'suffix' => '%', + 'width' => '', + 'value' => ''); + +$liste["item"][] = array('field' => 'instance_status', + 'datatype' => 'VARCHAR', + 'formtype' => 'SELECT', + 'op' => '=', + 'prefix' => '', + 'suffix' => '', + 'width' => '', + 'value' => array(INSTANCE_INSTALL => $app->lng('Installation_task'), + INSTANCE_ERROR => $app->lng('Installation_error'), + INSTANCE_SUCCESS => $app->lng('Installation_success'), + INSTANCE_REMOVE => $app->lng('Installation_remove'))); +?> \ No newline at end of file diff --git a/interface/web/sites/templates/aps_install_package.htm b/interface/web/sites/templates/aps_install_package.htm new file mode 100644 index 000000000..693a460a2 --- /dev/null +++ b/interface/web/sites/templates/aps_install_package.htm @@ -0,0 +1,54 @@ +

+ {tmpl_var name='installation_txt'}: {tmpl_var name='pkg_name'} {tmpl_var name='pkg_version'}-{tmpl_var name='pkg_release'} + + + {tmpl_var name='pkg_name'} + + +

+ + +

ERROR

    {tmpl_var name='error'}
+
+ +
+ +
\ No newline at end of file diff --git a/interface/web/sites/templates/aps_instances_list.htm b/interface/web/sites/templates/aps_instances_list.htm new file mode 100644 index 000000000..6d0fb57df --- /dev/null +++ b/interface/web/sites/templates/aps_instances_list.htm @@ -0,0 +1,60 @@ +

{tmpl_var name="list_head_txt"}

+ +
+
+
{tmpl_var name="list_head_txt"} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{tmpl_var name='name_txt'}{tmpl_var name='version_txt'}{tmpl_var name='customer_txt'}{tmpl_var name='install_location_txt'}{tmpl_var name='status_txt'} 
  + +
{tmpl_var name='package_name'}{tmpl_var name='package_version'}-{tmpl_var name='package_release'}{tmpl_var name='customer_name'}{tmpl_var name='install_location_short'}{tmpl_var name='instance_status'} + +
+
+
+
\ No newline at end of file diff --git a/interface/web/sites/templates/aps_packagedetails_show.htm b/interface/web/sites/templates/aps_packagedetails_show.htm new file mode 100644 index 000000000..267a1ea22 --- /dev/null +++ b/interface/web/sites/templates/aps_packagedetails_show.htm @@ -0,0 +1,122 @@ +

+ + {tmpl_var name='pkg_name'} + + {tmpl_var name='pkg_name'} +

+{tmpl_var name='pkg_summary'} +

 

+ +
+
+ +
+

 

 

+ + + +
+ + +
+

 

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{tmpl_var name='version_txt'}{tmpl_var name='pkg_version'} (Release {tmpl_var name='pkg_release'})
{tmpl_var name='category_txt'}{tmpl_var name='pkg_category'}
{tmpl_var name='description_txt'}{tmpl_var name='pkg_description'}
{tmpl_var name='homepage_txt'}{tmpl_var name='pkg_homepage'}
{tmpl_var name='installed_size_txt'}{tmpl_var name='pkg_installed_size'}
{tmpl_var name='supported_languages_txt'}{tmpl_var name='pkg_languages'}
{tmpl_var name='config_script_txt'}{tmpl_var name='pkg_config_script'}
{tmpl_var name='license_txt'} + {tmpl_var name='pkg_license_name'}
+ + {tmpl_var name='pkg_license_content'} + + + +
+ {tmpl_var name='ScreenDescription'}
+ {tmpl_var name='ScreenDescription'}

+
    +
  • {tmpl_var name='ChangelogVersion'}
  • +
    • {tmpl_var name='ChangelogDescription'}
    +
{tmpl_var name='php_extensions_txt'}{tmpl_var name='pkg_requirements_php_extensions'}
{tmpl_var name='php_settings_txt'} + {tmpl_var name='PHPSettingName'} = {tmpl_var name='PHPSettingValue'}
+
{tmpl_var name='supported_php_versions_txt'}{tmpl_var name='pkg_requirements_supported_php_versions'}
{tmpl_var name='database_txt'}{tmpl_var name='pkg_requirements_database'}
+
+
\ No newline at end of file diff --git a/interface/web/sites/templates/aps_packages_list.htm b/interface/web/sites/templates/aps_packages_list.htm new file mode 100644 index 000000000..941a8b119 --- /dev/null +++ b/interface/web/sites/templates/aps_packages_list.htm @@ -0,0 +1,48 @@ +

{tmpl_var name="list_head_txt"}

+ +
+
+
{tmpl_var name="list_head_txt"} ({tmpl_var name='package_count'}) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{tmpl_var name='name_txt'}{tmpl_var name='version_txt'}{tmpl_var name='category_txt'}{tmpl_var name='status_txt'} 
{tmpl_var name='name'}{tmpl_var name='version'}-{tmpl_var name='release'}{tmpl_var name='category'}{tmpl_var name='package_status'} 
+
+
+
diff --git a/interface/web/themes/default/css/screen/content_ispc.css b/interface/web/themes/default/css/screen/content_ispc.css index b3d935486..267818703 100644 --- a/interface/web/themes/default/css/screen/content_ispc.css +++ b/interface/web/themes/default/css/screen/content_ispc.css @@ -381,7 +381,10 @@ .icons16.icoLoginAs { background-image: url("../../icons/x16/user_go.png"); } .icons16.icoWebmailer { background-image: url("../../icons/x16/mails_arrow.png"); } - + #ajaxloader { + text-align:center; + margin-top: 180px; + } .blockLabel.email_at { width: 20px !important; diff --git a/interface/web/themes/default/images/ajax-loader.gif b/interface/web/themes/default/images/ajax-loader.gif new file mode 100644 index 0000000000000000000000000000000000000000..7c4804ebcaec27b830873a1ff4d157f1bde00ef1 GIT binary patch literal 3208 zcmc(idr%X10>_u^BadA@-3>MnI0F(9Y9Xjlo+<j=n zybK@-B!*~3#0O}#JPT@Zu%e+R?i!va{q8vuC!{(XFW{KUjWS6A2A*x10pKyh(#Q&ZE-%*@M|FUQBnv$C=h5)y!q zm)XCJxrw`Dl6MPPsd34%teAZiIX!e-wZoFB$fYi_y%EJQnaJ7LDGRVM_?fT@iYAHCeM}^RzX`-q!Y7y!w!t zsH*oHjuB~zi^U*^YtUdr^0hYm&4;_nedE#u11QgIty8G|rKnquR=P#f=AFGS&Ycy~ z$*E~Pw@8S#mk#Bn`Unb4k09=Dat<{-sv%0|LM6dqtES<&i$<^X(G{sro@(qgt>auc zpUkfGNh&N(jCA$Mw#gv|3oq7CugV2q5nGJ~LxEmRKVa}@{?Tdd3MTQ-zc0iezj5(8K1_SDO2=gh7c4VhM#XD`Xz6Y0wiE5rmJ2o8nmDzWm|ybFRWfzjpaM|?J`{WHs%oHazo-w7 zGp@ZxNiS=@bv^c|=uWJh_b`7td{Et<^8|R_{WL0;c)YE>>?JVNDl2-svij4?<}FR1 zUv!)_8a5;Sj!#;M|6&f`%5Dz-u^rD|xLvZUS`{*MRuQ_UM6u65KcoM)D~s4RYCjo3 zzdA|dj2c-vtSTEBZ=aLKPjqwy_W|yyyDxBrpve8|t0- zeg^1TV zThds=y3-sYjZuVpEVek(qw-&N`a~VjFlCJQw;~E?k50ugKu4Uc3 z1Na%OUiP=u?lILAy>Vk$>W*~=zU-if6O_d%Xz;SNYe^ZL93;*zRKkPzrkbQ@vOyW! zGNhrAz?xMkOf8d?xT*A*_C(`JtVMHTZPNu_4ZB-$=+YIAmR3)6`h_G&?1|w5j_-gr zK7GFo>Q5UXD50|G%;8n#faKne>FIQSV_DL9{8_`3^>NWUcG;a52Y+?Dk550iet3M{ z?vc*|`da@;KeUkj!|_||W=AZoyfnh?)fEIo+aDA{b*Ctt&A#}jC{dnU)gVZ%TEun` zoAX*%2`#pnOXctUI{w<@9s5NE0>utn30HiV8QleX9% zLdd+8fyth5OteW7lx}*!(=wp0ojj(Aci@c zR-QNR?ncXnDjA!E^k7J~dL28-3hlXEnUV+Pd99^)VJ$#pnAoToNPbZB=uzd@o<$aq z{Ne(hyG}+Xj*9bf`>7QCDz+~2oi9Dl!0==< z3EH$sumzQtic>13_Db4|-o)IWsi)ZK0_G>{cd$tbwuwhf4>(!+%c}^ark3ooyg=$y zdmK?VR5c^@k`{&D2^6zSO<)uSi{qR+JCR0Q$Kq1R-6+|-z{3>8JRqd|yk`W)Q*O`mD6i3q zpUf!Pc42{Q9(q#iqZ1u`e-_YGtv`+p_|zVAqSDqi=s?n zGzz+)(|g)8T+%=k?IvJdM{tgC6?PY(-atF}TkyLWejP=M0z4)O@aR zUgP8&t;{6i-E`L#N6#38JXqyN-?`DB2mQW;@i2PYxj-l#bQ|j9nOL3?c~GUo8mEK`?9xy#-JQkqQ}n z6EnJn;_%>5A$qe9cu0^3JNe+v348|c8kGHjJ$wA^lfg8To5-qRWzgiY#SwUu1gCd1%uk4gh7)cpIIv+zrxex)4MI-UGIzt+X0LjP8Bw z>m(zOodTvdCUD^~GBF#vfj{x}lW{`$9rGpqr|s{0Jw8|U0J+T$6vpUD&yPi*E##m1 zpF%*vz7`vP7=k4n%GzbHzEcr)sAZ2#4P-uB8& zYPI+iQn>761JH%=|J8sn>_00=?Pn