diff --git a/CHANGELOG.md b/CHANGELOG.md index 18e8026..2cbae80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,4 +40,9 @@ # v2.0.7 - Adds force update option -- Improves the book name display on Bible page \ No newline at end of file +- Improves the book name display on Bible page + +# v2.0.8 + +- Adds easy option to update book names in the back-end. +- Adds easy option to sync translations details in the back-end. \ No newline at end of file diff --git a/README.md b/README.md index b55f79b..b9608df 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Get Bible (2.0.7) +# Get Bible (2.0.8) ![Get Bible image](https://git.vdm.dev/getBible/joomla-component/raw/branch/master/admin/assets/images/vdm-component.jpg "GetBible") @@ -19,20 +19,20 @@ In essence, The Bible for Joomla is designed to transform how the Word of God is + *Name*: [Get Bible](https://getbible.net) + *First Build*: 3rd December, 2015 + *Last Build*: 1st August, 2023 -+ *Version*: 2.0.7 ++ *Version*: 2.0.8 + *Copyright*: Copyright (C) 2015. All Rights Reserved + *License*: GNU/GPL Version 2 or later - http://www.gnu.org/licenses/gpl-2.0.html ## Build Time -**544 Hours** or **68 Eight Hour Days** (actual time the author saved - +**545 Hours** or **68 Eight Hour Days** (actual time the author saved - 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*: **194879** -+ *File count*: **1698** ++ *Line count*: **195153** ++ *File count*: **1699** + *Folder count*: **162** **359 Hours** or **45 Eight Hour Days** (the actual time the author spent) @@ -43,7 +43,7 @@ due to [Automated Component Builder](https://www.joomlacomponentbuilder.com)) > **mapping @54hours** = codingtime / 10; > **office @91hours** = codingtime / 6;) -**903 Hours** or **113 Eight Hour Days** +**904 Hours** or **113 Eight Hour Days** (a total of the realistic time frame for this project) > (if creating a folder and file took **5 seconds** and writing one line of code took **10 seconds**, diff --git a/admin/README.txt b/admin/README.txt index b55f79b..b9608df 100644 --- a/admin/README.txt +++ b/admin/README.txt @@ -1,4 +1,4 @@ -# Get Bible (2.0.7) +# Get Bible (2.0.8) ![Get Bible image](https://git.vdm.dev/getBible/joomla-component/raw/branch/master/admin/assets/images/vdm-component.jpg "GetBible") @@ -19,20 +19,20 @@ In essence, The Bible for Joomla is designed to transform how the Word of God is + *Name*: [Get Bible](https://getbible.net) + *First Build*: 3rd December, 2015 + *Last Build*: 1st August, 2023 -+ *Version*: 2.0.7 ++ *Version*: 2.0.8 + *Copyright*: Copyright (C) 2015. All Rights Reserved + *License*: GNU/GPL Version 2 or later - http://www.gnu.org/licenses/gpl-2.0.html ## Build Time -**544 Hours** or **68 Eight Hour Days** (actual time the author saved - +**545 Hours** or **68 Eight Hour Days** (actual time the author saved - 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*: **194879** -+ *File count*: **1698** ++ *Line count*: **195153** ++ *File count*: **1699** + *Folder count*: **162** **359 Hours** or **45 Eight Hour Days** (the actual time the author spent) @@ -43,7 +43,7 @@ due to [Automated Component Builder](https://www.joomlacomponentbuilder.com)) > **mapping @54hours** = codingtime / 10; > **office @91hours** = codingtime / 6;) -**903 Hours** or **113 Eight Hour Days** +**904 Hours** or **113 Eight Hour Days** (a total of the realistic time frame for this project) > (if creating a folder and file took **5 seconds** and writing one line of code took **10 seconds**, diff --git a/admin/access.xml b/admin/access.xml index f722b7d..d7f2260 100644 --- a/admin/access.xml +++ b/admin/access.xml @@ -216,6 +216,8 @@ + + diff --git a/admin/controllers/translations.php b/admin/controllers/translations.php index 5291825..14922fd 100644 --- a/admin/controllers/translations.php +++ b/admin/controllers/translations.php @@ -20,6 +20,8 @@ defined('_JEXEC') or die('Restricted access'); use Joomla\CMS\MVC\Controller\AdminController; use Joomla\Utilities\ArrayHelper; +use VDM\Joomla\GetBible\Factory; +use VDM\Joomla\Utilities\ArrayHelper as UtilitiesArrayHelper; /** * Translations Admin Controller @@ -49,4 +51,90 @@ class GetbibleControllerTranslations extends AdminController { return parent::getModel($name, $prefix, $config); } + + public function updateTranslationsDetails() + { + // Check for request forgeries + JSession::checkToken() or die(Text::_('JINVALID_TOKEN')); + + // check if export is allowed for this user. + $user = JFactory::getUser(); + if ($user->authorise('translation.update_translations_details', 'com_getbible') && $user->authorise('core.export', 'com_getbible')) + { + if (Factory::_('GetBible.Watcher.Translation')->translations()) + { + // Redirect to the list screen with success. + $message = array(); + $message[] = '

