Fortify Bible reference interpretation

This commit is contained in:
2026-07-11 20:16:27 -05:00
parent 5835e5a3b2
commit f2bc638509
9 changed files with 284 additions and 326 deletions
@@ -1,5 +1,11 @@
# Bible Reference Tooltips Changelog
## 1.1.6 - 2026-07-12
- Centralizes Bible book aliases, eBible source-code aliases, lookup candidates, repair maps, and frontend detection aliases into one registry.
- Adds an admin text-integrity panel showing canonical book coverage for installed translations.
- Expands the Docker smoke matrix with direct interpretation-registry checks and additional alias REST lookups.
## 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.
@@ -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.5
* Version: 1.1.6
* Stage: Stable
* Release Channel: Public
* Public Release: Yes
@@ -26,11 +26,11 @@ if (!defined('ABSPATH')) exit;
if (!class_exists('Bible_Reference_Tooltips')) {
class Bible_Reference_Tooltips {
const VERSION = '1.1.5';
const VERSION = '1.1.6';
const SLUG = 'bible-reference-tooltips';
const NONCE = 'brt_nonce';
const OPTION_KEY = 'brt_settings';
const SETTINGS_VERSION = '1.1.5';
const SETTINGS_VERSION = '1.1.6';
const TABLE_VERSION = '1';
private $book_map = null;
@@ -191,6 +191,7 @@ if (!class_exists('Bible_Reference_Tooltips')) {
'underline' => !empty($settings['underline']),
'showTranslation' => !empty($settings['show_translation']),
'showTooltips' => !empty($settings['show_tooltips']),
'bookAliases' => $this->frontend_book_alias_patterns(),
'caseSensitive' => empty($settings['case_insensitive']),
'tagChapters' => !empty($settings['tag_chapters']),
'scanComments' => !empty($settings['scan_comments']),
@@ -428,17 +429,7 @@ if (!class_exists('Bible_Reference_Tooltips')) {
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'] ?? ''));
$variants = [
'JHN' => ['JHN', 'JOH', 'JOHN'],
'PSA' => ['PSA', 'PS', 'PSM', 'PSALM', 'PSALMS'],
'SNG' => ['SNG', 'SON', 'SOS', 'SONG'],
'JAS' => ['JAS', 'JA', 'JAM', 'JMS', 'JM', 'JAC', 'JACOB', 'EPISTLEOFJAMES', 'THEEPISTLEOFJAMES'],
'JOL' => ['JOL', 'JOE'],
'NAM' => ['NAM', 'NAH'],
'PHM' => ['PHM', 'PHLM', 'PHILEM'],
'REV' => ['REV', 'RE', 'APO'],
];
$candidates = array_merge([$abbr, $name], $variants[$abbr] ?? [], $this->source_book_lookup_candidates($abbr));
$candidates = array_merge([$abbr, $name], $this->book_registry_aliases($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));
@@ -451,14 +442,13 @@ if (!class_exists('Bible_Reference_Tooltips')) {
private function book_name_lookup_candidates($parsed) {
$abbr = strtoupper(preg_replace('/[^A-Z0-9]/i', '', $parsed['book'] ?? ''));
$names = [$parsed['book_name'] ?? '', $abbr];
foreach ($this->book_map() as $alias => $entry) {
if (strtoupper($entry['abbr']) !== $abbr) continue;
$names[] = $entry['name'];
$names[] = $alias;
$names[] = strtoupper($alias);
}
if ($abbr === 'JAS') {
$names = array_merge($names, ['James', 'Jas', 'JAS', 'Ja', 'JA', 'Jam', 'JAM', 'Jms', 'JMS', 'Jm', 'JM', 'Jac', 'JAC', 'Jacob', 'Epistle of James', 'The Epistle of James']);
$book = $this->book_registry_entry($abbr);
if ($book) {
$names[] = $book['name'];
foreach ($book['aliases'] as $alias) {
$names[] = $alias;
$names[] = strtoupper($alias);
}
}
return array_values(array_unique(array_filter(array_map('strval', $names))));
}
@@ -525,115 +515,145 @@ if (!class_exists('Bible_Reference_Tooltips')) {
}
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)];
foreach ($this->book_registry() as $book) {
foreach ($book['source_aliases']['ebible_vpl'] as $source_abbr) {
$map[$source_abbr] = ['abbr' => $book['abbr'], 'name' => $book['name']];
}
}
return $map;
}
private function book_map() {
if ($this->book_map !== null) return $this->book_map;
$books = [
['GEN','Genesis',['gen','ge','gn']],
['EXO','Exodus',['exo','ex','exod']],
['LEV','Leviticus',['lev','le','lv']],
['NUM','Numbers',['num','nu','nm','nb']],
['DEU','Deuteronomy',['deut','deu','dt']],
['JOS','Joshua',['josh','jos']],
['JDG','Judges',['judg','jdg','jg']],
['RUT','Ruth',['ruth','rut']],
['1SA','1 Samuel',['1samuel','1sam','isamuel','isam']],
['2SA','2 Samuel',['2samuel','2sam','iisamuel','iisam']],
['1KI','1 Kings',['1kings','1kgs','1ki','ikings']],
['2KI','2 Kings',['2kings','2kgs','2ki','iikings']],
['1CH','1 Chronicles',['1chronicles','1chron','1chr','ichronicles']],
['2CH','2 Chronicles',['2chronicles','2chron','2chr','iichronicles']],
['EZR','Ezra',['ezra','ezr']],
['NEH','Nehemiah',['neh','ne']],
['EST','Esther',['esther','est']],
['JOB','Job',['job']],
['PSA','Psalms',['psalms','psalm','psa','ps']],
['PRO','Proverbs',['proverbs','prov','pro','pr']],
['ECC','Ecclesiastes',['ecclesiastes','eccl','ecc','qoheleth']],
['SNG','Song of Solomon',['songofsolomon','songofsongs','song','sos','sng']],
['ISA','Isaiah',['isaiah','isa']],
['JER','Jeremiah',['jeremiah','jer']],
['LAM','Lamentations',['lamentations','lam']],
['EZK','Ezekiel',['ezekiel','ezek','ezk','eze']],
['DAN','Daniel',['daniel','dan','dn']],
['HOS','Hosea',['hosea','hos']],
['JOL','Joel',['joel','jol','joe']],
['AMO','Amos',['amos','amo']],
['OBA','Obadiah',['obadiah','oba','obad']],
['JON','Jonah',['jonah','jon']],
['MIC','Micah',['micah','mic']],
['NAM','Nahum',['nahum','nah','nam']],
['HAB','Habakkuk',['habakkuk','hab']],
['ZEP','Zephaniah',['zephaniah','zeph','zep']],
['HAG','Haggai',['haggai','hag']],
['ZEC','Zechariah',['zechariah','zech','zec']],
['MAL','Malachi',['malachi','mal']],
['MAT','Matthew',['matthew','matt','mat','mt']],
['MRK','Mark',['mark','mrk','mk','mar']],
['LUK','Luke',['luke','luk','lk']],
['JHN','John',['john','jhn','jn','joh']],
['ACT','Acts',['acts','act']],
['ROM','Romans',['romans','rom','ro']],
['1CO','1 Corinthians',['1corinthians','1cor','1co','icorinthians']],
['2CO','2 Corinthians',['2corinthians','2cor','2co','iicorinthians']],
['GAL','Galatians',['galatians','gal']],
['EPH','Ephesians',['ephesians','eph']],
['PHP','Philippians',['philippians','phil','php','phi']],
['COL','Colossians',['colossians','col']],
['1TH','1 Thessalonians',['1thessalonians','1thess','1th','ithessalonians']],
['2TH','2 Thessalonians',['2thessalonians','2thess','2th','iithessalonians']],
['1TI','1 Timothy',['1timothy','1tim','1ti','itimothy']],
['2TI','2 Timothy',['2timothy','2tim','2ti','iitimothy']],
['TIT','Titus',['titus','tit']],
['PHM','Philemon',['philemon','phm']],
['HEB','Hebrews',['hebrews','heb']],
['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','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']],
];
$map = [];
foreach ($books as $book) {
$name_key = strtolower(preg_replace('/[^a-z0-9]/i', '', $book[1]));
$map[$name_key] = ['abbr' => $book[0], 'name' => $book[1]];
foreach ($book[2] as $alias) $map[$alias] = ['abbr' => $book[0], 'name' => $book[1]];
foreach ($this->book_registry() as $book) {
$map[$this->alias_key($book['name'])] = ['abbr' => $book['abbr'], 'name' => $book['name']];
foreach ($book['aliases'] as $alias) $map[$this->alias_key($alias)] = ['abbr' => $book['abbr'], 'name' => $book['name']];
}
return $this->book_map = $map;
}
private function book_registry_entry($abbr) {
$abbr = strtoupper(preg_replace('/[^A-Z0-9]/i', '', (string)$abbr));
foreach ($this->book_registry() as $book) {
if ($book['abbr'] === $abbr) return $book;
}
return null;
}
private function book_registry_aliases($abbr) {
$book = $this->book_registry_entry($abbr);
if (!$book) return [];
$aliases = [$book['abbr'], $book['name']];
$aliases = array_merge($aliases, $book['aliases'], $book['source_aliases']['ebible_vpl']);
return array_map(function($alias) {
return strtoupper(preg_replace('/[^A-Z0-9]/i', '', (string)$alias));
}, $aliases);
}
private function alias_key($alias) {
return strtolower(preg_replace('/[^a-z0-9]/i', '', (string)$alias));
}
private function frontend_book_alias_patterns() {
$patterns = [];
foreach ($this->book_registry() as $book) {
foreach (array_merge([$book['name']], $book['aliases']) as $alias) {
$patterns[] = $this->frontend_book_alias_pattern($alias);
}
}
usort($patterns, function($a, $b) { return strlen($b) - strlen($a); });
return array_values(array_unique(array_filter($patterns)));
}
private function frontend_book_alias_pattern($alias) {
$alias = trim((string)$alias);
if ($alias === '') return '';
$pattern = preg_quote($alias, '/');
$pattern = preg_replace('/\\\\\s+/', '\\\\s*', $pattern);
$pattern = preg_replace('/^([1-3])([A-Za-z])/', '$1\\\\s*$2', $pattern);
return str_replace('\\ ', '\\s*', $pattern);
}
private function book_registry() {
return [
['abbr'=>'GEN','name'=>'Genesis','aliases'=>['gen','ge','gn'],'source_aliases'=>['ebible_vpl'=>['GEN']]],
['abbr'=>'EXO','name'=>'Exodus','aliases'=>['exo','ex','exod'],'source_aliases'=>['ebible_vpl'=>['EXO']]],
['abbr'=>'LEV','name'=>'Leviticus','aliases'=>['lev','le','lv'],'source_aliases'=>['ebible_vpl'=>['LEV']]],
['abbr'=>'NUM','name'=>'Numbers','aliases'=>['num','nu','nm','nb'],'source_aliases'=>['ebible_vpl'=>['NUM']]],
['abbr'=>'DEU','name'=>'Deuteronomy','aliases'=>['deut','deu','dt'],'source_aliases'=>['ebible_vpl'=>['DEU']]],
['abbr'=>'JOS','name'=>'Joshua','aliases'=>['josh','jos'],'source_aliases'=>['ebible_vpl'=>['JOS']]],
['abbr'=>'JDG','name'=>'Judges','aliases'=>['judg','jdg','jg'],'source_aliases'=>['ebible_vpl'=>['JDG']]],
['abbr'=>'RUT','name'=>'Ruth','aliases'=>['ruth','rut'],'source_aliases'=>['ebible_vpl'=>['RUT']]],
['abbr'=>'1SA','name'=>'1 Samuel','aliases'=>['1samuel','1sam','i samuel','isamuel','isam'],'source_aliases'=>['ebible_vpl'=>['1SA']]],
['abbr'=>'2SA','name'=>'2 Samuel','aliases'=>['2samuel','2sam','ii samuel','iisamuel','iisam'],'source_aliases'=>['ebible_vpl'=>['2SA']]],
['abbr'=>'1KI','name'=>'1 Kings','aliases'=>['1kings','1kgs','1ki','i kings','ikings'],'source_aliases'=>['ebible_vpl'=>['1KI']]],
['abbr'=>'2KI','name'=>'2 Kings','aliases'=>['2kings','2kgs','2ki','ii kings','iikings'],'source_aliases'=>['ebible_vpl'=>['2KI']]],
['abbr'=>'1CH','name'=>'1 Chronicles','aliases'=>['1chronicles','1chron','1chr','i chronicles','ichronicles'],'source_aliases'=>['ebible_vpl'=>['1CH']]],
['abbr'=>'2CH','name'=>'2 Chronicles','aliases'=>['2chronicles','2chron','2chr','ii chronicles','iichronicles'],'source_aliases'=>['ebible_vpl'=>['2CH']]],
['abbr'=>'EZR','name'=>'Ezra','aliases'=>['ezra','ezr'],'source_aliases'=>['ebible_vpl'=>['EZR']]],
['abbr'=>'NEH','name'=>'Nehemiah','aliases'=>['neh','ne'],'source_aliases'=>['ebible_vpl'=>['NEH']]],
['abbr'=>'EST','name'=>'Esther','aliases'=>['esther','est'],'source_aliases'=>['ebible_vpl'=>['EST']]],
['abbr'=>'JOB','name'=>'Job','aliases'=>['job'],'source_aliases'=>['ebible_vpl'=>['JOB']]],
['abbr'=>'PSA','name'=>'Psalms','aliases'=>['psalms','psalm','psa','ps','psm'],'source_aliases'=>['ebible_vpl'=>['PSA']]],
['abbr'=>'PRO','name'=>'Proverbs','aliases'=>['proverbs','prov','pro','pr'],'source_aliases'=>['ebible_vpl'=>['PRO']]],
['abbr'=>'ECC','name'=>'Ecclesiastes','aliases'=>['ecclesiastes','eccl','ecc','qoheleth'],'source_aliases'=>['ebible_vpl'=>['ECC']]],
['abbr'=>'SNG','name'=>'Song of Solomon','aliases'=>['songofsolomon','song of songs','songofsongs','song','sos','sng','sol'],'source_aliases'=>['ebible_vpl'=>['SNG','SOL']]],
['abbr'=>'ISA','name'=>'Isaiah','aliases'=>['isaiah','isa'],'source_aliases'=>['ebible_vpl'=>['ISA']]],
['abbr'=>'JER','name'=>'Jeremiah','aliases'=>['jeremiah','jer'],'source_aliases'=>['ebible_vpl'=>['JER']]],
['abbr'=>'LAM','name'=>'Lamentations','aliases'=>['lamentations','lam'],'source_aliases'=>['ebible_vpl'=>['LAM']]],
['abbr'=>'EZK','name'=>'Ezekiel','aliases'=>['ezekiel','ezek','ezk','eze'],'source_aliases'=>['ebible_vpl'=>['EZK','EZE']]],
['abbr'=>'DAN','name'=>'Daniel','aliases'=>['daniel','dan','dn'],'source_aliases'=>['ebible_vpl'=>['DAN']]],
['abbr'=>'HOS','name'=>'Hosea','aliases'=>['hosea','hos'],'source_aliases'=>['ebible_vpl'=>['HOS']]],
['abbr'=>'JOL','name'=>'Joel','aliases'=>['joel','jol','joe'],'source_aliases'=>['ebible_vpl'=>['JOL','JOE']]],
['abbr'=>'AMO','name'=>'Amos','aliases'=>['amos','amo'],'source_aliases'=>['ebible_vpl'=>['AMO']]],
['abbr'=>'OBA','name'=>'Obadiah','aliases'=>['obadiah','oba','obad'],'source_aliases'=>['ebible_vpl'=>['OBA']]],
['abbr'=>'JON','name'=>'Jonah','aliases'=>['jonah','jon'],'source_aliases'=>['ebible_vpl'=>['JON']]],
['abbr'=>'MIC','name'=>'Micah','aliases'=>['micah','mic'],'source_aliases'=>['ebible_vpl'=>['MIC']]],
['abbr'=>'NAM','name'=>'Nahum','aliases'=>['nahum','nah','nam'],'source_aliases'=>['ebible_vpl'=>['NAM','NAH']]],
['abbr'=>'HAB','name'=>'Habakkuk','aliases'=>['habakkuk','hab'],'source_aliases'=>['ebible_vpl'=>['HAB']]],
['abbr'=>'ZEP','name'=>'Zephaniah','aliases'=>['zephaniah','zeph','zep'],'source_aliases'=>['ebible_vpl'=>['ZEP']]],
['abbr'=>'HAG','name'=>'Haggai','aliases'=>['haggai','hag'],'source_aliases'=>['ebible_vpl'=>['HAG']]],
['abbr'=>'ZEC','name'=>'Zechariah','aliases'=>['zechariah','zech','zec'],'source_aliases'=>['ebible_vpl'=>['ZEC']]],
['abbr'=>'MAL','name'=>'Malachi','aliases'=>['malachi','mal'],'source_aliases'=>['ebible_vpl'=>['MAL']]],
['abbr'=>'MAT','name'=>'Matthew','aliases'=>['matthew','matt','mat','mt'],'source_aliases'=>['ebible_vpl'=>['MAT']]],
['abbr'=>'MRK','name'=>'Mark','aliases'=>['mark','mrk','mk','mar'],'source_aliases'=>['ebible_vpl'=>['MRK','MAR']]],
['abbr'=>'LUK','name'=>'Luke','aliases'=>['luke','luk','lk'],'source_aliases'=>['ebible_vpl'=>['LUK']]],
['abbr'=>'JHN','name'=>'John','aliases'=>['john','jhn','jn','joh'],'source_aliases'=>['ebible_vpl'=>['JHN','JOH']]],
['abbr'=>'ACT','name'=>'Acts','aliases'=>['acts','act'],'source_aliases'=>['ebible_vpl'=>['ACT']]],
['abbr'=>'ROM','name'=>'Romans','aliases'=>['romans','rom','ro'],'source_aliases'=>['ebible_vpl'=>['ROM']]],
['abbr'=>'1CO','name'=>'1 Corinthians','aliases'=>['1corinthians','1cor','1co','i corinthians','icorinthians'],'source_aliases'=>['ebible_vpl'=>['1CO']]],
['abbr'=>'2CO','name'=>'2 Corinthians','aliases'=>['2corinthians','2cor','2co','ii corinthians','iicorinthians'],'source_aliases'=>['ebible_vpl'=>['2CO']]],
['abbr'=>'GAL','name'=>'Galatians','aliases'=>['galatians','gal'],'source_aliases'=>['ebible_vpl'=>['GAL']]],
['abbr'=>'EPH','name'=>'Ephesians','aliases'=>['ephesians','eph'],'source_aliases'=>['ebible_vpl'=>['EPH']]],
['abbr'=>'PHP','name'=>'Philippians','aliases'=>['philippians','phil','php','phi'],'source_aliases'=>['ebible_vpl'=>['PHP','PHI']]],
['abbr'=>'COL','name'=>'Colossians','aliases'=>['colossians','col'],'source_aliases'=>['ebible_vpl'=>['COL']]],
['abbr'=>'1TH','name'=>'1 Thessalonians','aliases'=>['1thessalonians','1thess','1th','i thessalonians','ithessalonians'],'source_aliases'=>['ebible_vpl'=>['1TH']]],
['abbr'=>'2TH','name'=>'2 Thessalonians','aliases'=>['2thessalonians','2thess','2th','ii thessalonians','iithessalonians'],'source_aliases'=>['ebible_vpl'=>['2TH']]],
['abbr'=>'1TI','name'=>'1 Timothy','aliases'=>['1timothy','1tim','1ti','i timothy','itimothy'],'source_aliases'=>['ebible_vpl'=>['1TI']]],
['abbr'=>'2TI','name'=>'2 Timothy','aliases'=>['2timothy','2tim','2ti','ii timothy','iitimothy'],'source_aliases'=>['ebible_vpl'=>['2TI']]],
['abbr'=>'TIT','name'=>'Titus','aliases'=>['titus','tit'],'source_aliases'=>['ebible_vpl'=>['TIT']]],
['abbr'=>'PHM','name'=>'Philemon','aliases'=>['philemon','phm','phlm','philemeon'],'source_aliases'=>['ebible_vpl'=>['PHM']]],
['abbr'=>'HEB','name'=>'Hebrews','aliases'=>['hebrews','heb'],'source_aliases'=>['ebible_vpl'=>['HEB']]],
['abbr'=>'JAS','name'=>'James','aliases'=>['james','jas','ja','jam','jms','jm','jac','jacob','epistleofjames','the epistle of james','theepistleofjames'],'source_aliases'=>['ebible_vpl'=>['JAS','JAM']]],
['abbr'=>'1PE','name'=>'1 Peter','aliases'=>['1peter','1pet','1pe','i peter','ipeter'],'source_aliases'=>['ebible_vpl'=>['1PE']]],
['abbr'=>'2PE','name'=>'2 Peter','aliases'=>['2peter','2pet','2pe','ii peter','iipeter'],'source_aliases'=>['ebible_vpl'=>['2PE']]],
['abbr'=>'1JN','name'=>'1 John','aliases'=>['1john','1jn','1jhn','1jo','i john','ijohn','ijn','ijhn','ijo'],'source_aliases'=>['ebible_vpl'=>['1JN','1JO']]],
['abbr'=>'2JN','name'=>'2 John','aliases'=>['2john','2jn','2jhn','2jo','ii john','iijohn','iijn','iijhn','iijo'],'source_aliases'=>['ebible_vpl'=>['2JN','2JO']]],
['abbr'=>'3JN','name'=>'3 John','aliases'=>['3john','3jn','3jhn','3jo','iii john','iiijohn','iiijn','iiijhn','iiijo'],'source_aliases'=>['ebible_vpl'=>['3JN','3JO']]],
['abbr'=>'JUD','name'=>'Jude','aliases'=>['jude','jud'],'source_aliases'=>['ebible_vpl'=>['JUD']]],
['abbr'=>'REV','name'=>'Revelation','aliases'=>['revelation','rev','re','apo'],'source_aliases'=>['ebible_vpl'=>['REV']]],
];
}
public function admin_page() {
if (!current_user_can('manage_options')) return;
$settings = $this->settings();
$stats = $this->translation_stats();
$translations = $this->available_translations();
$installed = $this->installed_translations();
$integrity = $this->text_integrity_summary();
$installed_by_code = [];
foreach ($installed as $row) $installed_by_code[$row['code']] = $row;
$msg = sanitize_text_field($_GET['msg'] ?? '');
@@ -732,6 +752,26 @@ if (!class_exists('Bible_Reference_Tooltips')) {
<button class="button button-primary">Save Settings</button>
</form>
</section>
<section class="brt-card brt-wide">
<h2>Text Integrity</h2>
<?php if (!$integrity): ?>
<p>No local Bible texts are installed yet.</p>
<?php else: ?>
<table class="widefat striped brt-table">
<thead><tr><th>Translation</th><th>Canonical books</th><th>Status</th></tr></thead>
<tbody>
<?php foreach ($integrity as $row): ?>
<tr>
<td><strong><?php echo esc_html($row['translation']); ?></strong></td>
<td><?php echo esc_html((int)$row['found'] . ' / ' . (int)$row['expected']); ?></td>
<td><?php echo $row['ok'] ? '<span class="brt-pill">Complete</span>' : esc_html('Missing: ' . implode(', ', $row['missing'])); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<p class="description">This checks whether each installed text has at least one row for every canonical Bible book after source-code interpretation.</p>
<?php endif; ?>
</section>
<section class="brt-card brt-wide">
<h2>Open Text Policy</h2>
<p>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.</p>
@@ -748,6 +788,29 @@ if (!class_exists('Bible_Reference_Tooltips')) {
return $wpdb->get_results("SELECT translation, COUNT(*) AS verses, MAX(source) AS source FROM " . self::table() . " GROUP BY translation ORDER BY translation ASC");
}
private function text_integrity_summary() {
global $wpdb;
$expected = array_map(function($book) { return $book['abbr']; }, $this->book_registry());
$translations = $wpdb->get_col("SELECT DISTINCT translation FROM " . self::table() . " ORDER BY translation ASC");
$summary = [];
foreach ((array)$translations as $translation) {
$found = $wpdb->get_col($wpdb->prepare(
"SELECT DISTINCT book FROM " . self::table() . " WHERE translation = %s ORDER BY book ASC",
$translation
));
$found = array_values(array_unique(array_map('strtoupper', array_map('strval', $found ?: []))));
$missing = array_values(array_diff($expected, $found));
$summary[] = [
'translation' => strtoupper((string)$translation),
'expected' => count($expected),
'found' => count(array_intersect($expected, $found)),
'missing' => $missing,
'ok' => !$missing,
];
}
return $summary;
}
private function available_translations() {
return [
'WEB' => [
@@ -1028,22 +1091,7 @@ if (!class_exists('Bible_Reference_Tooltips')) {
private function repair_book_aliases() {
if (get_option('brt_alias_repair_version') === self::VERSION) return;
global $wpdb;
$repairs = [
'JAM' => ['JAS', 'James'],
'JMS' => ['JAS', 'James'],
'JM' => ['JAS', 'James'],
'JA' => ['JAS', 'James'],
'JAC' => ['JAS', 'James'],
'JACOB' => ['JAS', 'James'],
'EPISTLEOFJAMES' => ['JAS', 'James'],
'THEEPISTLEOFJAMES' => ['JAS', 'James'],
'JOH' => ['JHN', 'John'],
'JOE' => ['JOL', 'Joel'],
'NAH' => ['NAM', 'Nahum'],
'PHI' => ['PHP', 'Philippians'],
'PHL' => ['PHM', 'Philemon'],
];
foreach ($repairs as $old => $new) {
foreach ($this->canonical_book_repairs() as $old => $new) {
$wpdb->query($wpdb->prepare(
"UPDATE IGNORE " . self::table() . " SET book = %s, book_name = %s WHERE book = %s",
$new[0],
@@ -1056,23 +1104,7 @@ if (!class_exists('Bible_Reference_Tooltips')) {
$new[0]
));
}
$name_repairs = [
'jam' => ['JAS', 'James'],
'jms' => ['JAS', 'James'],
'jm' => ['JAS', 'James'],
'ja' => ['JAS', 'James'],
'jas' => ['JAS', 'James'],
'jac' => ['JAS', 'James'],
'jacob' => ['JAS', 'James'],
'epistleofjames' => ['JAS', 'James'],
'theepistleofjames' => ['JAS', 'James'],
'joh' => ['JHN', 'John'],
'joe' => ['JOL', 'Joel'],
'nah' => ['NAM', 'Nahum'],
'phi' => ['PHP', 'Philippians'],
'phl' => ['PHM', 'Philemon'],
];
foreach ($name_repairs as $old_name => $new) {
foreach ($this->canonical_book_name_repairs() as $old_name => $new) {
$wpdb->query($wpdb->prepare(
"UPDATE IGNORE " . self::table() . " SET book = %s, book_name = %s WHERE LOWER(REPLACE(REPLACE(REPLACE(book_name, ' ', ''), '.', ''), '-', '')) = %s",
$new[0],
@@ -1083,6 +1115,28 @@ if (!class_exists('Bible_Reference_Tooltips')) {
update_option('brt_alias_repair_version', self::VERSION, false);
}
private function canonical_book_repairs() {
$repairs = [];
foreach ($this->book_registry() as $book) {
foreach (array_merge($book['aliases'], $book['source_aliases']['ebible_vpl']) as $alias) {
$key = strtoupper(preg_replace('/[^A-Z0-9]/i', '', (string)$alias));
if ($key && $key !== $book['abbr']) $repairs[$key] = [$book['abbr'], $book['name']];
}
}
return $repairs;
}
private function canonical_book_name_repairs() {
$repairs = [];
foreach ($this->book_registry() as $book) {
foreach (array_merge([$book['name']], $book['aliases'], $book['source_aliases']['ebible_vpl']) as $alias) {
$key = $this->alias_key($alias);
if ($key) $repairs[$key] = [$book['abbr'], $book['name']];
}
}
return $repairs;
}
private function repair_missing_james_verses() {
if (get_option('brt_james_backfill_version') === self::VERSION) return;
global $wpdb;
@@ -1126,20 +1180,7 @@ if (!class_exists('Bible_Reference_Tooltips')) {
return <<<'JS'
(function(){
if (!window.BibleReferenceTooltips || !BibleReferenceTooltips.rest) return;
const bookAliases = [
'Genesis','Gen','Ge','Gn','Exodus','Exod','Exo','Ex','Leviticus','Lev','Le','Lv','Numbers','Num','Nu','Nm','Nb','Deuteronomy','Deut','Deu','Dt',
'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','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','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','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 bookAliases = Array.isArray(BibleReferenceTooltips.bookAliases) ? BibleReferenceTooltips.bookAliases : [];
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';
+7 -1
View File
@@ -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.5
Stable tag: 1.1.6
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
@@ -49,6 +49,12 @@ Yes. The plugin is standalone and appears under Settings in the WordPress admin.
== Changelog ==
= 1.1.6 =
* Centralized Bible book aliases and eBible source-code interpretation into one registry.
* Added an admin text-integrity panel for installed Bible book coverage.
* Expanded smoke-test coverage for source-code normalization, human reference aliases, and compact references.
= 1.1.5 =
* Added the public workspace conventions and tracked Docker test lab as the bounded release workflow.
+2 -3
View File
@@ -6,14 +6,13 @@ The lab mounts plugin source from this workspace into disposable WordPress conta
## Environments
Initial matrix:
Default 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.
The `wp7-php84` image installs the latest WordPress available in the image line. Legacy WordPress/PHP environments should be added only for an explicit compatibility pass, not as part of the default bounded release gate.
## Commands
-7
View File
@@ -4,12 +4,5 @@
"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"
}
]
-149
View File
@@ -1,149 +0,0 @@
{
"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
}
+24 -4
View File
@@ -3,7 +3,7 @@
"wordpressImage": "wordpress:php8.4-apache",
"dbImage": "mariadb:11",
"hostPort": 8070,
"startedAt": "2026-07-11T19:46:20.2262126-05:00",
"startedAt": "2026-07-11T20:12:33.4678261-05:00",
"checks": [
{
"name": "wp-cli available",
@@ -28,13 +28,18 @@
{
"name": "plugin version detected",
"passed": true,
"detail": "version=1.1.5"
"detail": "version=1.1.6"
},
{
"name": "verse table created",
"passed": true,
"detail": "wp_brt_verses"
},
{
"name": "interpretation registry",
"passed": true,
"detail": "ok"
},
{
"name": "WEB import completed",
"passed": true,
@@ -90,6 +95,16 @@
"passed": true,
"detail": "ok"
},
{
"name": "REST lookup Jas 1:5",
"passed": true,
"detail": "ok"
},
{
"name": "REST lookup Jm 1:5",
"passed": true,
"detail": "ok"
},
{
"name": "REST lookup Mark 10:45",
"passed": true,
@@ -105,6 +120,11 @@
"passed": true,
"detail": "ok"
},
{
"name": "REST lookup Mar 10:45",
"passed": true,
"detail": "ok"
},
{
"name": "REST lookup Ezekiel 36:26",
"passed": true,
@@ -138,7 +158,7 @@
],
"phpVersion": "8.4.23",
"wordpressVersion": "7.0.1",
"pluginVersion": "1.1.5",
"completedAt": "2026-07-11T19:49:33.7883010-05:00",
"pluginVersion": "1.1.6",
"completedAt": "2026-07-11T20:16:05.3978156-05:00",
"passed": true
}
+1 -1
View File
@@ -7,7 +7,7 @@ $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'
$ExpectedPluginVersion = '1.1.6'
function Get-TestLabEnvironments {
Get-Content -LiteralPath $ConfigPath -Raw | ConvertFrom-Json
+43 -1
View File
@@ -80,6 +80,48 @@ echo $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->prefix . 'brt_v
$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
$interpretationPhp = @'
<?php
$plugin = new Bible_Reference_Tooltips();
$ref = new ReflectionClass($plugin);
$call = function($method, ...$args) use ($plugin, $ref) {
$m = $ref->getMethod($method);
$m->setAccessible(true);
return $m->invokeArgs($plugin, $args);
};
$checks = [];
foreach ([
['parse_reference', 'James 1:5', 'JAS'],
['parse_reference', 'Jas 1:5', 'JAS'],
['parse_reference', 'Jm 1:5', 'JAS'],
['parse_reference', 'Mark10:45', 'MRK'],
['parse_reference', '2Jn1:6', '2JN'],
['parse_reference', 'Eze 36:26', 'EZK'],
] as $case) {
$parsed = $call($case[0], $case[1]);
if (!$parsed || $parsed['book'] !== $case[2]) $checks[] = $case[1] . '=>' . json_encode($parsed);
}
foreach ([
['EZE 36:26 source', 'EZE', 'EZK'],
['MAR 10:45 source', 'MAR', 'MRK'],
['JOH 3:16 source', 'JOH', 'JHN'],
['JAM 1:5 source', 'JAM', 'JAS'],
['1JO 1:9 source', '1JO', '1JN'],
] as $case) {
$book = $call('normalize_source_book', $case[1], 'ebible_vpl');
if (!$book || $book['abbr'] !== $case[2]) $checks[] = $case[0] . '=>' . json_encode($book);
}
$aliases = $call('frontend_book_alias_patterns');
$alias_keys = array_map('strtolower', $aliases);
foreach (['James', 'Jm', 'Eze', 'Mar', '2\\s*Jn'] as $needle) {
if (!in_array(strtolower($needle), $alias_keys, true)) $checks[] = 'missing frontend alias ' . $needle;
}
echo $checks ? json_encode($checks) : 'ok';
'@
Write-ContainerPhpFile $environment '/tmp/brt-interpretation.php' $interpretationPhp
$interpretation = (Invoke-WpCli $environment 'wp eval-file /tmp/brt-interpretation.php --path=/var/www/html' | Out-String).Trim()
Add-Check 'interpretation registry' ($interpretation -eq 'ok') $interpretation
$translationsToImport = @('WEB', 'KJV', 'ASV', 'YLT')
foreach ($translationCode in $translationsToImport) {
$importPhp = @"
@@ -126,7 +168,7 @@ echo $missing ? json_encode($missing) : 'ok';
$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')
$refs = @('John 3:16', 'John 3.16', 'Jn3:16', 'Romans 8:28', 'James 1:5', 'Jas 1:5', 'Jm 1:5', 'Mark 10:45', 'Mark10:45', 'Mk 10:45', 'Mar 10:45', 'Ezekiel 36:26', 'Eze 36:26', '2 John 1:6', '2Jn1:6', '2 Jo 1:6')
foreach ($ref in $refs) {
$lookupPhp = @"
<?php