diff --git a/data-machine-code.php b/data-machine-code.php index 406200c..51c094d 100644 --- a/data-machine-code.php +++ b/data-machine-code.php @@ -6,7 +6,6 @@ * Version: 0.47.56 * Requires at least: 6.9 * Requires PHP: 8.2 - * Requires Plugins: data-machine * Author: Chris Huber, extrachill * Author URI: https://chubes.net * License: GPL v2 or later @@ -25,10 +24,6 @@ // PSR-4 Autoloading. require_once __DIR__ . '/vendor/autoload.php'; -// Bundle artifact types must be registered as soon as the plugin is loaded so -// Data Machine can validate DMC-owned artifacts during early import paths. -( new \DataMachineCode\Bundle\WorkspacePreloadArtifact() )->register(); - /** * Install DMC-owned database tables. */ @@ -63,12 +58,46 @@ function datamachine_code_maybe_upgrade_schema(): void { } add_action('plugins_loaded', 'datamachine_code_maybe_upgrade_schema', 5); +/** + * Whether Data Machine-specific integration surfaces are available. + */ +function datamachine_code_has_datamachine_integration(): bool { + return class_exists('DataMachine\Abilities\PermissionHelper'); +} + +/** + * Register optional Data Machine integrations. + */ +function datamachine_code_register_datamachine_integrations(): void { + static $registered = false; + + if ( $registered || ! datamachine_code_has_datamachine_integration() ) { + return; + } + + $registered = true; + + // Bundle artifact types are Data Machine-specific and are only registered + // when the bundle importer substrate is present. + ( new \DataMachineCode\Bundle\WorkspacePreloadArtifact() )->register(); + + // Project active workspace identity into Data Machine's engine_data snapshot. + \DataMachineCode\Runtime\ActiveWorkspaceProjector::register(); + + if ( class_exists('DataMachine\Core\Steps\HandlerRegistrationTrait') ) { + new \DataMachineCode\Handlers\GitHub\GitHub(); + new \DataMachineCode\Handlers\GitHub\GitHubIssuePublish(); + new \DataMachineCode\Handlers\GitHub\GitHubPullRequestPublish(); + new \DataMachineCode\Handlers\GitHub\GitHubUpsert(); + } +} + /** * Bootstrap the plugin after all plugins are loaded. * - * Data Machine core must be active — check at plugins_loaded time - * (not at plugin load time, since load order is alphabetical and - * data-machine-code loads before data-machine). + * Core workspace/GitHub/Agents API surfaces do not require Data Machine. + * Data Machine-specific handlers and memory/runtime hooks register through + * datamachine_code_register_datamachine_integrations() when available. */ function datamachine_code_bootstrap() { static $bootstrapped = false; @@ -77,22 +106,6 @@ function datamachine_code_bootstrap() { return; } - if ( ! class_exists('DataMachine\Abilities\PermissionHelper') ) { - add_action('init', 'datamachine_code_bootstrap', 1); - add_action('wp_abilities_api_init', 'datamachine_code_bootstrap', 1); - - add_action( - 'admin_notices', function () { - ?> -
-

