From 5835e5a3b289c4f8e26bfad2e67d3880775fb784 Mon Sep 17 00:00:00 2001 From: Jason Eugene Garrison Date: Sat, 11 Jul 2026 19:53:52 -0500 Subject: [PATCH] Add public WordPress release conventions --- .gitignore | 2 + WORDPRESS-PUBLIC-CONVENTIONS.md | 71 +++++ plugins/bible-reference-tooltips/CHANGELOG.md | 31 +- .../bible-reference-tooltips.php | 300 ++++++++---------- plugins/bible-reference-tooltips/readme.txt | 37 ++- tools/testlab/README.md | 61 ++++ tools/testlab/config/environments.json | 15 + tools/testlab/results/wp6-php74.json | 149 +++++++++ tools/testlab/results/wp7-php84.json | 144 +++++++++ tools/testlab/scripts/common.ps1 | 141 ++++++++ tools/testlab/scripts/down-all.ps1 | 6 + tools/testlab/scripts/down-env.ps1 | 9 + tools/testlab/scripts/run-all.ps1 | 9 + tools/testlab/scripts/run-env.ps1 | 158 +++++++++ .../scripts/seed-bible-reference-page-all.ps1 | 9 + .../scripts/seed-bible-reference-page.ps1 | 142 +++++++++ 16 files changed, 1109 insertions(+), 175 deletions(-) create mode 100644 WORDPRESS-PUBLIC-CONVENTIONS.md create mode 100644 tools/testlab/README.md create mode 100644 tools/testlab/config/environments.json create mode 100644 tools/testlab/results/wp6-php74.json create mode 100644 tools/testlab/results/wp7-php84.json create mode 100644 tools/testlab/scripts/common.ps1 create mode 100644 tools/testlab/scripts/down-all.ps1 create mode 100644 tools/testlab/scripts/down-env.ps1 create mode 100644 tools/testlab/scripts/run-all.ps1 create mode 100644 tools/testlab/scripts/run-env.ps1 create mode 100644 tools/testlab/scripts/seed-bible-reference-page-all.ps1 create mode 100644 tools/testlab/scripts/seed-bible-reference-page.ps1 diff --git a/.gitignore b/.gitignore index 9c63024..993423c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ vendor/ *.zip *.log .env + +tools/testlab/generated/ diff --git a/WORDPRESS-PUBLIC-CONVENTIONS.md b/WORDPRESS-PUBLIC-CONVENTIONS.md new file mode 100644 index 0000000..3b073c0 --- /dev/null +++ b/WORDPRESS-PUBLIC-CONVENTIONS.md @@ -0,0 +1,71 @@ +# WordPress Public Workspace Conventions + +This workspace prepares Christ IT WordPress plugins and themes for public distribution. Git is the development source of truth. WordPress.org SVN, public plugin directories, and installed production copies are release targets, not the working source. + +## Scope + +- Public plugin source lives under `plugins//`. +- Shared local release and smoke-test tooling lives under `tools/`. +- Do not remove or replace the matching internal GracePress package until the public package is accepted, published, installed on the target production site, and verified on the public update path. + +## Version Discipline + +Every meaningful public plugin change must update, in the same commit: + +- the plugin header `Version`; +- any runtime version constant; +- `readme.txt` `Stable tag`; +- the plugin `CHANGELOG.md`; +- the `readme.txt` changelog when the change is public-facing. + +Use one version number per bounded pass. Do not keep piling unrelated verification fixes into the same version after the pass has been declared complete. + +## Changelog Discipline + +The standalone plugin changelog is the durable engineering record. The WordPress.org readme changelog is the public release summary. + +For migrated GracePress plugins: + +- keep enough prior internal history to explain origin and compatibility; +- clearly mark the public fork version; +- remove or supersede public claims that no longer match the current package; +- record test-lab evidence in Git when a version is considered a release candidate. + +## Bounded Verification + +Use this default verification loop unless the user explicitly asks for more: + +1. Run PHP syntax checks for touched PHP files. +2. Run the configured Docker smoke matrix once. +3. Save smoke results under `tools/testlab/results/`. +4. Review failures only far enough to identify the next code or test fix. +5. After a passing matrix, commit and stop. + +Do not start recursive verification such as verifying the verifier, expanding the matrix, or repeatedly re-running browser checks unless a named release gate fails or the user explicitly asks for a broader pass. + +## Test Lab + +The local Docker test lab belongs in `tools/testlab/` so scripts, environment definitions, and result JSON stay tied to Git history. + +Smoke tests should prove the contract, not exhaustively explore the UI: + +- plugin activates; +- supported WordPress/PHP versions boot; +- expected plugin version is active; +- required tables are created; +- supported local Bible texts import; +- representative REST lookups pass; +- a browser-facing test page can be seeded for manual review. + +Manual browser review is a separate, explicitly requested step. + +## Release Boundary + +A "build/test/commit and stop" request means: + +- complete the named edits; +- run the bounded verification loop once; +- commit the coherent checkpoint if verification passes; +- report what passed and stop. + +Do not publish to WordPress.org, production sites, or public update channels unless the user explicitly asks for that release step. diff --git a/plugins/bible-reference-tooltips/CHANGELOG.md b/plugins/bible-reference-tooltips/CHANGELOG.md index b8e0432..81e73de 100644 --- a/plugins/bible-reference-tooltips/CHANGELOG.md +++ b/plugins/bible-reference-tooltips/CHANGELOG.md @@ -1,12 +1,41 @@ # Bible Reference Tooltips Changelog +## 1.1.5 - 2026-07-12 + +- Adds the public workspace conventions and tracked Docker test lab as the bounded release workflow for this public package. +- Adds a settings migration that removes retired external-link options while preserving active tooltip settings. +- Updates the standalone changelog to match the public 1.1.x transition history. + +## 1.1.4 - 2026-07-11 + +- Removes external Biblia, ref.ly, and Logos link generation from the public plugin. +- Adds optional WordPress admin scanning with separate admin scan selectors. +- Improves matching-rule defaults for capitalization, chapter-only references, and public comments. +- Generalizes the open text policy language. + +## 1.1.3 - 2026-07-11 + +- Fixes browser REST lookups when WordPress provides a query-string REST route. +- Improves local test-page layout and removes native browser title tooltips from test references. + +## 1.1.2 - 2026-07-11 + +- Splits eBible VPL source-code resolution from human Bible reference parsing. +- Adds exact source-code handling for compact eBible book identifiers such as `SOL`, `EZE`, `MAR`, `JOH`, `JAM`, `1JO`, `2JO`, and `3JO`. +- Improves compact reference detection for forms such as `Mark10:45` and `2Jn1:6`. + +## 1.1.1 - 2026-07-11 + +- Adds exact eBible VPL aliases for source abbreviations that were not standard display abbreviations. +- Fixes imports for books such as Mark and Ezekiel when VPL source files use compact book codes. + ## 1.1.0 - 2026-07-11 - Forked the former internal Bible tooltip package into a standalone public plugin named Bible Reference Tooltips. - Renamed the plugin slug, text domain, REST namespace, script/style handles, admin action names, option key, and local verse table for the public package. - Removed internal menu placement and internal logging dependencies from the public package. - Added public release metadata, GPL license headers, and WordPress.org-oriented naming. -- Kept local Bible text imports, frontend reference detection, hover/focus cards, translation tabs, Biblia.com links, Logos links, skipped-tag controls, comment scanning, and James alias/backfill repairs. +- Kept local Bible text imports, frontend reference detection, hover/focus cards, translation tabs, skipped-tag controls, comment scanning, and James alias/backfill repairs. ## 1.0.14 - 2026-06-25 diff --git a/plugins/bible-reference-tooltips/bible-reference-tooltips.php b/plugins/bible-reference-tooltips/bible-reference-tooltips.php index acb4636..7451ada 100644 --- a/plugins/bible-reference-tooltips/bible-reference-tooltips.php +++ b/plugins/bible-reference-tooltips/bible-reference-tooltips.php @@ -3,7 +3,7 @@ * Plugin Name: Bible Reference Tooltips * Plugin URI: https://christit.com/bible-reference-tooltips * Description: Local, open Bible reference tooltips for WordPress content. Detects verse references on page load and serves hover cards from a local verse table. - * Version: 1.1.0 + * Version: 1.1.5 * Stage: Stable * Release Channel: Public * Public Release: Yes @@ -26,10 +26,11 @@ if (!defined('ABSPATH')) exit; if (!class_exists('Bible_Reference_Tooltips')) { class Bible_Reference_Tooltips { - const VERSION = '1.1.0'; + const VERSION = '1.1.5'; const SLUG = 'bible-reference-tooltips'; const NONCE = 'brt_nonce'; const OPTION_KEY = 'brt_settings'; + const SETTINGS_VERSION = '1.1.5'; const TABLE_VERSION = '1'; private $book_map = null; @@ -40,6 +41,7 @@ if (!class_exists('Bible_Reference_Tooltips')) { add_action('admin_init', [$this, 'ensure_schema']); add_action('admin_menu', [$this, 'admin_menu'], 110); add_action('wp_enqueue_scripts', [$this, 'frontend_assets']); + add_action('admin_enqueue_scripts', [$this, 'admin_assets']); add_action('rest_api_init', [$this, 'rest_routes']); add_filter('the_content', [$this, 'mark_content_scope'], 1); add_filter('comment_text', [$this, 'mark_comment_scope'], 1); @@ -72,7 +74,16 @@ if (!class_exists('Bible_Reference_Tooltips')) { } public function ensure_defaults() { - if (!get_option(self::OPTION_KEY)) update_option(self::OPTION_KEY, self::defaults(), false); + $settings = get_option(self::OPTION_KEY); + if (!is_array($settings)) { + update_option(self::OPTION_KEY, self::defaults(), false); + update_option('brt_settings_version', self::SETTINGS_VERSION, false); + return; + } + if (get_option('brt_settings_version') !== self::SETTINGS_VERSION) { + update_option(self::OPTION_KEY, $this->normalize_settings($settings), false); + update_option('brt_settings_version', self::SETTINGS_VERSION, false); + } } public function ensure_schema() { @@ -88,23 +99,17 @@ if (!class_exists('Bible_Reference_Tooltips')) { public static function defaults() { return [ 'enabled' => '1', + 'admin_enabled' => '0', 'translation' => 'WEB', - 'scope' => 'body', + 'scope' => '.brt-scan-scope, main, .entry-content, .wp-block-post-content', + 'admin_scope' => '#wpbody-content', 'theme' => 'auto', 'underline' => '1', 'show_translation' => '1', 'show_tooltips' => '1', - 'link_references' => '1', - 'link_target' => 'same', - 'bible_version' => '', - 'logos_insert' => '0', - 'logos_existing' => '0', - 'logos_translation' => '', - 'logos_icon' => 'dark', - 'add_tooltips_to_links' => '0', - 'case_sensitive' => '0', - 'tag_chapters' => '0', - 'scan_comments' => '0', + 'case_insensitive' => '1', + 'tag_chapters' => '1', + 'scan_comments' => '1', 'skip_tags' => ['SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT', 'SELECT', 'BUTTON', 'CODE', 'PRE', 'H1', 'H2', 'H3'], 'max_range' => '12', ]; @@ -112,9 +117,18 @@ if (!class_exists('Bible_Reference_Tooltips')) { private function settings() { $settings = get_option(self::OPTION_KEY, []); + return $this->normalize_settings(is_array($settings) ? $settings : []); + } + + private function normalize_settings($settings) { $settings = array_merge(self::defaults(), is_array($settings) ? $settings : []); + if (array_key_exists('case_sensitive', $settings) && !array_key_exists('case_insensitive', $settings)) { + $settings['case_insensitive'] = empty($settings['case_sensitive']) ? '1' : '0'; + } $settings['skip_tags'] = $this->normalize_skip_tags($settings['skip_tags'] ?? []); - return $settings; + $clean = []; + foreach (array_keys(self::defaults()) as $key) $clean[$key] = $settings[$key]; + return $clean; } private static function install_schema() { @@ -150,6 +164,16 @@ if (!class_exists('Bible_Reference_Tooltips')) { public function frontend_assets() { $settings = $this->settings(); if (empty($settings['enabled']) || is_admin() || is_feed() || wp_doing_ajax()) return; + $this->enqueue_tooltip_assets($settings, (string)$settings['scope']); + } + + public function admin_assets($hook_suffix = '') { + $settings = $this->settings(); + if (empty($settings['admin_enabled']) || wp_doing_ajax()) return; + $this->enqueue_tooltip_assets($settings, (string)$settings['admin_scope']); + } + + private function enqueue_tooltip_assets($settings, $scope) { wp_register_script('brt', '', [], self::VERSION, true); wp_enqueue_script('brt'); wp_register_style('brt', false, [], self::VERSION); @@ -160,22 +184,14 @@ if (!class_exists('Bible_Reference_Tooltips')) { wp_localize_script('brt', 'BibleReferenceTooltips', [ 'rest' => esc_url_raw(rest_url('bible-reference-tooltips/v1/lookup')), 'nonce' => wp_create_nonce('wp_rest'), - 'scope' => (string)$settings['scope'], + 'scope' => $scope, 'translation' => $default_translation, 'translations' => $installed_translations, 'theme' => (string)$settings['theme'], 'underline' => !empty($settings['underline']), 'showTranslation' => !empty($settings['show_translation']), 'showTooltips' => !empty($settings['show_tooltips']), - 'linkReferences' => !empty($settings['link_references']), - 'linkTarget' => (string)$settings['link_target'], - 'bibleVersion' => (string)($settings['bible_version'] ?: $default_translation), - 'logosInsert' => !empty($settings['logos_insert']), - 'logosExisting' => !empty($settings['logos_existing']), - 'logosTranslation' => (string)($settings['logos_translation'] ?: $default_translation), - 'logosIcon' => (string)$settings['logos_icon'], - 'addTooltipsToLinks' => !empty($settings['add_tooltips_to_links']), - 'caseSensitive' => !empty($settings['case_sensitive']), + 'caseSensitive' => empty($settings['case_insensitive']), 'tagChapters' => !empty($settings['tag_chapters']), 'scanComments' => !empty($settings['scan_comments']), 'skipTags' => array_values((array)$settings['skip_tags']), @@ -409,26 +425,6 @@ if (!class_exists('Bible_Reference_Tooltips')) { ]; } - private function bible_link_versions() { - return [ - 'WEB' => 'WEB', - 'KJV' => 'KJV', - 'ASV' => 'ASV', - 'YLT' => 'YLT', - 'ESV' => 'ESV', - 'NIV' => 'NIV', - 'NKJV' => 'NKJV', - 'NLT' => 'NLT', - 'NASB' => 'NASB', - 'CSB' => 'CSB', - 'NET' => 'NET', - 'RSV' => 'RSV', - 'NRSV' => 'NRSV', - 'LEB' => 'LEB', - 'MSG' => 'The Message', - ]; - } - private function book_lookup_candidates($parsed) { $abbr = strtoupper(preg_replace('/[^A-Z0-9]/i', '', $parsed['book'] ?? '')); $name = strtoupper(preg_replace('/[^A-Z0-9]/i', '', $parsed['book_name'] ?? '')); @@ -442,7 +438,7 @@ if (!class_exists('Bible_Reference_Tooltips')) { 'PHM' => ['PHM', 'PHLM', 'PHILEM'], 'REV' => ['REV', 'RE', 'APO'], ]; - $candidates = array_merge([$abbr, $name], $variants[$abbr] ?? []); + $candidates = array_merge([$abbr, $name], $variants[$abbr] ?? [], $this->source_book_lookup_candidates($abbr)); foreach ($this->book_map() as $alias => $entry) { if (strtoupper($entry['abbr']) !== $abbr) continue; $candidates[] = strtoupper(preg_replace('/[^A-Z0-9]/i', '', $alias)); @@ -469,7 +465,7 @@ if (!class_exists('Bible_Reference_Tooltips')) { private function parse_reference($reference) { $reference = trim(preg_replace('/\s+/', ' ', (string)$reference)); - if (!preg_match('/^((?:[1-3]\s*)?[A-Za-z][A-Za-z .]+?)\s+(\d{1,3})(?:\s*:\s*(\d{1,3})(?:\s*[-\x{2013}\x{2014}]\s*(\d{1,3}))?)?$/u', $reference, $m)) return null; + if (!preg_match('/^((?:(?:[1-3]|I{1,3})\s*)?[A-Za-z][A-Za-z .\'-]*?)[\s.]*?(\d{1,3})(?:\s*[:.]\s*(\d{1,3})(?:\s*[-\x{2013}\x{2014}]\s*(\d{1,3}))?)?$/iu', $reference, $m)) return null; $book = $this->normalize_book($m[1]); if (!$book) return null; return [ @@ -507,6 +503,52 @@ if (!class_exists('Bible_Reference_Tooltips')) { return null; } + private function normalize_source_book($book, $source = '') { + $key = strtoupper(preg_replace('/[^A-Z0-9]/i', '', (string)$book)); + if ($source === 'ebible_vpl') { + $map = $this->ebible_vpl_book_map(); + if (isset($map[$key])) return $map[$key]; + return ['abbr' => $key, 'name' => $this->book_name_from_abbr($key)]; + } + $human = $this->normalize_book($book); + if ($human) return $human; + return ['abbr' => $key, 'name' => $this->book_name_from_abbr($key)]; + } + + private function source_book_lookup_candidates($canonical_abbr) { + $canonical_abbr = strtoupper(preg_replace('/[^A-Z0-9]/i', '', (string)$canonical_abbr)); + $candidates = []; + foreach ($this->ebible_vpl_book_map() as $source_abbr => $book) { + if (strtoupper($book['abbr']) === $canonical_abbr) $candidates[] = $source_abbr; + } + return $candidates; + } + + private function ebible_vpl_book_map() { + $source_to_canonical = [ + 'GEN' => 'GEN', 'EXO' => 'EXO', 'LEV' => 'LEV', 'NUM' => 'NUM', 'DEU' => 'DEU', + 'JOS' => 'JOS', 'JDG' => 'JDG', 'RUT' => 'RUT', '1SA' => '1SA', '2SA' => '2SA', + '1KI' => '1KI', '2KI' => '2KI', '1CH' => '1CH', '2CH' => '2CH', 'EZR' => 'EZR', + 'NEH' => 'NEH', 'EST' => 'EST', 'JOB' => 'JOB', 'PSA' => 'PSA', 'PRO' => 'PRO', + 'ECC' => 'ECC', 'SNG' => 'SNG', 'SOL' => 'SNG', 'ISA' => 'ISA', 'JER' => 'JER', 'LAM' => 'LAM', + 'EZE' => 'EZK', 'EZK' => 'EZK', 'DAN' => 'DAN', 'HOS' => 'HOS', 'JOL' => 'JOL', 'JOE' => 'JOL', + 'AMO' => 'AMO', 'OBA' => 'OBA', 'JON' => 'JON', 'MIC' => 'MIC', 'NAM' => 'NAM', 'NAH' => 'NAM', + 'HAB' => 'HAB', 'ZEP' => 'ZEP', 'HAG' => 'HAG', 'ZEC' => 'ZEC', 'MAL' => 'MAL', + 'MAT' => 'MAT', 'MAR' => 'MRK', 'MRK' => 'MRK', 'LUK' => 'LUK', 'JHN' => 'JHN', 'JOH' => 'JHN', + 'ACT' => 'ACT', 'ROM' => 'ROM', '1CO' => '1CO', '2CO' => '2CO', 'GAL' => 'GAL', + 'EPH' => 'EPH', 'PHP' => 'PHP', 'PHI' => 'PHP', 'COL' => 'COL', '1TH' => '1TH', '2TH' => '2TH', + '1TI' => '1TI', '2TI' => '2TI', 'TIT' => 'TIT', 'PHM' => 'PHM', 'HEB' => 'HEB', + 'JAS' => 'JAS', 'JAM' => 'JAS', '1PE' => '1PE', '2PE' => '2PE', '1JO' => '1JN', '1JN' => '1JN', + '2JO' => '2JN', '2JN' => '2JN', '3JO' => '3JN', '3JN' => '3JN', 'JUD' => 'JUD', + 'REV' => 'REV', + ]; + $map = []; + foreach ($source_to_canonical as $source_abbr => $canonical_abbr) { + $map[$source_abbr] = ['abbr' => $canonical_abbr, 'name' => $this->book_name_from_abbr($canonical_abbr)]; + } + return $map; + } + private function book_map() { if ($this->book_map !== null) return $this->book_map; $books = [ @@ -535,7 +577,7 @@ if (!class_exists('Bible_Reference_Tooltips')) { ['ISA','Isaiah',['isaiah','isa']], ['JER','Jeremiah',['jeremiah','jer']], ['LAM','Lamentations',['lamentations','lam']], - ['EZK','Ezekiel',['ezekiel','ezek','ezk']], + ['EZK','Ezekiel',['ezekiel','ezek','ezk','eze']], ['DAN','Daniel',['daniel','dan','dn']], ['HOS','Hosea',['hosea','hos']], ['JOL','Joel',['joel','jol','joe']], @@ -550,7 +592,7 @@ if (!class_exists('Bible_Reference_Tooltips')) { ['ZEC','Zechariah',['zechariah','zech','zec']], ['MAL','Malachi',['malachi','mal']], ['MAT','Matthew',['matthew','matt','mat','mt']], - ['MRK','Mark',['mark','mrk','mk']], + ['MRK','Mark',['mark','mrk','mk','mar']], ['LUK','Luke',['luke','luk','lk']], ['JHN','John',['john','jhn','jn','joh']], ['ACT','Acts',['acts','act']], @@ -571,9 +613,9 @@ if (!class_exists('Bible_Reference_Tooltips')) { ['JAS','James',['james','jas','ja','jam','jms','jm','jac','jacob','epistleofjames','theepistleofjames']], ['1PE','1 Peter',['1peter','1pet','1pe','ipeter']], ['2PE','2 Peter',['2peter','2pet','2pe','iipeter']], - ['1JN','1 John',['1john','1jn','1jhn','ijohn']], - ['2JN','2 John',['2john','2jn','2jhn','iijohn']], - ['3JN','3 John',['3john','3jn','3jhn','iiijohn']], + ['1JN','1 John',['1john','1jn','1jhn','1jo','ijohn','ijn','ijhn','ijo']], + ['2JN','2 John',['2john','2jn','2jhn','2jo','iijohn','iijn','iijhn','iijo']], + ['3JN','3 John',['3john','3jn','3jhn','3jo','iiijohn','iiijn','iiijhn','iiijo']], ['JUD','Jude',['jude','jud']], ['REV','Revelation',['revelation','rev','re']], ]; @@ -649,7 +691,6 @@ if (!class_exists('Bible_Reference_Tooltips')) {
- + + + +
@@ -673,31 +717,10 @@ if (!class_exists('Bible_Reference_Tooltips')) { - -
- - -
-

Logos Links

- - -
- - -

Matching Rules

- - - - + + + @@ -711,7 +734,8 @@ if (!class_exists('Bible_Reference_Tooltips')) {

Open Text Policy

-

This plugin stores only open/public texts in your local WordPress database. WEB, KJV, ASV, and YLT are available from eBible VPL packages. NIV, NASB, and most modern commercial translations should remain external/licensed integrations unless you explicitly secure rights to store and redistribute them.

+

This plugin is designed for Bible texts that are public domain, openly licensed, or otherwise legally permitted for local storage and display. It does not provide import tools for restricted commercial translations unless a future integration explicitly supports proper licensing.

+

The built-in importer currently supports selected open/public-domain eBible VPL packages.

@@ -765,32 +789,17 @@ if (!class_exists('Bible_Reference_Tooltips')) { if (!in_array($translation, array_unique(array_merge($known, $installed)), true)) $translation = 'WEB'; $theme = sanitize_key($_POST['theme'] ?? 'auto'); if (!in_array($theme, ['auto', 'dark', 'light'], true)) $theme = 'auto'; - $link_target = sanitize_key($_POST['link_target'] ?? 'same'); - if (!in_array($link_target, ['same', 'new'], true)) $link_target = 'same'; - $logos_icon = sanitize_key($_POST['logos_icon'] ?? 'dark'); - if (!in_array($logos_icon, ['dark', 'light'], true)) $logos_icon = 'dark'; - $link_versions = array_keys($this->bible_link_versions()); - $bible_version = strtoupper(sanitize_text_field($_POST['bible_version'] ?? $translation)); - if (!in_array($bible_version, $link_versions, true)) $bible_version = $translation; - $logos_translation = strtoupper(sanitize_text_field($_POST['logos_translation'] ?? $translation)); - if (!in_array($logos_translation, $link_versions, true)) $logos_translation = $translation; $settings = [ 'enabled' => !empty($_POST['enabled']) ? '1' : '0', + 'admin_enabled' => !empty($_POST['admin_enabled']) ? '1' : '0', 'translation' => $translation, 'scope' => sanitize_textarea_field($_POST['scope'] ?? self::defaults()['scope']), + 'admin_scope' => sanitize_textarea_field($_POST['admin_scope'] ?? self::defaults()['admin_scope']), 'theme' => $theme, 'underline' => !empty($_POST['underline']) ? '1' : '0', 'show_translation' => !empty($_POST['show_translation']) ? '1' : '0', 'show_tooltips' => !empty($_POST['show_tooltips']) ? '1' : '0', - 'link_references' => !empty($_POST['link_references']) ? '1' : '0', - 'link_target' => $link_target, - 'bible_version' => $bible_version, - 'logos_insert' => !empty($_POST['logos_insert']) ? '1' : '0', - 'logos_existing' => !empty($_POST['logos_existing']) ? '1' : '0', - 'logos_translation' => $logos_translation, - 'logos_icon' => $logos_icon, - 'add_tooltips_to_links' => !empty($_POST['add_tooltips_to_links']) ? '1' : '0', - 'case_sensitive' => !empty($_POST['case_sensitive']) ? '1' : '0', + 'case_insensitive' => !empty($_POST['case_insensitive']) ? '1' : '0', 'tag_chapters' => !empty($_POST['tag_chapters']) ? '1' : '0', 'scan_comments' => !empty($_POST['scan_comments']) ? '1' : '0', 'skip_tags' => $this->normalize_skip_tags($_POST['skip_tags'] ?? []), @@ -889,15 +898,22 @@ if (!class_exists('Bible_Reference_Tooltips')) { $zip = new ZipArchive(); if ($zip->open($tmp) !== true) { @unlink($tmp); + $this->delete_temp_dir($dir); return 'Could not open downloaded ' . $translation . ' archive.'; } $zip->extractTo($dir); $zip->close(); @unlink($tmp); $file = trailingslashit($dir) . $info['file']; - if (!file_exists($file)) return $translation . ' VPL file was not found in archive.'; + if (!file_exists($file)) { + $this->delete_temp_dir($dir); + return $translation . ' VPL file was not found in archive.'; + } $handle = fopen($file, 'r'); - if (!$handle) return 'Could not read ' . $translation . ' VPL file.'; + if (!$handle) { + $this->delete_temp_dir($dir); + return 'Could not read ' . $translation . ' VPL file.'; + } global $wpdb; $wpdb->delete(self::table(), ['translation' => $translation], ['%s']); $batch = []; @@ -913,6 +929,7 @@ if (!class_exists('Bible_Reference_Tooltips')) { } fclose($handle); if ($batch) $count += $this->insert_rows($batch, $info['source'], $info['license']); + $this->delete_temp_dir($dir); if ($make_default) $this->set_active_translation($translation); self::log_event('text_imported', ['translation' => $translation, 'verses' => $count], 'Imported Bible text.'); return $translation . ' import complete: ' . number_format_i18n($count) . ' verses installed.'; @@ -977,7 +994,7 @@ if (!class_exists('Bible_Reference_Tooltips')) { $line = trim((string)$line); if ($line === '') return null; if (!preg_match('/^((?:[1-3]\s*)?[A-Za-z][A-Za-z0-9 .\'-]{1,40}?)\s+(\d{1,3}):(\d{1,3})\s+(.+)$/u', $line, $m)) return null; - $book = $this->normalize_book($m[1]) ?: ['abbr' => strtoupper(preg_replace('/[^A-Z0-9]/i', '', $m[1])), 'name' => $this->book_name_from_abbr($m[1])]; + $book = $this->normalize_source_book($m[1], 'ebible_vpl'); $text = trim(wp_specialchars_decode($m[4], ENT_QUOTES)); if ($text === '') return null; return [ @@ -1102,7 +1119,7 @@ if (!class_exists('Bible_Reference_Tooltips')) { } private function frontend_css() { - return '.brt-ref{cursor:help;text-decoration:underline;text-decoration-style:dotted;text-underline-offset:3px;color:inherit;font-weight:700}.brt-ref:focus{outline:2px solid rgba(47,128,237,.45);outline-offset:2px}.brt-ref.no-underline{text-decoration:none}.brt-logos-link{display:inline-flex;align-items:center;justify-content:center;width:1.15em;height:1.15em;margin-left:.2em;border:1px solid currentColor;border-radius:.25em;font:800 .68em/1 system-ui,-apple-system,Segoe UI,sans-serif;text-decoration:none;vertical-align:.08em;opacity:.78}.brt-logos-link:hover,.brt-logos-link:focus{opacity:1;text-decoration:none}.brt-logos-dark{color:#162033;background:#fff}.brt-logos-light{color:#fff;background:#162033}.brt-pop{position:fixed;z-index:999999;max-width:min(520px,calc(100vw - 28px));padding:16px;border-radius:12px;border:1px solid rgba(148,163,184,.45);background:#fff;color:#0f172a;box-shadow:0 20px 50px rgba(15,23,42,.18);font:14px/1.55 system-ui,-apple-system,Segoe UI,sans-serif;pointer-events:auto;opacity:0;transform:translateY(5px);transition:opacity .22s ease,transform .22s ease}.brt-pop.ready{opacity:1;transform:translateY(0)}.brt-pop.dark{background:#080b10;color:#eef4ff;box-shadow:0 20px 60px rgba(0,0,0,.42)}.brt-pop.light{background:#fff;color:#0f172a;box-shadow:0 20px 50px rgba(15,23,42,.18)}.brt-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0 0 10px}.brt-title{font-weight:800;font-size:15px;color:inherit;white-space:nowrap}.brt-tabs{display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end}.brt-tab{border:1px solid rgba(148,163,184,.45);border-radius:999px;background:rgba(148,163,184,.12);color:inherit;cursor:pointer;font:700 11px/1 system-ui,-apple-system,Segoe UI,sans-serif;padding:5px 8px}.brt-tab.active{background:#2f80ed;border-color:#2f80ed;color:#fff}.brt-pop.dark .brt-tab.active{background:#60a5fa;border-color:#60a5fa;color:#06101f}.brt-verse{margin:0 0 9px}.brt-verse:last-child{margin-bottom:0}.brt-verse-num{font-weight:900;margin-right:5px}.brt-foot{display:block;margin-top:11px;color:#64748b;font-size:12px}.brt-pop.dark .brt-foot{color:#93a4bb}.brt-pop .err{color:#b91c1c}.brt-pop.dark .err{color:#fca5a5}.brt-loading{display:flex;align-items:center;gap:10px;margin:3px 0 1px;color:#64748b}.brt-pop.dark .brt-loading{color:#93a4bb}.brt-spinner{width:14px;height:14px;border:2px solid rgba(148,163,184,.35);border-top-color:#2f80ed;border-radius:999px;animation:brtSpin .8s linear infinite}@keyframes brtSpin{to{transform:rotate(360deg)}}@media(max-width:520px){.brt-head{align-items:flex-start;flex-direction:column}.brt-tabs{justify-content:flex-start}}'; + return '.brt-ref{cursor:help;text-decoration:underline;text-decoration-style:dotted;text-underline-offset:3px;color:inherit;font-weight:700}.brt-ref:focus{outline:2px solid rgba(47,128,237,.45);outline-offset:2px}.brt-ref.no-underline{text-decoration:none}.brt-pop{box-sizing:border-box;position:fixed;z-index:999999;width:max-content;max-width:min(560px,calc(100vw - 28px));min-width:min(320px,calc(100vw - 28px));padding:16px;border-radius:12px;border:1px solid rgba(148,163,184,.45);background:#fff;color:#0f172a;box-shadow:0 20px 50px rgba(15,23,42,.18);font:14px/1.55 system-ui,-apple-system,Segoe UI,sans-serif;pointer-events:auto;opacity:0;transform:translateY(5px);transition:opacity .22s ease,transform .22s ease}.brt-pop *{box-sizing:border-box}.brt-pop.ready{opacity:1;transform:translateY(0)}.brt-pop.dark{background:#080b10;color:#eef4ff;box-shadow:0 20px 60px rgba(0,0,0,.42)}.brt-pop.light{background:#fff;color:#0f172a;box-shadow:0 20px 50px rgba(15,23,42,.18)}.brt-head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;margin:0 0 12px}.brt-title{display:block;min-width:0;flex:1 1 auto;font-weight:800;font-size:15px;line-height:1.3;color:inherit;white-space:normal;overflow-wrap:anywhere}.brt-label{font-weight:700;color:#475569}.brt-pop.dark .brt-label{color:#a8b7cc}.brt-tabs{display:flex;flex:0 0 auto;max-width:210px;gap:6px;flex-wrap:wrap;justify-content:flex-end}.brt-tab{border:1px solid rgba(148,163,184,.45);border-radius:999px;background:rgba(148,163,184,.12);color:inherit;cursor:pointer;font:700 11px/1 system-ui,-apple-system,Segoe UI,sans-serif;padding:5px 8px}.brt-tab.active{background:#2f80ed;border-color:#2f80ed;color:#fff}.brt-pop.dark .brt-tab.active{background:#60a5fa;border-color:#60a5fa;color:#06101f}.brt-body{clear:both;display:block;min-height:1.55em}.brt-verse{display:block;margin:0 0 9px;color:inherit}.brt-verse:last-child{margin-bottom:0}.brt-verse-num{font-weight:900;margin-right:5px}.brt-foot{display:block;margin-top:11px;color:#64748b;font-size:12px}.brt-pop.dark .brt-foot{color:#93a4bb}.brt-pop .err{color:#b91c1c}.brt-pop.dark .err{color:#fca5a5}.brt-loading{display:flex;align-items:center;gap:10px;margin:3px 0 1px;color:#64748b}.brt-pop.dark .brt-loading{color:#93a4bb}.brt-spinner{width:14px;height:14px;border:2px solid rgba(148,163,184,.35);border-top-color:#2f80ed;border-radius:999px;animation:brtSpin .8s linear infinite}@keyframes brtSpin{to{transform:rotate(360deg)}}@media(max-width:520px){.brt-head{flex-direction:column}.brt-tabs{justify-content:flex-start;max-width:none}.brt-pop{min-width:min(280px,calc(100vw - 28px))}}'; } private function frontend_js() { @@ -1114,18 +1131,18 @@ if (!class_exists('Bible_Reference_Tooltips')) { 'Joshua','Josh','Jos','Judges','Judg','Jdg','Jg','Ruth','Rut','Ru','1\\s*Samuel','1\\s*Sam','1\\s*Sa','2\\s*Samuel','2\\s*Sam','2\\s*Sa', '1\\s*Kings','1\\s*Kgs','1\\s*Ki','2\\s*Kings','2\\s*Kgs','2\\s*Ki','1\\s*Chronicles','1\\s*Chron','1\\s*Chr','2\\s*Chronicles','2\\s*Chron','2\\s*Chr', 'Ezra','Ezr','Nehemiah','Neh','Ne','Esther','Est','Job','Psalms','Psalm','Psa','Ps','Proverbs','Prov','Pro','Pr','Ecclesiastes','Eccl','Ecc', - 'Song\\s*of\\s*Solomon','Song\\s*of\\s*Songs','Song','Sos','Sng','Isaiah','Isa','Jeremiah','Jer','Lamentations','Lam','Ezekiel','Ezek','Ezk', + 'Song\\s*of\\s*Solomon','Song\\s*of\\s*Songs','Song','Sos','Sng','Isaiah','Isa','Jeremiah','Jer','Lamentations','Lam','Ezekiel','Ezek','Ezk','Eze', 'Daniel','Dan','Dn','Hosea','Hos','Joel','Jol','Joe','Jl','Amos','Amo','Obadiah','Obad','Oba','Jonah','Jon','Micah','Mic','Nahum','Nah','Nam','Habakkuk','Hab','Zephaniah','Zeph','Zep', - 'Haggai','Hag','Zechariah','Zech','Zec','Malachi','Mal','Matthew','Matt','Mat','Mt','Mark','Mrk','Mk','Luke','Luk','Lk','John','Jhn','Jn', + 'Haggai','Hag','Zechariah','Zech','Zec','Malachi','Mal','Matthew','Matt','Mat','Mt','Mark','Mrk','Mk','Mar','Luke','Luk','Lk','John','Jhn','Jn', 'Acts','Act','Romans','Rom','Ro','1\\s*Corinthians','1\\s*Cor','1\\s*Co','2\\s*Corinthians','2\\s*Cor','2\\s*Co','Galatians','Gal','Ephesians','Eph', 'Philippians','Phil','Php','Colossians','Col','1\\s*Thessalonians','1\\s*Thess','1\\s*Th','2\\s*Thessalonians','2\\s*Thess','2\\s*Th', '1\\s*Timothy','1\\s*Tim','1\\s*Ti','2\\s*Timothy','2\\s*Tim','2\\s*Ti','Titus','Tit','Philemon','Phlm','Phm','Hebrews','Heb', - 'James\\.?','Jas\\.?','Ja\\.?','Jam\\.?','Jms\\.?','Jm\\.?','Jac\\.?','Jacob','Epistle\\s*of\\s*James','1\\s*Peter','1\\s*Pet','1\\s*Pe','2\\s*Peter','2\\s*Pet','2\\s*Pe','1\\s*John','1\\s*Jn','1\\s*Jhn', - '2\\s*John','2\\s*Jn','2\\s*Jhn','3\\s*John','3\\s*Jn','3\\s*Jhn','Jude','Jud','Revelation','Rev','Re' + 'James\\.?','Jas\\.?','Ja\\.?','Jam\\.?','Jms\\.?','Jm\\.?','Jac\\.?','Jacob','Epistle\\s*of\\s*James','1\\s*Peter','1\\s*Pet','1\\s*Pe','2\\s*Peter','2\\s*Pet','2\\s*Pe','1\\s*John','1\\s*Jn','1\\s*Jhn','1\\s*Jo', + '2\\s*John','2\\s*Jn','2\\s*Jhn','2\\s*Jo','3\\s*John','3\\s*Jn','3\\s*Jhn','3\\s*Jo','Jude','Jud','Revelation','Rev','Re' ]; const book = '(?:' + bookAliases.sort((a,b) => b.length - a.length).join('|') + ')'; - const chapterPattern = BibleReferenceTooltips.tagChapters ? '(?:\\s*:\\s*\\d{1,3}(?:\\s*[-\\u2013\\u2014]\\s*\\d{1,3})?)?' : '\\s*:\\s*\\d{1,3}(?:\\s*[-\\u2013\\u2014]\\s*\\d{1,3})?'; - const refPattern = '\\b(' + book + ')\\s+\\d{1,3}' + chapterPattern + '\\b'; + const chapterPattern = BibleReferenceTooltips.tagChapters ? '(?:\\s*[:.]\\s*\\d{1,3}(?:\\s*[-\\u2013\\u2014]\\s*\\d{1,3})?)?' : '\\s*[:.]\\s*\\d{1,3}(?:\\s*[-\\u2013\\u2014]\\s*\\d{1,3})?'; + const refPattern = '\\b(' + book + ')[\\s.]*\\d{1,3}' + chapterPattern + '\\b'; const refFlags = BibleReferenceTooltips.caseSensitive ? 'g' : 'gi'; const refRe = new RegExp(refPattern, refFlags); const refTestRe = new RegExp(refPattern, BibleReferenceTooltips.caseSensitive ? '' : 'i'); @@ -1165,7 +1182,7 @@ if (!class_exists('Bible_Reference_Tooltips')) { refRe.lastIndex = 0; let el = node.parentElement; while (el) { - if (skip.has(el.tagName) || el.classList.contains('brt-ref') || el.classList.contains('brt-pop') || el.classList.contains('brt-logos-link')) return NodeFilter.FILTER_REJECT; + if (skip.has(el.tagName) || el.classList.contains('brt-ref') || el.classList.contains('brt-pop')) return NodeFilter.FILTER_REJECT; el = el.parentElement; } return NodeFilter.FILTER_ACCEPT; @@ -1185,77 +1202,20 @@ if (!class_exists('Bible_Reference_Tooltips')) { if (offset > last) frag.appendChild(document.createTextNode(text.slice(last, offset))); const ref = makeReferenceNode(match); frag.appendChild(ref); - if (BibleReferenceTooltips.logosInsert) frag.appendChild(makeLogosLink(match)); last = offset + match.length; }); if (last < text.length) frag.appendChild(document.createTextNode(text.slice(last))); node.parentNode.replaceChild(frag, node); } function makeReferenceNode(ref){ - const el = BibleReferenceTooltips.linkReferences ? document.createElement('a') : document.createElement('span'); + const el = document.createElement('span'); el.className = 'brt-ref'; if (!BibleReferenceTooltips.underline) el.className += ' no-underline'; el.dataset.ref = ref; el.textContent = ref; - if (BibleReferenceTooltips.linkReferences) { - el.href = bibleUrl(ref, BibleReferenceTooltips.bibleVersion || BibleReferenceTooltips.translation || 'WEB'); - if (BibleReferenceTooltips.linkTarget === 'new') { - el.target = '_blank'; - el.rel = 'noopener noreferrer'; - } - } else { - el.tabIndex = 0; - } + el.tabIndex = 0; return el; } - function enhanceExistingLinks(root){ - if (!BibleReferenceTooltips.addTooltipsToLinks && !BibleReferenceTooltips.logosExisting) return; - const links = root.querySelectorAll ? root.querySelectorAll('a[href]') : []; - links.forEach(link => { - if (link.classList.contains('brt-logos-link')) return; - const href = String(link.getAttribute('href') || '').toLowerCase(); - const isBibleLink = href.includes('biblia.com') || href.includes('ref.ly') || href.includes('logos.com') || href.includes('logosres:'); - if (!isBibleLink) return; - const ref = extractRef(link.textContent || link.getAttribute('title') || link.getAttribute('aria-label') || ''); - if (ref && BibleReferenceTooltips.addTooltipsToLinks && !link.classList.contains('brt-ref')) { - link.classList.add('brt-ref'); - if (!BibleReferenceTooltips.underline) link.classList.add('no-underline'); - link.dataset.ref = ref; - } - if (BibleReferenceTooltips.logosExisting && href.includes('logos') && !link.nextElementSibling?.classList.contains('brt-logos-link')) { - link.insertAdjacentElement('afterend', makeLogosLink(ref || link.textContent || '')); - } - }); - } - function extractRef(text){ - refRe.lastIndex = 0; - const match = refRe.exec(String(text || '')); - refRe.lastIndex = 0; - return match ? match[0] : ''; - } - function bibleUrl(ref, version){ - const code = String(version || 'WEB').trim() || 'WEB'; - return 'https://biblia.com/bible/' + encodeURIComponent(code) + '/' + encodeURIComponent(String(ref || '').replace(/\s+/g, ' ').trim()); - } - function logosUrl(ref, version){ - const compact = String(ref || '').replace(/\s+/g, ''); - const code = String(version || 'WEB').trim() || 'WEB'; - return 'https://ref.ly/logosref/Bible.' + encodeURIComponent(compact) + '?ver=' + encodeURIComponent(code); - } - function makeLogosLink(ref){ - const link = document.createElement('a'); - const icon = BibleReferenceTooltips.logosIcon === 'light' ? 'light' : 'dark'; - link.className = 'brt-logos-link brt-logos-' + icon; - link.href = logosUrl(ref, BibleReferenceTooltips.logosTranslation || BibleReferenceTooltips.translation || 'WEB'); - link.textContent = 'L'; - link.setAttribute('aria-label', 'Open ' + String(ref || 'Bible reference') + ' in Logos Bible Software'); - link.title = 'Open in Logos Bible Software'; - if (BibleReferenceTooltips.linkTarget === 'new') { - link.target = '_blank'; - link.rel = 'noopener noreferrer'; - } - return link; - } async function show(e){ if (!BibleReferenceTooltips.showTooltips) return; const target = e.target.closest('.brt-ref'); @@ -1296,7 +1256,8 @@ if (!class_exists('Bible_Reference_Tooltips')) { const code = translation || 'WEB'; const key = code + ':' + ref.toLowerCase(); if (cache.has(key)) return cache.get(key); - const url = BibleReferenceTooltips.rest + '?ref=' + encodeURIComponent(ref) + '&translation=' + encodeURIComponent(code); + const separator = String(BibleReferenceTooltips.rest).includes('?') ? '&' : '?'; + const url = BibleReferenceTooltips.rest + separator + 'ref=' + encodeURIComponent(ref) + '&translation=' + encodeURIComponent(code); const data = await fetch(url, {headers:{'X-WP-Nonce': BibleReferenceTooltips.nonce || ''}}).then(r => r.json()); cache.set(key, data); return data; @@ -1383,7 +1344,6 @@ if (!class_exists('Bible_Reference_Tooltips')) { if (BibleReferenceTooltips.scanComments) selectors.push('.brt-comment-scope'); const roots = selectors.flatMap(s => Array.from(document.querySelectorAll(s))); Array.from(new Set(roots.length ? roots : [document.body])).forEach(root => { - enhanceExistingLinks(root); scan(root); }); } diff --git a/plugins/bible-reference-tooltips/readme.txt b/plugins/bible-reference-tooltips/readme.txt index 9763e6b..069f4c5 100644 --- a/plugins/bible-reference-tooltips/readme.txt +++ b/plugins/bible-reference-tooltips/readme.txt @@ -4,7 +4,7 @@ Tags: bible, scripture, references, tooltips, verses Requires at least: 6.0 Tested up to: 7.0.1 Requires PHP: 7.4 -Stable tag: 1.1.0 +Stable tag: 1.1.5 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html @@ -12,17 +12,17 @@ Detect Bible references in WordPress content and show local hover cards from ope == Description == -Bible Reference Tooltips scans public WordPress content for Bible references such as John 3:16, Romans 8:28, or James 1:5. Detected references can be underlined, linked, and shown with hover or keyboard-focus tooltip cards. +Bible Reference Tooltips scans WordPress content for Bible references such as John 3:16, Romans 8:28, or James 1:5. Detected references can be underlined and shown with hover or keyboard-focus tooltip cards. Verse text is looked up from local open/public-domain Bible texts installed into your WordPress database. The plugin currently supports World English Bible, King James Version, American Standard Version, and Young's Literal Translation imports from eBible VPL packages. Features include: -* Frontend Bible reference detection. +* Public-site Bible reference detection. +* Optional WordPress admin Bible reference detection. * Hover and focus tooltip cards. * Local verse storage with no remote lookup required during page views. * Multiple installed translations with quick-switch tabs in the card. -* Biblia.com links and optional Logos Bible Software links. * Configurable scan selectors, skipped tags, comment scanning, tooltip theme, and verse range limit. == Installation == @@ -49,6 +49,35 @@ Yes. The plugin is standalone and appears under Settings in the WordPress admin. == Changelog == += 1.1.5 = + +* Added the public workspace conventions and tracked Docker test lab as the bounded release workflow. +* Added a settings migration that removes retired external-link options while preserving current tooltip settings. +* Updated the standalone changelog to match the public 1.1.x transition history. + += 1.1.4 = + +* Removed external Biblia, ref.ly, and Logos link generation from the public plugin. +* Added optional WordPress admin scanning with separate admin scan selectors. +* Improved matching-rule defaults for capitalization, chapter-only references, and public comments. +* Generalized the open text policy language. + += 1.1.3 = + +* Fixed browser REST lookups when WordPress provides a query-string REST route. +* Improved local test-page layout and removed native browser title tooltips from test references. + += 1.1.2 = + +* Split eBible VPL source-code resolution from human Bible reference parsing. +* Added exact source-code handling for compact eBible book identifiers such as SOL, EZE, MAR, JOH, JAM, 1JO, 2JO, and 3JO. +* Improved compact reference detection for forms such as Mark10:45 and 2Jn1:6. + += 1.1.1 = + +* Added exact eBible VPL aliases for source abbreviations that were not standard display abbreviations. +* Fixed imports for books such as Mark and Ezekiel when VPL source files use compact book codes. + = 1.1.0 = * Forked the internal package into a standalone public plugin. diff --git a/tools/testlab/README.md b/tools/testlab/README.md new file mode 100644 index 0000000..524a7d4 --- /dev/null +++ b/tools/testlab/README.md @@ -0,0 +1,61 @@ +# WordPress Plugin Test Lab + +Local Docker test lab for public WordPress plugins. + +The lab mounts plugin source from this workspace into disposable WordPress containers, installs WordPress with WP-CLI, activates the selected plugin, and runs smoke tests. + +## Environments + +Initial matrix: + +| Environment | WordPress Image | PHP | Database | +| --- | --- | --- | --- | +| `wp7-php84` | `wordpress:php8.4-apache` | 8.4 | MariaDB 11 | +| `wp6-php74` | `wordpress:php7.4-apache` with WP `6.8.2` download | 7.4 | MariaDB 10.6 | + +The `wp7-php84` image installs the latest WordPress available in the image line. The `wp6-php74` environment uses the available PHP 7.4 WordPress image, then forces a WordPress 6.8.2 core download before install. The test script records the actual WordPress version after install. + +## Commands + +Run all configured environments: + +```powershell +cd tools\testlab +.\scripts\run-all.ps1 +``` + +Run one environment: + +```powershell +cd tools\testlab +.\scripts\run-env.ps1 -Env wp7-php84 +``` + +Stop and remove one environment: + +```powershell +cd tools\testlab +.\scripts\down-env.ps1 -Env wp7-php84 +``` + +Stop and remove all environments: + +```powershell +cd tools\testlab +.\scripts\down-all.ps1 +``` + +## What The Smoke Test Does + +- Starts WordPress and MariaDB. +- Waits for the database and WordPress container. +- Installs WordPress. +- Activates `bible-reference-tooltips`. +- Confirms PHP, WordPress, and plugin versions. +- Confirms the plugin-created table exists. +- Imports WEB, KJV, ASV, and YLT through the plugin's internal importer. +- Confirms imported verse rows and canonical book coverage exist. +- Confirms representative REST lookups, including compact references such as `Mark10:45`, `Eze 36:26`, and `2Jn1:6`. +- Creates a post containing those references. + +This stops before WordPress.org SVN or plugin submission work. diff --git a/tools/testlab/config/environments.json b/tools/testlab/config/environments.json new file mode 100644 index 0000000..4822279 --- /dev/null +++ b/tools/testlab/config/environments.json @@ -0,0 +1,15 @@ +[ + { + "name": "wp7-php84", + "wordpressImage": "wordpress:php8.4-apache", + "dbImage": "mariadb:11", + "hostPort": 8070 + }, + { + "name": "wp6-php74", + "wordpressImage": "wordpress:php7.4-apache", + "dbImage": "mariadb:10.6", + "hostPort": 8068, + "wordpressVersion": "6.8.2" + } +] diff --git a/tools/testlab/results/wp6-php74.json b/tools/testlab/results/wp6-php74.json new file mode 100644 index 0000000..e88d697 --- /dev/null +++ b/tools/testlab/results/wp6-php74.json @@ -0,0 +1,149 @@ +{ + "environment": "wp6-php74", + "wordpressImage": "wordpress:php7.4-apache", + "dbImage": "mariadb:10.6", + "hostPort": 8068, + "startedAt": "2026-07-11T19:49:33.8231872-05:00", + "checks": [ + { + "name": "wp-cli available", + "passed": true, + "detail": "" + }, + { + "name": "wordpress core version prepared", + "passed": true, + "detail": "target=6.8.2" + }, + { + "name": "wordpress installed", + "passed": true, + "detail": "installed" + }, + { + "name": "runtime versions", + "passed": true, + "detail": "WP=6.8.2 PHP=7.4.33" + }, + { + "name": "plugin activated", + "passed": true, + "detail": "" + }, + { + "name": "plugin version detected", + "passed": true, + "detail": "version=1.1.5" + }, + { + "name": "verse table created", + "passed": true, + "detail": "wp_brt_verses" + }, + { + "name": "WEB import completed", + "passed": true, + "detail": "WEB import complete: 36,601 verses installed." + }, + { + "name": "KJV import completed", + "passed": true, + "detail": "KJV import complete: 35,948 verses installed." + }, + { + "name": "ASV import completed", + "passed": true, + "detail": "ASV import complete: 31,086 verses installed." + }, + { + "name": "YLT import completed", + "passed": true, + "detail": "YLT import complete: 31,102 verses installed." + }, + { + "name": "translation rows stored", + "passed": true, + "detail": "[{\"translation\":\"ASV\",\"row_count\":\"31086\"},{\"translation\":\"KJV\",\"row_count\":\"35948\"},{\"translation\":\"WEB\",\"row_count\":\"36601\"},{\"translation\":\"YLT\",\"row_count\":\"31102\"}]" + }, + { + "name": "canonical book coverage", + "passed": true, + "detail": "ok" + }, + { + "name": "REST lookup John 3:16", + "passed": true, + "detail": "ok" + }, + { + "name": "REST lookup John 3.16", + "passed": true, + "detail": "ok" + }, + { + "name": "REST lookup Jn3:16", + "passed": true, + "detail": "ok" + }, + { + "name": "REST lookup Romans 8:28", + "passed": true, + "detail": "ok" + }, + { + "name": "REST lookup James 1:5", + "passed": true, + "detail": "ok" + }, + { + "name": "REST lookup Mark 10:45", + "passed": true, + "detail": "ok" + }, + { + "name": "REST lookup Mark10:45", + "passed": true, + "detail": "ok" + }, + { + "name": "REST lookup Mk 10:45", + "passed": true, + "detail": "ok" + }, + { + "name": "REST lookup Ezekiel 36:26", + "passed": true, + "detail": "ok" + }, + { + "name": "REST lookup Eze 36:26", + "passed": true, + "detail": "ok" + }, + { + "name": "REST lookup 2 John 1:6", + "passed": true, + "detail": "ok" + }, + { + "name": "REST lookup 2Jn1:6", + "passed": true, + "detail": "ok" + }, + { + "name": "REST lookup 2 Jo 1:6", + "passed": true, + "detail": "ok" + }, + { + "name": "test post created", + "passed": true, + "detail": "" + } + ], + "phpVersion": "7.4.33", + "wordpressVersion": "6.8.2", + "pluginVersion": "1.1.5", + "completedAt": "2026-07-11T19:52:42.5557675-05:00", + "passed": true +} diff --git a/tools/testlab/results/wp7-php84.json b/tools/testlab/results/wp7-php84.json new file mode 100644 index 0000000..703a476 --- /dev/null +++ b/tools/testlab/results/wp7-php84.json @@ -0,0 +1,144 @@ +{ + "environment": "wp7-php84", + "wordpressImage": "wordpress:php8.4-apache", + "dbImage": "mariadb:11", + "hostPort": 8070, + "startedAt": "2026-07-11T19:46:20.2262126-05:00", + "checks": [ + { + "name": "wp-cli available", + "passed": true, + "detail": "" + }, + { + "name": "wordpress installed", + "passed": true, + "detail": "installed" + }, + { + "name": "runtime versions", + "passed": true, + "detail": "WP=7.0.1 PHP=8.4.23" + }, + { + "name": "plugin activated", + "passed": true, + "detail": "" + }, + { + "name": "plugin version detected", + "passed": true, + "detail": "version=1.1.5" + }, + { + "name": "verse table created", + "passed": true, + "detail": "wp_brt_verses" + }, + { + "name": "WEB import completed", + "passed": true, + "detail": "WEB import complete: 36,601 verses installed." + }, + { + "name": "KJV import completed", + "passed": true, + "detail": "KJV import complete: 35,948 verses installed." + }, + { + "name": "ASV import completed", + "passed": true, + "detail": "ASV import complete: 31,086 verses installed." + }, + { + "name": "YLT import completed", + "passed": true, + "detail": "YLT import complete: 31,102 verses installed." + }, + { + "name": "translation rows stored", + "passed": true, + "detail": "[{\"translation\":\"ASV\",\"row_count\":\"31086\"},{\"translation\":\"KJV\",\"row_count\":\"35948\"},{\"translation\":\"WEB\",\"row_count\":\"36601\"},{\"translation\":\"YLT\",\"row_count\":\"31102\"}]" + }, + { + "name": "canonical book coverage", + "passed": true, + "detail": "ok" + }, + { + "name": "REST lookup John 3:16", + "passed": true, + "detail": "ok" + }, + { + "name": "REST lookup John 3.16", + "passed": true, + "detail": "ok" + }, + { + "name": "REST lookup Jn3:16", + "passed": true, + "detail": "ok" + }, + { + "name": "REST lookup Romans 8:28", + "passed": true, + "detail": "ok" + }, + { + "name": "REST lookup James 1:5", + "passed": true, + "detail": "ok" + }, + { + "name": "REST lookup Mark 10:45", + "passed": true, + "detail": "ok" + }, + { + "name": "REST lookup Mark10:45", + "passed": true, + "detail": "ok" + }, + { + "name": "REST lookup Mk 10:45", + "passed": true, + "detail": "ok" + }, + { + "name": "REST lookup Ezekiel 36:26", + "passed": true, + "detail": "ok" + }, + { + "name": "REST lookup Eze 36:26", + "passed": true, + "detail": "ok" + }, + { + "name": "REST lookup 2 John 1:6", + "passed": true, + "detail": "ok" + }, + { + "name": "REST lookup 2Jn1:6", + "passed": true, + "detail": "ok" + }, + { + "name": "REST lookup 2 Jo 1:6", + "passed": true, + "detail": "ok" + }, + { + "name": "test post created", + "passed": true, + "detail": "" + } + ], + "phpVersion": "8.4.23", + "wordpressVersion": "7.0.1", + "pluginVersion": "1.1.5", + "completedAt": "2026-07-11T19:49:33.7883010-05:00", + "passed": true +} diff --git a/tools/testlab/scripts/common.ps1 b/tools/testlab/scripts/common.ps1 new file mode 100644 index 0000000..53170eb --- /dev/null +++ b/tools/testlab/scripts/common.ps1 @@ -0,0 +1,141 @@ +$ErrorActionPreference = 'Stop' + +$LabRoot = Split-Path -Parent (Split-Path -Parent $PSCommandPath) +$WorkspaceRoot = Split-Path -Parent (Split-Path -Parent $LabRoot) +$ConfigPath = Join-Path $LabRoot 'config\environments.json' +$GeneratedRoot = Join-Path $LabRoot 'generated' +$ResultsRoot = Join-Path $LabRoot 'results' +$PluginSource = Join-Path $WorkspaceRoot 'plugins\bible-reference-tooltips' +$PluginSlug = 'bible-reference-tooltips' +$ExpectedPluginVersion = '1.1.5' + +function Get-TestLabEnvironments { + Get-Content -LiteralPath $ConfigPath -Raw | ConvertFrom-Json +} + +function Get-TestLabEnvironment { + param([Parameter(Mandatory)][string]$Name) + $envs = Get-TestLabEnvironments + $selected = $envs | Where-Object { $_.name -eq $Name } | Select-Object -First 1 + if (-not $selected) { + throw "Unknown environment '$Name'. Available: $($envs.name -join ', ')" + } + return $selected +} + +function Convert-ToComposePath { + param([Parameter(Mandatory)][string]$Path) + return ($Path -replace '\\', '/') +} + +function Ensure-TestLabDirectories { + New-Item -ItemType Directory -Force -Path $GeneratedRoot, $ResultsRoot | Out-Null +} + +function Get-EnvDir { + param([Parameter(Mandatory)][string]$Name) + return Join-Path $GeneratedRoot $Name +} + +function Get-ResultFile { + param([Parameter(Mandatory)][string]$Name) + return Join-Path $ResultsRoot "$Name.json" +} + +function Write-ComposeFile { + param([Parameter(Mandatory)]$Environment) + + Ensure-TestLabDirectories + $envDir = Get-EnvDir $Environment.name + New-Item -ItemType Directory -Force -Path $envDir | Out-Null + + $pluginPath = Convert-ToComposePath $PluginSource + $compose = @" +services: + db: + image: $($Environment.dbImage) + environment: + MYSQL_DATABASE: wordpress + MYSQL_USER: wordpress + MYSQL_PASSWORD: wordpress + MYSQL_ROOT_PASSWORD: rootpass + healthcheck: + test: ["CMD", "mariadb-admin", "ping", "-h", "localhost", "-uroot", "-prootpass"] + interval: 5s + timeout: 5s + retries: 30 + volumes: + - db_data:/var/lib/mysql + + wordpress: + image: $($Environment.wordpressImage) + depends_on: + db: + condition: service_healthy + ports: + - "$($Environment.hostPort):80" + environment: + WORDPRESS_DB_HOST: db:3306 + WORDPRESS_DB_USER: wordpress + WORDPRESS_DB_PASSWORD: wordpress + WORDPRESS_DB_NAME: wordpress + WORDPRESS_DEBUG: "1" + volumes: + - wp_data:/var/www/html + - ${pluginPath}:/var/www/html/wp-content/plugins/$PluginSlug + +volumes: + db_data: + wp_data: +"@ + Set-Content -LiteralPath (Join-Path $envDir 'compose.yml') -Value $compose -Encoding UTF8 +} + +function Invoke-Compose { + param( + [Parameter(Mandatory)]$Environment, + [Parameter(Mandatory)][string[]]$Arguments + ) + $envDir = Get-EnvDir $Environment.name + docker compose -p "wptest-$($Environment.name)" -f (Join-Path $envDir 'compose.yml') @Arguments + if ($LASTEXITCODE -ne 0) { + throw "docker compose failed with exit code ${LASTEXITCODE}: $($Arguments -join ' ')" + } +} + +function Invoke-WpCli { + param( + [Parameter(Mandatory)]$Environment, + [Parameter(Mandatory)][string]$Command + ) + Invoke-Compose $Environment @('exec', '-T', '-u', '33', 'wordpress', 'bash', '-lc', $Command) +} + +function Invoke-WpCliRoot { + param( + [Parameter(Mandatory)]$Environment, + [Parameter(Mandatory)][string]$Command + ) + Invoke-Compose $Environment @('exec', '-T', 'wordpress', 'bash', '-lc', $Command) +} + +function Write-ContainerPhpFile { + param( + [Parameter(Mandatory)]$Environment, + [Parameter(Mandatory)][string]$ContainerPath, + [Parameter(Mandatory)][string]$Content + ) + $encoded = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($Content)) + Invoke-WpCliRoot $Environment "rm -f '$ContainerPath' && printf '%s' '$encoded' | base64 -d > '$ContainerPath' && chown www-data:www-data '$ContainerPath'" | Out-Null +} + +function Write-ContainerTextFile { + param( + [Parameter(Mandatory)]$Environment, + [Parameter(Mandatory)][string]$ContainerPath, + [Parameter(Mandatory)][string]$Content, + [string]$Mode = '0644' + ) + $encoded = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($Content)) + Invoke-WpCliRoot $Environment "rm -f '$ContainerPath' && printf '%s' '$encoded' | base64 -d > '$ContainerPath' && chmod '$Mode' '$ContainerPath' && chown www-data:www-data '$ContainerPath'" | Out-Null +} diff --git a/tools/testlab/scripts/down-all.ps1 b/tools/testlab/scripts/down-all.ps1 new file mode 100644 index 0000000..dfca264 --- /dev/null +++ b/tools/testlab/scripts/down-all.ps1 @@ -0,0 +1,6 @@ +. "$PSScriptRoot\common.ps1" + +foreach ($environment in Get-TestLabEnvironments) { + Write-Host "=== Removing $($environment.name) ===" + & "$PSScriptRoot\down-env.ps1" -Env $environment.name +} diff --git a/tools/testlab/scripts/down-env.ps1 b/tools/testlab/scripts/down-env.ps1 new file mode 100644 index 0000000..23e2d0a --- /dev/null +++ b/tools/testlab/scripts/down-env.ps1 @@ -0,0 +1,9 @@ +param( + [Parameter(Mandatory)][string]$Env +) + +. "$PSScriptRoot\common.ps1" + +$environment = Get-TestLabEnvironment -Name $Env +Write-ComposeFile -Environment $environment +Invoke-Compose $environment @('down', '-v', '--remove-orphans') | Out-Host diff --git a/tools/testlab/scripts/run-all.ps1 b/tools/testlab/scripts/run-all.ps1 new file mode 100644 index 0000000..966f316 --- /dev/null +++ b/tools/testlab/scripts/run-all.ps1 @@ -0,0 +1,9 @@ +. "$PSScriptRoot\common.ps1" + +$environments = Get-TestLabEnvironments +foreach ($environment in $environments) { + $name = [string]$environment.name + Write-Host "" + Write-Host "=== Running $name ===" + & "$PSScriptRoot\run-env.ps1" -Env $name +} diff --git a/tools/testlab/scripts/run-env.ps1 b/tools/testlab/scripts/run-env.ps1 new file mode 100644 index 0000000..7121464 --- /dev/null +++ b/tools/testlab/scripts/run-env.ps1 @@ -0,0 +1,158 @@ +param( + [Parameter(Mandatory)][string]$Env +) + +. "$PSScriptRoot\common.ps1" + +$environment = Get-TestLabEnvironment -Name $Env +Write-ComposeFile -Environment $environment + +$result = [ordered]@{ + environment = $environment.name + wordpressImage = $environment.wordpressImage + dbImage = $environment.dbImage + hostPort = $environment.hostPort + startedAt = (Get-Date).ToString('o') + checks = @() +} + +function Add-Check { + param([string]$Name, [bool]$Passed, [string]$Detail = '') + $script:result.checks += [ordered]@{ name = $Name; passed = $Passed; detail = $Detail } + if ($Passed) { + Write-Host "[PASS] $Name $Detail" + } else { + Write-Host "[FAIL] $Name $Detail" + throw "Check failed: $Name $Detail" + } +} + +try { + Invoke-Compose $environment @('up', '-d') | Out-Host + + $wpCli = 'if ! command -v wp >/dev/null 2>&1; then curl -fsSL https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar -o /usr/local/bin/wp && chmod +x /usr/local/bin/wp; fi' + Invoke-WpCliRoot $environment $wpCli | Out-Host + Add-Check 'wp-cli available' $true + + if ($environment.PSObject.Properties.Name -contains 'wordpressVersion' -and $environment.wordpressVersion) { + Invoke-WpCli $environment "wp core download --version=$($environment.wordpressVersion) --force --path=/var/www/html" | Out-Host + Add-Check 'wordpress core version prepared' $true "target=$($environment.wordpressVersion)" + } + + $installScript = @' +#!/usr/bin/env bash +set -e +for i in $(seq 1 40); do + if wp core is-installed --path=/var/www/html >/dev/null 2>&1; then + exit 0 + fi + if wp core install --path=/var/www/html --url=http://localhost:__HOST_PORT__ --title=PluginTest --admin_user=admin --admin_password=adminpass --admin_email=admin@example.test --skip-email; then + exit 0 + fi + sleep 3 +done +exit 1 +'@ -replace '__HOST_PORT__', [string]$environment.hostPort + Write-ContainerTextFile $environment '/tmp/brt-install.sh' $installScript '0755' + Invoke-WpCli $environment 'bash /tmp/brt-install.sh' | Out-Host + $installed = (Invoke-WpCli $environment 'wp core is-installed --path=/var/www/html && echo installed' | Out-String).Trim() + Add-Check 'wordpress installed' ($installed -eq 'installed') $installed + + $phpVersion = (Invoke-WpCliRoot $environment "php -r 'echo PHP_VERSION;'" | Out-String).Trim() + $wpVersion = (Invoke-WpCli $environment 'wp core version --path=/var/www/html' | Out-String).Trim() + Add-Check 'runtime versions' $true "WP=$wpVersion PHP=$phpVersion" + $result.phpVersion = $phpVersion + $result.wordpressVersion = $wpVersion + + Invoke-WpCli $environment "wp plugin activate $PluginSlug --path=/var/www/html" | Out-Host + Add-Check 'plugin activated' $true + + $pluginVersion = (Invoke-WpCli $environment "wp plugin get $PluginSlug --field=version --path=/var/www/html" | Out-String).Trim() + Add-Check 'plugin version detected' ($pluginVersion -eq $ExpectedPluginVersion) "version=$pluginVersion" + $result.pluginVersion = $pluginVersion + + $tablePhp = @' +get_var($wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->prefix . 'brt_verses')); +'@ + Write-ContainerPhpFile $environment '/tmp/brt-table.php' $tablePhp + $tableExists = (Invoke-WpCli $environment 'wp eval-file /tmp/brt-table.php --path=/var/www/html' | Out-String).Trim() + Add-Check 'verse table created' ($tableExists -eq 'wp_brt_verses') $tableExists + + $translationsToImport = @('WEB', 'KJV', 'ASV', 'YLT') + foreach ($translationCode in $translationsToImport) { + $importPhp = @" +getMethod('import_vpl_translation'); +`$m->setAccessible(true); +`$count = `$m->invoke(`$plugin, '$translationCode', false); +echo `$count; +"@ + Write-ContainerPhpFile $environment '/tmp/brt-import.php' $importPhp + $imported = (Invoke-WpCli $environment 'wp eval-file /tmp/brt-import.php --path=/var/www/html' | Out-String).Trim() + $importNumberText = [regex]::Match($imported, '[\d,]+').Value -replace ',', '' + $importNumber = if ($importNumberText) { [int]$importNumberText } else { 0 } + Add-Check "$translationCode import completed" ($importNumber -gt 30000) $imported + } + Invoke-WpCli $environment "wp option patch update brt_settings translation WEB --path=/var/www/html" | Out-Host + + $countPhp = @' +get_results('SELECT translation, COUNT(*) row_count FROM ' . $wpdb->prefix . 'brt_verses GROUP BY translation ORDER BY translation', ARRAY_A); +echo json_encode($rows); +'@ + Write-ContainerPhpFile $environment '/tmp/brt-count.php' $countPhp + $rowCount = (Invoke-WpCli $environment 'wp eval-file /tmp/brt-count.php --path=/var/www/html' | Out-String).Trim() + Add-Check 'translation rows stored' ($rowCount -match 'WEB' -and $rowCount -match 'KJV' -and $rowCount -match 'ASV' -and $rowCount -match 'YLT') $rowCount + + $bookCoveragePhp = @' +get_col($wpdb->prepare('SELECT DISTINCT book FROM ' . $wpdb->prefix . 'brt_verses WHERE translation = %s', $translation)); + $diff = array_values(array_diff($expected, $found)); + if ($diff) $missing[$translation] = $diff; +} +echo $missing ? json_encode($missing) : 'ok'; +'@ + Write-ContainerPhpFile $environment '/tmp/brt-book-coverage.php' $bookCoveragePhp + $bookCoverage = (Invoke-WpCli $environment 'wp eval-file /tmp/brt-book-coverage.php --path=/var/www/html' | Out-String).Trim() + Add-Check 'canonical book coverage' ($bookCoverage -eq 'ok') $bookCoverage + + $refs = @('John 3:16', 'John 3.16', 'Jn3:16', 'Romans 8:28', 'James 1:5', 'Mark 10:45', 'Mark10:45', 'Mk 10:45', 'Ezekiel 36:26', 'Eze 36:26', '2 John 1:6', '2Jn1:6', '2 Jo 1:6') + foreach ($ref in $refs) { + $lookupPhp = @" +set_param('ref', '$ref'); +`$request->set_param('translation', 'WEB'); +`$response = rest_do_request(`$request); +`$data = `$response->get_data(); +echo (!empty(`$data['ok']) && !empty(`$data['verses'])) ? 'ok' : json_encode(`$data); +"@ + Write-ContainerPhpFile $environment '/tmp/brt-lookup.php' $lookupPhp + $lookupResult = (Invoke-WpCli $environment 'wp eval-file /tmp/brt-lookup.php --path=/var/www/html' | Out-String).Trim() + Add-Check "REST lookup $ref" ($lookupResult -eq 'ok') $lookupResult + } + + Invoke-WpCli $environment "wp post create --post_type=post --post_status=publish --post_title='Bible Reference Tooltip Smoke Test' --post_content='John 3:16 Romans 8:28 James 1:5' --path=/var/www/html" | Out-Host + Add-Check 'test post created' $true + + $result.completedAt = (Get-Date).ToString('o') + $result.passed = $true +} catch { + $result.completedAt = (Get-Date).ToString('o') + $result.passed = $false + $result.error = $_.Exception.Message + throw +} finally { + Ensure-TestLabDirectories + $result | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath (Get-ResultFile -Name $environment.name) -Encoding UTF8 +} diff --git a/tools/testlab/scripts/seed-bible-reference-page-all.ps1 b/tools/testlab/scripts/seed-bible-reference-page-all.ps1 new file mode 100644 index 0000000..49b4a9a --- /dev/null +++ b/tools/testlab/scripts/seed-bible-reference-page-all.ps1 @@ -0,0 +1,9 @@ +. "$PSScriptRoot\common.ps1" + +$envs = Get-Content -LiteralPath $ConfigPath -Raw | ConvertFrom-Json +foreach ($environment in $envs) { + $name = [string]$environment.name + Write-Host "" + Write-Host "=== Seeding $name ===" + & "$PSScriptRoot\seed-bible-reference-page.ps1" -Env $name +} diff --git a/tools/testlab/scripts/seed-bible-reference-page.ps1 b/tools/testlab/scripts/seed-bible-reference-page.ps1 new file mode 100644 index 0000000..cfad983 --- /dev/null +++ b/tools/testlab/scripts/seed-bible-reference-page.ps1 @@ -0,0 +1,142 @@ +param( + [Parameter(Mandatory)][string]$Env +) + +. "$PSScriptRoot\common.ps1" + +$environment = Get-TestLabEnvironment -Name $Env + +$seedPhp = @' +'; +$content .= ''; +$content .= '

This page intentionally includes one full-name and one abbreviated Bible reference for every book of the Bible across all installed local translations.

'; +$content .= '
'; +$content .= '
Book
'; +foreach ($translations as $translation) { + $content .= '
' . esc_html($translation[0]) . '
'; +} +foreach ($books as $book) { + [$name, $abbr, $fullRef, $abbrRef] = $book; + $content .= '
'; + $content .= '
' . esc_html($name) . '
'; + foreach ($translations as $translation) { + [$code, $label] = $translation; + $content .= '
'; + $content .= '' . esc_html($fullRef) . ''; + $content .= '' . esc_html($abbrRef) . ''; + $content .= '
'; + } + $content .= '
'; +} +$content .= '
'; + +$existing = get_page_by_path('bible-reference-tooltip-test'); +$post_data = [ + 'post_title' => 'Bible Reference Tooltip Test', + 'post_name' => 'bible-reference-tooltip-test', + 'post_type' => 'page', + 'post_status' => 'publish', + 'post_content' => $content, +]; + +if ($existing) { + $post_data['ID'] = $existing->ID; + $page_id = wp_update_post($post_data, true); +} else { + $page_id = wp_insert_post($post_data, true); +} + +if (is_wp_error($page_id)) { + fwrite(STDERR, $page_id->get_error_message()); + exit(1); +} + +update_option('show_on_front', 'page'); +update_option('page_on_front', (int) $page_id); +$settings = get_option('brt_settings', []); +if (is_array($settings)) { + $settings['translation'] = $settings['translation'] ?? 'WEB'; + update_option('brt_settings', $settings, false); +} +echo 'seeded front page ' . $page_id . ' with ' . count($books) . ' books, ' . count($translations) . ' translations, and ' . (count($books) * count($translations) * 2) . ' reference links'; +'@ + +Write-ContainerPhpFile $environment '/tmp/brt-seed-front.php' $seedPhp +Invoke-WpCli $environment 'wp eval-file /tmp/brt-seed-front.php --path=/var/www/html' | Out-Host