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
@@ -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';