-
- to_array() : get_object_vars($principal)); - if ( '' !== $agent_slug ) { - return $agent_slug; - } + $principal = PermissionHelper::get_execution_principal(); + if ( is_object($principal) ) { + $agent_slug = self::agentSlugFromContext(method_exists($principal, 'to_array') ? $principal->to_array() : get_object_vars($principal)); + if ( '' !== $agent_slug ) { + return $agent_slug; } } - if ( method_exists(PermissionHelper::class, 'get_acting_agent_slug') ) { - $agent_slug = PermissionHelper::get_acting_agent_slug(); - if ( is_string($agent_slug) && '' !== trim($agent_slug) ) { - return sanitize_text_field($agent_slug); - } + $agent_slug = PermissionHelper::get_acting_agent_slug(); + if ( '' !== trim($agent_slug) ) { + return sanitize_text_field($agent_slug); } return ''; diff --git a/inc/Abilities/GitSyncAbilities.php b/inc/Abilities/GitSyncAbilities.php index 5a27c1d..fb514ae 100644 --- a/inc/Abilities/GitSyncAbilities.php +++ b/inc/Abilities/GitSyncAbilities.php @@ -17,7 +17,7 @@ namespace DataMachineCode\Abilities; -use DataMachine\Abilities\PermissionHelper; +use DataMachineCode\Support\PermissionHelper; use DataMachineCode\GitSync\GitSync; defined('ABSPATH') || exit; diff --git a/inc/Abilities/WordPressRuntimeAbilities.php b/inc/Abilities/WordPressRuntimeAbilities.php index 5dec368..170f52f 100644 --- a/inc/Abilities/WordPressRuntimeAbilities.php +++ b/inc/Abilities/WordPressRuntimeAbilities.php @@ -7,7 +7,7 @@ namespace DataMachineCode\Abilities; -use DataMachine\Abilities\PermissionHelper; +use DataMachineCode\Support\PermissionHelper; use DataMachineCode\Runtime\WordPressRuntimeInspector; defined('ABSPATH') || exit; diff --git a/inc/Abilities/WorkspaceAbilities.php b/inc/Abilities/WorkspaceAbilities.php index 604c8c3..ccc05b9 100644 --- a/inc/Abilities/WorkspaceAbilities.php +++ b/inc/Abilities/WorkspaceAbilities.php @@ -14,7 +14,7 @@ namespace DataMachineCode\Abilities; -use DataMachine\Abilities\PermissionHelper; +use DataMachineCode\Support\PermissionHelper; use DataMachineCode\Workspace\CleanupRunService; use DataMachineCode\Workspace\RemoteWorkspaceBackend; use DataMachineCode\Workspace\Workspace; diff --git a/inc/Abilities/WorkspaceDiffAbilities.php b/inc/Abilities/WorkspaceDiffAbilities.php index 8891195..c47e120 100644 --- a/inc/Abilities/WorkspaceDiffAbilities.php +++ b/inc/Abilities/WorkspaceDiffAbilities.php @@ -7,7 +7,7 @@ namespace DataMachineCode\Abilities; -use DataMachine\Abilities\PermissionHelper; +use DataMachineCode\Support\PermissionHelper; use DataMachineCode\Workspace\RemoteWorkspaceBackend; use DataMachineCode\Workspace\WorkspaceDiff; diff --git a/inc/Support/GitHubCredentialResolver.php b/inc/Support/GitHubCredentialResolver.php index fc07454..1f7376a 100644 --- a/inc/Support/GitHubCredentialResolver.php +++ b/inc/Support/GitHubCredentialResolver.php @@ -32,7 +32,7 @@ namespace DataMachineCode\Support; -use DataMachine\Core\PluginSettings; +use DataMachineCode\Support\PluginSettings; defined('ABSPATH') || exit; @@ -197,7 +197,13 @@ public static function mintJwt( string $app_id, string $private_key, int $now ): 'iss' => $app_id, ); - $unsigned = self::base64UrlEncode(wp_json_encode($header)) . '.' . self::base64UrlEncode(wp_json_encode($payload)); + $header_json = wp_json_encode($header); + $payload_json = wp_json_encode($payload); + if ( ! is_string($header_json) || ! is_string($payload_json) ) { + return new \WP_Error('github_app_jwt_encode_failed', 'GitHub App JWT payload could not be encoded.', array( 'status' => 500 )); + } + + $unsigned = self::base64UrlEncode($header_json) . '.' . self::base64UrlEncode($payload_json); $signature = ''; // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- openssl_sign emits warnings for invalid keys; callers receive a WP_Error below. $ok = @openssl_sign($unsigned, $signature, $private_key, OPENSSL_ALGO_SHA256); diff --git a/inc/Support/GitHubCredentialSettingsMigration.php b/inc/Support/GitHubCredentialSettingsMigration.php index d1412a2..c9355a4 100644 --- a/inc/Support/GitHubCredentialSettingsMigration.php +++ b/inc/Support/GitHubCredentialSettingsMigration.php @@ -7,7 +7,7 @@ namespace DataMachineCode\Support; -use DataMachine\Core\PluginSettings; +use DataMachineCode\Support\PluginSettings; defined('ABSPATH') || exit; diff --git a/inc/Support/PermissionHelper.php b/inc/Support/PermissionHelper.php new file mode 100644 index 0000000..5359450 --- /dev/null +++ b/inc/Support/PermissionHelper.php @@ -0,0 +1,65 @@ + */ + public static function get_runtime_context(): array { + $class = self::data_machine_permission_helper_class(); + $callback = array( $class, 'get_runtime_context' ); + if ( is_callable($callback) ) { + $context = call_user_func($callback); + return is_array($context) ? $context : array(); + } + + return array(); + } + + public static function get_execution_principal(): mixed { + $class = self::data_machine_permission_helper_class(); + $callback = array( $class, 'get_execution_principal' ); + if ( is_callable($callback) ) { + return call_user_func($callback); + } + + return null; + } + + public static function get_acting_agent_slug(): string { + $class = self::data_machine_permission_helper_class(); + $callback = array( $class, 'get_acting_agent_slug' ); + if ( is_callable($callback) ) { + $agent_slug = call_user_func($callback); + return is_string($agent_slug) ? $agent_slug : ''; + } + + return ''; + } + + private static function data_machine_permission_helper_class(): string { + $class = apply_filters('datamachine_code_datamachine_permission_helper_class', '\\DataMachine\\Abilities\\PermissionHelper'); + return is_string($class) ? $class : ''; + } +} diff --git a/inc/Support/PluginSettings.php b/inc/Support/PluginSettings.php new file mode 100644 index 0000000..7a207e4 --- /dev/null +++ b/inc/Support/PluginSettings.php @@ -0,0 +1,35 @@ +code; } + public function get_error_message(): string { return $this->message; } + public function get_error_data(): mixed { return $this->data; } +} + +function dmc_standalone_add_hook( array &$store, string $hook, callable $callback, int $priority, int $accepted_args ): void { + $store[ $hook ][ $priority ][] = compact('callback', 'accepted_args'); +} + +function add_action( string $hook, callable $callback, int $priority = 10, int $accepted_args = 1 ): void { + dmc_standalone_add_hook($GLOBALS['dmc_standalone_actions'], $hook, $callback, $priority, $accepted_args); +} + +function add_filter( string $hook, callable $callback, int $priority = 10, int $accepted_args = 1 ): void { + dmc_standalone_add_hook($GLOBALS['dmc_standalone_filters'], $hook, $callback, $priority, $accepted_args); +} + +function do_action( string $hook, ...$args ): void { + $GLOBALS['dmc_standalone_did_action'][ $hook ] = ( $GLOBALS['dmc_standalone_did_action'][ $hook ] ?? 0 ) + 1; + $callbacks = $GLOBALS['dmc_standalone_actions'][ $hook ] ?? array(); + ksort($callbacks); + foreach ( $callbacks as $priority_callbacks ) { + foreach ( $priority_callbacks as $entry ) { + call_user_func($entry['callback'], ...array_slice($args, 0, (int) $entry['accepted_args'])); + } + } +} + +function apply_filters( string $hook, $value, ...$args ) { + $callbacks = $GLOBALS['dmc_standalone_filters'][ $hook ] ?? array(); + ksort($callbacks); + foreach ( $callbacks as $priority_callbacks ) { + foreach ( $priority_callbacks as $entry ) { + $value = call_user_func($entry['callback'], $value, ...array_slice($args, 0, (int) $entry['accepted_args'] - 1)); + } + } + return $value; +} + +function did_action( string $hook ): int { return (int) ( $GLOBALS['dmc_standalone_did_action'][ $hook ] ?? 0 ); } +function doing_action( string $hook ): bool { return did_action($hook) > 0; } +function register_activation_hook( string $file, callable $callback ): void { unset($file, $callback); } +function plugin_dir_path( string $file ): string { return dirname($file) . '/'; } +function plugin_dir_url( string $file ): string { return 'https://example.test/plugins/' . basename(dirname($file)) . '/'; } +function wp_installing(): bool { return false; } +function get_option( string $name, mixed $default = false ): mixed { return $GLOBALS['dmc_standalone_options'][ $name ] ?? $default; } +function update_option( string $name, mixed $value, mixed $autoload = null ): bool { unset($autoload); $GLOBALS['dmc_standalone_options'][ $name ] = $value; return true; } +function current_user_can( string $capability ): bool { unset($capability); return true; } +function wp_register_ability_category( string $category, array $args ): void { $GLOBALS['dmc_standalone_categories'][ $category ] = $args; } +function wp_register_ability( string $ability, array $args ): void { $GLOBALS['dmc_standalone_abilities'][ $ability ] = $args; } +function wp_has_ability( string $ability ): bool { return isset($GLOBALS['dmc_standalone_abilities'][ $ability ]); } +function wp_json_encode( mixed $value ): string|false { return json_encode($value); } +function is_wp_error( mixed $value ): bool { return $value instanceof WP_Error; } +function sanitize_text_field( mixed $value ): string { return trim((string) $value); } +function sanitize_key( mixed $value ): string { return strtolower(preg_replace('/[^a-zA-Z0-9_\-]/', '', (string) $value) ?? ''); } +function __( string $text, string $domain = 'default' ): string { unset($domain); return $text; } +function esc_html_e( string $text, string $domain = 'default' ): void { unset($domain); echo htmlspecialchars($text, ENT_QUOTES); } +function wp_parse_url( string $url, int $component = -1 ): mixed { return parse_url($url, $component); } + +require __DIR__ . '/../data-machine-code.php'; + +do_action('plugins_loaded'); +do_action('wp_abilities_api_categories_init'); +do_action('wp_abilities_api_init'); + +$failures = array(); +$assert = static function ( string $label, bool $condition ) use ( &$failures ): void { + echo ( $condition ? ' ok ' : ' fail ' ) . $label . "\n"; + if ( ! $condition ) { + $failures[] = $label; + } +}; + +echo "Data Machine Code standalone bootstrap - smoke\n"; + +$assert('Data Machine PermissionHelper is absent', ! class_exists('DataMachine\\Abilities\\PermissionHelper')); +$assert('DMC version constant is defined', defined('DATAMACHINE_CODE_VERSION')); +$assert('workspace category registers', isset($GLOBALS['dmc_standalone_categories']['datamachine-code-workspace'])); +$assert('GitHub category registers', isset($GLOBALS['dmc_standalone_categories']['datamachine-code-github'])); +$assert('workspace ability registers', isset($GLOBALS['dmc_standalone_abilities']['datamachine-code/workspace-read'])); +$assert('GitHub ability registers', isset($GLOBALS['dmc_standalone_abilities']['datamachine-code/list-github-issues'])); +$assert('GitSync ability registers', isset($GLOBALS['dmc_standalone_abilities']['datamachine-code/gitsync-status'])); +$assert('code task ability registers', isset($GLOBALS['dmc_standalone_abilities']['datamachine-code/create-code-task'])); +$assert('permission facade allows core callbacks', \DataMachineCode\Support\PermissionHelper::can_manage()); +$assert('settings facade falls back to options', 'fallback' === \DataMachineCode\Support\PluginSettings::get('missing_setting', 'fallback')); + +if ( ! empty($failures) ) { + echo "\nFAIL: " . count($failures) . " assertion(s) failed\n"; + exit(1); +} + +echo "\nOK\n";