View source
<?php
require_once 'cdn.constants.inc';
function cdn_file_url_alter(&$original_uri) {
$mode = variable_get(CDN_MODE_VARIABLE, CDN_MODE_BASIC);
$farfuture = variable_get(CDN_BASIC_FARFUTURE_VARIABLE, CDN_BASIC_FARFUTURE_DEFAULT);
$stats = variable_get(CDN_STATS_VARIABLE, FALSE) && user_access(CDN_PERM_ACCESS_STATS);
$https_support = variable_get(CDN_HTTPS_SUPPORT_VARIABLE, FALSE);
$maintenance_mode = variable_get('maintenance_mode', FALSE);
$is_https_page = cdn_request_is_https();
if (defined('MAINTENANCE_MODE')) {
return;
}
if (cdn_status_is_enabled()) {
$scheme = file_uri_scheme($original_uri);
if ($scheme && ($scheme == 'http' || $scheme == 'https') || drupal_substr($original_uri, 0, 2) == '//') {
return;
}
elseif ($scheme) {
$local_schemes = array_keys(file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL));
if (!in_array($scheme, $local_schemes)) {
return;
}
elseif ($scheme === 'private') {
return;
}
if ($wrapper = file_stream_wrapper_get_instance_by_uri($original_uri)) {
$uri = str_replace($GLOBALS['base_url'] . '/', '', $wrapper
->getExternalUrl());
}
else {
return;
}
}
else {
$uri = $original_uri;
}
if (!cdn_check_protocol()) {
return;
}
if (!cdn_check_drupal_path(current_path())) {
return;
}
if (!cdn_check_file($uri)) {
return;
}
if ($stats) {
cdn_load_include('stats');
$start = microtime(TRUE);
}
if ($mode == CDN_MODE_BASIC && $farfuture && !$maintenance_mode) {
cdn_load_include('basic.farfuture');
$uri = urldecode($uri);
if (!file_exists($uri) && !_cdn_basic_farfuture_generate_file($uri, $original_uri)) {
$path = drupal_encode_path($uri);
return;
}
$ufi = cdn_basic_farfuture_get_identifier($uri);
$uri = drupal_encode_path($uri);
$uri_before_farfuture = $uri;
$path_info = pathinfo(urldecode($uri));
$token = drupal_hmac_base64($ufi . $path_info['filename'], drupal_get_private_key() . drupal_get_hash_salt());
$uri = "cdn/farfuture/{$token}/{$ufi}/{$uri}";
}
cdn_load_include($mode == CDN_MODE_BASIC ? 'basic' : 'advanced');
$servers = $mode == CDN_MODE_BASIC ? cdn_basic_get_servers($uri) : cdn_advanced_get_servers($uri);
if (count($servers) == 0) {
$cdn_url = FALSE;
$server = FALSE;
}
elseif (count($servers) > 1 && function_exists('cdn_pick_server')) {
$picked_server = cdn_pick_server($servers);
$cdn_url = $picked_server['url'];
$server = $picked_server['server'];
}
elseif (count($servers) > 1) {
$filename = basename($servers[0]['url']);
$unique_file_id = hexdec(substr(md5($filename), 0, 5));
$choice = $unique_file_id % count($servers);
$cdn_url = $servers[$choice]['url'];
$server = $servers[$choice]['server'];
}
else {
$cdn_url = $servers[0]['url'];
$server = $servers[0]['server'];
}
if ($is_https_page && $https_support && !empty($cdn_url)) {
$cdn_url = preg_replace('/^http:/', 'https:', $cdn_url);
}
if ($stats) {
$end = microtime(TRUE);
$source_uri = $mode == CDN_MODE_BASIC && $farfuture && !$maintenance_mode ? $uri_before_farfuture : $original_uri;
_cdn_devel_page_stats($source_uri, $cdn_url, $server, $end - $start);
}
if ($cdn_url !== FALSE) {
$original_uri = $cdn_url;
}
}
}
function cdn_cdn_unique_file_identifier_info() {
return array(
'md5_hash' => array(
'label' => t('MD5 hash'),
'prefix' => 'md5',
'description' => t('MD5 hash of the file.'),
'filesystem' => TRUE,
'callback' => 'md5_file',
),
'mtime' => array(
'label' => t('Last modification time'),
'prefix' => 'mtime',
'description' => t('Last modification time of the file.'),
'filesystem' => TRUE,
'callback' => 'filemtime',
),
'perpetual' => array(
'label' => t('Perpetual'),
'prefix' => 'perpetual',
'description' => t('Perpetual files never change (or are never cached
by the browser, e.g. video files).'),
'filesystem' => FALSE,
'value' => 'forever',
),
'drupal_version' => array(
'label' => t('Drupal version'),
'prefix' => 'drupal',
'description' => t('Drupal core version — this should only be applied
to files that ship with Drupal core.'),
'filesystem' => FALSE,
'value' => VERSION,
),
'drupal_cache' => array(
'label' => t('Drupal cache'),
'prefix' => 'drupal-cache',
'description' => t('Uses the current Drupal cache ID
(<code>css_js_query_string</code>). This ID is
updated automatically whenever the Drupal cache is
flushed (e.g. when you submit the modules form). Be
aware that this can change relatively often, forcing
redownloads by your visitors.'),
'filesystem' => FALSE,
'value' => variable_get('css_js_query_string', 0),
),
'deployment_id' => array(
'label' => t('Deployment ID'),
'prefix' => 'deployment',
'description' => t('A developer-defined deployment ID. Can be an
arbitrary string or number, as long as it uniquely
identifies deployments and therefore the affected
files.<br />
Define this deployment ID in any enabled module or
in <code>settings.php</code> as the
<code>CDN_DEPLOYMENT_ID</code>
constant, and it will be picked up instantaneously.'),
'filesystem' => FALSE,
'callback' => '_cdn_ufi_deployment_id',
),
);
}
function cdn_element_info_alter(&$type) {
if (!cdn_status_is_enabled()) {
return;
}
$mode = variable_get(CDN_MODE_VARIABLE, CDN_MODE_BASIC);
if ($mode == CDN_MODE_BASIC) {
cdn_load_include('basic.css');
$type['styles']['#aggregate_callback'] = '_cdn_aggregate_css';
}
}
function cdn_css_alter(&$css) {
if (!cdn_status_is_enabled()) {
return;
}
if (!cdn_check_file('*.css')) {
return;
}
foreach (array_keys($css) as $key) {
if (is_numeric($key)) {
continue;
}
elseif (!cdn_check_file($key)) {
$css[$key]['preprocess'] = FALSE;
}
}
}
function cdn_js_alter(&$javascript) {
if (!cdn_status_is_enabled()) {
return;
}
if (!cdn_check_file('*.js')) {
return;
}
foreach (array_keys($javascript) as $key) {
if (is_numeric($key)) {
continue;
}
elseif ($key === 'settings') {
continue;
}
elseif (!cdn_check_file($key)) {
$javascript[$key]['preprocess'] = FALSE;
}
}
}
function cdn_menu() {
$items['admin/config/development/cdn'] = array(
'title' => 'CDN',
'description' => 'Configure CDN integration.',
'access arguments' => array(
CDN_PERM_ADMIN,
),
'page callback' => 'drupal_get_form',
'page arguments' => array(
'cdn_admin_general_settings_form',
),
'type' => MENU_NORMAL_ITEM,
'file' => 'cdn.admin.inc',
);
$items['admin/config/development/cdn/general'] = array(
'title' => 'General',
'description' => 'General settings.',
'access arguments' => array(
CDN_PERM_ADMIN,
),
'weight' => -10,
'type' => MENU_DEFAULT_LOCAL_TASK,
'file' => 'cdn.admin.inc',
);
$items['admin/config/development/cdn/details'] = array(
'title' => 'Details',
'access arguments' => array(
CDN_PERM_ADMIN,
),
'page callback' => 'drupal_get_form',
'page arguments' => array(
'cdn_admin_details_form',
),
'weight' => -8,
'type' => MENU_LOCAL_TASK,
'file' => 'cdn.admin.inc',
);
$items['admin/config/development/cdn/other'] = array(
'title' => 'Other',
'description' => 'Other settings.',
'access arguments' => array(
CDN_PERM_ADMIN,
),
'page callback' => 'drupal_get_form',
'page arguments' => array(
'cdn_admin_other_settings_form',
),
'weight' => -4,
'type' => MENU_LOCAL_TASK,
'file' => 'cdn.admin.inc',
);
$items['admin/cdn/touch/%'] = array(
'title' => 'Touch file',
'description' => 'Touch a file to force a resync with File Conveyor.',
'access arguments' => array(
CDN_PERM_TOUCH,
),
'page callback' => 'drupal_get_form',
'page arguments' => array(
'cdn_touch_file_form',
3,
),
'type' => MENU_CALLBACK,
'file' => 'cdn.stats.inc',
);
$items['cdn/farfuture/%/%/%menu_tail'] = array(
'title' => 'Download a far futured file',
'access callback' => TRUE,
'page callback' => 'cdn_basic_farfuture_download',
'page arguments' => array(
2,
3,
4,
),
'type' => MENU_CALLBACK,
'load arguments' => array(
'%map',
'%index',
),
'file' => 'cdn.basic.farfuture.inc',
);
$items['cdn/farfuture/reverse-proxy-test/%'] = array(
'title' => 'Far Future reverse proxy test',
'access callback' => TRUE,
'page callback' => 'cdn_basic_farfuture_reverseproxy_test',
'page arguments' => array(
3,
),
'type' => MENU_CALLBACK,
'file' => 'cdn.basic.farfuture.inc',
);
return $items;
}
function cdn_permission() {
return array(
CDN_PERM_ADMIN => array(
'title' => t('Administer CDN configuration settings'),
'restrict access' => TRUE,
),
CDN_PERM_ACCESS_STATS => array(
'title' => t('Access per-page statistics'),
),
CDN_PERM_ACCESS_TESTING => array(
'title' => t('Access files on the CDN when in testing mode'),
'description' => t('Users with this permission will get files from the
CDN when testing mode is enabled.'),
),
CDN_PERM_TOUCH => array(
'title' => t('Touch files'),
'description' => t("'Touch' files through the links provided by the\n per-page statistics. This will change the last\n modification time of the file, and depending on your\n set-up, may cause a resync of the file."),
),
);
}
function cdn_node_view_alter(&$build) {
if (!cdn_status_is_enabled()) {
return;
}
$build['#post_render'][] = 'cdn_post_render_html_alter';
}
function cdn_block_view_alter(&$data, $block) {
if (!cdn_status_is_enabled()) {
return;
}
if (isset($data['content'])) {
if (is_array($data['content']) && $data['content']) {
$data['content']['#post_render'][] = 'cdn_post_render_html_alter';
}
elseif (is_string($data['content'])) {
$data['content'] = cdn_post_render_html_alter($data['content']);
}
}
}
function cdn_ctools_render_alter(&$info, $page, $context) {
if (!cdn_status_is_enabled()) {
return;
}
if ($context['task']['name'] === 'node_view' && !empty($info['content']) && is_string($info['content'])) {
$info['content'] = cdn_post_render_html_alter($info['content']);
}
}
function cdn_theme() {
return array(
'cdn_page_stats' => array(
'file' => 'theme.inc',
'variables' => array(
'file_count' => NULL,
'cdn_file_count' => NULL,
'synced_files_per_server_count' => NULL,
'total_time' => NULL,
'synced_files' => NULL,
'unsynced_files' => NULL,
),
),
'cdn_page_stats_file_link' => array(
'file' => 'theme.inc',
'variables' => array(
'file' => NULL,
'absolute_path' => NULL,
'synced' => NULL,
'cdn_url' => NULL,
'server' => NULL,
),
),
);
}
function cdn_html_head_alter(&$head_elements) {
if (!cdn_status_is_enabled()) {
return;
}
$domains = cdn_get_domains();
$markup = null;
$ie_markup = null;
if (count($domains)) {
$head_elements['cdn_dns_prefetch_meta'] = array(
'#type' => 'html_tag',
'#tag' => 'meta',
'#attributes' => array(
'http-equiv' => 'x-dns-prefetch-control',
'content' => 'on',
),
'#weight' => -900.9999,
);
foreach ($domains as $domain) {
$element = array(
'#tag' => 'link',
'#attributes' => array(
'rel' => 'dns-prefetch',
'href' => '//' . $domain,
),
);
$link_el = theme('html_tag', array(
'element' => $element,
));
$markup .= $link_el;
$ie_markup .= preg_replace('/rel="dns-prefetch"/', 'rel="prefetch"', $link_el);
}
$markup .= '<!--[if IE 9]>' . PHP_EOL;
$markup .= $ie_markup;
$markup .= '<![endif]-->' . PHP_EOL;
$head_elements['cdn_dns_prefetch_block'] = array(
'#type' => 'markup',
'#markup' => $markup,
'#weight' => -900,
);
}
}
function cdn_boot() {
if (variable_get(CDN_STATUS_VARIABLE, CDN_DISABLED) === CDN_DISABLED) {
return;
}
require_once DRUPAL_ROOT . '/includes/common.inc';
require_once DRUPAL_ROOT . '/' . variable_get('path_inc', 'includes/path.inc');
require_once DRUPAL_ROOT . '/includes/unicode.inc';
$redirect_url = FALSE;
$seo_redirect_callback = variable_get(CDN_SEO_REDIRECT_CALLBACK_VARIABLE, CDN_SEO_REDIRECT_CALLBACK_DEFAULT);
if (function_exists($seo_redirect_callback)) {
$redirect_url = $seo_redirect_callback(current_path());
}
if ($redirect_url !== FALSE) {
header('HTTP/1.0 301 Moved Permanently');
header('Link: <' . $redirect_url . '>; rel="canonical"');
header('Location: ' . $redirect_url);
header('Drupal-CDN-Redirect: duplicate content prevention');
exit;
}
}
function cdn_init() {
if (!cdn_status_is_enabled()) {
return;
}
if (variable_get(CDN_STATS_VARIABLE, FALSE) && user_access(CDN_PERM_ACCESS_STATS)) {
drupal_add_css(drupal_get_path('module', 'cdn') . '/cdn.css', array(
'every_page' => TRUE,
));
}
}
function cdn_exit($destination = NULL) {
if (!function_exists('_cdn_devel_page_stats')) {
return;
}
if (function_exists('drupal_get_http_header') && !strstr(drupal_get_http_header('Content-Type'), 'html')) {
return;
}
if (!$destination && variable_get(CDN_STATUS_VARIABLE, CDN_DISABLED) != CDN_DISABLED && variable_get(CDN_STATS_VARIABLE, FALSE) && user_access(CDN_PERM_ACCESS_STATS)) {
list($file_count, $cdn_file_count, $synced_files_per_server_count, $total_time, $synced_files, $unsynced_files, ) = _cdn_devel_page_stats();
print theme('cdn_page_stats', array(
'file_count' => $file_count,
'cdn_file_count' => $cdn_file_count,
'synced_files_per_server_count' => $synced_files_per_server_count,
'total_time' => $total_time,
'synced_files' => $synced_files,
'unsynced_files' => $unsynced_files,
));
}
}
function cdn_flush_caches() {
cdn_get_blacklist(TRUE);
variable_del('cdn_css_cache_files_http');
file_scan_directory('public://cdn/css/http', '/.*/', array(
'callback' => 'drupal_delete_file_if_stale',
));
variable_del('cdn_css_cache_files_https');
file_scan_directory('public://cdn/css/https', '/.*/', array(
'callback' => 'drupal_delete_file_if_stale',
));
}
function cdn_cdn_blacklist() {
$blacklist = array();
if (module_exists('wysiwyg')) {
foreach (wysiwyg_get_all_editors() as $editor) {
if (!$editor['installed']) {
continue;
}
$blacklist[] = $editor['library path'] . '/*';
}
}
$blacklist[] = 'image_captcha*';
$blacklist[] = "*simpletest/verbose/*";
return $blacklist;
}
function cdn_cacheaudit() {
$results = array(
array(
'Settings',
'Value',
),
);
$status = variable_get(CDN_STATUS_VARIABLE, CDN_DISABLED);
if ($status == CDN_ENABLED) {
$status_value = 'enabled';
}
elseif ($status == CDN_TESTING) {
$status_value = 'testing';
}
else {
$status_value = 'disabled';
}
$results[] = array(
'Status',
$status_value,
);
$mode = variable_get(CDN_MODE_VARIABLE, CDN_MODE_BASIC);
$results[] = array(
'Mode',
$mode ? 'Origin Pull' : 'File Conveyor',
);
if ($mode == CDN_MODE_BASIC) {
cdn_load_include('basic');
$results[] = array(
' mappings',
count(_cdn_basic_parse_raw_mapping(cdn_basic_get_mapping())),
);
$results[] = array(
' Far Future expiration',
variable_get(CDN_BASIC_FARFUTURE_VARIABLE, CDN_BASIC_FARFUTURE_DEFAULT),
);
}
$results[] = array(
'Domains',
count(cdn_get_domains()),
);
return array(
'cdn' => $results,
);
}
function cdn_get_domains() {
$domains = array();
if (variable_get(CDN_MODE_VARIABLE, CDN_MODE_BASIC) == CDN_MODE_BASIC) {
cdn_load_include('basic');
$mapping = cdn_basic_get_mapping();
$lines = preg_split("/[\n\r]+/", $mapping, -1, PREG_SPLIT_NO_EMPTY);
foreach ($lines as $line) {
$line = trim($line);
if (empty($line)) {
continue;
}
$parts = explode('|', $line);
foreach (explode(' ', $parts[0]) as $part) {
$part = trim($part);
if (strpos($part, '//') === 0) {
$part = 'http:' . $part;
}
$domains[] = parse_url($part, PHP_URL_HOST);
}
}
}
elseif (variable_get(CDN_MODE_VARIABLE, CDN_MODE_BASIC) == CDN_MODE_ADVANCED) {
cdn_load_include('advanced');
$db = _cdn_advanced_get_db_connection();
if (!$db) {
return array();
}
$sql = "SELECT url\n FROM synced_files\n GROUP BY server";
$stmt = $db
->prepare($sql);
$stmt
->execute();
$rows = $stmt
->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
$domains[] = parse_url($row['url'], PHP_URL_HOST);
}
}
return array_unique($domains);
}
function cdn_get_blacklist($reset = FALSE) {
static $blacklist = NULL;
if (is_null($blacklist) || $reset) {
$cache = cache_get('cdn_blacklist');
if (!isset($cache->data) || $reset) {
$blacklist = module_invoke_all('cdn_blacklist');
drupal_alter('cdn_blacklist', $blacklist);
$blacklist = array_unique($blacklist);
$blacklist = implode("\n", $blacklist);
cache_set('cdn_blacklist', $blacklist, 'cache', CACHE_TEMPORARY);
}
else {
$blacklist = $cache->data;
}
}
return $blacklist;
}
function cdn_serve_from_https() {
return variable_get(CDN_HTTPS_SUPPORT_VARIABLE, FALSE) && cdn_request_is_https();
}
function cdn_request_is_https() {
return isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' || isset($_SERVER['HTTP_X_FORWARDED_PROTOCOL']) && $_SERVER['HTTP_X_FORWARDED_PROTOCOL'] == 'https';
}
function cdn_status_is_enabled() {
$status = variable_get(CDN_STATUS_VARIABLE, CDN_DISABLED);
return $status == CDN_ENABLED || $status == CDN_TESTING && user_access(CDN_PERM_ACCESS_TESTING);
}
function cdn_check_protocol() {
$https_support = variable_get(CDN_HTTPS_SUPPORT_VARIABLE, FALSE);
if (cdn_request_is_https() && !$https_support) {
return FALSE;
}
return TRUE;
}
function cdn_check_drupal_path($path) {
global $user;
$blacklist = variable_get(CDN_EXCEPTION_DRUPAL_PATH_BLACKLIST_VARIABLE, CDN_EXCEPTION_DRUPAL_PATH_BLACKLIST_DEFAULT);
$auth_blacklist = variable_get(CDN_EXCEPTION_AUTH_USERS_BLACKLIST_VARIABLE, CDN_EXCEPTION_AUTH_USERS_BLACKLIST_DEFAULT);
if (drupal_match_path($path, $blacklist)) {
return FALSE;
}
if ($user->uid > 0 && drupal_match_path($path, $auth_blacklist)) {
return FALSE;
}
return TRUE;
}
function cdn_check_file($uri) {
$file_path_blacklist = variable_get(CDN_EXCEPTION_FILE_PATH_BLACKLIST_VARIABLE, CDN_EXCEPTION_FILE_PATH_BLACKLIST_DEFAULT);
$file_path_whitelist = variable_get(CDN_EXCEPTION_FILE_PATH_WHITELIST_VARIABLE, CDN_EXCEPTION_FILE_PATH_WHITELIST_DEFAULT);
$module_blacklist = cdn_get_blacklist();
if ((drupal_match_path($uri, $file_path_blacklist) || drupal_match_path($uri, $module_blacklist)) && !drupal_match_path($uri, $file_path_whitelist)) {
return FALSE;
}
return TRUE;
}
function cdn_load_include($basename) {
module_load_include('inc', 'cdn', "cdn.{$basename}");
}
function cdn_post_render_html_alter($html, $elements = array()) {
cdn_load_include('fallback');
cdn_html_alter_image_urls($html);
cdn_html_alter_anchor_urls($html);
return $html;
}
function _cdn_ufi_deployment_id($path) {
return CDN_DEPLOYMENT_ID;
}
function _cdn_seo_should_redirect($path) {
if (variable_get(CDN_SEO_REDIRECT_VARIABLE, CDN_SEO_REDIRECT_DEFAULT)) {
$forbidden_extensions = variable_get(CDN_SEO_FORBIDDEN_EXTENSIONS_VARIABLE, CDN_SEO_FORBIDDEN_EXTENSIONS_DEFAULT);
$extension = drupal_strtolower(pathinfo($path, PATHINFO_EXTENSION));
if (!empty($extension) && !in_array($extension, explode("\n", $forbidden_extensions))) {
return FALSE;
}
if (isset($_SERVER['HTTP_USER_AGENT'])) {
$ua = drupal_strtolower($_SERVER['HTTP_USER_AGENT']);
$cdn_user_agents = explode("\n", drupal_strtolower(variable_get(CDN_SEO_USER_AGENTS_VARIABLE, CDN_SEO_USER_AGENTS_DEFAULT)));
foreach ($cdn_user_agents as $cdn_ua) {
if (strstr($ua, trim($cdn_ua))) {
return url($path, array(
'absolute' => TRUE,
));
}
}
}
}
return FALSE;
}