From 22dd00c4c0a1a1e0a0d1fb90c89fd80f54004c63 Mon Sep 17 00:00:00 2001 From: reaper Date: Sat, 15 Aug 2026 06:52:05 -0500 Subject: [PATCH] Add clean-room WordPress connector plugin --- package.json | 1 + .../aurora-dockside-connector.php | 60 +++++++++++++ .../class-aurora-dockside-manifest.php | 58 ++++++++++++ .../includes/class-aurora-dockside-rest.php | 90 +++++++++++++++++++ packages/aurora-dockside-connector/readme.txt | 31 +++++++ scripts/package-connector.cjs | 19 ++++ 6 files changed, 259 insertions(+) create mode 100644 packages/aurora-dockside-connector/aurora-dockside-connector.php create mode 100644 packages/aurora-dockside-connector/includes/class-aurora-dockside-manifest.php create mode 100644 packages/aurora-dockside-connector/includes/class-aurora-dockside-rest.php create mode 100644 packages/aurora-dockside-connector/readme.txt create mode 100644 scripts/package-connector.cjs diff --git a/package.json b/package.json index bc546db..20d47ed 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "dev": "electron-vite dev", "build": "npm run typecheck && electron-vite build", "build:modules": "node scripts/package-modules.cjs", + "build:connector": "node scripts/package-connector.cjs", "module:new": "node scripts/create-module.cjs", "postinstall": "electron-builder install-app-deps", "build:unpack": "npm run build && electron-builder --dir", diff --git a/packages/aurora-dockside-connector/aurora-dockside-connector.php b/packages/aurora-dockside-connector/aurora-dockside-connector.php new file mode 100644 index 0000000..cf53909 --- /dev/null +++ b/packages/aurora-dockside-connector/aurora-dockside-connector.php @@ -0,0 +1,60 @@ + +
+

+

+ + + + + + +
+

+
    +
  1. +
  2. +
  3. +
+

