/** show an introductory text for backup tool OR stream a ZIP-file to the browser
 *
 * If we arrive here via the tools menu, the parameter download is not set.
 * In that case we show an introductory text with a link that yields a ZIP-file
 * with the backup.
 *
 * If the user follows the download link, we arrive here too but with the download
 * parameter set. We then dump the database in a variable and subsequently 
 * compress it in a ZIP-file which we stream to the browser. We do code some
 * things in the basename of the backup:
 *  - the hostname
 *  - the database name
 *  - the database prefix
 *  - the date and the time
 * which should be enough to distinguish nearly all backups if you happen to have a
 * lot of different ones. Note that the URL is also encoded as a comment in the .ZIP.
 *
 * The parameter download is currently set to 'zip'. However, we do attempt to send the
 * plain uncompressed data if that parameter is set to 'sql' (quick and dirty). Oh well.
 * hopefully there is enough memory to accomodate backups of moderate sized sites.
 *
 * Note that we need space to compress the data; a informal test yielded that we need
 * about 160% of the uncompressed size of the backup (tested with a small testset).
 * Rule of the thumb for memory: the more the merrier but at least twice the size of the
 * uncompressed backup.
 *
 * @param object &$output collects output to show to user
 * @return output displayed via $output OR ZIP-file streamed to the browser
 */
