You are here

cdn.inc in CDN 5

Basic functions for CDN integration and synchronization.

File

cdn.inc
View source
<?php

/**
 * @file
 * Basic functions for CDN integration and synchronization.
 */

/**
 * Implementation of hook_file_server().
 */
function cdn_file_server($op, $file_path, $absolute_url = FALSE) {
  switch ($op) {
    case 'info':
      return array(
        'type' => t('Content Delivery Network'),
        'description' => t('A content delivery network is a load-balanced network of static file
          servers that are located at various places all over the planet.'),
        'configuration_page' => 'admin/settings/cdn',
      );
      break;
    case 'url':

      // If debug mode is enabled and the user doesn't have the necessary
      // permission to access files on the CDN when debug mode is enabled,
      // return FALSE (i.e. pretend the file does not exist on the CDN).
      if ((bool) variable_get('cdn_debug_mode', 0) && !user_access('access files on CDN when in debug mode')) {
        return FALSE;
      }

      // URLs on a CDN are always absolute!
      $url = cdn_file_url($file_path);

      // If the user can access it, add this to the per-page statistics.
      if (variable_get('cdn_dev_page_stats', 0) && user_access('access per-page statistics')) {
        _cdn_devel_page_stats($file_path, !$url ? '' : $url, (bool) $url);
      }
      return $url;
      break;
  }
}

/**
 * Given a file path relative to the Drupal root directory, get the URL of the
 * corresponding file on the CDN, or the normal URL if the corresponding file
 * does not (yet) exist on the CDN.
 *
 * @param $file_path
 *   A file path relative to the Drupal root directory.
 * @param $files_synced
 *   (optional) An array of synced files, with the keys the source file paths
 *   and the values the file paths on the CDN. This parameter is used by the
 *   cdn_update_file() function for example. In normal usage, you should not
 *   need it.
 * @return
 *   The URL to the file on the CDN, if available, otherwise the normal URL.
 */
function cdn_file_url($file_path, $files_synced = array()) {
  $files_synced = !empty($files_synced) ? $files_synced : variable_get('cdn_files_synced', array());
  $file_path = cdn_clean_filepath($file_path);
  return in_array($file_path, array_keys($files_synced)) ? variable_get('cdn_url', 'yourcdn.com/') . $files_synced[$file_path] : FALSE;
}

/**
 * Alters filename or file path to make the filename unique.
 *
 * @param $file_path
 *   The path to the file, relative to the Drupal root directory.
 * @param $unique_settings
 *   The settings to generate the unique file path for the CDN. This is an
 *   array with the following structure:
 *   array(
 *     'method' => 'mtime',    // Other values: 'none', 'md5'.
 *     'where'  => 'filename', // Other values: 'common parent directory' (should be used for themes to not break URLs in CSS files, enables a new method: 'md5 of mtimes').
 *     'params' => array(),    // An array of parameters. Optional. Keys in case of 'common parent directory': 'path' and 'files'. 
 *   )
 * @return
 *   The new basename.
 */
function cdn_unique_filename($file_path, $unique_settings = array()) {
  if ($unique_settings['method'] != 'none') {

    // Generate the file version identifier that will be included in the
    // name to make sure we get a unique filename.
    switch ($unique_settings['method']) {
      case 'md5 of mtimes':
        static $md5_mtimes;
        if (!isset($md5_mtimes[$unique_settings['params']['path']])) {
          $mtimes_string = '';
          foreach ($unique_settings['params']['files'] as $file) {
            $mtimes_string .= filemtime($file);
          }
          $md5_mtimes[$unique_settings['params']['path']] = md5($mtimes_string);
        }
        $unique = $md5_mtimes[$unique_settings['params']['path']];
        break;
      case 'md5':
        $unique = md5_file($file_path);
        break;
      case 'mtime':
      default:
        $unique = filemtime($file_path);
        break;
    }
    $dirs = explode('/', $file_path);
    $basename = end($dirs);
    unset($dirs[count($dirs) - 1]);
    $path = implode('/', $dirs);
    switch ($unique_settings['where']) {
      case 'filename':
        if (($pos = strrpos($basename, '.')) !== FALSE) {
          $first = substr($basename, 0, $pos);
          $last = substr($basename, $pos, strlen($basename) - $pos);
          $basename = $first . '-' . $unique . $last;
        }
        else {
          $basename .= '-' . $unique;
        }
        break;
      case 'common parent directory':
        $path = $dirs[0];
        for ($i = 1; $i < count($dirs); $i++) {
          $path .= "/{$dirs[$i]}";
          if ($path == $unique_settings['params']['path']) {
            $path .= "/{$unique}";
          }
        }
        break;
    }
    return $path . '/' . $basename;
  }
  else {
    return $file_path;
  }
}

/**
 * Removes the leading "/", "/" or "../"'s from a path.
 *
 * @param $file_path
 *   A path.
 * @return
 *   The cleaned path.
 */
function cdn_clean_filepath($file_path) {
  return preg_replace('/^(?:(?:\\.\\.\\/)+|\\.\\/|\\/)?(.*)/', '$1', $file_path);
}

/**
 * Updates the contents of the file for CDN compatibility: it searches all
 * relative URLs and replaces it with absolute URLs to files hosted on the
 * CDN, if they're available on the CDN already, otherwise they are replaced
 * with absolute URLs to the files hosted on the local server.
 *
 * @param $file_path
 *   The path to the file, relative to the Drupal root directory.
 * @param $files_synced
 *   (optional) An array of synced files, with the keys the source file paths
 *   and the values the file paths on the server. This parameter is used by
 *   to be able to update files while the synchronization is still running,
 *   to get the new files on the CDN.
 * @return
 *   Path to the updated file (the original file will never be changed).
 */
function cdn_update_file($file_path, $files_synced = array()) {
  $contents = file_get_contents($file_path);

  // Strip surrounding apostrophes or quotes.
  $contents = preg_replace('/(?<=url\\()([\'"]{1})(.*)\\1{1}/', '\\2', $contents);

  // Remove the leading base path.
  $contents = preg_replace('/url\\(' . preg_quote(base_path(), '/.') . '([\\d\\w-_\\.\\/]+)/i', 'url(\\1', $contents);

  // Route all paths through the relative path resolving function, ignoring
  // absolute URLs.
  $contents = preg_replace_callback('/(?<=url\\()[\\d\\w-_\\.\\/]+(?=\\))/i', create_function('$matches', 'return _cdn_file_path_resolve_relative_paths($matches[0]);'), $contents);

  // Create the function body of the anonymous function.
  if (empty($files_synced)) {
    $function_body = 'return cdn_file_url($matches[0]);';
  }
  else {
    $function_body = 'return cdn_file_url($matches[0], ' . var_export($files_synced, TRUE) . ');';
  }

  // Route all paths through cdn_file_url(), ignoring absolute URLs.
  $contents = preg_replace_callback('/(?<=url\\()[\\d\\w-_\\.\\/]+(?=\\))/i', create_function('$matches', $function_body), $contents);

  // Create the CDN directory if it does not yet exist.
  file_check_directory(file_create_path('cdn'), FILE_CREATE_DIRECTORY);
  return file_save_data($contents, 'cdn/' . md5($file_path), FILE_EXISTS_REPLACE);
}

/**
 * Helper function to resolve relative file paths.
 *
 * I got the regular expression for this from:
 *   http://www.troubleshooters.com/codecorn/littperl/perlreg.htm
 * and adapted it to work with PHP.
 *
 * @param file_path
 *   A file path that possibly contains relative references (double dot).
 * @return
 *   The same file path, but with the relative references resolved.
 */
function _cdn_file_path_resolve_relative_paths($file_path) {

  // Prepend a leading slash, this allows for a simpler regexp.
  $file_path = "/" . $file_path;

  // Regular expression
  while (preg_match('/\\.\\./', $file_path)) {
    $file_path = preg_replace('/\\/[^\\/]*\\/\\.\\./', '', $file_path);
  }

  // Remove the leading slash again.
  $file_path = substr($file_path, 1);
  return $file_path;
}

Functions

Namesort descending Description
cdn_clean_filepath Removes the leading "/", "/" or "../"'s from a path.
cdn_file_server Implementation of hook_file_server().
cdn_file_url Given a file path relative to the Drupal root directory, get the URL of the corresponding file on the CDN, or the normal URL if the corresponding file does not (yet) exist on the CDN.
cdn_unique_filename Alters filename or file path to make the filename unique.
cdn_update_file Updates the contents of the file for CDN compatibility: it searches all relative URLs and replaces it with absolute URLs to files hosted on the CDN, if they're available on the CDN already, otherwise they are replaced with absolute URLs to the…
_cdn_file_path_resolve_relative_paths Helper function to resolve relative file paths.