Location: PHPKode > projects > ORION-Backup > cws-orion-backup.1.1.366/usr/local/orion-backup/php/event_handler_web.class.php
<?php

require_once('event_handler.class.php'); // parent
require_once('auth.class.php');

/**
 * This is the core of the web server.
 * It supplies all logic necessary to have ORION respond to web requests.
 * 
 * It is designed NOT to need any database access, since the main page(s)
 * of ORION (browse_files and download page) don't need it. Because of this, 
 * it takes different constructor parameters than most of its children.
 * 
 */
class WebEventHandler extends EventHandler {

    protected $op = '';
    protected $body_onload = '';    // javascript commands that will be run when the page or AJAX response loads.
    protected $account_links = '';  // html links for login, logout, password/account, etc
    protected $output = '';         // stores the HTML output for the page or AJAX response
    
    protected $month = '';          // current month being browsed (1-12)
    protected $day = '';            // current day being browsed (1-31)
    protected $hour = '';           // current hour being browsed (0-24)
    protected $backupid = '';       // current backup ID being browsed (i.e. name of backup set)
    protected $show_hidden = '';    // whether or not to display hidden files in backup set contents list
    
    private   $allowed_operations = array();
    protected $default_operation = null; // needs to be set!
    
    
    function __construct() {
        parent::__construct();
        
        // Get ready for global & session values
        session_register("loggedin"); // TODO remove me
        session_register("backupid");
        session_register("day");
        session_register("hour");
        session_register("month");
        session_register("show_hidden");    // "show hidden files" indicator (set with a checkbox)
        session_register("year");
        session_register("open_dirs");    

        if (!isset($_SESSION['open_dirs'])) $_SESSION['open_dirs'] = array();
        
        // add allowances for any operations included in this subclass
        $this->allow(array(
            'logout',
        ));
    }
    
    
    
    /**
     * Add function names to the list of allowed functions (i.e. those that can be called from the URL as op=function_name)
     *
     * @param array $function_names list of function names to add to the "allowed" list
     */
    protected function allow($function_names = array()) {
        $this->allowed_operations = array_merge($this->allowed_operations, $function_names);
    }
    
    
    
    protected function getAllowedOperations() {
        return $this->allowed_operations;
    }
    
    
    
    /**
     * The main responder function. This is essentially a callback that is fired by the init.php script
     * during the page load. It just gets the parameters from the URL and calls the appropriate function.
     *
     */
    function handle($op = null) {
        // collect the URL/form data and put it in the session
        $this->get_request_parameters();
        
        // pass off control to appropriate function
        if (is_null($op)) {
            $op = @$_REQUEST['op']; // test for null so that '' can be passed in to trigger the default (bypassing $_REQUEST)
        }

        // only the listed functions can be invoked via URL (limits auth involvement)
        if (!$op || !in_array($op, $this->allowed_operations)) {
            $op = $this->default_operation;
        }
        
        // save the operation for future use
        $this->op = $op;
                
        // call the function indicated by the URL
        if ($op) $this->$op();
    }

    
    /*
     * get the skin used by this user, site, etc. MODIFY AS DESIRED
     */
    function getSkin() {
        // TODO make this database driven? > requires subclass of web handler with DB access
        return 'default';
    }

    
    
    /*
     * getter for the body.onload script text variable
     */
    function getBodyOnload() {
        return $this->body_onload;
    }

    
    
    function getAccountLinks() {
        return $this->account_links;
    }

    
    
    /**
     * set the login/logout/account link HTML
     * @return null
     */
    function setAccountLinks() {
        $auth_object = Auth::getInstance();
        $this->account_links = "
            <div class='account_links'>
                <a href='./?op=logout'>
                    logout
                </a>
            </div>";
        if ($auth_object->requireUserRole('admin', true)) {
            $this->account_links .= "
                <div class='account_links'>
                    <a href='admin.php'>
                        admin
                    </a>
                </div>
            ";
        }
        $this->account_links .= "
            <div class='account_links'>
                <a href='./?op=account'>
                    my account
                </a>
            </div>";
    }
    
    
    
