Skip to content

Commit 1d978dd

Browse files
committed
Add WordPress Libraries section with docs for each library
1 parent c1c6b8c commit 1d978dd

6 files changed

Lines changed: 506 additions & 0 deletions

File tree

.vitepress/config.mjs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,16 @@ export default defineConfig({
124124
{ text: 'Developer Docs', link: '/lazy-load-for-comments/developer-docs' },
125125
]
126126
},
127+
{
128+
text: 'WordPress Libraries',
129+
items: [
130+
{ text: 'Overview', link: '/wp-libraries/' },
131+
{ text: 'Freemius Plugin Licensing', link: '/wp-libraries/freemius-plugin-licensing' },
132+
{ text: 'WP Cache Helper', link: '/wp-libraries/wp-cache-helper' },
133+
{ text: 'WP Queue Process', link: '/wp-libraries/wp-queue-process' },
134+
{ text: 'WP Review Notice', link: '/wp-libraries/wp-review-notice' },
135+
]
136+
},
127137
{
128138
text: 'About Us',
129139
link: '/about',
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Freemius Plugin Licensing
2+
3+
A lite, UI-free Freemius SDK for Duck Dev WordPress plugins. It handles license activation, deactivation, update delivery, and addon listing by talking to the Freemius API directly. The library deliberately ships no admin screens — host plugins build their own UI and call into this library for the underlying logic.
4+
5+
## Requirements
6+
7+
- PHP 7.4+
8+
- WordPress 5.0+
9+
- Composer
10+
11+
## Installation
12+
13+
```console
14+
composer require duckdev/freemius-plugin-licensing
15+
```
16+
17+
Classes autoload under the `DuckDev\Freemius\` namespace via PSR-4.
18+
19+
## Initialisation
20+
21+
Initialise the container by calling `Freemius::get_instance()` with your Freemius product ID and an arguments array:
22+
23+
```php
24+
$freemius = \DuckDev\Freemius\Freemius::get_instance(
25+
12345, // Freemius product ID.
26+
array(
27+
'slug' => 'loggedin',
28+
'main_file' => LOGGEDIN_FILE,
29+
'public_key' => 'pk_XXXXXXXXXXXXXXXXX',
30+
'is_premium' => true,
31+
'has_addons' => false,
32+
)
33+
);
34+
```
35+
36+
The first call creates the container and registers WordPress hooks; subsequent calls for the same plugin ID return the existing instance.
37+
38+
## Options
39+
40+
| Key | Type | Description |
41+
| --- | --- | --- |
42+
| `slug` | `string` | Unique Freemius slug for the plugin. |
43+
| `main_file` | `string` | Absolute path to the plugin's main file (used for `plugin_basename()` and `get_plugin_data()`). |
44+
| `public_key` | `string` | Freemius public key (`pk_…`). Required for plugin-scoped endpoints (addons, info). |
45+
| `is_premium` | `bool` | Whether this build is the premium edition. Update hooks only register when `true`. Default `false`. |
46+
| `has_addons` | `bool` | Whether the product has addons to list. Default `false`. |
47+
48+
## Actions
49+
50+
| Hook | Arguments | When |
51+
| --- | --- | --- |
52+
| `duckdev_freemius_license_activated` | `array $activation, bool $success` | After a successful activation. |
53+
| `duckdev_freemius_license_deactivated` | `array $activation, bool $success` | After a successful deactivation. |
54+
55+
## Filters
56+
57+
| Hook | Arguments | Use |
58+
| --- | --- | --- |
59+
| `duckdev_freemius_api_request_args` | `array $args, string $method, string $url, array $data, array $headers` | Tweak request arguments before they reach `wp_remote_request()`. |
60+
| `duckdev_freemius_api_request_verify_ssl` | `bool $verify, Client $client` | Disable SSL verification (typically only in local dev). |
61+
| `duckdev_freemius_format_addon_data` | `array $addon, Addon $service` | Rewrite or augment each addon entry before it is returned. |
62+
63+
## Example usage
64+
65+
### License activation
66+
67+
```php
68+
$result = $freemius->license()->activate( 'XXXX-XXXX-XXXX' );
69+
70+
if ( is_wp_error( $result ) ) {
71+
echo esc_html( $result->get_error_message() );
72+
}
73+
```
74+
75+
`activate()` returns `true` / `false` from the option update on success, or a `WP_Error` when the key is empty, the plugin is not the premium build, the API call fails, or the response does not include an install ID.
76+
77+
### License deactivation
78+
79+
```php
80+
$result = $freemius->license()->deactivate();
81+
```
82+
83+
`deactivate()` refuses to proceed when the stored UID does not match the current site — that means the activation was moved elsewhere, and the new host correctly appears unlicensed rather than silently freeing the original seat.
84+
85+
### Reading the current activation
86+
87+
```php
88+
$activation = $freemius->license()->get_activation();
89+
90+
if ( $activation->is_active() ) {
91+
$key = $activation->license_key();
92+
$install = $activation->install_id();
93+
}
94+
```
95+
96+
### Updates
97+
98+
Update hooks are registered automatically during `boot()` for premium builds. To force a refresh:
99+
100+
```php
101+
$freemius->update()->get_update_data( true );
102+
```
103+
104+
### Addons
105+
106+
```php
107+
$addons = $freemius->addon()->get_addons(); // Cached for 24h.
108+
$addons = $freemius->addon()->get_addons( true ); // Force refresh.
109+
```
110+
111+
Each entry is enriched with a `link` field (Freemius checkout URL) and an `is_premium` boolean.
112+
113+
## Security notes
114+
115+
- The library does **not** verify nonces or capabilities. Host plugins MUST do that before forwarding form input to `License::activate()` / `License::deactivate()`.
116+
- The license key is stored in the `duckdev_freemius_activation_data` option, keyed by plugin ID. It is blanked from storage on deactivation.

wp-libraries/index.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# WordPress Libraries
2+
3+
A collection of small, focused PHP libraries we maintain and reuse across our WordPress plugins. Each one solves a single problem well, ships with a full test suite, and is built around small, injectable collaborators so it stays easy to test and extend.
4+
5+
| Library | Purpose |
6+
| --- | --- |
7+
| [Freemius Plugin Licensing](/wp-libraries/freemius-plugin-licensing) | UI-free Freemius SDK: license activation, updates, and addon listing. |
8+
| [WP Cache Helper](/wp-libraries/wp-cache-helper) | Callback-style `remember()` wrapper around the object cache and transients, with group flushing. |
9+
| [WP Queue Process](/wp-libraries/wp-queue-process) | Non-blocking async requests and a self-healing background queue for long jobs. |
10+
| [WP Review Notice](/wp-libraries/wp-review-notice) | A gentle, dismissable admin notice asking for a wp.org review after a few days of usage. |
11+
12+
All libraries require **PHP 7.4+**, ship on Composer under the `duckdev/*` namespace, and are released under **GPL-2.0-or-later**.

wp-libraries/wp-cache-helper.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# WP Cache Helper
2+
3+
WP Cache Helper is a small WordPress library that wraps the object cache and transient APIs with a callback-style `remember()` helper, group-flush support for the object cache (which [core does not provide](https://core.trac.wordpress.org/ticket/4476)), and per-prefix scoping so multiple consumers on the same site never collide.
4+
5+
Inspired by [WP Cache Remember](https://github.com/stevegrunwell/wp-cache-remember).
6+
7+
## Requirements
8+
9+
- PHP 7.4+
10+
- WordPress 5.0+
11+
- Composer
12+
13+
## Installation
14+
15+
```console
16+
composer require duckdev/wp-cache-helper
17+
```
18+
19+
Classes autoload under the `DuckDev\Cache\` namespace via PSR-4.
20+
21+
## Initialisation
22+
23+
Each container instance is scoped to a single prefix. Pass any non-empty string the first time you ask for it; the same prefix returns the same instance on subsequent calls:
24+
25+
```php
26+
$cache = \DuckDev\Cache\Cache::get_instance( 'my_plugin' );
27+
```
28+
29+
You can also instantiate directly (useful for tests):
30+
31+
```php
32+
$cache = new \DuckDev\Cache\Cache( 'my_plugin' );
33+
```
34+
35+
Every key, group, and the `{prefix}_can_cache` toggle filter are namespaced under the supplied prefix.
36+
37+
## Options
38+
39+
There are no runtime options — the only configuration is the prefix passed to the constructor. Behaviour is instead tuned via the filter below.
40+
41+
## Methods
42+
43+
| Method | Backed by | Purpose |
44+
| --- | --- | --- |
45+
| `remember( $key, $callback, $group, $expiry )` | Object cache | Read, or compute + cache on miss. |
46+
| `forget( $key, $group, $default )` | Object cache | Read then delete; return `$default` on miss. |
47+
| `persist( $key, $callback, $site_wide, $expiry )` | Transients | Read, or compute + cache on miss. |
48+
| `cease( $key, $site_wide, $default )` | Transients | Read then delete; return `$default` on miss. |
49+
| `flush_group( $group )` | Object cache | Invalidate every entry in a group. |
50+
| `flush()` | Object cache | Flush the entire object cache. **Last resort.** |
51+
| `object_cache()` / `transient_cache()` || Access the underlying driver for finer-grained control. |
52+
53+
Every callback-based helper checks the callback return with `is_wp_error()` and skips caching when a `WP_Error` is returned, so a transient API failure is not memorised.
54+
55+
## Filters
56+
57+
| Filter | Arguments | Use |
58+
| --- | --- | --- |
59+
| `{prefix}_can_cache` | `bool $enabled, string $type` | Return `false` to disable caching. `$type` is `'object'` or `'transient'` so the two can be toggled independently. |
60+
61+
## Actions
62+
63+
The library does not fire any actions.
64+
65+
## Example usage
66+
67+
### `remember()` — object-cache read-through
68+
69+
```php
70+
$cache = \DuckDev\Cache\Cache::get_instance( 'my_plugin' );
71+
72+
$posts = $cache->remember( 'latest_posts', function () {
73+
return new WP_Query( array(
74+
'posts_per_page' => 5,
75+
'orderby' => 'post_date',
76+
'order' => 'desc',
77+
) );
78+
}, 'queries', HOUR_IN_SECONDS );
79+
```
80+
81+
Unlike a naive `wp_cache_get()`-then-fall-back pattern, `remember()` distinguishes a legitimately cached `0`, `''`, `[]`, or `false` from a true miss — the callback only runs when nothing was cached.
82+
83+
### `forget()` — one-shot read
84+
85+
```php
86+
$error = $cache->forget( 'form_errors', 'flash', false );
87+
88+
if ( $error ) {
89+
echo 'An error occurred: ' . esc_html( $error );
90+
}
91+
```
92+
93+
### `persist()` — transient read-through
94+
95+
```php
96+
$cache->persist( 'latest_tweets_' . $user_id, function () use ( $user_id ) {
97+
return get_latest_tweets_for_user( $user_id );
98+
}, false, 15 * MINUTE_IN_SECONDS );
99+
```
100+
101+
Pass `true` for the third argument to use site-wide (multisite) transients.
102+
103+
::: tip
104+
Transients use boolean `false` as the miss sentinel, so a legitimately cached `false` value is indistinguishable from a miss. Reach for `remember()` if you need to cache `false`.
105+
:::
106+
107+
### `flush_group()`
108+
109+
```php
110+
$cache->flush_group( 'queries' );
111+
```
112+
113+
Internally increments a per-group version sentinel — old entries become unreadable on next access without touching the rest of the object cache.
114+
115+
### Disabling caching for debugging
116+
117+
```php
118+
add_filter( 'my_plugin_can_cache', '__return_false' );
119+
```

0 commit comments

Comments
 (0)