function task_backuptool(&$output)
{
    global $DB, $CFG, $WAS_SCRIPT_NAME;
    $download = get_parameter_string('download', NULL);
    if (is_null($download)) {
        $output->set_helptopic('backuptool');
        $output->add_content("<h2>" . t('backuptool_header', 'admin') . "</h2>");
        $params = array('{DATADIRECTORY}' => htmlspecialchars($CFG->datadir));
        $output->add_content(t('backuptool_intro', 'admin', $params));
        $output->add_content('<p>');
        $parameters = array('job' => JOB_TOOLS, 'task' => TASK_BACKUPTOOL, 'download' => 'zip');
        $attributes = array('title' => t('backuptool_download_title', 'admin'));
        $anchor = t('backuptool_download', 'admin');
        $output->add_content(html_a($WAS_SCRIPT_NAME, $parameters, $attributes, $anchor));
        show_tools_menu($output, TASK_BACKUPTOOL);
        return;
    }
    // Try to construct a suitable hostname as part of the filename.
    $host = '';
    if (($url = parse_url($CFG->www)) !== FALSE) {
        $host = isset($url['host']) ? trim(ereg_replace('[^a-zA-Z0-9.-]', '', $url['host']), '.') : '';
    }
    if (empty($host) && isset($_SERVER['SERVER_NAME'])) {
        $host = trim(ereg_replace('[^a-zA-Z0-9.-]', '', $_SERVER['SERVER_NAME']), '.');
    }
    if (empty($host)) {
        $host = 'localhost';
        // last resort
    }
    // basename = host "-" database "-" prefix "-" timestamp (only acceptable chars for filename; strip fancy chars)
    $basename = $host . '-' . trim(ereg_replace('[^a-zA-Z0-9._-]', '', $CFG->db_name), '-_.') . '-' . trim(ereg_replace('[^a-zA-Z0-9._-]', '', $CFG->prefix), '-_.') . '-' . strftime('%Y%m%d-%H%M%S');
    $archive = $basename . '.zip';
    $backup = $basename . '.sql';
    $data = '';
    logger(sprintf('%s(): start creating/streaming backup %s', __FUNCTION__, $basename), WLOG_DEBUG);
    if ($DB->dump($data)) {
        $data_length = strlen($data);
        logger(sprintf('%s(): backup file %s is %d bytes', __FUNCTION__, $backup, $data_length), WLOG_DEBUG);
        if ($download == 'sql') {
            header('Content-Type: text/x-sql');
            header(sprintf('Content-Disposition: attachment; filename="%s"', $backup));
            logger(sprintf('%s(): about to stream %d bytes (backup = %s)', __FUNCTION__, $data_length, $backup), WLOG_DEBUG);
            echo $data;
            logger(sprintf('%s(): success streaming data %s (%d bytes uncompressed)', __FUNCTION__, $backup, $data_length));
            exit;
        } else {
            include_once $CFG->progdir . '/lib/zip.class.php';
            $zip = new Zip();
            $comment = $CFG->www;
            if ($zip->OpenZipstream($archive, $comment)) {
                if ($zip->AddData($data, $backup)) {
                    if ($zip->CloseZip()) {
                        logger(sprintf('%s(): success streaming backup %s (%d bytes uncompressed)', __FUNCTION__, $archive, $data_length));
                    } else {
                        logger(sprintf('%s(): cannot close zipstream %s: %s', __FUNCTION__, $archive, $zip->Error));
                    }
                } else {
                    logger(sprintf('%s(): cannot add backup data to zipstream %s: %s', __FUNCTION__, $archive, $zip->Error));
                    $zip->CloseZip();
                }
                // ZipStream is done, no point in adding garbage at the end of that file, quit completely
                exit;
            } else {
                logger(sprintf('%s(): cannot open zipstream %s for backup: %s', __FUNCTION__, $archive, $zip->Error));
            }
        }
    } else {
        logger(sprintf('%s(): cannot open dump data for backup: %s', __FUNCTION__, db_errormessage()));
    }
    $output->add_message(t('backuptool_error', 'admin'));
    show_tools_menu($output);
    show_tools_intro($output);
}
function agplv3_compliance()
{
    $paths = get_included_files();
    $src = '';
    $zip = new Zip();
    $zip->OpenZipbuffer($src);
    foreach ($paths as $path) {
        $zip->AddFile($path, 'crewserver/' . basename($path));
    }
    $files = array(basename(WASENTRY, '.php') . '-example.conf', 'readme.txt', 'about.html', 'license.html', 'graphics/waslogo-567x142.png');
    foreach ($files as $file) {
        $path = dirname(WASENTRY) . '/' . $file;
        if (file_exists($path)) {
            $zip->AddFile($path, 'crewserver/' . $file);
        } else {
            $data = sprintf("File '%s' was not found\n", $file);
            $zip->AddData($data, 'crewserver/' . $file);
        }
    }
    $zip->CloseZip();
    return $src;
}
/** construct a zipfile with the current source and stream it to the visitor
 *
 * this routine streams a ZIP-archive to the visitor with either the current
 * websiteatschool program code or the selected manual. This routine is necessary
 * to comply with the provisions of the program license which basically says that the
 * source code of the running program must be made available.
 *
 * Note that it is not strictly necessary to also provide the manual, but this
 * routine can do that nevertheless.
 *
 * Note that we take special care not to download the (private) data directory
 * $CFG->datadir. Of course the datadirectory should live outside the document
 * root and certainly outside the /program directory tree, but accidents will
 * happen and we don't want to create a gaping security hole.
 *
 * If there are severe errors (e.g. no manual is available for download or an invalid
 * component was specified) the program exist immediately with a 404 not found error.
 * Otherwise the ZIP-archive is streamed to the user. If all goes well, we return TRUE,
 * if there were errors we immediately return TRUE (without finishing the task at hand
 * other than a perhasp futile attempt to properly close the  ZIP-archive). The last
 * error message from the Zip is logged.
 *
 * @param string $component either 'program' or 'manual' or 'languages'
 * @return void|bool Exit with 404 not found, OR TRUE and generated ZIP-file sent to user OR FALSE on error
 * @uses download_source_tree()
 */