    /*
     * getter for the HTML-text variable
     */
    function getBodyHTML() {
        return $this->output;
    }
    
    
    
    /*
     * Sends a header redirecting browser to the login screen
     */
    function showLogin($message) {
        ob_clean();
        header('Location: '.$this->getLoginURL().'&m='.base64_encode($message));
        exit;
    }
    /*
     * returns the URL for the current site (without the query string)
     */
    function getSiteURL() {
        $path = str_replace('index.php','',$_SERVER['PHP_SELF']);
        return 'http://'.$_SERVER['SERVER_NAME'].$path;
    }

    
    /*
     * returns the full URL for the login page
     */
    function getLoginURL() {
        if (defined($_REQUEST['returnpage'])) {
            $return_page = urlencode($_REQUEST['return_page']);
        }
        elseif (!stristr($this->op,'login')) {
            $return_page = urlencode($this->op);
        }
        else {
            $return_page = '';
        }
        
        return $this->getSiteURL().'?op=login&returnpage='.$return_page;
    }

    
    /**
     * function for handling a failed authentication. For web context, show the login screen.
     */
    function authFailed($username = '') {
        $this->showLogin('Username or password was incorrect. Please try again.');
    }
    

    /**
     * function for handling insufficient authentication. For web context, show the login screen.
     */
    function authRequired($message = '') {
        $this->showLogin($message);
    }
    

    /**
     * function for logging out a user
     */
    protected function logout() {
        $auth_object = Auth::getInstance();
        $auth_object->logout();
        $this->handle(''); // empty string triggers the 'default' page
    }
    
    

    protected function processUserChanges($user, $new_data) {
        // process user_group change
        if (@$new_data['user_group']) {
            $user->user_group = $new_data['user_group'];
        }
        
        // process password change
        if (@$new_data['password']) {
            $user->password = Auth::getInstance()->hashPassword($new_data['password']);
        }
        
        // save if necessary
        if ($user->isModified()) {
            $user->save();
        }
        return $user;
    }
    
    
    
    /**
     * Clears all "open node" indicators, in effect collapsing all nodes in the user interface.
     * This is useful when changing the backup set, since all previously expanded node IDs become meaningless.
     *
     */
    protected function clear_session_thinger() {
        $_SESSION['open_dirs'] = array();
    }
    
    
    
    /**
     * Get request values from POST or GET; set them in SESSION and class variables
     * 
     */
    protected function get_request_parameters() {
        // Date/Time
        $today = getdate();
        if(isset($_REQUEST['year']))                        $_SESSION['year'] = $_REQUEST['year'];
        if (!preg_match("/^[0-9]{4}$/", $_SESSION['year'])) $_SESSION['year'] = $today['year'];
        $this->year = $_SESSION['year'];
        
        if(isset($_REQUEST['month']))                       $_SESSION['month'] = $_REQUEST['month'];
        if (!preg_match("/^[0-9]{1,2}$/", $_SESSION['month'])
        ||  intval($_SESSION['month']) < 1 
        ||  intval($_SESSION['month']) > 12)                $_SESSION['month'] = $today['mon'];
        $this->month = $_SESSION['month'];
        
        if(isset($_REQUEST['day']))                         $_SESSION['day'] = $_REQUEST['day'];
        if (!preg_match("/^[0-9]{1,2}$/", $_SESSION['day']) 
        ||  intval($_SESSION['day']) < 1
        ||  intval($_SESSION['day']) > 31)                  $_SESSION['day'] = $today['mday'];
        $this->day = $_SESSION['day'];
        
        if(isset($_REQUEST['hour']))                        $_SESSION['hour'] = $_REQUEST['hour'];
        if (!preg_match("/^[0-9]{1,2}$/", $_SESSION['hour']) 
        ||  intval($_SESSION['hour']) < 0
        ||  intval($_SESSION['hour']) > 23)                 $_SESSION['hour'] = $today['hours'];
        $this->hour = $_SESSION['hour'];
        
        // What backup set does the user want?
        if(isset($_REQUEST['backupid']))                    $_SESSION['backupid'] = $_REQUEST['backupid'];
        $this->backupid = $_SESSION['backupid'];
        
        // Does the user want to see hidden files?
        if(isset($_REQUEST['show_hidden']))                 $_SESSION['show_hidden'] = $_REQUEST['show_hidden'];
        if(!isset($_SESSION['show_hidden']))                $_SESSION['show_hidden'] = 0;
        $this->show_hidden = $_SESSION['show_hidden'];
    }
    
    

