--- name: liberlite-license description: Add a LiberLite licence-key check to a PHP website script that is sold on the LiberLite marketplace (installer licence step, request guard, admin licence page, slug check). Use when the user mentions LiberLite, a LiberLite licence key, PRODUCT_SLUG, LiberLiteLicense.php, or selling a script on LiberLite. --- # LiberLite licensed scripts You are integrating the LiberLite licence check into the user's script so it can be sold on the LiberLite marketplace. Follow these rules exactly. The full guide is in `reference/guide.md`; the code to copy is in `reference/`. ## Before writing code 1. **Get the real product slug.** The order is: choose and reserve the slug (draft, no file needed) → develop with PRODUCT_SLUG → upload. Ask the user for the slug if they have not given it. It is shown on https://liberlite.com/app/sell → their product → "Product slug for your script", e.g. `hotel-booking-pro`. - If they have no product yet, tell them to open Sell a product → "A website script with a licence key", type the title, check the Product slug field and press **Save draft** — no file is needed. Then continue. - Never use a placeholder such as `your-product-slug` or `my-script`; they are refused anyway. 2. **Verify the slug** by running (replace YOUR-SLUG): `curl -X POST https://liberlite.com/api/licenses/v1/activate -d "license_key=LL-AAAAA-BBBBB-CCCCC-DDDDD&product=YOUR-SLUG&domain=test.test&instance_id=0123456789abcdef"` - `"status":"unknown_product"` → the slug is wrong; stop and ask the user to copy it again. - `"status":"invalid"` → the slug is right (the dummy key is expected to be unknown). 3. Find the script's framework (plain PHP, Laravel, CodeIgniter, WordPress…), its installer, its entry points (front controller, admin, API, cron) and where libraries live. ## Rules (R1–R9) - **R1** Copy `reference/LiberLiteLicense.php` unchanged into the project (where libraries live). Do not edit its logic; replacing it with a newer official version is allowed. Copy `reference/liberlite-public.pem` next to it (or put its text in a constant). Never use or ask for a private key. - **R2** Put the slug in ONE constant `PRODUCT_SLUG`, used by every `new LiberLiteLicense([...])`. - **R3** Choose one licence state file (`storage_file`): writable by PHP, NOT reachable from the web (outside the web root or a folder with `Deny from all`), never shipped in the zip, the same for installer and guard. - **R4** Installer order: database details → test connection → licence key (`activate()`) → only when `valid` is true write config, create tables, create the admin and mark installed. A wrong key writes nothing; show `$license->message()` and stay on the licence step. If there is no installer, adapt `reference/example-install.php`. - **R5** Guard every request (web, admin, AJAX/API, cron) after the "is it installed?" check: when `isValid()` is false, stop with HTTP 403 and show `message()` plus buttons from `help()` (marketplace link, call, WhatsApp); CLI and cron exit with code 1. See `reference/example-guard.php`. No bypass flags, env vars or trial modes. - **R6** Call `isValid()` on every request; it only goes online when due. `check_every` ≥ 1 hour, `grace` ≤ 72 hours. - **R7** Add an admin licence page: status, domain, expiry from `details()`; a "Release this domain" button calling `deactivate()`; mask the key except its last 5 characters. - **R8** The zip must not contain config, the state file, the installed marker, test data or any licence key. - **R9** Local installs (localhost, 127.0.0.1, *.test, *.local, *.localhost) are free; add no special code. ## Where the guard goes - Plain PHP: top of the front controller / common bootstrap include. - Laravel: global middleware (bootstrap/app.php on 11+, `web` + `api` groups before), skipping `install*` routes. - CodeIgniter 4: a filter in `$globals['before']` (app/Config/Filters.php), excluding installer routes. - WordPress: settings page for the key (`activate()`), check on `plugins_loaded`; when invalid, disable the plugin's features and show an admin notice — never break the whole site. ## When you finish List every file you changed, then tell the user how to test: 1. The slug check above answers `invalid`. 2. Get a free Test key: Sell page → the product → **Test key**. 3. Fresh install on localhost: a wrong key writes nothing; the Test key installs. 4. Edit the state file (`"valid":false` → `true`): the site must refuse it. 5. Unzip the release into an empty folder: no config, .env, state file (license.json), installed marker (installed.lock), database dump, .git/ or .claude/ folder, or licence key — the Sell page blocks or warns about these when the zip is sent for review, and warns if the slug does not appear in the zip. ## Troubleshooting (client messages) | Message | Cause → fix | |---|---| | Unknown product slug 'X' / `unknown_product` | Wrong slug → copy it from the Sell page, update `PRODUCT_SLUG` | | "meant for another installation" (old client) | Usually a wrong slug; else the state file is not writable → fix both, update the client | | "not signed by LiberLite" | Wrong/outdated public key → copy https://liberlite.com/api/licenses/v1/public-key | | "Could not reach LiberLite" | Outbound HTTPS blocked, or no cURL / allow_url_fopen | | `wrong_product` | The key is for another script | | `limit_reached` | Release a domain on the marketplace or admin licence page | | `not_activated` | Run the installer's licence step again | Help for the user: https://liberlite.com/app/marketplace or +255753099003. Full guide: https://liberlite.com/api/docs#licensed-scripts