tbrehm
2012-02-03 5a43e7a2ea0cf7af35c100cb67e4a53566cbc496
- Implemented new backup and restore functions for websites and databases (see also FS#1389)
- Added "actions" framework in server to replace the functions provided by the core modules
- Moved system update function from remoteactins core module to software update plugin.
27 files modified
5 files added
974 ■■■■ changed files
install/sql/incremental/upd_0028.sql 12 ●●●●● patch | view | raw | blame | history
install/sql/ispconfig3.sql 20 ●●●●● patch | view | raw | blame | history
install/tpl/server.ini.master 2 ●●● patch | view | raw | blame | history
interface/lib/classes/plugin_backuplist.inc.php 138 ●●●●● patch | view | raw | blame | history
interface/web/admin/form/server_config.tform.php 10 ●●●●● patch | view | raw | blame | history
interface/web/admin/lib/lang/en_server_config.lng 5 ●●●●● patch | view | raw | blame | history
interface/web/admin/templates/server_config_server_edit.htm 10 ●●●● patch | view | raw | blame | history
interface/web/js/scrigo.js.php 6 ●●●●● patch | view | raw | blame | history
interface/web/mail/mail_user_edit.php 8 ●●●● patch | view | raw | blame | history
interface/web/sites/database_edit.php 93 ●●●● patch | view | raw | blame | history
interface/web/sites/form/database.tform.php 11 ●●●●● patch | view | raw | blame | history
interface/web/sites/form/web_domain.tform.php 11 ●●●● patch | view | raw | blame | history
interface/web/sites/lib/lang/en_database.lng 5 ●●●●● patch | view | raw | blame | history
interface/web/sites/lib/lang/en_web_backup_list.lng 16 ●●●●● patch | view | raw | blame | history
interface/web/sites/templates/database_edit.htm 18 ●●●● patch | view | raw | blame | history
interface/web/sites/templates/web_backup_list.htm 40 ●●●●● patch | view | raw | blame | history
interface/web/sites/templates/web_domain_backup.htm 2 ●●●●● patch | view | raw | blame | history
interface/web/sites/tools.inc.php 2 ●●● patch | view | raw | blame | history
interface/web/sites/web_domain_del.php 6 ●●●●● patch | view | raw | blame | history
interface/web/sites/web_domain_edit.php 23 ●●●●● patch | view | raw | blame | history
interface/web/themes/default/css/screen/content_ispc.css 2 ●●●●● patch | view | raw | blame | history
server/cron_daily.php 162 ●●●● patch | view | raw | blame | history
server/lib/classes/db_mysql.inc.php 7 ●●●●● patch | view | raw | blame | history
server/lib/classes/modules.inc.php 38 ●●●●● patch | view | raw | blame | history
server/lib/classes/plugins.inc.php 42 ●●●●● patch | view | raw | blame | history
server/mods-available/remoteaction_core_module.inc.php 7 ●●●●● patch | view | raw | blame | history
server/mods-available/web_module.inc.php 11 ●●●●● patch | view | raw | blame | history
server/plugins-available/backup_plugin.inc.php 134 ●●●●● patch | view | raw | blame | history
server/plugins-available/mysql_clientdb_plugin.inc.php 12 ●●●● patch | view | raw | blame | history
server/plugins-available/openvz_plugin.inc.php 54 ●●●●● patch | view | raw | blame | history
server/plugins-available/software_update_plugin.inc.php 34 ●●●● patch | view | raw | blame | history
server/server.php 33 ●●●● patch | view | raw | blame | history
install/sql/incremental/upd_0028.sql
New file
@@ -0,0 +1,12 @@
ALTER TABLE  `web_database` ADD  `parent_domain_id` int(11) unsigned NOT NULL DEFAULT  '0' AFTER  `server_id`;
ALTER TABLE  `web_database` ADD  `backup_interval` VARCHAR( 255 ) NOT NULL DEFAULT 'none', ADD `backup_copies` INT NOT NULL DEFAULT '1' AFTER  `remote_ips`;
CREATE TABLE `web_backup` (
  `backup_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `server_id` int(10) unsigned NOT NULL,
  `parent_domain_id` int(10) unsigned NOT NULL,
  `backup_type` enum('web','mysql') NOT NULL DEFAULT 'web',
  `backup_mode` varchar(64) NOT NULL DEFAULT  '',
  `tstamp` int(10) unsigned NOT NULL,
  `filename` varchar(255) NOT NULL,
  PRIMARY KEY (`backup_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
install/sql/ispconfig3.sql
@@ -1438,6 +1438,23 @@
-- --------------------------------------------------------
--
-- Table structure for table `web_backup`
--
CREATE TABLE `web_backup` (
  `backup_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `server_id` int(10) unsigned NOT NULL,
  `parent_domain_id` int(10) unsigned NOT NULL,
  `backup_type` enum('web','mysql') NOT NULL DEFAULT 'web',
  `backup_mode` varchar(64) NOT NULL DEFAULT  '',
  `tstamp` int(10) unsigned NOT NULL,
  `filename` varchar(255) NOT NULL,
  PRIMARY KEY (`backup_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
-- --------------------------------------------------------
--
-- Table structure for table `web_database`
--
@@ -1449,6 +1466,7 @@
  `sys_perm_group` varchar(5) DEFAULT NULL,
  `sys_perm_other` varchar(5) DEFAULT NULL,
  `server_id` int(11) unsigned NOT NULL DEFAULT '0',
  `parent_domain_id` int(11) unsigned NOT NULL DEFAULT  '0',
  `type` varchar(16) NOT NULL DEFAULT 'y',
  `database_name` varchar(64) DEFAULT NULL,
  `database_user` varchar(64) DEFAULT NULL,
@@ -1456,6 +1474,8 @@
  `database_charset` varchar(64) DEFAULT NULL,
  `remote_access` enum('n','y') NOT NULL DEFAULT 'y',
  `remote_ips` text NOT NULL,
  `backup_interval` VARCHAR( 255 ) NOT NULL DEFAULT 'none',
  `backup_copies` INT NOT NULL DEFAULT '1',
  `active` enum('n','y') NOT NULL DEFAULT 'y',
  PRIMARY KEY (`database_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
install/tpl/server.ini.master
@@ -13,7 +13,7 @@
nameservers=192.168.0.1,192.168.0.2
loglevel=2
backup_dir=/var/backup
backup_dir_ftpread=n
backup_mode=rootgz
[mail]
module=postfix_mysql
interface/lib/classes/plugin_backuplist.inc.php
New file
@@ -0,0 +1,138 @@
<?php
/*
Copyright (c) 2012, Till Brehm, ISPConfig UG
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.
*/
class plugin_backuplist extends plugin_base {
        var $module;
        var $form;
        var $tab;
        var $record_id;
        var $formdef;
        var $options;
        function onShow() {
                global $app;
                $listTpl = new tpl;
                $listTpl->newTemplate('templates/web_backup_list.htm');
                //* Loading language file
                $lng_file = "lib/lang/".$_SESSION["s"]["language"]."_web_backup_list.lng";
                include($lng_file);
                $listTpl->setVar($wb);
                $message = '';
                $error = '';
                if(isset($_GET['backup_action'])) {
                    $backup_id = intval($_GET['backup_id']);
                    if($_GET['backup_action'] == 'download' && $backup_id > 0) {
                        $sql = "SELECT count(action_id) as number FROM sys_remoteaction WHERE action_state = 'pending' AND action_type = 'backup_download' AND action_param = '$backup_id'";
                        $tmp = $app->db->queryOneRecord($sql);
                        if($tmp['number'] == 0) {
                            $message .= $wb['download_info_txt'];
                            $sql =     "INSERT INTO sys_remoteaction (server_id, tstamp, action_type, action_param, action_state, response) " .
                                "VALUES (".
                                (int)$this->form->dataRecord['server_id'] . ", " .
                                time() . ", " .
                                "'backup_download', " .
                                "'".$backup_id."', " .
                                "'pending', " .
                                "''" .
                                ")";
                            $app->db->query($sql);
                        } else {
                            $error .= $wb['download_pending_txt'];
                        }
                    }
                    if($_GET['backup_action'] == 'restore' && $backup_id > 0) {
                        $sql = "SELECT count(action_id) as number FROM sys_remoteaction WHERE action_state = 'pending' AND action_type = 'backup_restore' AND action_param = '$backup_id'";
                        $tmp = $app->db->queryOneRecord($sql);
                        if($tmp['number'] == 0) {
                            $message .= $wb['restore_info_txt'];
                            $sql =     "INSERT INTO sys_remoteaction (server_id, tstamp, action_type, action_param, action_state, response) " .
                                "VALUES (".
                                (int)$this->form->dataRecord['server_id'] . ", " .
                                time() . ", " .
                                "'backup_restore', " .
                                "'".$backup_id."', " .
                                "'pending', " .
                                "''" .
                                ")";
                        $app->db->query($sql);
                        } else {
                            $error .= $wb['restore_pending_txt'];
                        }
                    }
                }
                //* Get the data
                $sql = "SELECT * FROM web_backup WHERE parent_domain_id = ".$this->form->id." ORDER BY tstamp DESC, backup_type ASC";
                $records = $app->db->queryAllRecords($sql);
                $bgcolor = "#FFFFFF";
                if(is_array($records)) {
                        foreach($records as $rec) {
                                // Change of color
                                $bgcolor = ($bgcolor == "#FFFFFF")?"#EEEEEE":"#FFFFFF";
                                $rec["bgcolor"] = $bgcolor;
                                $rec['date'] = date($app->lng('conf_format_datetime'),$rec['tstamp']);
                                $rec['backup_type'] = $wb[('backup_type_'.$rec['backup_type'])];
                                $records_new[] = $rec;
                        }
                }
                $listTpl->setLoop('records',@$records_new);
                $listTpl->setVar('parent_id',$this->form->id);
                $listTpl->setVar('msg',$message);
                $listTpl->setVar('error',$error);
                // Setting Returnto information in the session
                $list_name = 'backup_list';
                // $_SESSION["s"]["list"][$list_name]["parent_id"] = $app->tform_actions->id;
                $_SESSION["s"]["list"][$list_name]["parent_id"] = $this->form->id;
                $_SESSION["s"]["list"][$list_name]["parent_name"] = $app->tform->formDef["name"];
                $_SESSION["s"]["list"][$list_name]["parent_tab"] = $_SESSION["s"]["form"]["tab"];
                $_SESSION["s"]["list"][$list_name]["parent_script"] = $app->tform->formDef["action"];
                $_SESSION["s"]["form"]["return_to"] = $list_name;
                return $listTpl->grab();
        }
}
?>
interface/web/admin/form/server_config.tform.php
@@ -138,11 +138,13 @@
            'width' => '40',
            'maxlength' => '255'
        ),
        'backup_dir_ftpread' => array(
        'backup_mode' => array(
            'datatype' => 'VARCHAR',
            'formtype' => 'CHECKBOX',
            'default' => 'y',
            'value' => array(0 => 'n', 1 => 'y')
            'formtype' => 'SELECT',
            'default' => 'userzip',
            'value' => array('userzip' => 'backup_mode_userzip', 'rootgz' => 'backup_mode_rootgz'),
            'width' => '40',
            'maxlength' => '255'
        ),
    ##################################
    # ENDE Datatable fields
interface/web/admin/lib/lang/en_server_config.lng
@@ -87,7 +87,9 @@
$wb["CA_path_txt"] = 'CA Path';
$wb["CA_pass_txt"] = 'CA passphrase';
$wb["fastcgi_config_syntax_txt"] = 'FastCGI config syntax';
$wb["backup_dir_ftpread_txt"] = 'Backup dir. readable for website FTP users.';
$wb["backup_mode_txt"] = 'Backup mode';
$wb["backup_mode_userzip"] = 'Backup web files owned by web user as zip';
$wb["backup_mode_rootgz"] = 'Backup all files in web directory as root user';
$wb["server_type_txt"] = 'Server Type';
$wb["nginx_vhost_conf_dir_txt"] = 'Nginx Vhost config dir';
$wb["nginx_vhost_conf_enabled_dir_txt"] = 'Nginx Vhost config enabled dir';
@@ -153,7 +155,6 @@
$wb["add_web_users_to_sshusers_group_txt"] = 'Add web users to -sshusers- group';
$wb["connect_userid_to_webid_txt"] = 'Connect Linux userid to webid';
$wb["connect_userid_to_webid_start_txt"] = 'Start ID for userid/webid connect';
$wb["realtime_blackhole_list_txt"] = 'Real-time Blackhole List';
$wb["realtime_blackhole_list_note_txt"] = '(Separate RBL\'s with commas)';
?>
interface/web/admin/templates/server_config_server_edit.htm
@@ -43,11 +43,11 @@
        <input name="backup_dir" id="backup_dir" value="{tmpl_var name='backup_dir'}" size="40" maxlength="255" type="text" class="textInput" />
      </div>
      <div class="ctrlHolder">
        <p class="label">{tmpl_var name='backup_dir_ftpread_txt'}</p>
          <div class="multiField">
            {tmpl_var name='backup_dir_ftpread'}
          </div>
      </div>
          <label for="backup_mode">{tmpl_var name='backup_mode_txt'}</label>
        <select name="backup_mode" id="backup_mode" class="selectInput">
                    {tmpl_var name='backup_mode'}
                </select>
      </div>
    </fieldset>
    <input type="hidden" name="id" value="{tmpl_var name='id'}">
interface/web/js/scrigo.js.php
@@ -269,6 +269,12 @@
  }
}
function confirm_action(link,confirmation) {
  if(window.confirm(confirmation)) {
          loadContent(link);
  }
}
function loadContentInto(elementid,pagename) {
  var pageContentObject2 = jQuery.ajax({    type: "GET", 
                                            url: pagename,
interface/web/mail/mail_user_edit.php
@@ -231,14 +231,14 @@
        // Spamfilter policy
        $policy_id = intval($this->dataRecord["policy"]);
        if($policy_id > 0) {
            $tmp_user = $app->db->queryOneRecord("SELECT id FROM spamfilter_users WHERE email = '".mysql_real_escape_string($this->dataRecord["email"])."'");
            $tmp_user = $app->db->queryOneRecord("SELECT id FROM spamfilter_users WHERE email = '".$app->db->quote($this->dataRecord["email"])."'");
            if($tmp_user["id"] > 0) {
                // There is already a record that we will update
                $app->db->datalogUpdate('spamfilter_users', "policy_id = $policy_id", 'id', $tmp_user["id"]);
            } else {
                // We create a new record
                $insert_data = "(`sys_userid`, `sys_groupid`, `sys_perm_user`, `sys_perm_group`, `sys_perm_other`, `server_id`, `priority`, `policy_id`, `email`, `fullname`, `local`) 
                        VALUES (".$_SESSION["s"]["user"]["userid"].", ".$domain["sys_groupid"].", 'riud', 'riud', '', ".$domain["server_id"].", 10, ".$policy_id.", '".mysql_real_escape_string($this->dataRecord["email"])."', '".mysql_real_escape_string($this->dataRecord["email"])."', 'Y')";
                        VALUES (".$_SESSION["s"]["user"]["userid"].", ".$domain["sys_groupid"].", 'riud', 'riud', '', ".$domain["server_id"].", 10, ".$policy_id.", '".$app->db->quote($this->dataRecord["email"])."', '".$app->db->quote($this->dataRecord["email"])."', 'Y')";
                $app->db->datalogInsert('spamfilter_users', $insert_data, 'id');
            }
        }  // endif spamfilter policy
@@ -266,7 +266,7 @@
        
            // Spamfilter policy
            $policy_id = intval($this->dataRecord["policy"]);
            $tmp_user = $app->db->queryOneRecord("SELECT id FROM spamfilter_users WHERE email = '".mysql_real_escape_string($this->dataRecord["email"])."'");
            $tmp_user = $app->db->queryOneRecord("SELECT id FROM spamfilter_users WHERE email = '".$app->db->quote($this->dataRecord["email"])."'");
            if($policy_id > 0) {
                if($tmp_user["id"] > 0) {
                    // There is already a record that we will update
@@ -274,7 +274,7 @@
                } else {
                    // We create a new record
                    $insert_data = "(`sys_userid`, `sys_groupid`, `sys_perm_user`, `sys_perm_group`, `sys_perm_other`, `server_id`, `priority`, `policy_id`, `email`, `fullname`, `local`) 
                            VALUES (".$_SESSION["s"]["user"]["userid"].", ".$domain["sys_groupid"].", 'riud', 'riud', '', ".$domain["server_id"].", 10, ".$policy_id.", '".mysql_real_escape_string($this->dataRecord["email"])."', '".mysql_real_escape_string($this->dataRecord["email"])."', 'Y')";
                            VALUES (".$_SESSION["s"]["user"]["userid"].", ".$domain["sys_groupid"].", 'riud', 'riud', '', ".$domain["server_id"].", 10, ".$policy_id.", '".$app->db->quote($this->dataRecord["email"])."', '".$app->db->quote($this->dataRecord["email"])."', 'Y')";
                    $app->db->datalogInsert('spamfilter_users', $insert_data, 'id');
                }
            }else {
interface/web/sites/database_edit.php
@@ -91,20 +91,6 @@
            $tmp = $app->db->queryOneRecord("SELECT server_name FROM server WHERE server_id = $client[default_webserver]");
            $app->tpl->setVar("server_id","<option value='$client[default_webserver]'>$tmp[server_name]</option>");
            unset($tmp);
            // Fill the client select field
            $sql = "SELECT groupid, name FROM sys_group, client WHERE sys_group.client_id = client.client_id AND client.parent_client_id = ".$client['client_id']." ORDER BY name";
            $clients = $app->db->queryAllRecords($sql);
            $tmp = $app->db->queryOneRecord("SELECT groupid FROM sys_group WHERE client_id = ".$client['client_id']);
            $client_select = '<option value="'.$tmp['groupid'].'">'.$client['contact_name'].'</option>';
            //$tmp_data_record = $app->tform->getDataRecord($this->id);
            if(is_array($clients)) {
                foreach( $clients as $client) {
                    $selected = @(is_array($this->dataRecord) && ($client["groupid"] == $this->dataRecord['client_group_id'] || $client["groupid"] == $this->dataRecord['sys_groupid']))?'SELECTED':'';
                    $client_select .= "<option value='$client[groupid]' $selected>$client[name]</option>\r\n";
                }
            }
            $app->tpl->setVar("client_group_id",$client_select);
        } else {
@@ -116,33 +102,6 @@
                $tmp = $app->db->queryOneRecord("SELECT server_id FROM server WHERE web_server = 1 ORDER BY server_name LIMIT 0,1");
                $server_id = $tmp['server_id'];
            }
            $sql = "SELECT ip_address FROM server_ip WHERE server_id = $server_id";
            $ips = $app->db->queryAllRecords($sql);
            $ip_select = "<option value='*'>*</option>";
            //$ip_select = "";
            if(is_array($ips)) {
                foreach( $ips as $ip) {
                    $selected = ($ip["ip_address"] == $this->dataRecord["ip_address"])?'SELECTED':'';
                    $ip_select .= "<option value='$ip[ip_address]' $selected>$ip[ip_address]</option>\r\n";
                }
            }
            $app->tpl->setVar("ip_address",$ip_select);
            unset($tmp);
            unset($ips);
            // Fill the client select field
            $sql = "SELECT groupid, name FROM sys_group WHERE client_id > 0 ORDER BY name";
            $clients = $app->db->queryAllRecords($sql);
            $client_select = "<option value='0'></option>";
            //$tmp_data_record = $app->tform->getDataRecord($this->id);
            if(is_array($clients)) {
                foreach( $clients as $client) {
                    $selected = @(is_array($this->dataRecord) && ($client["groupid"] == $this->dataRecord['client_group_id'] || $client["groupid"] == $this->dataRecord['sys_groupid']))?'SELECTED':'';
                    $client_select .= "<option value='$client[groupid]' $selected>$client[name]</option>\r\n";
                }
            }
            $app->tpl->setVar("client_group_id",$client_select);
        }
@@ -212,9 +171,6 @@
                }
            }
            // Clients may not set the client_group_id, so we unset them if user is not a admin and the client is not a reseller
            if(!$app->auth->has_clients($_SESSION['s']['user']['userid'])) unset($this->dataRecord["client_group_id"]);
        }
@@ -224,10 +180,8 @@
    function onBeforeUpdate() {
        global $app, $conf, $interfaceConf;
        /*
        * If the names should be restricted -> do it!
        */
        //* Site shell not be empty
        if($this->dataRecord['parent_domain_id'] == 0) $app->tform->errorMessage .= $app->tform->lng("database_site_error_empty").'<br />';
        
        //* Get the database name and database user prefix
        $app->uses('getconf');
@@ -290,6 +244,9 @@
    function onBeforeInsert() {
        global $app, $conf, $interfaceConf;
        
        //* Site shell not be empty
        if($this->dataRecord['parent_domain_id'] == 0) $app->tform->errorMessage .= $app->tform->lng("database_site_error_empty").'<br />';
        //* Database username and database name shall not be empty
        if($this->dataRecord['database_name'] == '') $app->tform->errorMessage .= $app->tform->wordbook["database_name_error_empty"].'<br />';
        if($this->dataRecord['database_user'] == '') $app->tform->errorMessage .= $app->tform->wordbook["database_user_error_empty"].'<br />';
@@ -330,31 +287,33 @@
    function onAfterInsert() {
        global $app, $conf;
        // make sure that the record belongs to the clinet group and not the admin group when a dmin inserts it
        // also make sure that the user can not delete domain created by a admin
        if($_SESSION["s"]["user"]["typ"] == 'admin' && isset($this->dataRecord["client_group_id"])) {
            $client_group_id = intval($this->dataRecord["client_group_id"]);
            $app->db->query("UPDATE web_database SET sys_groupid = $client_group_id, sys_perm_group = 'ru' WHERE database_id = ".$this->id);
        }
        if($app->auth->has_clients($_SESSION['s']['user']['userid']) && isset($this->dataRecord["client_group_id"])) {
            $client_group_id = intval($this->dataRecord["client_group_id"]);
            $app->db->query("UPDATE web_database SET sys_groupid = $client_group_id, sys_perm_group = 'riud' WHERE database_id = ".$this->id);
        if($this->dataRecord["parent_domain_id"] > 0) {
            $web = $app->db->queryOneRecord("SELECT * FROM web_domain WHERE domain_id = ".intval($this->dataRecord["parent_domain_id"]));
            //* The Database user shall be owned by the same group then the website
            $sys_groupid = $web['sys_groupid'];
            $backup_interval = $web['backup_interval'];
            $backup_copies = $web['backup_copies'];
            $sql = "UPDATE web_database SET sys_groupid = '$sys_groupid', backup_interval = '$backup_interval', backup_copies = '$backup_copies' WHERE database_id = ".$this->id;
            $app->db->query($sql);
        }
    }
    function onAfterUpdate() {
        global $app, $conf;
        // make sure that the record belongs to the client group and not the admin group when a admin inserts it
        // also make sure that the user can not delete domain created by a admin
        if($_SESSION["s"]["user"]["typ"] == 'admin' && isset($this->dataRecord["client_group_id"])) {
            $client_group_id = intval($this->dataRecord["client_group_id"]);
            $app->db->query("UPDATE web_database SET sys_groupid = $client_group_id, sys_perm_group = 'ru' WHERE database_id = ".$this->id);
        }
        if($app->auth->has_clients($_SESSION['s']['user']['userid']) && isset($this->dataRecord["client_group_id"])) {
            $client_group_id = intval($this->dataRecord["client_group_id"]);
            $app->db->query("UPDATE web_database SET sys_groupid = $client_group_id, sys_perm_group = 'riud' WHERE database_id = ".$this->id);
        if($this->dataRecord["parent_domain_id"] > 0) {
            $web = $app->db->queryOneRecord("SELECT * FROM web_domain WHERE domain_id = ".intval($this->dataRecord["parent_domain_id"]));
            //* The Database user shall be owned by the same group then the website
            $sys_groupid = $web['sys_groupid'];
            $backup_interval = $web['backup_interval'];
            $backup_copies = $web['backup_copies'];
            $sql = "UPDATE web_database SET sys_groupid = '$sys_groupid', backup_interval = '$backup_interval', backup_copies = '$backup_copies' WHERE database_id = ".$this->id;
            $app->db->query($sql);
        }
    }
interface/web/sites/form/database.tform.php
@@ -69,6 +69,17 @@
                                     ),
            'value'        => ''
        ),
        'parent_domain_id' => array (
            'datatype'    => 'INTEGER',
            'formtype'    => 'SELECT',
            'default'    => '',
            'datasource'    => array (     'type'    => 'SQL',
                                        'querystring' => "SELECT domain_id,domain FROM web_domain WHERE type = 'vhost' AND {AUTHSQL} ORDER BY domain",
                                        'keyfield'=> 'domain_id',
                                        'valuefield'=> 'domain'
                                     ),
            'value'        => array('0' => $app->tform->lng('select_site_txt'))
        ),
        'type' => array (
            'datatype'    => 'VARCHAR',
            'formtype'    => 'SELECT',
interface/web/sites/form/web_domain.tform.php
@@ -431,7 +431,7 @@
    )
);
if($_SESSION["s"]["user"]["typ"] == 'admin') {
// if($_SESSION["s"]["user"]["typ"] == 'admin') {
//* Backup
$form["tabs"]['backup'] = array (
@@ -458,10 +458,17 @@
    ##################################
    # ENDE Datatable fields
    ##################################
    ),
    'plugins' => array (
         'backup_records' => array (
             'class'   => 'plugin_backuplist',
             'options' => array(
            )
        )
    )
);
}
// }
if($_SESSION["s"]["user"]["typ"] == 'admin') {
interface/web/sites/lib/lang/en_database.lng
@@ -22,4 +22,9 @@
$wb["database_charset_change_txt"] = 'The database charset can not be changed';
$wb["database_name_error_len"] = 'Database name - {db} - too long. The max. database name length incl. prefix is 64 chars.';
$wb["database_user_error_len"] = 'Database username - {user}- too long. The max. database username length incl. prefix is 16 chars.';
$wb["parent_domain_id_txt"] = 'Site';
$wb["database_site_error_empty"] = 'Select the site to which the database belongs.';
$wb["select_site_txt"] = '- Select Site -';
$wb["btn_save_txt"] = 'Save';
$wb["btn_cancel_txt"] = 'Cancel';
?>
interface/web/sites/lib/lang/en_web_backup_list.lng
New file
@@ -0,0 +1,16 @@
<?php
$wb['list_head_txt'] = 'Existing backups';
$wb['date_txt'] = 'Date';
$wb['backup_type_txt'] = 'Type';
$wb['filename_txt'] = 'Backup file';
$wb['restore_backup_txt'] = 'Restore backup';
$wb['download_backup_txt'] = 'Download backup';
$wb['download_info_txt'] = 'The backup file will be available for download in the backup folder of the website in a few minutes.';
$wb['restore_info_txt'] = 'Restore of the backup has been started. This action takes several minutes to be completed.';
$wb['restore_confirm_txt'] = 'Restoring will overwrite existing files in your website. Do you really want to restore this backup?';
$wb['download_pending_txt'] = 'There is already a pending backup download job.';
$wb['restore_pending_txt'] = 'There is already a pending backup restore job.';
$wb['backup_type_mysql'] = 'MySQL Database';
$wb['backup_type_web'] = 'Website files';
?>
interface/web/sites/templates/database_edit.htm
@@ -20,21 +20,13 @@
        </select>
        </tmpl_if>
      </div>
      <div class="ctrlHolder">
          <label for="client_group_id">{tmpl_var name='client_txt'}</label>
        <select name="client_group_id" id="client_group_id" class="selectInput">
                    {tmpl_var name='client_group_id'}
        </select>
      </div>
      </tmpl_if>
      <tmpl_if name="is_reseller">
      <div class="ctrlHolder">
          <label for="client_group_id">{tmpl_var name='client_txt'}</label>
        <select name="client_group_id" id="client_group_id" class="selectInput">
                    {tmpl_var name='client_group_id'}
      <div class="ctrlHolder">
        <label for="parent_domain_id">{tmpl_var name='parent_domain_id_txt'}</label>
        <select name="parent_domain_id" id="parent_domain_id" class="selectInput">
                    {tmpl_var name='parent_domain_id'}
        </select>
      </div>
      </tmpl_if>
      </div>
      <div class="ctrlHolder">
          <label for="type">{tmpl_var name='type_txt'}</label>
        <select name="type" id="type" class="selectInput formLengthHalf">
interface/web/sites/templates/web_backup_list.htm
New file
@@ -0,0 +1,40 @@
<tmpl_if name="msg">
    <div id="OKMsg"><p><tmpl_var name="msg"></p></div>
</tmpl_if>
<tmpl_if name="error">
    <div id="errorMsg"><h3>ERROR</h3><ol><tmpl_var name="error"></ol></div>
</tmpl_if>
<h3><tmpl_var name="list_head_txt"></h3>
  <div class="pnl_listarea">
    <fieldset><legend><tmpl_var name="list_head_txt"></legend>
      <table class="list">
        <thead>
          <tr>
            <th class="tbl_col_date" scope="col"><tmpl_var name="date_txt"></th>
            <th class="tbl_col_date" scope="col"><tmpl_var name="backup_type_txt"></th>
            <th class="tbl_col_filename" scope="col"><tmpl_var name="filename_txt"></th>
            <th class="tbl_col_buttons" scope="col">&nbsp;</th>
          </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_date">{tmpl_var name="date"}</td>
            <td class="tbl_col_date">{tmpl_var name="backup_type"}</td>
            <td class="tbl_col_filename">{tmpl_var name="filename"}</td>
            <td class="tbl_col_buttons" style="width:300px;">
              <div class="buttons" >
                <button class="iconstxt icoRestore" type="button" onClick="confirm_action('sites/web_domain_edit.php?id={tmpl_var name='parent_id'}&next_tab=backup&backup_action=restore&backup_id={tmpl_var name='backup_id'}','{tmpl_var name='restore_confirm_txt'}');">
                    <span>{tmpl_var name="restore_backup_txt"}</span>
                </button>
                <button class="iconstxt icoDownload" type="button" onClick="loadContent('sites/web_domain_edit.php?id={tmpl_var name='parent_id'}&next_tab=backup&backup_action=download&backup_id={tmpl_var name='backup_id'}');">
                    <span>{tmpl_var name="download_backup_txt"}</span>
                </button>
              </div>
            </td>
          </tr>
          </tmpl_loop>
        </tbody>
      </table>
    </fieldset>
  </div>
interface/web/sites/templates/web_domain_backup.htm
@@ -18,6 +18,8 @@
                </select>
      </div>
    </fieldset>
    {tmpl_var name='backup_records'}
    <input type="hidden" name="id" value="{tmpl_var name='id'}">
interface/web/sites/tools.inc.php
@@ -96,7 +96,7 @@
          }
    }
    /* get the name of the client */
    $tmp = $app->db->queryOneRecord("SELECT client_id FROM sys_group WHERE groupid = " . $client_group_id);
    $tmp = $app->db->queryOneRecord("SELECT client_id FROM sys_group WHERE groupid = " . intval($client_group_id));
    $clientID = $tmp['client_id'];
    if ($clientID == '') $clientID = '0';
    return $clientID;
interface/web/sites/web_domain_del.php
@@ -91,6 +91,12 @@
            $app->db->datalogDelete('webdav_user','webdav_user_id',$rec['webdav_user_id']);
        }
        
        //* Delete all records that belog to this web
        $records = $app->db->queryAllRecords("SELECT backup_id FROM web_backup WHERE parent_domain_id = '".intval($this->id)."'");
        foreach($records as $rec) {
            $app->db->datalogDelete('web_backup','backup_id',$rec['backup_id']);
        }
        //* Delete all web folders
        $records = $app->db->queryAllRecords("SELECT web_folder_id FROM web_folder WHERE parent_domain_id = '".intval($this->id)."'");
        foreach($records as $rec) {
interface/web/sites/web_domain_edit.php
@@ -607,6 +607,14 @@
            }
            unset($records);
            unset($rec);
            //* Update all databases
            $records = $app->db->queryAllRecords("SELECT database_id FROM web_database WHERE parent_domain_id = ".$this->id);
            foreach($records as $rec) {
                $app->db->datalogUpdate('web_database', "sys_userid = '".$web_rec['sys_userid']."', sys_groupid = '".$web_rec['sys_groupid']."'", 'database_id', $rec['database_id']);
            }
            unset($records);
            unset($rec);
        }
@@ -638,6 +646,21 @@
            $sql = "UPDATE web_domain SET php_open_basedir = '$php_open_basedir' WHERE domain_id = ".$this->id;
            $app->db->query($sql);
        }
        //* Change database backup options when web backup options have been changed
        if(isset($this->dataRecord['backup_interval']) && ($this->dataRecord['backup_interval'] != $this->oldDataRecord['backup_interval'] || $this->dataRecord['backup_copies'] != $this->oldDataRecord['backup_copies'])) {
            //* Update all databases
            $backup_interval = $this->dataRecord['backup_interval'];
            $backup_copies = $this->dataRecord['backup_copies'];
            $records = $app->db->queryAllRecords("SELECT database_id FROM web_database WHERE parent_domain_id = ".$this->id);
            foreach($records as $rec) {
                $app->db->datalogUpdate('web_database', "backup_interval = '$backup_interval', backup_copies = '$backup_copies'", 'database_id', $rec['database_id']);
            }
            unset($records);
            unset($rec);
            unset($backup_copies);
            unset($backup_interval);
        }
    }
interface/web/themes/default/css/screen/content_ispc.css
@@ -366,6 +366,8 @@
    .iconstxt.icoAdd { background-image: url(../../icons/x16/plus_circle_frame.png); }
    .iconstxt.icoKey { background-image: url("../../icons/x16/key.png"); }
    .iconstxt.icoDelete { background-image: url("../../icons/x16/minus_circle_frame.png"); }
    .iconstxt.icoDownload { background-image: url("../../icons/x16/arrow_270.png"); }
    .iconstxt.icoRestore { background-image: url("../../icons/x16/arrow_circle_225.png"); }
    
    /* Button with icon and without text */
    .icons16 span { display: none; }
server/cron_daily.php
@@ -1,7 +1,7 @@
<?php
/*
Copyright (c) 2007, Till Brehm, projektfarm Gmbh
Copyright (c) 2007-2012, Till Brehm, projektfarm Gmbh
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
@@ -480,6 +480,11 @@
$server_config = $app->getconf->get_server_config($conf['server_id'], 'server');
$backup_dir = $server_config['backup_dir'];
$backup_mode = $server_config['backup_mode'];
if($backup_mode == '') $backup_mode = 'userzip';
$web_config = $app->getconf->get_server_config($conf['server_id'], 'web');
$http_server_user = $web_config['user'];
if($backup_dir != '') {
    
@@ -495,12 +500,12 @@
        chmod(escapeshellcmd($backup_dir), $backup_dir_permissions);
    }
    
    $sql = "SELECT * FROM web_domain WHERE type = 'vhost'";
    $sql = "SELECT * FROM web_domain WHERE server_id = '".$conf['server_id']."' AND type = 'vhost' AND backup_interval != 'none'";
    $records = $app->db->queryAllRecords($sql);
    if(is_array($records)) {
        foreach($records as $rec) {
            
            // Create a backup
            //* Do the website backup
            if($rec['backup_interval'] == 'daily' or ($rec['backup_interval'] == 'weekly' && date('w') == 0) or ($rec['backup_interval'] == 'monthly' && date('d') == '01')) {
                
                $web_path = $rec['document_root'];
@@ -510,46 +515,71 @@
                $web_backup_dir = $backup_dir.'/web'.$web_id;
                if(!is_dir($web_backup_dir)) mkdir($web_backup_dir, 0750);
                chmod($web_backup_dir, 0750); 
                if(isset($server_config['backup_dir_ftpread']) && $server_config['backup_dir_ftpread'] == 'y') {
                    chown($web_backup_dir, $rec['system_user']);
                    chgrp($web_backup_dir, $rec['system_group']);
                } else {
                //if(isset($server_config['backup_dir_ftpread']) && $server_config['backup_dir_ftpread'] == 'y') {
                chown($web_backup_dir, $rec['system_user']);
                chgrp($web_backup_dir, $rec['system_group']);
                /*} else {
                    chown($web_backup_dir, 'root');
                    chgrp($web_backup_dir, 'root');
                }*/
                if($backup_mode == 'userzip') {
                    //* Create a .zip backup as web user and include also files owned by apache / nginx user
                    $web_backup_file = 'web'.$web_id.'_'.date('Y-m-d_H-i').'.zip';
                    exec('cd '.escapeshellarg($web_path).' && sudo -u '.escapeshellarg($web_user).' find . -group '.escapeshellarg($web_group).' -print 2> /dev/null | zip --exclude=backup\* --symlinks '.escapeshellarg($web_backup_dir.'/'.$web_backup_file).' -@');
                    exec('cd '.escapeshellarg($web_path).' && sudo -u '.escapeshellarg($web_user).' find . -user '.escapeshellarg($http_server_user).' -print 2> /dev/null | zip --exclude=backup\* --update --symlinks '.escapeshellarg($web_backup_dir.'/'.$web_backup_file).' -@');
                } else {
                    //* Create a tar.gz backup as root user
                    $web_backup_file = 'web'.$web_id.'_'.date('Y-m-d_H-i').'.tar.gz';
                    exec('tar pczf '.escapeshellarg($web_backup_dir.'/'.$web_backup_file).' --exclude=backup\* --directory '.escapeshellarg($web_path).' .');
                }
                exec('cd '.escapeshellarg($web_path).' && sudo -u '.escapeshellarg($web_user).' find . -group '.escapeshellarg($web_group).' -print | zip -y '.escapeshellarg($web_backup_dir.'/web.zip').' -@');
                chown($web_backup_dir.'/web.zip', $rec['system_user']);
                chgrp($web_backup_dir.'/web.zip', $rec['system_group']);
                chmod($web_backup_dir.'/web.zip', 0750);
                chown($web_backup_dir.'/'.$web_backup_file, 'root');
                chgrp($web_backup_dir.'/'.$web_backup_file, 'root');
                chmod($web_backup_dir.'/'.$web_backup_file, 0750);
                
                // Rename or remove old backups
                //* Insert web backup record in database
                $insert_data = "(server_id,parent_domain_id,backup_type,backup_mode,tstamp,filename) VALUES (".$conf['server_id'].",".$web_id.",'web','".$backup_mode."',".time().",'".$app->db->quote($web_backup_file)."')";
                $app->dbmaster->datalogInsert('web_backup', $insert_data, 'backup_id');
                //* Remove old backups
                $backup_copies = intval($rec['backup_copies']);
                
                //* delete any older backup copies that previously existed
                for ($n = $backup_copies; $n <= 10; $n++) {
                    if(is_file($web_backup_dir.'/web.'.$n.'.zip')) unlink($web_backup_dir.'/web.'.$n.'.zip');
                }
                for($n = $backup_copies - 1; $n >= 1; $n--) {
                    if(is_file($web_backup_dir.'/web.'.$n.'.zip')) {
                        rename($web_backup_dir.'/web.'.$n.'.zip',$web_backup_dir.'/web.'.($n+1).'.zip');
                $dir_handle = dir($web_backup_dir);
                $files = array();
                while (false !== ($entry = $dir_handle->read())) {
                    if($entry != '.' && $entry != '..' && substr($entry,0,3) == 'web' && is_file($web_backup_dir.'/'.$entry)) {
                        $files[] = $entry;
                    }
                }
                if(is_file($web_backup_dir.'/web.zip')) rename($web_backup_dir.'/web.zip',$web_backup_dir.'/web.1.zip');
                // Create backupdir symlink
                if(is_link($web_path.'/backup')) unlink($web_path.'/backup');
                symlink($web_backup_dir,$web_path.'/backup');
                // chmod($web_path.'/backup', 0755);
                chown($web_path.'/backup', $rec['system_user']);
                chgrp($web_path.'/backup', $rec['system_group']);
                $dir_handle->close();
                rsort($files);
                for ($n = $backup_copies; $n <= 10; $n++) {
                    if(isset($files[$n]) && is_file($web_backup_dir.'/'.$files[$n])) {
                        unlink($web_backup_dir.'/'.$files[$n]);
                        $sql = "SELECT backup_id FROM web_backup WHERE server_id = ".$conf['server_id']." AND parent_domain_id = $web_id AND filename = '".$app->db->quote($files[$n])."'";
                        $tmp = $app->dbmaster->queryOneRecord($sql);
                        $app->dbmaster->datalogDelete('web_backup', 'backup_id', $tmp['backup_id']);
                    }
                }
                unset($files);
                unset($dir_handle);
                //* Remove backupdir symlink and create as directory instead
                if(is_link($web_path.'/backup')) {
                    unlink($web_path.'/backup');
                }
                if(!is_dir($web_path.'/backup')) {
                    mkdir($web_path.'/backup');
                    chown($web_path.'/backup', $rec['system_user']);
                    chgrp($web_path.'/backup', $rec['system_group']);
                }
                
            }
            
            /* If backup_interval is set to none and we have a 
            backup directory for the website, then remove the backups */
            if($rec['backup_interval'] == 'none') {
                $web_id = $rec['domain_id'];
                $web_user = $rec['system_user'];
@@ -560,6 +590,78 @@
            }
        }
    }
    $sql = "SELECT * FROM web_database WHERE server_id = '".$conf['server_id']."' AND backup_interval != 'none'";
    $records = $app->db->queryAllRecords($sql);
    if(is_array($records)) {
        include('lib/mysql_clientdb.conf');
        foreach($records as $rec) {
            //* Do the database backup
            if($rec['backup_interval'] == 'daily' or ($rec['backup_interval'] == 'weekly' && date('w') == 0) or ($rec['backup_interval'] == 'monthly' && date('d') == '01')) {
                $web_id = $rec['parent_domain_id'];
                $db_backup_dir = $backup_dir.'/web'.$web_id;
                if(!is_dir($web_backup_dir)) mkdir($web_backup_dir, 0750);
                chmod($web_backup_dir, 0750);
                chown($web_backup_dir, 'root');
                chgrp($web_backup_dir, 'root');
                //* Do the mysql database backup with mysqldump
                $db_id = $rec['database_id'];
                $db_name = $rec['database_name'];
                $db_backup_file = 'db_'.$db_name.'_'.date('Y-m-d_H-i').'.sql';
                $command = "mysqldump -h '".escapeshellcmd($clientdb_host)."' -u '".escapeshellcmd($clientdb_user)."' -p'".escapeshellcmd($clientdb_password)."' -c --add-drop-table --create-options --quick --result-file='".$db_backup_dir.'/'.$db_backup_file."' '".$db_name."'";
                exec($command);
                //* Compress the backup with gzip
                exec("gzip -c '".escapeshellcmd($db_backup_dir.'/'.$db_backup_file)."' > '".escapeshellcmd($db_backup_dir.'/'.$db_backup_file).".gz'");
                chmod($db_backup_dir.'/'.$db_backup_file.'.gz', 0750);
                chown($db_backup_dir.'/'.$db_backup_file.'.gz', fileowner($db_backup_dir));
                chgrp($db_backup_dir.'/'.$db_backup_file.'.gz', filegroup($db_backup_dir));
                //* Insert web backup record in database
                $insert_data = "(server_id,parent_domain_id,backup_type,backup_mode,tstamp,filename) VALUES (".$conf['server_id'].",$web_id,'mysql','sqlgz',".time().",'".$app->db->quote($db_backup_file).".gz')";
                $app->dbmaster->datalogInsert('web_backup', $insert_data, 'backup_id');
                //* Remove the uncompressed file
                unlink($db_backup_dir.'/'.$db_backup_file);
                //* Remove old backups
                $backup_copies = intval($rec['backup_copies']);
                $dir_handle = dir($db_backup_dir);
                $files = array();
                while (false !== ($entry = $dir_handle->read())) {
                    if($entry != '.' && $entry != '..' && substr($entry,0,2) == 'db' && is_file($db_backup_dir.'/'.$entry)) {
                        $files[] = $entry;
                    }
                }
                $dir_handle->close();
                rsort($files);
                for ($n = $backup_copies; $n <= 10; $n++) {
                    if(isset($files[$n]) && is_file($db_backup_dir.'/'.$files[$n])) {
                        unlink($db_backup_dir.'/'.$files[$n]);
                        $sql = "SELECT backup_id FROM web_backup WHERE server_id = ".$conf['server_id']." AND parent_domain_id = $web_id AND filename = '".$app->db->quote($files[$n])."'";
                        $tmp = $app->dbmaster->queryOneRecord($sql);
                        $app->dbmaster->datalogDelete('web_backup', 'backup_id', $tmp['backup_id']);
                    }
                }
                unset($files);
                unset($dir_handle);
            }
        }
        unset($clientdb_host);
        unset($clientdb_user);
        unset($clientdb_password);
    }
}
server/lib/classes/db_mysql.inc.php
@@ -1,4 +1,3 @@
<?php
/*
   Copyright (c) 2005, Till Brehm, projektfarm Gmbh
@@ -226,10 +225,10 @@
    if(is_array($record_old) && count($record_old) > 0) {
      foreach($record_old as $key => $val) {
    // if(!isset($record_new[$key]) || $record_new[$key] != $val) {
    if($record_new[$key] != $val) {
    if(@$record_new[$key] != $val) {
      // Record has changed
      $diffrec_full['old'][$key] = $val;
      $diffrec_full['new'][$key] = $record_new[$key];
      $diffrec_full['new'][$key] = @$record_new[$key];
      $diff_num++;
    } else {
      $diffrec_full['old'][$key] = $val;
@@ -593,4 +592,4 @@
          }
          ?>
          ?>
server/lib/classes/modules.inc.php
@@ -229,8 +229,46 @@
                $app->log('Processed datalog_id '.$d['datalog_id'],LOGLEVEL_DEBUG);
            }
        }
    }
    function processActions() {
        global $app,$conf;
        
        //* get the server_id of the local server
        $server_id = intval($conf["server_id"]);
        
        include_once (SCRIPT_PATH."/lib/remote_action.inc.php");
        //* SQL query to get all pending actions
        $sql = "SELECT action_id, action_type, action_param " .
                "FROM sys_remoteaction " .
                "WHERE server_id = " . $server_id . " ".
                " AND  action_id > " . intval($maxid_remote_action) . " ".
                "ORDER BY action_id";
        $actions = $app->dbmaster->queryAllRecords($sql);
        if(is_array($actions)) {
            foreach($actions as $action) {
                //* Raise the action
                $state = $app->plugins->raiseAction($action['action_type'],$action['action_param']);
                //* Update the action state
                $sql = "UPDATE sys_remoteaction " .
                        "SET action_state = '" . $app->dbmaster->quote($state) . "' " .
                        "WHERE action_id = " . intval($action['action_id']);
                $app->dbmaster->query($sql);
                /*
                * Then save the maxid for the next time...
                */
                $fp = fopen(ISPC_LIB_PATH."/remote_action.inc.php", 'wb');
                $content = '<?php' . "\n" . '$maxid_remote_action = ' . $action['action_id'] . ';' . "\n?>";
                fwrite($fp, $content);
                fclose($fp);
            }
        }
        
        
        
server/lib/classes/plugins.inc.php
@@ -1,7 +1,7 @@
<?php
/*
Copyright (c) 2007, Till Brehm, projektfarm Gmbh
Copyright (c) 2007-2012, Till Brehm, projektfarm Gmbh
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
@@ -32,6 +32,7 @@
    
    var $available_events = array();
    var $subscribed_events = array();
    var $subscribed_actions = array();
    var $debug = false;
    
    /*
@@ -126,6 +127,45 @@
        unset($events);
    }
    
    /*
     This function is called by the plugin to register for an action
    */
    function registerAction($action_name,$plugin_name,$function_name) {
        global $app;
        $this->subscribed_actions[$action_name][] = array('plugin' => $plugin_name, 'function' => $function_name);
        if($this->debug)  $app->log("Registered function '$function_name' from plugin '$plugin_name' for action '$event_name'.",LOGLEVEL_DEBUG);
    }
    function raiseAction($action_name,$data) {
        global $app;
        //* Get the subscriptions for this action
        $actions = (isset($this->subscribed_actions[$action_name]))?$this->subscribed_actions[$action_name]:'';
        if($this->debug) $app->log('Raised action: '.$action_name,LOGLEVEL_DEBUG);
        if(is_array($actions)) {
            foreach($actions as $action) {
                $plugin_name = $action['plugin'];
                $function_name = $action['function'];
                $state_out = 'ok';
                //* Call the processing function of the plugin
                $app->log("Calling function '$function_name' from plugin '$plugin_name' raised by action '$action_name'.",LOGLEVEL_DEBUG);
                $state = call_user_func(array($app->loaded_plugins[$plugin_name],$function_name),$action_name,$data);
                //* ensure that we return the highest warning / error level if a error occured in one of the functions
                if($state == 'warning' && $state_out != 'error') $state_out = 'warning';
                if($state == 'error') $state_out = 'error';
                unset($plugin_name);
                unset($function_name);
            }
        }
        unset($action);
        unset($actions);
        return $state_out;
    }
}
?>
server/mods-available/remoteaction_core_module.inc.php
@@ -1,6 +1,6 @@
<?php
/*
Copyright (c) 2007-2010, Till Brehm, projektfarm Gmbh and Oliver Vogel www.muv.com
Copyright (c) 2007-2010, Till Brehm, projektfarm Gmbh, Oliver Vogel www.muv.com
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
@@ -35,7 +35,7 @@
    //* This function is called during ispconfig installation to determine
    //  if a symlink shall be created for this plugin.
    function onInstall() {
        return true;
        return false;
    }
    /*
@@ -45,7 +45,8 @@
        /*
            * Check for actions to execute
        */
        $this->_execActions();
        //* This module has been replaced by the new action framework.
        // $this->_execActions();
    }
    /*
server/mods-available/web_module.inc.php
@@ -49,7 +49,10 @@
                                    'web_folder_delete',
                                    'web_folder_user_insert',
                                    'web_folder_user_update',
                                    'web_folder_user_delete');
                                    'web_folder_user_delete',
                                    'web_backup_insert',
                                    'web_backup_update',
                                    'web_backup_delete');
    
    //* This function is called during ispconfig installation to determine
    //  if a symlink shall be created for this plugin.
@@ -94,6 +97,7 @@
        $app->modules->registerTableHook('webdav_user','web_module','process');
        $app->modules->registerTableHook('web_folder','web_module','process');
        $app->modules->registerTableHook('web_folder_user','web_module','process');
        $app->modules->registerTableHook('web_backup','web_module','process');
        
        // Register service
        $app->services->registerService('httpd','web_module','restartHttpd');
@@ -139,6 +143,11 @@
                if($action == 'u') $app->plugins->raiseEvent('web_folder_user_update',$data);
                if($action == 'd') $app->plugins->raiseEvent('web_folder_user_delete',$data);
            break;
            case 'web_backup':
                if($action == 'i') $app->plugins->raiseEvent('web_backup_insert',$data);
                if($action == 'u') $app->plugins->raiseEvent('web_backup_update',$data);
                if($action == 'd') $app->plugins->raiseEvent('web_backup_delete',$data);
            break;
        } // end switch
    } // end function
    
server/plugins-available/backup_plugin.inc.php
New file
@@ -0,0 +1,134 @@
<?php
/*
Copyright (c) 2012, Till Brehm, ISPConfig UG
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.
*/
class backup_plugin {
    var $plugin_name = 'backup_plugin';
    var $class_name  = 'backup_plugin';
    //* This function is called during ispconfig installation to determine
    //  if a symlink shall be created for this plugin.
    public function onInstall() {
        global $conf;
        return true;
    }
    /*
         This function is called when the plugin is loaded
    */
    public function onLoad() {
        global $app;
        //* Register for actions
        $app->plugins->registerAction('backup_download',$this->plugin_name,'backup_action');
        $app->plugins->registerAction('backup_restore',$this->plugin_name,'backup_action');
    }
    //* Do a backup action
    public function backup_action($action_name,$data) {
        global $app,$conf;
        $backup_id = intval($data);
        $backup = $app->db->queryOneRecord("SELECT * FROM web_backup WHERE backup_id = $backup_id");
        if(is_array($backup)) {
            $app->uses('ini_parser,file,getconf');
            $web = $app->db->queryOneRecord("SELECT * FROM web_domain WHERE domain_id = ".$backup['parent_domain_id']);
            $server_config = $app->getconf->get_server_config($conf['server_id'], 'server');
            $backup_dir = $server_config['backup_dir'].'/web'.$web['domain_id'];
            //* Make backup available for download
            if($action_name == 'backup_download') {
                //* Copy the backup file to the backup folder of the website
                if(file_exists($backup_dir.'/'.$backup['filename']) && !stristr($backup_dir.'/'.$backup['filename'],'..') && !stristr($backup_dir.'/'.$backup['filename'],'etc')) {
                    copy($backup_dir.'/'.$backup['filename'],$web['document_root'].'/backup/'.$backup['filename']);
                    chgrp($web['document_root'].'/backup/'.$backup['filename'],$web['system_group']);
                    $app->log('cp '.$backup_dir.'/'.$backup['filename'].' '.$web['document_root'].'/backup/'.$backup['filename'],LOGLEVEL_DEBUG);
                }
            }
            //* Restore a mysql backup
            if($action_name == 'backup_restore' && $backup['backup_type'] == 'mysql') {
                //* Load sql dump into db
                include('lib/mysql_clientdb.conf');
                if(file_exists($backup_dir.'/'.$backup['filename'])) {
                    $parts = explode('_',$backup['filename']);
                    $db_name = $parts[1];
                    $command = "gunzip --stdout ".escapeshellarg($backup_dir.'/'.$backup['filename'])." | mysql -h '".escapeshellcmd($clientdb_host)."' -u '".escapeshellcmd($clientdb_user)."' -p'".escapeshellcmd($clientdb_password)."' '".$db_name."'";
                    exec($command);
                }
                unset($clientdb_host);
                unset($clientdb_user);
                unset($clientdb_password);
                $app->log('Restored MySQL backup '.$backup_dir.'/'.$backup['filename'],LOGLEVEL_DEBUG);
            }
            //* Restore a web backup
            if($action_name == 'backup_restore' && $backup['backup_type'] == 'web') {
                if($backup['backup_mode'] == 'userzip') {
                    if(file_exists($backup_dir.'/'.$backup['filename']) && $web['document_root'] != '' && $web['document_root'] != '/' && !stristr($backup_dir.'/'.$backup['filename'],'..') && !stristr($backup_dir.'/'.$backup['filename'],'etc')) {
                        if(file_exists($web['document_root'].'/backup/'.$backup['filename'])) rename($web['document_root'].'/backup/'.$backup['filename'],$web['document_root'].'/backup/'.$backup['filename'].'.bak');
                        copy($backup_dir.'/'.$backup['filename'],$web['document_root'].'/backup/'.$backup['filename']);
                        chgrp($web['document_root'].'/backup/'.$backup['filename'],$web['system_group']);
                        //chown($web['document_root'].'/backup/'.$backup['filename'],$web['system_user']);
                        $command = 'sudo -u '.escapeshellarg($web['system_user']).' unzip -qq -o  '.escapeshellarg($web['document_root'].'/backup/'.$backup['filename']).' -d '.escapeshellarg($web['document_root']).' 2> /dev/null';
                        exec($command);
                        unlink($web['document_root'].'/backup/'.$backup['filename']);
                        if(file_exists($web['document_root'].'/backup/'.$backup['filename'].'.bak')) rename($web['document_root'].'/backup/'.$backup['filename'].'.bak',$web['document_root'].'/backup/'.$backup['filename']);
                        $app->log('Restored Web backup '.$backup_dir.'/'.$backup['filename'],LOGLEVEL_DEBUG);
                    }
                }
                if($backup['backup_mode'] == 'rootgz') {
                    if(file_exists($backup_dir.'/'.$backup['filename']) && $web['document_root'] != '' && $web['document_root'] != '/' && !stristr($backup_dir.'/'.$backup['filename'],'..') && !stristr($backup_dir.'/'.$backup['filename'],'etc')) {
                        $command = 'tar xzf '.escapeshellarg($backup_dir.'/'.$backup['filename']).' --directory '.escapeshellarg($web['document_root']);
                        exec($command);
                        $app->log('Restored Web backup '.$backup_dir.'/'.$backup['filename'],LOGLEVEL_DEBUG);
                    }
                }
            }
        } else {
            $app->log('No backup with ID '.$backup_id.' found.',LOGLEVEL_DEBUG);
        }
        return 'ok';
    }
} // end class
?>
server/plugins-available/mysql_clientdb_plugin.inc.php
@@ -100,7 +100,7 @@
          if($valid == false) continue;
          
          if($action == 'GRANT') {
              if(!$link->query("GRANT ALL ON ".$link->escape_string($database_name,$link).".* TO '".$link->escape_string($database_user,$link)."'@'$db_host' IDENTIFIED BY PASSWORD '".$link->escape_string($database_password,$link)."';",$link)) $success = false;
              if(!$link->query("GRANT ALL ON ".$link->escape_string($database_name).".* TO '".$link->escape_string($database_user)."'@'$db_host' IDENTIFIED BY PASSWORD '".$link->escape_string($database_password)."';")) $success = false;
          } elseif($action == 'REVOKE') {
              //mysql_query("REVOKE ALL PRIVILEGES ON ".mysql_real_escape_string($database_name,$link).".* FROM '".mysql_real_escape_string($database_user,$link)."';",$link);
          } elseif($action == 'DROP') {
@@ -108,7 +108,7 @@
          } elseif($action == 'RENAME') {
              if(!$link->query("RENAME USER '".$link->escape_string($database_user)."'@'$db_host' TO '".$link->escape_string($database_rename_user)."'@'$db_host'")) $success = false;
          } elseif($action == 'PASSWORD') {
              if(!$link->query("SET PASSWORD FOR '".$link->escape_string($database_user,$link)."'@'$db_host' = '".$link->escape_string($database_password,$link)."';",$link)) $success = false;
              if(!$link->query("SET PASSWORD FOR '".$link->escape_string($database_user)."'@'$db_host' = '".$link->escape_string($database_password)."';")) $success = false;
          }
      }
      
@@ -158,7 +158,7 @@
                }
                
                $db_host = 'localhost';
                $link->query("GRANT ALL ON `".str_replace(array('_','%'),array('\\_','\\%'),$link->escape_string($data['new']['database_name'],$link))."`.* TO '".$link->escape_string($data['new']['database_user'],$link)."'@'$db_host' IDENTIFIED BY PASSWORD '".$link->escape_string($data['new']['database_password'],$link)."';",$link);
                $link->query("GRANT ALL ON `".str_replace(array('_','%'),array('\\_','\\%'),$link->escape_string($data['new']['database_name']))."`.* TO '".$link->escape_string($data['new']['database_user'])."'@'$db_host' IDENTIFIED BY PASSWORD '".$link->escape_string($data['new']['database_password'])."';");
                
            }
@@ -197,7 +197,7 @@
                }
                
                $db_host = 'localhost';
                $link->query("GRANT ALL ON `".str_replace(array('_','%'),array('\\_','\\%'),$link->escape_string($data['new']['database_name'],$link))."`.* TO '".$link->escape_string($data['new']['database_user'],$link)."'@'$db_host' IDENTIFIED BY PASSWORD '".$link->escape_string($data['new']['database_password'],$link)."';",$link);
                $link->query("GRANT ALL ON `".str_replace(array('_','%'),array('\\_','\\%'),$link->escape_string($data['new']['database_name']))."`.* TO '".$link->escape_string($data['new']['database_user'])."'@'$db_host' IDENTIFIED BY PASSWORD '".$link->escape_string($data['new']['database_password'])."';");
                
                // mysql_query("GRANT ALL ON ".mysql_real_escape_string($data["new"]["database_name"],$link).".* TO '".mysql_real_escape_string($data["new"]["database_user"],$link)."'@'$db_host' IDENTIFIED BY '".mysql_real_escape_string($data["new"]["database_password"],$link)."';",$link);
                //echo "GRANT ALL ON ".mysql_real_escape_string($data["new"]["database_name"]).".* TO '".mysql_real_escape_string($data["new"]["database_user"])."'@'$db_host' IDENTIFIED BY '".mysql_real_escape_string($data["new"]["database_password"])."';";
@@ -247,10 +247,10 @@
            //* Change password
            if($data['new']['database_password'] != $data['old']['database_password']) {
                $db_host = 'localhost';
                $link->query("SET PASSWORD FOR '".$link->escape_string($data['new']['database_user'],$link)."'@'$db_host' = '".$link->escape_string($data['new']['database_password'],$link)."';",$link);
                $link->query("SET PASSWORD FOR '".$link->escape_string($data['new']['database_user'])."'@'$db_host' = '".$link->escape_string($data['new']['database_password'])."';");
                if($data['new']['remote_access'] == 'y') {
                    $this->process_host_list('PASSWORD', '', $data['new']['database_user'], $data['new']['database_password'], $data['new']['remote_ips']);
                    $this->process_host_list('PASSWORD', '', $data['new']['database_user'], $data['new']['database_password'], $data['new']['remote_ips'],$link);
                }
                $app->log('Changing MySQL user password for: '.$data['new']['database_user'],LOGLEVEL_DEBUG);
            }
server/plugins-available/openvz_plugin.inc.php
@@ -1,7 +1,7 @@
<?php
/*
Copyright (c) 2011, Till Brehm, projektfarm Gmbh
Copyright (c) 2011-2012, Till Brehm, projektfarm Gmbh
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
@@ -63,6 +63,14 @@
        $app->plugins->registerEvent('openvz_vm_insert',$this->plugin_name,'vm_insert');
        $app->plugins->registerEvent('openvz_vm_update',$this->plugin_name,'vm_update');
        $app->plugins->registerEvent('openvz_vm_delete',$this->plugin_name,'vm_delete');
        //* Register for actions
        $app->plugins->registerAction('openvz_start_vm',$this->plugin_name,'actions');
        $app->plugins->registerAction('openvz_stop_vm',$this->plugin_name,'actions');
        $app->plugins->registerAction('openvz_restart_vm',$this->plugin_name,'actions');
        $app->plugins->registerAction('openvz_create_ostpl',$this->plugin_name,'actions');
        
    }
    
@@ -149,6 +157,50 @@
            
    }
    
    function actions($action_name,$data) {
        global $app, $conf;
        if ($action_name == 'openvz_start_vm') {
            $veid = intval($data);
            if($veid > 0) {
                exec("vzctl start $veid");
                $app->log("Start VM: vzctl start $veid",LOGLEVEL_DEBUG);
            }
            return 'ok';
        }
        if ($action_name == 'openvz_stop_vm') {
            $veid = intval($data);
            if($veid > 0) {
                exec("vzctl stop $veid");
                $app->log("Stop VM: vzctl stop $veid",LOGLEVEL_DEBUG);
            }
            return 'ok';
        }
        if ($action_name == 'openvz_restart_vm') {
            $veid = intval($data);
            if($veid > 0) {
                exec("vzctl restart $veid");
                $app->log("Restart VM: vzctl restart $veid",LOGLEVEL_DEBUG);
            }
            return 'ok';
        }
        if ($action_name == 'openvz_create_ostpl') {
            $parts = explode(':',$data);
            $veid = intval($parts[0]);
            $template_cache_dir = '/vz/template/cache/';
            $template_name = escapeshellcmd($parts[1]);
            if($veid > 0 && $template_name != '' && is_dir($template_cache_dir)) {
                $command = "vzdump --suspend --compress --stdexcludes --dumpdir $template_cache_dir $veid";
                exec($command);
                exec("mv ".$template_cache_dir."vzdump-openvz-".$veid."*.tgz ".$template_cache_dir.$template_name.".tar.gz");
                exec("rm -f ".$template_cache_dir."vzdump-openvz-".$veid."*.log");
            }
            $app->log("Created OpenVZ OStemplate $template_name from VM $veid",LOGLEVEL_DEBUG);
            return 'ok';
        }
    }
} // end class
server/plugins-available/software_update_plugin.inc.php
@@ -1,7 +1,7 @@
<?php
/*
Copyright (c) 2007, Till Brehm, projektfarm Gmbh
Copyright (c) 2007-2012, Till Brehm, projektfarm Gmbh, Oliver Vogel www.muv.com
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
@@ -35,7 +35,7 @@
    
    //* This function is called during ispconfig installation to determine
    //  if a symlink shall be created for this plugin.
    function onInstall() {
    public function onInstall() {
        global $conf;
        
        return true;
@@ -47,29 +47,31 @@
         This function is called when the plugin is loaded
    */
    
    function onLoad() {
    public function onLoad() {
        global $app;
        
        /*
        Register for the events
        */
        
        //* Mailboxes
        $app->plugins->registerEvent('software_update_inst_insert',$this->plugin_name,'process');
        //$app->plugins->registerEvent('software_update_inst_update',$this->plugin_name,'process');
        //$app->plugins->registerEvent('software_update_inst_delete',$this->plugin_name,'process');
        
        //* Register for actions
        $app->plugins->registerAction('os_update',$this->plugin_name,'os_update');
        
    }
    
    function set_install_status($inst_id, $status) {
    private function set_install_status($inst_id, $status) {
        global $app;
        
        $app->db->query("UPDATE software_update_inst SET status = '{$status}' WHERE software_update_inst_id = '{$inst_id}'");
        $app->dbmaster->query("UPDATE software_update_inst SET status = '{$status}' WHERE software_update_inst_id = '{$inst_id}'");
    }
    
    function process($event_name,$data) {
    public function process($event_name,$data) {
        global $app, $conf;
        
        //* Get the info of the package:
@@ -271,6 +273,26 @@
        }
        
    }
    //* Operating system update
    public function os_update($action_name,$data) {
        global $app;
        //** Debian and compatible Linux distributions
        if(file_exists('/etc/debian_version')) {
            exec("aptitude update");
            exec("aptitude safe-upgrade -y");
            $app->log('Execeuted Debian / Ubuntu update',LOGLEVEL_DEBUG);
        }
        //** Gentoo Linux
        if(file_exists('/etc/gentoo-release')) {
            exec("glsa-check -f --nocolor affected");
            $app->log('Execeuted Gentoo update',LOGLEVEL_DEBUG);
        }
        return 'ok';
    }
} // end class
server/server.php
@@ -144,27 +144,26 @@
    $tmp_num_records = $tmp_rec['number'];
    unset($tmp_rec);
    //** Load required base-classes
    $app->uses('modules,plugins,file,services');
    //** Load the modules that are in the mods-enabled folder
    $app->modules->loadModules('all');
    //** Load the plugins that are in the plugins-enabled folder
    $app->plugins->loadPlugins('all');
    if ($tmp_num_records > 0) {
        /*
          There is something to do, triggert by the database -> do it!
         */
        // Write the Log
        $app->log("Found $tmp_num_records changes, starting update process.", LOGLEVEL_DEBUG);
        // Load required base-classes
        $app->uses('modules,plugins,file,services');
        // Load the modules that are in the mods-enabled folder
        $app->modules->loadModules('all');
        // Load the plugins that are in the plugins-enabled folder
        $app->plugins->loadPlugins('all');
        // Go through the sys_datalog table and call the processing functions
        // from the modules that are hooked on to the table actions
        //** Go through the sys_datalog table and call the processing functions
        //** from the modules that are hooked on to the table actions
        $app->modules->processDatalog();
        // Restart services that need to after configuration
        $app->services->processDelayedActions();
        // All modules are already loaded and processed, so there is NO NEED to load the core once again...
        $needStartCore = false;
    }
    //** Process actions from sys_remoteaction table
    $app->modules->processActions();
    //** Restart services that need to after configuration
    $app->services->processDelayedActions();
    //** All modules are already loaded and processed, so there is NO NEED to load the core once again...
    $needStartCore = false;
} else {
    if ($app->db->connect->connect_error == NULL) {
        $app->log('Unable to connect to local server.' . $app->db->errorMessage, LOGLEVEL_WARN);