    /**
     * Generates HTML for the fancy-pants calendar widget
     *
     * @return string the HTML code that was generated
     */
    protected function get_calendar() {
        /*
         * A big thank you to Eric and phpfreaks.com for the cool calendar tutorial!
         * This is a great looking calendar, and is easy to set up and modify.
         * The tutorial can be found at http://www.phpfreaks.com/tutorials/83/0.php.
         * 
         */
                
        $result = "";
        $date = mktime(0,0,0,$this->month, $this->day, $this->year); // make an official date value
        $month_start = mktime(0,0,0,$this->month, 1, $this->year); // Get the first day of the month
        $month_name = date('M', $month_start); // Get friendly month name
        $month_start_day = date('D', $month_start); // Figure out which day of the week the month starts on.
        
        switch($month_start_day) {
            case "Sun": $offset = 0; break;
            case "Mon": $offset = 1; break;
            case "Tue": $offset = 2; break;
            case "Wed": $offset = 3; break;
            case "Thu": $offset = 4; break;
            case "Fri": $offset = 5; break;
            case "Sat": $offset = 6; break;
        }
        
        // determine how many days are in the previous month.
        if($this->month == 1)   $num_days_last = cal_days_in_month(0, 12, ($this->year -1));
        else                    $num_days_last = cal_days_in_month(0, ($this->month -1), $this->year);
        
        // determine how many days are in the current month.
        $num_days_current = cal_days_in_month(0, $this->month, $this->year);
        
        // Build an array for the current days in the month
        for($i = 1; $i <= $num_days_current; $i++) $num_days_array[] = $i;
        
        // Build an array for the number of days in last month
        for($i = 1; $i <= $num_days_last; $i++) $num_days_last_array[] = $i;
        
        // If the $offset from the starting day of the week happens to be Sunday, 
        // $offset would be 0, so don't need an offset correction.
        if($offset > 0) {
            $offset_correction = array_slice($num_days_last_array, -$offset, $offset);
            $new_count = array_merge($offset_correction, $num_days_array);
            $offset_count = count($offset_correction); // number of days from last month that appear on this calendar.
        } 
        else {
            // don't build the $offset array.
            $offset_count = 0;
            $new_count = $num_days_array;
        }
        
        // count how many days we have with the two previous arrays merged together
        $current_num = count($new_count);
        
        // Since we will have 5 HTML table rows (TR) with 7 table data entries (TD)
        // we need to fill in 35 TDs so, we will have to figure out how many days 
        // to appened to the end of the final array to make it 35 days.
        if($current_num > 35) {
             $num_weeks = 6;
             $outset = (42 - $current_num);
        } 
        elseif($current_num < 35) {
             $num_weeks = 5;
             $outset = (35 - $current_num);
        }
        else {
             $num_weeks = 5;
             $outset = 0;
        }
        
        // Outset Correction
        for($i = 1; $i <= $outset; $i++) $new_count[] = $i;
        
        // "chunk" the calendar array into weeks.
        $weeks = array_chunk($new_count, 7);
        
        // Build Previous and Next Links
        if($this->month == 1) {
             $link_month=12;
             $link_year=$this->year-1;
        }
        else {
             $link_month=$this->month-1;
             $link_year=$this->year;
        }
        $previous_link = "<a href='./?op=browse_files&month=$link_month&year=$link_year&day=".$this->day
            ."&hour=".$this->hour."&backupid=".$this->backupid."'><< Prev</a>";
        if($this->month == 12){
             $link_month=1;
             $link_year=$this->year+1;
        } else {
             $link_month=$this->month+1;
             $link_year=$this->year;
        }
        $next_link = "<a href='./?op=browse_files&month=$link_month&year=$link_year&day=".$this->day
            ."&hour=".$this->hour."&backupid=".$this->backupid."'>    Next >></a>";
        
        // Get the text that will display the time of day
        if($this->hour == 0)        $hourtext = "Midnight";
        elseif($this->hour == 12)   $hourtext = "Noon";
        elseif($this->hour < 12)    $hourtext = $this->hour."am";
        else                        $hourtext = ($this->hour - 12)."pm";
        
        // Build the heading portion of the calendar table
        $result .= "
        <div class='calendar'>
            <div class='calendar_labels'>
                <table class='calendar_labels'>
                    <tr>
                        <td class='calendar_link'>$previous_link</td>
                        <td class='calendar_title'>$month_name ".$this->day.", ".$this->year."<br>$hourtext</td>
                        <td class='calendar_link'>$next_link</td>
                    </tr>
                </table>
            </div>
            <div class='calendar_contents'>
                <table>
                    <tr class='daylabels'>
                        <td>S</td>
                        <td>M</td>
                        <td>T</td>
                        <td>W</td>
                        <td>T</td>
                        <td>F</td>
                        <td>S</td>
                    </tr>";
    
                    // show each week as a <tr>
                    $i = 0;
                    foreach($weeks AS $week) {
                        $result .= "<tr>\n";
                        foreach($week as $d) {
                            if($i < $offset_count) {
                                // last month's days
                                if ($this->month == 1) {
                                    $day_link = "<a href='./?op=browse_files&month=12&year=".($this->year-1)
                                        ."&day=$d&hour=".$this->hour."&backupid=".$this->backupid."'>$d</a>";
                                } else {
                                    $day_link = "<a href='./?op=browse_files&month=".($this->month-1)."&year=".$this->year
                                        ."&day=$d&hour=".$this->hour."&backupid=".$this->backupid."'>$d</a>";
                                }
                                $class = "nonmonthdays";
                            }
                            if(($i >= $offset_count) && ($i < ($num_weeks * 7) - $outset)) {
                                // this month's days
                                if($date == mktime(0,0,0,$this->month,$d,$this->year)) {
                                    $day_link = $d;
                                    $class = "today";
                                } else {
                                    $day_link = "<a href='./?op=browse_files&month=".$this->month."&year=".$this->year
                                        ."&day=$d&hour=".$this->hour."&backupid=".$this->backupid."'>$d</a>";
                                    $class = "days";
                                }
                            }
                            elseif(($outset > 0)) {
                                // next month's days
                                if(($i >= ($num_weeks * 7) - $outset)) {
                                    if ($this->month == 12) {
                                        $day_link = "<a href='./?op=browse_files&month=1&year=".($this->year+1)
                                            ."&day=$d&hour=".$this->hour."&backupid=".$this->backupid."'>$d</a>";
                                    } 
                                    else {
                                        $day_link = "<a href='./?op=browse_files&month=".($this->month+1)."&year=".$this->year
                                            ."&day=$d&hour=".$this->hour."&backupid=".$this->backupid."'>$d</a>";
                                    }
                                    $class = "nonmonthdays";
                                }
                            }
                            $result .= "<td class='$class'>$day_link</td>\n";
                            $i++;
                        }
                        $result .= "</tr>\n";     
                    }
                $result .= "</table>
            </div>"; //end of class='calendar_contents'
        
            // Create time links
            $result .= "
            <div class='calendar_timelinks'>
                <table>
                    <tr>
                        <td class='am_links'>";
                            // Show the link for each hour. Loop from 0 to 23.
                            for($h = 0; $h < 24; $h++) {
                                $result .= "<a href='./?op=browse_files&month=".$this->month."&year=".$this->year
                                    ."&day=".$this->day."&hour=$h&backupid=".$this->backupid."'>";
                                
                                if($h == 0)         $hourlabel = "Midnight";
                                elseif($h == 12)    $hourlabel = "Noon";
                                elseif($h < 12)     $hourlabel = $h.":00 am";
                                else                $hourlabel = ($h-12).":00 pm";
                                
                                if($h == $this->hour)   $result .= "<b>$hourlabel</b>"; // Show the currently selected hour in bold
                                else                    $result .= $hourlabel;
                            
                                $result .= "</a><br>\n";
                                
                                // Go to the next column as necessary.
                                if($h == 5)                     $result .= "</td><td class='am_links'>";
                                elseif($h == 11 || $h == 17)    $result .= "</td><td class='pm_links'>";
                            }
                            $result .="
                        </td>
                    </tr>
                </table>
            </div>";    // end of calendar_timelinks
            
            // Close out the outer calendar div
            $result .= "
        </div>";
        
        return $result;
    }
    
    
    /**
     * Web-specific function for dumping all system messages that were generated during a script run or page load
     * this version puts <pre> tags around the message.
     */
    function showLogMessages() {
        if (DEBUG_LEVEL > 1) {
            $system = SystemObject::getInstance();
            $msg = $system->getAllMessages();
            echo "<pre>$msg</pre>";
        }
    }


