Download all files as ZIP from inputfield file?

4 posts in this topic

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

1 person likes this

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

3 people like this

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;


2 people like this

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 mn-martin
      today I've tried the following:
      Use .htaccess to rewrite the url conditionally if an image file was not found.
      Rewrite target was http://www.this-is-the-live-system.com/site/assets/files/$1
      I guess that would work out great. Unfortunately Processwire checks to see if the file exists and outputs an error message in the Page Editor.
      An option to disable this check would be great. (Similar to $config->debugIf = '::1'; or something)
      It would be great being able to just use the live database locally without broken images all over the place.
      I guess this might be a simple good enough solution for most use cases.
    • By alec
      Hello everyone. I have strange things happening when I want to edit Site translation files. When I try to edit files I got error: " Session: File does not exist: /site\templates\include\footer.inc (translation file not needed? textdomain: site--templates--include--footer-inc) "
      Here I noticed strange division on left. Somehow it is change direction. Can anyone explain to me why this is happening, and can cause this error?

    • By EyeDentify
      Hello There PW fans and users.

      I Thought i share something that i came up with a while ago when i decided to adopt the teachings in this guide:
      This guide has made my development much easier cause i allways was stressed about having all code in one giant file and jumping around trying to keep everything organized and clear.

      So i decided to go with modules, as i call them, having all the CSS belonging to a specific module in one file, and also the responsive media querys that belongs to the module in the same file. Now... so far so good. Turns out that there is a 31 file limit in browsers to how many files you can load with css.

      And also i wanted to make less HTTP requests, so i started thinking, and came up with a simple PHP script that i run on my PW site in a hidden template and when that PHP script runs, it combines/compiles or merges the CSS module files together to one giant file that i link to in my website header.

      This way, there is one HTTP request for all the CSS that runs the site, minus some third party stuff.

      Lets look at the PHP script
      <?PHP /* EyeDentifys CSS File combiner. v1.0 Array of essential files - update as needed. We do this to ensure the order of how the files are combined. You can have as many CSS module files here as you want. Remember that the combined file is compiled in the order they are placed in the array. Remember to check all the paths so they reflect your setup before use. And also, the combined file is overwritten without notice when you re-combine. */ $cssModules[] = 'module_global.css'; $cssModules[] = 'module_table.css'; $cssModules[] = 'module_forms.css'; $cssModules[] = 'module_layout.css'; /* init vars */ $str = ''; $moduleCount = 0; $errorCount = 0; $listFiles = ''; /* add a string with the latest update time and date to the top of the combined file */ $dateStr = date('Y-m-d H:i:s'); $str .= '/* Combined file last updated at ' . $dateStr . ' */' . PHP_EOL; /* go through modules and concatenate them in a string var */ foreach($cssModules AS $key => $module){ /* check if the file exist */ if(file_exists('css/modules/' . $module)){ /* read file data */ $str .= file_get_contents('css/modules/' . $module); /* add module count - used for output in template */ $moduleCount++; /* add the file to list */ $listFiles[] = '[' . $module . ']'; } else { /* if the file do not exist add it to the "do not exist" list */ $listFiles[] = 'Error - file [' . $module . '] do not exist!'; /* increment error count */ $errorCount++; } } /* render the combined css */ echo('<h2>Combined CSS</h2><pre class="code-view-pre">' . $str . '</pre>'); /* list combined files */ echo('<h2>Combined files</h2>'); echo('<ul class="unstyled-list">'); foreach($listFiles AS $key => $file) { echo('<li>' . $file . '</li>'); } echo('</ul>'); echo('<p>Number of file errors <strong>' . $errorCount . '</strong> st.</p>'); /* the file name defaults */ $combinedFileName = 'css/combined_styles.css'; $backupFileName = 'css/backups/backup_styles_' . date("Y-m-d_H-i-s") . '.css'; /* backup the old combined file before updating it */ copy($combinedFileName, $backupFileName); echo('<p>Backup created to file: <strong>' . $backupFileName . '</strong></p>'); /* create update the combined css file */ file_put_contents($combinedFileName, $str); echo('<p>Combined css file: <strong>' . $combinedFileName . '</strong> updated!</p>'); echo('<p><strong>' . $moduleCount . '</strong> files combined.</p>'); ?>
      Like i said above, i have this in a hidden Template file that i run when logged in as ADMIN. 
      I just refresh the page everytime i made a change and uploaded to the server so the new changes is combined into the big file and can be seen on the website.

      My script assumes the following directory structure:
      1. This is where the script looks for the module files:
      templates -> css -> modules
      2. This is where the script saves the combined big CSS file:
      templates -> css
      3. This is where the script saves a backup of the current big CSS file before creating and writing over the new big file:
      templates -> css -> backups

      You need to go through the script and change the hardcoded paths to suit your needs before use.
      Also please try this out at some test site first so you get the hang of it. Be safe.

      This way of dealing with alot of CSS code has realy made my life much easier.
      Cause if i want to change some modules CSS i just open that file, make the changes, upload the module, and run the script and its compiled in with the other CSS in the big file.

      I am sure you creative fellows on the forums can make refinements to this code, but it has served me well.

      Hope it helps someone.
    • By mjut
      Hello Forum,
      I am relatively new to processwire and have been playing around with some simple websites so far with it.
      I love the simplicity and the straight forward approach to it. I felt "at home" right away! =D
      The only thing, I just can not get done is implementing a simple post-form in a template. (front-end)
      I want to get registered users to create content via a simple form. Yes, there are several tutorials in this forum.
      I want the file upload to look like the upload in the back-end:
      - multiple upload
      - rearrange order of files
      Is that hard to build? It should be "there"... or do i need to install a module like https://processwire.com/talk/topic/12050-module-jquery-file-upload/
      I would like to build it with core functionality.
      What do you guys think? Can you point me into the right direction?
    • By isellsoap
      PW version 2.7.1 dev template cache is disabled Chrome Canary is opened with disabled cache and in inkognito view mode When I make a change to a template file (say the basic home.php) I want to immediately see that change after refreshing the browser. Example:
      This is the content of home.php:
      <?php echo "bla"; I browse to the root page and see the "bla" output.
      I change the code to
      <?php echo "bla1"; and save the file. After that I immediately reload the page in the browser and I don’t see the change to "bla1". Only after multiple browser reloads (at times also more than 20 reloads) and "hard" reloads does the change appear. 
      What I tried to do:
      set $config->dbCache, $config->sessionFingerprint and $config->sessionChallenge to false in config.php delete all sessions re-save the page in the ProcessWire backend and reload the browser page completely delete the browser history (cookies, cache, everything) after the first visit to the page Nothing helped.
      Can someone replicate this behavior? What can I do to achieve the desired behavior? Thanks a lot in advance for any help.