Skip to content
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

[Data Liberation] Use WordPress for editing static files in a local directory #2098

Draft
wants to merge 15 commits into
base: add-epub-reader
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 97 additions & 20 deletions packages/playground/blueprints/src/lib/steps/activate-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { phpVar } from '@php-wasm/util';
import { StepHandler } from '.';
import { logger } from '@php-wasm/logger';

/**
* @inheritDoc activatePlugin
* @example
Expand Down Expand Up @@ -39,18 +37,18 @@ 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" );
require_once( ${phpVar(docroot)}. "/wp-admin/includes/plugin.php" );
require_once( getenv('DOCROOT') . "/wp-load.php" );
require_once( getenv('DOCROOT') . "/wp-admin/includes/plugin.php" );

// Set current user to admin
wp_set_current_user( get_users(array('role' => 'Administrator') )[0]->ID );

$plugin_path = ${phpVar(pluginPath)};
$plugin_path = getenv('PLUGIN_PATH');
$response = false;
if (!is_dir($plugin_path)) {
if ( ! is_dir( $plugin_path)) {
$response = activate_plugin($plugin_path);
}

Expand All @@ -65,22 +63,101 @@ 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() );
} else if ( false === $response ) {
die( "The activatePlugin step wasn't able to find the plugin $plugin_path." );
}

throw new Exception( 'Unable to activate plugin' );
`,
env: {
PLUGIN_PATH: pluginPath,
DOCROOT: docroot,
},
});
if (result.text !== 'Plugin activated successfully') {
logger.debug(result);
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`
if (activatePluginResult.text) {
logger.warn(
`Plugin ${pluginPath} activation printed the following bytes: ${activatePluginResult.text}`
);
}

/**
* 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
* 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, WordPress will assume it's an activation error and return a WP_Error.
* WordPress will still activate the plugin and load the required page,
* but it will also show the error as a notice in wp-admin.
* 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( getenv( 'DOCROOT' ) . "/wp-load.php" );

/**
* Extracts the relative plugin path from either an absolute or relative plugin path.
*
* Absolute paths starting with plugin directory (e.g., '/wordpress/wp-content/plugins/test-plugin/index.php')
* should be converted to relative paths (e.g., 'test-plugin/index.php')
*
* Directories should finish with a trailing slash to ensure we match the full plugin directory name.
*
* Examples:
* - '/wordpress/wp-content/plugins/test-plugin/index.php' → 'test-plugin/index.php'
* - '/wordpress/wp-content/plugins/test-plugin/' → 'test-plugin/'
* - '/wordpress/wp-content/plugins/test-plugin' → 'test-plugin/'
* - 'test-plugin/index.php' → 'test-plugin/index.php'
* - 'test-plugin/' → 'test-plugin/'
* - 'test-plugin' → 'test-plugin/'
*/
$plugin_directory = WP_PLUGIN_DIR . '/';
$relative_plugin_path = getenv( 'PLUGIN_PATH' );
if (strpos($relative_plugin_path, $plugin_directory) === 0) {
$relative_plugin_path = substr($relative_plugin_path, strlen($plugin_directory));
}

if ( is_dir( $plugin_directory . $relative_plugin_path ) ) {
$relative_plugin_path = rtrim( $relative_plugin_path, '/' ) . '/';
}

$active_plugins = get_option( 'active_plugins' );
foreach ( $active_plugins as $plugin ) {
if ( substr( $plugin, 0, strlen( $relative_plugin_path ) ) === $relative_plugin_path ) {
ob_end_clean();
die( 'true' );
}
}
die( ob_get_flush() ?: 'false' );
`,
env: {
DOCROOT: docroot,
PLUGIN_PATH: pluginPath,
},
});

if (isActiveCheckResult.text === 'true') {
// Plugin activation was successful, yay!
return;
}

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.`
);
};
5 changes: 5 additions & 0 deletions packages/playground/data-liberation-markdown/plugin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php
/**
* Plugin Name: Data Liberation – Markdown importer
*/
require_once __DIR__ . '/src/bootstrap.php';
Loading
Loading