You are here

archive.action.inc in Views Bulk Operations (VBO) 7.3

Same filename and directory in other branches
  1. 6 actions/archive.action.inc

Provides an action for creating a zip archive of selected files.

An entry in the {file_managed} table is created for the newly created archive, and it is marked as permanent or temporary based on the operation settings.

File

actions/archive.action.inc
View source
<?php

/**
 * @file
 * Provides an action for creating a zip archive of selected files.
 *
 * An entry in the {file_managed} table is created for the newly created archive,
 * and it is marked as permanent or temporary based on the operation settings.
 */

/**
 * Implements hook_action_info().
 */
function views_bulk_operations_archive_action_info() {
  $actions = array();
  if (function_exists('zip_open')) {
    $actions['views_bulk_operations_archive_action'] = array(
      'type' => 'file',
      'label' => t('Create an archive of selected files'),
      // This action only works when invoked through VBO. That's why it's
      // declared as non-configurable to prevent it from being shown in the
      // "Create an advanced action" dropdown on admin/config/system/actions.
      'configurable' => FALSE,
      'vbo_configurable' => TRUE,
      'behavior' => array(
        'views_property',
      ),
      'triggers' => array(
        'any',
      ),
    );
  }
  return $actions;
}

/**
 * Since Drupal's Archiver doesn't abstract properly the archivers it implements
 * (Archive_Tar and ZipArchive), it can't be used here.
 */
function views_bulk_operations_archive_action($file, $context) {
  global $user;
  static $archive_contents = array();

  // Adding a non-existent file to the archive crashes ZipArchive on close().
  if (file_exists($file->uri)) {
    $destination = $context['destination'];
    $zip = new ZipArchive();

    // If the archive already exists, open it. If not, create it.
    if (file_exists($destination)) {
      $opened = $zip
        ->open(drupal_realpath($destination));
    }
    else {
      $opened = $zip
        ->open(drupal_realpath($destination), ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE);
    }
    if ($opened) {

      // Create a list of all files in the archive. Used for duplicate checking.
      if (empty($archive_contents)) {
        for ($i = 0; $i < $zip->numFiles; $i++) {
          $archive_contents[] = $zip
            ->getNameIndex($i);
        }
      }

      // Make sure that the target filename is unique.
      $filename = _views_bulk_operations_archive_action_create_filename(basename($file->uri), $archive_contents);

      // Note that the actual addition happens on close(), hence the need
      // to open / close the archive each time the action runs.
      $zip
        ->addFile(drupal_realpath($file->uri), $filename);
      $zip
        ->close();
      $archive_contents[] = $filename;
    }
  }

  // The operation is complete, create a file entity and provide a download
  // link to the user.
  if ($context['progress']['current'] == $context['progress']['total']) {
    $archive_file = new stdClass();
    $archive_file->uri = $destination;
    $archive_file->filename = basename($destination);
    $archive_file->filemime = file_get_mimetype($destination);
    $archive_file->uid = $user->uid;
    $archive_file->status = $context['settings']['temporary'] ? FALSE : FILE_STATUS_PERMANENT;

    // Clear filesize() cache to avoid private file system differences in
    // filesize.
    // @see https://www.drupal.org/node/2743999
    clearstatcache();
    file_save($archive_file);
    $url = file_create_url($archive_file->uri);
    $url = l($url, $url, array(
      'absolute' => TRUE,
    ));
    _views_bulk_operations_log(t('An archive has been created and can be downloaded from: !url', array(
      '!url' => $url,
    )));
  }
}

/**
 * Configuration form shown to the user before the action gets executed.
 */
function views_bulk_operations_archive_action_form($context) {

  // Pass the scheme as a value, so that the submit callback can access it.
  $form['scheme'] = array(
    '#type' => 'value',
    '#value' => $context['settings']['scheme'],
  );
  $form['filename'] = array(
    '#type' => 'textfield',
    '#title' => t('Filename'),
    '#default_value' => 'vbo_archive_' . date('Ymd'),
    '#field_suffix' => '.zip',
    '#description' => t('The name of the archive file.'),
  );
  return $form;
}