    /**
     * When a link is clicked in the browse_files function, this function copies the file to be restored,
     * as of the required date, into a temporary storage location where it can simply be clicked on and downloaded.
     * 
     * At the moment, the file remains there until the hourly cron job is run which, empties the folder.
     * 
     */
    protected function get_download() {
        $auth_object = Auth::getInstance();
        $system_object = SystemObject::getInstance();
        
        // user must be logged in
        $auth_object->requireUserRole('user'); // will auto-show login page if auth fails
        
        // do the restore
        $pathname = str_replace("\'","'",$_REQUEST['link_to_file']); // strips out the escape chars for single quotes
        $filename = $this->restoreBackup($pathname);

        if (!$filename) {
            $this->failed_download();
        }

        // we're just going to send the file directly (but we still use the restored file from disk.)
        $this->sendDownloadFile($filename); // will exit silently
    }



    /**
     * restores a file from a backup set and saves it in the temp directory
     * also zips up directories before returning
     *
     * @param string $pathname the full system path, not relative to a backup root.
     * @param bolean $zip_dirs if true, then a restored directory will be converted to an archive
     * @return string filename of restored file or archive
     */
    protected function restoreBackup($pathname, $zip_dirs = true) {
        $system_object = SystemObject::getInstance();
        $filename = basename($pathname);
        $temp_file = DOWNLOAD_LOCATION.$filename;
        
        // restore the file or dir
        $this->rdiffRestoreAsOf($pathname);

        // check that the user can access the file
        if (!$this->enforceFilePermissions($temp_file, true)) {
            return false;
        }

        // zip directories
        if (filetype($temp_file) == 'dir' && $zip_dirs) {
            $filename = $this->archive($filename);
        }

        // set owner & perms so that apache can send it to the browser
        $this->chownTempFile($filename);
        
        return $filename;
    }
    
    
    /**
     * do the actual restore command - pathname should be a full system path, not relative to any backup root.
     *
     * @param string $pathname
     * @param string $browseat in the ANSI format used by rdiff
     * @return result from system->do_command
     */
    protected function rdiffRestoreAsOf($pathname, $browseat = null) {
        $system_object = SystemObject::getInstance();
        if (!$browseat) {
            $browseat = $this->getDateString();
        }
        
        $source_esc = escapeshellarg(BACKUP_STORE_DIR.$pathname);
        $filename = basename($pathname);
        $temp_file = DOWNLOAD_LOCATION.$filename;
        $temp_file_esc = escapeshellarg($temp_file);
        
        $cmd = RDIFF_BACKUP . " --restore-as-of '$browseat' --force $source_esc $temp_file_esc";
        $result = $system_object->do_command($cmd, RDIFF_BACKUP_RESTORE_USER);
        return $result;
    }
    
    
    
