View source
<?php
define('S3FS_VERSION', '7.x-1.4');
class S3fsException extends Exception {
}
function s3fs_stream_wrappers() {
return array(
's3' => array(
'name' => 'S3 File System',
'class' => 'S3fsStreamWrapper',
'description' => t('Amazon Simple Storage Service'),
),
);
}
function s3fs_libraries_info() {
return array(
'awssdk2' => array(
'title' => 'AWS SDK for PHP',
'vendor url' => 'http://docs.aws.amazon.com/aws-sdk-php/guide/latest/index.html',
'download url' => 'https://github.com/aws/aws-sdk-php/releases/download/2.4.10/aws.zip',
'version arguments' => array(
'file' => 'Aws/Common/Aws.php',
'pattern' => "/const VERSION = '(.*)';/",
'lines' => 200,
),
'files' => array(
'php' => array(
'aws-autoloader.php',
),
),
),
);
}
function s3fs_menu() {
$items = array();
$items['admin/config/media/s3fs'] = array(
'title' => 'S3 File System',
'description' => 'Configure S3 File System.',
'page callback' => 'drupal_get_form',
'page arguments' => array(
's3fs_settings',
),
'access arguments' => array(
'administer s3fs',
),
'file' => 's3fs.admin.inc',
'type' => MENU_NORMAL_ITEM,
);
$items['admin/config/media/s3fs/settings'] = array(
'title' => 'Settings',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => 0,
);
$items['admin/config/media/s3fs/actions'] = array(
'title' => 'Actions',
'description' => 'Actions for S3 File System.',
'page callback' => 'drupal_get_form',
'page arguments' => array(
's3fs_actions',
),
'access arguments' => array(
'administer s3fs',
),
'file' => 's3fs.admin.inc',
'type' => MENU_LOCAL_TASK,
'weight' => 10,
);
$items['s3/files/styles/%image_style'] = array(
'title' => 'Generate image style in S3',
'page callback' => '_s3fs_image_style_deliver',
'page arguments' => array(
3,
),
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
return $items;
}
function s3fs_permission() {
return array(
'administer s3fs' => array(
'title' => t('Administer S3 File System'),
),
);
}
function s3fs_help($path, $arg) {
$actions = 'admin/config/media/s3fs/actions';
$settings = 'admin/config/media/s3fs';
if ($path == $settings) {
$msg = t('To perform actions, such as refreshing the metadata cache, visit the !link.', array(
'!link' => l(t('actions page'), $actions),
));
return "<p>{$msg}</p>";
}
elseif ($path == $actions) {
$msg = t('These are the actions that you can perform upon S3 File System.');
$msg .= '<br>' . t('To change your settings, visit the !link.', array(
'!link' => l(t('settings page'), $settings),
));
return "<p>{$msg}</p>";
}
}
function _s3fs_image_style_deliver() {
$args = func_get_args();
$style = array_shift($args);
$scheme = array_shift($args);
$filename = implode('/', $args);
$valid = !empty($style);
if (!variable_get('image_allow_insecure_derivatives', FALSE) || strpos(ltrim($filename, '\\/'), 'styles/') === 0) {
$valid = $valid && isset($_GET[IMAGE_DERIVATIVE_TOKEN]) && $_GET[IMAGE_DERIVATIVE_TOKEN] === image_style_path_token($style['name'], "{$scheme}://{$filename}");
}
if (!$valid) {
return MENU_ACCESS_DENIED;
}
$image_uri = "{$scheme}://{$filename}";
$derivative_uri = image_style_path($style['name'], $image_uri);
if (!is_file($image_uri)) {
watchdog('s3fs', 'Source image at %source_image_path not found while trying to generate derivative image at %derivative_path.', array(
'%source_image_path' => $image_uri,
'%derivative_path' => $derivative_uri,
));
return MENU_NOT_FOUND;
}
$lock_name = '_s3fs_image_style_deliver:' . $style['name'] . ':' . drupal_hash_base64($image_uri);
if (!file_exists($derivative_uri)) {
$lock_acquired = lock_acquire($lock_name);
if (!$lock_acquired) {
drupal_add_http_header('Status', '503 Service Unavailable');
drupal_add_http_header('Content-Type', 'text/html; charset=utf-8');
drupal_add_http_header('Retry-After', 3);
print t('Image generation in progress. Try again shortly.');
drupal_exit();
}
}
$success = file_exists($derivative_uri);
if (!$success) {
$success = image_style_create_derivative($style, $image_uri, $derivative_uri) && file_stream_wrapper_get_instance_by_scheme('s3')
->waitUntilFileExists($derivative_uri);
}
if (!empty($lock_acquired)) {
lock_release($lock_name);
}
if ($success) {
drupal_goto(file_create_url($derivative_uri));
}
else {
watchdog('S3 File System', 'Unable to generate an image derivative at %path.', array(
'%path' => $derivative_uri,
));
drupal_add_http_header('Status', '500 Internal Server Error');
drupal_add_http_header('Content-Type', 'text/html; charset=utf-8');
print t('Error generating image.');
drupal_exit();
}
}
function _s3fs_validate_config($config) {
if (!empty($config['use_customhost']) && empty($config['hostname'])) {
form_set_error('s3fs_hostname', 'You must specify a Hostname to use the Custom Host feature.');
return FALSE;
}
if (!empty($config['use_cname']) && empty($config['domain'])) {
form_set_error('s3fs_domain', 'You must specify a CDN Domain Name to use the CNAME feature.');
return FALSE;
}
try {
$s3 = _s3fs_get_amazons3_client($config);
} catch (S3fsException $e) {
form_set_error('s3fs_bucket', $e
->getMessage());
return FALSE;
}
try {
$s3
->listObjects(array(
'Bucket' => $config['bucket'],
));
} catch (Aws\S3\Exception\InvalidAccessKeyIdException $e) {
form_set_error('', t('The Access Key in your AWS credentials is invalid.'));
return FALSE;
} catch (Aws\S3\Exception\SignatureDoesNotMatchException $e) {
form_set_error('', t('The Secret Key in your AWS credentials is invalid.'));
return FALSE;
} catch (Aws\S3\Exception\NoSuchBucketException $e) {
form_set_error('s3fs_bucket', t('The specified bucket does not exist.'));
return FALSE;
} catch (Aws\S3\Exception\PermanentRedirectException $e) {
form_set_error('s3fs_region', t('This bucket exists, but it is not in the specified region.'));
return FALSE;
} catch (Exception $e) {
form_set_error('s3fs_bucket', t('An unexpected %exception occured, with the following error message:<br>%error', array(
'%exception' => get_class($e),
'%error' => $e
->getMessage(),
)));
return FALSE;
}
return TRUE;
}
function _s3fs_refresh_cache($config) {
if (!_s3fs_validate_config($config)) {
form_set_error('s3fs_refresh_cache][refresh', t('Unable to validate S3 configuration settings.'));
return;
}
$s3 = _s3fs_get_amazons3_client($config);
$file_metadata_list = array();
$iterator_args = array(
'Bucket' => $config['bucket'],
);
if (!empty($config['prefix'])) {
$iterator_args['Prefix'] = $config['prefix'];
}
$iterator = $s3
->getListObjectVersionsIterator($iterator_args);
$iterator
->setPageSize(1000);
$folders = array();
$existing_folders = db_select('s3fs_file', 's')
->fields('s', array(
'uri',
))
->condition('dir', 1, '=');
if (!empty($config['prefix'])) {
$existing_folders = $existing_folders
->condition('uri', db_like("s3://{$config['prefix']}") . '%', 'LIKE');
}
foreach ($existing_folders
->execute()
->fetchCol(0) as $folder_uri) {
$folders[$folder_uri] = TRUE;
}
module_load_install('s3fs');
$schema = s3fs_schema();
try {
db_create_table('s3fs_file_temp', $schema['s3fs_file']);
$options = Database::getConnectionInfo('default');
switch ($options['default']['driver']) {
case 'pgsql':
break;
case 'sqlite':
break;
case 'mysql':
db_query("ALTER TABLE {s3fs_file_temp} CONVERT TO CHARACTER SET utf8 COLLATE utf8_bin");
break;
}
} catch (DatabaseSchemaObjectExistsException $e) {
db_truncate('s3fs_file_temp')
->execute();
}
$dispatcher = $iterator
->getEventDispatcher();
$dispatcher
->addListener('resource_iterator.before_send', function ($event) use (&$file_metadata_list, &$folders) {
_s3fs_write_metadata($file_metadata_list, $folders);
});
foreach ($iterator as $s3_metadata) {
$uri = "s3://{$s3_metadata['Key']}";
if ($uri[strlen($uri) - 1] == '/') {
$folders[rtrim($uri, '/')] = TRUE;
}
else {
if (isset($s3_metadata['IsLatest']) && !$s3_metadata['IsLatest']) {
continue;
}
if (!isset($s3_metadata['StorageClass'])) {
continue;
}
if (isset($s3_metadata['VersionId']) && $s3_metadata['VersionId'] == 'null') {
unset($s3_metadata['VersionId']);
}
$file_metadata_list[] = _s3fs_convert_metadata($uri, $s3_metadata);
}
}
_s3fs_write_metadata($file_metadata_list, $folders);
if ($folders) {
$insert_query = db_insert('s3fs_file_temp')
->fields(array(
'uri',
'filesize',
'timestamp',
'dir',
'mode',
'uid',
'version',
));
foreach ($folders as $folder_uri => $ph) {
if (!empty($config['prefix']) && strpos($folder_uri, "s3://{$config['prefix']}") === FALSE) {
continue;
}
$metadata = _s3fs_convert_metadata($folder_uri, array());
$insert_query
->values($metadata);
}
$insert_query
->execute();
}
if (empty($config['prefix'])) {
db_rename_table('s3fs_file', 's3fs_file_old');
db_rename_table('s3fs_file_temp', 's3fs_file');
db_drop_table('s3fs_file_old');
}
else {
$transaction = db_transaction();
try {
$rows_to_copy = db_select('s3fs_file_temp', 's')
->fields('s', array(
'uri',
'filesize',
'timestamp',
'dir',
'mode',
'uid',
'version',
));
$delete_query = db_delete('s3fs_file')
->condition('uri', db_like("s3://{$config['prefix']}") . '%', 'LIKE')
->execute();
db_insert('s3fs_file')
->from($rows_to_copy)
->execute();
db_drop_table('s3fs_file_temp');
} catch (Exception $e) {
$transaction
->rollback();
watchdog_exception('S3 File System', $e);
drupal_set_message(t('S3 File System cache refresh failed. Please see log messages for details.'), 'error');
return;
}
unset($transaction);
}
if (empty($config['prefix'])) {
drupal_set_message(t('S3 File System cache refreshed.'));
}
else {
drupal_set_message(t('Files in the S3 File System cache with prefix %prefix have been refreshed.', array(
'%prefix' => $config['prefix'],
)));
}
}
function _s3fs_write_metadata(&$file_metadata_list, &$folders) {
if ($file_metadata_list) {
$insert_query = db_insert('s3fs_file_temp')
->fields(array(
'uri',
'filesize',
'timestamp',
'dir',
'mode',
'uid',
'version',
));
foreach ($file_metadata_list as $metadata) {
$insert_query
->values($metadata);
$uri = dirname($metadata['uri']);
while (strlen($uri) > 5) {
$folders[$uri] = TRUE;
$uri = dirname($uri);
}
}
$insert_query
->execute();
}
$file_metadata_list = array();
}
function _s3fs_convert_metadata($uri, $s3_metadata) {
$metadata = array(
'uri' => $uri,
'filesize' => 0,
'timestamp' => time(),
'dir' => 0,
'mode' => 0100000,
'uid' => '',
'version' => '',
);
if (empty($s3_metadata)) {
$metadata['dir'] = 1;
$metadata['uid'] = 'S3 File System';
$metadata['mode'] = 040000;
}
else {
if (isset($s3_metadata['Size'])) {
$metadata['filesize'] = $s3_metadata['Size'];
}
if (isset($s3_metadata['LastModified'])) {
$metadata['timestamp'] = date('U', strtotime($s3_metadata['LastModified']));
}
if (isset($s3_metadata['Owner']['ID'])) {
$metadata['uid'] = $s3_metadata['Owner']['ID'];
}
if (isset($s3_metadata['VersionId'])) {
$metadata['version'] = $s3_metadata['VersionId'];
}
}
$metadata['mode'] |= 0777;
return $metadata;
}
function _s3fs_get_amazons3_client($config) {
static $s3;
static $static_config;
if (!isset($s3) || $static_config != $config) {
$access_key = !empty($config['awssdk2_access_key']) ? $config['awssdk2_access_key'] : FALSE;
$access_key = variable_get('awssdk2_access_key', $access_key);
$secret_key = !empty($config['awssdk2_secret_key']) ? $config['awssdk2_secret_key'] : FALSE;
$secret_key = variable_get('awssdk2_secret_key', $secret_key);
$default_cache_config = !empty($config['awssdk2_default_cache_config']) ? $config['awssdk2_default_cache_config'] : FALSE;
$default_cache_config = variable_get('awssdk2_default_cache_config', $default_cache_config);
$use_instance_profile = !empty($config['use_instance_profile']);
$library = _s3fs_load_awssdk2_library();
if (!$library['loaded']) {
throw new S3fsException(t('Unable to load the AWS SDK. Please ensure that the awssdk2 library is installed correctly.'));
}
elseif (!class_exists('Aws\\S3\\S3Client')) {
throw new S3fsException(t('Cannot load Aws\\S3\\S3Client class. Please ensure that the awssdk2 library is installed correctly.'));
}
elseif (!$use_instance_profile && (!$secret_key || !$access_key)) {
throw new S3fsException(t("Your AWS credentials have not been properly configured. Please set them on the S3 File System !settings_page or\n set \$conf['awssdk2_access_key'] and \$conf['awssdk2_secret_key'] in your site's settings.php file.", array(
'!settings_page' => l(t('settings page'), 'admin/config/media/s3fs/settings'),
)));
}
elseif ($use_instance_profile && empty($default_cache_config)) {
throw new s3fsException(t("Your AWS credentials have not been properly configured.\n You are attempting to use instance profile credentials but you have not set a default cache location.\n Please set it on the !settings_page or set \$conf['awssdk2_default_cache_config'] in your site's settings.php file.", array(
'!settings_page' => l(t('settings page'), 'admin/config/media/s3fs/settings'),
)));
}
if ($use_instance_profile) {
$client_config = array(
'default_cache_config' => $default_cache_config,
);
}
else {
$client_config = array(
'key' => $access_key,
'secret' => $secret_key,
);
}
if (!empty($config['region'])) {
$client_config['region'] = $config['region'];
}
if (!empty($config['use_customhost']) && !empty($config['hostname'])) {
$client_config['base_url'] = $config['hostname'];
}
$s3 = Aws\S3\S3Client::factory($client_config);
}
$static_config = $config;
return $s3;
}
function _s3fs_get_config() {
global $conf;
$config = array();
foreach ($conf as $key => $value) {
if (substr($key, 0, 5) == 's3fs_') {
$config[substr($key, 5)] = $value;
}
}
return $config;
}
function _s3fs_load_awssdk2_library() {
$library = libraries_load('awssdk2');
if (!empty($library['loaded'])) {
return $library;
}
cache_clear_all('awssdk2', 'cache_libraries');
drupal_static_reset('libraries_load');
return libraries_load('awssdk2');
}