From 477d4e981a4171dcc8b7886b2130abbee0442067 Mon Sep 17 00:00:00 2001
From: tbrehm <t.brehm@ispconfig.org>
Date: Thu, 24 May 2012 07:26:50 -0400
Subject: [PATCH] Initial commit of the interface part of the APS installer.
---
interface/web/sites/list/aps_availablepackages.list.php | 86 +
interface/web/sites/aps_installedpackages_list.php | 127 ++
install/dist/lib/fedora.lib.php | 4
interface/web/sites/lib/module.conf.php | 28
interface/lib/classes/aps_guicontroller.inc.php | 770 ++++++++++++++++
interface/web/sites/aps_install_package.php | 198 ++++
interface/web/sites/lib/lang/en_aps.lng | 57 +
interface/web/themes/default/images/ajax-loader.gif | 0
interface/web/sites/list/aps_installedpackages.list.php | 81 +
interface/web/sites/templates/aps_install_package.htm | 54 +
install/lib/installer_base.lib.php | 4
install/sql/ispconfig3.sql | 71 +
interface/web/sites/templates/aps_packages_list.htm | 48 +
interface/web/sites/lib/lang/en_aps_packages_list.lng | 8
interface/web/themes/default/css/screen/content_ispc.css | 5
install/sql/incremental/upd_0034.sql | 76 +
interface/lib/classes/aps_base.inc.php | 109 ++
interface/web/sites/aps_cron_apscrawler_if.php | 59 +
interface/web/sites/templates/aps_instances_list.htm | 60 +
interface/web/js/scrigo.js.php | 5
interface/web/sites/lib/lang/en_aps_instances_list.lng | 13
install/dist/lib/gentoo.lib.php | 4
install/dist/lib/opensuse.lib.php | 4
interface/web/sites/aps_do_operation.php | 110 ++
interface/web/sites/aps_packagedetails_show.php | 99 ++
interface/web/sites/templates/aps_packagedetails_show.htm | 122 ++
interface/lib/classes/aps_crawler.inc.php | 534 +++++++++++
interface/web/sites/aps_availablepackages_list.php | 56 +
28 files changed, 2,790 insertions(+), 2 deletions(-)
diff --git a/install/dist/lib/fedora.lib.php b/install/dist/lib/fedora.lib.php
index ba1c285..f6ed418 100644
--- a/install/dist/lib/fedora.lib.php
+++ b/install/dist/lib/fedora.lib.php
@@ -865,6 +865,10 @@
}
}
+ //* 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 5e5c93c..ad94bad 100644
--- a/install/dist/lib/gentoo.lib.php
+++ b/install/dist/lib/gentoo.lib.php
@@ -858,6 +858,10 @@
}
}
+ //* 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 88f508a..2dfcd70 100644
--- a/install/dist/lib/opensuse.lib.php
+++ b/install/dist/lib/opensuse.lib.php
@@ -903,6 +903,10 @@
}
}
+ //* 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 94a176a..b472844 100644
--- a/install/lib/installer_base.lib.php
+++ b/install/lib/installer_base.lib.php
@@ -1767,6 +1767,10 @@
}
}
}
+
+ //* 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 0000000..ea84577
--- /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 842d618..8a08f31 100644
--- a/install/sql/ispconfig3.sql
+++ b/install/sql/ispconfig3.sql
@@ -54,6 +54,67 @@
-- --------------------------------------------------------
--
+-- 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 @@
`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 @@
`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 @@
-- --------------------------------------------------------
-- --------------------------------------------------------
+--
+-- 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 0000000..9822cae
--- /dev/null
+++ b/interface/lib/classes/aps_base.inc.php
@@ -0,0 +1,109 @@
+<?php
+/*
+Copyright (c) 2012, ISPConfig UG
+Contributors: web wack creations, http://www.web-wack.at
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of ISPConfig nor the names of its contributors
+ may be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+// Constants describing instances
+define('INSTANCE_PENDING', 0);
+define('INSTANCE_INSTALL', 1);
+define('INSTANCE_ERROR', 2);
+define('INSTANCE_SUCCESS', 3);
+define('INSTANCE_REMOVE', 4);
+
+// Constants describing packages
+define('PACKAGE_LOCKED', 1);
+define('PACKAGE_ENABLED', 2);
+define('PACKAGE_OUTDATED', 3);
+define('PACKAGE_ERROR_NOMETA', 4);
+
+class ApsBase
+{
+ protected $app = null;
+ protected $db = null;
+
+ protected $log_prefix = '';
+ protected $fetch_url = '';
+ protected $aps_version = '';
+ protected $packages_dir = '';
+ protected $temp_pkg_dir = '';
+ protected $interface_pkg_dir = '';
+ protected $interface_mode = false; // server mode by default
+
+ /**
+ * Constructor
+ *
+ * @param $app the application instance (db handle + log method)
+ * @param $interface_mode act in interface (true) or server mode (false)
+ * @param $log_prefix a prefix to set before all log entries
+ */
+ public function __construct($app, $log_prefix = 'APS: ', $interface_mode = false)
+ {
+ $this->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 0000000..640bb34
--- /dev/null
+++ b/interface/lib/classes/aps_crawler.inc.php
@@ -0,0 +1,534 @@
+<?php
+/*
+Copyright (c) 2012, ISPConfig UG
+Contributors: web wack creations, http://www.web-wack.at
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of ISPConfig nor the names of its contributors
+ may be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+require_once('aps_base.inc.php');
+
+@set_time_limit(0);
+@ignore_user_abort(1);
+
+class ApsCrawler extends ApsBase
+{
+ /**
+ * Constructor
+ *
+ * @param $app the application instance (db handle + log method)
+ * @param $interface_mode act in interface (true) or server mode (false)
+ */
+ public function __construct($app, $interface_mode = false)
+ {
+ parent::__construct($app, 'APS crawler: ', $interface_mode);
+ }
+
+ /**
+ * Before the cron is executed, make sure all necessary options are set
+ * and all functions (i.e. cURL) are available
+ */
+ private function checkRequirements()
+ {
+ try
+ {
+ // Check if allow_url_fopen is enabled
+ if(!@ini_get('allow_url_fopen')) throw new Exception('allow_url_fopen is not enabled');
+ // Check if the cURL module is available
+ if(!function_exists('curl_version')) throw new Exception('cURL is not available');
+
+ // Check if used folders are writable (chmod 777)
+ if($this->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("/\<a href=\"(.+)\/\" class=\"vendor\"/i", $vendor_page, $matches);
+ $vendors = array_map('urldecode', $matches[1]);
+ if(!$vendors) throw new Exception('Unable to fetch vendors. Aborting');
+
+ // Format all vendors for further processing (i.e. typo3.org -> /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], '<opensearch:totalResults>0</opensearch:totalResults>');
+ 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 0000000..0b4038f
--- /dev/null
+++ b/interface/lib/classes/aps_guicontroller.inc.php
@@ -0,0 +1,770 @@
+<?php
+/*
+Copyright (c) 2012, ISPConfig UG
+Contributors: web wack creations, http://www.web-wack.at
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of ISPConfig nor the names of its contributors
+ may be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+require_once('aps_base.inc.php');
+
+class ApsGUIController extends ApsBase
+{
+ /**
+ * Constructor
+ *
+ * @param $app the application instance (db handle)
+ */
+ public function __construct($app)
+ {
+ parent::__construct($app);
+ }
+
+ /**
+ * Reads in a package metadata file and registers it's namespaces
+ *
+ * @param $filename the file to read
+ * @return $sxe a SimpleXMLElement handle
+ */
+ private function readInMetaFile($filename)
+ {
+ $metadata = file_get_contents($filename);
+ $metadata = str_replace("xmlns=", "ns=", $metadata);
+ $sxe = new SimpleXMLElement($metadata);
+ $namespaces = $sxe->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('<br />', $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 4a33f96..75200ab 100644
--- a/interface/web/js/scrigo.js.php
+++ b/interface/web/js/scrigo.js.php
@@ -203,6 +203,9 @@
var pageContentObject2 = jQuery.ajax({ type: "GET",
url: pagename,
dataType: "html",
+ beforeSend: function() {
+ jQuery('#pageContent').html('<div id="ajaxloader"><img src="themes/default/images/ajax-loader.gif" /></div>');
+ },
success: function(data, textStatus, jqXHR) {
if(jqXHR.responseText.indexOf('HEADER_REDIRECT:') > -1) {
var parts = jqXHR.responseText.split(':');
@@ -215,9 +218,9 @@
//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 0000000..3e3b83b
--- /dev/null
+++ b/interface/web/sites/aps_availablepackages_list.php
@@ -0,0 +1,56 @@
+<?php
+/*
+Copyright (c) 2012, ISPConfig UG
+Contributors: web wack creations, http://www.web-wack.at
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of ISPConfig nor the names of its contributors
+ may be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+require_once('../../lib/config.inc.php');
+require_once('../../lib/app.inc.php');
+//require_once('classes/class.base.php'); // for constants
+$app->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 0000000..e40b746
--- /dev/null
+++ b/interface/web/sites/aps_cron_apscrawler_if.php
@@ -0,0 +1,59 @@
+<?php
+/*
+Copyright (c) 2012, ISPConfig UG
+Contributors: web wack creations, http://www.web-wack.at
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of ISPConfig nor the names of its contributors
+ may be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+require_once('../../lib/config.inc.php');
+require_once('../../lib/app.inc.php');
+//require_once('classes/class.crawler.php');
+$app->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 '<div id="OKMsg"><p>'.$app->lng('packagelist_update_finished_txt').'</p></div>';
+
+
+
+?>
\ 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 0000000..493cde4
--- /dev/null
+++ b/interface/web/sites/aps_do_operation.php
@@ -0,0 +1,110 @@
+<?php
+/*
+Copyright (c) 2012, ISPConfig UG
+Contributors: web wack creations, http://www.web-wack.at
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of ISPConfig nor the names of its contributors
+ may be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+require_once('../../lib/config.inc.php');
+require_once('../../lib/app.inc.php');
+require_once('classes/class.guicontroller.php');
+
+// Check the module permissions
+$app->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 '<div class="swap" id="ir-Yes"><span>'.$app->lng('Yes').'</span></div>';
+ }
+ else
+ {
+ $app->db->query("UPDATE aps_packages SET PackageStatus = ".PACKAGE_LOCKED." WHERE ID = '".intval($_GET['id'])."';");
+ echo '<div class="swap" id="ir-No"><span>'.$app->lng('No').'</span></div>';
+ }
+}
+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 0000000..be60121
--- /dev/null
+++ b/interface/web/sites/aps_install_package.php
@@ -0,0 +1,198 @@
+<?php
+/*
+Copyright (c) 2012, ISPConfig UG
+Contributors: web wack creations, http://www.web-wack.at
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of ISPConfig nor the names of its contributors
+ may be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+require_once('../../lib/config.inc.php');
+require_once('../../lib/app.inc.php');
+//require_once('classes/class.guicontroller.php');
+$app->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('<br />', $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[] = '<select name="main_domain" id="main_domain" class="selectInput">';
+ foreach($domains as $domain)
+ {
+ $selected = '';
+ if((count($_POST) > 1)
+ && (isset($result['input']['main_domain']))
+ && ($result['input']['main_domain'] == $domain))
+ $selected = ' selected ';
+ $set[] = '<option value="'.$domain.'" '.$selected.'>'.$domain.'</option>';
+ }
+ $set[] = '</select>';
+
+ $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[] = '<legend>'.$app->lng('package_settings_txt').'</legend>';
+ foreach($settings as $setting)
+ {
+ $set[] = '<div class="ctrlHolder">';
+ $set[] = '<label for="'.$setting['SettingID'].'">'.$setting['SettingName'].'</label>';
+ 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[] = '<input type="'.$input_type.'" class="textInput" name="'.$setting['SettingID'].'" maxlength="'.$setting['SettingMaxLength'].'" id="'.$setting['SettingID'].'" value="'.$input_value.'" />
+ <p class="formHint">'.$setting['SettingDescription'].'</p>';
+ }
+ 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[] = '<input type="checkbox" id="'.$setting['SettingID'].'" name="'.$setting['SettingID'].'" '.$checked.'/>
+ <p class="formHint">'.$setting['SettingDescription'].'</p>';
+ }
+ else if($setting['SettingInputType'] == 'select')
+ {
+ $set[] = '<select size="1" class="selectInput" name="'.$setting['SettingID'].'">';
+ foreach($setting['SettingChoices'] as $choice)
+ {
+ $selected = '';
+ if((count($_POST) > 1)
+ && (isset($result['input'][$setting['SettingID']])))
+ {
+ if($result['input'][$setting['SettingID']] == $choice['EnumID'])
+ $selected = 'selected ';
+ }
+ else if($setting['SettingDefaultValue'] == $choice['EnumID']) $selected = 'selected ';
+
+ $set[] = '<option value="'.$choice['EnumID'].'" '.$selected.'>'.$choice['EnumName'].'</option>';
+ }
+ $set[] = '</select>
+ <p class="formHint">'.$setting['SettingDescription'].'</p>';
+ }
+
+ $set[] = '</div>';
+ }
+ $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 0000000..43b6053
--- /dev/null
+++ b/interface/web/sites/aps_installedpackages_list.php
@@ -0,0 +1,127 @@
+<?php
+/*
+Copyright (c) 2012, ISPConfig UG
+Contributors: web wack creations, http://www.web-wack.at
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of ISPConfig nor the names of its contributors
+ may be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+require_once('../../lib/config.inc.php');
+require_once('../../lib/app.inc.php');
+//require_once('classes/class.base.php'); // for constants
+$app->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 0000000..cff22af
--- /dev/null
+++ b/interface/web/sites/aps_packagedetails_show.php
@@ -0,0 +1,99 @@
+<?php
+/*
+Copyright (c) 2012, ISPConfig UG
+Contributors: web wack creations, http://www.web-wack.at
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of ISPConfig nor the names of its contributors
+ may be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+require_once('../../lib/config.inc.php');
+require_once('../../lib/app.inc.php');
+//require_once('classes/class.guicontroller.php');
+$app->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 0000000..d0ecb77
--- /dev/null
+++ b/interface/web/sites/lib/lang/en_aps.lng
@@ -0,0 +1,57 @@
+<?php
+$wb['overview_txt'] = 'Overview';
+$wb['administration_txt'] = 'Administration';
+$wb['available_packages_txt'] = 'Available packages';
+$wb['installed_packages_txt'] = 'Installed packages';
+$wb['yes_txt'] = 'Yes';
+$wb['no_txt'] = 'No';
+$wb['invalid_id_txt'] = 'No valid ID has been provided.';
+$wb['details_txt'] = 'Details';
+$wb['version_txt'] = 'Version';
+$wb['category_txt'] = 'Category';
+$wb['homepage_txt'] = 'Homepage';
+$wb['supported_languages_txt'] = 'Supported languages';
+$wb['description_txt'] = 'Description';
+$wb['config_script_txt'] = 'Configuration script';
+$wb['installed_size_txt'] = 'Size after installation';
+$wb['license_txt'] = 'License';
+$wb['screenshots_txt'] = 'Screenshots';
+$wb['changelog_txt'] = 'Changelog';
+$wb['server_requirements_txt'] = 'Server requirements';
+$wb['php_extensions_txt'] = 'PHP extensions';
+$wb['php_settings_txt'] = 'PHP settings';
+$wb['supported_php_versions_txt'] = 'Supported PHP versions';
+$wb['database_txt'] = 'Database';
+$wb['settings_txt'] = 'Settings';
+$wb['install_package_txt'] = 'Install this package';
+$wb['installation_txt'] = 'Installation';
+$wb['install_location_txt'] = 'Install location';
+$wb['btn_install'] = 'Install';
+$wb['btn_cancel'] = 'Cancel';
+$wb['acceptance_txt'] = 'Acceptance';
+$wb['acceptance_text_txt'] = 'Yes, i\'ve read the license and agree.';
+$wb['install_language_txt'] = 'Interface language';
+$wb['new_database_password_txt'] = 'New database password';
+$wb['basic_settings_txt'] = 'Basic settings';
+$wb['package_settings_txt'] = 'Package settings';
+$wb['error_main_domain'] = 'The domain of the installation path is invalid.';
+$wb['error_no_main_location'] = 'You have provided no valid installation path.';
+$wb['error_inv_main_location'] = 'The given install location folder is invalid.';
+$wb['error_license_agreement'] = 'In order to continue you have to accept the license agreement.';
+$wb['error_no_database_pw'] = 'You have provided no valid database password.';
+$wb['error_short_database_pw'] = 'Please choose a longer database password.';
+$wb['error_no_value_for'] = 'The field "%s" must not be empty.';
+$wb['error_short_value_for'] = 'The field "%s" requires a longer input value.';
+$wb['error_long_value_for'] = 'The field "%s" requires a shorter input value.';
+$wb['error_inv_value_for'] = 'You have entered an invalid value for the field "%s".';
+$wb['error_inv_email_for'] = 'You have entered an invalid mail address for the field "%s".';
+$wb['error_inv_domain_for'] = 'You have entered an invalid domain for the field "%s".';
+$wb['error_inv_integer_for'] = 'You have entered an invalid number for the field "%s".';
+$wb['error_inv_float_for'] = 'You have entered an invalid floating point number for the field "%s".';
+$wb['error_used_location'] = 'The installation path already contains a package installation.';
+$wb['installation_task_txt'] = 'Install planned';
+$wb['installation_error_txt'] = 'Install error';
+$wb['installation_success_txt'] = 'Installed';
+$wb['installation_remove_txt'] = 'Removal planned';
+$wb['packagelist_update_finished_txt'] = 'APS Packagelist update finished.';
+?>
\ 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 0000000..3b9c7e9
--- /dev/null
+++ b/interface/web/sites/lib/lang/en_aps_instances_list.lng
@@ -0,0 +1,13 @@
+<?php
+$wb['list_head_txt'] = 'Installed packages';
+$wb['name_txt'] = 'Name';
+$wb['version_txt'] = 'Version';
+$wb['customer_txt'] = 'Client';
+$wb['status_txt'] = 'Status';
+$wb['install_location_txt'] = 'Install location';
+$wb['pkg_delete_confirmation'] = 'Do you really want to delete this installation?';
+$wb['pkg_reinstall_confirmation'] = 'Do you really want to reinstall this package with the same settings?';
+$wb['filter_txt'] = 'Search';
+$wb['delete_txt'] = 'Delete';
+$wb['reinstall_txt'] = 'Reinstall';
+?>
\ 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 0000000..12cc30d
--- /dev/null
+++ b/interface/web/sites/lib/lang/en_aps_packages_list.lng
@@ -0,0 +1,8 @@
+<?php
+$wb['list_head_txt'] = 'Available packages';
+$wb['name_txt'] = 'Name';
+$wb['version_txt'] = 'Version';
+$wb['category_txt'] = 'Category';
+$wb['status_txt'] = 'Unlocked';
+$wb['filter_txt'] = 'Search';
+?>
\ 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 cbcc62b..a253dcd 100644
--- a/interface/web/sites/lib/module.conf.php
+++ b/interface/web/sites/lib/module.conf.php
@@ -153,6 +153,34 @@
'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 0000000..d07b85a
--- /dev/null
+++ b/interface/web/sites/list/aps_availablepackages.list.php
@@ -0,0 +1,86 @@
+<?php
+/*
+Copyright (c) 2012, ISPConfig UG
+Contributors: web wack creations, http://www.web-wack.at
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of ISPConfig nor the names of its contributors
+ may be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+$liste['name'] = 'aps_packages'; // Name of the list
+$liste['table'] = 'aps_packages'; // Database table
+$liste['table_idx'] = 'id'; // Table index
+$liste["search_prefix"] = 'search_'; // Search field prefix
+$liste['records_per_page'] = 15; // Records per page
+$liste['file'] = 'aps_availablepackages_list.php'; // Script file for this list
+$liste['edit_file'] = ''; // Script file to edit
+$liste['delete_file'] = ''; // Script file to delete
+$liste['paging_tpl'] = 'templates/paging.tpl.htm'; // Paging template
+$liste['auth'] = 'no'; // Handling it myself (check for admin)
+
+// Search fields
+$liste["item"][] = array('field' => '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 => '<div class="swap" id="ir-Yes"><span>'.$app->lng('Yes').'</span></div>',
+ PACKAGE_LOCKED => '<div class="swap" id="ir-No"><span>'.$app->lng('No').'</span></div>'));
+}
+?>
\ 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 0000000..573df2a
--- /dev/null
+++ b/interface/web/sites/list/aps_installedpackages.list.php
@@ -0,0 +1,81 @@
+<?php
+/*
+Copyright (c) 2012, ISPConfig UG
+Contributors: web wack creations, http://www.web-wack.at
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of ISPConfig nor the names of its contributors
+ may be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+$liste['name'] = 'aps_instances'; // Name of the list
+$liste['table'] = 'aps_instances,aps_packages'; // Database table
+$liste['table_idx'] = 'id'; // Table index
+$liste["search_prefix"] = 'search_'; // Search field prefix
+$liste['records_per_page'] = 15; // Records per page
+$liste['file'] = 'aps_installedpackages_list.php'; // Script file for this list
+$liste['edit_file'] = ''; // Script file to edit
+$liste['delete_file'] = ''; // Script file to delete
+$liste['paging_tpl'] = 'templates/paging.tpl.htm'; // Paging template
+$liste['auth'] = 'no'; // Handling it myself (check for admin)
+
+// Search fields
+$liste["item"][] = array('field' => '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 0000000..693a460
--- /dev/null
+++ b/interface/web/sites/templates/aps_install_package.htm
@@ -0,0 +1,54 @@
+<h2>
+ {tmpl_var name='installation_txt'}: {tmpl_var name='pkg_name'} {tmpl_var name='pkg_version'}-{tmpl_var name='pkg_release'}
+ <span style="float:right">
+ <tmpl_if name='pkg_icon' op='!=' value=''>
+ <img src="{tmpl_var name='pkg_icon'}" height="32" width="32" alt="{tmpl_var name='pkg_name'}" style="vertical-align:text-bottom;" />
+ </tmpl_if>
+ </span>
+</h2>
+
+<tmpl_if name='error'>
+ <div id="errorMsg"><h3>ERROR</h3><ol>{tmpl_var name='error'}</ol></div>
+</tmpl_if>
+
+<div class="panel panel_install_package">
+ <div class="pnl_formsarea">
+ <fieldset class="inlineLabels">
+ <legend>{tmpl_var name='basic_settings_txt'}</legend>
+ <div class="ctrlHolder">
+ <label for="main_domain">{tmpl_var name='install_location_txt'}</label>
+ <div class="resetButton">http(s):// </div>DOMAIN_LIST_SPACE<div style="float:left;"> / </div>
+ <input type="text" name="main_location" id="main_location" value="{tmpl_var name='inp_main_location'}" maxlength="255" class="textInput formLengthHalf" />
+ </div>
+ <tmpl_if name='pkg_requirements_database' op='!=' value=''>
+ <div class="ctrlHolder">
+ <label for="main_database_password">{tmpl_var name='new_database_password_txt'}</label>
+ <input type="text" class="textInput" name="main_database_password" id="main_database_password" value="{tmpl_var name='inp_main_database_password'}" size="10" maxlength="255" />
+ </div>
+ </tmpl_if>
+
+PKG_SETTINGS_SPACE
+
+ <legend>{tmpl_var name='license_txt'}</legend>
+ <div class="ctrlHolder">
+ <label for="license">{tmpl_var name='license_txt'}</label>
+ <tmpl_if name='pkg_license_content' op='==' value=''>{tmpl_var name='pkg_license_name'}<br /></tmpl_if>
+ <tmpl_if name='pkg_license_type' op='==' value='url'>
+ <a href="{tmpl_var name='pkg_license_content'}" target="_blank">{tmpl_var name='pkg_license_content'}</a>
+ <tmpl_elseif name='pkg_license_content'>
+ <textarea rows="10" cols="80" id="license_text">{tmpl_var name='pkg_license_content'}</textarea>
+ </tmpl_if>
+ </div>
+ <div class="ctrlHolder">
+ <label for="license">{tmpl_var name='acceptance_txt'}</label>
+ <input type="checkbox" name="license" id="license" <tmpl_if name='inp_license' op='==' value='true'>checked</tmpl_if> /> {tmpl_var name='acceptance_text'}
+ </div>
+ </fieldset>
+
+ <input type="hidden" name="install" value="0" />
+ <div class="buttonHolder buttons">
+ <button class="positive iconstxt icoPositive" type="button" value="{tmpl_var name='btn_install'}" name="btn_install" onClick="document.pageForm.install.value=1; submitForm('pageForm','sites/aps_install_package.php?id={tmpl_var name='pkg_id'}');"><span>{tmpl_var name='btn_install'}</span></button>
+ <button class="negative iconstxt icoNegative" type="button" value="{tmpl_var name='btn_cancel'}" onClick="loadContent('aps/availablepackages_list.php');"><span>{tmpl_var name='btn_cancel'}</span></button>
+ </div>
+ </div>
+</div>
\ 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 0000000..6d0fb57
--- /dev/null
+++ b/interface/web/sites/templates/aps_instances_list.htm
@@ -0,0 +1,60 @@
+<h2>{tmpl_var name="list_head_txt"}</h2>
+
+<div class="panel panel_list_instances">
+ <div class="pnl_listarea">
+ <fieldset><legend>{tmpl_var name="list_head_txt"}</legend>
+ <table class="list">
+ <thead>
+ <tr>
+ <th class="tbl_col_name" scope="col">{tmpl_var name='name_txt'}</th>
+ <th class="tbl_col_version" scope="col">{tmpl_var name='version_txt'}</th>
+ <tmpl_if name='is_noclient'>
+ <th class="tbl_col_customer" scope="col">{tmpl_var name='customer_txt'}</th>
+ </tmpl_if>
+ <th class="tbl_col_installlocation" scope="col">{tmpl_var name='install_location_txt'}</th>
+ <th class="tbl_col_instancestatus" scope="col">{tmpl_var name='status_txt'}</th>
+ <th class="tbl_col_buttons" scope="col" > </th>
+ </tr>
+ <tr>
+ <td class="tbl_col_name"><input type="text" name="search_name" value="{tmpl_var name='search_name'}" /></td>
+ <td class="tbl_col_version"><input type="text" name="search_version" value="{tmpl_var name='search_version'}" /></td>
+ <tmpl_if name='is_noclient'>
+ <td class="tbl_col_customer"><input type="text" name="search_customer_name" value="{tmpl_var name='search_customer_name'}" /></td>
+ </tmpl_if>
+ <td class="tbl_col_installlocation"> </td>
+ <td class="tbl_col_instancestatus"><select name="search_instance_status" onChange="submitForm('pageForm','sites/aps_installedpackages_list.php');">{tmpl_var name='search_instance_status'}</select></td>
+ <td class="tbl_col_buttons">
+ <button type="button" class="icons16 icoFilter" name="Filter" id="Filter" value="{tmpl_var name='filter_txt'}" onClick="submitForm('pageForm','sites/aps_installedpackages_list.php');">
+ <span>{tmpl_var name='filter_txt'}</span>
+ </button>
+ </td>
+ </tr>
+ </thead>
+ <tbody>
+ <tmpl_loop name='records'>
+ <tr class="tbl_row_<tmpl_if name='__EVEN__'}even<tmpl_else>uneven</tmpl_if>">
+ <td class="tbl_col_name"><a href="#" onClick="loadContent('sites/aps_packagedetails_show.php?id={tmpl_var name='package_id'}');">{tmpl_var name='package_name'}</a></td>
+ <td class="tbl_col_version">{tmpl_var name='package_version'}-{tmpl_var name='package_release'}</td>
+ <tmpl_if name='is_noclient'>
+ <td class="tbl_col_customer"><a href="#" onClick="loadContent('client/client_edit.php?id={tmpl_var name='CustomerID'}');">{tmpl_var name='customer_name'}</a></td>
+ </tmpl_if>
+ <td class="tbl_col_installlocation"><a href="http://{tmpl_var name='install_location'}" target="_blank">{tmpl_var name='install_location_short'}</a></td>
+ <td class="tbl_col_instancestatus"><span id="status_content{tmpl_var name='__ROWNUM__'}">{tmpl_var name='instance_status'}</span></td>
+ <td class="tbl_col_buttons">
+ <div class="buttons icons16" style="width:60px;">
+ <tmpl_if name='delete_possible'><a class="icons16 icoDelete" href="javascript:if(window.confirm('{tmpl_var name='pkg_delete_confirmation'}')){ loadContentInto('status_content{tmpl_var name='__ROWNUM__'}', 'sites/aps_do_operation.php?action=delete_instance&id={tmpl_var name='id'}&phpsessid={tmpl_var name='phpsessid'}'); }"><span>{tmpl_var name='delete_txt'}</span></a></tmpl_if>
+ <tmpl_if name='reinstall_possible'><a class="icons16 icoEdit" href="javascript:if(window.confirm('{tmpl_var name='pkg_reinstall_confirmation'}')){ loadContentInto('status_content{tmpl_var name='__ROWNUM__'}', 'sites/aps_do_operation.php?action=reinstall_instance&id={tmpl_var name='id'}&phpsessid={tmpl_var name='phpsessid'}'); }"><span>{tmpl_var name='reinstall_txt'}</span></a></tmpl_if>
+ </div>
+ </td>
+ </tr>
+ </tmpl_loop>
+ </tbody>
+ <tfoot>
+ <tr>
+ <td class="tbl_footer tbl_paging" colspan="6">{tmpl_var name='paging'}</td>
+ </tr>
+ </tfoot>
+ </table>
+ </fieldset>
+ </div>
+</div>
\ 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 0000000..267a1ea
--- /dev/null
+++ b/interface/web/sites/templates/aps_packagedetails_show.htm
@@ -0,0 +1,122 @@
+<h2>
+ <tmpl_if name='pkg_icon' op='!=' value=''>
+ <img src="{tmpl_var name='pkg_icon'}" height="32" width="32" alt="{tmpl_var name='pkg_name'}" style="vertical-align:text-bottom;" />
+ </tmpl_if>
+ {tmpl_var name='pkg_name'}
+</h2>
+<b>{tmpl_var name='pkg_summary'}</b>
+<p> </p>
+
+<div class="pnl_toolsarea">
+ <div class="buttons">
+ <button class="iconstxt icoAdd" type="button" onClick="loadContent('sites/aps_install_package.php?id={tmpl_var name='pkg_id'}');">
+ <span>{tmpl_var name='install_package_txt'}</span>
+ </button>
+ </div>
+ <p> </p><p> </p>
+</ddiv>
+
+
+<div class="tabbox_tabs">
+ <input type="hidden" name="next_tab" value="" />
+ <ul>
+ <li<tmpl_if name='next_tab' op='==' value='details'> class="active"</tmpl_if>>
+ <a href="javascript:changeTab('details', 'sites/aps_packagedetails_show.php?id={tmpl_var name='pkg_id'}');">{tmpl_var name='details_txt'}</a>
+ </li>
+ <tmpl_if name='pkg_screenshots'>
+ <li<tmpl_if name='next_tab' op='==' value='screenshots'> class="active"</tmpl_if>>
+ <a href="javascript:changeTab('screenshots', 'sites/aps_packagedetails_show.php?id={tmpl_var name='pkg_id'}');">{tmpl_var name='screenshots_txt'}</a>
+ </li></tmpl_if>
+ <tmpl_if name='pkg_changelog'>
+ <li<tmpl_if name='next_tab' op='==' value='changelog'> class="active"</tmpl_if>>
+ <a href="javascript:changeTab('changelog', 'sites/aps_packagedetails_show.php?id={tmpl_var name='pkg_id'}');">{tmpl_var name='changelog_txt'}</a>
+ </li></tmpl_if>
+ <li<tmpl_if name='next_tab' op='==' value='settings'> class="active"</tmpl_if>>
+ <a href="javascript:changeTab('settings', 'sites/aps_packagedetails_show.php?id={tmpl_var name='pkg_id'}');">{tmpl_var name='settings_txt'}</a>
+ </li>
+ </ul>
+</div>
+<p> </p>
+
+<div class="panel panel_list_packages">
+ <div class="pnl_listarea">
+ <table class="list">
+ <tbody>
+ <tmpl_if name='next_tab' op='==' value='details'>
+ <tr class="tbl_row_uneven">
+ <td width="25%">{tmpl_var name='version_txt'}</td>
+ <td>{tmpl_var name='pkg_version'} (Release {tmpl_var name='pkg_release'})</td>
+ </tr>
+ <tr class="tbl_row_even">
+ <td>{tmpl_var name='category_txt'}</td>
+ <td>{tmpl_var name='pkg_category'}</td>
+ </tr>
+ <tr class="tbl_row_uneven">
+ <td>{tmpl_var name='description_txt'}</td>
+ <td>{tmpl_var name='pkg_description'}</td>
+ </tr>
+ <tr class="tbl_row_even">
+ <td>{tmpl_var name='homepage_txt'}</td>
+ <td><a href="{tmpl_var name='pkg_homepage'}" target="_blank">{tmpl_var name='pkg_homepage'}</a></td>
+ </tr>
+ <tr class="tbl_row_uneven">
+ <td>{tmpl_var name='installed_size_txt'}</td>
+ <td>{tmpl_var name='pkg_installed_size'}</td>
+ </tr>
+ <tr class="tbl_row_even">
+ <td>{tmpl_var name='supported_languages_txt'}</td>
+ <td>{tmpl_var name='pkg_languages'}</td>
+ </tr>
+ <tr class="tbl_row_uneven">
+ <td>{tmpl_var name='config_script_txt'}</td>
+ <td>{tmpl_var name='pkg_config_script'}</td>
+ </tr>
+ <tr class="tbl_row_even">
+ <td>{tmpl_var name='license_txt'}</td>
+ <td>
+ <tmpl_if name='pkg_license_name'>{tmpl_var name='pkg_license_name'}<br /></tmpl_if>
+ <tmpl_if name='pkg_license_type' op='==' value='url'>
+ <a href="{tmpl_var name='pkg_license_content'}" target="_blank">{tmpl_var name='pkg_license_content'}</a>
+ <tmpl_elseif name='pkg_license_content'>
+ <textarea rows="10" cols="80">{tmpl_var name='pkg_license_content'}</textarea>
+ </tmpl_if>
+ </td>
+ </tr>
+ <tmpl_elseif name='next_tab' op='==' value='screenshots'>
+ <tmpl_if name='pkg_screenshots'><tr class="tbl_row_even">
+ <td style="text-align:center;"><tmpl_loop name='pkg_screenshots'>
+ <img src="{tmpl_var name='ScreenPath'}" alt="{tmpl_var name='ScreenDescription'}" /><br />
+ <em>{tmpl_var name='ScreenDescription'}</em><br /><br />
+ </tmpl_loop></td>
+ </tr></tmpl_if>
+ <tmpl_elseif name='next_tab' op='==' value='changelog'>
+ <tmpl_if name='pkg_changelog'><tr class="tbl_row_even">
+ <td><ul><tmpl_loop name='pkg_changelog'>
+ <li>{tmpl_var name='ChangelogVersion'}</li>
+ <ul><tmpl_if name='ChangelogDescription'><li>{tmpl_var name='ChangelogDescription'}</li></tmpl_if></ul>
+ </tmpl_loop></ul></td>
+ </tr></tmpl_if>
+ <tmpl_elseif name='next_tab' op='==' value='settings'>
+ <tr class="tbl_row_uneven">
+ <td width="25%">{tmpl_var name='php_extensions_txt'}</td>
+ <td>{tmpl_var name='pkg_requirements_php_extensions'}</td>
+ </tr>
+ <tr class="tbl_row_even">
+ <td>{tmpl_var name='php_settings_txt'}</td>
+ <td><tmpl_loop name='pkg_requirements_php_settings'>
+ {tmpl_var name='PHPSettingName'} = {tmpl_var name='PHPSettingValue'}<br />
+ </tmpl_loop></td>
+ </tr>
+ <tr class="tbl_row_uneven">
+ <td>{tmpl_var name='supported_php_versions_txt'}</td>
+ <td>{tmpl_var name='pkg_requirements_supported_php_versions'}</td>
+ </tr>
+ <tr class="tbl_row_even">
+ <td>{tmpl_var name='database_txt'}</td>
+ <td>{tmpl_var name='pkg_requirements_database'}</a></td>
+ </tr>
+ </tmpl_if>
+ </tbody>
+ </table>
+ </div>
+</div>
\ 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 0000000..941a8b1
--- /dev/null
+++ b/interface/web/sites/templates/aps_packages_list.htm
@@ -0,0 +1,48 @@
+<h2>{tmpl_var name="list_head_txt"}</h2>
+
+<div class="panel panel_list_packages">
+ <div class="pnl_listarea">
+ <fieldset><legend>{tmpl_var name="list_head_txt"} ({tmpl_var name='package_count'})</legend>
+ <table class="list">
+ <thead>
+ <tr>
+ <th class="tbl_col_name" scope="col">{tmpl_var name='name_txt'}</th>
+ <th class="tbl_col_version" scope="col">{tmpl_var name='version_txt'}</th>
+ <th class="tbl_col_category" scope="col">{tmpl_var name='category_txt'}</th>
+ <tmpl_if name='is_admin'>
+ <th class="tbl_col_status" scope="col">{tmpl_var name='status_txt'}</th>
+ </tmpl_if>
+ <th class="tbl_col_buttons" scope="col" width="60px;"> </th>
+ </tr>
+ <tr>
+ <td class="tbl_col_name"><input type="text" name="search_name" value="{tmpl_var name='search_name'}" /></td>
+ <td class="tbl_col_version"><input type="text" name="search_version" value="{tmpl_var name='search_version'}" /></td>
+ <td class="tbl_col_customerid"><select name="search_category" onChange="submitForm('pageForm','sites/aps_availablepackages_list.php');">{tmpl_var name='search_category'}</select></td>
+ <tmpl_if name='is_admin'>
+ <td class="tbl_col_status"><select name="search_package_status" onChange="submitForm('pageForm','sites/aps_availablepackages_list.php');">{tmpl_var name='search_package_status'}</select></td>
+ </tmpl_if>
+ <td class="tbl_col_buttons"><button type="button" class="icons16 icoFilter" name="Filter" id="Filter" value="{tmpl_var name='filter_txt'}" onClick="submitForm('pageForm','sites/aps_availablepackages_list.php');"><span>{tmpl_var name='filter_txt'}</span></button></td>
+ </tr>
+ </thead>
+ <tbody>
+ <tmpl_loop name='records'>
+ <tr class="tbl_row_<tmpl_if name='__EVEN__'}even<tmpl_else>uneven</tmpl_if>">
+ <td class="tbl_col_name"><a href="#" onClick="loadContent('sites/aps_packagedetails_show.php?id={tmpl_var name='id'}');">{tmpl_var name='name'}</a></td>
+ <td class="tbl_col_version">{tmpl_var name='version'}-{tmpl_var name='release'}</td>
+ <td class="tbl_col_category">{tmpl_var name='category'}</td>
+ <tmpl_if name='is_admin'>
+ <td class="tbl_col_status"><a href="javascript:loadContentInto('status_content{tmpl_var name='__ROWNUM__'}', 'sites/aps_do_operation.php?action=change_status&id={tmpl_var name='id'}&phpsessid={tmpl_var name='phpsessid'}');"><span id="status_content{tmpl_var name='__ROWNUM__'}">{tmpl_var name='package_status'}</span></a></td>
+ </tmpl_if>
+ <td> </td>
+ </tr>
+ </tmpl_loop>
+ </tbody>
+ <tfoot>
+ <tr>
+ <td class="tbl_footer tbl_paging" colspan="5">{tmpl_var name='paging'}</td>
+ </tr>
+ </tfoot>
+ </table>
+ </fieldset>
+ </div>
+</div>
diff --git a/interface/web/themes/default/css/screen/content_ispc.css b/interface/web/themes/default/css/screen/content_ispc.css
index b3d9354..2678187 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 0000000..7c4804e
--- /dev/null
+++ b/interface/web/themes/default/images/ajax-loader.gif
Binary files differ
--
Gitblit v1.9.1