tbrehm
2012-05-24 477d4e981a4171dcc8b7886b2130abbee0442067
Initial commit of the interface part of the APS installer.
20 files added
8 files modified
2792 ■■■■■ changed files
install/dist/lib/fedora.lib.php 4 ●●●● patch | view | raw | blame | history
install/dist/lib/gentoo.lib.php 4 ●●●● patch | view | raw | blame | history
install/dist/lib/opensuse.lib.php 4 ●●●● patch | view | raw | blame | history
install/lib/installer_base.lib.php 4 ●●●● patch | view | raw | blame | history
install/sql/incremental/upd_0034.sql 76 ●●●●● patch | view | raw | blame | history
install/sql/ispconfig3.sql 71 ●●●●● patch | view | raw | blame | history
interface/lib/classes/aps_base.inc.php 109 ●●●●● patch | view | raw | blame | history
interface/lib/classes/aps_crawler.inc.php 534 ●●●●● patch | view | raw | blame | history
interface/lib/classes/aps_guicontroller.inc.php 770 ●●●●● patch | view | raw | blame | history
interface/web/js/scrigo.js.php 5 ●●●● patch | view | raw | blame | history
interface/web/sites/aps_availablepackages_list.php 56 ●●●●● patch | view | raw | blame | history
interface/web/sites/aps_cron_apscrawler_if.php 59 ●●●●● patch | view | raw | blame | history
interface/web/sites/aps_do_operation.php 110 ●●●●● patch | view | raw | blame | history
interface/web/sites/aps_install_package.php 198 ●●●●● patch | view | raw | blame | history
interface/web/sites/aps_installedpackages_list.php 127 ●●●●● patch | view | raw | blame | history
interface/web/sites/aps_packagedetails_show.php 99 ●●●●● patch | view | raw | blame | history
interface/web/sites/lib/lang/en_aps.lng 57 ●●●●● patch | view | raw | blame | history
interface/web/sites/lib/lang/en_aps_instances_list.lng 13 ●●●●● patch | view | raw | blame | history
interface/web/sites/lib/lang/en_aps_packages_list.lng 8 ●●●●● patch | view | raw | blame | history
interface/web/sites/lib/module.conf.php 28 ●●●●● patch | view | raw | blame | history
interface/web/sites/list/aps_availablepackages.list.php 86 ●●●●● patch | view | raw | blame | history
interface/web/sites/list/aps_installedpackages.list.php 81 ●●●●● patch | view | raw | blame | history
interface/web/sites/templates/aps_install_package.htm 54 ●●●●● patch | view | raw | blame | history
interface/web/sites/templates/aps_instances_list.htm 60 ●●●●● patch | view | raw | blame | history
interface/web/sites/templates/aps_packagedetails_show.htm 122 ●●●●● patch | view | raw | blame | history
interface/web/sites/templates/aps_packages_list.htm 48 ●●●●● patch | view | raw | blame | history
interface/web/themes/default/css/screen/content_ispc.css 5 ●●●● patch | view | raw | blame | history
interface/web/themes/default/images/ajax-loader.gif patch | view | raw | blame | history
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");
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');
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");
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);
install/sql/incremental/upd_0034.sql
New file
@@ -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`;
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', '');
-- --------------------------------------------------------
--
interface/lib/classes/aps_base.inc.php
New file
@@ -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;
    }
}
?>
interface/lib/classes/aps_crawler.inc.php
New file
@@ -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;
        }
    }
}
?>
interface/lib/classes/aps_guicontroller.inc.php
New file
@@ -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;
    }
}
?>
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');
interface/web/sites/aps_availablepackages_list.php
New file
@@ -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();
?>
interface/web/sites/aps_cron_apscrawler_if.php
New file
@@ -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>';
?>
interface/web/sites/aps_do_operation.php
New file
@@ -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');
}
?>
interface/web/sites/aps_install_package.php
New file
@@ -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;
?>
interface/web/sites/aps_installedpackages_list.php
New file
@@ -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();
?>
interface/web/sites/aps_packagedetails_show.php
New file
@@ -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();
?>
interface/web/sites/lib/lang/en_aps.lng
New file
@@ -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.';
?>
interface/web/sites/lib/lang/en_aps_instances_list.lng
New file
@@ -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';
?>
interface/web/sites/lib/lang/en_aps_packages_list.lng
New file
@@ -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';
?>
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();
interface/web/sites/list/aps_availablepackages.list.php
New file
@@ -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>'));
}
?>
interface/web/sites/list/aps_installedpackages.list.php
New file
@@ -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')));
?>
interface/web/sites/templates/aps_install_package.htm
New file
@@ -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)://&nbsp;</div>DOMAIN_LIST_SPACE<div style="float:left;">&nbsp;/&nbsp;</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> />&nbsp;&nbsp;{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>
interface/web/sites/templates/aps_instances_list.htm
New file
@@ -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" >&nbsp;</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">&nbsp;</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>
interface/web/sites/templates/aps_packagedetails_show.htm
New file
@@ -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>&nbsp;</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>&nbsp;</p><p>&nbsp;</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>&nbsp;</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>
interface/web/sites/templates/aps_packages_list.htm
New file
@@ -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;">&nbsp;</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>&nbsp;</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>
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;
interface/web/themes/default/images/ajax-loader.gif