Advanced aggregation module; 404 handler.

File

includes/missing.inc

View source
<?php


/**
 * @file
 * Advanced aggregation module; 404 handler.
 *
 */

/**
 * Menu Callback; regenerates a missing css file.
 */
function advagg_missing_css() {
    ignore_user_abort();
    // Try to regenerate missing file
    $msg = advagg_missing_regenerate();
    // If here send out fast 404.
    advagg_missing_fast404($msg);
}

/**
 * Menu Callback; regenerates a missing js file.
 */
function advagg_missing_js() {
    ignore_user_abort();
    // Try to regenerate missing file
    $msg = advagg_missing_regenerate();
    // If here send out fast 404.
    advagg_missing_fast404($msg);
}

/**
 * regenerates a missing css file.
 *
 * @param $filename
 *   filename
 * @param $type
 *   css or js
 * @return
 *   false if bundle couldn't be generated.
 */
function advagg_missing_regenerate() {
    global $base_path, $conf;
    // Get filename from request.
    $arg = arg();
    $filename = array_pop($arg);
    $filename = explode('?', $filename);
    $filename = array_shift($filename);
    $data = advagg_get_bundle_from_filename($filename);
    if (is_array($data)) {
        list($type, $md5, $counter) = $data;
    }
    else {
        return $data;
    }
    $_GET['redirect_counter'] = isset($_GET['redirect_counter']) ? intval($_GET['redirect_counter']) : 0;
    if ($_GET['redirect_counter'] > 5) {
        watchdog('advagg', 'This request could not generate correctly. Loop detected. Request data: %info', array(
            '%info' => $_GET['q'],
        ));
        return t('In a Loop.');
    }
    // Counter in database.
    $counter_in_db = db_query("SELECT counter FROM {advagg_bundles} WHERE bundle_md5 = :bundle_md5", array(
        ':bundle_md5' => $md5,
    ))->fetchField();
    if ($counter_in_db === FALSE) {
        return t('Not a valid bundle.');
    }
    // Cast counter as int
    $counter_in_db = intval($counter_in_db);
    $counter = intval($counter);
    // Set file(s) in cache to FALSE.
    $arg[] = $filename;
    cache_set(implode('/', $arg), FALSE, 'cache_advagg', TRUE);
    advagg_missing_remove_cache($md5);
    // Build filepath.
    list($css_path, $js_path) = advagg_get_root_files_dir();
    if ($type == 'js') {
        $file_type_path = $js_path;
    }
    if ($type == 'css') {
        $file_type_path = $css_path;
    }
    $new_filename = advagg_build_filename($type, $md5, $counter);
    $filepath = $file_type_path . '/' . $new_filename;
    // Only process if we got an older counter.
    // If we have an out of range counter see if a simlar file exists and serve
    // that up.
    if ($counter > $counter_in_db || $counter < 0) {
        $new_filename = advagg_build_filename($type, $md5, $counter_in_db);
        $filepath = $file_type_path . '/' . $new_filename;
        advagg_missing_send_file($filepath, advagg_build_uri($filepath), $type);
        exit;
    }
    // Break connection and do generation in the background.
    if (!empty($_GET['generator'])) {
        advagg_missing_async_opp($md5 . ' ' . $counter);
    }
    // Rebuild file.
    $conf['advagg_async_generation'] = FALSE;
    $good = advagg_rebuild_bundle($md5, $counter, TRUE);
    if (!$good) {
        watchdog('advagg', 'This request could not generate correctly. Aggregate not generated. Request data: %info', array(
            '%info' => $_GET['q'],
        ));
        return t('Rebuild Failed.');
    }
    // Serve direct or redirect to file.
    $_GET['redirect_counter']++;
    $uri = $base_path . $_GET['q'] . '?redirect_counter=' . $_GET['redirect_counter'];
    advagg_missing_send_file($filepath, $uri, $type);
    exit;
}

/**
 * Send the file or send a 307 redirect.
 *
 * @param $filepath
 *   filename
 * @param $uri
 *   css or js
 */
function advagg_missing_send_file($filepath, $uri, $type) {
    if (!headers_sent()) {
        // Code from file_download.
        if (file_exists($filepath)) {
            $headers = module_invoke_all('file_download', $filepath, $type);
            if ($key = array_search(-1, $headers)) {
                unset($headers[$key]);
            }
            // Remove all previously set Cache-Control headers, because we're going to
            // override it. Since multiple Cache-Control headers might have been set,
            // simply setting a new, overriding header isn't enough: that would only
            // override the *last* Cache-Control header. Yay for PHP!
            foreach ($headers as $key => $header) {
                if (strpos($header, 'Cache-Control:') === 0) {
                    unset($headers[$key]);
                }
                elseif (strpos($header, 'ETag:') === 0) {
                    unset($headers[$key]);
                }
            }
            if (function_exists('header_remove')) {
                header_remove('Cache-Control');
                header_remove('ETag');
            }
            else {
                drupal_add_http_header('Cache-Control', '');
                drupal_add_http_header('Cache-Control', '');
                drupal_add_http_header('ETag', '');
                drupal_add_http_header('ETag', '');
            }
            // Set a far future Cache-Control header (480 weeks), which prevents
            // intermediate caches from transforming the data and allows any
            // intermediate cache to cache it, since it's marked as a public resource.
            $headers[] = "Cache-Control: max-age=290304000, no-transform, public";
            if (count($headers)) {
                advagg_missing_file_transfer($filepath, $headers);
            }
        }
        // advagg_missing_file_transfer didn't run/send data, redirect via header.
        header('Location: ' . $uri, TRUE, 307);
        usleep(250000);
        // Sleep for 250ms
    }
}

