# Caching & Transients

CWP Snippets provides a set of helper functions for caching data using WordPress transients. All cache keys are automatically prefixed with `cwpUF_` to avoid conflicts with other plugins.

---

## Core Caching Functions

### `cwpCacheData($key, $data = null, $expire = 3600)`

Set or get cached data using WordPress transients. All keys are automatically prefixed with `cwpUF_`.

**Set cache:**
```php
cwpCacheData('my_key', $data, 3600); // Cache for 1 hour
```

**Get cache:**
```php
$data = cwpCacheData('my_key'); // Returns cached value or false if expired/not found
```

**Disable caching (development):**
```php
// Set cache duration to 0 to disable caching
$cache_duration = 0;
if ($cache_duration > 0) {
    cwpCacheData('my_key', $data, $cache_duration);
}
```

**Parameters:**
- `$key` (string, required): Unique cache key (will be automatically prefixed with `cwpUF_`)
- `$data` (mixed, optional): Data to cache. If null, function returns the cached value
- `$expire` (int, optional): Expiry in seconds (default: 3600 = 1 hour)

**Returns:**
- When getting (data is null): Returns cached value or false
- When setting: Returns true on success, false on failure

---

### `cwpClearCache($key)`

Clear a specific cached item by key.

**Usage:**
```php
$result = cwpClearCache('my_key'); // Returns true if deleted, false otherwise
```

**Parameters:**
- `$key` (string, required): Cache key to clear (will be automatically prefixed with `cwpUF_`)

**Returns:** Boolean - true if deleted, false otherwise

---

### `cwpClearCacheWildcard($prefix, $limit = 25)`

Delete multiple transients by prefix wildcard. For example, prefix `alex_` deletes all keys starting with `alex_`.

**Usage:**
```php
$result = cwpClearCacheWildcard('user_', 50);
// Returns: ['matched' => 3, 'deleted' => 3, 'matched_keys' => [...], 'warning' => null]
```

**Parameters:**
- `$prefix` (string, required): Logical prefix (without `cwpUF_` prefix). Do NOT include trailing `*`
- `$limit` (int, optional): Maximum number of matches to process (default: 25)

**Returns:** Array with keys:
- `matched` (int): Total number of matching transients found
- `deleted` (int): Number successfully deleted
- `matched_keys` (array): List of matched cache keys
- `warning` (string|null): Warning message if limits exceeded or errors occurred

**Important notes:**
- Do NOT include the asterisk (*) in the prefix parameter
- If more keys match than the limit, only the first N (alphabetically) are processed
- A warning is returned if the match count exceeds the limit
- Invalid prefixes (empty string, contains *) return a warning

---

### `cwpClearAllTransients($prefix = 'cwpUF_')`

Clear ALL transients with the specified prefix. By default, clears all CWP Snippets cache.

**Usage:**
```php
$deleted = cwpClearAllTransients(); // Clears all cwpUF_* transients
// Returns: 12 (number of transients deleted)
```

**Parameters:**
- `$prefix` (string, optional): Prefix to match (default: `'cwpUF_'`)

**Returns:** Integer - number of transients deleted

---

## REST API Endpoint

### `POST /wp-json/cwp/v1/clear-cache`

Allows external applications (e.g., FileMaker) to clear cache via REST API.

#### Clear a Specific Key

**Request:**
```bash
curl -X POST https://example.com/wp-json/cwp/v1/clear-cache \
  -H "Content-Type: application/json" \
  -d '{"cache_key":"my_key"}'
```

**Response:**
```json
{
  "success": true,
  "message": "Cache 'my_key' cleared."
}
```

#### Clear Multiple Keys

**Request:**
```bash
curl -X POST https://example.com/wp-json/cwp/v1/clear-cache \
  -H "Content-Type: application/json" \
  -d '{"cache_key":["key1","key2","key3"]}'
```

**Response:**
```json
{
  "success": true,
  "results": {
    "exact": {
      "key1": true,
      "key2": true,
      "key3": false
    }
  },
  "message": "Processed multiple cache keys."
}
```

#### Clear by Wildcard Prefix

**Request:**
```bash
curl -X POST https://example.com/wp-json/cwp/v1/clear-cache \
  -H "Content-Type: application/json" \
  -d '{"cache_key":"user_*"}'
```

**Response:**
```json
{
  "success": true,
  "result": {
    "matched": 3,
    "deleted": 3,
    "matched_keys": ["user_123", "user_456", "user_789"],
    "warning": null
  },
  "message": "Processed wildcard prefix."
}
```