/**
 * Assembles a sanitized and unique URI for the archive.
 *
 * @returns array
 *   A URI array used by the action callback
 *   (views_bulk_operations_archive_action).
 */
function views_bulk_operations_archive_action_submit($form, $form_state) {

  // Validate the scheme, fallback to public if it's somehow invalid.
  $scheme = $form_state['values']['scheme'];
  if (!file_stream_wrapper_valid_scheme($scheme)) {
    $scheme = 'public';
  }
  $destination = $scheme . '://' . basename($form_state['values']['filename']) . '.zip';

  // If the chosen filename already exists, file_destination() will append
  // an integer to it in order to make it unique.
  $destination = file_destination($destination, FILE_EXISTS_RENAME);
  return array(
    'destination' => $destination,
  );
}

/**
 * Settings form (embedded into the VBO field settings in the Views UI).
 */
function views_bulk_operations_archive_action_views_bulk_operations_form($options) {
  $scheme_options = array();
  foreach (file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL_NORMAL) as $scheme => $stream_wrapper) {
    $scheme_options[$scheme] = $stream_wrapper['name'];
  }
  if (count($scheme_options) > 1) {
    $form['scheme'] = array(
      '#type' => 'radios',
      '#title' => t('Storage'),
      '#options' => $scheme_options,
      '#default_value' => !empty($options['scheme']) ? $options['scheme'] : variable_get('file_default_scheme', 'public'),
      '#description' => t('Select where the archive should be stored. Private file storage has significantly more overhead than public files, but allows restricted access.'),
    );
  }
  else {
    $scheme_option_keys = array_keys($scheme_options);
    $form['scheme'] = array(
      '#type' => 'value',
      '#value' => reset($scheme_option_keys),
    );
  }
  $form['temporary'] = array(
    '#type' => 'checkbox',
    '#title' => t('Temporary'),
    '#default_value' => isset($options['temporary']) ? $options['temporary'] : TRUE,
    '#description' => t('Temporary files older than 6 hours are removed when cron runs.'),
  );
  return $form;
}

/**
 * Create a sanitized and unique version of the provided filename.
 *
 * @param string $filename
 *   The filename to create.
 * @param array $archive_list
 *   The list of files already in the archive.
 *
 * @return string
 *   The new filename.
 */
function _views_bulk_operations_archive_action_create_filename($filename, $archive_list) {

  // Strip control characters (ASCII value < 32). Though these are allowed in
  // some filesystems, not many applications handle them well.
  $filename = preg_replace('/[\\x00-\\x1F]/u', '_', $filename);
  if (substr(PHP_OS, 0, 3) == 'WIN') {

    // These characters are not allowed in Windows filenames.
    $filename = str_replace(array(
      ':',
      '*',
      '?',
      '"',
      '<',
      '>',
      '|',
    ), '_', $filename);
  }
  if (in_array($filename, $archive_list)) {

    // Destination file already exists, generate an alternative.
    $pos = strrpos($filename, '.');
    if ($pos !== FALSE) {
      $name = substr($filename, 0, $pos);
      $ext = substr($filename, $pos);
    }
    else {
      $name = $filename;
      $ext = '';
    }
    $counter = 0;
    do {
      $filename = $name . '_' . $counter++ . $ext;
    } while (in_array($filename, $archive_list));
  }
  return $filename;
}

Functions

Namesort descending Description
views_bulk_operations_archive_action Since Drupal's Archiver doesn't abstract properly the archivers it implements (Archive_Tar and ZipArchive), it can't be used here.
views_bulk_operations_archive_action_form Configuration form shown to the user before the action gets executed.
views_bulk_operations_archive_action_info Implements hook_action_info().
views_bulk_operations_archive_action_submit Assembles a sanitized and unique URI for the archive.
views_bulk_operations_archive_action_views_bulk_operations_form Settings form (embedded into the VBO field settings in the Views UI).
_views_bulk_operations_archive_action_create_filename Create a sanitized and unique version of the provided filename.