You are here

node_export_file.module in Node export 6.2

Same filename and directory in other branches
  1. 6.3 modules/node_export_file/node_export_file.module

The Node Export File module.

Export helper module for handling CCK FileFields, Uploaded files, and Images.

File

modules/node_export_file/node_export_file.module
View 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

Namesort descending 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

Namesort descending Description
EXPORT_FILE_FILES_DIR_SUBSTITUTION @file The Node Export File module.