Jump to content

Download all files as ZIP from inputfield file?

Recommended Posts

In page-edit mode, it would be nice to have an optional download-button for inputfield type file.

i.e. when a user has uploaded two dozen files (perhaps through the frontend), and someone else needs to download these from the backend all at once, without having to open an FTP client. There would simply be a download link / button, and a ZIP would be generated on the fly, containing all files.

If someone has an idea how to do that (a hook?), I'd be all ears :D

  • Like 1

Share this post

Link to post
Share on other sites

Here are some starters:

  • Hook into InputfieldFile::render method to add (modify $event->return and concatenate html string) an anchor link with its href set to a custom template/PHP file where you build your own zip file.
    (Preferentially, add a form with a download button and a hidden field for current page id etc and set its action attribute to the template file)
  • Inside the template file, you'll get the page id from $input->post then build your zip file
  • foreach($page->files_field as $name => $file) {
        $filePath = $file->path
        // ...
    Use $files->zip() method to zip multiple files

    or (from /wire/core/WireFileTools.php)
    // Create zip of all files in directory $dir to file $zip
    $dir = $config->paths->cache . "my-files/"; 
    $zip = $config->paths->cache . "my-file.zip";
    $result = $files->zip($zip, $dir); 
    echo "<h3>These files were added to the ZIP:</h3>";
    foreach($result['files'] as $file) {
      echo "<li>" $sanitizer->entities($file) . "</li>";
    if(count($result['errors'])) {
      echo "<h3>There were errors:</h3>";
      foreach($result['errors'] as $error) {
        echo "<li>" . $sanitizer->entities($error) . "</li>";
    and build your zip file
  • Once the zip file is built force browser to download

If you have any questions, ask away

  • Like 5

Share this post

Link to post
Share on other sites

Excellent! Thanks a lot. Will try it this weekend.

Share this post

Link to post
Share on other sites

Have done this for image fields, it adds a button to the end of the inputfield, code below. Checks for ZipArchive class before installing. Now that I read the code it should work for files but haven't tested! It's got configuration options too, to enable per field basis, either way should it should be super quick to edit if something is not working.


class ExportFieldImages extends WireData implements Module, ConfigurableModule {
    static public function getDefaultConfig() {
        return array(
            "includeFields" => ''

    public static function install(){
        $session = wire("session");
            throw new WireException("ZipArchive is required to create the zip file");
    public static function getModuleInfo() {
        return array(
            'title' => 'ExportFieldImages',
            'version' => 0.1, 
            'summary' => "Adds a button bellow images field to download a zip containing files.",
            'author' => 'Eduardo San Miguel Garcia',
            'singular' => true,
            'href' => '',
            'autoload' => true

    public function init() {
        $this->addHookAfter('InputfieldImage::render', $this, 'afterInputfieldImageRender');
        $this->addHookAfter("ProcessPageEdit::execute", $this, "pageEditExecuteBefore");

    public function pageEditExecuteBefore(HookEvent $event){
        $session = wire("session");
        $input = wire("input");
        $page = wire('pages')->get($input->get->id);
        $name = $page->name;
        $zip = $config->paths->cache . "${$name}.zip";
        if($input->get->downloadImages == "true"){
            wire("log")->save("custom", "excecuted page edit!");
            $array = wire('files')->zip($zip,
                                        array("overwrite" => true)  );
                foreach($array["errors"] as $error){
            } else{
                header('Content-Type: application/zip');
                header("Content-Disposition: attachment; filename='{$name}.zip'");
                echo readfile($zip);
                return $this->halt();
    public function afterInputfieldImageRender(HookEvent $event){
        $field = $event->object;
        $input = wire("input");
        $configData = wire('modules')->getModuleConfigData($this);
        $out = $event->return;
        if(in_array( $field->name, $configData['includeFields'])){

            $button = wire('modules')->get('InputfieldButton');
            $button->class = $button->class . " ExportFieldImages";
            $button->icon = 'download';
            $button->href = "./?downloadImages=true&field={$field->name}&id={$input->get->id}";
            $button->value = "Download Files";
            $btn .= "<span style='display:block; position:relative; float:right;' class='InputfieldPageTableButtons'>" . $button->render() . "</span>";
            $out .= $btn;
        $event->return = $out;
    public function getModuleConfigInputfields(array $data){
        $modules = wire("modules");
        $fields = wire('fields');
        $defaults = self::getDefaultConfig();
		$data = array_merge($defaults, $data);
        $form = new InputfieldWrapper();
        $field = $modules->get("InputfieldAsmSelect");
		$field->name = "includeFields";
		$field->label = __("Fields to enable download button");
		$field->description = __("Choose the image fields where download button should appear");
        foreach($fields as $f){
            if($f->flags & Field::flagSystem) continue;
            if($f->type == "FieldtypeImage" || $f->type == "FieldtypeFile"){
		$field->value = $data['includeFields'];
        return $form;


  • Like 4

Share this post

Link to post
Share on other sites
On 8/11/2017 at 12:28 PM, abdus said:


Should be $file->filename or $file->filename() :-)

Share this post

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By Robin S
      If you've ever needed to insert links to a large number of files within CKEditor you may have found that the standard PW link modal is a somewhat slow way to do it.
      This module provides a quicker way to insert links to files on the page being edited. You can insert a link to an individual file, or insert an unordered list of links to all files on the page with a single click.
      CKEditor Link Files
      Adds a menu to CKEditor to allow the quick insertion of links to files on the page being edited.

      Hover a menu item to see the "Description" of the corresponding file (if present). Click a menu item to insert a link to the corresponding file at the current cursor position. The filename is used as the link text. If you Alt-click a menu item the file description is used as the link text (with fallback to filename if no description entered). If text is currently selected in the editor then the selected text is used as the link text. Click "* Insert links to all files *" to insert an unordered list of links to all files on the page. Also works with the Alt-click option. Menu is built via AJAX so newly uploaded files are included in the menu without the page needing to be saved. However, descriptions are not available for newly uploaded files until the page is saved. There is an option in the module config to include files from Repeater fields in the edited page. Nested Repeater fields (files inside a Repeater inside another Repeater) are not supported. Installation
      Install the CKEditor Link Files module.
      For any CKEditor field where you want the "Insert link to file" dropdown menu to appear in the CKEditor toolbar, visit the field settings and add "LinkFilesMenu" to the "CKEditor Toolbar" settings field.
    • By neophron
      I was searching in the forum about a method how to remove a file extension, like .jpg, but couldn't find anything.
      There are some php methods out in the wild, but I wanted first to get sure, that Processwire is offering a solution.
      The background is, that a clients website has a lot of images, packed in different galleries. All image files contain the name, dimensions and other stuff about the image (painting). I want to echo the image name in the image tag.
    • By Sergio
      Hi everyone,
      Yesterday I began working on a module to create a filesystem abstraction for files and images using the Flysytem library (http://flysystem.thephpleague.com), out of the necessity of having the images copied (and probably linked) on Amazon S3 and other places like Dropbox. There two reasons why I decided to tackle this:
      1 - When I work on the project in my machine, I need a way to automatically sync the images from the server and/or the external storage.
      When an image is added on the server and the database is imported on my local env, PW shows a blank thumbnail of it. The idea for the module if to check if the page image has a width == 0 and if it exists on the server, add it to the local filesystem.
      2 - In the past, I had to move a large website to a different server in a hurry and a lot of images were left behind (it was a mess). So I'm planning for a possible future worst-case scenario of the server exploding 🙂 
      The code I quickly wrote is below (please bear with me that it's pretty raw at the moment). One thing I had to figure it out is why PW fires the Pageimage::size hook wherever a page is loaded in the admin, even though the thumbnails are already created. I was planning to save the image variations on S3 as well. Can someone clarify?
      I know that @LostKobrakai was working on a similar project, and so I would like to ask him and everyone else if you think I (and who may help) should evolve this idea into a full-featured module where the user can select which server (adapter) to use (AWS, Digital Ocean spaces, Dropbox etc.)
      <?php namespace ProcessWire; use Aws\S3\S3Client; use League\Flysystem\AwsS3v3\AwsS3Adapter; use League\Flysystem\Filesystem; use League\Flysystem\Adapter\Local; use Spatie\Dropbox\Client; use Spatie\FlysystemDropbox\DropboxAdapter; class ProcessFly extends WireData implements Module, ConfigurableModule { public static function getModuleInfo() { return array( 'title' => 'ProcessWire Flysystem Integration', 'version' => 001, 'summary' => 'Synchronize all the page assets uploaded through PW to a specified bucket in Amazon S3 and...', 'author' => 'Sérgio Jardim', 'singular' => true, 'autoload' => true, 'icon' => 'image' ); } public function init() { $this->client = S3Client::factory([ 'credentials' => [ 'key' => '', 'secret' => '', ], 'region' => 'sa-east-1', 'version' => 'latest', ]); $this->bucket_name = ""; $this->dir_name = "images"; $this->s3_adapter = new AwsS3Adapter($this->client, $this->bucket_name, $this->dir_name); $this->s3_filesystem = new Filesystem($this->s3_adapter); // DROPBOX $this->authorizationToken = ""; $this->dropbox_client = new Client($this->authorizationToken); $this->adapter_dropbox = new DropboxAdapter($this->dropbox_client); $this->dropbox_filesystem = new Filesystem($this->adapter_dropbox); $this->addHookAfter('Pages::saved', $this, 'checkImageS3'); // Download images that are not in local filesystem for whatever reason but are available in the remove one. $this->addHookAfter('InputfieldFile::fileAdded', $this, 'uploadImageS3'); // Fired when a file/image is added to a page in the admin // $this->addHookAfter('Pageimage::size', $this, 'uploadImageS3'); // Fired when a file is resized via API. Note: this hook is also called when the page is loaded in Admin. Need to know why. $this->addHookAfter('Pageimage::url', $this, 'redirectImageURL'); //Replace image URL for the S3 path } public function redirectImageURL($event){ if($event->page->template == 'admin') return; else $event->return = "https://s3-sa-east-1.amazonaws.com/[bucket name]/images/" . $event->object->page . "/" . $event->object->name; } // UPLOAD public function uploadImageS3($event){ if(count($event->return)) { //if it is a image resize event get image variation data $file = $event->return; } else { $file = $event->arguments(0); } $filename = $file->name; $filename_original = $file->url; $pathToFile = $file->page . "/" . $filename; $system_path = $file->filename(); try{ $file_on_s3 = $this->s3_filesystem->has($pathToFile); //check if file exists on AWS if(!$file_on_s3) { $contents = file_get_contents($system_path); $this->s3_filesystem->put($pathToFile, $contents, array( 'visibility' => 'public', )); //upload file with the same folder structure as in assets/files: page_id/file_name //Also add a copy to Dropbox (OPTIONAL) $this->dropbox_filesystem->put($pathToFile, $contents); } } catch (Exception $e){ throw new WireException("Error: Image not Added to S3: {$e->getMessage()}"); } } public function checkImageS3($event){ $page = $event->arguments(0); if(count($page->images)) { foreach($page->images as $file) { if($file->width == 0) { return $this->downloadImageS3($page, $file); }; } } } public function downloadImageS3($page, $file){ $pathToFile = $page->id . "/" . $file->name; $file_on_s3 = $this->s3_filesystem->has($pathToFile); //check if file exists on AWS if($file_on_s3) { $image = "https://s3-sa-east-1.amazonaws.com/[bucket name]/images/" . $pathToFile; // $page->images->remove($image); $page->images->add($image); $page->save(); } else { throw new WireException("Error: Image not found on S3: {$file->name}"); } } }  
    • By rgaikema
      Hi all,
      Hopefully there's somebody who can help me with an issue while unzipping files.
      My goal is to show banners in a portfolio.
      In ProcessWire I have created a field, type 'Files', where I can upload the .zip files, one at a time.
      I've selected the option Decompress ZIP files, but any subfolder in the .zip somehow disappears.
      For example the 'assets' folder is gone after I uploaded the .zip file (see the attachment for the file structure).
      I'm curious, is there a way to preserve the file structure inside a .zip file after unzipping it?
      Kind regards,

  • Create New...