From 4387dc53cb0ab4ea6237edabf3e7df6a619384c3 Mon Sep 17 00:00:00 2001 From: Niek ten Hoopen Date: Sat, 31 Aug 2019 16:27:35 +0200 Subject: [PATCH] Init --- .craftplugin | 1 + .gitignore | 32 ++ CHANGELOG.md | 9 + LICENSE.md | 40 ++ README.md | 6 + composer.json | 40 ++ src/EntryGpsCoordinates.php | 132 ++++++ .../EntryCoordinatesFieldAsset.php | 65 +++ .../dist/css/EntryCoordinates.css | 11 + .../dist/img/EntryCoordinates-icon.svg | 49 +++ .../dist/js/EntryCoordinates.js | 55 +++ src/fields/EntryCoordinates.php | 383 ++++++++++++++++++ src/icon.svg | 3 + .../fields/EntryCoordinates_input.twig | 145 +++++++ .../fields/EntryCoordinates_settings.twig | 26 ++ src/translations/en/entry-gps-coordinates.php | 25 ++ 16 files changed, 1022 insertions(+) create mode 100644 .craftplugin create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 composer.json create mode 100644 src/EntryGpsCoordinates.php create mode 100644 src/assetbundles/entrycoordinatesfield/EntryCoordinatesFieldAsset.php create mode 100644 src/assetbundles/entrycoordinatesfield/dist/css/EntryCoordinates.css create mode 100644 src/assetbundles/entrycoordinatesfield/dist/img/EntryCoordinates-icon.svg create mode 100644 src/assetbundles/entrycoordinatesfield/dist/js/EntryCoordinates.js create mode 100644 src/fields/EntryCoordinates.php create mode 100644 src/icon.svg create mode 100644 src/templates/_components/fields/EntryCoordinates_input.twig create mode 100644 src/templates/_components/fields/EntryCoordinates_settings.twig create mode 100644 src/translations/en/entry-gps-coordinates.php diff --git a/.craftplugin b/.craftplugin new file mode 100644 index 0000000..1dcdd63 --- /dev/null +++ b/.craftplugin @@ -0,0 +1 @@ +{"pluginName":"Entry GPS Coordinates","pluginDescription":"Pick a GPS location for an entry","pluginVersion":"1.0.0","pluginAuthorName":"NTH media","pluginVendorName":"nthmedia","pluginAuthorUrl":"https://nthmedia.nl","pluginAuthorGithub":"nthmedia","codeComments":"yes","pluginComponents":["fieldtypes"],"consolecommandName":"","controllerName":"","cpsectionName":"","elementName":"","fieldName":"EntryCoordinates","modelName":"","purchasableName":"","recordName":"","serviceName":"","taskName":"","utilityName":"","widgetName":"","apiVersion":"api_version_3_0"} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a17970c --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# CRAFT ENVIRONMENT +.env.php +.env.sh +.env + +# COMPOSER +/vendor + +# BUILD FILES +/bower_components/* +/node_modules/* +/build/* +/yarn-error.log + +# MISC FILES +.cache +.DS_Store +.idea +.project +.settings +*.esproj +*.sublime-workspace +*.sublime-project +*.tmproj +*.tmproject +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +config.codekit3 +prepros-6.config diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..63f0997 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +# Entry GPS Coordinates Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). + +## 1.0.0 - 2019-08-31 +### Added +- Initial release diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..d25c52e --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,40 @@ +Copyright © NTH media + +Permission is hereby granted to any person obtaining a copy of this software +(the “Software”) to use, copy, modify, merge, publish and/or distribute copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +1. **Don’t plagiarize.** The above copyright notice and this license shall be + included in all copies or substantial portions of the Software. + +2. **Don’t use the same license on more than one project.** Each licensed copy + of the Software shall be actively installed in no more than one production + environment at a time. + +3. **Don’t mess with the licensing features.** Software features related to + licensing shall not be altered or circumvented in any way, including (but + not limited to) license validation, payment prompts, feature restrictions, + and update eligibility. + +4. **Pay up.** Payment shall be made immediately upon receipt of any notice, + prompt, reminder, or other message indicating that a payment is owed. + +5. **Follow the law.** All use of the Software shall not violate any applicable + law or regulation, nor infringe the rights of any other person or entity. + +Failure to comply with the foregoing conditions will automatically and +immediately result in termination of the permission granted hereby. This +license does not include any right to receive updates to the Software or +technical support. Licensees bear all risk related to the quality and +performance of the Software and any modifications made or obtained to it, +including liability for actual and consequential harm, such as loss or +corruption of data, and any necessary service, repair, or correction. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER +LIABILITY, INCLUDING SPECIAL, INCIDENTAL AND CONSEQUENTIAL DAMAGES, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..4d4024f --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# Entry GPS Coordinates plugin for Craft CMS 3.x + +Pick a GPS location for an entry + +![PluginImpression](https://user-images.githubusercontent.com/3450011/64065265-e3cbc680-cc0b-11e9-89fc-ed682123b109.gif) + diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..4598d7f --- /dev/null +++ b/composer.json @@ -0,0 +1,40 @@ +{ + "name": "nthmedia/entry-gps-coordinates", + "description": "Pick a GPS location for an entry", + "type": "craft-plugin", + "version": "1.0.0", + "keywords": [ + "craft", + "cms", + "craftcms", + "craft-plugin", + "entry gps coordinates" + ], + "support": { + "docs": "https://github.com/nthmedia/entry-gps-coordinates", + "issues": "https://github.com/nthmedia/entry-gps-coordinates/issues" + }, + "license": "MIT", + "authors": [ + { + "name": "NTH media", + "homepage": "https://nthmedia.nl" + } + ], + "require": { + "craftcms/cms": "^3.0.0-RC1" + }, + "autoload": { + "psr-4": { + "nthmedia\\entrygpscoordinates\\": "src/" + } + }, + "extra": { + "name": "Entry GPS Coordinates", + "handle": "entry-gps-coordinates", + "hasCpSettings": false, + "hasCpSection": false, + "changelogUrl": "https://github.com/nthmedia/entry-gps-coordinates/CHANGELOG.md", + "class": "nthmedia\\entrygpscoordinates\\EntryGpsCoordinates" + } +} diff --git a/src/EntryGpsCoordinates.php b/src/EntryGpsCoordinates.php new file mode 100644 index 0000000..087da3e --- /dev/null +++ b/src/EntryGpsCoordinates.php @@ -0,0 +1,132 @@ +types[] = EntryCoordinatesField::class; + } + ); + + // Do something after we're installed + Event::on( + Plugins::class, + Plugins::EVENT_AFTER_INSTALL_PLUGIN, + function (PluginEvent $event) { + if ($event->plugin === $this) { + // We were just installed + } + } + ); + +/** + * Logging in Craft involves using one of the following methods: + * + * Craft::trace(): record a message to trace how a piece of code runs. This is mainly for development use. + * Craft::info(): record a message that conveys some useful information. + * Craft::warning(): record a warning message that indicates something unexpected has happened. + * Craft::error(): record a fatal error that should be investigated as soon as possible. + * + * Unless `devMode` is on, only Craft::warning() & Craft::error() will log to `craft/storage/logs/web.log` + * + * It's recommended that you pass in the magic constant `__METHOD__` as the second parameter, which sets + * the category to the method (prefixed with the fully qualified class name) where the constant appears. + * + * To enable the Yii debug toolbar, go to your user account in the AdminCP and check the + * [] Show the debug toolbar on the front end & [] Show the debug toolbar on the Control Panel + * + * http://www.yiiframework.com/doc-2.0/guide-runtime-logging.html + */ + Craft::info( + Craft::t( + 'entry-gps-coordinates', + '{name} plugin loaded', + ['name' => $this->name] + ), + __METHOD__ + ); + } + + // Protected Methods + // ========================================================================= + +} diff --git a/src/assetbundles/entrycoordinatesfield/EntryCoordinatesFieldAsset.php b/src/assetbundles/entrycoordinatesfield/EntryCoordinatesFieldAsset.php new file mode 100644 index 0000000..6f6dcd2 --- /dev/null +++ b/src/assetbundles/entrycoordinatesfield/EntryCoordinatesFieldAsset.php @@ -0,0 +1,65 @@ +sourcePath = "@nthmedia/entrygpscoordinates/assetbundles/entrycoordinatesfield/dist"; + + // define the dependencies + $this->depends = [ + CpAsset::class, + ]; + + // define the relative path to CSS/JS files that should be registered with the page + // when this asset bundle is registered + $this->js = [ + 'js/EntryCoordinates.js', + ]; + + $this->css = [ + 'css/EntryCoordinates.css', + ]; + + parent::init(); + } +} diff --git a/src/assetbundles/entrycoordinatesfield/dist/css/EntryCoordinates.css b/src/assetbundles/entrycoordinatesfield/dist/css/EntryCoordinates.css new file mode 100644 index 0000000..65f1adf --- /dev/null +++ b/src/assetbundles/entrycoordinatesfield/dist/css/EntryCoordinates.css @@ -0,0 +1,11 @@ +/** + * Entry GPS Coordinates plugin for Craft CMS + * + * EntryCoordinates Field CSS + * + * @author NTH media + * @copyright Copyright (c) 2019 NTH media + * @link https://nthmedia.nl + * @package EntryGpsCoordinates + * @since 1.0.0 + */ diff --git a/src/assetbundles/entrycoordinatesfield/dist/img/EntryCoordinates-icon.svg b/src/assetbundles/entrycoordinatesfield/dist/img/EntryCoordinates-icon.svg new file mode 100644 index 0000000..1c2ba2a --- /dev/null +++ b/src/assetbundles/entrycoordinatesfield/dist/img/EntryCoordinates-icon.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assetbundles/entrycoordinatesfield/dist/js/EntryCoordinates.js b/src/assetbundles/entrycoordinatesfield/dist/js/EntryCoordinates.js new file mode 100644 index 0000000..bf9115c --- /dev/null +++ b/src/assetbundles/entrycoordinatesfield/dist/js/EntryCoordinates.js @@ -0,0 +1,55 @@ +/** + * Entry GPS Coordinates plugin for Craft CMS + * + * EntryCoordinates Field JS + * + * @author NTH media + * @copyright Copyright (c) 2019 NTH media + * @link https://nthmedia.nl + * @package EntryGpsCoordinates + * @since 1.0.0EntryGpsCoordinatesEntryCoordinates + */ + + ;(function ( $, window, document, undefined ) { + + var pluginName = "EntryGpsCoordinatesEntryCoordinates", + defaults = { + }; + + // Plugin constructor + function Plugin( element, options ) { + this.element = element; + + this.options = $.extend( {}, defaults, options) ; + + this._defaults = defaults; + this._name = pluginName; + + this.init(); + } + + Plugin.prototype = { + + init: function(id) { + var _this = this; + + $(function () { + +/* -- _this.options gives us access to the $jsonVars that our FieldType passed down to us */ + + }); + } + }; + + // A really lightweight plugin wrapper around the constructor, + // preventing against multiple instantiations + $.fn[pluginName] = function ( options ) { + return this.each(function () { + if (!$.data(this, "plugin_" + pluginName)) { + $.data(this, "plugin_" + pluginName, + new Plugin( this, options )); + } + }); + }; + +})( jQuery, window, document ); diff --git a/src/fields/EntryCoordinates.php b/src/fields/EntryCoordinates.php new file mode 100644 index 0000000..5d0f77b --- /dev/null +++ b/src/fields/EntryCoordinates.php @@ -0,0 +1,383 @@ + ''], + ]); + return $rules; + } + + /** + * Returns the column type that this field should get within the content table. + * + * This method will only be called if [[hasContentColumn()]] returns true. + * + * @return string The column type. [[\yii\db\QueryBuilder::getColumnType()]] will be called + * to convert the give column type to the physical one. For example, `string` will be converted + * as `varchar(255)` and `string(100)` becomes `varchar(100)`. `not null` will automatically be + * appended as well. + * @see \yii\db\QueryBuilder::getColumnType() + */ + public function getContentColumnType(): string + { + return Schema::TYPE_STRING; + } + + /** + * Normalizes the field’s value for use. + * + * This method is called when the field’s value is first accessed from the element. For example, the first time + * `entry.myFieldHandle` is called from a template, or right before [[getInputHtml()]] is called. Whatever + * this method returns is what `entry.myFieldHandle` will likewise return, and what [[getInputHtml()]]’s and + * [[serializeValue()]]’s $value arguments will be set to. + * + * @param mixed $value The raw field value + * @param ElementInterface|null $element The element the field is associated with, if there is one + * + * @return mixed The prepared field value + */ + public function normalizeValue($value, ElementInterface $element = null) + { + if (is_string($value)) + $value = Json::decodeIfJson($value); + + if (is_string($value)) + $value = ['coordinates' => $value]; + + if ($value === null) + $value = []; + + $value['coordinates'] = array_key_exists('coordinates', $value) ? $value['coordinates'] : ''; + $value['searchQuery'] = array_key_exists('searchQuery', $value) ? $value['searchQuery'] : ''; + + return $value; + } + + /** + * Modifies an element query. + * + * This method will be called whenever elements are being searched for that may have this field assigned to them. + * + * If the method returns `false`, the query will be stopped before it ever gets a chance to execute. + * + * @param ElementQueryInterface $query The element query + * @param mixed $value The value that was set on this field’s corresponding [[ElementCriteriaModel]] param, + * if any. + * + * @return null|false `false` in the event that the method is sure that no elements are going to be found. + */ + public function serializeValue($value, ElementInterface $element = null) + { + return parent::serializeValue(Json::encode($value), $element); + } + + /** + * Returns the component’s settings HTML. + * + * An extremely simple implementation would be to directly return some HTML: + * + * ```php + * return ''; + * ``` + * + * For more complex settings, you might prefer to create a template, and render it via + * [[\craft\web\View::renderTemplate()]]. For example, the following code would render a template loacated at + * craft/plugins/myplugin/templates/_settings.html, passing the settings to it: + * + * ```php + * return Craft::$app->getView()->renderTemplate('myplugin/_settings', [ + * 'settings' => $this->getSettings() + * ]); + * ``` + * + * If you need to tie any JavaScript code to your settings, it’s important to know that any `name=` and `id=` + * attributes within the returned HTML will probably get [[\craft\web\View::namespaceInputs() namespaced]], + * however your JavaScript code will be left untouched. + * + * For example, if getSettingsHtml() returns the following HTML: + * + * ```html + * + * + * + * ``` + * + * …then it might actually look like this before getting output to the browser: + * + * ```html + * + * + * + * ``` + * + * As you can see, that JavaScript code will not be able to find the textarea, because the textarea’s `id=` + * attribute was changed from `foo` to `namespace-foo`. + * + * Before you start adding `namespace-` to the beginning of your element ID selectors, keep in mind that the actual + * namespace is going to change depending on the context. Often they are randomly generated. So it’s not quite + * that simple. + * + * Thankfully, [[\craft\web\View]] service provides a couple handy methods that can help you deal + * with this: + * + * - [[\craft\web\View::namespaceInputId()]] will give you the namespaced version of a given ID. + * - [[\craft\web\View::namespaceInputName()]] will give you the namespaced version of a given input name. + * - [[\craft\web\View::formatInputId()]] will format an input name to look more like an ID attribute value. + * + * So here’s what a getSettingsHtml() method that includes field-targeting JavaScript code might look like: + * + * ```php + * public function getSettingsHtml() + * { + * // Come up with an ID value for 'foo' + * $id = Craft::$app->getView()->formatInputId('foo'); + * + * // Figure out what that ID is going to be namespaced into + * $namespacedId = Craft::$app->getView()->namespaceInputId($id); + * + * // Render and return the input template + * return Craft::$app->getView()->renderTemplate('myplugin/_fieldinput', [ + * 'id' => $id, + * 'namespacedId' => $namespacedId, + * 'settings' => $this->getSettings() + * ]); + * } + * ``` + * + * And the _settings.html template might look like this: + * + * ```twig + * + * + * + * ``` + * + * The same principles also apply if you’re including your JavaScript code with + * [[\craft\web\View::registerJs()]]. + * + * @return string|null + */ + public function getSettingsHtml() + { + // Render the settings template + return Craft::$app->getView()->renderTemplate( + 'entry-gps-coordinates/_components/fields/EntryCoordinates_settings', + [ + 'field' => $this, + ] + ); + } + + /** + * Returns the field’s input HTML. + * + * An extremely simple implementation would be to directly return some HTML: + * + * ```php + * return ''; + * ``` + * + * For more complex inputs, you might prefer to create a template, and render it via + * [[\craft\web\View::renderTemplate()]]. For example, the following code would render a template located at + * craft/plugins/myplugin/templates/_fieldinput.html, passing the $name and $value variables to it: + * + * ```php + * return Craft::$app->getView()->renderTemplate('myplugin/_fieldinput', [ + * 'name' => $name, + * 'value' => $value + * ]); + * ``` + * + * If you need to tie any JavaScript code to your input, it’s important to know that any `name=` and `id=` + * attributes within the returned HTML will probably get [[\craft\web\View::namespaceInputs() namespaced]], + * however your JavaScript code will be left untouched. + * + * For example, if getInputHtml() returns the following HTML: + * + * ```html + * + * + * + * ``` + * + * …then it might actually look like this before getting output to the browser: + * + * ```html + * + * + * + * ``` + * + * As you can see, that JavaScript code will not be able to find the textarea, because the textarea’s `id=` + * attribute was changed from `foo` to `namespace-foo`. + * + * Before you start adding `namespace-` to the beginning of your element ID selectors, keep in mind that the actual + * namespace is going to change depending on the context. Often they are randomly generated. So it’s not quite + * that simple. + * + * Thankfully, [[\craft\web\View]] provides a couple handy methods that can help you deal with this: + * + * - [[\craft\web\View::namespaceInputId()]] will give you the namespaced version of a given ID. + * - [[\craft\web\View::namespaceInputName()]] will give you the namespaced version of a given input name. + * - [[\craft\web\View::formatInputId()]] will format an input name to look more like an ID attribute value. + * + * So here’s what a getInputHtml() method that includes field-targeting JavaScript code might look like: + * + * ```php + * public function getInputHtml($value, $element) + * { + * // Come up with an ID value based on $name + * $id = Craft::$app->getView()->formatInputId($name); + * + * // Figure out what that ID is going to be namespaced into + * $namespacedId = Craft::$app->getView()->namespaceInputId($id); + * + * // Render and return the input template + * return Craft::$app->getView()->renderTemplate('myplugin/_fieldinput', [ + * 'name' => $name, + * 'id' => $id, + * 'namespacedId' => $namespacedId, + * 'value' => $value + * ]); + * } + * ``` + * + * And the _fieldinput.html template might look like this: + * + * ```twig + * + * + * + * ``` + * + * The same principles also apply if you’re including your JavaScript code with + * [[\craft\web\View::registerJs()]]. + * + * @param mixed $value The field’s value. This will either be the [[normalizeValue() normalized value]], + * raw POST data (i.e. if there was a validation error), or null + * @param ElementInterface|null $element The element the field is associated with, if there is one + * + * @return string The input HTML. + */ + public function getInputHtml($value, ElementInterface $element = null): string + { + if (!$element) return ''; + + // Register our asset bundle + Craft::$app->getView()->registerAssetBundle(EntryCoordinatesFieldAsset::class); + + // Get our id and namespace + $id = Craft::$app->getView()->formatInputId($this->handle); + $namespacedId = Craft::$app->getView()->namespaceInputId($id); + + // Variables to pass down to our field JavaScript to let it namespace properly + $jsonVars = [ + 'id' => $id, + 'name' => $this->handle, + 'namespace' => $namespacedId, + 'prefix' => Craft::$app->getView()->namespaceInputId(''), + ]; + $jsonVars = Json::encode($jsonVars); + Craft::$app->getView()->registerJs("$('#{$namespacedId}-field').EntryGpsCoordinatesEntryCoordinates(" . $jsonVars . ");"); + + // Render the input template + return Craft::$app->getView()->renderTemplate( + 'entry-gps-coordinates/_components/fields/EntryCoordinates_input', + [ + 'name' => $this->handle, + 'value' => $value, + 'field' => $this, + 'id' => $id, + 'namespacedId' => $namespacedId, + + 'googleApiKey' => $this->googleApiKey + ] + ); + } +} diff --git a/src/icon.svg b/src/icon.svg new file mode 100644 index 0000000..0bdf982 --- /dev/null +++ b/src/icon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/templates/_components/fields/EntryCoordinates_input.twig b/src/templates/_components/fields/EntryCoordinates_input.twig new file mode 100644 index 0000000..40d3c01 --- /dev/null +++ b/src/templates/_components/fields/EntryCoordinates_input.twig @@ -0,0 +1,145 @@ +{# @var craft \craft\web\twig\variables\CraftVariable #} +{# +/** + * Entry GPS Coordinates plugin for Craft CMS 3.x + * + * EntryCoordinates Field Input + * + * @author NTH media + * @copyright Copyright (c) 2019 NTH media + * @link https://nthmedia.nl + * @package EntryGpsCoordinates + * @since 1.0.0 + */ +#} + +{% import "_includes/forms" as forms %} + +{{ forms.textField({ + id: name, + name: name ~ '[coordinates]', + value: value.coordinates +}) }} + +{{ forms.text({ + id: 'location-search', + name: name ~ '[searchQuery]', + value: value.searchQuery, + placeholder: 'Search Address' +}) }} + + + + +
+ + + + + \ No newline at end of file diff --git a/src/templates/_components/fields/EntryCoordinates_settings.twig b/src/templates/_components/fields/EntryCoordinates_settings.twig new file mode 100644 index 0000000..37a1483 --- /dev/null +++ b/src/templates/_components/fields/EntryCoordinates_settings.twig @@ -0,0 +1,26 @@ +{# @var craft \craft\web\twig\variables\CraftVariable #} +{# +/** + * Entry GPS Coordinates plugin for Craft CMS + * + * EntryCoordinates Field Settings + * + * @author NTH media + * @copyright Copyright (c) 2019 NTH media + * @link https://nthmedia.nl + * @package EntryGpsCoordinates + * @since 1.0.0 + */ +#} + +{% import "_includes/forms" as forms %} + +{% do view.registerAssetBundle("nthmedia\\entrygpscoordinates\\assetbundles\\entrycoordinatesfield\\EntryCoordinatesFieldAsset") %} + +{{ forms.textField({ + label: 'Google API Key', + instructions: 'Enter your Google API key here. It needs permission to access "Maps JavaScript API" and "Places API".', + id: 'googleApiKey', + name: 'googleApiKey', + value: field['googleApiKey']}) +}} diff --git a/src/translations/en/entry-gps-coordinates.php b/src/translations/en/entry-gps-coordinates.php new file mode 100644 index 0000000..89e4dea --- /dev/null +++ b/src/translations/en/entry-gps-coordinates.php @@ -0,0 +1,25 @@ + 'Entry GPS Coordinates plugin loaded', +];