From 057c38f16f0b67f89a0e1b43e6cddff70ab85491 Mon Sep 17 00:00:00 2001 From: aB0t <robot@vdm.io> Date: Sun, 18 Feb 2024 23:54:30 +0200 Subject: [PATCH] Stable release of v3.0.3 Add option to target mysql 8+ with the search regex. --- CHANGELOG.md | 4 ++ README.md | 10 ++-- admin/README.txt | 10 ++-- admin/sql/updates/mysql/3.0.2.sql | 1 + admin/views/book/tmpl/edit.php | 2 +- admin/views/chapter/tmpl/edit.php | 2 +- admin/views/linker/tmpl/edit.php | 2 +- admin/views/note/tmpl/edit.php | 2 +- admin/views/open_ai_message/tmpl/edit.php | 2 +- admin/views/open_ai_response/tmpl/edit.php | 2 +- admin/views/password/tmpl/edit.php | 2 +- admin/views/prompt/tmpl/edit.php | 2 +- admin/views/tag/tmpl/edit.php | 2 +- admin/views/tagged_verse/tmpl/edit.php | 2 +- admin/views/translation/tmpl/edit.php | 2 +- admin/views/verse/tmpl/edit.php | 2 +- getbible.xml | 6 +- script.php | 2 +- site/models/search.php | 65 ++++++++++++++++++++-- site/views/app/tmpl/default.php | 1 + site/views/openai/tmpl/default.php | 1 + site/views/search/tmpl/default.php | 1 + site/views/tag/tmpl/default.php | 1 + update_server.xml | 18 ++++++ 24 files changed, 114 insertions(+), 30 deletions(-) create mode 100644 admin/sql/updates/mysql/3.0.2.sql diff --git a/CHANGELOG.md b/CHANGELOG.md index 45b6cda..42e0873 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# v3.0.3 + +- Add option to target mysql 8+ with the search regex. + # v3.0.2 - More namespace updates. diff --git a/README.md b/README.md index 8490f4f..117292c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Get Bible (3.0.2) +# Get Bible (3.0.3) ![Get Bible image](https://git.vdm.dev/getBible/joomla-component/raw/branch/master/admin/assets/images/vdm-component.jpg "GetBible") @@ -18,8 +18,8 @@ In essence, The Bible for Joomla is designed to transform how the Word of God is + *Author*: [Llewellyn van der Merwe](mailto:joomla@vdm.io) + *Name*: [Get Bible](https://getbible.net) + *First Build*: 3rd December, 2015 -+ *Last Build*: 10th February, 2024 -+ *Version*: 3.0.2 ++ *Last Build*: 18th February, 2024 ++ *Version*: 3.0.3 + *Copyright*: Copyright (C) 2015. All Rights Reserved + *License*: GNU/GPL Version 2 or later - http://www.gnu.org/licenses/gpl-2.0.html @@ -31,8 +31,8 @@ due to [Automated Component Builder](https://www.joomlacomponentbuilder.com)) > (if creating a folder and file took **5 seconds** and writing one line of code took **10 seconds**, > never making one mistake or taking any coffee break.) -+ *Line count*: **201645** -+ *File count*: **1714** ++ *Line count*: **201717** ++ *File count*: **1715** + *Folder count*: **150** **371 Hours** or **47 Eight Hour Days** (the actual time the author spent) diff --git a/admin/README.txt b/admin/README.txt index 8490f4f..117292c 100644 --- a/admin/README.txt +++ b/admin/README.txt @@ -1,4 +1,4 @@ -# Get Bible (3.0.2) +# Get Bible (3.0.3) ![Get Bible image](https://git.vdm.dev/getBible/joomla-component/raw/branch/master/admin/assets/images/vdm-component.jpg "GetBible") @@ -18,8 +18,8 @@ In essence, The Bible for Joomla is designed to transform how the Word of God is + *Author*: [Llewellyn van der Merwe](mailto:joomla@vdm.io) + *Name*: [Get Bible](https://getbible.net) + *First Build*: 3rd December, 2015 -+ *Last Build*: 10th February, 2024 -+ *Version*: 3.0.2 ++ *Last Build*: 18th February, 2024 ++ *Version*: 3.0.3 + *Copyright*: Copyright (C) 2015. All Rights Reserved + *License*: GNU/GPL Version 2 or later - http://www.gnu.org/licenses/gpl-2.0.html @@ -31,8 +31,8 @@ due to [Automated Component Builder](https://www.joomlacomponentbuilder.com)) > (if creating a folder and file took **5 seconds** and writing one line of code took **10 seconds**, > never making one mistake or taking any coffee break.) -+ *Line count*: **201645** -+ *File count*: **1714** ++ *Line count*: **201717** ++ *File count*: **1715** + *Folder count*: **150** **371 Hours** or **47 Eight Hour Days** (the actual time the author spent) diff --git a/admin/sql/updates/mysql/3.0.2.sql b/admin/sql/updates/mysql/3.0.2.sql new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/admin/sql/updates/mysql/3.0.2.sql @@ -0,0 +1 @@ + diff --git a/admin/views/book/tmpl/edit.php b/admin/views/book/tmpl/edit.php index 2b2be57..0758f7d 100644 --- a/admin/views/book/tmpl/edit.php +++ b/admin/views/book/tmpl/edit.php @@ -51,7 +51,7 @@ $componentParams = $this->params; // will be removed just use $this->params inst <?php echo LayoutHelper::render('book.details_above', $this); ?> <div class="form-horizontal"> - <?php echo Html::_('bootstrap.startTabSet', 'bookTab', array('active' => 'details')); ?> + <?php echo Html::_('bootstrap.startTabSet', 'bookTab', ['active' => 'details', 'recall' => true]); ?> <?php echo Html::_('bootstrap.addTab', 'bookTab', 'details', Text::_('COM_GETBIBLE_BOOK_DETAILS', true)); ?> <div class="row-fluid form-horizontal-desktop"> diff --git a/admin/views/chapter/tmpl/edit.php b/admin/views/chapter/tmpl/edit.php index 0ff856d..0038d65 100644 --- a/admin/views/chapter/tmpl/edit.php +++ b/admin/views/chapter/tmpl/edit.php @@ -51,7 +51,7 @@ $componentParams = $this->params; // will be removed just use $this->params inst <?php echo LayoutHelper::render('chapter.details_above', $this); ?> <div class="form-horizontal"> - <?php echo Html::_('bootstrap.startTabSet', 'chapterTab', array('active' => 'details')); ?> + <?php echo Html::_('bootstrap.startTabSet', 'chapterTab', ['active' => 'details', 'recall' => true]); ?> <?php echo Html::_('bootstrap.addTab', 'chapterTab', 'details', Text::_('COM_GETBIBLE_CHAPTER_DETAILS', true)); ?> <div class="row-fluid form-horizontal-desktop"> diff --git a/admin/views/linker/tmpl/edit.php b/admin/views/linker/tmpl/edit.php index e04b486..7f6ee42 100644 --- a/admin/views/linker/tmpl/edit.php +++ b/admin/views/linker/tmpl/edit.php @@ -51,7 +51,7 @@ $componentParams = $this->params; // will be removed just use $this->params inst <?php echo LayoutHelper::render('linker.details_above', $this); ?> <div class="form-horizontal"> - <?php echo Html::_('bootstrap.startTabSet', 'linkerTab', array('active' => 'details')); ?> + <?php echo Html::_('bootstrap.startTabSet', 'linkerTab', ['active' => 'details', 'recall' => true]); ?> <?php echo Html::_('bootstrap.addTab', 'linkerTab', 'details', Text::_('COM_GETBIBLE_LINKER_DETAILS', true)); ?> <div class="row-fluid form-horizontal-desktop"> diff --git a/admin/views/note/tmpl/edit.php b/admin/views/note/tmpl/edit.php index 500a98f..524f8f7 100644 --- a/admin/views/note/tmpl/edit.php +++ b/admin/views/note/tmpl/edit.php @@ -51,7 +51,7 @@ $componentParams = $this->params; // will be removed just use $this->params inst <?php echo LayoutHelper::render('note.details_above', $this); ?> <div class="form-horizontal"> - <?php echo Html::_('bootstrap.startTabSet', 'noteTab', array('active' => 'details')); ?> + <?php echo Html::_('bootstrap.startTabSet', 'noteTab', ['active' => 'details', 'recall' => true]); ?> <?php echo Html::_('bootstrap.addTab', 'noteTab', 'details', Text::_('COM_GETBIBLE_NOTE_DETAILS', true)); ?> <div class="row-fluid form-horizontal-desktop"> diff --git a/admin/views/open_ai_message/tmpl/edit.php b/admin/views/open_ai_message/tmpl/edit.php index 2a1a981..fb06290 100644 --- a/admin/views/open_ai_message/tmpl/edit.php +++ b/admin/views/open_ai_message/tmpl/edit.php @@ -51,7 +51,7 @@ $componentParams = $this->params; // will be removed just use $this->params inst <?php echo LayoutHelper::render('open_ai_message.message_above', $this); ?> <div class="form-horizontal"> - <?php echo Html::_('bootstrap.startTabSet', 'open_ai_messageTab', array('active' => 'message')); ?> + <?php echo Html::_('bootstrap.startTabSet', 'open_ai_messageTab', ['active' => 'message', 'recall' => true]); ?> <?php echo Html::_('bootstrap.addTab', 'open_ai_messageTab', 'message', Text::_('COM_GETBIBLE_OPEN_AI_MESSAGE_MESSAGE', true)); ?> <div class="row-fluid form-horizontal-desktop"> diff --git a/admin/views/open_ai_response/tmpl/edit.php b/admin/views/open_ai_response/tmpl/edit.php index 84b5158..af5cb21 100644 --- a/admin/views/open_ai_response/tmpl/edit.php +++ b/admin/views/open_ai_response/tmpl/edit.php @@ -51,7 +51,7 @@ $componentParams = $this->params; // will be removed just use $this->params inst <?php echo LayoutHelper::render('open_ai_response.details_above', $this); ?> <div class="form-horizontal"> - <?php echo Html::_('bootstrap.startTabSet', 'open_ai_responseTab', array('active' => 'details')); ?> + <?php echo Html::_('bootstrap.startTabSet', 'open_ai_responseTab', ['active' => 'details', 'recall' => true]); ?> <?php echo Html::_('bootstrap.addTab', 'open_ai_responseTab', 'details', Text::_('COM_GETBIBLE_OPEN_AI_RESPONSE_DETAILS', true)); ?> <div class="row-fluid form-horizontal-desktop"> diff --git a/admin/views/password/tmpl/edit.php b/admin/views/password/tmpl/edit.php index 1e68aaa..3b552f8 100644 --- a/admin/views/password/tmpl/edit.php +++ b/admin/views/password/tmpl/edit.php @@ -51,7 +51,7 @@ $componentParams = $this->params; // will be removed just use $this->params inst <?php echo LayoutHelper::render('password.details_above', $this); ?> <div class="form-horizontal"> - <?php echo Html::_('bootstrap.startTabSet', 'passwordTab', array('active' => 'details')); ?> + <?php echo Html::_('bootstrap.startTabSet', 'passwordTab', ['active' => 'details', 'recall' => true]); ?> <?php echo Html::_('bootstrap.addTab', 'passwordTab', 'details', Text::_('COM_GETBIBLE_PASSWORD_DETAILS', true)); ?> <div class="row-fluid form-horizontal-desktop"> diff --git a/admin/views/prompt/tmpl/edit.php b/admin/views/prompt/tmpl/edit.php index 929f2a1..99a959b 100644 --- a/admin/views/prompt/tmpl/edit.php +++ b/admin/views/prompt/tmpl/edit.php @@ -51,7 +51,7 @@ $componentParams = $this->params; // will be removed just use $this->params inst <?php echo LayoutHelper::render('prompt.prompt_above', $this); ?> <div class="form-horizontal"> - <?php echo Html::_('bootstrap.startTabSet', 'promptTab', array('active' => 'prompt')); ?> + <?php echo Html::_('bootstrap.startTabSet', 'promptTab', ['active' => 'prompt', 'recall' => true]); ?> <?php echo Html::_('bootstrap.addTab', 'promptTab', 'prompt', Text::_('COM_GETBIBLE_PROMPT_PROMPT', true)); ?> <div class="row-fluid form-horizontal-desktop"> diff --git a/admin/views/tag/tmpl/edit.php b/admin/views/tag/tmpl/edit.php index e44f39f..923a5ce 100644 --- a/admin/views/tag/tmpl/edit.php +++ b/admin/views/tag/tmpl/edit.php @@ -51,7 +51,7 @@ $componentParams = $this->params; // will be removed just use $this->params inst <?php echo LayoutHelper::render('tag.details_above', $this); ?> <div class="form-horizontal"> - <?php echo Html::_('bootstrap.startTabSet', 'tagTab', array('active' => 'details')); ?> + <?php echo Html::_('bootstrap.startTabSet', 'tagTab', ['active' => 'details', 'recall' => true]); ?> <?php echo Html::_('bootstrap.addTab', 'tagTab', 'details', Text::_('COM_GETBIBLE_TAG_DETAILS', true)); ?> <div class="row-fluid form-horizontal-desktop"> diff --git a/admin/views/tagged_verse/tmpl/edit.php b/admin/views/tagged_verse/tmpl/edit.php index a2ca34f..47b9773 100644 --- a/admin/views/tagged_verse/tmpl/edit.php +++ b/admin/views/tagged_verse/tmpl/edit.php @@ -51,7 +51,7 @@ $componentParams = $this->params; // will be removed just use $this->params inst <?php echo LayoutHelper::render('tagged_verse.details_above', $this); ?> <div class="form-horizontal"> - <?php echo Html::_('bootstrap.startTabSet', 'tagged_verseTab', array('active' => 'details')); ?> + <?php echo Html::_('bootstrap.startTabSet', 'tagged_verseTab', ['active' => 'details', 'recall' => true]); ?> <?php echo Html::_('bootstrap.addTab', 'tagged_verseTab', 'details', Text::_('COM_GETBIBLE_TAGGED_VERSE_DETAILS', true)); ?> <div class="row-fluid form-horizontal-desktop"> diff --git a/admin/views/translation/tmpl/edit.php b/admin/views/translation/tmpl/edit.php index 796fd21..5baec13 100644 --- a/admin/views/translation/tmpl/edit.php +++ b/admin/views/translation/tmpl/edit.php @@ -51,7 +51,7 @@ $componentParams = $this->params; // will be removed just use $this->params inst <?php echo LayoutHelper::render('translation.details_above', $this); ?> <div class="form-horizontal"> - <?php echo Html::_('bootstrap.startTabSet', 'translationTab', array('active' => 'details')); ?> + <?php echo Html::_('bootstrap.startTabSet', 'translationTab', ['active' => 'details', 'recall' => true]); ?> <?php echo Html::_('bootstrap.addTab', 'translationTab', 'details', Text::_('COM_GETBIBLE_TRANSLATION_DETAILS', true)); ?> <div class="row-fluid form-horizontal-desktop"> diff --git a/admin/views/verse/tmpl/edit.php b/admin/views/verse/tmpl/edit.php index 7fae5c6..18e3b77 100644 --- a/admin/views/verse/tmpl/edit.php +++ b/admin/views/verse/tmpl/edit.php @@ -51,7 +51,7 @@ $componentParams = $this->params; // will be removed just use $this->params inst <?php echo LayoutHelper::render('verse.details_above', $this); ?> <div class="form-horizontal"> - <?php echo Html::_('bootstrap.startTabSet', 'verseTab', array('active' => 'details')); ?> + <?php echo Html::_('bootstrap.startTabSet', 'verseTab', ['active' => 'details', 'recall' => true]); ?> <?php echo Html::_('bootstrap.addTab', 'verseTab', 'details', Text::_('COM_GETBIBLE_VERSE_DETAILS', true)); ?> <div class="row-fluid form-horizontal-desktop"> diff --git a/getbible.xml b/getbible.xml index b3085df..011ef2e 100644 --- a/getbible.xml +++ b/getbible.xml @@ -1,15 +1,15 @@ <?xml version="1.0" encoding="utf-8"?> <extension type="component" version="3.10" method="upgrade"> <name>COM_GETBIBLE</name> - <creationDate>10th February, 2024</creationDate> + <creationDate>18th February, 2024</creationDate> <author>Llewellyn van der Merwe</author> <authorEmail>joomla@vdm.io</authorEmail> <authorUrl>https://getbible.net</authorUrl> <copyright>Copyright (C) 2015. All Rights Reserved</copyright> <license>GNU/GPL Version 2 or later - http://www.gnu.org/licenses/gpl-2.0.html</license> - <version>3.0.2</version> + <version>3.0.3</version> <description><![CDATA[ - <h1>Get Bible (v.3.0.2)</h1> + <h1>Get Bible (v.3.0.3)</h1> <div style="clear: both;"></div> <p>Welcome to the next level of scripture engagement - The Bible for Joomla! Our purpose is to bring the Word of God to every person, in their native language, entirely free. This isn't just a typical extension; it's a groundbreaking tool developed to span language divides and deliver a rich, customizable Bible study experience to users worldwide. diff --git a/script.php b/script.php index 3db70bd..877aaea 100644 --- a/script.php +++ b/script.php @@ -1546,7 +1546,7 @@ class Com_GetbibleInstallerScript echo '<div style="background-color: #fff;" class="alert alert-info"><a target="_blank" href="https://getbible.net" title="Get Bible"> <img src="components/com_getbible/assets/images/vdm-component.jpg"/> </a> - <h3>Upgrade to Version 3.0.2 Was Successful! Let us know if anything is not working as expected.</h3></div>'; + <h3>Upgrade to Version 3.0.3 Was Successful! Let us know if anything is not working as expected.</h3></div>'; // Set db if not set already. if (!isset($db)) diff --git a/site/models/search.php b/site/models/search.php index a88f4cf..46ad459 100644 --- a/site/models/search.php +++ b/site/models/search.php @@ -397,6 +397,14 @@ class GetbibleModelSearch extends ListModel $conditions = []; $case = $this->case == 2 ? 'BINARY' : ' '; + // Determine the database type and version + $db_type = $this->getDatabaseType($db); // Implement this method based on your environment + $use_modern_regex = $db_type === 'mysql' && $this->isModernMySQL($db); // Implement isModernMySQL to detect MySQL 8+ + + // Adjust REGEXP syntax based on the database type and version + $word_boundary_start = $use_modern_regex ? '\\b' : '[[:<:]]'; + $word_boundary_end = $use_modern_regex ? '\\b' : '[[:>:]]'; + // 2 = ANY WORDS if($this->words == 2) { @@ -436,11 +444,12 @@ class GetbibleModelSearch extends ListModel { if ($i == 0) { - $condition .= $db->quote('[[:<:]]' . $db->escape($word, true). '[[:>:]]'); + $condition .= $db->quote($word_boundary_start . $db->escape($word, true). $word_boundary_end); } else { - $condition .= ' OR ' . $case . ' a.text REGEXP '. $db->quote('[[:<:]]' . $db->escape($word, true) . '[[:>:]]'); + $condition .= ' OR ' . $case . ' a.text REGEXP '. + $db->quote($word_boundary_start . $db->escape($word, true) . $word_boundary_end); } $i++; } @@ -470,7 +479,8 @@ class GetbibleModelSearch extends ListModel { // 1 = exact match // For exact phrase, escape and quote the entire phrase and use REGEXP to match it exactly - $search = $case . ' a.text REGEXP ' . $db->quote('[[:<:]]' . $db->escape($this->search, true) . '[[:>:]]'); + $search = $case . ' a.text REGEXP ' . + $db->quote($word_boundary_start . $db->escape($this->search, true) . $word_boundary_end); $conditions[] = '(' . $search . ')'; } } @@ -498,7 +508,8 @@ class GetbibleModelSearch extends ListModel { if ($this->hasLength($word)) { - $search = $case . ' a.text REGEXP '. $db->quote('[[:<:]]' . $db->escape($word, true) . '[[:>:]]'); + $search = $case . ' a.text REGEXP '. + $db->quote($word_boundary_start . $db->escape($word, true) . $word_boundary_end); $conditions[] = '( ' . $search . ')'; } } @@ -662,5 +673,51 @@ class GetbibleModelSearch extends ListModel protected function hasLength(string $word): bool { return GetBibleFactory::_('GetBible.Utilities.String')->hasLength($word); + } + + /** + * Determines whether the database is MySQL or MariaDB. + * + * This function executes a query to fetch the version comment from the database. + * MariaDB includes its name in the version comment, allowing us to distinguish between MySQL and MariaDB. + * + * @param \JDatabaseDriver $db The database driver object from Joomla. + * + * @return string Returns 'mysql' for MySQL, 'mariadb' for MariaDB, or 'unknown' for other or undetectable types. + */ + protected function getDatabaseType($db): string + { + try { + // Attempt to get the version comment from the database + $versionComment = $db->setQuery("SELECT VERSION()")->loadResult(); + if (strpos(strtolower($versionComment), 'mariadb') !== false) { + return 'mariadb'; + } else { + return 'mysql'; // Assuming MySQL if MariaDB is not detected + } + } catch (\Exception $e) { + // Handle exceptions or fallback for other databases + return 'unknown'; + } + } + + /** + * Checks if the MySQL version is 8.0 or higher. + * + * This function queries the database version directly and compares it against 8.0 + * to determine if modern MySQL features, such as enhanced regular expression syntax, can be used. + * It's specifically designed for MySQL and does not apply to MariaDB or other databases. + * + * @param \JDatabaseDriver $db The database driver object. + * + * @return bool Returns true if the MySQL version is 8.0 or higher, false otherwise. + */ + protected function isModernMySQL($db): bool + { + // Query the database for its version + $version = $db->getVersion(); + + // Compare the version to determine if it's modern MySQL + return version_compare($version, '8.0', '>='); } } diff --git a/site/views/app/tmpl/default.php b/site/views/app/tmpl/default.php index 7fd8797..0a0eadc 100644 --- a/site/views/app/tmpl/default.php +++ b/site/views/app/tmpl/default.php @@ -21,6 +21,7 @@ defined('_JEXEC') or die('Restricted access'); use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\CMS\Router\Route; +use Joomla\CMS\HTML\HTMLHelper as Html; ?> <?php if ($this->item): ?> diff --git a/site/views/openai/tmpl/default.php b/site/views/openai/tmpl/default.php index f323a58..eccd74d 100644 --- a/site/views/openai/tmpl/default.php +++ b/site/views/openai/tmpl/default.php @@ -21,6 +21,7 @@ defined('_JEXEC') or die('Restricted access'); use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\CMS\Router\Route; +use Joomla\CMS\HTML\HTMLHelper as Html; ?> <?php if ($this->item): ?> diff --git a/site/views/search/tmpl/default.php b/site/views/search/tmpl/default.php index a3a200b..7343cb7 100644 --- a/site/views/search/tmpl/default.php +++ b/site/views/search/tmpl/default.php @@ -21,6 +21,7 @@ defined('_JEXEC') or die('Restricted access'); use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\CMS\Router\Route; +use Joomla\CMS\HTML\HTMLHelper as Html; ?> <?php if ($this->params->get('activate_search') == 1): ?> diff --git a/site/views/tag/tmpl/default.php b/site/views/tag/tmpl/default.php index 904f090..25f20ff 100644 --- a/site/views/tag/tmpl/default.php +++ b/site/views/tag/tmpl/default.php @@ -21,6 +21,7 @@ defined('_JEXEC') or die('Restricted access'); use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\CMS\Router\Route; +use Joomla\CMS\HTML\HTMLHelper as Html; ?> <?php if ($this->params->get('activate_tags') == 1): ?> diff --git a/update_server.xml b/update_server.xml index b891c45..7db3841 100644 --- a/update_server.xml +++ b/update_server.xml @@ -593,4 +593,22 @@ <maintainerurl>https://getbible.net</maintainerurl> <targetplatform name="joomla" version="3.*"/> </update> + <update> + <name>Get Bible</name> + <description>The Bible for Joomla</description> + <element>pkg_getbible</element> + <type>package</type> + <client>site</client> + <version>3.0.3</version> + <infourl title="Get Bible!">https://getbible.net</infourl> + <downloads> + <downloadurl type="full" format="zip">https://git.vdm.dev/api/v1/repos/getBible/joomla-pkg/archive/v3.0.3.zip</downloadurl> + </downloads> + <tags> + <tag>stable</tag> + </tags> + <maintainer>Llewellyn van der Merwe</maintainer> + <maintainerurl>https://getbible.net</maintainerurl> + <targetplatform name="joomla" version="3.*"/> + </update> </updates> \ No newline at end of file