# Implementation Plan: PHP 7.4 Compatibility

## Approach

**Core strategy**: Let the plugin activate gracefully on PHP 7.4 by intercepting execution *before* Composer's `platform_check.php` runs. The plugin itself still requires PHP 8.0 to function — we are only preventing the hard crash and replacing it with a friendly admin notice.

Two-layer defence:
1. **Entry file gate** — `version_compare()` check at the top of each main plugin file, above any `require_once`. Returns early on PHP < 8.0 after registering an admin notice.
2. **platform_check.php patch** — neutralise the `throw new RuntimeException` so if the gate is ever bypassed (e.g. direct autoload include from another plugin), nothing explodes.

---

## Files to Modify

| File | Change Type | Reason |
|------|-------------|--------|
| `archive-master.php` | Modify | Add early PHP gate above `require_once vendor/autoload.php`; update header |
| `archive-master-pro/archve-master.php` | Modify | Add early PHP gate above class definition; update header |
| `vendor/composer/platform_check.php` | Modify | Neutralise `throw RuntimeException`; replace with silent early-return |
| `composer.json` | Modify | Change `platform.php` from `8.0` to `7.4` |
| `includes/Admin/Assets.php` | Modify | Fix `private mixed $plugin_now`; fix `str_contains()` |
| `includes/polyfills.php` | **Create** | Polyfills for `str_contains`, `str_starts_with`, `str_ends_with`, `array_is_list` |
| `readme.txt` | Modify | `Requires PHP: 8.0` → `7.4` |

---

## Step-by-Step Changes

### Step 1 — `vendor/composer/platform_check.php`

**Why first**: Neutralising this before touching the entry file means even if we make a mistake in Step 2, no crash is possible.

```php
// BEFORE:
if (!(PHP_VERSION_ID >= 80000)) {
    $issues[] = '...';
}
if ($issues) {
    ...
    throw new \RuntimeException('...');
}

// AFTER:
if ( PHP_VERSION_ID >= 80000 ) {
    return; // PHP 8.0+ — no issues.
}
// PHP < 8.0: skip enforcement. Entry file gate already returned before autoload ran.
```

### Step 2 — `composer.json`

```json
// BEFORE:
"config": { "platform": { "php": "8.0" } }

// AFTER:
"config": { "platform": { "php": "7.4" } }
```

After committing, run `composer update --ignore-platform-reqs` to regenerate `platform_check.php` properly targeting 7.4.

### Step 3 — `archive-master.php` (Free)

**Header change:**
```php
// BEFORE: Require PHP:  8.0
// AFTER:  Requires PHP: 7.4
```

**Boot sequence change** — gate inserted between ABSPATH check and autoload require:

```php
if ( ! defined('ABSPATH') ) {
    exit;
}

// PHP gate MUST be above require_once so Composer never runs on incompatible servers.
if ( version_compare( PHP_VERSION, '8.0.0', '<' ) ) {
    add_action(
        'admin_notices',
        function () {
            echo '<div class="notice notice-error"><p>'
                . '<strong>ArchiveMaster</strong> requires PHP 8.0 or higher. '
                . 'You are running PHP ' . esc_html( PHP_VERSION ) . '. '
                . 'Please upgrade PHP or contact your host.</p></div>';
        }
    );
    return;
}

require_once __DIR__ . '/includes/polyfills.php';
require_once __DIR__ . '/vendor/autoload.php';
```

**Cleanup inside class:**
- Remove `version_compare` check inside `init_plugin()` — unreachable now (gate already returned).
- Remove `admin_notic_php_version()` method — dead code.

### Step 4 — `includes/polyfills.php` (New File)

Loaded before autoload. Guards with `function_exists` so native PHP 8.0 implementations win.

```php
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }

if ( ! function_exists( 'str_contains' ) ) {
    function str_contains( $haystack, $needle ) {
        return '' === $needle || false !== strpos( $haystack, $needle );
    }
}
if ( ! function_exists( 'str_starts_with' ) ) {
    function str_starts_with( $haystack, $needle ) {
        return '' === $needle || 0 === strpos( $haystack, $needle );
    }
}
if ( ! function_exists( 'str_ends_with' ) ) {
    function str_ends_with( $haystack, $needle ) {
        return '' === $needle || substr( $haystack, -strlen( $needle ) ) === $needle;
    }
}
if ( ! function_exists( 'array_is_list' ) ) {
    function array_is_list( array $arr ) {
        if ( array() === $arr ) { return true; }
        return array_keys( $arr ) === range( 0, count( $arr ) - 1 );
    }
}
```

