2

🖥️ Code

function my_custom_endpoint(WP_REST_Request $request)
{
  $plugin_file = $request->get_param("plugin_file");

  if(!class_exists('Plugin_Upgrader'))
  {
    require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
  }

  ob_start();
  $upgrader = new Plugin_Upgrader();
  $result = $upgrader->upgrade($plugin_file);
  $logs = ob_get_clean();

  // I m testing here if the upgrade is successfull before return the response

  return rest_ensure_response(array("logs" => $logs));
}

â›” The issue

I create a custom WordPress REST endpoint to update some plugin from another website.

The method Plugin_Upgrader::upgrade write a lot of strings in STDOUT, I wanna catch in $logs but when I try buffering it with ob_start I still get it in my http response.

<divclass="wrap">
<h1></h1>
<p>downloading_package</p>
<p>L’authenticitéde<spanclass="code">complianz-gdpr.6.5.4.zip</span>
n’apaspuêtrevérifiéecaraucunesignaturen’aététrouvée.</p>
<p>unpack_package</p>
<p>Installationdeladernièreversion&#8230;</p>
<p>remove_old</p>
<p>process_success</p>
</div>
<b>Warning</b>: Cannotmodifyheaderinformation-headersalreadysentby(outputstartedatC: \xampp\htdocs\oust\wp-includes\functions.php: 5349)in<b>C: \xampp\htdocs\oust\wp-includes\rest-api\class-wp-rest-server.php</b>online<b>1769</b><br/>
{
  'logs' => false
}

🇫🇷 French Logs Sorry

PHP ob_get_clean: If output buffering isn't active then false is returned.

This return seems to be the cause of all my problems, but I don't understand why buffering is deactivate after $upgrader->upgrade($plugin_file) call (I tried replace it with simple echo and the buffering work)..

🙆‍♂️ Expected

{
  "logs": "<divclass="wrap"><h1></h1><p>downloading_package</p><p>L’authenticitéde<spanclass="code">complianz-gdpr.6.5.4.zip</span>n’apaspuêtrevérifiéecaraucunesignaturen’aététrouvée.</p><p>unpack_package</p><p>Installationdeladernièreversion&#8230;</p><p>remove_old</p><p>process_success</p></div>"
}

Thanks in advance for your time and suggestions ❤️

8
  • 1
    can you expand your initial code block? It's unclear what comes before the ob_start or when you're including the various files to get the plugin upgrader class. If there's nothing of note then it's best we can see that to avoid repeated unnecessary questions
    – Tom J Nowell
    Commented Sep 21, 2023 at 13:35
  • Code block updated Commented Sep 21, 2023 at 13:39
  • 1
    thanks, I also noticed that you're not using the rest request object and bypassing it straight to $_POST like this: $plugin_file = $_POST['plugin_file']; is there a reason for this? Would it not be safer/more compatible to do this: function my_custom_endpoint( \WP_REST_Request $request) : \WP_REST_Response|\WP_Error { and $plugin_file = $request['plugin_file'];? The REST API will take care of figuring out if it's GET or POST, using $_POST directly means none of the validation or sanitisation features will work
    – Tom J Nowell
    Commented Sep 21, 2023 at 14:18
  • also have you tried moving the ob_start to before the if statement that requires the file? I can't find anything in the WP source that messes or uses output buffers for upgrading, is it possible somewhere in your code a filter is flushing the output buffer?
    – Tom J Nowell
    Commented Sep 21, 2023 at 14:19
  • 1
    also, you could adjust the plugin upgrader class and replace its skin attribute with an upgrader skin of your own that has no output, the purpose of the upgrader skin is to do all the output and display, the upgrader class itself doesn't output anything, though that doesn't answer your output buffering question
    – Tom J Nowell
    Commented Sep 21, 2023 at 14:53

1 Answer 1

4

✨ Explanation and Solution

Updating the plugin to prevent standard output couldn't be easier. No need to create a custom skin, WordPress already provides one for this case:

Automatic_Upgrader_Skin

Description: This skin is designed to be used when no output is intended, all output is captured and stored for the caller to process and log/email/discard.

After digging a lot I found that after every plugin update the next action is called 'upgrader_process_complete', array('Language_Pack_Upgrader', 'async_upgrade'). This action is called to update other parts such as the language packs.

This action create new instance of Language_Pack_Upgrader with a new custom Language_Pack_Upgrader_Skin which also generates data in the standard output.

But if the WP_Upgrader who call it use and Automatic_Upgrader_Skin the Language_Pack_Upgrader will reuse it.

We therefore only need the following few lines to prevent standard output during an update:

$skin = new Automatic_Upgrader_Skin();
$plugin_upgrader = new Plugin_Upgrader($skin);
$result = $plugin_upgrader->upgrade($name);

đź“Ť Why ob_start and ob_get_clean doesn't work

Skins generated by the "upgrader_process_complete" action call the skin->feekback() method, which in turn calls show_message().

This contains the following lines, which broke my first implementation.

wp_ob_end_flush_all();
flush();

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.