0

I'm doing an export function in my plugin but I cannot force a direct download. I get the error:

Cannot modify header information - headers already sent

Apparently at some point before my function fires WordPress has already started printing in the body of the response so I can't change the headers when my function reaches. Is there any way I can go around this and add my headers so I can force a direct download?

I've tried de-registering all scripts on admin_init when the words "direct-download" are found in the URL but apparently the scripts is not the only thing WordPress is printing out into the response body.

Is there a standard way of achieving a direct download in the admin section?

The plugin I'm working on has an MVC format. My Export button is just a simple link (<a>) that leads to my action method:

public function exportRecordsToExcelAction()
{
    // Get parameters
    // Get columns              
    // Get records
    // Excel stuff

    header('Content-type: application/vnd.ms-excel');
    header('Content-Disposition: attachment; filename="Records.xlsx"');

    $objWriter->save('php://output');
}

The headers already sent errors refer to line 958 of wp-includes/script-loader.php. This is where WordPress starts printing out to the response body and ends my ability to change the headers. It's not the only place that does this so obviously trying to circumvent this function does not help in the least.

The annoyingly abstracted architecture that I'm working with is the following.

Folder structure:

pluginFolder
    setup
        admin_actions.php
            add_submenu_page() hooks
    app
        module
            Admin
                controllers
                    AccountController.php

How my we reach my function in AccountController.php:

When I execute any URL in the plugin pages an object in a file in the pluginFolder first executes a function that parses the request URL and from it determines the controller name and action method. Once they're parsed we have the method name in a variable $method and the controller name in a variable called $controller. Then it instantiates the controller object using a factory method like so: $controller = self::getController($controller);. Since now it has the proper controller object and it knows the name of the action method it just does this: $controller->$method(); and the action method (my function in the first code) is executed.

At the moment I can't see how changing any of this would help me since all of this is executed after the headers have already been sent. Maybe the only way to force a download is for me to skip this MVC action method stuff this time and just create a regular function that gets executed when I click the Export button?

  • My guess is you're trying to send the download from within your add_submenu_page callback? Post some relevant code and I'll steer you clear. – TheDeadMedic Jan 15 '16 at 9:47
  • @TheDeadMedic I've edited my question. – Alternatex Jan 15 '16 at 9:58
  • Need moar. When is exportRecordsToExcelAction called? Can you show your hooks/filters/menu code. – TheDeadMedic Jan 15 '16 at 10:01
  • @TheDeadMedic The plugin I'm working on is halfway developed by some other company and apparently before my function even reaches the page (header, footer) is almost completely drawn into the response body. I guess the only way to fix it is to discuss it with them since there's a LOT of code that gets executed before my function gets called. – Alternatex Jan 15 '16 at 10:29
1

It's what I thought - you're trying to send the file from the display callback, by which time it's too late. Look for the add_*_page() call that registers the menu item in the admin sidebar - the return value of this function call is the name of the hook you can use to attach the download function:

$hook = add_submenu_page( $parent, $title, $menu_title, $cap, $menu_slug, $callback );

if ( $hook ) // Will be false if current user does not neccessary permissions
    add_action( "load-$hook", $download_callback );
  • I understood what's happening. Isn't there any way I can force a download from a subpage without doing it in the same place where the add_submenu_page hooks are? The problem is that I can't call the download function there because in order to do that I'd need to reference a thousand classes (db classes, extended classes, controller classes etc.). – Alternatex Jan 15 '16 at 12:11
  • Can you not use a global to reference the instantiated class? I understand your frustration but it's really, really difficult to help when I've seen very little code and have almost no idea about the architecture/functioning of your plugin. – TheDeadMedic Jan 15 '16 at 12:16
  • Added some additional info. – Alternatex Jan 15 '16 at 12:43
  • The very last paragraph in your updated question seems the way to go. Sorry I can't be of any real help. If you were willing to share your codebase on github/similar I could possibly offer an alternative solution – TheDeadMedic Jan 15 '16 at 12:50
  • Thank you for your help. At least now I know what I'm working with :) – Alternatex Jan 15 '16 at 12:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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