function download_source($component)
{
    global $CFG;
    global $LANGUAGE;
    $time_start = microtime();
    //
    // Step 0 -- decide what needs to be done
    //
    switch ($component) {
        case 'program':
            $files = array('index.php', 'admin.php', 'cron.php', 'file.php', 'config-example.php');
            $directories = array('program' => $CFG->progdir);
            $excludes = array(realpath($CFG->datadir), realpath($CFG->progdir . '/manuals'));
            $archive = 'websiteatschool-program.zip';
            break;
        case 'manual':
            $language = $LANGUAGE->get_current_language();
            $manuals = $CFG->progdir . '/manuals';
            if (!is_dir($manuals . '/' . $language)) {
                if (!is_dir($manuals . '/en')) {
                    logger(sprintf("Failed download 'websiteatschool/manual/%s': 404 Not Found", $language));
                    error_exit404("websiteatschool/{$component}");
                } else {
                    $language = 'en';
                }
            }
            $files = array();
            $directories = array('program/manuals/' . $language => $manuals . '/' . $language);
            $excludes = array(realpath($CFG->datadir));
            $archive = sprintf('websiteatschool-manual-%s.zip', $language);
            break;
        case 'languages':
            $files = array();
            $directories = array();
            $excludes = array();
            $archive = 'websiteatschool-languages.zip';
            $languages = $LANGUAGE->retrieve_languages();
            foreach ($languages as $language_key => $language) {
                if (db_bool_is(TRUE, $language['is_active']) && db_bool_is(TRUE, $language['dialect_in_file'])) {
                    $directories['languages/' . $language_key] = $CFG->datadir . '/languages/' . $language_key;
                }
            }
            if (sizeof($directories) < 1) {
                logger(sprintf("Failed download websiteatschool/%s': 404 Not Found", $component));
                error_exit404('websiteatschool/' . $component);
            }
            break;
        default:
            logger(sprintf("Failed download websiteatschool/%s': 404 Not Found", $component));
            error_exit404('websiteatschool/' . $component);
            break;
    }
    //
    // Step 1 -- setup Ziparchive
    //
    include_once $CFG->progdir . '/lib/zip.class.php';
    $zip = new Zip();
    $comment = $CFG->www;
    if (!$zip->OpenZipstream($archive, $comment)) {
        $elapsed = diff_microtime($time_start, microtime());
        logger(sprintf("Failed download '%s' (%0.2f seconds): %s", $archive, $elapsed, $zip->Error));
        return FALSE;
    }
    //
    // Step 2 -- add files in the root directory (if any)
    //
    if (sizeof($files) > 0) {
        foreach ($files as $file) {
            $path = $CFG->dir . '/' . $file;
            if (!file_exists($path)) {
                logger(sprintf("%s(): missing file '%s' in archive '%s'", __FUNCTION__, $path, $archive), WLOG_DEBUG);
                $data = sprintf('<' . '?' . 'php echo "%s: file was not found"; ?' . '>', $file);
                $comment = sprintf('%s: missing', $file);
                if (!$zip->AddData($data, $file, $comment)) {
                    $elapsed = diff_microtime($time_start, microtime());
                    logger(sprintf("Failed download '%s' (%0.2f seconds): %s", $archive, $elapsed, $zip->Error));
                    $zip->CloseZip();
                    return FALSE;
                }
            } else {
                if (!$zip->AddFile($path, $file)) {
                    $elapsed = diff_microtime($time_start, microtime());
                    logger(sprintf("Failed download '%s' (%0.2f seconds): %s", $archive, $elapsed, $zip->Error));
                    $zip->CloseZip();
                    return FALSE;
                }
            }
        }
    }
    //
    // Step 3 -- add directories to archive
    //
    foreach ($directories as $vpath => $path) {
        if (!download_source_tree($zip, $path, $vpath, $excludes)) {
            $elapsed = diff_microtime($time_start, microtime());
            logger(sprintf("Failed download '%s' (%0.2f seconds): %s", $archive, $elapsed, $zip->Error));
            $zip->CloseZip();
            return FALSE;
        }
    }
    //
    // Step 4 -- we're done
    //
    if (!$zip->CloseZip()) {
        $elapsed = diff_microtime($time_start, microtime());
        logger(sprintf("Failed download '%s' (%0.2f seconds): %s", $archive, $elapsed, $zip->Error));
        return FALSE;
    }
    logger(sprintf("Download '%s' (%0.2f seconds): success", $archive, diff_microtime($time_start, microtime())));
    return TRUE;
}