-1

I'm trying to write a method in a php class that will use ajax to execute a php function that will push a file back to the browser.

It seems like its trying to write the file to the modx log, getting a lot of binary garbage in there.

Here is the method:

public function pushDocuments($formdata){

    $data = $formdata['formdata'];

    $file = MODX_PROTECTED_STORAGE . $data['target'];

    $file_name = basename($file);

    if (file_exists($file)) {

        header("Content-Disposition: attachment; filename=\"$file_name\"");
        header("Content-Length: " . filesize($file));
        header("Content-Type: application/octet-stream;");
        readfile($file);

    };

    $output = array(

        'status' => 'success',
        'error_messages' => array(),
        'success_messages' => array(),

    );

    $output = $this->modx->toJSON($output);

    return $output;

}

and here is the jquery:

$('.btn-get-document').click(function(){

    var target = $(this).attr('data-target');
    var postdata = {"snippet":"DataSync", "function":"pushDocuments", "target": target}; // data object ~ not json!!
    console.log('target = ' + target + postdata );

    $.ajax({
        type: "POST",
        url: "processors/processor.ajax.generic/",
        dataType  : "json",
        cache : false,
        data: postdata, // posting object, not json
        success: function(data){
            if(data.status == 'success'){
                console.log("SUCCESS status posting data");
            }else if(data.status == 'error'){
                console.log("error status posting data");
            }

        },
        error: function(data){
                console.log("FATAL: error posting data");
            }
    });
});

it's running through the scripts and giving a success in the console [because I am forcing success] but no file is prompted for download and the binary garbage shows up in the modx log

What am I doing wrong?

Sean Kimball
  • 4,506
  • 9
  • 42
  • 73
  • 1
    Isn't the problem that your jquery request is receiving the response, not the parent browser window? – snow_FFFFFF Jul 29 '15 at 21:52
  • 1
    it looks like you're trying to send both a json response and a file to the browser at the same time. that's not gonna work. – Kevin B Jul 29 '15 at 22:11

2 Answers2

3

In order to download a file, you'd have to use JS to redirect to the file's location. You can't pull the file contents through AJAX and direct the browser to save those contents as a file.

You would need to structurally change your setup. For instance, your PHP script can verify the existence of the file to be downloaded, then send a link to JS in order to download the file. Something like this:

if ( file_exists( $file )) {
    $success_message = array(
        'file_url' => 'http://example.com/file/to/download.zip'
   );
}

$output = array(
    'status'           => 'success',
    'error_messages'   => array(),
    'success_messages' => $success_message
);

Then modify the "success" portion of your AJAX return like this:

success: function( data ) {
    if ( data.status == 'success' ) {
        location.href = data.success_messages.file_url;
    } else if ( data.status == 'error' ) {
        console.log( 'error status posting data' );
    }
},

Since you're directing to a file, the browser window won't actually go anywhere, so long as the file's content-disposition is set to attachment. Typically this would happen if you directed to any file the browser didn't internally handle (like a ZIP file). If you want control over this so that it downloads all files (including things the browser may handle with plugins), you can direct to another PHP script that would send the appropriate headers and then send the file (similar to the way you're sending the headers and using readfile() in your example).

Nick Coons
  • 3,682
  • 1
  • 19
  • 21
  • Or just post a hidden form to an iframe – Steve Jul 29 '15 at 22:16
  • Hmmm, the problem with that is that the files are in a non web accessible directory. I can't just forward a link to the file. But it seems like the root of the matter is that I can't push a file through an ajax response ... correct? – Sean Kimball Jul 30 '15 at 00:41
  • @SeanKimball, the last sentence of my answer will address this. Create a PHP script that can take some reference to the file (it could be the filename, but however you're setup to be able to identify it). Have that script output the headers and use `readfile()` to send the data. Instead of returning a link to the file to your AJAX, return a link to this PHP script with the appropriate identifier. – Nick Coons Jul 30 '15 at 01:44
0

@sean-kimball,

You might want to extend MODX's class based processor instead:

https://github.com/modxcms/revolution/blob/master/core/model/modx/processors/browser/file/download.class.php

It does the download from any media source and also access checking if you want.

Its implementation on manager side is: https://github.com/modxcms/revolution/blob/master/manager/assets/modext/widgets/system/modx.tree.directory.js#L553

Back to your case, these examples might bring you some ideas.

JS Example:

$.ajax({
    type: "POST",
    // read my note down below about connector file
    url: "assets/components/mypackage/connectors/web.php", 
    dataType  : "json",
    cache : false,
    data: {
        action: 'mypath/to/processor/classfile'
    }
    success: function(data){

    },
    error: function(data){
        console.log("FATAL: error posting data");
    }
});

Processor example:

<?php

require_once MODX_CORE_PATH . 'model/modx/processors/browser/file/download.class.php';

class myDownloadProcessor extends modBrowserFileDownloadProcessor {
    // override things in here
}

return 'myDownloadProcessor';

For this, I also suggest you to use MODX's index.php main file as the AJAX's connector so the $modx object in processor inherits the access permission as well.

http://www.virtudraft.com/blog/ajaxs-connector-file-using-modxs-main-index.php.html

goldsky
  • 801
  • 7
  • 11