Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,13 @@ jobs:
uses: wordpress/plugin-check-action@v1
with:
plugin-repo: '.'
# Real slug is "tetapi" (matches the Text Domain header) — without
# this the action defaults to the GitHub repo directory name
# ("wordpress-plugin"), which also trips the trademarked-term
# check since "plugin" is a restricted word in real wp.org slugs.
slug: 'tetapi'
# .github/ and CLAUDE.md never ship in the wp.org submission zip
# (that's packaged separately in task 12.3) — exclude them here so
# the check reflects what actually gets submitted.
exclude-directories: '.git,.github'
exclude-files: 'CLAUDE.md'
36 changes: 20 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# TETA+PI WordPress Plugin — Plan

Task 12.1 (direction 12, wordpress). Connects a WordPress site to a TETA+PI
entity (`api.tetapi.dev`), proves domain ownership, and displays the resulting
trust badge. Free tier fully functional, standalone; premium ($25 one-time
pack) is stubbed behind a license-key field for a later task.
Task 12.1 (direction 12, wordpress), free-launch copy pass in 12.2. Connects
a WordPress site to a TETA+PI entity (`api.tetapi.dev`), proves domain
ownership, and displays the resulting trust badge. Launched 100% free —
premium modules are teaser-only, unlockable solely via an owner-gifted promo
code (no payment, see § Premium).