### Step 5 — `includes/Admin/Assets.php`

Two surgical changes only:

```php
// BEFORE (line 18):
private mixed $plugin_now;

// AFTER:
/** @var mixed */
private $plugin_now;
```

```php
// BEFORE (line 54):
if ( str_contains($page, 'master-archives') ) {

// AFTER:
if ( false !== strpos( $page, 'master-archives' ) ) {
```

Note: `str_contains` polyfill in `polyfills.php` would also cover this — but the inline `strpos` replacement is more defensive (works even if polyfills.php load order changes).

### Step 6 — `archive-master-pro/archve-master.php` (Pro)

**Header change:**
```php
// BEFORE: Require PHP:  8.0
// AFTER:  Requires PHP: 7.4
```

**Boot sequence gate** — inserted between ABSPATH check and `final class` declaration. Critical: must be above class definition, because parsing the class body references `ARCHM\` namespaced classes which don't exist if free plugin bailed early.

```php
if ( ! defined( 'ABSPATH' ) ) { exit; }

// Gate above class definition — parsing the class body on PHP < 8.0 would
// reference ARCHM\ classes that were never loaded (free plugin bailed early).
if ( version_compare( PHP_VERSION, '8.0.0', '<' ) ) {
    add_action(
        'admin_notices',
        function () {
            echo '<div class="notice notice-error"><p>'
                . '<strong>Archive Master Pro</strong> requires PHP 8.0 or higher. '
                . 'You are running PHP ' . esc_html( PHP_VERSION ) . '. '
                . 'Please upgrade PHP or contact your host.</p></div>';
        }
    );
    return;
}

final class Archm_Pro_PluginLoader { ...
```

Note: `define_constants(): void` uses `: void` — PHP 7.1+, safe on 7.4. No change needed.

### Step 7 — `readme.txt`

```
// BEFORE: Requires PHP: 8.0
// AFTER:  Requires PHP: 7.4
```

---

## Trade-offs Considered

| Option | Decision | Reason |
|--------|----------|--------|
| Lower minimum to PHP 7.4 entirely | Rejected | Plugin requires PHP 8.0 to function; we only fix the crash UX |
| Delete `platform_check.php` entirely | Rejected | Composer regenerates it on next `composer update`; patching is durable |
| Use `try/catch` around autoload instead of early return | Rejected | Catching `RuntimeException` after autoload fail still leaves half-initialised state |
| Add polyfills even though gate returns early on PHP 7.4 | Kept | Polyfills are used on PHP 8.0+ too (e.g. `str_starts_with` in future code); zero overhead |
| Fix `str_contains` with polyfill only (not inline strpos) | Not chosen | Inline `strpos` is belt-and-suspenders; polyfill is defence-in-depth for future callers |

---

## Post-Implementation: Composer Regeneration

After code changes are merged, run once from plugin root:

```bash
cd "wp-content/plugins/archive-master"
composer update --ignore-platform-reqs
```

This regenerates `vendor/composer/platform_check.php` with `PHP_VERSION_ID >= 70400` check. Then commit the updated vendor file.

---

## Test Checklist

- [ ] PHP 7.4 — activate free plugin → admin notice shown, no crash, no PHP error in log
- [ ] PHP 7.4 — activate Pro plugin → admin notice shown, no crash
- [ ] PHP 7.4 — both plugins deactivated cleanly (no lingering hooks)
- [ ] PHP 8.0 — activate free plugin → activates, WooCommerce check runs normally
- [ ] PHP 8.0 — activate Pro plugin → activates, license check runs normally
- [ ] PHP 8.1 — activate both → no deprecation notices, no errors
- [ ] PHP 8.2 — activate both → no deprecation notices, no errors
- [ ] PHP 8.0 — archive/export WooCommerce orders → all features work
- [ ] `debug.log` on any version → zero fatal errors
- [ ] WP.org plugin page → shows "Requires PHP: 7.4"
