You are here

node_export_file.module in Node export 6.3

Same filename and directory in other branches
  1. 6.2 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('NODE_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');

    // Remove buttons, but store for reattaching.
    $buttons = $form['buttons'];
    unset($form['buttons']);
    $form['file'] = array(
      '#type' => 'fieldset',
      '#title' => t('Export files'),
    );
    $types = node_get_types('names');
    $form['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'];
    $mode_message_display = $textarea_delivery != 'file';
    $form['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".') . '</div></div>',
    );
    $form['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,
    );

    // Reattach buttons.
    $form['buttons'] = $buttons;
  }
}

/**
 * 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 node_export_file_node_export_node_alter(&$node, $original_node, $op) {
  if ($op == 'export') {
    $types = array_filter(variable_get('node_export_file_types', array()));
    if (!in_array($node->type, $types)) {
      return;
    }
  }
  node_export_file_alter_filefield($node, $original_node, $op, 'node_export_file_filefield_filter');
  node_export_file_alter_upload($node, $original_node, $op);
  node_export_file_alter_image($node, $original_node, $op);
}

/**
 * FileField field filter callback used in node_export_file_alter_filefield().
 */
function node_export_file_filefield_filter($attribute, $field) {
  return drupal_substr($attribute, 0, 6) == 'field_' && is_array($field);
}

/**
 * Handles importing and exporting CCK FileFields
 */
function node_export_file_alter_upload(&$node, $original_node, $op) {
  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 ($op == '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_alter_filefield($node, $original_node, $op, 'node_export_file_upload_filter');
  }
}

/**
 * Upload module field filter callback used in node_export_file_alter_filefield().
 */
function node_export_file_upload_filter($attribute, $field) {
  return $attribute == 'files' && is_array($field);
}

/**
 * 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 node_export_file_alter_image(&$node, $original_node, $op) {

  // Only act on image nodes
  if ($node->type != 'image') {
    return;
  }
  if ($op == '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_alter_filefield($node, $original_node, $op, 'node_export_file_image_filter');

  // The file objects have been saved to the database but the rest of the
  // image tables have not been hooked up yet.
  if ($op == 'import' && !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_alter_filefield().
 */
function node_export_file_image_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_alter_filefield(&$node, $original_node, $op, $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 ($op == '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 ($op == '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 ($op == 'import') {
        $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(), '/') . '/', NODE_EXPORT_FILE_FILES_DIR_SUBSTITUTION, $path);
}

/**
 * Replaces the NODE_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(
    NODE_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) && strtoupper(dechex(crc32(file_get_contents($file->uri)))) == strtoupper(dechex(crc32(file_get_contents($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: @filepath", array(
      '@filepath' => $file->filepath,
    )), 'error');
    return FALSE;
  }
  return TRUE;
}

Functions

Namesort descending Description
node_export_file_alter_filefield Abstract hook_node_export_node_alter() used by filefield, upload, and image.
node_export_file_alter_image 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…
node_export_file_alter_upload Handles importing and exporting CCK FileFields
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_filefield_filter FileField field filter callback used in node_export_file_alter_filefield().
node_export_file_form_alter Implementation of hook_form_alter().
node_export_file_get_export_data Handles the file copying parts and local/remote parts of the file export.
node_export_file_help Implementation of hook_help().
node_export_file_image_filter Upload module field filter callback used in node_export_file_alter_filefield().
node_export_file_import_file Detects remote and local file exports and imports accordingly.
node_export_file_node_export_node_alter Implementation of hook_node_export_node_alter().
node_export_file_unclean_local_file_path Replaces the NODE_EXPORT_FILE_FILES_DIR_SUBSTITUTION with the files directory path, wherever found.
node_export_file_upload_filter Upload module field filter callback used in node_export_file_alter_filefield().

Constants

Namesort descending Description
NODE_EXPORT_FILE_FILES_DIR_SUBSTITUTION @file The Node export file module.