A fresh install leaves `permalink_structure` empty, so WordPress snapshots
$wp_rewrite with no post/page permalink rules and every single post resolves
to 404 until someone re-saves Settings -> Permalinks.
The existing mitigation fired too late to help: routing.php wrote the option
and flushed from `init`, but WP_Rewrite was already constructed with the empty
structure, and core defers pre-`wp_loaded` flushes to a `wp_loaded` callback
that regenerates from that stale snapshot. The rewrite_rules option ended up
non-empty but broken, so the 404 persisted on every request.
New \Jio\Core\RewriteBootstrap owns this: on `after_setup_theme` (still inside
wp-settings.php, before wp-blog-header.php parses the query) it calls
$wp_rewrite->set_permalink_structure() — the same API wp-admin/options-permalink.php
uses — which persists the option *and* refreshes the in-memory snapshot, so both
the current request and the rules written for the next one are correct. A
structure an administrator chose is never overridden.
Also consolidates the two duplicated flush sites that lived inline in
functions.php (`after_switch_theme` closure and the `jio_flushed_version`
block, whose `delete_option('rewrite_rules')` forced a needless regenerate),
and drops the now-redundant permalink write from routing.php. The version
stamp moves to `wp_loaded` priority 20 so it sees the complete rule set
instead of the pre-`add_rewrite_rule()` snapshot.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
126 lines
3.6 KiB
PHP
126 lines
3.6 KiB
PHP
<?php
|
|
/**
|
|
* Behavioural harness for \Jio\Core\RewriteBootstrap.
|
|
*
|
|
* WordPress stubs only: an option bag, a WP_Rewrite stub recording what the theme asked of
|
|
* it, and an add_action/do_action pair so the hook wiring is exercised rather than assumed.
|
|
*/
|
|
|
|
namespace {
|
|
define('ABSPATH', __DIR__);
|
|
define('JIO_VERSION', '9.9.9-test');
|
|
|
|
$GLOBALS['jio_test_options'] = [
|
|
'permalink_structure' => '',
|
|
];
|
|
$GLOBALS['jio_test_set_permalink_calls'] = [];
|
|
$GLOBALS['jio_test_flush_calls'] = [];
|
|
$GLOBALS['jio_test_hooks'] = [];
|
|
}
|
|
|
|
namespace {
|
|
function jio_test_reset(array $options): void {
|
|
$GLOBALS['jio_test_options'] = $options;
|
|
$GLOBALS['jio_test_set_permalink_calls'] = [];
|
|
$GLOBALS['jio_test_flush_calls'] = [];
|
|
}
|
|
|
|
function get_option(string $key, $default = false) {
|
|
return array_key_exists($key, $GLOBALS['jio_test_options'])
|
|
? $GLOBALS['jio_test_options'][$key]
|
|
: $default;
|
|
}
|
|
|
|
function update_option(string $key, $value): bool {
|
|
$GLOBALS['jio_test_options'][$key] = $value;
|
|
return true;
|
|
}
|
|
|
|
function delete_option(string $key): bool {
|
|
unset($GLOBALS['jio_test_options'][$key]);
|
|
return true;
|
|
}
|
|
|
|
function add_action(string $hook, $callback, int $priority = 10, int $args = 1): void {
|
|
$GLOBALS['jio_test_hooks'][] = [
|
|
'hook' => $hook,
|
|
'priority' => $priority,
|
|
'method' => is_array($callback) ? (string) $callback[1] : (string) $callback,
|
|
];
|
|
}
|
|
|
|
function flush_rewrite_rules(bool $hard = true): void {
|
|
$GLOBALS['jio_test_flush_calls'][] = $hard;
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
if (!class_exists('WP_Rewrite')) {
|
|
final class WP_Rewrite {
|
|
public array $structures = [];
|
|
|
|
public function set_permalink_structure(string $structure): void {
|
|
$this->structures[] = $structure;
|
|
// Core persists the option and refreshes in-memory state; the stub mirrors the
|
|
// persistence half so assertions on the option bag stay meaningful.
|
|
$GLOBALS['jio_test_options']['permalink_structure'] = $structure;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
require_once dirname(__DIR__, 2) . '/src/Core/RewriteBootstrap.php';
|
|
|
|
$bootstrap = \Jio\Core\RewriteBootstrap::get_instance();
|
|
$bootstrap->init();
|
|
|
|
$result = [
|
|
'hooks' => $GLOBALS['jio_test_hooks'],
|
|
];
|
|
|
|
// --- Case 1: fresh install, no permalink structure chosen yet.
|
|
jio_test_reset(['permalink_structure' => '']);
|
|
$wp_rewrite = new WP_Rewrite();
|
|
$GLOBALS['wp_rewrite'] = $wp_rewrite;
|
|
$bootstrap->ensure_permalink_structure();
|
|
|
|
$result['fresh_install'] = [
|
|
'option' => get_option('permalink_structure'),
|
|
'set_permalink' => $wp_rewrite->structures,
|
|
];
|
|
|
|
// --- Case 2: administrator already chose a structure — must not be overridden.
|
|
jio_test_reset(['permalink_structure' => '/%category%/%postname%.html']);
|
|
$wp_rewrite = new WP_Rewrite();
|
|
$GLOBALS['wp_rewrite'] = $wp_rewrite;
|
|
$bootstrap->ensure_permalink_structure();
|
|
|
|
$result['chosen_structure'] = [
|
|
'option' => get_option('permalink_structure'),
|
|
'set_permalink' => $wp_rewrite->structures,
|
|
];
|
|
|
|
// --- Case 3: version bump flushes once, then is a no-op.
|
|
jio_test_reset(['permalink_structure' => '/%postname%/']);
|
|
$bootstrap->flush_when_version_changes();
|
|
$first_flush = count($GLOBALS['jio_test_flush_calls']);
|
|
$stamp = get_option('jio_rewrite_flushed_version');
|
|
$bootstrap->flush_when_version_changes();
|
|
|
|
$result['version_flush'] = [
|
|
'flush_count' => $first_flush,
|
|
'stamp' => $stamp,
|
|
'repeat_flush' => count($GLOBALS['jio_test_flush_calls']) - $first_flush,
|
|
];
|
|
|
|
// --- Case 4: activation flush.
|
|
$bootstrap->flush_on_activate();
|
|
$result['activate'] = [
|
|
'flush_count' => count($GLOBALS['jio_test_flush_calls']),
|
|
];
|
|
|
|
echo json_encode($result, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
|
echo PHP_EOL;
|
|
}
|