alleyinteractive / alleyinteractive/mantle-framework

`is_within_wordpress_install()` false-positives for wp-content-rooted checkouts, silently disabling `maybe_rsync()` / `with_sqlite()`

Abierto
#906 0 comentarios 0 reacciones 0 asignados Ver en GitHub
Lenguaje dominante
PHP
Estrellas
28
Forks
7
Merge medio
2 d 4 h
PR fusionados (30 d)
2

Descripción

### Description of the bug

### Summary

`Rsync_Installation::is_within_wordpress_install()` decides whether the framework is
already running inside an installed WordPress by string-matching its own vendored path:

```php
// src/mantle/testing/concerns/trait-rsync-installation.php
public function is_within_wordpress_install(): bool {
return false !== strpos( __DIR__, '/wp-content/' );
}
```

`__DIR__` here is the framework's own install location
(`.../wp-content/.../vendor/alleyinteractive/mantle-framework/...`). In any project laid
out with the repository root **named or nested under `wp-content`** — common in WordPress
VIP / wp-content-rooted monorepos — this string is always present, so the method returns
`true` even when there is **no WordPress core above the checkout**.

Because this method guards several builder calls, they all become silent no-ops.

### Affected version

- `v1.19.3` (ref `d37a354fde52bc27395e86f47295a9b721f8e782`). The check is unchanged on
current `main` at the time of writing.

### Guarded call sites (all early-return `$this` with no error/log)

- `maybe_rsync()`
- `with_vip_mu_plugins()`
- `with_object_cache()`
- `with_sqlite()`

Example:

```php
public function with_sqlite( bool $install = true ): static {
if ( $this->is_within_wordpress_install() ) {
return $this; // <-- silently does nothing for wp-content-rooted repos
}
putenv( 'MANTLE_USE_SQLITE=' . ( $install ? '1' : '0' ) );
// ...
}
```

### Impact

For wp-content-rooted checkouts, `->maybe_rsync_wp_content()->with_sqlite()` in a test
bootstrap **does nothing** — the suite silently runs against the in-place MySQL DB instead
of an rsynced SQLite install. There is:

- **No error and no log line** indicating the calls were skipped, and
- **No public override** to force the "not within an install" branch.

So the documented "Using SQLite for the database" flow
(https://mantle.alley.com/docs/testing/installation-manager#using-sqlite-for-the-database)
is unreachable in these layouts without editing vendored code.

### Reproduction

1. Check out any project whose path contains `/wp-content/` above the vendor dir
(e.g. repo root is `wp-content/`, Mantle at
`wp-content/.../vendor/alleyinteractive/mantle-framework`), with **no `wp-load.php`**
above `wp-content`.
2. Test bootstrap:
```php
use function Mantle\Testing\manager;
manager()->maybe_rsync_wp_content()->with_sqlite()->install();
```
3. Run PHPUnit on the host. **Expected:** rsync to a fresh install + SQLite drop-in.
**Actual:** both calls are no-ops; the suite runs in place against MySQL. No indication
that rsync/SQLite were skipped.

### Root cause

`strpos( __DIR__, '/wp-content/' )` is a proxy for "am I inside a real WP install," but it
matches on a directory *name* rather than the presence of WordPress itself, so it collides
with wp-content-rooted repo layouts.

### Proposed fix (suggestion)

Keep the fast string check as a cheap negative, then confirm an actual WP core exists above
`wp-content`. This also preserves the method's second role as the **rsync recursion guard**
— the rsynced scratch copy *does* have `wp-load.php` above it, so it still reads as "within
an install" and won't re-rsync:

```php
public function is_within_wordpress_install(): bool {
if ( false === strpos( __DIR__, '/wp-content/' ) ) {
return false;
}

$parent = preg_replace( '/\/wp-content\/.*$/', '', __DIR__ );

return is_file( $parent . '/wp-load.php' );
}
```

Alternatives worth considering (maintainer's call):

- Walk up from `__DIR__` looking for `wp-load.php` rather than assuming the `wp-content`
segment is the boundary (handles non-standard core locations).
- Honor an explicit env/override (e.g. `MANTLE_WITHIN_WP_INSTALL=0`) so callers can force
the rsync path deterministically.
- At minimum, `log()`/emit a notice when a guarded call is skipped because the gate is
`true`, so the no-op is not silent.

### Steps To Reproduce

See above.

### Additional Information

_No response_

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.