node_export_file.module in Node export 6.2
Same filename and directory in other branches
The Node Export File module.
Export helper module for handling CCK FileFields, Uploaded files, and Images.
File
modules/node_export_file/node_export_file.moduleView source
<?php
/**
* @file
* The Node Export File module.
*
* Export helper module for handling CCK FileFields, Uploaded files, and Images.
*/
define('EXPORT_FILE_FILES_DIR_SUBSTITUTION', '#FILES_DIRECTORY_PATH#');
/**
* Implementation of hook_help().
*/
function node_export_file_help($path, $arg) {
switch ($path) {
case 'admin/help#node_export_file':
return '<p>' . t('Export helper module for handling files: CCK FileField, Upload, and Image.') . '</p>';
}
}
/**
* Implementation of hook_form_alter().
*/
function node_export_file_form_alter(&$form, $form_state, $form_id) {
if ($form_id == 'node_export_form') {
// TODO: Add extra options to the export form?
}
elseif ($form_id == 'node_export_settings') {
drupal_add_js(drupal_get_path('module', 'node_export_file') . '/js/node_export_file_admin.js');
$form['node_export_file'] = array(
'#type' => 'fieldset',
'#title' => t('Export Files'),
'#weight' => -1,
);
$types = node_get_types('names');
$form['node_export_file']['node_export_file_types'] = array(
'#type' => 'checkboxes',
'#title' => t('Files exported for content types'),
'#default_value' => variable_get('node_export_file_types', array()),
'#options' => $types,
'#description' => t('Which content types should export file attachments and uploads?'),
);
$textarea_delivery = $form['basic']['node_export_node_code']['#default_value'];
$bulk_delivery = $form['basic']['node_export_bulk_code']['#default_value'];
$mode_message_display = $textarea_delivery != 'file' || $bulk_delivery != 'file';
$form['node_export_file']['node_export_file_mode'] = array(
'#type' => 'radios',
'#title' => t('File export mode'),
'#default_value' => variable_get('node_export_file_mode', 'inline'),
'#options' => array(
'inline' => t('Inside export code (base 64 encoded string)'),
'local' => t('Local file export'),
'remote' => t('Remote file export, URL'),
),
'#description' => t('Should file exports be inline inside the export code, a local path to the file or a URL? "Inside export code" is the easiest option to use, local and remote modes are more useful for power users. <em>NOTE: Remote mode only works with a public files directory.</em>'),
'#prefix' => '<div id="node-export-file-mode-message"><div class="message warning" style="display: ' . ($mode_message_display ? 'block' : 'none') . ';">' . t('Warning, "Inside export code (base 64 encoded string)" mode generates much larger exports. For this reason it is recommended you set the export code delivery methods to "Text file download" (both <strong>Node export code delivery</strong> and <strong>Bulk node export code delivery</strong>).') . '</div></div>',
);
$form['node_export_file']['node_export_file_assets_path'] = array(
'#type' => 'textfield',
'#title' => t('Export FileField Assets Path'),
'#size' => 60,
'#maxlength' => 255,
'#default_value' => variable_get('node_export_file_assets_path', ''),
'#description' => t('Optionally, copy file uploads to this path when the node is exported.
The primary advantage of this is to divert exported files into a
safe location so they can be commmitted to source control (eg: Subversion,
CVS, git). <em>Tip: For install profile developers, setting this
path to <code>profiles/my_profile/node_export_assets</code> may be
useful.</em>'),
'#required' => FALSE,
);
}
}
/**
* Implementation of hook_node_export_node_alter().
*
* Handles importing and exporting CCK FileFields
*
* @param $module
* An extra paramater that allows this hook implementation to do work
* for other modules that are very similar to filefield (eg: upload).
*/
function filefield_node_export_node_alter(&$node, $original_node, $method) {
_node_export_file_node_alter($node, $original_node, $method, '_filefield_field_filter');
}
/**
* FileField field filter callback used in _node_export_file_node_alter().
*/
function _filefield_field_filter($attribute, $field) {
return drupal_substr($attribute, 0, 6) == 'field_' && is_array($field);
}
/**
* Implementation of hook_node_export_node_alter().
*
* Handles importing and exporting CCK FileFields
*/
function upload_node_export_node_alter(&$node, $original_node, $method) {
if (isset($original_node->files) && count($original_node->files) > 0) {
// Re-attach files, node_export module strips them off
$node->files = $original_node->files;
if ($method == 'export') {
// Mark all file uploads as "new", this is required by the upload module
foreach ($node->files as $fid => $file) {
$node->files[$fid]->new = TRUE;
}
}
_node_export_file_node_alter($node, $original_node, $method, '_upload_field_filter');
}
}
/**
* Upload module field filter callback used in _node_export_file_node_alter().
*/
function _upload_field_filter($attribute, $field) {
return $attribute == 'files' && is_array($field);
}
/**
* Implementation of hook_node_export_node_alter().
*
* Handles importing and exporting photos uploaded to image nodes. This
* is more complicated than for filefields or upload attachments because
* the image module doesn't attach the file objects for images to the node.
* To get around this we create a special 'image_export_files' attribute
* on the node. This will be stripped off when the node is imported.
*/
function image_node_export_node_alter(&$node, $original_node, $method) {
// Only act on image nodes
if ($node->type != 'image') {
return;
}
if ($method == 'export') {
// Only export the original image, the build-derivatives attribute
// is set later to rebuild all image sizes on the import side
$result = db_query("SELECT f.*\n FROM {image} AS i\n INNER JOIN {files} AS f ON i.fid = f.fid\n WHERE i.nid = %d AND f.filename = '%s'", $original_node->nid, IMAGE_ORIGINAL);
$node->image_export_files = array(
db_fetch_object($result),
);
// Unset the images array, this will cause problems during the
// 'prepopulate' step during import
$node->images = array();
$node->rebuild_images = TRUE;
}
_node_export_file_node_alter($node, $original_node, $method, '_image_field_filter');
// The file objects have been saved to the database but the rest of the
// image tables have not been hooked up yet.
if (($method == 'save-edit' || $method == 'prepopulate') && !empty($node->image_export_files)) {
$node->images = array();
// Create the images array based on the imported files
foreach ($node->image_export_files as $file) {
$node->images[$file->filename] = $file->filepath;
}
}
}
/**
* Upload module field filter callback used in _node_export_file_node_alter().
*/
function _image_field_filter($attribute, $field) {
return $attribute == 'image_export_files' && is_array($field);
}
/**
* Abstract hook_node_export_node_alter() used by filefield, upload, and image.
*/
function _node_export_file_node_alter(&$node, $original_node, $method, $attribute_filter_callback) {
$assets_path = variable_get('node_export_file_assets_path', '');
$export_mode = variable_get('node_export_file_mode', 'inline');
switch ($export_mode) {
case 'local':
$export_var = 'node_export_file_path';
break;
case 'remote':
$export_var = 'node_export_file_url';
break;
default:
case 'inline':
$export_var = 'node_export_file_data';
break;
}
if ($method == 'export') {
if (_node_export_file_check_assets_path($export_mode, $assets_path) === FALSE) {
// Don't continue if the assets path is not ready
return;
}
}
foreach ($node as $attr => $value) {
$field =& $node->{$attr};
// Filter this attribute by callback
if (!call_user_func_array($attribute_filter_callback, array(
'attribute' => $attr,
'field' => $field,
))) {
continue;
}
// Strip off anything that isn't a file, if no files are found skip to
// the next attribute
if (($files = _node_export_file_check_files($field)) == FALSE) {
continue;
}
foreach ($files as $i => $file) {
// When exporting a node ...
if ($method == 'export') {
if (!isset($file->filepath) || !is_file($file->filepath)) {
drupal_set_message(t("CCK file upload found on node, but doesn't exist on disk? '!path'", array(
'!path' => $file->filepath,
)), 'error');
continue;
}
$export_data = _node_export_file_get_export_data($file, $export_mode, $assets_path);
if (is_object($field[$i])) {
$field[$i]->{$export_var} = $export_data;
$field[$i]->fid = NULL;
_node_export_file_clean_local_file_path($field[$i]->filepath);
}
elseif (is_array($field[$i])) {
$field[$i][$export_var] = $export_data;
$field[$i]['fid'] = NULL;
_node_export_file_clean_local_file_path($field[$i]['filepath']);
}
}
elseif ($method == 'save-edit' || $method == 'prepopulate') {
$result = _node_export_file_import_file($file);
// The file was saved successfully, update the file field (by reference)
if ($result == TRUE && isset($file->fid)) {
if (is_object($field[$i])) {
$field[$i]->fid = $file->fid;
$field[$i]->filepath = $file->filepath;
}
elseif (is_array($field[$i])) {
$field[$i]['fid'] = $file->fid;
$field[$i]['filepath'] = $file->filepath;
}
}
}
}
}
}
/**
* Ensure the assets path is exists and is writeable.
*
* @param $export_mode
* The files export mode, 'local' or 'remote'
* @param $assets_path
* The assets path, should be empty ('') if assets are not supposed to
* be copied.
* @return TRUE or FALSE or NULL
* TRUE if the assets path is ready, and files should be copied there
* FALSE if the assets path is not ready, and files should be copied there
* NULL if files shouldn't be copied to the assets path
*/
function _node_export_file_check_assets_path($export_mode, $assets_path) {
// If files are supposed to be copied to the assets path.
if ($export_mode == 'local' && $assets_path) {
// Ensure the assets path is created
if (!is_dir($assets_path) && mkdir($assets_path, 0777, TRUE) == FALSE) {
drupal_set_message(t("Could not create assets path! '!path'", array(
'!path' => $assets_path,
)), 'error');
return FALSE;
}
// Ensure it is writable
if (!is_writable($assets_path)) {
drupal_set_message(t("Assets path is not writable! '!path'", array(
'!path' => $assets_path,
)), 'error');
return FALSE;
}
return TRUE;
}
return NULL;
}
/**
* Checks an array of files, removing any that are invalid.
*
* @return $files or FALSE
* Returns an array of $files objects, or FALSE if there are no files.
*/
function _node_export_file_check_files($original_files) {
$files = array();
foreach ($original_files as $i => $file) {
$file = (object) $file;
if (isset($file->filepath)) {
// NOTE: Preserving the $i key is important for later manipulation
$files[$i] = $file;
}
}
return count($files) > 0 ? $files : FALSE;
}
/**
* Handles the file copying parts and local/remote parts of the file export.
*
* @param $file
* The file object to handle.
* @param $export_mode
* The mode, 'inline' / 'local' / 'remote'
* @param $assets_path
* Either empty ('') if files should not be copied to the assets path, or
* the path to a writable directory.
*/
function _node_export_file_get_export_data($file, $export_mode, $assets_path) {
if ($export_mode == 'local') {
if ($assets_path) {
$export_data = $assets_path . '/' . basename($file->filepath);
if (!copy($file->filepath, $export_data)) {
drupal_set_message(t("Export file error, could not copy '%filepath' to '%exportpath'.", array(
'%filepath' => $file->filepath,
'%exportpath' => $export_data,
)), 'error');
return FALSE;
}
}
else {
$export_data = $file->filepath;
}
}
elseif ($export_mode == 'remote') {
$export_data = url($file->filepath, array(
'absolute' => TRUE,
));
}
else {
$export_data = base64_encode(file_get_contents($file->filepath));
}
return $export_data;
}
/**
* Replaces the files directory portion of a path with a substition
* this forces clients decoding the node export to use their own
* files directory path. This helps get around issues with multi-site
* installations and non-standard files directory locations.
*/
function _node_export_file_clean_local_file_path(&$path) {
$path = preg_replace('/^' . preg_quote(file_directory_path(), '/') . '/', EXPORT_FILE_FILES_DIR_SUBSTITUTION, $path);
}
/**
* Replaces the EXPORT_FILE_FILES_DIR_SUBSTITUTION with the files directory
* path, wherever found.
*/
function _node_export_file_unclean_local_file_path(&$path) {
$path = strtr($path, array(
EXPORT_FILE_FILES_DIR_SUBSTITUTION => file_directory_path(),
));
}
/**
* Detects remote and local file exports and imports accordingly.
*
* @param &$file
* The file, passed by reference.
* @return TRUE or FALSE
* Depending on success or failure. On success the $file object will
* have a valid $file->fid attribute.
*/
function _node_export_file_import_file(&$file) {
// Ensure the filepath is set correctly relative to this Drupal site's
// files directory
_node_export_file_unclean_local_file_path($file->filepath);
// The file is already in the right location AND either the
// node_export_file_path is not set or the node_export_file_path and filepath
// contain the same file
// FIXME: same filesize does NOT mean "same file".
if (is_file($file->filepath) && (!is_file($file->node_export_file_path) || is_file($file->node_export_file_path) && filesize($file->filepath) == filesize($file->node_export_file_path))) {
drupal_write_record('files', $file);
}
elseif (isset($file->node_export_file_data)) {
$directory = file_create_path(dirname($file->filepath));
if (file_check_directory($directory, TRUE)) {
if (file_put_contents($file->filepath, base64_decode($file->node_export_file_data))) {
drupal_write_record('files', $file);
}
}
}
elseif (isset($file->node_export_file_path) && is_file($file->node_export_file_path)) {
$directory = file_create_path(dirname($file->filepath));
if (file_check_directory($directory, TRUE)) {
// The $file->node_export_file_path is passed to reference, and modified
// by file_copy(). Making a copy to avoid tainting the original.
$node_export_file_path = $file->node_export_file_path;
file_copy($node_export_file_path, $directory, FILE_EXISTS_REPLACE);
// At this point the $file->node_export_file_path will contain the
// destination of the copied file
$file->filepath = $node_export_file_path;
drupal_write_record('files', $file);
}
}
elseif (isset($file->node_export_file_url)) {
// Need time to do the download
ini_set('max_execution_time', 900);
$temp_path = file_directory_temp() . '/' . md5(mt_rand()) . '.txt';
if (($source = fopen($file->node_export_file_url, 'r')) == FALSE) {
drupal_set_message(t("Could not open '@file' for reading.", array(
'@file' => $file->node_export_file_url,
)));
return FALSE;
}
elseif (($dest = fopen($temp_path, 'w')) == FALSE) {
drupal_set_message(t("Could not open '@file' for writing.", array(
'@file' => $file->filepath,
)));
return FALSE;
}
else {
// PHP5 specific, downloads the file and does buffering
// automatically.
$bytes_read = @stream_copy_to_stream($source, $dest);
// Flush all buffers and wipe the file statistics cache
@fflush($source);
@fflush($dest);
clearstatcache();
if ($bytes_read != filesize($temp_path)) {
drupal_set_message(t("Remote export '!url' could not be fully downloaded, '@file' to temporary location '!temp'.", array(
'!url' => $file->node_export_file_url,
'@file' => $file->filepath,
'!temp' => $temp_path,
)));
return FALSE;
}
else {
if (!@copy($temp_path, $file->filepath)) {
unlink($temp_path);
drupal_set_message(t("Could not move temporary file '@temp' to '@file'.", array(
'@temp' => $temp_path,
'@file' => $file->filepath,
)));
return FALSE;
}
unlink($temp_path);
$file->filesize = filesize($file->filepath);
$file->filemime = file_get_mimetype($file->filepath);
}
}
fclose($source);
fclose($dest);
drupal_write_record('files', $file);
}
else {
drupal_set_message(t("Unknown error occurred attempting to import file: {$file->filepath}"), 'error');
return FALSE;
}
return TRUE;
}
Functions
Name | Description |
---|---|
filefield_node_export_node_alter | Implementation of hook_node_export_node_alter(). |
image_node_export_node_alter | Implementation of hook_node_export_node_alter(). |
node_export_file_form_alter | Implementation of hook_form_alter(). |
node_export_file_help | Implementation of hook_help(). |
upload_node_export_node_alter | Implementation of hook_node_export_node_alter(). |
_filefield_field_filter | FileField field filter callback used in _node_export_file_node_alter(). |
_image_field_filter | Upload module field filter callback used in _node_export_file_node_alter(). |
_node_export_file_check_assets_path | Ensure the assets path is exists and is writeable. |
_node_export_file_check_files | Checks an array of files, removing any that are invalid. |
_node_export_file_clean_local_file_path | Replaces the files directory portion of a path with a substition this forces clients decoding the node export to use their own files directory path. This helps get around issues with multi-site installations and non-standard files directory locations. |
_node_export_file_get_export_data | Handles the file copying parts and local/remote parts of the file export. |
_node_export_file_import_file | Detects remote and local file exports and imports accordingly. |
_node_export_file_node_alter | Abstract hook_node_export_node_alter() used by filefield, upload, and image. |
_node_export_file_unclean_local_file_path | Replaces the EXPORT_FILE_FILES_DIR_SUBSTITUTION with the files directory path, wherever found. |
_upload_field_filter | Upload module field filter callback used in _node_export_file_node_alter(). |
Constants
Name | Description |
---|---|
EXPORT_FILE_FILES_DIR_SUBSTITUTION | @file The Node Export File module. |