diff --git a/.cursor/Dockerfile b/.cursor/Dockerfile index 5898f3f..f8d431d 100644 --- a/.cursor/Dockerfile +++ b/.cursor/Dockerfile @@ -24,7 +24,7 @@ RUN printf '%s\n' \ 'export WP_TESTS_DB_USER=root' \ 'export WP_TESTS_DB_PASSWORD=root' \ 'export WP_TESTS_DB_HOST=127.0.0.1' \ - > /etc/profile.d/fqj-wp-tests-db.sh + > /etc/profile.d/rfsd-wp-tests-db.sh RUN echo 'ubuntu ALL=(ALL) NOPASSWD: /usr/sbin/service mariadb *' > /etc/sudoers.d/mariadb \ && chmod 440 /etc/sudoers.d/mariadb diff --git a/.cursor/cloud-install.sh b/.cursor/cloud-install.sh index 4deb9c2..603a325 100755 --- a/.cursor/cloud-install.sh +++ b/.cursor/cloud-install.sh @@ -4,8 +4,8 @@ set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT" -if [[ ! -f /etc/profile.d/fqj-wp-tests-db.sh ]]; then - sudo tee /etc/profile.d/fqj-wp-tests-db.sh >/dev/null <<'EOF' +if [[ ! -f /etc/profile.d/rfsd-wp-tests-db.sh ]]; then + sudo tee /etc/profile.d/rfsd-wp-tests-db.sh >/dev/null <<'EOF' export WP_TESTS_DB_NAME=wordpress_test export WP_TESTS_DB_USER=root export WP_TESTS_DB_PASSWORD=root diff --git a/.cursor/environment.json b/.cursor/environment.json index 66ac950..3480ea8 100644 --- a/.cursor/environment.json +++ b/.cursor/environment.json @@ -1,5 +1,5 @@ { - "name": "FAQ JSON-LD WordPress Plugin", + "name": "Renderbit FAQ Structured Data WordPress Plugin", "build": { "dockerfile": "Dockerfile", "context": "." diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 3f5d898..3ce993e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,5 +1,5 @@ name: Bug Report -description: Report a bug or issue with the FAQ JSON-LD Manager plugin +description: Report a bug or issue with the Renderbit FAQ Structured Data plugin title: "[Bug]: " labels: ["bug"] body: @@ -12,7 +12,7 @@ body: id: version attributes: label: Plugin Version - description: What version of FAQ JSON-LD Manager are you using? + description: What version of Renderbit FAQ Structured Data are you using? placeholder: e.g., 3.0.0 validations: required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 9776822..9ac1f67 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,5 +1,5 @@ name: Feature Request -description: Suggest a new feature or improvement for the FAQ JSON-LD Manager plugin +description: Suggest a new feature or improvement for the Renderbit FAQ Structured Data plugin title: "[Feature]: " labels: ["enhancement"] body: diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 37c71da..6f70709 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,4 +1,4 @@ -# Copilot Instructions for FAQ JSON-LD WordPress Plugin +# Copilot Instructions for Renderbit FAQ Structured Data WordPress Plugin ## Overview @@ -6,20 +6,20 @@ This plugin manages FAQ items as a custom post type (CPT) and injects FAQ JSON-L ## Architecture & Key Components -- **Custom Post Type:** `faq_item` is registered for FAQ entries. -- **Mapping Table:** MySQL table (`fqj_mappings`) links FAQ items to posts, post types, terms, URLs, or global context. See `includes/indexer.php`. +- **Custom Post Type:** `rfsd_faq` is registered for FAQ entries. +- **Mapping Table:** MySQL table (`rfsd_mappings`) links FAQ items to posts, post types, terms, URLs, or global context. See `includes/indexer.php`. - **JSON-LD Output:** Injected on the frontend using `includes/frontend.php`, with per-post transient caching for performance. - **Cache Invalidation:** Uses a background queue (`includes/queue.php`) processed by WP-Cron or WP-CLI. Queue state and logs are visible in the Health admin page. -- **Admin UI:** Custom meta boxes and settings in `includes/admin.php` and `includes/settings.php`. Select2 (bundled in `assets/vendor/select2`) is used for post selectors (see `assets/js/fqj-admin.js`). +- **Admin UI:** Custom meta boxes and settings in `includes/admin.php` and `includes/settings.php`. Select2 (bundled in `assets/vendor/select2`) is used for post selectors (see `assets/js/rfsd-admin.js`). - **Health & Diagnostics:** Admin page (`includes/health.php`) shows queue status, logs, and provides manual actions. -- **WP-CLI Integration:** `includes/class-fqjcli.php` provides commands for purging transients, processing the invalidation queue and showing its length. +- **WP-CLI Integration:** `includes/class-rfsd-cli.php` provides commands for purging transients, processing the invalidation queue and showing its length. ## Developer Workflows - **Activate/Upgrade:** On plugin activation, the mapping table is created/updated automatically. - **Settings:** Accessible under the FAQ Items menu. Options include cache TTL, batch size, and output type. - **Cache Management:** - - Manual: Use Health admin page or WP-CLI (`wp fqj purge-transients`, `wp fqj process-queue [--limit=N]`, `wp fqj queue-info`). + - Manual: Use Health admin page or WP-CLI (`wp rfsd purge-transients`, `wp rfsd process-queue [--limit=N]`, `wp rfsd queue-info`). - Automatic: Invalidation queue is processed every 5 minutes by WP-Cron. - **Debugging:** - Check the Health admin page for queue/logs. @@ -28,8 +28,8 @@ This plugin manages FAQ items as a custom post type (CPT) and injects FAQ JSON-L ## Project-Specific Patterns - **Mapping Structure:** All associations (post, post_type, term, url, global) are stored in a single table for fast lookups. -- **Transient Keys:** Format is `fqj_faq_json_{generation}_{post_id}`; always build it with `fqj_cache_key( $post_id )`. The mapping table name comes from `fqj_table_name()`. -- **Admin JS:** Uses Select2 for post selectors, AJAX for search (`fqj_search_posts` action). +- **Transient Keys:** Format is `rfsd_faq_json_{generation}_{post_id}`; always build it with `rfsd_cache_key( $post_id )`. The mapping table name comes from `rfsd_table_name()`. +- **Admin JS:** Uses Select2 for post selectors, AJAX for search (`rfsd_search_posts` action). - **Settings/Health Pages:** Registered as submenus under the FAQ Items CPT. ## Integration Points @@ -40,15 +40,15 @@ This plugin manages FAQ items as a custom post type (CPT) and injects FAQ JSON-L ## Key Files -- `faq-jsonld.php`: Main plugin loader and CPT registration. +- `renderbit-faq-structured-data.php`: Main plugin loader and CPT registration. - `includes/indexer.php`: Mapping table logic. - `includes/queue.php`: Invalidation queue logic. - `includes/frontend.php`: JSON-LD output and caching. - `includes/admin.php`: Admin UI/meta boxes. - `includes/settings.php`: Settings page. - `includes/health.php`: Health/diagnostics UI. -- `includes/class-fqjcli.php`: WP-CLI commands. -- `assets/js/fqj-admin.js`, `assets/js/fqj-health.js`: Admin JS (Select2, AJAX). +- `includes/class-rfsd-cli.php`: WP-CLI commands. +- `assets/js/rfsd-admin.js`, `assets/js/rfsd-health.js`: Admin JS (Select2, AJAX). ## Example: Adding a New Mapping Type diff --git a/.github/workflows/deploy-wporg.yml b/.github/workflows/deploy-wporg.yml index 7a4606c..eddb6f0 100644 --- a/.github/workflows/deploy-wporg.yml +++ b/.github/workflows/deploy-wporg.yml @@ -22,10 +22,10 @@ jobs: run: | tag="${GITHUB_REF#refs/tags/}" tag="${tag#v}" - header="$(sed -n 's/^ \* Version: *//p' faq-jsonld.php)" - constant="$(sed -n "s/^define( 'FQJ_VERSION', '\(.*\)' );$/\1/p" faq-jsonld.php)" + header="$(sed -n 's/^ \* Version: *//p' renderbit-faq-structured-data.php)" + constant="$(sed -n "s/^define( 'RFSD_VERSION', '\(.*\)' );$/\1/p" renderbit-faq-structured-data.php)" stable="$(sed -n 's/^Stable tag: *//p' readme.txt)" - echo "tag=$tag header=$header FQJ_VERSION=$constant stable=$stable" + echo "tag=$tag header=$header RFSD_VERSION=$constant stable=$stable" [ "$tag" = "$header" ] && [ "$tag" = "$constant" ] && [ "$tag" = "$stable" ] - name: Deploy to WordPress.org SVN @@ -33,4 +33,4 @@ jobs: env: SVN_USERNAME: ${{ secrets.SVN_USERNAME }} SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} - SLUG: faq-jsonld + SLUG: renderbit-faq-structured-data diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index 7006952..bf6ff74 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -40,6 +40,6 @@ jobs: name: Release ${{ env.TAG_NAME }} draft: true generate_release_notes: true - files: faq-jsonld.zip + files: renderbit-faq-structured-data.zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5eee2dd..ee3dd46 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -32,7 +32,7 @@ jobs: tools: none - name: Lint plugin files - run: find faq-jsonld.php uninstall.php includes -name '*.php' -print0 | xargs -0 -n1 php -l + run: find renderbit-faq-structured-data.php uninstall.php includes -name '*.php' -print0 | xargs -0 -n1 php -l phpcs: name: Coding standards @@ -59,12 +59,12 @@ jobs: # Check what ships (the git archive, as in the release zip and WordPress.org SVN), not the dev files. - name: Export plugin files - run: mkdir -p build/faq-jsonld && git archive HEAD | tar -x -C build/faq-jsonld + run: mkdir -p build/renderbit-faq-structured-data && git archive HEAD | tar -x -C build/renderbit-faq-structured-data - uses: WordPress/plugin-check-action@10857da14b6c2246d15402b3e69f777edcf8c12e # v1.1.9 with: - build-dir: build/faq-jsonld - slug: faq-jsonld + build-dir: build/renderbit-faq-structured-data + slug: renderbit-faq-structured-data strict: true phpunit: diff --git a/AGENTS.md b/AGENTS.md index 2f922f6..9822920 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,8 +1,8 @@ # AGENTS.md -WordPress plugin `faq-jsonld`: FAQs as a `faq_item` CPT, schema.org `FAQPage` JSON-LD printed on singular posts/pages. Procedural PHP, every function prefixed `fqj_`, no build step, no runtime Composer deps. +WordPress plugin `renderbit-faq-structured-data`: FAQs as a `rfsd_faq` CPT (`RFSD_POST_TYPE`; FAQs from the old `faq-jsonld` plugin are imported on activation), schema.org `FAQPage` JSON-LD printed on singular posts/pages. Procedural PHP, every function prefixed `rfsd_`, no build step, no runtime Composer deps. -**PHP ceiling is 7.2 for shipped code** — `composer.json` pins platform 7.4, so syntax up to 7.4 passes locally while CI's `php -l` matrix (7.2–8.4) fails. Write 7.2-compatible syntax in `faq-jsonld.php`, `uninstall.php`, `includes/`. +**PHP ceiling is 7.2 for shipped code** — `composer.json` pins platform 7.4, so syntax up to 7.4 passes locally while CI's `php -l` matrix (7.2–8.4) fails. Write 7.2-compatible syntax in `renderbit-faq-structured-data.php`, `uninstall.php`, `includes/`. Full architecture, data flow (save → index → render → invalidate), and versioning rules live in `CLAUDE.md` — read its Architecture section before touching `includes/indexer.php`, `includes/queue.php`, `includes/frontend.php`, or cache keys. Keep `CLAUDE.md` in sync if you change behavior it describes. @@ -11,13 +11,13 @@ Full architecture, data flow (save → index → render → invalidate), and ver ```bash composer install && npm install # setup; .env.testing must exist (copy .env.testing.example) -composer lint # phpcs, WordPress Coding Standards, text domain faq-jsonld +composer lint # phpcs, WordPress Coding Standards, text domain renderbit-faq-structured-data composer test # PHPUnit, single-site composer test:multisite # same suite as a multisite network vendor/bin/phpunit --filter test_name # one test vendor/bin/phpunit tests/php/test-queue.php # one file npm test # Jest (jsdom) for assets/js -npx jest tests/js/fqj-admin.test.js -t "name" # one JS test +npx jest tests/js/rfsd-admin.test.js -t "name" # one JS test ``` CI (`.github/workflows/tests.yml`) runs all of the above plus `WordPress/plugin-check` on every PR. Verify with `composer lint && composer test && npm test` before claiming done. @@ -29,6 +29,6 @@ CI (`.github/workflows/tests.yml`) runs all of the above plus `WordPress/plugin- - Test groups: network-only tests are tagged `@group ms-required`, single-site-only `@group ms-excluded`; each phpunit config excludes the other group, so a test in neither group runs twice and one in both runs never. - `composer build-zip` runs `git archive HEAD` — **uncommitted changes are not in the zip**. `composer check-zip` verifies contents against `.gitattributes` `export-ignore`, which also decides what ships to WordPress.org SVN. Add new dev-only files there. - Raw `$wpdb` queries use `%i` for table identifiers and carry targeted `phpcs:ignore`/`phpcs:disable … phpcs:enable` comments naming the specific sniffs — keep this pattern so `composer lint` stays clean. -- Two independent version constants: bump `FQJ_DB_VERSION` only when `CREATE TABLE` SQL in `fqj_create_table()` changes (dbDelta rules: no `IF NOT EXISTS`, two spaces after `PRIMARY KEY`, `KEY` not `INDEX`, no inline comments); bump `FQJ_DATA_VERSION` only to force a one-time reindex + cache purge. -- Transient keys are always built with `fqj_cache_key( $post_id )` (embeds `fqj_cache_gen`) — never hand-build `fqj_faq_json_*` keys, or purge-all breaks with a persistent object cache. -- Releasing: `composer bump-patch|minor|major` keeps four version strings in sync (`composer.json`, plugin header, `FQJ_VERSION`, `readme.txt` Stable tag); add the `readme.txt` changelog entry by hand. Pushing a `vX.Y.Z` tag to `main` creates a draft release; publishing it deploys to WordPress.org SVN. +- Two independent version constants: bump `RFSD_DB_VERSION` only when `CREATE TABLE` SQL in `rfsd_create_table()` changes (dbDelta rules: no `IF NOT EXISTS`, two spaces after `PRIMARY KEY`, `KEY` not `INDEX`, no inline comments); bump `RFSD_DATA_VERSION` only to force a one-time reindex + cache purge. +- Transient keys are always built with `rfsd_cache_key( $post_id )` (embeds `rfsd_cache_gen`) — never hand-build `rfsd_faq_json_*` keys, or purge-all breaks with a persistent object cache. +- Releasing: `composer bump-patch|minor|major` keeps four version strings in sync (`composer.json`, plugin header, `RFSD_VERSION`, `readme.txt` Stable tag); add the `readme.txt` changelog entry by hand. Pushing a `vX.Y.Z` tag to `main` creates a draft release; publishing it deploys to WordPress.org SVN. diff --git a/CLAUDE.md b/CLAUDE.md index 054ea11..3fcf28c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,61 +2,61 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. -WordPress plugin (slug `faq-jsonld`, published on WordPress.org) that manages FAQs as a `faq_item` custom post type and prints schema.org `FAQPage` JSON-LD in `wp_head` on the singular posts/pages each FAQ is mapped to. Procedural PHP, every function prefixed `fqj_`; no build step and no runtime Composer dependencies. Supports WordPress 6.5+ and PHP 7.2+, so don't use syntax newer than PHP 7.2 in shipped code. +WordPress plugin (slug `renderbit-faq-structured-data`, published on WordPress.org) that manages FAQs as a `rfsd_faq` custom post type (`RFSD_POST_TYPE`) and prints schema.org `FAQPage` JSON-LD in `wp_head` on the singular posts/pages each FAQ is mapped to. Procedural PHP, every function prefixed `rfsd_`; no build step and no runtime Composer dependencies. Supports WordPress 6.5+ and PHP 7.2+, so don't use syntax newer than PHP 7.2 in shipped code. ## Commands ```bash composer install && npm install -composer lint # phpcs with WordPress Coding Standards (phpcs.xml), text domain faq-jsonld +composer lint # phpcs with WordPress Coding Standards (phpcs.xml), text domain renderbit-faq-structured-data composer test # PHPUnit integration suite, single-site composer test:multisite # same suite as a multisite network vendor/bin/phpunit --filter test_name_here # one test vendor/bin/phpunit tests/php/test-queue.php # one file npm test # Jest (jsdom) tests for assets/js -npx jest tests/js/fqj-admin.test.js -t "name" # one JS test +npx jest tests/js/rfsd-admin.test.js -t "name" # one JS test composer build-zip && composer check-zip # release zip from `git archive HEAD`, then verify contents composer bump-patch # or bump-minor / bump-major ``` -PHPUnit runs against a real MySQL/MariaDB database that the suite **wipes on every run**. Settings come from `.env.testing` (copy `.env.testing.example`); shell env vars win. Tests are loaded by `tests/php/bootstrap.php`, which loads the plugin on `muplugins_loaded` and calls `fqj_activate()` on `init`. Extend `FQJ_TestCase` (`tests/php/class-fqj-testcase.php`, has `create_faq()` helpers) or `FQJ_Multisite_TestCase`. Tag network-only tests `@group ms-required` and single-site-only tests `@group ms-excluded`; each config excludes the other group. PHPUnit converts notices, warnings and deprecations to failures. WP-CLI is stubbed in `tests/php/stubs/class-wp-cli.php`. +PHPUnit runs against a real MySQL/MariaDB database that the suite **wipes on every run**. Settings come from `.env.testing` (copy `.env.testing.example`); shell env vars win. Tests are loaded by `tests/php/bootstrap.php`, which loads the plugin on `muplugins_loaded` and calls `rfsd_activate()` on `init`. Extend `RFSD_TestCase` (`tests/php/class-rfsd-testcase.php`, has `create_faq()` helpers) or `RFSD_Multisite_TestCase`. Tag network-only tests `@group ms-required` and single-site-only tests `@group ms-excluded`; each config excludes the other group. PHPUnit converts notices, warnings and deprecations to failures. WP-CLI is stubbed in `tests/php/stubs/class-wp-cli.php`. ## Architecture -`faq-jsonld.php` defines constants, requires every file in `includes/`, registers the CPT, and handles activation/deactivation (looping over all sites via `fqj_for_each_site()` on network activation and setting up new sites on `wp_initialize_site`). +`renderbit-faq-structured-data.php` defines constants, requires every file in `includes/`, registers the CPT, imports a site's data from the old FAQ JSON-LD Manager plugin (`faq-jsonld`, `fqj_` names listed in `rfsd_legacy_storage_names()`) once per site in `rfsd_import_legacy_data()` (on `plugins_loaded` priority 5 and on activation, before the schema upgrade, the data migration and activation's has-FAQs check), and handles activation/deactivation (looping over all sites via `rfsd_for_each_site()` on network activation and setting up new sites on `wp_initialize_site`). Data flow: -1. **Save (`includes/admin.php`)**: the meta box stores the FAQ's associations as JSON in post meta `fqj_assoc_data_json` (keys `urls`, `posts`, `post_types`, `terms`, `global`), then the index is rebuilt. -2. **Index (`includes/indexer.php`)**: `fqj_rebuild_index_for_faq()` replaces the FAQ's rows in the `{prefix}fqj_mappings` table (`faq_id`, `mapping_type` ∈ post/post_type/term/url/global, `mapping_value`). URLs go through `fqj_normalize_url()`, which stores URLs on this site relative to the site root (`/about`, `/`, `/?p=12`). A URL association covers whichever post's permalink equals that URL now, so it never gets a `post` row (ADR-0001). Old and new mappings are both passed to `fqj_enqueue_invalidation_for_mappings()`. Hooks on status transitions, post updates, deletes, term changes and permalink changes keep the index and cache in sync. -3. **Render (`includes/frontend.php`)**: on `is_singular()` only, builds candidate (type, value) pairs for the current post (its ID, post type, terms in all its taxonomies, normalized permalink, global; the legacy absolute and trailing-slash URL shapes only while data version is below 3), runs one query against the mapping table, and caches the `' ); + set_transient( rfsd_cache_key( $post ), '' ); $html = $this->render_head_for( $post ); $this->assertStringContainsString( '(cached)', $html ); @@ -97,21 +97,21 @@ public function test_cached_output_is_served_from_transient() { public function test_blank_cached_value_reports_unknown_state() { $post = self::factory()->post->create(); - set_transient( fqj_cache_key( $post ), ' ' ); + set_transient( rfsd_cache_key( $post ), ' ' ); $this->assertStringContainsString( 'cached, unknown state', $this->render_head_for( $post ) ); } public function test_generated_output_is_cached_with_ttl() { - update_option( FQJ_OPTION_KEY, array( 'cache_ttl' => 600 ) ); + update_option( RFSD_OPTION_KEY, array( 'cache_ttl' => 600 ) ); $post = self::factory()->post->create(); $this->create_faq( 'Q', 'A', array( 'posts' => array( $post ) ) ); $html = $this->render_head_for( $post ); - $cached = get_transient( fqj_cache_key( $post ) ); + $cached = get_transient( rfsd_cache_key( $post ) ); $this->assertStringStartsWith( '