    /**
     * turn a directory into a zip
     * return the filename of the newly created zipfile
     *
     * @param string $filename
     * @return string
     */
    protected function archive($filename) {
        $system_object = SystemObject::getInstance();
        
        // do the zipping
        $cmd = ZIP_SCRIPT.escapeshellarg($filename);
        $system_object->do_command($cmd, RDIFF_BACKUP_RESTORE_USER);

        // change the filename to work with the zip instead of the folder
        $filename .= ZIP_SUFFIX;
        
        return $filename;
    }
    
    
    
    /**
     * Sends an existing file in the temp dir as a MIME-typed download, including headers that force the popup window.
     *
     * @param string $filename sans path
     */
    public function sendDownloadFile($filename) {
        // get content type from filesystem
        $system = SystemObject::getInstance();
        $mimetype = $system->getMimeType(DOWNLOAD_LOCATION.$filename);
        $size = $system->getFileSize(DOWNLOAD_LOCATION.$filename);
        
        // send headers - this forces the download window to pop up, instead of the browser trying to render it.
        header("Content-Type: $mimetype");
        header("Content-Description: File Transfer");            
        header("Content-Length: " . $size);
        $attachment = (FORCE_DOWNLOAD_DIALOG) ? 'attachment; ' : '';
        header("Content-Disposition: $attachment filename=\"".str_replace('"','',$filename).'"');
        flush(); // for memory lightness & responsive window-pop-uppage
        
        // send content
        readfile(DOWNLOAD_LOCATION.$filename);

        $this->deleteTempFile($filename); // for security
        exit; // don't want any html to be sent by index.php
    }
    
    
    /**
     * make sure a user is privileged to see/download a file in the temp dir.
     *
     * @param unknown_type $filename
     */
    public function enforceFilePermissions($filename, $delete_on_failure = false) {
        $auth = Auth::getInstance();
        
        $owner = posix_getpwuid(fileowner($filename));
        $owner = $owner['name'];
        if ($owner != $auth->getLoggedInUser()) {
            // if the user doesn't own the file, then s/he has to be an administrator.
            if (!$auth->requireUserRole('admin',true)) {
                if ($delete_on_failure) $this->deleteTempFile($filename);
                return false;
            }
        }
        return true;
    }
    
    
    
    /**
     * set owner & perms of a temp file so that apache/php user can work with it.
     *
     * @param string $filename
     */
    protected function chownTempFile($filename) {
        $system_object = SystemObject::getInstance();
        $cmd = CHOWN_SCRIPT.' '.escapeshellarg($filename);
        $result = $system_object->do_command($cmd, RDIFF_BACKUP_RESTORE_USER);
        return $result;
    }
    


    /**
     * removes a temp file from the temp directory
     *
     * @param unknown_type $filename
     */
    public function deleteTempFile($filename) {
        // erase the tmp file
        unlink(DOWNLOAD_LOCATION.$filename);
    }
    
    
    
    protected function createAlertsHTML($messages) {
        if (!$messages) {
            return '';
        }
        
        if (!is_array($messages)) {
            $messages = array($messages);
        }
        
        $alerts = "<ul class='errormessage'>";
        foreach ($messages as $message) {
            $alerts .= "<li>$message</li>";
        }
        $alerts .= "</ul>";
        return $alerts;
    }
}
Return current item: ORION-Backup