' . JText::_('COM_GETBIBLE_UPDATE_COMPLETED') . '

'; + $message[] = '

' . JText::_('COM_GETBIBLE_ALL_TRANSLATIONS_WERE_SUCCESSFULLY_UPDATED_AND_THEY_ARE_NOW_IN_SYNC_WITH_THE_GETBIBLE_API') . '

'; + $this->setRedirect(JRoute::_('index.php?option=com_getbible&view=translations', false), implode('', $message), 'Success'); + return; + } + } + else + { + // Redirect to the list screen with error. + $message = JText::_('COM_GETBIBLE_YOU_DO_NOT_HAVE_PERMISSION_TO_UPDATE_THE_BOOK_NAMES_PLEASE_CONTACT_YOUR_SYSTEM_ADMINISTRATOR_FOR_MORE_HELP'); + $this->setRedirect(JRoute::_('index.php?option=com_getbible&view=translations', false), $message, 'error'); + return; + } + // Redirect to the list screen with error. + $message = JText::_('COM_GETBIBLE_UPDATE_FAILED_PLEASE_TRY_AGAIN_LATTER'); + $this->setRedirect(JRoute::_('index.php?option=com_getbible&view=translations', false), $message, 'error'); + return; + } + + public function updateBookNames() + { + // Check for request forgeries + JSession::checkToken() or die(Text::_('JINVALID_TOKEN')); + + // check if export is allowed for this user. + $user = JFactory::getUser(); + if ($user->authorise('translation.update_book_names', 'com_getbible') && $user->authorise('core.export', 'com_getbible')) + { + // Get the input + $input = JFactory::getApplication()->input; + $pks = $input->post->get('cid', array(), 'array'); + // Sanitize the input + JArrayHelper::toInteger($pks); + // check if there is any selections + $number = UtilitiesArrayHelper::check($pks); + if (!$number) + { + // Redirect to the list screen with error. + $message = JText::_('COM_GETBIBLE_NO_TRANSLATION_WAS_SELECTED_PLEASE_MAKE_A_SELECTION_AND_TRY_AGAIN'); + $this->setRedirect(JRoute::_('index.php?option=com_getbible&view=translations', false), $message, 'error'); + return; + } + elseif (Factory::_('GetBible.Watcher.Book')->translations($pks)) + { + // Redirect to the list screen with success. + $message = array(); + $message[] = '

' . JText::_('COM_GETBIBLE_UPDATE_COMPLETED') . '

'; + // get the data to export + if ($number == 1) + { + $message[] = '

' . JText::_('COM_GETBIBLE_THE_BOOK_NAMES_OF_THE_TRANSLATION_WERE_SUCCESSFULLY_UPDATED_AND_THEY_ARE_NOW_IN_SYNC_WITH_THE_GETBIBLE_API') . '

'; + } + else + { + $message[] = '

' . JText::_('COM_GETBIBLE_THE_BOOK_NAMES_OF_THE_SELECTED_TRANSLATIONS_WERE_SUCCESSFULLY_UPDATED_AND_THEY_ARE_NOW_IN_SYNC_WITH_THE_GETBIBLE_API') . '

