2

Using Views Data Export, I have multiple CSV files generated/provided by several different displays. Rather than setting up something where the user clicks a "Download CSV" button corresponding to each Views display, I'd like to have a single button where all of the files are zipped into a single archive file that is downloaded.

I know Views Bulk Operations has the ability to take multiple managed files and create a zip archive from them... Is it possible to use custom actions or VBO somehow to achieve what I'm looking to do? Or is there a better way to do something like this? Any advice would be appreciated!

2 Answers 2

1

You can apply the patch from #1436726 which adds VBO integration into Views Data Export module, however it's for Drupal 7.x.

diff --git a/views_data_export.module b/views_data_export.module
index 0a8c90b..9b7b784 100644
--- a/views_data_export.module
+++ b/views_data_export.module
@@ -272,6 +272,84 @@ function views_data_export_clear($export_id) {
   views_data_export_view_clear($export_id);
 }
 
+/**
+ * Implements hook_action_info().
+ */
+function views_data_export_action_info() {
+  return array(
+    'views_data_export_action_csv_export' => array(
+      'type' => 'entity',
+      'label' => t('Export to CSV'),
+      'configurable' => FALSE,
+      'aggregate' => TRUE,
+      'behavior' => array('views_data_export_export_csv'),
+    ),
+  );
+}
+
+/**
+ * CSV export action callback.
+ */
+function views_data_export_action_csv_export($entity, &$context) {
+
+  if (isset($context['view_info'])) {
+    $view = views_get_view($context['view_info']['name']);
+    $display_id = FALSE;
+
+    foreach ($view->display as $display) {
+      if ($display->display_plugin == 'views_data_export') {
+        $display_id = $display->id;
+        break;
+      }
+    }
+
+    if ($display_id) {
+      $args = array();
+      // If a subset of the available entities was selected, provide the list
+      // of selected IDs to the view as a contrived argument, which we'll
+      // use later to alter the query.
+      // Extra code for backward/forward compatibility.
+      // @see https://www.drupal.org/node/2488146.
+      if (isset($context['view_info']['total_rows'])) {
+        $total_rows = $context['view_info']['total_rows'];
+      }
+      else {
+        $countView = clone $view;
+        $countView->execute($context['view_info']['display']);
+        $total_rows = $countView->total_rows;
+      }
+      $entity_ids = array_keys($entity);
+      if (count($entity_ids) < $total_rows) {
+        $args['entity_ids'] = implode('+', $entity_ids);
+      }
+      $view->execute_display($display_id, $args);
+      // If the export is configured to run as a batch job, it will handle the
+      // display of progress & result/download pages, deliver the generated
+      // file, and never return here. Otherwise, executing the display here
+      // won't cause the file to be delivered to the browser for download.
+      // Dying here ensures that it does, but there must be a better way.
+      die();
+    }
+    else {
+      drupal_set_message(t("Can't export data, because the !view view does not have a !display display.", array('!view' => $view->name, '!display' => 'views_data_export')), 'error');
+    }
+  }
+}
+
+/**
+ * Implements hook_views_query_alter().
+ */
+function views_data_export_views_query_alter(&$view, &$query) {
+  if ($view->display_handler->plugin_name == 'views_data_export') {
+    // If an explicit list of entity IDs was provided, use that to refine the
+    // query.
+    if (isset($view->args['entity_ids'])) {
+      $query->add_where(0, $view->base_table . '.' . $view->base_field, explode('+', $view->args['entity_ids']), 'IN');
+      // Now remove this fake argument to avoid confusion.
+      unset($view->args['entity_ids']);
+    }
+  }
+}
 
 /**
  * Store a view in the object cache.
1

For Drupal >=8.x you can use VBO export module which extends VBO.

Then you can write your own plugin. Here is the example for a class:

<?php

namespace Drupal\my_module\Plugin\Action;

use Drupal\Core\Form\FormStateInterface;
use Drupal\file\Entity\File;

/**
 * Generates ZIP with exported files.
 *
 * @Action(
 *   id = "vbo_export_generate_zip_action",
 *   label = @Translation("Generate ZIP from selected view results"),
 *   type = ""
 * )
 */
class VboExportZip extends \Drupal\vbo_export\Plugin\Action\VboExportBase {

  const EXTENSION = 'zip';

  /**
   * {@inheritdoc}
   *
   * Settings for preliminary config.
   */
  public function buildPreConfigurationForm(array $form, array $values, FormStateInterface $form_state) {
    $form = parent::buildPreConfigurationForm($form, $values, $form_state);
    return $form;
  }

  /**
   * Output generated string to file. Message user.
   *
   * @param string $output
   *   The string that will be saved to a file.
   */
  protected function sendToFile($output) {
    if (!empty($output)) {
      $rand = substr(hash('ripemd160', uniqid()), 0, 8);
      $filename = $this->context['view_id'] . '_' . date('Y_m_d_H_i', $this->time->getRequestTime()) . '-' . $rand . '.' . static::EXTENSION;

      $wrappers = $this->streamWrapperManager->getWrappers();
      if (isset($wrappers['private'])) {
        $wrapper = 'private';
      }
      else {
        $wrapper = 'public';
      }

      $destination = $wrapper . '://' . $filename;

      $zip = new \ZipArchive();

      foreach ($output as $row) {
        // Adding one file at once.
        $opened = $zip->open(drupal_realpath($destination), \ZIPARCHIVE::CREATE);

        if (!$opened) {
          // Error.
          $this->messenger()->addStatus($this->t('Export failed!'));
          return;
        }

        $zip->addFile($row['file_path_abs'], $row['file_name']);
        $zip->close();
      }

      // ZIP file will be managed by Drupal. Also marking file as temporary.
      $file = File::create([
        'filename' => \basename($destination),
        'filemime' => 'application/zip',
        'uri' => $destination,
        'uid' => \Drupal::currentUser()->id(),
        'status' => 0,
      ]);
      $file->save();

      // Get relative url to prevent mixed content errors when using HTTPS + HTTP.
      $relative_url = \file_url_transform_relative(file_create_url($file->getFileUri()));
      $this->messenger()->addStatus($this->t('Export file created, <a href=":url" target="_blank">Click here</a> to download.', [':url' => $relative_url]));
    }
  }

  /**
   * Generate output string.
   */
  protected function generateOutput() {
    $rows = $this->getRows();

    // Generate output.
    $output = [];

    // @todo: Customize below.
    $output[] = [
      'file_name' => strip_tags($row['some_file_field']),
      // 'file_url' => $attachment['url'],
      'file_path_abs' => str_replace(["\\", '//'], '/', $attachment['path']),
      'file_mime_type' => $attachment['mime_type'],
    ];

    return $output;
  }

}

Place the code under src/Plugin/Action/ in your my_module module (e.g. VboExportZip.php). Ensure you've php-zip extension installed (e.g. apt install php-zip).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.