/**
 * Set cache value to FALSE.
 *
 * @param $bundle_md5
 *   Bundle's machine name.
 */
function advagg_missing_remove_cache($bundle_md5) {
    $files = array();
    $results = db_query("SELECT filename, filetype FROM {advagg_files} AS af INNER JOIN {advagg_bundles} AS ab USING ( filename_md5 ) WHERE bundle_md5 = :bundle_md5 ORDER BY porder ASC", array(
        ':bundle_md5' => $bundle_md5,
    ));
    while ($row = db_fetch_array($results)) {
        $files[] = $row['filename'];
        $type = $row['filetype'];
    }
    list($css_path, $js_path) = advagg_get_root_files_dir();
    if ($type == 'js') {
        $file_type_path = $js_path;
    }
    if ($type == 'css') {
        $file_type_path = $css_path;
    }
    $filenames = advagg_get_filename($files, $type, '', $bundle_md5);
    if (!empty($filenames)) {
        foreach ($filenames as $key => $info) {
            $filename = $info['filename'];
            $filepath = $file_type_path . '/' . $filename;
            cache_set($filepath, FALSE, 'cache_advagg', TRUE);
        }
    }
}

/**
 * Output text & set php in async mode.
 *
 * @param $output
 *  string - Text to output to open connection.
 * @param $wait
 *  bool - Wait 1 second?
 * @param $content_type
 *  string - Content type header.
 * @param $length
 *  int - Content length.
 */
function advagg_missing_async_opp($output, $wait = TRUE, $content_type = "text/html; charset=utf-8", $length = 0) {
    if (headers_sent()) {
        return FALSE;
    }
    // Calculate Content Length
    if ($length == 0) {
        $output .= "\n";
        $length = advagg_missing_strlen($output) - 1;
    }
    // Prime php for background operations
    $loop = 0;
    while (ob_get_level() && $loop < 25) {
        ob_end_clean();
        $loop++;
    }
    header("Connection: close");
    ignore_user_abort();
    // Output headers & data
    ob_start();
    header("Content-type: " . $content_type);
    header("Expires: Sun, 19 Nov 1978 05:00:00 GMT");
    header("Cache-Control: no-cache");
    header("Cache-Control: must-revalidate");
    header("Content-Length: " . $length);
    header("Connection: close");
    print $output;
    ob_end_flush();
    flush();
    // wait for 1 second
    if ($wait) {
        sleep(1);
    }
    // text returned and connection closed.
    // Do background processing. Time taken after should not effect page load times.
    return TRUE;
}

/**
 * Get the length of a string in bytes
 *
 * @param $string
 *   get string length
 */
function advagg_missing_strlen($string) {
    if (function_exists('mb_strlen')) {
        return mb_strlen($string, '8bit');
    }
    else {
        return strlen($string);
    }
}

/**
 * Transfer file using http to client. Pipes a file through Drupal to the
 * client.
 *
 * @param $source File to transfer.
 * @param $headers An array of http headers to send along with file.
 */
function advagg_missing_file_transfer($source, $headers) {
    $source = advagg_missing_file_create_path($source);
    $fd = fopen($source, 'rb');
    // Return if we can't open the file. Will try a 307 in browser to send file.
    if (!$fd) {
        return;
    }
    // Clear the buffer.
    if (ob_get_level()) {
        ob_end_clean();
    }
    // Add in headers.
    foreach ($headers as $header) {
        // To prevent HTTP header injection, we delete new lines that are
        // not followed by a space or a tab.
        // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
        $header = preg_replace('/\\r?\\n(?!\\t| )/', '', $header);
        drupal_add_http_header($header);
    }
    // Transfer file in 8096 byte chunks.
    while (!feof($fd)) {
        print fread($fd, 8096);
    }
    fclose($fd);
    exit;
}

/**
 * Make sure the destination is a complete path and resides in the file system
 * directory, if it is not prepend the file system directory.
 *
 * @param $dest A string containing the path to verify. If this value is
 *   omitted, Drupal's 'files' directory will be used.
 * @return A string containing the path to file, with file system directory
 *   appended if necessary, or FALSE if the path is invalid (i.e. outside the
 *   configured 'files' or temp directories).
 */
function advagg_missing_file_create_path($dest = 0) {
    list($css_path, $js_path) = advagg_get_root_files_dir();
    $valid_paths = array();
    $valid_paths[] = file_directory_path();
    $valid_paths[] = $css_path;
    $valid_paths[] = $js_path;
    foreach ($valid_paths as $file_path) {
        if (!$dest) {
            return $file_path;
        }
        // file_check_location() checks whether the destination is inside the Drupal files directory.
        if (file_check_location($dest, $file_path)) {
            return $dest;
        }
        else {
            if (file_check_location($dest, file_directory_temp())) {
                return $dest;
            }
            else {
                if (file_check_location($file_path . '/' . $dest, $file_path)) {
                    return $file_path . '/' . $dest;
                }
            }
        }
    }
    // File not found.
    return FALSE;
}

Functions

Title Deprecated Summary
advagg_missing_async_opp Output text & set php in async mode.
advagg_missing_css Menu Callback; regenerates a missing css file.
advagg_missing_file_create_path Make sure the destination is a complete path and resides in the file system directory, if it is not prepend the file system directory.
advagg_missing_file_transfer Transfer file using http to client. Pipes a file through Drupal to the client.
advagg_missing_js Menu Callback; regenerates a missing js file.
advagg_missing_regenerate regenerates a missing css file.
advagg_missing_remove_cache Set cache value to FALSE.
advagg_missing_send_file Send the file or send a 307 redirect.
advagg_missing_strlen Get the length of a string in bytes