#### Clear All Cache

**Request (Method 1 - clear_all flag):**
```bash
curl -X POST https://example.com/wp-json/cwp/v1/clear-cache \
  -H "Content-Type: application/json" \
  -d '{"clear_all":true}'
```

**Request (Method 2 - special cache_key):**
```bash
curl -X POST https://example.com/wp-json/cwp/v1/clear-cache \
  -H "Content-Type: application/json" \
  -d '{"cache_key":"clear_all"}'
```

**Response:**
```json
{
  "success": true,
  "deleted": 12,
  "message": "Deleted 12 transients."
}
```

#### Delimiter-Separated Keys

You can send comma, semicolon, or newline-separated keys as a string:

**Request:**
```bash
curl -X POST https://example.com/wp-json/cwp/v1/clear-cache \
  -H "Content-Type: application/json" \
  -d '{"cache_key":"key1, key2, user_*"}'
```

---

## FileMaker Integration

### Insert from URL Examples

#### Clear Specific Key

**FileMaker Script:**
```
Set Variable [ $url ; Value: "https://your-site.com/wp-json/cwp/v1/clear-cache" ]
Set Variable [ $cacheKey ; Value: "my_cache_key" ]
Set Variable [ $curlOptions ; Value: "--request POST --header \"Content-Type: application/json\" --data \"{\\\"cache_key\\\":\\\"" & $cacheKey & "\\\"}\"" ]
Insert from URL [ Select ; With dialog: Off ; Target: $$result ; $url ; cURL options: $curlOptions ]
```

#### Clear All Cache

**FileMaker Script:**
```
Set Variable [ $url ; Value: "https://your-site.com/wp-json/cwp/v1/clear-cache" ]
Set Variable [ $curlOptions ; Value: "--request POST --header \"Content-Type: application/json\" --data \"{\\\"clear_all\\\":true}\"" ]
Insert from URL [ Select ; With dialog: Off ; Target: $$result ; $url ; cURL options: $curlOptions ]
```

#### Using Let Calculation (No Manual Escaping)

```
Let(
  [
    json = JSONSetElement("{}"; "cache_key"; "my_key"; JSONString);
    curl = "--request POST " &
           "--header " & Quote("Content-Type: application/json") & " " &
           "--data " & Quote(json) & " " &
           "--max-time 30"
  ];
  curl
)
```

---

## Security Considerations

**Important:** The REST endpoint is intentionally permissive by default for ease of testing and development. For production use, you should add authentication:

1. **Token-based authentication:**
```php
// Add to the REST endpoint permission_callback
if ($request->get_param('token') !== 'your_secret_token') {
    return false;
}
```

2. **IP allowlist:**
```php
// Restrict to specific IP addresses
if (!in_array($_SERVER['REMOTE_ADDR'], ['1.2.3.4', '5.6.7.8'])) {
    return false;
}
```

3. **WordPress capability check:**
```php
// Require admin user
if (!current_user_can('manage_options')) {
    return false;
}
```

**Clear-all operations** explicitly check for `manage_options` capability before executing, providing an additional security layer for destructive operations.

---

## Additional Helper Functions

### `cwp_cache_bust($url, $version = null)`

Append a cache-busting query string to asset URLs to force browsers to reload updated files.

**Usage:**
```php
// Using file modification time (recommended)
$url = cwp_cache_bust(get_stylesheet_directory_uri() . '/style.css');
// Returns: https://site.com/style.css?v=1704844800

// Using custom version
$url = cwp_cache_bust(get_stylesheet_directory_uri() . '/script.js', '1.2.3');
// Returns: https://site.com/script.js?v=1.2.3
```

**Parameters:**
- `$url` (string, required): The asset URL to modify
- `$version` (string|int, optional): Version or timestamp. If null, uses file modification time or current timestamp

**Returns:** Cache-busted URL string, or false if URL is invalid

---

### `cwpIsURLValid($data, $logging = false, $checkCount = 1, $random = false)`

Quickly check whether cached FileMaker responses contain expired Streaming_SSL image URLs.

**Purpose:** Validates FileMaker container image URLs before serving cached data to ensure images are still accessible.

**Usage:**
```php
$cached = cwpCacheData('fm_records');
if ($cached && cwpIsURLValid($cached, true)) {
    // Cache is valid, use it
} else {
    // Refresh cache from FileMaker
}
```

