uid > 0 && drupal_match_path($path, $auth_users_blacklist)) {
return;
}
if ($stats) {
require_once(drupal_get_path('module', 'cdn') . '/cdn.stats.inc');
}
if ($status == CDN_ENABLED || ($status == CDN_TESTING && user_access(CDN_PERM_ACCESS_TESTING))) {
if ($stats) {
$start = microtime();
}
// Depending on the mode, use a different function to get the servers on
// which the file is available.
$servers = ($mode == CDN_MODE_BASIC) ? cdn_basic_get_servers($path) : cdn_advanced_get_servers($path);
// The file is not available on any server.
if (count($servers) == 0) {
$cdn_url = FALSE;
$server = FALSE;
}
// If the file is available from multiple servers, then allow a special
// function to pick the desired server. The decision can be made on any
// desired criteria: user's location, user's role, current date …
elseif (count($servers) > 1 && function_exists('cdn_pick_server')) {
$picked_server = cdn_pick_server($servers);
$cdn_url = $picked_server['url'];
$server = $picked_server['server'];
}
// The file is available from at least one server, simply pick the first.
else {
$cdn_url = $servers[0]['url'];
$server = $servers[0]['server'];
}
// If the current page is being served via HTTPS, and the CDN supports
// HTTPS, then use the HTTPS file URL.
if ($is_https_page && $https_support) {
$cdn_url = preg_replace('/^http/', 'https', $cdn_url);
}
// If the user can access it, add this to the per-page statistics.
if ($stats) {
$end = microtime();
_cdn_devel_page_stats($path, $cdn_url, $server, $end - $start);
}
// Override the path with the corresponding CDN URL, *if* the file is
// available on the CDN (it may only be not available in advanced mode).
if ($cdn_url !== FALSE) {
$path = $cdn_url;
}
}
}
/**
* Implementation of hook_menu().
*/
function cdn_menu() {
$items['admin/settings/cdn'] = array(
'title' => 'CDN',
'description' => 'Configure CDN integration.',
'access arguments' => array('administer site configuration'),
'page callback' => 'drupal_get_form',
'page arguments' => array('cdn_admin_general_settings_form'),
'type' => MENU_NORMAL_ITEM,
'file' => 'cdn.admin.inc',
);
$items['admin/settings/cdn/general'] = array(
'title' => 'General',
'description' => 'General settings.',
'access arguments' => array('administer site configuration'),
'weight' => -10,
'type' => MENU_DEFAULT_LOCAL_TASK,
'file' => 'cdn.admin.inc',
);
$items['admin/settings/cdn/details'] = array(
'title' => 'Details',
'access arguments' => array('administer site configuration'),
'page callback' => 'drupal_get_form',
'page arguments' => array('cdn_admin_details_form'),
'weight' => -8,
'type' => MENU_LOCAL_TASK,
'file' => 'cdn.admin.inc',
);
$items['admin/settings/cdn/other'] = array(
'title' => 'Other',
'description' => 'Other settings.',
'access arguments' => array('administer site configuration'),
'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',
);
return $items;
}
/**
* Implementation of hook_perm().
*/
function cdn_perm() {
return array(CDN_PERM_ACCESS_STATS, CDN_PERM_ACCESS_TESTING, CDN_PERM_TOUCH);
}
/**
* Implementation of hook_form_alter().
*/
function cdn_form_system_performance_settings_alter(&$form, &$form_state) {
// Add an id to the "Clear cached data" fieldset in the
// "system_performance_settings" form, so we can link to it directly.
$form['clear_cache']['#attributes']['id'] = 'clear-cache';
}
/**
* Implementation of hook_theme().
*/
function cdn_theme() {
return array(
'cdn_page_stats' => array(
'file' => 'theme.inc',
'arguments' => 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',
'arguments' => array('file' => NULL),
),
);
}
/**
* Implementation of hook_init().
*/
function cdn_init() {
// When the fallback mechanism to alter file URLs (i.e. using the theme
// layer) is enabled, load cdn.fallback.inc. But only load it when the
// CDN integration is enabled or if testing mode is enabled and the current
// user has access to that.
$status = variable_get(CDN_STATUS_VARIABLE, CDN_DISABLED);
if (($status == CDN_ENABLED || ($status == CDN_TESTING && user_access(CDN_PERM_ACCESS_TESTING)))
&& variable_get(CDN_THEME_LAYER_FALLBACK_VARIABLE, FALSE) == TRUE
) {
require_once drupal_get_path('module', 'cdn') . '/cdn.fallback.inc';
}
// When per-page statistics are enabled, add the CSS that will be used to
// make these statistics more usable.
if (variable_get(CDN_STATUS_VARIABLE, CDN_DISABLED) != CDN_DISABLED
&& variable_get(CDN_STATS_VARIABLE, FALSE)
&& user_access(CDN_PERM_ACCESS_STATS)
) {
drupal_add_css(drupal_get_path('module', 'cdn') . '/cdn.css');
}
}
/**
* Implementation of hook_exit().
*/
function cdn_exit($destination = NULL) {
// When the _cdn_devel_page_stats() function does not exist, there are no
// stats to show, hence we can return immediately.
// This can happen when the stats are disabled (for the current user or
// entirely), or when a cached page is being served.
if (!function_exists('_cdn_devel_page_stats')) {
return;
}
// Try not to break non-HTML pages.
if (function_exists('drupal_get_headers') && !strstr(drupal_get_headers(), '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',
$file_count,
$cdn_file_count,
$synced_files_per_server_count,
$total_time,
$synced_files,
$unsynced_files
);
}
}
/**
* Implementation of hook_requirements().
*/
function cdn_requirements($phase) {
$requirements = array();
$t = get_t();
switch ($phase) {
case 'install' :
case 'runtime' :
require_once drupal_get_path('module', 'cdn') . '/cdn.requirements.inc';
// Detect whether the fallback should be enabled or not.
_cdn_requirements_detect_fallback();
$integration_method = _cdn_requirements_get_integration_mechanism();
// Only check if the CDN Drupal core patch is properly applied if the
// integration methis is *not* 'pressflow'. Show as a warning when the
// integration method is 'fallback'.
if (!_cdn_requirements_is_pressflow()) {
_cdn_requirements_generate_requirement_for_patch(
$requirements,
'core',
($integration_method == 'fallback') ? $t('CDN — Drupal core patch (for complete CDN coverage)') : $t('CDN — Drupal core patch'),
REQUIREMENT_WARNING
);
}
// Only check if the CDN ImageCache patch is properly applied if the
// integration method is *not* 'fallback' (thus either 'pressflow' or
// 'core patch') *and* when the ImageCache module is enabled.
if ($integration_method != 'fallback' && module_exists('imagecache')) {
// Check if either of the patches is applied, then remove the
// requirements for the last one if neither patch is applied.
_cdn_requirements_generate_requirement_for_patch(
$requirements,
'imagecache',
$t('CDN — ImageCache patch')
);
_cdn_requirements_generate_requirement_for_patch(
$requirements,
'imagecache_6_2',
$t('CDN — ImageCache patch')
);
if ($requirements['cdn_imagecache_patch']['value'] == $requirements['cdn_imagecache_6_2_patch']['value']) {
unset($requirements['cdn_imagecache_6_2_patch']);
}
}
// CDN status.
$status = variable_get(CDN_STATUS_VARIABLE, CDN_DISABLED);
$mode = variable_get(CDN_MODE_VARIABLE, CDN_MODE_BASIC);
$requirements['cdn']['title'] = $t('CDN');
// Set the basic info: disabled/testing/enabled.
if ($status == CDN_DISABLED) {
$requirements['cdn'] += array(
'description' => $t('CDN integration is disabled for all users.'),
'severity' => REQUIREMENT_WARNING,
);
$requirements['cdn']['value'] = $t('Disabled');
}
elseif ($status == CDN_TESTING) {
$requirements['cdn'] += array(
'description' => $t(
'CDN integration is only enabled for users with the
%cdn-testing-mode-permission permission',
array('%cdn-testing-mode-permission' => CDN_PERM_ACCESS_TESTING)
),
'severity' => REQUIREMENT_WARNING,
);
$requirements['cdn']['value'] = $t('Testing');
}
else {
$requirements['cdn'] += array(
'description' => $t('CDN integration is enabled for all users.'),
'severity' => REQUIREMENT_OK,
);
$requirements['cdn']['value'] = $t('Enabled');
}
// When enabled, add more information.
if ($status != CDN_DISABLED) {
if ($mode == CDN_MODE_BASIC) {
$requirements['cdn']['value'] .= ' – '. t('Origin Pull mode');
}
else {
$requirements['cdn']['value'] .= ' – '. t('File Conveyor mode');
$items = array();
$synced_files_db = variable_get(CDN_ADVANCED_SYNCED_FILES_DB_VARIABLE, FALSE);
if ($synced_files_db !== FALSE) {
$daemon_pid_file = str_replace(CDN_DAEMON_SYNCED_FILES_DB, CDN_DAEMON_PID_FILE, $synced_files_db);
$persistent_data_db = str_replace(CDN_DAEMON_SYNCED_FILES_DB, CDN_DAEMON_PERSISTENT_DATA_DB, $synced_files_db);
$drupal_root_path = variable_get(CDN_DRUPAL_ROOT_VARIABLE, realpath('.'));
$synced_files_db_exists = file_exists($synced_files_db);
$synced_files_db_readable = @fopen($synced_files_db, 'r');
$persistent_data_db_exists = file_exists($persistent_data_db);
$persistent_data_db_readable = @fopen($persistent_data_db, 'r');
$daemon_pid_file_exists = file_exists($daemon_pid_file);
$db = _cdn_advanced_get_db_connection();
if ($db !== FALSE) {
$input_file_mask = $drupal_root_path .'%';
$sql = "SELECT COUNT(*) AS count, server
FROM synced_files
WHERE input_file LIKE :input_file
GROUP BY server";
$stmt = $db->prepare($sql);
$stmt->bindParam(':input_file', $input_file_mask, PDO::PARAM_STR);
$stmt->execute();
$result = $stmt->fetchAll();
$synced_file_stats = $result;
}
if ($persistent_data_db_exists && $persistent_data_db_readable) {
try {
$db = new PDO('sqlite:' . $persistent_data_db);
} catch (PDOException $e) {
$items[] = t("Could not connect to persistent data database.");
}
if ($db !== FALSE) {
$sql = "SELECT COUNT(*)
FROM pipeline_queue";
$stmt = $db->prepare($sql);
$stmt->execute();
$result = $stmt->fetchAll();
$pipeline_queue_count = $result[0][0];
$sql = "SELECT COUNT(*)
FROM pipeline_list";
$stmt = $db->prepare($sql);
$stmt->execute();
$result = $stmt->fetchAll();
$pipeline_list_count = $result[0][0];
}
}
$items[] = ($synced_files_db_exists) ? t('The synced files database exists.') : t("The synced files database doesn't exist.");
$items[] = ($synced_files_db_readable) ? t('The synced files database is readable.') : t("The synced files database isn't readable.");
if ($synced_files_db_readable) {
foreach ($synced_file_stats as $row) {
$items[] = t('!synced-file-count files have been synced to the %server server.', array('!synced-file-count' => $row['count'], '%server' => $row['server']));
}
}
else {
$items[] = t("Number of synced files is unknown.");
}
$items[] = ($daemon_pid_file_exists) ? t('File Conveyor is currently running.') : '' . t('File Conveyor is currently not running.') . '';
if (isset($pipeline_queue_count)) {
$items[] = t("!pipeline-queue-count files are waiting to be synced.", array('!pipeline-queue-count' => $pipeline_queue_count));
$items[] = t("!pipeline-list-count files are currently being synced.", array('!pipeline-list-count' => $pipeline_list_count));
}
// If either of these 3 checks failed, mark this requirement's
// severity as being an error.
if (!($synced_files_db_exists && $synced_files_db_readable && $daemon_pid_file_exists)) {
$requirements['cdn']['severity'] = REQUIREMENT_ERROR;
}
}
else {
$items[] = t('The synced files database setting has not yet been configured.');
$requirements['cdn']['severity'] = REQUIREMENT_ERROR;
}
$requirements['cdn']['description'] .= '
' . theme('item_list', $items);
}
}
// Finally, add very brief info about the integration mechanism in use.
switch ($integration_method) {
case 'fallback':
$requirements['cdn']['value'] .= ' — ' . $t('Fallback mechanism');
$requirements['cdn']['description'] .= '
' . t('If you want complete CDN coverage, you should either apply the included Drupal core patch or switch to Pressflow.');
break;
case 'pressflow':
$requirements['cdn']['value'] .= ' — ' . $t('Pressflow');
break;
case 'core patch':
$requirements['cdn']['value'] .= ' — ' . $t('Core patch');
break;
}
}
return $requirements;
}
//----------------------------------------------------------------------------
// Public functions.
/**
* Gets the servers on which a file is available when basic mode is enabled.
*
* @param $path
* The path to get the servers for.
*/
function cdn_basic_get_servers($path) {
static $mapping;
$servers = array();
// We only need to parse the textual CDN mapping once into a lookup table.
if (!isset($mapping)) {
$mapping = _cdn_basic_parse_raw_mapping(variable_get(CDN_BASIC_MAPPING_VARIABLE, ''));
}
// Parse the file extension from the given path.
$file_extension = pathinfo($path, PATHINFO_EXTENSION);
// Based on the file extension, determine which key should be used to find
// the CDN URLs in the mapping lookup table, if any.
if (array_key_exists($file_extension, $mapping)) {
$key = $file_extension;
}
elseif (array_key_exists('*', $mapping)) {
$key = '*';
}
else {
$key = NULL;
}
// If there is a key to look up the CDN URLs in the mapping lookup table,
// then build the list of servers.
if (isset($key)) {
$base_path = base_path();
foreach ($mapping[$key] as $cdn_url) {
$servers[] = array(
'server' => $cdn_url,
'url' => $cdn_url . $base_path . $path,
);
}
}
return $servers;
}
/**
* Parse the raw (textual) mapping into a lookup table, where the key is the
* file extension and the value is a list of CDN URLs that serve the file.
*
* @param $mapping_raw
* A raw (textual) mapping.
* @return
* The corresponding mapping lookup table.
*/
function _cdn_basic_parse_raw_mapping($mapping_raw) {
$mapping = array();
if (!empty($mapping_raw)) {
$lines = preg_split("/[\n\r]+/", $mapping_raw, -1, PREG_SPLIT_NO_EMPTY);
foreach ($lines as $line) {
// Parse this line. It may or may not limit the CDN URL to a list of
// file extensions.
if (strpos($line, '|') !== FALSE) {
$parts = explode('|', $line);
$cdn_url = rtrim(trim($parts[0]), '/'); // Remove whitespace and a trailing slash.
$extensions = explode(' ', trim(str_replace('.', '', $parts[1]))); // Remove periods, whitespace and split on ' '.
}
else {
$cdn_url = trim($line);
$extensions = array('*'); // Use the asterisk as a wildcard.
}
// Create the mapping lookup table.
foreach ($extensions as $extension) {
$mapping[$extension][] = $cdn_url;
}
}
}
return $mapping;
}
/**
* Gets the servers on which a file is available when advanced mode is enabled.
*
* @param $path
* The path to get the servers for.
*/
function cdn_advanced_get_servers($path) {
$db = _cdn_advanced_get_db_connection();
// In case no connection to the database could be made, pretend the file was
// not found in the synced files database.
if (!$db) {
return array();
}
// Get the real path to the file (resolves symbolic links).
$input_file = realpath('./' . $path);
// Retrieve the URLs of the file on the CDN.
$sql = "SELECT url, server
FROM synced_files
WHERE input_file = :input_file";
$stmt = $db->prepare($sql);
$stmt->bindParam(':input_file', $input_file, PDO::PARAM_STR);
$stmt->execute();
$servers = $stmt->fetchAll(PDO::FETCH_ASSOC);
return $servers;
}
/**
* Get all domains from which files might be served. This information is
* necessary for some modules, e.g. Boost.
*
* @return
* An array of domain names.
*/
function cdn_get_domains() {
$domains = array();
// Origin Pull mode domains.
if (variable_get(CDN_MODE_VARIABLE, FALSE) == CDN_MODE_BASIC) {
$mapping = variable_get(CDN_BASIC_MAPPING_VARIABLE, '');
$lines = preg_split("/[\n\r]+/", $mapping, -1, PREG_SPLIT_NO_EMPTY);
foreach ($lines as $line) {
// Parse this line. It may or may not limit the CDN URL to a list of
// file extensions.
if (strpos($line, '|') !== FALSE) {
$parts = explode('|', $line);
$domains[] = parse_url(trim($parts[0]), PHP_URL_HOST);
}
else {
$domains[] = parse_url(trim($line), PHP_URL_HOST);
}
}
}
// File Conveyor mode domains.
elseif (variable_get(CDN_MODE_VARIABLE, FALSE) == CDN_MODE_ADVANCED) {
$db = _cdn_advanced_get_db_connection();
// In case no connection to the database could be made, pretend no
// domains are being used.
if (!$db) {
return array();
}
// Retrieve all unique domains (by retrieving one URL per server) and then
// parsing the domain names in those URLs.
$sql = "SELECT url
FROM synced_files
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);
}
//----------------------------------------------------------------------------
// Private functions.
/**
* Get a connection to the database. The resulting PDO object is statically
* cached.
*
* @return
* A database connection (through PDO), or FALSE in case of failure.
*/
function _cdn_advanced_get_db_connection() {
static $db;
$synced_files_db = variable_get(CDN_ADVANCED_SYNCED_FILES_DB_VARIABLE, FALSE);
if ($synced_files_db === FALSE || !file_exists($synced_files_db) || filesize($synced_files_db) == 0) {
$db = FALSE;
}
elseif (!isset($db)) {
try {
$db = new PDO('sqlite:' . variable_get(CDN_ADVANCED_SYNCED_FILES_DB_VARIABLE, ''));
} catch (PDOException $e) {
watchdog('cdn', "Could not open synced files DB: %error.", array('%error' => $e));
$db = FALSE;
}
}
return $db;
}
/**
* Helper function to evaluate CDN_PICK_SERVER_PHP_CODE_VARIABLE, when that is
* being used instead of an actual cdn_pick_server() function.
*
* @param $php_code
* The PHP code to be evaluated.
* @param $servers_for_file
* The $servers_for_file parameter, as it will be available to the eval()'d
* $php_code. This variable is not passed by reference, hence it will not be
* changed outside the scope of this function.
*/
function _cdn_eval_pick_server($php_code, $servers_for_file) {
ob_start();
$result = @eval($php_code);
ob_end_clean();
return $result;
}