'; + } + $this->setRedirect(JRoute::_('index.php?option=com_getbible&view=translations', false), implode('', $message), 'Success'); + return; + } + } + else + { + // Redirect to the list screen with error. + $message = JText::_('COM_GETBIBLE_YOU_DO_NOT_HAVE_PERMISSION_TO_UPDATE_THE_BOOK_NAMES_PLEASE_CONTACT_YOUR_SYSTEM_ADMINISTRATOR_FOR_MORE_HELP'); + $this->setRedirect(JRoute::_('index.php?option=com_getbible&view=translations', false), $message, 'error'); + return; + } + // Redirect to the list screen with error. + $message = JText::_('COM_GETBIBLE_UPDATE_FAILED_PLEASE_TRY_AGAIN_LATTER'); + $this->setRedirect(JRoute::_('index.php?option=com_getbible&view=translations', false), $message, 'error'); + return; + } } diff --git a/admin/language/en-GB/en-GB.com_getbible.ini b/admin/language/en-GB/en-GB.com_getbible.ini index 4b5d857..27faba0 100644 --- a/admin/language/en-GB/en-GB.com_getbible.ini +++ b/admin/language/en-GB/en-GB.com_getbible.ini @@ -3,6 +3,7 @@ COM_GETBIBLE_ACCESS_ALREADY_EXIST_BUT_COULD_NOT_BE_REACTIVATED="Access already e COM_GETBIBLE_ACCESS_REVOKED="Access revoked." COM_GETBIBLE_ALL_IS_GOOD_PLEASE_CHECK_AGAIN_LATTER="All is good, please check again latter." COM_GETBIBLE_ALL_TRANSLATIONS="All Translations" +COM_GETBIBLE_ALL_TRANSLATIONS_WERE_SUCCESSFULLY_UPDATED_AND_THEY_ARE_NOW_IN_SYNC_WITH_THE_GETBIBLE_API="All translations were successfully updated, and they are now in sync with the getBible API." COM_GETBIBLE_ARCHIVED="Archived" COM_GETBIBLE_ARE_YOU_SURE_YOU_WANT_TO_DELETE_CONFIRMING_WILL_PERMANENTLY_DELETE_THE_SELECTED_ITEMS="Are you sure you want to delete? Confirming will permanently delete the selected item(s)!" COM_GETBIBLE_AUTHOR="Author" @@ -761,6 +762,7 @@ COM_GETBIBLE_NOTE_VERSION_DESC="A count of the number of times this Note has bee COM_GETBIBLE_NOTE_VERSION_LABEL="Version" COM_GETBIBLE_NOT_FOUND_OR_ACCESS_DENIED="Not found, or access denied." COM_GETBIBLE_NO_ACCESS_GRANTED="No Access Granted!" +COM_GETBIBLE_NO_TRANSLATION_WAS_SELECTED_PLEASE_MAKE_A_SELECTION_AND_TRY_AGAIN="No translation was selected, please make a selection and try again!" COM_GETBIBLE_OPEN_AI_DISABLED="Open AI (disabled)" COM_GETBIBLE_OPEN_AI_MESSAGE="Open AI Message" COM_GETBIBLE_OPEN_AI_MESSAGES="Open AI Messages" @@ -1569,6 +1571,8 @@ COM_GETBIBLE_TAG_STATUS="Status" COM_GETBIBLE_TAG_SUCCESSFULLY_DELETED="Tag successfully deleted." COM_GETBIBLE_TAG_VERSION_DESC="A count of the number of times this Tag has been revised." COM_GETBIBLE_TAG_VERSION_LABEL="Version" +COM_GETBIBLE_THE_BOOK_NAMES_OF_THE_SELECTED_TRANSLATIONS_WERE_SUCCESSFULLY_UPDATED_AND_THEY_ARE_NOW_IN_SYNC_WITH_THE_GETBIBLE_API="The book names of the selected translations were successfully updated, and they are now in sync with the getBible API." +COM_GETBIBLE_THE_BOOK_NAMES_OF_THE_TRANSLATION_WERE_SUCCESSFULLY_UPDATED_AND_THEY_ARE_NOW_IN_SYNC_WITH_THE_GETBIBLE_API="The book names of the translation were successfully updated, and they are now in sync with the getBible API." COM_GETBIBLE_THE_NAME_COULD_NOT_BE_UPDATED="The name could not be updated." COM_GETBIBLE_THE_NAME_HAS_BEEN_UPDATED="The name has been updated." COM_GETBIBLE_THE_NOTE_WAS_SUCCESSFULLY_CREATED="The note was successfully created." @@ -1745,6 +1749,10 @@ COM_GETBIBLE_TRANSLATION_TRANSLATION_DESCRIPTION="Enter some translation" COM_GETBIBLE_TRANSLATION_TRANSLATION_HINT="King James Version" COM_GETBIBLE_TRANSLATION_TRANSLATION_LABEL="Translation" COM_GETBIBLE_TRANSLATION_TRANSLATION_MESSAGE="Error! Please add some translation here." +COM_GETBIBLE_TRANSLATION_UPDATE_BOOK_NAMES_BUTTON_ACCESS="Translation Update Book Names Button Access" +COM_GETBIBLE_TRANSLATION_UPDATE_BOOK_NAMES_BUTTON_ACCESS_DESC="Allows the users in this group to access the update book names button." +COM_GETBIBLE_TRANSLATION_UPDATE_TRANSLATIONS_DETAILS_BUTTON_ACCESS="Translation Update Translations Details Button Access" +COM_GETBIBLE_TRANSLATION_UPDATE_TRANSLATIONS_DETAILS_BUTTON_ACCESS_DESC="Allows the users in this group to access the update translations details button." COM_GETBIBLE_TRANSLATION_VERSION_DESC="A count of the number of times this Translation has been revised." COM_GETBIBLE_TRANSLATION_VERSION_HINT="history_1.0" COM_GETBIBLE_TRANSLATION_VERSION_LABEL="Version" @@ -1764,6 +1772,10 @@ COM_GETBIBLE_TYPE_TAG="Tag" COM_GETBIBLE_TYPE_TAGGED_VERSE="Tagged Verse" COM_GETBIBLE_TYPE_TRANSLATION="Translation" COM_GETBIBLE_TYPE_VERSE="Verse" +COM_GETBIBLE_UPDATE_BOOK_NAMES="Update Book Names" +COM_GETBIBLE_UPDATE_COMPLETED="Update Completed!" +COM_GETBIBLE_UPDATE_FAILED_PLEASE_TRY_AGAIN_LATTER="Update failed, please try again latter!" +COM_GETBIBLE_UPDATE_TRANSLATIONS_DETAILS="Update Translations Details" COM_GETBIBLE_UP_TO_DATE="Up to date" COM_GETBIBLE_USE_BATCH="Use Batch" COM_GETBIBLE_USE_BATCH_DESC="Allows users in this group to use batch copy/update method." @@ -1864,4 +1876,5 @@ COM_GETBIBLE_WITHOUT_SELECTING_THE_CORRECT_FAVOURITE_VERSEBR_YOU_CANT_PERFORM_TH COM_GETBIBLE_YOU_ARE_CURRENTLY_VIEWING_THE_TRASHED_ITEMS="You are currently viewing the trashed items." COM_GETBIBLE_YOU_ARE_CURRENTLY_VIEWING_THE_TRASH_AREA_AND_YOU_DONT_HAVE_ANY_ITEMS_IN_TRASH_AT_THE_MOMENT="You are currently viewing the trash area, and you don't have any items in trash at the moment!" COM_GETBIBLE_YOU_CAN_DIRECTLY_DOWNLOAD_THE_LATEST_UPDATE_OR_USE_THE_JOOMLA_UPDATE_AREA="You can directly download the latest update, or use the Joomla update area." +COM_GETBIBLE_YOU_DO_NOT_HAVE_PERMISSION_TO_UPDATE_THE_BOOK_NAMES_PLEASE_CONTACT_YOUR_SYSTEM_ADMINISTRATOR_FOR_MORE_HELP="You do not have permission to update the book names, please contact your system administrator for more help." COM_GETBIBLE_YOU_WILL_HAVE_TO_ENABLE_OPEN_AI_IN_THE_GLOBAL_OPTIONS_OF_YOUR_COMPONENT_SINCE_IT_IS_CURRENTLY_DISABLED="You will have to enable Open AI in the global options of your component, since it is currently disabled." \ No newline at end of file diff --git a/admin/language/en-GB/en-GB.com_getbible.sys.ini b/admin/language/en-GB/en-GB.com_getbible.sys.ini index ef86291..2e22fad 100644 --- a/admin/language/en-GB/en-GB.com_getbible.sys.ini +++ b/admin/language/en-GB/en-GB.com_getbible.sys.ini @@ -485,6 +485,10 @@ COM_GETBIBLE_TRANSLATIONS_EDIT_TRANSLATION="Translations Edit Translation" COM_GETBIBLE_TRANSLATIONS_EDIT_TRANSLATION_DESC="Allows the users in this group to edit translation of translation" COM_GETBIBLE_TRANSLATIONS_SUBMENU="Translations Submenu" COM_GETBIBLE_TRANSLATIONS_SUBMENU_DESC="Allows the users in this group to submenu of translation" +COM_GETBIBLE_TRANSLATION_UPDATE_BOOK_NAMES_BUTTON_ACCESS="Translation Update Book Names Button Access" +COM_GETBIBLE_TRANSLATION_UPDATE_BOOK_NAMES_BUTTON_ACCESS_DESC="Allows the users in this group to access the update book names button." +COM_GETBIBLE_TRANSLATION_UPDATE_TRANSLATIONS_DETAILS_BUTTON_ACCESS="Translation Update Translations Details Button Access" +COM_GETBIBLE_TRANSLATION_UPDATE_TRANSLATIONS_DETAILS_BUTTON_ACCESS_DESC="Allows the users in this group to access the update translations details button." COM_GETBIBLE_USE_BATCH="Use Batch" COM_GETBIBLE_USE_BATCH_DESC="Allows users in this group to use batch copy/update method." COM_GETBIBLE_VERSES_ACCESS="Verses Access" diff --git a/admin/sql/updates/mysql/2.0.7.sql b/admin/sql/updates/mysql/2.0.7.sql new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/admin/sql/updates/mysql/2.0.7.sql @@ -0,0 +1 @@ + diff --git a/admin/views/translations/view.html.php b/admin/views/translations/view.html.php index f937f6d..0c8b03e 100644 --- a/admin/views/translations/view.html.php +++ b/admin/views/translations/view.html.php @@ -132,6 +132,11 @@ class GetbibleViewTranslations extends HtmlView $dhtml = $layout->render(array('title' => $title)); $bar->appendButton('Custom', $dhtml, 'batch'); } + if ($this->user->authorise('translation.update_book_names', 'com_getbible')) + { + // add Update Book Names button. + JToolBarHelper::custom('translations.updateBookNames', 'bookmark custom-button-updatebooknames', '', 'COM_GETBIBLE_UPDATE_BOOK_NAMES', 'true'); + } if ($this->state->get('filter.published') == -2 && ($this->canState && $this->canDelete)) { @@ -142,6 +147,11 @@ class GetbibleViewTranslations extends HtmlView JToolbarHelper::trash('translations.trash'); } } + if ($this->user->authorise('translation.update_translations_details', 'com_getbible')) + { + // add Update Translations Details button. + JToolBarHelper::custom('translations.updateTranslationsDetails', 'book custom-button-updatetranslationsdetails', '', 'COM_GETBIBLE_UPDATE_TRANSLATIONS_DETAILS', false); + } // set help url for this view if found $this->help_url = GetbibleHelper::getHelpUrl('translations'); diff --git a/getbible.xml b/getbible.xml index ca4141e..b09ca1c 100644 --- a/getbible.xml +++ b/getbible.xml @@ -7,9 +7,9 @@ https://getbible.net Copyright (C) 2015. All Rights Reserved GNU/GPL Version 2 or later - http://www.gnu.org/licenses/gpl-2.0.html - 2.0.7 + 2.0.8 Get Bible (v.2.0.7) +

