diff --git a/.htaccess.sample b/.htaccess.sample index 57dfd443..ca6b9215 100644 --- a/.htaccess.sample +++ b/.htaccess.sample @@ -3,6 +3,10 @@ # $config['index_page'] = ''; RewriteEngine On + +RewriteCond %{REQUEST_URI} ^/backup/$ +RewriteRule ^(.*)$ /index.php?/$1 [L] + RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d -RewriteRule ^(.*)$ /index.php?/$1 [L] \ No newline at end of file +RewriteRule ^(.*)$ /index.php?/$1 [L] diff --git a/application/migrations/102_add_version_two_trigger_to_options.php b/application/migrations/102_add_version_two_trigger_to_options.php index 3b5307d9..e0dac738 100644 --- a/application/migrations/102_add_version_two_trigger_to_options.php +++ b/application/migrations/102_add_version_two_trigger_to_options.php @@ -13,9 +13,12 @@ class Migration_add_version_two_trigger_to_options extends CI_Migration { { $data = array( array('option_name' => "version2_trigger", 'option_value' => "false", 'autoload' => "yes"), - ); + ); - $this->db->insert_batch('options', $data); + $query = $this->db->select('option_name')->where('option_name', 'version2_trigger')->get('options'); + if($query->num_rows() == 0) { + $this->db->insert_batch('options', $data); + } } public function down() diff --git a/application/migrations/105_create_dxcc_master_tables.php b/application/migrations/105_create_dxcc_master_tables.php index 58aae898..f66a9f0a 100644 --- a/application/migrations/105_create_dxcc_master_tables.php +++ b/application/migrations/105_create_dxcc_master_tables.php @@ -4,6 +4,7 @@ class Migration_create_dxcc_master_tables extends CI_Migration { public function up() { + if (!$this->db->table_exists('dxcc_master')) { $this->db->query("CREATE TABLE `dxcc_master` ( `DXCCPrefix` varchar(6) DEFAULT NULL, `DXCCSearch` varchar(6) DEFAULT NULL, @@ -1041,6 +1042,7 @@ class Migration_create_dxcc_master_tables extends CI_Migration { $this->db->query("INSERT INTO `dxcc_master` (DXCCPrefix,DXCCSearch,DXCCMap,DXCCSort,CountryCode,PrefixList,DXCCName,Location,Continent,CQZone,ITUZone,IOTA,TimeZone,Latitude,Longitude,StartDate,EndDate) VALUES ('YB','YB','YB',17,327,'YB9[L-P],YB9Z[L-P],YC9[L-P],YC9Z[L-P],YD9[L-P],YD9Z[L-P],YE9[L-P],YE9Z[L-P],YF9[L-P],YF9Z[L-P],YG9[L-P],YG9Z[L-P],YH9[L-P],YH9Z[L-P]','Indonesia','East Nusatenggara','OC','28','54',NULL,-8.0,-8.67,121.5,NULL,NULL);"); $this->db->query("INSERT INTO `dxcc_master` (DXCCPrefix,DXCCSearch,DXCCMap,DXCCSort,CountryCode,PrefixList,DXCCName,Location,Continent,CQZone,ITUZone,IOTA,TimeZone,Latitude,Longitude,StartDate,EndDate) VALUES ('CE9','CE9','CE9',29,13,'RI1ANZ','Antarctica','Progress Station (Russia)','AN','39','69','AN-016',-6.0,-69.397,76.373,NULL,NULL);"); $this->db->query("INSERT INTO `dxcc_master` (DXCCPrefix,DXCCSearch,DXCCMap,DXCCSort,CountryCode,PrefixList,DXCCName,Location,Continent,CQZone,ITUZone,IOTA,TimeZone,Latitude,Longitude,StartDate,EndDate) VALUES ('UA0','UA0','UA',1,15,'UA0','Asiatic Russia','Asiatic Russia','AS',NULL,NULL,NULL,NULL,64.0,130.0,NULL,NULL);"); + } } public function down(){ diff --git a/application/models/Logbook_model.php b/application/models/Logbook_model.php index 14fdc063..eb1fa4b4 100755 --- a/application/models/Logbook_model.php +++ b/application/models/Logbook_model.php @@ -2343,12 +2343,17 @@ class Logbook_model extends CI_Model { function eqsl_not_yet_sent() { $this->db->select('station_profile.*, '.$this->config->item('table_name').'.COL_PRIMARY_KEY, '.$this->config->item('table_name').'.COL_TIME_ON, '.$this->config->item('table_name').'.COL_CALL, '.$this->config->item('table_name').'.COL_MODE, '.$this->config->item('table_name').'.COL_SUBMODE, '.$this->config->item('table_name').'.COL_BAND, '.$this->config->item('table_name').'.COL_COMMENT, '.$this->config->item('table_name').'.COL_RST_SENT, '.$this->config->item('table_name').'.COL_PROP_MODE, '.$this->config->item('table_name').'.COL_SAT_NAME, '.$this->config->item('table_name').'.COL_SAT_MODE, '.$this->config->item('table_name').'.COL_QSLMSG'); $this->db->from('station_profile'); - $this->db->join($this->config->item('table_name'),'station_profile.station_id = '.$this->config->item('table_name').'.station_id AND station_profile.eqslqthnickname != ""','right'); - $this->db->where('station_profile.eqslqthnickname !=', ''); + $this->db->join($this->config->item('table_name'),'station_profile.station_id = '.$this->config->item('table_name').'.station_id'); + $this->db->where("coalesce(station_profile.eqslqthnickname, '') <> ''"); $this->db->where($this->config->item('table_name').'.COL_CALL !=', ''); - $this->db->where($this->config->item('table_name').'.COL_EQSL_QSL_SENT !=', 'Y'); - $this->db->where($this->config->item('table_name').'.COL_EQSL_QSL_SENT !=', 'I'); - $this->db->or_where(array($this->config->item('table_name').'.COL_EQSL_QSL_SENT' => NULL)); + $this->db->group_start(); + $this->db->where($this->config->item('table_name').'.COL_EQSL_QSL_SENT is null'); + $this->db->or_where($this->config->item('table_name').'.COL_EQSL_QSL_SENT', ''); + $this->db->or_where($this->config->item('table_name').'.COL_EQSL_QSL_SENT', 'R'); + $this->db->or_where($this->config->item('table_name').'.COL_EQSL_QSL_SENT', 'Q'); + $this->db->or_where($this->config->item('table_name').'.COL_EQSL_QSL_SENT', 'N'); + $this->db->group_end(); + return $this->db->get(); } diff --git a/application/views/api/help.php b/application/views/api/help.php index 5ec2aa9e..c1caf1c8 100644 --- a/application/views/api/help.php +++ b/application/views/api/help.php @@ -16,6 +16,7 @@

