-
Notifications
You must be signed in to change notification settings - Fork 277
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Blueprints] Prevent plugin activation error if plugin redirects during activation or produces an output #2066
Changes from 11 commits
6fed4da
2ca6335
2f45f25
3d69181
d1ee3c9
1747574
75cd7b4
a46d412
7204067
7aa4329
aaf683d
d255141
d351a63
cf2a580
a3eccc6
8627934
fd4cb60
7414381
2dd659e
9b148b2
622099d
d7e7827
56cd875
2efc8e2
dde935a
c0dd231
0c09fc0
e27c6fb
bb24b49
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,6 @@ | ||
import { phpVar } from '@php-wasm/util'; | ||
import { StepHandler } from '.'; | ||
import { logger } from '@php-wasm/logger'; | ||
|
||
/** | ||
* @inheritDoc activatePlugin | ||
* @example | ||
|
@@ -39,7 +38,7 @@ export const activatePlugin: StepHandler<ActivatePluginStep> = async ( | |
progress?.tracker.setCaption(`Activating ${pluginName || pluginPath}`); | ||
|
||
const docroot = await playground.documentRoot; | ||
const result = await playground.run({ | ||
const activatePluginResult = await playground.run({ | ||
code: `<?php | ||
define( 'WP_ADMIN', true ); | ||
require_once( ${phpVar(docroot)}. "/wp-load.php" ); | ||
|
@@ -65,22 +64,61 @@ export const activatePlugin: StepHandler<ActivatePluginStep> = async ( | |
} | ||
} | ||
|
||
if ( null === $response ) { | ||
die('Plugin activated successfully'); | ||
} else if ( is_wp_error( $response ) ) { | ||
throw new Exception( $response->get_error_message() ); | ||
if (is_wp_error($response)) { | ||
die($response->get_error_message()); | ||
} | ||
`, | ||
}); | ||
|
||
/** | ||
* Instead of checking the plugin activation response, | ||
* check if the plugin is active by looking at the active plugins list. | ||
* | ||
* We have to split the activation and the check into two PHP runs | ||
bgrgicak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* because some plugins might redirect during activation, | ||
* which would prevent any output that happens after activation from being returned. | ||
* | ||
* Relying on the plugin activation response is not reliable because if the plugin activation | ||
* produces any output, it will be threaded as an error. | ||
bgrgicak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* See WordPress source code for more details: | ||
* https://github.com/WordPress/wordpress-develop/blob/6.7/src/wp-admin/includes/plugin.php#L733 | ||
* | ||
* Because some plugins can create an output, we need to use output buffering | ||
* to ensure the 'true' response is not polluted by other outputs. | ||
* If the plugin activation fails, we will return the buffered output as it might | ||
* contain more information about the failure. | ||
*/ | ||
const isActiveCheckResult = await playground.run({ | ||
code: `<?php | ||
ob_start(); | ||
require_once( ${phpVar(docroot)}. "/wp-load.php" ); | ||
|
||
throw new Exception( 'Unable to activate plugin' ); | ||
$relative_plugin_path = str_replace( ${phpVar( | ||
bgrgicak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
docroot | ||
)}. "/wp-content/plugins/", '', ${phpVar(pluginPath)} ); | ||
$active_plugins = get_option( 'active_plugins' ); | ||
foreach ( $active_plugins as $plugin ) { | ||
if ( strpos( $plugin, $relative_plugin_path ) === 0 ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would yield a false positive if one plugin is called There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How would this happen? We required absolute or relative plugin paths for plugin activation, not slugs.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My suggestion for the whole section is to be more explicit on why you use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have a working solution, but it's not clean. Let me think a bit more about it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I update the is active check to either match the full Let me know what you think of the implementation https://github.com/WordPress/wordpress-playground/pull/2066/files#diff-4a02b123bb6311fff3ec089ca1ade75c75f1af35aaa4f1a717f14d444fc9b485R101-R147 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is looking really good, thank you @bgrgicak! I left a few minor nitpicks, no major changes needed. |
||
ob_end_clean(); | ||
die('true'); | ||
} | ||
} | ||
die(ob_get_flush() ?? 'false'); | ||
bgrgicak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
`, | ||
}); | ||
if (result.text !== 'Plugin activated successfully') { | ||
logger.debug(result); | ||
|
||
if (activatePluginResult.text) { | ||
logger.warn(activatePluginResult.text); | ||
bgrgicak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
if (isActiveCheckResult.text !== 'true') { | ||
bgrgicak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (isActiveCheckResult.text !== 'false') { | ||
logger.debug(isActiveCheckResult.text); | ||
} | ||
throw new Error( | ||
`Plugin ${pluginPath} could not be activated – WordPress exited with no error. ` + | ||
`Sometimes, when $_SERVER or site options are not configured correctly, ` + | ||
`WordPress exits early with a 301 redirect. ` + | ||
`Inspect the "debug" logs in the console for more details` | ||
`Inspect the "debug" logs in the console for more details.` | ||
); | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would it mean when
$response
isnull
orfalse
and how should we act in each of these cases? Just ignore it and rely on the check below? Also, why replacethrow
withdie()
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When using
throw
a WP_Error caused by the plugin activation would cause PHP to fail with a 255 response code and we wouldn't be able to read the error message AFAIK.We would also need to use
try/catch
for the activation code which would be less elegant, especially in a case where we don't consider all WP_Errors created by the the activation process to be errors for us.More details about why we don't consider all WP_Errors to be activation errors are documented here https://github.com/WordPress/wordpress-playground/pull/2066/files/aaf683d47f401d18f9958a8437b7abe4f428d915#diff-4a02b123bb6311fff3ec089ca1ade75c75f1af35aaa4f1a717f14d444fc9b485R81
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
null
is success, and I deliberately left the response empty. Otherwise it would be printed to logs (or we would need to account for it in a if) and it doesn't provide any value.false
means the plugin couldn't be found by theactivatePlugin
step. I added a check for it now in 8627934. The error message I added is is different from the error message provided by WordPress when a plugin isn't found so that we will be able to distinguish between these two.