Server-side (`teta-pi/api`, `teta-pi/web`, `teta-pi/mcp`) is untouched — this
plugin is a pure API client using existing endpoints ([`docs/api.md`](https://github.com/teta-pi/infra/blob/main/docs/api.md),
Expand Down Expand Up @@ -68,15 +69,17 @@ for caching). PHP 7.4+ compatible (no enums, no readonly props, no `match`).
| Entity picker | populated from `GET /businesses` once key is valid |
| Domain verification status | none / pending / verified, with Start/Check buttons |
| Badge preview | live preview of the shortcode output |
| License key (premium) | optional field, unlocks premium sections when valid — validation stubbed (§ Premium) |
| Redeem code (premium) | optional field, flips `Tetapi_Premium::is_licensed()` true when it matches a promo code hash — no payment, gift-only (§ Premium) |

## $25 Premium Pack — proposed contents (stub only in this task)
## Premium modules — proposed contents (teaser only, 12.2 free launch)

One-time $25 purchase (Gumroad/Lemon Squeezy license key, no payment code in
this repo — the plugin only ever *checks* a license key against a stub
function that returns "not implemented" for now). Sold separately from the
TETA+PI platform itself; distribution/checkout mechanics are a direction-13
GTM decision, not built here.
**Owner decision 2026-07-14: launched 100% free, zero payment code.** Module
#1 ($25) and Module #2 ($52) are teased as "coming soon" in the settings page
and `readme.txt`; neither is for sale. The only unlock path is a promo code
the plugin owner hands out manually (e.g. as a social-media reward) — checked
locally against a SHA-256 hash in `class-tetapi-premium.php`, no license
server, no payment provider. Distribution/checkout mechanics for an eventual
paid launch are a direction-13 GTM decision, not built here.

1. **Badge style pack** — 5 additional badge layouts (compact pill, card with
description, footer strip, floating corner ribbon) instead of the one free
Expand All @@ -97,11 +100,12 @@ GTM decision, not built here.
6. **Priority badge refresh** — shorter transient TTL (near-live trust_level)
instead of the free tier's 15-minute cache.

All six are represented in this task only as disabled UI ("🔒 Coming with
Premium — enter a license key to unlock") behind `class-tetapi-premium.php`,
which exposes a single `Tetapi_Premium::is_licensed()` stub returning `false`.
No license-server calls, no payment/checkout code, nothing that phones home
beyond the existing `api.tetapi.dev` client.
All six are represented as disabled UI ("🔒 Module #1 — $25, coming soon")
behind `class-tetapi-premium.php`. `Tetapi_Premium::is_licensed()` stays
false by default and only flips true if the site owner enters a code
matching a hash in a hardcoded, owner-maintained list — no license-server
calls, no payment/checkout code, nothing that phones home beyond the
existing `api.tetapi.dev` client.

## Plugin Check / wp.org readiness

Expand Down
5 changes: 0 additions & 5 deletions includes/class-tetapi-plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,11 @@ public static function instance() {
}

private function __construct() {
add_action( 'plugins_loaded', array( $this, 'load_textdomain' ) );
register_activation_hook( TETAPI_PLUGIN_FILE, array( 'Tetapi_Domain', 'activate' ) );
register_deactivation_hook( TETAPI_PLUGIN_FILE, array( 'Tetapi_Domain', 'deactivate' ) );

new Tetapi_Settings();
new Tetapi_Domain();
new Tetapi_Badge();
}

public function load_textdomain() {
load_plugin_textdomain( 'tetapi', false, dirname( plugin_basename( TETAPI_PLUGIN_FILE ) ) . '/languages' );
}
}
51 changes: 43 additions & 8 deletions includes/class-tetapi-premium.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
<?php
/**
* Premium ($25 pack) stub — no license-server calls, no payment code. Exists
* so the free-tier settings page can show what premium unlocks without
* shipping any of it yet.
* Premium teaser + promo-unlock — no payment code, no license-server calls.
* Premium isn't for sale yet (owner decision 2026-07-14: free launch). This
* class only (a) shows what's planned, and (b) lets the plugin owner GIFT
* premium to a site by handing out a redeemable code — e.g. as a reward for
* a social-media action — with zero server-side infrastructure.
*/

if ( ! defined( 'ABSPATH' ) ) {
Expand All @@ -12,11 +14,36 @@
class Tetapi_Premium {

/**
* Always false in this release — license validation is not implemented.
* Kept as a single choke point so a future task can wire up real
* validation without touching call sites.
* SHA-256 hashes of valid promo codes, keyed by hash so the plaintext
* code never sits in the public plugin source (wp.org listings are
* fully readable). Empty by default — is_licensed() stays false until
* the plugin owner adds a hash and ships an update.
*
* To hand out a new code: pick a code (e.g. "TETAPI-LAUNCH-2026"), hash
* it, and add the hash below:
* php -r "echo hash('sha256', strtoupper('YOURCODE'));"
*/
private static $promo_code_hashes = array(
// 'paste-sha256-hash-here',
);

/**
* True only if the site owner entered a code matching one of the hashes
* above. No payment, no license-server call — a purely local, per-site
* check against a code the plugin owner handed out manually.
*/
public static function is_licensed() {
$entered = trim( (string) get_option( 'tetapi_license_key', '' ) );
if ( '' === $entered || empty( self::$promo_code_hashes ) ) {
return false;
}

$hash = hash( 'sha256', strtoupper( $entered ) );
foreach ( self::$promo_code_hashes as $valid_hash ) {
if ( hash_equals( $valid_hash, $hash ) ) {
return true;
}
}
return false;
}

Expand All @@ -29,14 +56,22 @@ public static function render_locked_sections() {
__( 'Verification nudges', 'tetapi' ) => __( 'Dashboard reminders to complete email/registry verification.', 'tetapi' ),
__( 'Priority badge refresh', 'tetapi' ) => __( 'Near-live trust level instead of the 15-minute cache.', 'tetapi' ),
);
$licensed = self::is_licensed();
?>
<?php if ( ! $licensed ) : ?>
<p class="description">
<?php esc_html_e( 'Two premium modules are planned — Module #1 ($25) covers the features below, Module #2 ($52) is a further tier coming later. Not for sale yet; nothing to buy here.', 'tetapi' ); ?>
</p>
<?php endif; ?>
<ul class="tetapi-premium-list">
<?php foreach ( $sections as $title => $description ) : ?>
<li class="tetapi-premium-list__item">
<span class="tetapi-premium-list__lock" aria-hidden="true">🔒</span>
<span class="tetapi-premium-list__lock" aria-hidden="true"><?php echo $licensed ? '✅' : '🔒'; ?></span>
<strong><?php echo esc_html( $title ); ?></strong>
<span class="tetapi-premium-list__desc"><?php echo esc_html( $description ); ?></span>
<span class="tetapi-premium-list__badge"><?php esc_html_e( 'Coming with Premium', 'tetapi' ); ?></span>
<span class="tetapi-premium-list__badge">
<?php echo $licensed ? esc_html__( 'Unlocked', 'tetapi' ) : esc_html__( 'Module #1 — $25, coming soon', 'tetapi' ); ?>
</span>
</li>
<?php endforeach; ?>
</ul>
Expand Down
13 changes: 10 additions & 3 deletions includes/views/settings-page.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
exit;
}

$status_messages = array(

Check warning on line 11 in includes/views/settings-page.php

View workflow job for this annotation

GitHub Actions / PHP lint + WordPress Plugin Check

WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound

Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$status_messages".

Check warning on line 11 in includes/views/settings-page.php

View workflow job for this annotation

GitHub Actions / PHP lint + WordPress Plugin Check

WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound

Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$status_messages".
'started' => __( 'Verification started — add the DNS record or wait a moment for the well-known file, then click Check.', 'tetapi' ),
'verified' => __( 'Domain ownership verified.', 'tetapi' ),
'not_yet' => __( 'Not verified yet — the DNS record or well-known file was not found. Try again in a minute.', 'tetapi' ),
Expand Down Expand Up @@ -54,7 +54,7 @@
<?php else : ?>
<select id="tetapi_entity_id" name="tetapi_entity_id">
<option value=""><?php esc_html_e( '— choose —', 'tetapi' ); ?></option>
<?php foreach ( $businesses as $business ) : ?>

Check warning on line 57 in includes/views/settings-page.php

View workflow job for this annotation

GitHub Actions / PHP lint + WordPress Plugin Check

WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound

Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$business".

Check warning on line 57 in includes/views/settings-page.php

View workflow job for this annotation

GitHub Actions / PHP lint + WordPress Plugin Check

WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound

Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$business".
<option value="<?php echo esc_attr( $business['id'] ); ?>" <?php selected( $entity_id, $business['id'] ); ?>>
<?php echo esc_html( $business['name'] ); ?>
</option>
Expand Down Expand Up @@ -116,13 +116,20 @@
<?php settings_fields( Tetapi_Settings::OPTION_GROUP ); ?>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><label for="tetapi_license_key"><?php esc_html_e( 'License Key', 'tetapi' ); ?></label></th>
<th scope="row"><label for="tetapi_license_key"><?php esc_html_e( 'Redeem Code', 'tetapi' ); ?></label></th>
<td>
<input type="text" id="tetapi_license_key" name="tetapi_license_key" class="regular-text" value="<?php echo esc_attr( get_option( 'tetapi_license_key', '' ) ); ?>" placeholder="<?php esc_attr_e( 'Enter your $25 Premium Pack license key', 'tetapi' ); ?>" />
<input type="text" id="tetapi_license_key" name="tetapi_license_key" class="regular-text" value="<?php echo esc_attr( get_option( 'tetapi_license_key', '' ) ); ?>" placeholder="<?php esc_attr_e( 'Have a redeemable code? Enter it here', 'tetapi' ); ?>" />
<?php if ( Tetapi_Premium::is_licensed() ) : ?>
<p class="description tetapi-premium-status tetapi-premium-status--ok">✅ <?php esc_html_e( 'Premium unlocked on this site.', 'tetapi' ); ?></p>
<?php elseif ( get_option( 'tetapi_license_key', '' ) ) : ?>
<p class="description tetapi-premium-status tetapi-premium-status--bad"><?php esc_html_e( 'That code is not valid.', 'tetapi' ); ?></p>
<?php else : ?>
<p class="description"><?php esc_html_e( 'Premium is not for sale yet. Early users may receive a free redeemable code via TetaPi\'s social channels.', 'tetapi' ); ?></p>
<?php endif; ?>
</td>
</tr>
</table>
<?php submit_button( __( 'Save License Key', 'tetapi' ) ); ?>
<?php submit_button( __( 'Save Code', 'tetapi' ) ); ?>
</form>

<?php Tetapi_Premium::render_locked_sections(); ?>
Expand Down
33 changes: 27 additions & 6 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Contributors: tetapi
Tags: trust, verification, badge, domain verification, security
Requires at least: 5.8
Tested up to: 6.6
Tested up to: 7.0
Requires PHP: 7.4
Stable tag: 1.0.0
License: GPLv2 or later
Expand All @@ -23,10 +23,13 @@ This plugin lets a site owner:
* Display a verified-entity badge anywhere via the `[tetapi_badge]` shortcode
or the "TETA+PI Badge" widget.

A $25 Premium Pack (additional badge styles, automatic placement,
multi-entity support, WooCommerce integration) is planned; this release ships
the free tier in full and shows what premium unlocks without requiring
payment to try the plugin.
This release is 100% free — every feature above is fully functional with no
payment required. Two premium modules are planned for later: **Module #1
($25)** — additional badge styles, automatic placement, multi-entity support,
WooCommerce integration — and **Module #2 ($52)**, a further tier coming
after that. Neither is for sale yet; the settings page shows what's planned
and has a field to redeem a free code if TetaPi gives you one (e.g. as a
thank-you for a social-media shoutout).

Learn more at [tetapi.dev](https://tetapi.dev).

Expand Down Expand Up @@ -57,8 +60,26 @@ then generate a personal API key from your account settings.

No, it is encrypted at rest using your site's own WordPress salts.

= Do I have to pay for anything? =

No. Every feature in this release is free. Two premium modules are planned
for later (Module #1 / Module #2) but neither is for sale yet.

= How do I unlock Premium early? =

Premium isn't for sale yet. TetaPi occasionally gives out free redeemable
codes to early users, e.g. as a thank-you for a social-media shoutout. If you
have one, enter it under Settings > TETA+PI > Premium to unlock it on your
site.

== Screenshots ==

1. Settings > TETA+PI — connect your entity with a personal API key.
2. Domain ownership verification status and controls.
3. The TETA+PI trust badge, rendered via shortcode.

== Changelog ==

= 1.0.0 =
* Initial release: settings/connect page, domain ownership verification,
badge shortcode + widget, Premium Pack stubs.
badge shortcode + widget, Premium module teasers + free promo-code redeem.
3 changes: 1 addition & 2 deletions teta-pi.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@
* Version: 1.0.0
* Requires at least: 5.8
* Requires PHP: 7.4
* Author: TetaPi GmbH
* Author: TETA+PI
* Author URI: https://tetapi.dev
* License: GPL v2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: tetapi
* Domain Path: /languages
*/

if ( ! defined( 'ABSPATH' ) ) {
Expand Down
Loading