The Cloudlog API (Application Programming Interface) lets third party systems access Cloudlog in a controlled way. Access to the API is managed via API keys.

You will need to generate an API key for each tool you wish to use (e.g. CloudlogCAT). Generate a read-write key if the application needs to send data to Cloudlog. Generate a read-only key if the application only needs to obtain data from Cloudlog.

+

API URL The API URL for this Cloudlog instance is:

Info It's good practice to delete a key if you are no longer using the associated application.

num_rows() > 0) { ?> diff --git a/application/views/interface_assets/footer.php b/application/views/interface_assets/footer.php index c66bc8ad..f4f78473 100644 --- a/application/views/interface_assets/footer.php +++ b/application/views/interface_assets/footer.php @@ -16,6 +16,7 @@ + uri->segment(1) == "activators") { ?> @@ -158,6 +159,16 @@ function copyApiKey(apiKey) { }); } +function copyApiUrl() { + var apiUrlField = $('#apiUrl'); + navigator.clipboard.writeText("").then(function() { + }); + apiUrlField.addClass('flash-copy') + .delay('1000').queue(function() { + apiUrlField.removeClass('flash-copy').dequeue(); + }); +} + $(function () { $('[data-toggle="tooltip"]').tooltip({'delay': { show: 500, hide: 0 }, 'placement': 'right'}); }); @@ -1292,7 +1303,11 @@ $(document).ready(function(){ layers: [layer], center: [19, 0], zoom: 2, - minZoom: 1 + minZoom: 1, + fullscreenControl: true, + fullscreenControlOptions: { + position: 'topleft' + }, }); var printer = L.easyPrint({ @@ -1435,7 +1450,11 @@ $(document).ready(function(){ layers: [layer], center: [19, 0], zoom: 2, - minZoom: 1 + minZoom: 1, + fullscreenControl: true, + fullscreenControlOptions: { + position: 'topleft' + }, }); var grid_two = ; diff --git a/application/views/interface_assets/header.php b/application/views/interface_assets/header.php index 4cb69d2d..c3161fee 100644 --- a/application/views/interface_assets/header.php +++ b/application/views/interface_assets/header.php @@ -20,6 +20,7 @@ + uri->segment(1) == "search" && $this->uri->segment(2) == "filter") { ?> diff --git a/application/views/visitor/layout/footer.php b/application/views/visitor/layout/footer.php index fe48b51a..23689ee2 100644 --- a/application/views/visitor/layout/footer.php +++ b/application/views/visitor/layout/footer.php @@ -69,7 +69,11 @@ var map = L.map('gridsquare_map', { layers: [layer], center: [19, 0], - zoom: 2 + zoom: 2, + fullscreenControl: true, + fullscreenControlOptions: { + position: 'topleft' + }, }); var printer = L.easyPrint({ diff --git a/assets/css/general.css b/assets/css/general.css index 97718a79..7bfbf8e4 100644 --- a/assets/css/general.css +++ b/assets/css/general.css @@ -405,6 +405,10 @@ div#station_logbooks_linked_table_paginate { } } +.api-url { + font-family: Monospace; +} + .api-key { font-family: Monospace; } diff --git a/assets/js/leaflet/Control.FullScreen.css b/assets/js/leaflet/Control.FullScreen.css new file mode 100644 index 00000000..e07fea53 --- /dev/null +++ b/assets/js/leaflet/Control.FullScreen.css @@ -0,0 +1,10 @@ +.fullscreen-icon { background-image: url(icon-fullscreen.svg); background-size:26px 52px; } +.fullscreen-icon.leaflet-fullscreen-on { background-position:0 -26px; } +.leaflet-touch .fullscreen-icon { background-position: 2px 2px; } +.leaflet-touch .fullscreen-icon.leaflet-fullscreen-on { background-position: 2px -24px; } +/* one selector per rule as explained here : http://www.sitepoint.com/html5-full-screen-api/ */ +.leaflet-container:-webkit-full-screen { width: 100% !important; height: 100% !important; z-index: 99999; } +.leaflet-container:-ms-fullscreen { width: 100% !important; height: 100% !important; z-index: 99999; } +.leaflet-container:full-screen { width: 100% !important; height: 100% !important; z-index: 99999; } +.leaflet-container:fullscreen { width: 100% !important; height: 100% !important; z-index: 99999; } +.leaflet-pseudo-fullscreen { position: fixed !important; width: 100% !important; height: 100% !important; top: 0px !important; left: 0px !important; z-index: 99999; } \ No newline at end of file diff --git a/assets/js/leaflet/Control.FullScreen.js b/assets/js/leaflet/Control.FullScreen.js new file mode 100644 index 00000000..fe582e79 --- /dev/null +++ b/assets/js/leaflet/Control.FullScreen.js @@ -0,0 +1,345 @@ +/*! +* Based on package 'screenfull' +* v5.2.0 - 2021-11-03 +* (c) Sindre Sorhus; MIT License +* Added definition for using screenfull as an amd module +* Must be placed before the definition of leaflet.fullscreen +* as it is required by that +*/ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define('screenfull', factory); + } else if (typeof module === 'object' && module.exports) { + module.exports.screenfull = factory(); + } else { + // Save 'screenfull' into global window variable + root.screenfull = factory(); + } +}(typeof self !== 'undefined' ? self : this, function () { + 'use strict'; + + var document = typeof window !== 'undefined' && typeof window.document !== 'undefined' ? window.document : {}; + + var fn = (function () { + var val; + + var fnMap = [ + [ + 'requestFullscreen', + 'exitFullscreen', + 'fullscreenElement', + 'fullscreenEnabled', + 'fullscreenchange', + 'fullscreenerror' + ], + // New WebKit + [ + 'webkitRequestFullscreen', + 'webkitExitFullscreen', + 'webkitFullscreenElement', + 'webkitFullscreenEnabled', + 'webkitfullscreenchange', + 'webkitfullscreenerror' + + ], + // Old WebKit + [ + 'webkitRequestFullScreen', + 'webkitCancelFullScreen', + 'webkitCurrentFullScreenElement', + 'webkitCancelFullScreen', + 'webkitfullscreenchange', + 'webkitfullscreenerror' + + ], + [ + 'mozRequestFullScreen', + 'mozCancelFullScreen', + 'mozFullScreenElement', + 'mozFullScreenEnabled', + 'mozfullscreenchange', + 'mozfullscreenerror' + ], + [ + 'msRequestFullscreen', + 'msExitFullscreen', + 'msFullscreenElement', + 'msFullscreenEnabled', + 'MSFullscreenChange', + 'MSFullscreenError' + ] + ]; + + var i = 0; + var l = fnMap.length; + var ret = {}; + + for (; i < l; i++) { + val = fnMap[i]; + if (val && val[1] in document) { + for (i = 0; i < val.length; i++) { + ret[fnMap[0][i]] = val[i]; + } + return ret; + } + } + + return false; + })(); + + var eventNameMap = { + change: fn.fullscreenchange, + error: fn.fullscreenerror + }; + + var screenfull = { + request: function (element, options) { + return new Promise(function (resolve, reject) { + var onFullScreenEntered = function () { + this.off('change', onFullScreenEntered); + resolve(); + }.bind(this); + + this.on('change', onFullScreenEntered); + + element = element || document.documentElement; + + var returnPromise = element[fn.requestFullscreen](options); + + if (returnPromise instanceof Promise) { + returnPromise.then(onFullScreenEntered).catch(reject); + } + }.bind(this)); + }, + exit: function () { + return new Promise(function (resolve, reject) { + if (!this.isFullscreen) { + resolve(); + return; + } + + var onFullScreenExit = function () { + this.off('change', onFullScreenExit); + resolve(); + }.bind(this); + + this.on('change', onFullScreenExit); + + var returnPromise = document[fn.exitFullscreen](); + + if (returnPromise instanceof Promise) { + returnPromise.then(onFullScreenExit).catch(reject); + } + }.bind(this)); + }, + toggle: function (element, options) { + return this.isFullscreen ? this.exit() : this.request(element, options); + }, + onchange: function (callback) { + this.on('change', callback); + }, + onerror: function (callback) { + this.on('error', callback); + }, + on: function (event, callback) { + var eventName = eventNameMap[event]; + if (eventName) { + document.addEventListener(eventName, callback, false); + } + }, + off: function (event, callback) { + var eventName = eventNameMap[event]; + if (eventName) { + document.removeEventListener(eventName, callback, false); + } + }, + raw: fn + }; + + if (!fn) { + return {isEnabled: false}; + } else { + Object.defineProperties(screenfull, { + isFullscreen: { + get: function () { + return Boolean(document[fn.fullscreenElement]); + } + }, + element: { + enumerable: true, + get: function () { + return document[fn.fullscreenElement]; + } + }, + isEnabled: { + enumerable: true, + get: function () { + // Coerce to boolean in case of old WebKit + return Boolean(document[fn.fullscreenEnabled]); + } + } + }); + return screenfull; + } +})); + +/*! +* leaflet.fullscreen +*/ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // define an AMD module that requires 'leaflet' and 'screenfull' + // and resolve to an object containing leaflet and screenfull + define('leafletFullScreen', ['leaflet', 'screenfull'], factory); + } else if (typeof module === 'object' && module.exports) { + // define a CommonJS module that requires 'leaflet' and 'screenfull' + module.exports = factory(require('leaflet'), require('screenfull')); + } else { + // Assume 'leaflet' and 'screenfull' are loaded into global variable already + factory(root.L, root.screenfull); + } +}(typeof self !== 'undefined' ? self : this, function (leaflet, screenfull) { + 'use strict'; + + leaflet.Control.FullScreen = leaflet.Control.extend({ + options: { + position: 'topleft', + title: 'Full Screen', + titleCancel: 'Exit Full Screen', + forceSeparateButton: false, + forcePseudoFullscreen: false, + fullscreenElement: false + }, + + _screenfull: screenfull, + + onAdd: function (map) { + var className = 'leaflet-control-zoom-fullscreen', container, content = ''; + + if (map.zoomControl && !this.options.forceSeparateButton) { + container = map.zoomControl._container; + } else { + container = leaflet.DomUtil.create('div', 'leaflet-bar'); + } + + if (this.options.content) { + content = this.options.content; + } else { + className += ' fullscreen-icon'; + } + + this._createButton(this.options.title, className, content, container, this.toggleFullScreen, this); + this._map.fullscreenControl = this; + + this._map.on('enterFullscreen exitFullscreen', this._toggleState, this); + + return container; + }, + + onRemove: function () { + leaflet.DomEvent + .off(this.link, 'click', leaflet.DomEvent.stop) + .off(this.link, 'click', this.toggleFullScreen, this); + + if (this._screenfull.isEnabled) { + leaflet.DomEvent + .off(this._container, this._screenfull.raw.fullscreenchange, leaflet.DomEvent.stop) + .off(this._container, this._screenfull.raw.fullscreenchange, this._handleFullscreenChange, this); + + leaflet.DomEvent + .off(document, this._screenfull.raw.fullscreenchange, leaflet.DomEvent.stop) + .off(document, this._screenfull.raw.fullscreenchange, this._handleFullscreenChange, this); + } + }, + + _createButton: function (title, className, content, container, fn, context) { + this.link = leaflet.DomUtil.create('a', className, container); + this.link.href = '#'; + this.link.title = title; + this.link.innerHTML = content; + + this.link.setAttribute('role', 'button'); + this.link.setAttribute('aria-label', title); + + L.DomEvent.disableClickPropagation(container); + + leaflet.DomEvent + .on(this.link, 'click', leaflet.DomEvent.stop) + .on(this.link, 'click', fn, context); + + if (this._screenfull.isEnabled) { + leaflet.DomEvent + .on(container, this._screenfull.raw.fullscreenchange, leaflet.DomEvent.stop) + .on(container, this._screenfull.raw.fullscreenchange, this._handleFullscreenChange, context); + + leaflet.DomEvent + .on(document, this._screenfull.raw.fullscreenchange, leaflet.DomEvent.stop) + .on(document, this._screenfull.raw.fullscreenchange, this._handleFullscreenChange, context); + } + + return this.link; + }, + + toggleFullScreen: function () { + var map = this._map; + map._exitFired = false; + if (map._isFullscreen) { + if (this._screenfull.isEnabled && !this.options.forcePseudoFullscreen) { + this._screenfull.exit(); + } else { + leaflet.DomUtil.removeClass(this.options.fullscreenElement ? this.options.fullscreenElement : map._container, 'leaflet-pseudo-fullscreen'); + map.invalidateSize(); + } + map.fire('exitFullscreen'); + map._exitFired = true; + map._isFullscreen = false; + } + else { + if (this._screenfull.isEnabled && !this.options.forcePseudoFullscreen) { + this._screenfull.request(this.options.fullscreenElement ? this.options.fullscreenElement : map._container); + } else { + leaflet.DomUtil.addClass(this.options.fullscreenElement ? this.options.fullscreenElement : map._container, 'leaflet-pseudo-fullscreen'); + map.invalidateSize(); + } + map.fire('enterFullscreen'); + map._isFullscreen = true; + } + }, + + _toggleState: function () { + this.link.title = this._map._isFullscreen ? this.options.title : this.options.titleCancel; + this._map._isFullscreen ? L.DomUtil.removeClass(this.link, 'leaflet-fullscreen-on') : L.DomUtil.addClass(this.link, 'leaflet-fullscreen-on'); + }, + + _handleFullscreenChange: function () { + var map = this._map; + map.invalidateSize(); + if (!this._screenfull.isFullscreen && !map._exitFired) { + map.fire('exitFullscreen'); + map._exitFired = true; + map._isFullscreen = false; + } + } + }); + + leaflet.Map.include({ + toggleFullscreen: function () { + this.fullscreenControl.toggleFullScreen(); + } + }); + + leaflet.Map.addInitHook(function () { + if (this.options.fullscreenControl) { + this.addControl(leaflet.control.fullscreen(this.options.fullscreenControlOptions)); + } + }); + + leaflet.control.fullscreen = function (options) { + return new leaflet.Control.FullScreen(options); + }; + + // must return an object containing also screenfull to make screenfull + // available outside of this package, if used as an amd module, + // as webpack cannot handle amd define with moduleid + return {leaflet: leaflet, screenfull: screenfull}; +})); diff --git a/assets/js/leaflet/icon-fullscreen.svg b/assets/js/leaflet/icon-fullscreen.svg new file mode 100644 index 00000000..6107d8c3 --- /dev/null +++ b/assets/js/leaflet/icon-fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/js/sections/cqmap.js b/assets/js/sections/cqmap.js index d374f71b..1e688830 100644 --- a/assets/js/sections/cqmap.js +++ b/assets/js/sections/cqmap.js @@ -119,7 +119,13 @@ function load_cq_map2(data) { $("#cqmaptab").append('
'); } - var map = L.map('cqmap'); + var map = new L.Map('cqmap', { + fullscreenControl: true, + fullscreenControlOptions: { + position: 'topleft' + }, + }); + L.tileLayer( osmUrl, { @@ -151,9 +157,9 @@ function load_cq_map2(data) { color: mapColor, strokeOpacity: 0.3, strokeWeight: 2, - }).addTo(map); + }).addTo(map); - var title = '' + (Number(i)+Number(1)) + ''; + var title = '' + (Number(i)+Number(1)) + ''; var myIcon = L.divIcon({className: 'my-div-icon', html: title}); L.marker( @@ -162,7 +168,7 @@ function load_cq_map2(data) { title: (Number(i)+Number(1)), zIndex: 1000, } - ).addTo(map).on('click', onClick); + ).addTo(map).on('click', onClick); } /*Legend specific*/ diff --git a/assets/js/sections/dxccmap.js b/assets/js/sections/dxccmap.js index e117ffec..8a8a9f8f 100644 --- a/assets/js/sections/dxccmap.js +++ b/assets/js/sections/dxccmap.js @@ -42,7 +42,13 @@ function load_dxcc_map2(data, worked, confirmed, notworked) { $("#dxccmaptab").append('
'); } - var map = L.map('dxccmap'); + var map = new L.Map('dxccmap', { + fullscreenControl: true, + fullscreenControlOptions: { + position: 'topleft' + }, + }); + L.tileLayer( osmUrl, { diff --git a/assets/js/sections/iotamap.js b/assets/js/sections/iotamap.js index 8a2b7f4d..5c579387 100644 --- a/assets/js/sections/iotamap.js +++ b/assets/js/sections/iotamap.js @@ -42,7 +42,13 @@ function load_iota_map2(data, worked, confirmed, notworked) { $("#iotamaptab").append('
'); } - var map = L.map('iotamap'); + var map = new L.Map('iotamap', { + fullscreenControl: true, + fullscreenControlOptions: { + position: 'topleft' + }, + }); + L.tileLayer( osmUrl, { diff --git a/assets/json/satellite_data.json b/assets/json/satellite_data.json index f074ef0a..8f21974b 100644 --- a/assets/json/satellite_data.json +++ b/assets/json/satellite_data.json @@ -136,7 +136,7 @@ "U/V":[ { "Uplink_Mode":"LSB", - "Uplink_Freq":"4352800000", + "Uplink_Freq":"435280000", "Downlink_Mode":"USB", "Downlink_Freq":"145925000" }