You are here

protected function DownloadController::transferDownload in Ubercart 8.4

Sends the file's binary data to a user via HTTP and updates the database.

Parameters

$file_user: The file_user object from the uc_file_users.

string $ip: The string containing the IP address the download is going to.

1 call to DownloadController::transferDownload()
DownloadController::download in uc_file/src/Controller/DownloadController.php
Handles file downloading and error states.

File

uc_file/src/Controller/DownloadController.php, line 343

Class

DownloadController
Handles administrative view of files that may be purchased and downloaded.

Namespace

Drupal\uc_file\Controller

Code

protected function transferDownload($file_user, $ip) {

  // Create the response.
  $response = new BinaryFileResponse();

  // Check if any hook_uc_file_transfer_alter() calls alter the download.
  $module_handler = $this
    ->moduleHandler();
  foreach ($module_handler
    ->getImplementations('uc_file_transfer_alter') as $module) {
    $name = $module . '_uc_file_transfer_alter';
    $file_user->full_path = $name($file_user, $ip, $file_user->fid, $file_user->full_path);
  }

  // This could get clobbered, so make a copy.
  $filename = $file_user->filename;

  // Gather relevant info about the file.
  $size = filesize($file_user->full_path);
  $mimetype = file_get_mimetype($filename);

  // Workaround for IE filename bug with multiple periods / multiple dots
  // in filename that adds square brackets to filename -
  // eg. setup.abc.exe becomes setup[1].abc.exe.
  if (strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE')) {
    $filename = preg_replace('/\\./', '%2e', $filename, substr_count($filename, '.') - 1);
  }

  // Check if HTTP_RANGE is sent by browser (or download manager).
  $range = NULL;
  if (isset($_SERVER['HTTP_RANGE'])) {
    if (substr($_SERVER['HTTP_RANGE'], 0, 6) == 'bytes=') {

      // Multiple ranges could be specified at the same time,
      // but for simplicity only serve the first range
      // See http://tools.ietf.org/id/draft-ietf-http-range-retrieval-00.txt
      list($range, $extra_ranges) = explode(',', substr($_SERVER['HTTP_RANGE'], 6), 2);
    }
    else {
      $response->headers
        ->set('Status', '416 Requested Range Not Satisfiable');
      $response->headers
        ->set('Content-Range', 'bytes */' . $size);
      return $response;
    }
  }

  // Figure out download piece from range (if set).
  if (isset($range)) {
    list($seek_start, $seek_end) = explode('-', $range, 2);
  }

  // Set start and end based on range (if set),
  // else set defaults and check for invalid ranges.
  $seek_end = intval(empty($seek_end) ? $size - 1 : min(abs(intval($seek_end)), $size - 1));
  $seek_start = intval(empty($seek_start) || $seek_end < abs(intval($seek_start)) ? 0 : max(abs(intval($seek_start)), 0));

  // Only send partial content header if downloading a piece of the file (IE
  // workaround).
  if ($seek_start > 0 || $seek_end < $size - 1) {
    $response->headers
      ->set('Status', '206 Partial Content');
  }

  // Standard headers, including content-range and length.
  $response->headers
    ->set('Pragma', 'public');
  $response->headers
    ->set('Cache-Control', 'cache, must-revalidate');
  $response->headers
    ->set('Accept-Ranges', 'bytes');
  $response->headers
    ->set('Content-Range', 'bytes ' . $seek_start . '-' . $seek_end . '/' . $size);
  $response->headers
    ->set('Content-Type', $mimetype);
  $response->headers
    ->set('Content-Disposition', 'attachment; filename="' . $filename . '"');
  $response->headers
    ->set('Content-Length', $seek_end - $seek_start + 1);

  // Last-Modified is required for content served dynamically.
  $response->headers
    ->set('Last-Modified', gmdate("D, d M Y H:i:s", filemtime($file_user->full_path)) . " GMT");

  // Etag header is required for Firefox3 and other managers.
  $response->headers
    ->set('ETag', md5($file_user->full_path));

  // Open the file and seek to starting byte.
  $fp = fopen($file_user->full_path, 'rb');
  fseek($fp, $seek_start);

  // Start buffered download.
  while (!feof($fp)) {

    // Reset time limit for large files.
    drupal_set_time_limit(0);

    // Push the data to the client.
    print fread($fp, UC_FILE_BYTE_SIZE);
    flush();

    // Suppress PHP notice that occurs when output buffering isn't enabled.
    // The ob_flush() is needed because if output buffering *is* enabled,
    // clicking on the file download link won't download anything if the
    // buffer isn't flushed.
    @ob_flush();
  }

  // Finished serving the file, close the stream and log the download
  // to the user table.
  fclose($fp);
  $this
    ->logDownload($file_user, $ip);
}