+
+ $scope, + 'cursor' => $cursor, + 'next_cursor' => $cursor + count($slice) < count($files) ? $cursor + count($slice) : null, + 'total' => count($files), + 'files' => $slice, + ); + } + + private static function scan(string $root): array + { + if (!is_dir($root) || !is_readable($root)) { + return array(); + } + $result = array(); + $iterator = new RecursiveIteratorIterator( + new RecursiveCallbackFilterIterator( + new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS), + function (SplFileInfo $current): bool { + return !in_array($current->getFilename(), self::EXCLUDED_PARTS, true) && !$current->isLink(); + } + ), + RecursiveIteratorIterator::LEAVES_ONLY + ); + foreach ($iterator as $file) { + if (!$file->isFile() || !$file->isReadable()) { + continue; + } + $absolute = $file->getPathname(); + $relative = ltrim(str_replace('\\', '/', substr($absolute, strlen($root))), '/'); + $result[] = array( + 'path' => $relative, + 'bytes' => $file->getSize(), + 'modified' => $file->getMTime(), + 'sha256' => hash_file('sha256', $absolute), + ); + } + usort($result, fn(array $a, array $b): int => strcmp($a['path'], $b['path'])); + return $result; + } +} diff --git a/packages/aurora-dockside-connector/includes/class-aurora-dockside-rest.php b/packages/aurora-dockside-connector/includes/class-aurora-dockside-rest.php new file mode 100644 index 0000000..5148654 --- /dev/null +++ b/packages/aurora-dockside-connector/includes/class-aurora-dockside-rest.php @@ -0,0 +1,90 @@ + WP_REST_Server::READABLE, + 'callback' => array(self::class, 'status'), + 'permission_callback' => array(self::class, 'authorize'), + )); + register_rest_route(self::NAMESPACE, '/manifest', array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array(self::class, 'manifest'), + 'permission_callback' => array(self::class, 'authorize'), + 'args' => array( + 'scope' => array('type' => 'string', 'enum' => array('content', 'uploads'), 'default' => 'content'), + 'cursor' => array('type' => 'integer', 'minimum' => 0, 'default' => 0), + 'limit' => array('type' => 'integer', 'minimum' => 1, 'maximum' => 1000, 'default' => 250), + ), + )); + register_rest_route(self::NAMESPACE, '/database', array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array(self::class, 'database'), + 'permission_callback' => array(self::class, 'authorize'), + )); + } + + public static function authorize(): bool + { + return is_user_logged_in() && current_user_can('manage_options'); + } + + public static function status(): WP_REST_Response + { + global $wp_version, $wpdb; + $theme = wp_get_theme(); + return new WP_REST_Response(array( + 'connector' => array('name' => 'Aurora Dockside Connector', 'version' => AURORA_DOCKSIDE_CONNECTOR_VERSION, 'mode' => 'read-only'), + 'site' => array( + 'name' => get_bloginfo('name'), + 'url' => home_url('/'), + 'admin_url' => admin_url('/'), + 'multisite' => is_multisite(), + 'language' => get_locale(), + ), + 'runtime' => array('wordpress' => $wp_version, 'php' => PHP_VERSION, 'database' => $wpdb->db_version()), + 'theme' => array('stylesheet' => $theme->get_stylesheet(), 'version' => $theme->get('Version')), + 'capabilities' => array('status' => true, 'file_manifest' => true, 'database_inventory' => true, 'pull' => false, 'push' => false), + )); + } + + public static function manifest(WP_REST_Request $request): WP_REST_Response + { + $result = Aurora_Dockside_Manifest::page( + (string) $request->get_param('scope'), + (int) $request->get_param('cursor'), + (int) $request->get_param('limit') + ); + return new WP_REST_Response($result); + } + + public static function database(): WP_REST_Response + { + global $wpdb; + $tables = $wpdb->get_col('SHOW TABLES'); + $inventory = array(); + foreach ($tables as $table) { + $safe_table = esc_sql($table); + $rows = (int) $wpdb->get_var("SELECT COUNT(*) FROM `{$safe_table}`"); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $size = $wpdb->get_row($wpdb->prepare( + 'SELECT DATA_LENGTH, INDEX_LENGTH FROM information_schema.TABLES WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s', + DB_NAME, + $table + ), ARRAY_A); + $inventory[] = array( + 'name' => $table, + 'rows' => $rows, + 'bytes' => (int) (($size['DATA_LENGTH'] ?? 0) + ($size['INDEX_LENGTH'] ?? 0)), + ); + } + return new WP_REST_Response(array('engine_version' => $wpdb->db_version(), 'prefix' => $wpdb->prefix, 'tables' => $inventory)); + } +} diff --git a/packages/aurora-dockside-connector/readme.txt b/packages/aurora-dockside-connector/readme.txt new file mode 100644 index 0000000..43a3741 --- /dev/null +++ b/packages/aurora-dockside-connector/readme.txt @@ -0,0 +1,31 @@ +=== Aurora Dockside Connector === +Contributors: auroradockside +Tags: local development, migration, development +Requires at least: 6.5 +Tested up to: 6.8 +Requires PHP: 8.0 +Stable tag: 0.1.0 +License: GPLv2 or later + +An original, secure connector between WordPress and Aurora Dockside. + +== Description == + +Aurora Dockside Connector exposes authenticated site metadata, a paginated file manifest, and a database inventory to Aurora Dockside. Authentication uses WordPress Application Passwords and requires an administrator account. + +Version 0.1 is deliberately read-only. It does not modify the production database or production files. + +== Installation == + +1. Upload the plugin ZIP in Plugins → Add New → Upload Plugin. +2. Activate Aurora Dockside Connector. +3. Open Tools → Aurora Dockside. +4. Create an Application Password from Users → Profile. + +== Changelog == + += 0.1.0 = +* Initial read-only connection API. +* Site and runtime discovery. +* Paginated content and uploads manifests. +* Database table inventory. diff --git a/scripts/package-connector.cjs b/scripts/package-connector.cjs new file mode 100644 index 0000000..2bc0ef6 --- /dev/null +++ b/scripts/package-connector.cjs @@ -0,0 +1,19 @@ +'use strict' + +const { mkdirSync, rmSync } = require('fs') +const { join, resolve } = require('path') +const { spawnSync } = require('child_process') + +const root = resolve(__dirname, '..') +const source = join(root, 'packages', 'aurora-dockside-connector') +const outputDir = join(root, 'dist', 'connectors') +const output = join(outputDir, 'aurora-dockside-connector-0.1.0.zip') +mkdirSync(outputDir, { recursive: true }) +rmSync(output, { force: true }) +const result = spawnSync('zip', ['-qr', output, 'aurora-dockside-connector'], { + cwd: join(root, 'packages'), + stdio: 'inherit' +}) +if (result.error) throw result.error +if (result.status !== 0) throw new Error(`zip failed with exit code ${result.status}`) +process.stdout.write(`${output}\n`)