From 25d50912f62ad503cd3b7940207b1ac7712bc4f6 Mon Sep 17 00:00:00 2001 From: Vladimir Bryzgalov Date: Sun, 17 May 2026 12:20:31 +0500 Subject: [PATCH] feat(wpmc S7): Plugin::register_update_checker wires PUC v5.6 Co-Authored-By: Claude Opus 4.7 --- includes/class-plugin.php | 18 ++++++ tests/PluginBootS7Test.php | 46 +++++++++++++++ tests/PluginBootTest.php | 3 + tests/bootstrap.php | 113 +++++++++++++++++++++++++++++++++++++ 4 files changed, 180 insertions(+) create mode 100644 tests/PluginBootS7Test.php diff --git a/includes/class-plugin.php b/includes/class-plugin.php index fc82aa2..bdd2968 100644 --- a/includes/class-plugin.php +++ b/includes/class-plugin.php @@ -58,6 +58,24 @@ final class Plugin TitleFilter::register(); LinkFilter::register(); RestUrlFilter::register(); + self::register_update_checker(); + } + + private static function register_update_checker(): void + { + $loader = WPMC_PLUGIN_DIR . 'includes/lib/plugin-update-checker/plugin-update-checker.php'; + if (!\is_file($loader)) { + return; + } + require_once $loader; + if (!\class_exists('YahnisElsts\\PluginUpdateChecker\\v5\\PucFactory')) { + return; + } + \YahnisElsts\PluginUpdateChecker\v5\PucFactory::buildUpdateChecker( + 'https://git.netranking.ru/bryzgalov/wp-multi-city/raw/branch/main/wp-plugin-info.json', + WPMC_PLUGIN_FILE, + 'wp-multi-city' + ); } private function dependencies_satisfied(): bool diff --git a/tests/PluginBootS7Test.php b/tests/PluginBootS7Test.php new file mode 100644 index 0000000..8e5168b --- /dev/null +++ b/tests/PluginBootS7Test.php @@ -0,0 +1,46 @@ +getProperty('booted'); + $prop->setValue($plugin, false); + } + + public function test_boot_loads_puc_factory(): void + { + if (!class_exists('ACF', false)) { + eval('class ACF {}'); + } + + Plugin::instance()->boot(); + + self::assertTrue( + class_exists('YahnisElsts\\PluginUpdateChecker\\v5\\PucFactory'), + 'boot() should require PUC loader and make PucFactory available' + ); + } + + public function test_boot_double_call_is_idempotent(): void + { + if (!class_exists('ACF', false)) { + eval('class ACF {}'); + } + + Plugin::instance()->boot(); + Plugin::instance()->boot(); + + self::assertTrue(true, 'Double boot must not throw'); + } +} diff --git a/tests/PluginBootTest.php b/tests/PluginBootTest.php index acb9e51..ea35668 100644 --- a/tests/PluginBootTest.php +++ b/tests/PluginBootTest.php @@ -80,6 +80,9 @@ final class PluginBootTest extends TestCase \Brain\Monkey\Actions\expectAdded('wp') ->with([\WPMultiCity\Router::class, 'route'], 9999) ->once(); + // plugin-update-checker registers its text-domain loader on init + \Brain\Monkey\Actions\expectAdded('init') + ->once(); $plugin->boot(); $plugin->boot(); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index c5f8219..3117d79 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -7,6 +7,119 @@ if (!defined('ABSPATH')) { define('ABSPATH', __DIR__ . '/'); } +// WordPress path constants needed by plugin-update-checker +if (!defined('WP_PLUGIN_DIR')) { + define('WP_PLUGIN_DIR', dirname(__DIR__, 2)); +} +if (!defined('WPMU_PLUGIN_DIR')) { + define('WPMU_PLUGIN_DIR', dirname(__DIR__, 2) . '/mu-plugins'); +} +if (!defined('WP_DEBUG')) { + define('WP_DEBUG', false); +} + +// WPMC plugin constants (normally defined in wp-multi-city.php when running under WordPress) +if (!defined('WPMC_PLUGIN_DIR')) { + define('WPMC_PLUGIN_DIR', dirname(__DIR__) . '/'); +} +if (!defined('WPMC_PLUGIN_FILE')) { + define('WPMC_PLUGIN_FILE', dirname(__DIR__) . '/wp-multi-city.php'); +} +if (!defined('WPMC_VERSION')) { + define('WPMC_VERSION', '1.0.0'); +} + +// WP function shims needed by plugin-update-checker at runtime. +// NOTE: add_action, add_filter, apply_filters, do_action, has_action, has_filter, +// is_admin, current_user_can are provided by Brain Monkey — do NOT define them here. +if (!function_exists('wp_parse_url')) { + function wp_parse_url(string $url, int $component = -1) { + return parse_url($url, $component); + } +} +if (!function_exists('wp_normalize_path')) { + function wp_normalize_path(string $path): string { + $path = str_replace('\\', '/', $path); + $path = preg_replace('|(?<=.)/+|', '/', $path); + return $path; + } +} +if (!function_exists('esc_html')) { + function esc_html(string $s): string { + return htmlspecialchars($s, ENT_QUOTES, 'UTF-8'); + } +} +if (!function_exists('get_file_data')) { + function get_file_data(string $file, array $headers): array { + $data = []; + foreach ($headers as $key => $header) { + $data[$key] = ''; + } + return $data; + } +} +if (!function_exists('get_option')) { + function get_option(string $key, $default = false) { + return $default; + } +} +if (!function_exists('update_option')) { + function update_option(string $key, $value, $autoload = null): bool { + return true; + } +} +if (!function_exists('set_transient')) { + function set_transient(string $key, $value, int $expiry = 0): bool { + return true; + } +} +if (!function_exists('get_transient')) { + function get_transient(string $key) { + return false; + } +} +if (!function_exists('delete_transient')) { + function delete_transient(string $key): bool { + return true; + } +} +if (!function_exists('wp_next_scheduled')) { + function wp_next_scheduled(string $hook, array $args = []) { + return false; + } +} +if (!function_exists('wp_schedule_event')) { + function wp_schedule_event(int $timestamp, string $recurrence, string $hook, array $args = []): bool { + return true; + } +} +if (!function_exists('is_wp_error')) { + function is_wp_error($thing): bool { + return false; + } +} +if (!function_exists('plugin_basename')) { + function plugin_basename(string $file): string { + return basename(dirname($file)) . '/' . basename($file); + } +} +if (!function_exists('register_deactivation_hook')) { + function register_deactivation_hook(string $file, $cb): void {} +} +if (!function_exists('register_activation_hook')) { + function register_activation_hook(string $file, $cb): void {} +} +if (!function_exists('wp_remote_get')) { + function wp_remote_get(string $url, array $args = []) { + return false; + } +} +if (!function_exists('wp_remote_post')) { + function wp_remote_post(string $url, array $args = []) { + return false; + } +} + require_once __DIR__ . '/../includes/class-plugin.php'; \WPMultiCity\Plugin::register_autoloader();