Get Bible (v.2.0.8)

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/libraries/jcb_powers/VDM.Joomla.GetBible/src/Watcher/Book.php b/libraries/jcb_powers/VDM.Joomla.GetBible/src/Watcher/Book.php index 1f34b90..0d9c1bc 100644 --- a/libraries/jcb_powers/VDM.Joomla.GetBible/src/Watcher/Book.php +++ b/libraries/jcb_powers/VDM.Joomla.GetBible/src/Watcher/Book.php @@ -59,6 +59,32 @@ final class Book extends Watcher $this->table = 'book'; } + /** + * Update translations books + * + * @param array $translations The translations ids. + * + * @return bool True on success + * @since 2.0.1 + */ + public function translations(array $translations): bool + { + foreach ($translations as $translation) + { + if (($abbreviation = $this->load->value(['id' => $translation], 'abbreviation', 'translation')) === null) + { + return false; + } + + if (!$this->update($abbreviation)) + { + return false; + } + } + + return true; + } + /** * Sync the target being watched * @@ -161,7 +187,7 @@ final class Book extends Watcher { // check if the verse exist $match['value'] = (string) $book->nr; - if (($object = $this->getTarget($match, $local_books)) !== null) + if ($local_books !== null && ($object = $this->getTarget($match, $local_books)) !== null) { $book->id = $object->id; $book->created = $this->today; @@ -174,9 +200,10 @@ final class Book extends Watcher } // check if we have values to insert + $inserted = false; if ($insert !== []) { - $this->insert->items($insert, $this->table); + $inserted = $this->insert->items($insert, $this->table); } // update the local values @@ -185,7 +212,7 @@ final class Book extends Watcher return true; } - return false; + return $inserted; } } diff --git a/libraries/jcb_powers/VDM.Joomla.GetBible/src/Watcher/Translation.php b/libraries/jcb_powers/VDM.Joomla.GetBible/src/Watcher/Translation.php index a336180..e3c9ab8 100644 --- a/libraries/jcb_powers/VDM.Joomla.GetBible/src/Watcher/Translation.php +++ b/libraries/jcb_powers/VDM.Joomla.GetBible/src/Watcher/Translation.php @@ -59,6 +59,17 @@ final class Translation extends Watcher $this->table = 'translation'; } + /** + * Update translations details + * + * @return bool True on success + * @since 2.0.1 + */ + public function translations(): bool + { + return $this->update(); + } + /** * Sync the target being watched * @@ -170,9 +181,10 @@ final class Translation extends Watcher } // check if we have values to insert + $inserted = false; if ($insert !== []) { - $this->insert->items($insert, $this->table); + $inserted = $this->insert->items($insert, $this->table); } // update the local values @@ -181,7 +193,7 @@ final class Translation extends Watcher return true; } - return false; + return $inserted; } } diff --git a/script.php b/script.php index 28c0969..3d6cda7 100644 --- a/script.php +++ b/script.php @@ -1125,7 +1125,7 @@ class com_getbibleInstallerScript { $rule_length = $db->loadResult(); // Check the size of the rules column - if ($rule_length <= 43520) + if ($rule_length <= 43840) { // Fix the assets table rules column size $fix_rules_size = "ALTER TABLE `#__assets` CHANGE `rules` `rules` TEXT NOT NULL COMMENT 'JSON encoded access control. Enlarged to TEXT by JCB';"; @@ -1539,7 +1539,7 @@ class com_getbibleInstallerScript echo ' -

Upgrade to Version 2.0.7 Was Successful! Let us know if anything is not working as expected.

'; +

Upgrade to Version 2.0.8 Was Successful! Let us know if anything is not working as expected.

'; // Set db if not set already. if (!isset($db)) diff --git a/update_server.xml b/update_server.xml index b816806..140f0a8 100644 --- a/update_server.xml +++ b/update_server.xml @@ -125,4 +125,22 @@ https://getbible.net + + Get Bible + The Bible for Joomla + pkg_getbible + component + site + 2.0.8 + https://getbible.net + + https://git.vdm.dev/api/v1/repos/getBible/joomla-pkg/archive/v2.0.8.zip + + + stable + + Llewellyn van der Merwe + https://getbible.net + + \ No newline at end of file