**Parameters:**
- `$data` (string|array, required): A URL string or FileMaker response array to validate
- `$logging` (bool, optional): Log failures to CWP error log (default: false)
- `$checkCount` (int, optional): Number of URLs to validate (default: 1)
- `$random` (bool, optional): Sample URLs randomly if true, or use first N if false (default: false)

**Returns:** Boolean
- `true` if all sampled URLs return 2xx HTTP status
- `false` if any URL fails validation

**Behavior:**
- Recursively scans arrays for values containing `Streaming_SSL` (case-insensitive)
- Performs HTTP HEAD requests with short timeouts
- Falls back to GET request if HEAD fails
- Returns true immediately if no Streaming_SSL URLs found

**Examples:**
```php
// Check single cached response with logging
if (!cwpIsURLValid($cached, true)) {
    // Refresh cache
}

// Check 3 random images
if (!cwpIsURLValid($response, true, 3, true)) {
    // One or more URLs failed
}
```

---

### `cwpShowTransients()`

Display all current CWP transients and their expiry times for debugging.

**Usage:**
```php
cwpShowTransients(); // Only visible to users with 'manage_options' capability
```

**Security:** Automatically restricted to admin users only.

---

## Best Practices

1. **Use descriptive cache keys:**
   ```php
   // Good
   cwpCacheData('user_profile_123', $data);
   
   // Bad
   cwpCacheData('data', $data);
   ```

2. **Set appropriate expiry times:**
   ```php
   // Frequently changing data - 5 minutes
   cwpCacheData('stock_prices', $data, 300);
   
   // Rarely changing data - 7 days
   cwpCacheData('company_info', $data, 604800);
   ```

3. **Clear cache when data changes:**
   ```php
   // After updating a record
   cwpClearCache('user_profile_' . $user_id);
   
   // Or clear related wildcards
   cwpClearCacheWildcard('user_profile_', 50);
   ```

4. **Use wildcards for grouped data:**
   ```php
   // Cache with consistent prefix
   cwpCacheData('invoice_' . $invoice_id, $data);
   cwpCacheData('invoice_meta_' . $invoice_id, $meta);
   
   // Clear all invoice-related cache
   cwpClearCacheWildcard('invoice_', 100);
   ```

5. **Disable caching during development:**
   ```php
   $cache_duration = 0; // Set to 0 to disable
   if ($cache_duration > 0) {
       cwpCacheData($key, $data, $cache_duration);
   }
   ```

6. **Add logging for external clears:**
   ```php
   // In your REST endpoint permission_callback or handler
   cwpLog('cache_cleared', 'External API cleared cache: ' . $cache_key);
   ```
           "--max-time 30"
  ];
  curl
)
```

  - To include a token in the JSON body: add `JSONSetElement()` entries for `token` or append `--header "Authorization: Bearer <token>"` using `Quote()`.

- Admin-AJAX (simple backdoor / quick test):
  - URL (GET): `https://your-site/wp-admin/admin-ajax.php?action=cwp_clear_cache&clear_all=1`
  - Or POST (form-encoded):

```
--request POST --header "Content-Type: application/x-www-form-urlencoded" --data "clear_all=1" --max-time 30
```

  - From FileMaker you can either place the query string in the URL field (no cURL options needed) or send the POST body via the cURL options box.

Notes about escaping in FileMaker
- If you paste raw cURL options into the cURL box, JSON double-quotes must be escaped (e.g., `\"clear_all\":true`).
- Use `Quote()` and `JSONSetElement()` in a calculation to avoid manual escaping and to produce valid JSON automatically.

Why JSON vs form-encoded
- The REST endpoint expects JSON and will parse the body automatically when Content-Type is `application/json`. Admin-AJAX can accept form-encoded parameters and is convenient for quick tests.

Safety & recommendations
- The REST route and the simple AJAX handler are intentionally permissive in this codebase for convenience and testing. In production, prefer:
  - A long unguessable token validated in `permission_callback` or inside the handler; or
  - Restrict access by IP/network; or
  - Require an authenticated admin request (remove `nopriv` registration).
- Add logging (`cwpLog`) for any external clears so you can audit who/when cleared caches.


---

## Transient Expiry Viewer

### `cwpShowTransients()`

**Purpose:**
- Displays all current CWP transients (cache items) and their expiry times.
- Only visible to users with `manage_options` capability (admins/webmasters).

**Usage:**
```php
cwpShowTransients();
```

**Security Note:**
- This function is for debugging and admin use only. It is not visible to regular users.
- The code does not reveal or hint at any sensitive or private transients.
