start rework from app store template
2
.bowerrc
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"directory": "vendor"
|
||||
"directory": "js/vendor"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
|
||||
# Created by https://www.gitignore.io/api/node,bower
|
||||
|
||||
### Bower ###
|
||||
bower_components
|
||||
.bower-cache
|
||||
.bower-registry
|
||||
.bower-tmp
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
|
||||
# End of https://www.gitignore.io/api/node,bower
|
|
@ -1,12 +0,0 @@
|
|||
filter:
|
||||
excluded_paths:
|
||||
- 'vendor/*'
|
||||
- 'js/3rdparty*'
|
||||
|
||||
imports:
|
||||
- javascript
|
||||
- php
|
||||
|
||||
tools:
|
||||
external_code_coverage: true
|
||||
|
97
.travis.yml
|
@ -1,49 +1,58 @@
|
|||
sudo: required
|
||||
dist: trusty
|
||||
language: php
|
||||
php:
|
||||
- 5.6
|
||||
- 7.0
|
||||
|
||||
- 5.6
|
||||
- 7
|
||||
env:
|
||||
global:
|
||||
- CORE_BRANCH=master
|
||||
- APP_NAME=maps
|
||||
matrix:
|
||||
- DB=sqlite
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
before_install:
|
||||
# - composer install
|
||||
- wget https://raw.githubusercontent.com/nextcloud/travis_ci/master/before_install.sh
|
||||
- bash ./before_install.sh $APP_NAME $CORE_BRANCH $DB
|
||||
- cd ../server
|
||||
- php occ app:enable $APP_NAME
|
||||
|
||||
script:
|
||||
# Test lint
|
||||
- cd apps/$APP_NAME
|
||||
# Test lint
|
||||
- find . -name \*.php -not -path './vendor/*' -exec php -l "{}" \;
|
||||
|
||||
# Run phpunit tests
|
||||
- phpunit --configuration phpunit.xml
|
||||
|
||||
# Create coverage report
|
||||
- wget https://scrutinizer-ci.com/ocular.phar
|
||||
- php ocular.phar code-coverage:upload --format=php-clover clover.xml
|
||||
global:
|
||||
- CORE_BRANCH=stable12
|
||||
matrix:
|
||||
- DB=pgsql
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- php: 7.0
|
||||
env: DB=mysql
|
||||
- php: 7.0
|
||||
env: DB=pgsql
|
||||
- php: 5.4
|
||||
env: "DB=mysql CORE_BRANCH=stable9"
|
||||
- php: 5.4
|
||||
env: "DB=mysql CORE_BRANCH=stable10"
|
||||
allow_failures:
|
||||
- php: hhvm
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- env: DB=pgsql CORE_BRANCH=master
|
||||
include:
|
||||
- php: 5.6
|
||||
env: DB=sqlite
|
||||
- php: 5.6
|
||||
env: DB=mysql
|
||||
- php: 5.6
|
||||
env: DB=pgsql CORE_BRANCH=master
|
||||
fast_finish: true
|
||||
|
||||
before_install:
|
||||
# enable a display for running JavaScript tests
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
- if [[ "$DB" == 'mysql' ]]; then sudo apt-get -y install mariadb-server; fi
|
||||
- nvm install 6
|
||||
- npm install -g npm@latest
|
||||
- make
|
||||
- make appstore
|
||||
# install core
|
||||
- cd ../
|
||||
- git clone https://github.com/nextcloud/server.git --recursive --depth 1 -b $CORE_BRANCH nextcloud
|
||||
- mv maps nextcloud/apps/
|
||||
|
||||
before_script:
|
||||
- if [[ "$DB" == 'pgsql' ]]; then createuser -U travis -s oc_autotest; fi
|
||||
- if [[ "$DB" == 'mysql' ]]; then mysql -u root -e 'create database oc_autotest;'; fi
|
||||
- if [[ "$DB" == 'mysql' ]]; then mysql -u root -e "CREATE USER 'oc_autotest'@'localhost' IDENTIFIED BY '';"; fi
|
||||
- if [[ "$DB" == 'mysql' ]]; then mysql -u root -e "grant all on oc_autotest.* to 'oc_autotest'@'localhost';"; fi
|
||||
- cd nextcloud
|
||||
- mkdir data
|
||||
- ./occ maintenance:install --database-name oc_autotest --database-user oc_autotest --admin-user admin --admin-pass admin --database $DB --database-pass=''
|
||||
- ./occ app:enable maps
|
||||
- php -S localhost:8080 &
|
||||
- cd apps/maps
|
||||
|
||||
script:
|
||||
- make test
|
||||
|
||||
after_failure:
|
||||
- cat ../../data/nextcloud.log
|
||||
|
||||
addons:
|
||||
firefox: "latest"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Authors
|
||||
|
||||
* Sander Brand: <brantje@gmail.com>
|
||||
|
||||
* Vinzenz Rosenkranz: <vinzenz.rosenkranz@gmail.com>
|
||||
|
|
2
COPYING
|
@ -658,4 +658,4 @@ specific requirements.
|
|||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
# This file is licensed under the Affero General Public License version 3 or
|
||||
# later. See the COPYING file.
|
||||
# @author Bernhard Posselt <dev@bernhard-posselt.com>
|
||||
# @copyright Bernhard Posselt 2016
|
||||
|
||||
# Generic Makefile for building and packaging a Nextcloud app which uses npm and
|
||||
# Composer.
|
||||
#
|
||||
# Dependencies:
|
||||
# * make
|
||||
# * which
|
||||
# * curl: used if phpunit and composer are not installed to fetch them from the web
|
||||
# * tar: for building the archive
|
||||
# * npm: for building and testing everything JS
|
||||
#
|
||||
# If no composer.json is in the app root directory, the Composer step
|
||||
# will be skipped. The same goes for the package.json which can be located in
|
||||
# the app root or the js/ directory.
|
||||
#
|
||||
# The npm command by launches the npm build script:
|
||||
#
|
||||
# npm run build
|
||||
#
|
||||
# The npm test command launches the npm test script:
|
||||
#
|
||||
# npm run test
|
||||
#
|
||||
# The idea behind this is to be completely testing and build tool agnostic. All
|
||||
# build tools and additional package managers should be installed locally in
|
||||
# your project, since this won't pollute people's global namespace.
|
||||
#
|
||||
# The following npm scripts in your package.json install and update the bower
|
||||
# and npm dependencies and use gulp as build system (notice how everything is
|
||||
# run from the node_modules folder):
|
||||
#
|
||||
# "scripts": {
|
||||
# "test": "node node_modules/gulp-cli/bin/gulp.js karma",
|
||||
# "prebuild": "npm install && node_modules/bower/bin/bower install && node_modules/bower/bin/bower update",
|
||||
# "build": "node node_modules/gulp-cli/bin/gulp.js"
|
||||
# },
|
||||
|
||||
app_name=$(notdir $(CURDIR))
|
||||
build_tools_directory=$(CURDIR)/build/tools
|
||||
source_build_directory=$(CURDIR)/build/artifacts/source
|
||||
source_package_name=$(source_build_directory)/$(app_name)
|
||||
appstore_build_directory=$(CURDIR)/build/artifacts/appstore
|
||||
appstore_package_name=$(appstore_build_directory)/$(app_name)
|
||||
npm=$(shell which npm 2> /dev/null)
|
||||
composer=$(shell which composer 2> /dev/null)
|
||||
|
||||
all: build
|
||||
|
||||
# Fetches the PHP and JS dependencies and compiles the JS. If no composer.json
|
||||
# is present, the composer step is skipped, if no package.json or js/package.json
|
||||
# is present, the npm step is skipped
|
||||
.PHONY: build
|
||||
build:
|
||||
ifneq (,$(wildcard $(CURDIR)/composer.json))
|
||||
make composer
|
||||
endif
|
||||
ifneq (,$(wildcard $(CURDIR)/package.json))
|
||||
make npm
|
||||
endif
|
||||
ifneq (,$(wildcard $(CURDIR)/js/package.json))
|
||||
make npm
|
||||
endif
|
||||
|
||||
# Installs and updates the composer dependencies. If composer is not installed
|
||||
# a copy is fetched from the web
|
||||
.PHONY: composer
|
||||
composer:
|
||||
ifeq (, $(composer))
|
||||
@echo "No composer command available, downloading a copy from the web"
|
||||
mkdir -p $(build_tools_directory)
|
||||
curl -sS https://getcomposer.org/installer | php
|
||||
mv composer.phar $(build_tools_directory)
|
||||
php $(build_tools_directory)/composer.phar install --prefer-dist
|
||||
php $(build_tools_directory)/composer.phar update --prefer-dist
|
||||
else
|
||||
composer install --prefer-dist
|
||||
composer update --prefer-dist
|
||||
endif
|
||||
|
||||
# Installs npm dependencies
|
||||
.PHONY: npm
|
||||
npm:
|
||||
ifeq (,$(wildcard $(CURDIR)/package.json))
|
||||
cd js && $(npm) run build
|
||||
else
|
||||
npm run build
|
||||
endif
|
||||
|
||||
# Removes the appstore build
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf ./build
|
||||
|
||||
# Same as clean but also removes dependencies installed by composer, bower and
|
||||
# npm
|
||||
.PHONY: distclean
|
||||
distclean: clean
|
||||
rm -rf vendor
|
||||
rm -rf node_modules
|
||||
rm -rf js/vendor
|
||||
rm -rf js/node_modules
|
||||
|
||||
# Builds the source and appstore package
|
||||
.PHONY: dist
|
||||
dist:
|
||||
make source
|
||||
make appstore
|
||||
|
||||
# Builds the source package
|
||||
.PHONY: source
|
||||
source:
|
||||
rm -rf $(source_build_directory)
|
||||
mkdir -p $(source_build_directory)
|
||||
tar cvzf $(source_package_name).tar.gz ../$(app_name) \
|
||||
--exclude-vcs \
|
||||
--exclude="../$(app_name)/build" \
|
||||
--exclude="../$(app_name)/js/node_modules" \
|
||||
--exclude="../$(app_name)/node_modules" \
|
||||
--exclude="../$(app_name)/*.log" \
|
||||
--exclude="../$(app_name)/js/*.log" \
|
||||
|
||||
# Builds the source package for the app store, ignores php and js tests
|
||||
.PHONY: appstore
|
||||
appstore:
|
||||
rm -rf $(appstore_build_directory)
|
||||
mkdir -p $(appstore_build_directory)
|
||||
tar cvzf $(appstore_package_name).tar.gz ../$(app_name) \
|
||||
--exclude-vcs \
|
||||
--exclude="../$(app_name)/build" \
|
||||
--exclude="../$(app_name)/tests" \
|
||||
--exclude="../$(app_name)/Makefile" \
|
||||
--exclude="../$(app_name)/*.log" \
|
||||
--exclude="../$(app_name)/phpunit*xml" \
|
||||
--exclude="../$(app_name)/composer.*" \
|
||||
--exclude="../$(app_name)/js/node_modules" \
|
||||
--exclude="../$(app_name)/js/tests" \
|
||||
--exclude="../$(app_name)/js/test" \
|
||||
--exclude="../$(app_name)/js/*.log" \
|
||||
--exclude="../$(app_name)/js/package.json" \
|
||||
--exclude="../$(app_name)/js/bower.json" \
|
||||
--exclude="../$(app_name)/js/karma.*" \
|
||||
--exclude="../$(app_name)/js/protractor.*" \
|
||||
--exclude="../$(app_name)/package.json" \
|
||||
--exclude="../$(app_name)/bower.json" \
|
||||
--exclude="../$(app_name)/karma.*" \
|
||||
--exclude="../$(app_name)/protractor\.*" \
|
||||
--exclude="../$(app_name)/.*" \
|
||||
--exclude="../$(app_name)/js/.*" \
|
||||
|
||||
# Command for running JS and PHP tests. Works for package.json files in the js/
|
||||
# and root directory. If phpunit is not installed systemwide, a copy is fetched
|
||||
# from the internet
|
||||
.PHONY: test
|
||||
test:
|
||||
ifneq (,$(wildcard $(CURDIR)/js/package.json))
|
||||
cd js && $(npm) run test
|
||||
endif
|
||||
ifneq (,$(wildcard $(CURDIR)/package.json))
|
||||
$(npm) run test
|
||||
endif
|
||||
ifeq (, $(shell which phpunit 2> /dev/null))
|
||||
@echo "No phpunit command available, downloading a copy from the web"
|
||||
mkdir -p $(build_tools_directory)
|
||||
curl -sSL https://phar.phpunit.de/phpunit.phar -o $(build_tools_directory)/phpunit.phar
|
||||
php $(build_tools_directory)/phpunit.phar -c phpunit.xml
|
||||
php $(build_tools_directory)/phpunit.phar -c phpunit.integration.xml
|
||||
else
|
||||
phpunit -c phpunit.xml --coverage-clover build/php-unit.clover
|
||||
phpunit -c phpunit.integration.xml --coverage-clover build/php-unit.clover
|
||||
endif
|
60
README.md
|
@ -1,26 +1,52 @@
|
|||
# Maps
|
||||
[![Build Status](https://travis-ci.org/owncloud/maps.svg?branch=master)](https://travis-ci.org/owncloud/maps)
|
||||
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/owncloud/maps/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/owncloud/maps/?branch=master)
|
||||
[![Code Coverage](https://scrutinizer-ci.com/g/owncloud/maps/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/owncloud/maps/?branch=master)
|
||||
Place this app in **nextcloud/apps/**
|
||||
|
||||
Place this app in **owncloud/apps/**
|
||||
## Building the app
|
||||
|
||||
## What is this?
|
||||
This app displays openstreetmap layers including POIs.
|
||||
It supports searching for cities and addresses.
|
||||
Additionally it is possible to display (and search for) contacts and gpx/gps tracks (WIP, see issues for more information).
|
||||
The app can be built by using the provided Makefile by running:
|
||||
|
||||
## Screenshots
|
||||
![start](screenshots/start.png)
|
||||
![contacts](screenshots/contacts.png)
|
||||
make
|
||||
|
||||
This requires the following things to be present:
|
||||
* make
|
||||
* which
|
||||
* tar: for building the archive
|
||||
* curl: used if phpunit and composer are not installed to fetch them from the web
|
||||
* npm: for building and testing everything JS, only required if a package.json is placed inside the **js/** folder
|
||||
|
||||
The make command will install or update Composer dependencies if a composer.json is present and also **npm run build** if a package.json is present in the **js/** folder. The npm **build** script should use local paths for build systems and package managers, so people that simply want to build the app won't need to install npm libraries globally, e.g.:
|
||||
|
||||
**package.json**:
|
||||
```json
|
||||
"scripts": {
|
||||
"test": "node node_modules/gulp-cli/bin/gulp.js karma",
|
||||
"prebuild": "npm install && node_modules/bower/bin/bower install && node_modules/bower/bin/bower update",
|
||||
"build": "node node_modules/gulp-cli/bin/gulp.js"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Publish to App Store
|
||||
|
||||
First get an account for the [App Store](http://apps.nextcloud.com/) then run:
|
||||
|
||||
make && make appstore
|
||||
|
||||
The archive is located in build/artifacts/appstore and can then be uploaded to the App Store.
|
||||
|
||||
## Running tests
|
||||
After [Installing PHPUnit](http://phpunit.de/getting-started.html) run:
|
||||
You can use the provided Makefile to run all tests by using:
|
||||
|
||||
phpunit
|
||||
make test
|
||||
|
||||
##Sources
|
||||
- OpenLayers https://github.com/openlayers/openlayers
|
||||
This will run the PHP unit and integration tests and if a package.json is present in the **js/** folder will execute **npm run test**
|
||||
|
||||
##Support
|
||||
Only MYSQL is supported
|
||||
Of course you can also install [PHPUnit](http://phpunit.de/getting-started.html) and use the configurations directly:
|
||||
|
||||
phpunit -c phpunit.xml
|
||||
|
||||
or:
|
||||
|
||||
phpunit -c phpunit.integration.xml
|
||||
|
||||
for integration tests
|
||||
|
|
|
@ -26,7 +26,7 @@ $l = \OC::$server->getL10N('maps');
|
|||
|
||||
// the icon that will be shown in the navigation
|
||||
// this file needs to exist in img/
|
||||
'icon' => \OC::$server->getURLGenerator()->imagePath('maps', 'maps.svg'),
|
||||
'icon' => \OC::$server->getURLGenerator()->imagePath('maps', 'app.svg'),
|
||||
|
||||
// the title of your application. This will be used in the
|
||||
// navigation or on the settings page of your app
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Sander Brand <brantje@gmail.com>, Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.com>
|
||||
* @copyright Sander Brand 2014, Vinzenz Rosenkranz 2016
|
||||
* @copyright Sander Brand 2014, Vinzenz Rosenkranz 2016, 2017
|
||||
*/
|
||||
|
||||
namespace OCA\Maps\AppInfo;
|
||||
|
@ -14,91 +14,13 @@ namespace OCA\Maps\AppInfo;
|
|||
|
||||
use OC\AppFramework\Utility\SimpleContainer;
|
||||
use \OCP\AppFramework\App;
|
||||
use \OCA\Maps\Db\CacheManager;
|
||||
use \OCA\Maps\Db\DeviceMapper;
|
||||
use \OCA\Maps\Db\LocationMapper;
|
||||
use \OCA\Maps\Db\FavoriteMapper;
|
||||
use \OCA\Maps\Db\ApiKeyMapper;
|
||||
use \OCA\Maps\Controller\PageController;
|
||||
use \OCA\Maps\Controller\LocationController;
|
||||
use \OCA\Maps\Controller\FavoriteController;
|
||||
use OCA\Maps\Controller\PageController;
|
||||
|
||||
|
||||
class Application extends App {
|
||||
|
||||
|
||||
public function __construct (array $urlParams=array()) {
|
||||
parent::__construct('maps', $urlParams);
|
||||
|
||||
$container = $this->getContainer();
|
||||
|
||||
/**
|
||||
* Controllers
|
||||
*/
|
||||
$container->registerService('PageController', function($c) {
|
||||
/** @var SimpleContainer $c */
|
||||
return new PageController(
|
||||
$c->query('AppName'),
|
||||
$c->query('Request'),
|
||||
$c->query('UserId'),
|
||||
$c->query('CacheManager'),
|
||||
$c->query('DeviceMapper'),
|
||||
$c->query('ApiKeyMapper')
|
||||
);
|
||||
});
|
||||
$container->registerService('LocationController', function($c) {
|
||||
/** @var SimpleContainer $c */
|
||||
return new LocationController(
|
||||
$c->query('AppName'),
|
||||
$c->query('Request'),
|
||||
$c->query('LocationMapper'),
|
||||
$c->query('DeviceMapper'),
|
||||
$c->query('UserId')
|
||||
);
|
||||
});
|
||||
$container->registerService('FavoriteController', function($c) {
|
||||
/** @var SimpleContainer $c */
|
||||
return new FavoriteController(
|
||||
$c->query('AppName'),
|
||||
$c->query('Request'),
|
||||
$c->query('FavoriteMapper'),
|
||||
$c->query('UserId')
|
||||
);
|
||||
});
|
||||
|
||||
$server = $container->getServer();
|
||||
$container->registerService('CacheManager', function($c) use ($server) {
|
||||
/** @var SimpleContainer $c */
|
||||
return new CacheManager(
|
||||
$server->getDatabaseConnection()
|
||||
);
|
||||
});
|
||||
$container->registerService('LocationMapper', function($c) use ($server) {
|
||||
/** @var SimpleContainer $c */
|
||||
return new LocationMapper(
|
||||
$server->getDb()
|
||||
);
|
||||
});
|
||||
$container->registerService('DeviceMapper', function($c) use ($server) {
|
||||
/** @var SimpleContainer $c */
|
||||
return new DeviceMapper(
|
||||
$server->getDb()
|
||||
);
|
||||
});
|
||||
$container->registerService('FavoriteMapper', function($c) use ($server) {
|
||||
/** @var SimpleContainer $c */
|
||||
return new FavoriteMapper(
|
||||
$server->getDb()
|
||||
);
|
||||
});
|
||||
$container->registerService('ApiKeyMapper', function($c) use ($server) {
|
||||
/** @var SimpleContainer $c */
|
||||
return new ApiKeyMapper(
|
||||
$server->getDb()
|
||||
);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -4,137 +4,6 @@
|
|||
<create>true</create>
|
||||
<overwrite>true</overwrite>
|
||||
<charset>utf8</charset>
|
||||
<table>
|
||||
<name>*dbprefix*maps_adress_cache</name>
|
||||
<declaration>
|
||||
<field>
|
||||
<name>adres_hash</name>
|
||||
<type>text</type>
|
||||
<length>64</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>serialized</name>
|
||||
<type>clob</type>
|
||||
<notnull>true</notnull>
|
||||
</field>
|
||||
<index>
|
||||
<name>cache_adres_hash</name>
|
||||
<field>
|
||||
<name>adres_hash</name>
|
||||
</field>
|
||||
</index>
|
||||
</declaration>
|
||||
</table>
|
||||
<table>
|
||||
<name>*dbprefix*maps_locations</name>
|
||||
<declaration>
|
||||
<field>
|
||||
<name>id</name>
|
||||
<type>integer</type>
|
||||
<notnull>true</notnull>
|
||||
<autoincrement>1</autoincrement>
|
||||
<length>41</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>device_hash</name>
|
||||
<type>text</type>
|
||||
<length>64</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>lat</name>
|
||||
<type>text</type>
|
||||
<length>64</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>lng</name>
|
||||
<type>text</type>
|
||||
<length>64</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>timestamp</name>
|
||||
<type>text</type>
|
||||
<length>64</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>hdop</name>
|
||||
<type>text</type>
|
||||
<length>64</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>altitude</name>
|
||||
<type>text</type>
|
||||
<length>64</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>speed</name>
|
||||
<type>text</type>
|
||||
<length>64</length>
|
||||
</field>
|
||||
<index>
|
||||
<name>maps_location_device_hash</name>
|
||||
<field>
|
||||
<name>device_hash</name>
|
||||
</field>
|
||||
</index>
|
||||
<index>
|
||||
<name>maps_location_id</name>
|
||||
<field>
|
||||
<name>id</name>
|
||||
</field>
|
||||
</index>
|
||||
</declaration>
|
||||
</table>
|
||||
<table>
|
||||
<name>*dbprefix*maps_location_track_users</name>
|
||||
<declaration>
|
||||
<field>
|
||||
<name>id</name>
|
||||
<type>integer</type>
|
||||
<default>0</default>
|
||||
<notnull>true</notnull>
|
||||
<autoincrement>1</autoincrement>
|
||||
<length>41</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>user_id</name>
|
||||
<type>text</type>
|
||||
<length>64</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>name</name>
|
||||
<type>text</type>
|
||||
<length>64</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>hash</name>
|
||||
<type>text</type>
|
||||
<length>64</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>created</name>
|
||||
<type>text</type>
|
||||
<length>64</length>
|
||||
</field>
|
||||
<index>
|
||||
<name>maps_user_id</name>
|
||||
<field>
|
||||
<name>user_id</name>
|
||||
</field>
|
||||
</index>
|
||||
<index>
|
||||
<name>maps_location_track_user_id</name>
|
||||
<field>
|
||||
<name>user_id</name>
|
||||
</field>
|
||||
</index>
|
||||
<index>
|
||||
<name>maps_location_track_id</name>
|
||||
<field>
|
||||
<name>id</name>
|
||||
</field>
|
||||
</index>
|
||||
</declaration>
|
||||
</table>
|
||||
<table>
|
||||
<name>*dbprefix*maps_favorites</name>
|
||||
<declaration>
|
||||
|
|
|
@ -1,13 +1,23 @@
|
|||
<?xml version="1.0"?>
|
||||
<info>
|
||||
<info xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
|
||||
<id>maps</id>
|
||||
<name>Maps</name>
|
||||
<description>A simple maps app</description>
|
||||
<licence>AGPL</licence>
|
||||
<author>Vinzenz Rosenkranz, Hendrik Leppelsack, Sander Brand, Jan-Christoph Borchardt</author>
|
||||
<version>0.0.6</version>
|
||||
<summary>Simple maps app for Nextcloud</summary>
|
||||
<description><![CDATA[Simple maps app to display a leaflet map.]]></description>
|
||||
<licence>agpl</licence>
|
||||
<author mail="vinzenz.rosenkranz@gmail.com" >Vinzenz Rosenkranz</author>
|
||||
<version>0.0.1</version>
|
||||
<namespace>Maps</namespace>
|
||||
<category>multimedia</category>
|
||||
<bugs>https://github.com/nextcloud/maps/issues</bugs>
|
||||
<dependencies>
|
||||
<owncloud min-version="8.2" max-version="9.1"/>
|
||||
<nextcloud min-version="9" max-version="11"/>
|
||||
<nextcloud min-version="12" max-version="12"/>
|
||||
</dependencies>
|
||||
<navigations>
|
||||
<navigation>
|
||||
<name>Maps</name>
|
||||
<route>maps.page.index</route>
|
||||
</navigation>
|
||||
</navigations>
|
||||
</info>
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0"?>
|
||||
<info>
|
||||
<id>maps</id>
|
||||
<name>Maps</name>
|
||||
<description>A simple maps app</description>
|
||||
<licence>AGPL</licence>
|
||||
<author>Vinzenz Rosenkranz, Hendrik Leppelsack, Sander Brand, Jan-Christoph Borchardt</author>
|
||||
<version>0.0.6</version>
|
||||
<dependencies>
|
||||
<owncloud min-version="8.2" max-version="9.1"/>
|
||||
<nextcloud min-version="9" max-version="11"/>
|
||||
</dependencies>
|
||||
</info>
|
|
@ -1,46 +1,15 @@
|
|||
<?php
|
||||
/**
|
||||
* ownCloud - maps
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Sander Brand <brantje@gmail.com>, Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.com>
|
||||
* @copyright Sander Brand 2014, Vinzenz Rosenkranz 2016
|
||||
*/
|
||||
|
||||
namespace OCA\Maps\AppInfo;
|
||||
|
||||
/**
|
||||
* Create your routes in here. The name is the lowercase name of the controller
|
||||
* without the controller part, the stuff after the hash is the method.
|
||||
* e.g. page#index -> PageController->index()
|
||||
* e.g. page#index -> OCA\Maps\Controller\PageController->index()
|
||||
*
|
||||
* The controller class has to be registered in the application.php file since
|
||||
* it's instantiated in there
|
||||
*/
|
||||
$application = new Application();
|
||||
|
||||
$application->registerRoutes($this, array('routes' => array(
|
||||
array('name' => 'page#index', 'url' => '/', 'verb' => 'GET'),
|
||||
array('name' => 'page#do_proxy', 'url' => '/router', 'verb' => 'GET'),
|
||||
array('name' => 'page#getlayer', 'url' => '/layer', 'verb' => 'GET'),
|
||||
array('name' => 'page#adresslookup', 'url' => '/adresslookup', 'verb' => 'GET'),
|
||||
array('name' => 'page#geodecode', 'url' => '/geodecode', 'verb' => 'GET'),
|
||||
array('name' => 'page#search', 'url' => '/search', 'verb' => 'GET'),
|
||||
|
||||
array('name' => 'location#update', 'url' => '/api/1.0/location/update', 'verb' => 'GET'),
|
||||
array('name' => 'location#add_device', 'url' => '/api/1.0/location/adddevice', 'verb' => 'POST'),
|
||||
array('name' => 'location#remove_device', 'url' => '/api/1.0/location/removedevice', 'verb' => 'POST'),
|
||||
array('name' => 'location#load_devices', 'url' => '/api/1.0/location/loadDevices', 'verb' => 'GET'),
|
||||
array('name' => 'location#load_locations', 'url' => '/api/1.0/location/loadLocations', 'verb' => 'GET'),
|
||||
array('name' => 'favorite#add_favorite', 'url' => '/api/1.0/favorite/addToFavorites', 'verb' => 'POST'),
|
||||
array('name' => 'favorite#get_favorites', 'url' => '/api/1.0/favorite/getFavorites', 'verb' => 'POST'),
|
||||
array('name' => 'favorite#remove_favorite', 'url' => '/api/1.0/favorite/removeFromFavorites', 'verb' => 'POST'),
|
||||
array('name' => 'favorite#update_favorite', 'url' => '/api/1.0/favorite/updateFavorite', 'verb' => 'POST'),
|
||||
array('name' => 'favorite#get_favorites_by_name', 'url' => '/api/1.0/favorite/getFavoritesByName', 'verb' => 'POST'),
|
||||
|
||||
array('name' => 'apikey#get_key', 'url' => '/api/1.0/apikey/getKey', 'verb' => 'POST'),
|
||||
array('name' => 'apikey#add_key', 'url' => '/api/1.0/apikey/addKey', 'verb' => 'POST'),
|
||||
|
||||
)));
|
||||
return [
|
||||
'routes' => [
|
||||
['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],
|
||||
['name' => 'page#do_echo', 'url' => '/echo', 'verb' => 'POST'],
|
||||
]
|
||||
];
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
/**
|
||||
* ownCloud - maps
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Sander Brand <brantje@gmail.com>, Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.com>
|
||||
* @copyright Sander Brand 2014, Vinzenz Rosenkranz 2016, 2017
|
||||
*/
|
||||
|
||||
namespace OCA\Maps\AppInfo;
|
||||
|
||||
/**
|
||||
* Create your routes in here. The name is the lowercase name of the controller
|
||||
* without the controller part, the stuff after the hash is the method.
|
||||
* e.g. page#index -> PageController->index()
|
||||
*
|
||||
* The controller class has to be registered in the application.php file since
|
||||
* it's instantiated in there
|
||||
*/
|
||||
$application = new Application();
|
||||
|
||||
$application->registerRoutes($this, array('routes' => array(
|
||||
array('name' => 'page#index', 'url' => '/', 'verb' => 'GET'),
|
||||
array('name' => 'page#do_proxy', 'url' => '/router', 'verb' => 'GET'),
|
||||
array('name' => 'page#getlayer', 'url' => '/layer', 'verb' => 'GET'),
|
||||
array('name' => 'page#adresslookup', 'url' => '/adresslookup', 'verb' => 'GET'),
|
||||
array('name' => 'page#geodecode', 'url' => '/geodecode', 'verb' => 'GET'),
|
||||
array('name' => 'page#search', 'url' => '/search', 'verb' => 'GET'),
|
||||
|
||||
array('name' => 'favorite#add_favorite', 'url' => '/api/1.0/favorite/addToFavorites', 'verb' => 'POST'),
|
||||
array('name' => 'favorite#get_favorites', 'url' => '/api/1.0/favorite/getFavorites', 'verb' => 'POST'),
|
||||
array('name' => 'favorite#remove_favorite', 'url' => '/api/1.0/favorite/removeFromFavorites', 'verb' => 'POST'),
|
||||
array('name' => 'favorite#update_favorite', 'url' => '/api/1.0/favorite/updateFavorite', 'verb' => 'POST'),
|
||||
array('name' => 'favorite#get_favorites_by_name', 'url' => '/api/1.0/favorite/getFavoritesByName', 'verb' => 'POST'),
|
||||
|
||||
array('name' => 'apikey#get_key', 'url' => '/api/1.0/apikey/getKey', 'verb' => 'POST'),
|
||||
array('name' => 'apikey#add_key', 'url' => '/api/1.0/apikey/addKey', 'verb' => 'POST'),
|
||||
|
||||
)));
|
21
bower.json
|
@ -1,11 +1,20 @@
|
|||
{
|
||||
"name": "owncloud-maps",
|
||||
"description": "",
|
||||
"moduleType": [],
|
||||
"license": "AGPL",
|
||||
"homepage": "",
|
||||
"name": "contacts",
|
||||
"authors": [
|
||||
"Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.com>"
|
||||
],
|
||||
"description": "Maps app",
|
||||
"license": "AGPL-3.0",
|
||||
"homepage": "https://github.com/nextcloud/maps",
|
||||
"private": true,
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"tests"
|
||||
],
|
||||
"dependencies": {
|
||||
"maki": "mapbox/maki#^0.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* ownCloud - maps
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.com>
|
||||
* @copyright Vinzenz Rosenkranz 2015, 2016
|
||||
*/
|
||||
|
||||
namespace OCA\Maps\Controller;
|
||||
|
||||
use OCA\Maps\Db\ApiKey;
|
||||
use OCA\Maps\Db\ApiKeyMapper;
|
||||
use \OCP\IRequest;
|
||||
use \OCP\AppFramework\Http\JSONResponse;
|
||||
use \OCP\AppFramework\ApiController;
|
||||
|
||||
|
||||
class ApiKeyController extends ApiController {
|
||||
|
||||
private $userId;
|
||||
private $apiKeyMapper;
|
||||
|
||||
public function __construct($appName, IRequest $request, ApiKeyMapper $apiKeyMapper, $userId) {
|
||||
parent::__construct($appName, $request);
|
||||
$this->apiKeyMapper = $apiKeyMapper;
|
||||
$this->userId = $userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @param $key string
|
||||
* @param $id int
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function updateKey($key, $id) {
|
||||
|
||||
$apikey = new ApiKey();
|
||||
$apikey->setId($id);
|
||||
$apikey->setApiKey($key);
|
||||
|
||||
/* Only save apiKey if it exists in db */
|
||||
try {
|
||||
$this->apiKeyMapper->find($id);
|
||||
return new JSONResponse($this->apiKeyMapper->update($apikey));
|
||||
} catch(\OCP\AppFramework\Db\DoesNotExistException $e) {
|
||||
return new JSONResponse([
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @param $key string
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function addKey($key){
|
||||
$apikey = new ApiKey();
|
||||
$apikey->setApiKey($key);
|
||||
$apikey->setUserId($this->userId);
|
||||
|
||||
/* @var $apikey ApiKey */
|
||||
$apikey = $this->apiKeyMapper->insert($apikey);
|
||||
|
||||
$response = array('id'=> $apikey->getId());
|
||||
return new JSONResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function getKey(){
|
||||
$apikey = new ApiKey();
|
||||
try {
|
||||
$apikey = $this->apiKeyMapper->findByUser($this->userId);
|
||||
} catch(\OCP\AppFramework\Db\DoesNotExistException $e) {
|
||||
$apikey->setUserId($this->userId);
|
||||
}
|
||||
return new JSONResponse($apikey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @param $id int
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function removeApiKey($id){
|
||||
$apikey = $this->apiKeyMapper->find($id);
|
||||
if($apikey->userId == $this->userId) {
|
||||
$this->apiKeyMapper->delete($apikey);
|
||||
}
|
||||
return new JSONResponse();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* ownCloud - maps
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.com>
|
||||
* @copyright Vinzenz Rosenkranz 2015, 2016
|
||||
*/
|
||||
|
||||
namespace OCA\Maps\Controller;
|
||||
|
||||
use OCA\Maps\Db\Favorite;
|
||||
use OCA\Maps\Db\FavoriteMapper;
|
||||
use \OCP\IRequest;
|
||||
use \OCP\AppFramework\Http\JSONResponse;
|
||||
use \OCP\AppFramework\ApiController;
|
||||
|
||||
|
||||
class FavoriteController extends ApiController {
|
||||
|
||||
private $userId;
|
||||
private $favoriteMapper;
|
||||
|
||||
public function __construct($appName, IRequest $request, FavoriteMapper $favoriteMapper, $userId) {
|
||||
parent::__construct($appName, $request);
|
||||
$this->favoriteMapper = $favoriteMapper;
|
||||
$this->userId = $userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @param $name string
|
||||
* @param $id int
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function updateFavorite($name, $id) {
|
||||
|
||||
$favorite = new Favorite();
|
||||
$favorite->setId($id);
|
||||
$favorite->setName($name);
|
||||
$favorite->setTimestamp(time());
|
||||
|
||||
/* Only save favorite if it exists in db */
|
||||
try {
|
||||
$this->favoriteMapper->find($id);
|
||||
return new JSONResponse($this->favoriteMapper->update($favorite));
|
||||
} catch(\OCP\AppFramework\Db\DoesNotExistException $e) {
|
||||
return new JSONResponse([
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @param $lat float
|
||||
* @param $lng float
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function addFavorite($lat, $lng, $name = null){
|
||||
$favorite = new Favorite();
|
||||
$favorite->setName($name);
|
||||
$favorite->setTimestamp(time());
|
||||
$favorite->setUserId($this->userId);
|
||||
$favorite->setLat($lat);
|
||||
$favorite->setLng($lng);
|
||||
|
||||
/* @var $favorite Favorite */
|
||||
$favorite = $this->favoriteMapper->insert($favorite);
|
||||
|
||||
$response = array('id'=> $favorite->getId());
|
||||
return new JSONResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function getFavorites(){
|
||||
$favorites = $this->favoriteMapper->findAll();
|
||||
return new JSONResponse($favorites);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @param $name string
|
||||
*
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function getFavoritesByName($name){
|
||||
$favorites = $this->favoriteMapper->findByName($name);
|
||||
return new JSONResponse($favorites);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @param $id int
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function removeFavorite($id){
|
||||
$favorite = $this->favoriteMapper->find($id);
|
||||
if($favorite->userId == $this->userId) {
|
||||
$this->favoriteMapper->delete($favorite);
|
||||
}
|
||||
return new JSONResponse();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,145 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* ownCloud - maps
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Sander Brand <brantje@gmail.com>, Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.com>
|
||||
* @copyright Sander Brand 2014, Vinzenz Rosenkranz 2016
|
||||
*/
|
||||
|
||||
namespace OCA\Maps\Controller;
|
||||
|
||||
use OCA\Maps\Db\Device;
|
||||
use OCA\Maps\Db\DeviceMapper;
|
||||
use OCA\Maps\Db\Location;
|
||||
use OCA\Maps\Db\LocationMapper;
|
||||
use \OCP\IRequest;
|
||||
use \OCP\AppFramework\Http\JSONResponse;
|
||||
use \OCP\AppFramework\ApiController;
|
||||
|
||||
|
||||
class LocationController extends ApiController {
|
||||
|
||||
private $userId;
|
||||
private $locationMapper;
|
||||
private $deviceMapper;
|
||||
|
||||
public function __construct($appName, IRequest $request, LocationMapper $locationMapper, DeviceMapper $deviceMapper, $userId) {
|
||||
parent::__construct($appName, $request);
|
||||
$this->locationMapper = $locationMapper;
|
||||
$this->deviceMapper = $deviceMapper;
|
||||
$this->userId = $userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @param $lat int
|
||||
* @param $lon int
|
||||
* @param $timestamp string
|
||||
* @param $hdop string
|
||||
* @param $altitude int
|
||||
* @param $speed int
|
||||
* @param $hash string
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function update($lat, $lon, $timestamp, $hdop, $altitude, $speed, $hash) {
|
||||
|
||||
$location = new Location();
|
||||
$location->lat = $lat;
|
||||
$location->lng = $lon;
|
||||
if((string)(float)$timestamp === $timestamp) {
|
||||
if(strtotime(date('d-m-Y H:i:s',$timestamp)) === (int)$timestamp) {
|
||||
$location->timestamp = (int)$timestamp;
|
||||
} elseif(strtotime(date('d-m-Y H:i:s',$timestamp/1000)) === (int)floor($timestamp/1000)) {
|
||||
$location->timestamp = (int)floor($timestamp/1000);
|
||||
}
|
||||
} else {
|
||||
$location->timestamp = strtotime($timestamp);
|
||||
}
|
||||
$location->hdop = $hdop;
|
||||
$location->altitude = $altitude;
|
||||
$location->speed = $speed;
|
||||
$location->deviceHash = $hash;
|
||||
|
||||
/* Only save location if hash exists in db */
|
||||
try {
|
||||
$this->deviceMapper->findByHash($hash);
|
||||
return new JSONResponse($this->locationMapper->insert($location));
|
||||
} catch(\OCP\AppFramework\Db\DoesNotExistException $e) {
|
||||
return new JSONResponse([
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @param $name string
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function addDevice($name){
|
||||
$device = new Device();
|
||||
$device->name = $name;
|
||||
$device->hash = uniqid();
|
||||
$device->created = time();
|
||||
$device->userId = $this->userId;
|
||||
|
||||
/* @var $device Device */
|
||||
$device = $this->deviceMapper->insert($device);
|
||||
|
||||
$response = array('id'=> $device->getId(),'hash'=>$device->hash);
|
||||
return new JSONResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function loadDevices(){
|
||||
$response = $this->deviceMapper->findAll($this->userId);
|
||||
return new JSONResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @param $devices string comma separated list of device ids
|
||||
* @param $from string
|
||||
* @param $till string
|
||||
* @param $limit int
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function loadLocations($devices, $from, $till, $limit){
|
||||
$deviceIds = explode(',',$devices);
|
||||
$from = ($from) ? strtotime($from) : null;
|
||||
$till = ($till !== '') ? strtotime($till) : strtotime('now');
|
||||
$limit = ($limit !== '') ? (int) $limit : 2000;
|
||||
$response = array();
|
||||
foreach($deviceIds as $deviceId){
|
||||
$hash = $this->deviceMapper->findById($deviceId)->hash;
|
||||
$response[$deviceId] = $this->locationMapper->findBetween($hash, $from, $till, $limit);
|
||||
}
|
||||
return new JSONResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @param $deviceId string
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function removeDevice($deviceId){
|
||||
/* @var $device Device */
|
||||
$device = $this->deviceMapper->findById($deviceId);
|
||||
if($device->userId == $this->userId) {
|
||||
$this->deviceMapper->delete($device);
|
||||
}
|
||||
return new JSONResponse();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,39 +1,26 @@
|
|||
<?php
|
||||
/**
|
||||
* ownCloud - maps
|
||||
* Nextcloud - maps
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Sander Brand <brantje@gmail.com>, Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.com>
|
||||
* @copyright Sander Brand 2014, Vinzenz Rosenkranz 2016
|
||||
* @author Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.com>
|
||||
* @copyright Vinzenz Rosenkranz 2017
|
||||
*/
|
||||
|
||||
namespace OCA\Maps\Controller;
|
||||
|
||||
use \OCA\Maps\Db\ApiKey;
|
||||
use \OCA\Maps\Db\DeviceMapper;
|
||||
use \OCA\Maps\Db\ApiKeyMapper;
|
||||
use \OCP\IRequest;
|
||||
use \OCP\AppFramework\Http\TemplateResponse;
|
||||
use \OCP\AppFramework\Controller;
|
||||
use \OCA\Maps\Db\CacheManager;
|
||||
|
||||
class PageController extends Controller {
|
||||
|
||||
private $userId;
|
||||
private $cacheManager;
|
||||
private $deviceMapper;
|
||||
private $apiKeyMapper;
|
||||
public function __construct($appName, IRequest $request, $userId,
|
||||
CacheManager $cacheManager,
|
||||
DeviceMapper $deviceMapper,
|
||||
ApiKeyMapper $apiKeyMapper) {
|
||||
|
||||
public function __construct($appName, IRequest $request, $userId) {
|
||||
parent::__construct($appName, $request);
|
||||
$this -> userId = $userId;
|
||||
$this -> cacheManager = $cacheManager;
|
||||
$this -> deviceMapper = $deviceMapper;
|
||||
$this -> apiKeyMapper = $apiKeyMapper;
|
||||
$this->userId = $userId;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -47,213 +34,14 @@ class PageController extends Controller {
|
|||
* @NoCSRFRequired
|
||||
*/
|
||||
public function index() {
|
||||
|
||||
$params = array('user' => $this -> userId,'devices'=>$this->deviceMapper->findAll($this->userId));
|
||||
$response = new TemplateResponse('maps', 'main', $params);
|
||||
$params = array('user' => $this->userId);
|
||||
$response = new TemplateResponse('maps', 'index', $params);
|
||||
if (class_exists('OCP\AppFramework\Http\ContentSecurityPolicy')) {
|
||||
$csp = new \OCP\AppFramework\Http\ContentSecurityPolicy();
|
||||
// map tiles
|
||||
$csp->addAllowedImageDomain('http://*.mqcdn.com');
|
||||
// marker icons
|
||||
$csp->addAllowedImageDomain('https://api.tiles.mapbox.com');
|
||||
// inline images
|
||||
$csp->addAllowedImageDomain('data:');
|
||||
//overpasslayer api
|
||||
$csp->addAllowedConnectDomain('http://overpass-api.de/api/interpreter?');
|
||||
$tmpkey = new ApiKey();
|
||||
try {
|
||||
$tmpkey = $this->apiKeyMapper->findByUser($this->userId);
|
||||
} catch(\OCP\AppFramework\Db\DoesNotExistException $e) {
|
||||
$tmpkey->setUserId($this->userId);
|
||||
}
|
||||
if($tmpkey->apiKey != null && strlen($tmpkey->apiKey) > 0) {
|
||||
// mapzen geocoder
|
||||
$csp->addAllowedConnectDomain('http://search.mapzen.com/v1/search?');
|
||||
$csp->addAllowedConnectDomain('http://search.mapzen.com/v1/reverse?');
|
||||
} else {
|
||||
// nominatim geocoder
|
||||
$csp->addAllowedScriptDomain('http://nominatim.openstreetmap.org/search?q=*');
|
||||
$csp->addAllowedScriptDomain('http://nominatim.openstreetmap.org/reverse');
|
||||
$csp->addAllowedConnectDomain('http://router.project-osrm.org');
|
||||
}
|
||||
$response->setContentSecurityPolicy($csp);
|
||||
}
|
||||
return $response;
|
||||
// templates/main.php
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an layer
|
||||
* @NoAdminRequired
|
||||
* @NoCSRFRequired
|
||||
*/
|
||||
public function getlayer() {
|
||||
$layer = ($this -> params('layer')) ? $this -> params('layer') : null;
|
||||
if ($layer === "contacts") {
|
||||
if (\OCP\App::isEnabled('contacts')) {
|
||||
|
||||
} else {
|
||||
OCP\Util::writeLog('maps', "App contacts missing for Maps", \OCP\Util::WARN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply method that posts back the payload of the request
|
||||
* @NoAdminRequired
|
||||
* @NoCSRFRequired
|
||||
*/
|
||||
public function doProxy($echo) {
|
||||
$url = ($this -> params('url')) ? $this -> params('url') : '';
|
||||
$allowedHosts = array('overpass.osm.rambler.ru', 'overpass-api.de', 'dev.virtualearth.net', 'router.project-osrm.org', 'nominatim.openstreetmap.org', 'maps.googleapis.com');
|
||||
$parseUrl = parse_url($url);
|
||||
|
||||
if (in_array($parseUrl['host'], $allowedHosts)) {
|
||||
header('Content-Type: application/javascript');
|
||||
$split = explode('url=', $_SERVER['REQUEST_URI']);
|
||||
echo $this -> getURL($split[1]);
|
||||
}
|
||||
die();
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply method that posts back the payload of the request
|
||||
* @NoAdminRequired
|
||||
* @NoCSRFRequired
|
||||
*/
|
||||
public function search() {
|
||||
$cm = \OC::$server -> getContactsManager();
|
||||
$kw = $this -> params('search');
|
||||
$bbox = $this -> params('bbox');
|
||||
$response = array('contacts'=>array(),'nodes'=>array(),'addresses'=>array());
|
||||
|
||||
$contacts = $cm -> search($kw, array('FN', 'ADR'));
|
||||
foreach ($contacts as $r) {
|
||||
$data = array();
|
||||
$contact = $r;
|
||||
for($i=0; $i<count($r['ADR']); $i++){
|
||||
$lookupAdr = implode(',', array_filter($r['ADR'][$i]));
|
||||
$lookup = $this -> doAdresslookup($lookupAdr);
|
||||
$contact ['location'][] = $lookup[0];
|
||||
}
|
||||
array_push($response['contacts'],$contact);
|
||||
}
|
||||
$response['nodes'] = $this->bboxSearch($kw, $bbox);
|
||||
$addresses = $this->doAdresslookup(urlencode($kw));
|
||||
foreach($addresses as $address){
|
||||
array_push($response['addresses'],$address);
|
||||
if($address->osm_type === "node"){
|
||||
}
|
||||
}
|
||||
//$response['addresses'] = (array)($this->doAdresslookup($kw));
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply method that posts back the payload of the request
|
||||
* @NoAdminRequired
|
||||
* @NoCSRFRequired
|
||||
*/
|
||||
public function geodecode(){
|
||||
$lat = $this->params('lat');
|
||||
$lng = $this->params('lng');
|
||||
$zoom = $this->params('zoom');
|
||||
|
||||
$hash = md5($lat.','.$lng.'@'.$zoom);
|
||||
|
||||
$checkCache = $this -> checkGeoCache($hash);
|
||||
if(!$checkCache){
|
||||
$url = 'http://nominatim.openstreetmap.org/reverse/?format=json&email=brantje@gmail.com&lat='.$lat.'&lng='. $lng.'&zoom=67108864';
|
||||
$response = $this->getURL($url,false);
|
||||
if($response){
|
||||
$this -> cacheManager -> insert($hash, $response);
|
||||
}
|
||||
} else {
|
||||
$response = $checkCache;
|
||||
}
|
||||
echo $response;
|
||||
die();
|
||||
}
|
||||
/**
|
||||
* Simply method that posts back the payload of the request
|
||||
* @NoAdminRequired
|
||||
* @NoCSRFRequired
|
||||
*/
|
||||
public function adresslookup() {
|
||||
//
|
||||
$street = ($this -> params('street')) ? $this -> params('street') : '';
|
||||
$city = ($this -> params('city')) ? $this -> params('city') : '';
|
||||
$country = ($this -> params('country')) ? $this -> params('country') : '';
|
||||
|
||||
$q = urlencode($street . ',' . $city . ',' . $country);
|
||||
$r = (array) $this -> doAdresslookup($q);
|
||||
echo json_encode($r[0]);
|
||||
die();
|
||||
}
|
||||
|
||||
private function bboxSearch($q,$bbox){
|
||||
$apiUrl = 'http://nominatim.openstreetmap.org/search?format=json&limit=100&q=' . $q . '&viewbox='.$bbox.'&bounded=1';
|
||||
//echo $apiUrl;
|
||||
$r = $this -> getURL($apiUrl, false);
|
||||
$s = (array)json_decode($r);
|
||||
return $s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $q
|
||||
*/
|
||||
private function doAdresslookup($q) {
|
||||
|
||||
$q = str_replace(" ", "+", $q);
|
||||
$geohash = md5($q);
|
||||
$checkCache = $this -> checkGeoCache($geohash);
|
||||
if (!$checkCache) {
|
||||
//$apiUrl = 'https://maps.googleapis.com/maps/api/geocode/json?address='. str_replace(' ','+',$q) .'&key=AIzaSyAIHAIBv_uPKZgoxQt0ingc1gWsdAhG7So';
|
||||
//$apiUrl = 'http://nominatim.openstreetmap.org/search?format=json&street='. $street . '&city='.$city.'&country='.$country.'&limit=1';
|
||||
$apiUrl = 'http://nominatim.openstreetmap.org/search?format=json&q=' . $q;
|
||||
$r = $this -> getURL($apiUrl, false);
|
||||
$s = (array)json_decode($r);
|
||||
|
||||
$r -> apiUrl = $apiUrl;
|
||||
$r = $s;
|
||||
$this -> cacheManager -> insert($geohash, $s);
|
||||
} else {
|
||||
$checkCache -> cachedResult = true;
|
||||
$r = $checkCache;
|
||||
}
|
||||
return $r;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $hash
|
||||
*/
|
||||
private function checkGeoCache($hash) {
|
||||
return $this -> cacheManager -> check($hash);
|
||||
}
|
||||
|
||||
private function getURL($url, $userAgent = true) {
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
|
||||
curl_setopt($ch, CURLOPT_HEADER, 0);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 900);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
|
||||
if ($userAgent) {
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 GTB5');
|
||||
}
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
$tmp = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($httpCode === 404) {
|
||||
return false;
|
||||
} else {
|
||||
if ($tmp !== false) {
|
||||
return $tmp;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
#map {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#app-content-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
<?php
|
||||
namespace OCA\Maps\Db;
|
||||
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
|
||||
/**
|
||||
* @method string getUserId()
|
||||
* @method void setUserId(string $value)
|
||||
* @method string getApiKey()
|
||||
* @method void setApiKey(string $value)
|
||||
*/
|
||||
class ApiKey extends Entity {
|
||||
public $userId;
|
||||
public $apiKey;
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
<?php
|
||||
namespace OCA\Maps\Db;
|
||||
|
||||
use OCP\AppFramework\Db\Mapper;
|
||||
use OCP\IDb;
|
||||
|
||||
class ApiKeyMapper extends Mapper {
|
||||
|
||||
public function __construct(IDB $db) {
|
||||
parent::__construct($db, 'maps_apikeys', '\OCA\Maps\Db\ApiKey');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @throws \OCP\AppFramework\Db\DoesNotExistException if not found
|
||||
* @return ApiKey
|
||||
*/
|
||||
public function find($id) {
|
||||
$sql = 'SELECT * FROM `*PREFIX*maps_apikeys` '.
|
||||
'WHERE `id` = ?';
|
||||
return $this->findEntity($sql, [$id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uid
|
||||
* @throws \OCP\AppFramework\Db\DoesNotExistException if not found
|
||||
* @return ApiKey
|
||||
*/
|
||||
public function findByUser($uid) {
|
||||
$sql = 'SELECT * FROM `*PREFIX*maps_apikeys` '.
|
||||
'WHERE `user_id` = ?';
|
||||
return $this->findEntity($sql, [$uid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $limit
|
||||
* @param int $offset
|
||||
* @return ApiKey[]
|
||||
*/
|
||||
public function findAll($limit=null, $offset=null) {
|
||||
$sql = 'SELECT * FROM `*PREFIX*maps_apikeys`';
|
||||
return $this->findEntities($sql, $limit, $offset);
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* ownCloud - passman
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Sander Brand <brantje@gmail.com>
|
||||
* @copyright Sander Brand 2014
|
||||
*/
|
||||
namespace OCA\Maps\Db;
|
||||
|
||||
use OCP\IDBConnection;
|
||||
|
||||
class CacheManager {
|
||||
private $db;
|
||||
public function __construct(IDBConnection $db) {
|
||||
$this -> db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* List items in a folder
|
||||
*/
|
||||
|
||||
|
||||
public function insert($hash,$raw){
|
||||
$serialized = serialize($raw);
|
||||
$sql = "INSERT INTO `*PREFIX*maps_adress_cache` (adres_hash,serialized) VALUES(?,?)";
|
||||
$query = $this -> db -> prepare($sql);
|
||||
$query -> bindParam(1, $hash, \PDO::PARAM_STR);
|
||||
$query -> bindParam(2, $serialized, \PDO::PARAM_STR);
|
||||
$result = $query -> execute();
|
||||
}
|
||||
|
||||
public function check($hash){
|
||||
$sql = 'SELECT * from `*PREFIX*maps_adress_cache` where adres_hash=?';
|
||||
$query = $this -> db -> prepare($sql);
|
||||
$query -> bindParam(1, $hash, \PDO::PARAM_STR);
|
||||
$query -> execute();
|
||||
$result = $query->fetch();
|
||||
return unserialize($result['serialized']);
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?php
|
||||
namespace OCA\Maps\Db;
|
||||
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
|
||||
class Device extends Entity {
|
||||
public $userId;
|
||||
public $name;
|
||||
public $hash;
|
||||
public $created;
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
<?php
|
||||
namespace OCA\Maps\Db;
|
||||
|
||||
use OCP\AppFramework\Db\Mapper;
|
||||
use OCP\IDb;
|
||||
|
||||
class DeviceMapper extends Mapper {
|
||||
|
||||
public function __construct(IDB $db) {
|
||||
parent::__construct($db, 'maps_location_track_users', '\OCA\Maps\Db\Device');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $hash
|
||||
* @throws \OCP\AppFramework\Db\DoesNotExistException if not found
|
||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException if more than one result
|
||||
* @return Device
|
||||
*/
|
||||
public function findByHash($hash) {
|
||||
$sql = 'SELECT * FROM `*PREFIX*maps_location_track_users` '.
|
||||
'WHERE `hash` = ?';
|
||||
return $this->findEntity($sql, [$hash]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @throws \OCP\AppFramework\Db\DoesNotExistException if not found
|
||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException if more than one result
|
||||
* @return Device
|
||||
*/
|
||||
public function findById($id) {
|
||||
$sql = 'SELECT * FROM `*PREFIX*maps_location_track_users` '.
|
||||
'WHERE `id` = ?';
|
||||
return $this->findEntity($sql, [$id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $userId
|
||||
* @param int $limit
|
||||
* @param int $offset
|
||||
* @return Device[]
|
||||
*/
|
||||
public function findAll($userId, $limit=null, $offset=null) {
|
||||
$sql = 'SELECT * FROM `*PREFIX*maps_location_track_users`'.
|
||||
'WHERE `user_id` = ?';
|
||||
return $this->findEntities($sql, [$userId], $limit, $offset);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
<?php
|
||||
namespace OCA\Maps\Db;
|
||||
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
|
||||
/**
|
||||
* @method string getUserId()
|
||||
* @method void setUserId(string $value)
|
||||
* @method string getName()
|
||||
* @method void setName(string $value)
|
||||
* @method string getTimestamp()
|
||||
* @method void setTimestamp(string $value)
|
||||
* @method string getLat()
|
||||
* @method void setLat(string $value)
|
||||
* @method string getLng()
|
||||
* @method void setLng(string $value)
|
||||
*/
|
||||
class Favorite extends Entity {
|
||||
public $userId;
|
||||
public $name;
|
||||
public $timestamp;
|
||||
public $lat;
|
||||
public $lng;
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
<?php
|
||||
namespace OCA\Maps\Db;
|
||||
|
||||
use OCP\AppFramework\Db\Mapper;
|
||||
use OCP\IDb;
|
||||
|
||||
class FavoriteMapper extends Mapper {
|
||||
|
||||
public function __construct(IDB $db) {
|
||||
parent::__construct($db, 'maps_favorites', '\OCA\Maps\Db\Favorite');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @throws \OCP\AppFramework\Db\DoesNotExistException if not found
|
||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException if more than one result
|
||||
* @return Favorite
|
||||
*/
|
||||
public function find($id) {
|
||||
$sql = 'SELECT * FROM `*PREFIX*maps_favorites` '.
|
||||
'WHERE `id` = ?';
|
||||
return $this->findEntity($sql, [$id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return Favorite[]
|
||||
*/
|
||||
public function findByName($name) {
|
||||
$sql = 'SELECT * FROM `*PREFIX*maps_favorites` '.
|
||||
'WHERE `name` ILIKE ?';
|
||||
return $this->findEntities($sql, ['%' . addcslashes($name, '\\_%') . '%']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $userId
|
||||
* @param string $from
|
||||
* @param string $until
|
||||
* @param int $limit
|
||||
* @param int $offset
|
||||
* @return Favorite[]
|
||||
*/
|
||||
public function findBetween($userId, $from, $until, $limit=null, $offset=null) {
|
||||
$sql = 'SELECT * FROM `*PREFIX*maps_favorites` '.
|
||||
'WHERE `userId` = ?'.
|
||||
'AND `timestamp` BETWEEN ? and ?';
|
||||
return $this->findEntities($sql, [$userId, $from, $until], $limit, $offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $limit
|
||||
* @param int $offset
|
||||
* @return Favorite[]
|
||||
*/
|
||||
public function findAll($limit=null, $offset=null) {
|
||||
$sql = 'SELECT * FROM `*PREFIX*maps_favorites`';
|
||||
return $this->findEntities($sql, $limit, $offset);
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
<?php
|
||||
namespace OCA\Maps\Db;
|
||||
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
|
||||
class Location extends Entity {
|
||||
public $deviceHash;
|
||||
public $lat;
|
||||
public $lng;
|
||||
public $timestamp;
|
||||
public $hdop;
|
||||
public $altitude;
|
||||
public $speed;
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
<?php
|
||||
namespace OCA\Maps\Db;
|
||||
|
||||
use OCP\AppFramework\Db\Mapper;
|
||||
use OCP\IDb;
|
||||
|
||||
class LocationMapper extends Mapper {
|
||||
|
||||
public function __construct(IDB $db) {
|
||||
parent::__construct($db, 'maps_locations', '\OCA\Maps\Db\Location');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @throws \OCP\AppFramework\Db\DoesNotExistException if not found
|
||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException if more than one result
|
||||
* @return Location
|
||||
*/
|
||||
public function find($id) {
|
||||
$sql = 'SELECT * FROM `*PREFIX*maps_locations` '.
|
||||
'WHERE `id` = ?';
|
||||
return $this->findEntity($sql, [$id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $deviceHash
|
||||
* @param string $from
|
||||
* @param string $until
|
||||
* @param int $limit
|
||||
* @param int $offset
|
||||
* @return Location[]
|
||||
*/
|
||||
public function findBetween($deviceHash, $from, $until, $limit=null, $offset=null) {
|
||||
$sql = 'SELECT * FROM `*PREFIX*maps_locations` '.
|
||||
'WHERE `device_hash` = ?'.
|
||||
'AND `timestamp` BETWEEN ? and ?';
|
||||
return $this->findEntities($sql, [$deviceHash, $from, $until], $limit, $offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $limit
|
||||
* @param int $offset
|
||||
* @return Location[]
|
||||
*/
|
||||
public function findAll($limit=null, $offset=null) {
|
||||
$sql = 'SELECT * FROM `*PREFIX*maps_locations`';
|
||||
return $this->findEntities($sql, $limit, $offset);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
height="32"
|
||||
width="32"
|
||||
version="1"
|
||||
viewBox="0 0 32 32"
|
||||
id="svg4"
|
||||
sodipodi:docname="app.svg"
|
||||
inkscape:version="0.92.1 r">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="789"
|
||||
inkscape:window-height="480"
|
||||
id="namedview6"
|
||||
showgrid="false"
|
||||
inkscape:zoom="7.375"
|
||||
inkscape:cx="-8.3389831"
|
||||
inkscape:cy="16"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg4" />
|
||||
<path
|
||||
d="M13.733 0a.915.915 0 0 0-.933.934V3.6c-1.182.304-2.243.794-3.267 1.4L7.6 3.068a.93.93 0 0 0-1.334 0l-3.2 3.2a.93.93 0 0 0 0 1.334L5 9.535c-.607 1.024-1.097 2.085-1.4 3.267H.933a.915.915 0 0 0-.933.934v4.533c0 .53.403.934.933.934H3.6c.303 1.182.793 2.243 1.4 3.267l-1.934 1.935a.93.93 0 0 0 0 1.333l3.2 3.2a.93.93 0 0 0 1.333 0L9.532 27c1.024.61 2.085 1.097 3.266 1.4v2.667c0 .53.402.933.932.933h4.534c.53 0 .933-.403.933-.935V28.4c1.18-.305 2.24-.795 3.265-1.4L24.4 28.93a.93.93 0 0 0 1.332 0l3.2-3.2a.93.93 0 0 0 0-1.333L27 22.465c.607-1.024 1.096-2.085 1.4-3.266h2.665a.915.915 0 0 0 .935-.933v-4.534a.915.915 0 0 0-.934-.933H28.4c-.304-1.182-.792-2.243-1.4-3.267L28.932 7.6a.93.93 0 0 0 0-1.334l-3.2-3.2a.93.93 0 0 0-1.333 0L22.465 5c-1.024-.607-2.084-1.097-3.266-1.4V.933A.915.915 0 0 0 18.267 0zM16 8.87A7.134 7.134 0 0 1 23.13 16 7.134 7.134 0 0 1 16 23.133c-3.936 0-7.13-3.196-7.13-7.132S12.063 8.87 16 8.87z"
|
||||
display="block"
|
||||
fill="#fff"
|
||||
id="path2"
|
||||
style="fill:#ffffff" />
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 2.4 KiB |
Двоичные данные
img/favicon-touch.png
До Ширина: | Высота: | Размер: 2.9 KiB |
|
@ -1,2 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 595.275 311.111" xml:space="preserve" height="128" width="128" version="1.1" y="0px" x="0px" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" viewBox="0 0 128 128"><rect rx="20" ry="20" height="128" width="128" y="-.0000015" x="0" fill="#1d2d44"/><path fill="#fff" d="m65.096 4c-22.696 0-41.096 18.402-41.096 41.096 0 22.696 41.096 78.904 41.096 78.904s41.096-56.208 41.096-78.904c0-22.694-18.404-41.096-41.096-41.096zm0 64.108c-12.71 0-23.012-10.302-23.012-23.015 0-12.71 10.302-23.009 23.012-23.009 12.713 0 23.012 10.299 23.012 23.009 0.003 12.713-10.299 23.015-23.012 23.015z"/></svg>
|
До Ширина: | Высота: | Размер: 804 B |
Двоичные данные
img/favicon.ico
До Ширина: | Высота: | Размер: 3.2 KiB |
Двоичные данные
img/favicon.png
До Ширина: | Высота: | Размер: 778 B |
|
@ -1,2 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 595.275 311.111" xml:space="preserve" height="32" width="32" version="1.1" y="0px" x="0px" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" viewBox="0 0 32 32"><rect rx="5" ry="5" height="32" width="32" y="-.0000052588" x="0" fill="#1d2d44"/><path fill="#fff" d="m16.274 1c-5.674 0-10.274 4.6006-10.274 10.274 0 5.674 10.274 19.726 10.274 19.726s10.274-14.052 10.274-19.726c0-5.6734-4.601-10.274-10.274-10.274zm0 16.027c-3.1775 0-5.7529-2.5754-5.7529-5.7537 0-3.1775 2.5754-5.7522 5.7529-5.7522 3.1782 0 5.7529 2.5747 5.7529 5.7522 0.00074 3.1783-2.5747 5.7537-5.7529 5.7537z"/></svg>
|
До Ширина: | Высота: | Размер: 800 B |
Двоичные данные
img/icons/compass-icon.png
До Ширина: | Высота: | Размер: 1.7 KiB |
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" version="1.1">
|
||||
<path d="m16.274 1c-5.674 0-10.274 4.6006-10.274 10.274 0 5.674 10.274 19.726 10.274 19.726s10.274-14.052 10.274-19.726c0-5.6734-4.601-10.274-10.274-10.274z" fill="#f55"/>
|
||||
<path d="m16.371 2.9969 2.2508 5.6417 6.1419 0.48331-4.6699 3.8839 1.4384 5.991-5.1369-3.2411-5.2533 3.2192l1.495-5.887-4.6847-4.0013 6.0607-0.3973z" fill="#fc0"/>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 478 B |
|
@ -1,2 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xml:space="preserve" height="32" width="32" enable-background="new 0 0 100 100" y="0px" x="0px" viewBox="0 0 32 32"><path d="m16.274 1c-5.674 0-10.274 4.6006-10.274 10.274 0 5.674 10.274 19.726 10.274 19.726s10.274-14.052 10.274-19.726c0-5.6734-4.601-10.274-10.274-10.274zm0 16.027c-3.1775 0-5.7529-2.5754-5.7529-5.7537 0-3.1775 2.5754-5.7522 5.7529-5.7522 3.1782 0 5.7529 2.5747 5.7529 5.7522 0.00074 3.1783-2.5747 5.7537-5.7529 5.7537z" fill="#55739a"/></svg>
|
До Ширина: | Высота: | Размер: 571 B |
|
@ -1,2 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xml:space="preserve" height="32" width="32" enable-background="new 0 0 100 100" y="0px" x="0px" viewBox="0 0 32 32"><path d="m16.274 1c-5.674 0-10.274 4.6006-10.274 10.274 0 5.674 10.274 19.726 10.274 19.726s10.274-14.052 10.274-19.726c0-5.6734-4.601-10.274-10.274-10.274zm0 16.027c-3.1775 0-5.7529-2.5754-5.7529-5.7537 0-3.1775 2.5754-5.7522 5.7529-5.7522 3.1782 0 5.7529 2.5747 5.7529 5.7522 0.00074 3.1783-2.5747 5.7537-5.7529 5.7537z" fill="#55739a"/></svg>
|
До Ширина: | Высота: | Размер: 571 B |
|
@ -1,2 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:osb="http://www.openswatchbook.org/uri/2009/osb" xml:space="preserve" height="27.868" width="27.751" enable-background="new 0 0 100 100" y="0px" x="0px" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 27.7508 27.868079"><defs><filter id="d" style="color-interpolation-filters:sRGB"><feGaussianBlur stdDeviation="0.5" result="blur"/><feComposite operator="atop" result="composite1" in2="blur" in="SourceGraphic"/><feComposite operator="in" result="composite2" in2="composite1"/><feComposite operator="in" result="fbSourceGraphic" in2="composite2"/><feColorMatrix values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0" result="fbSourceGraphicAlpha" in="fbSourceGraphic"/><feGaussianBlur in="fbSourceGraphic" stdDeviation="1.1" result="blur"/><feComposite operator="atop" result="composite1" in2="blur" in="fbSourceGraphic"/><feComposite operator="in" in2="composite1" result="composite2"/><feComposite operator="in" in2="composite2" result="composite3"/></filter><filter id="c" style="color-interpolation-filters:sRGB"><feGaussianBlur stdDeviation="0.4" result="blur"/><feComposite operator="atop" result="composite1" in2="blur" in="SourceGraphic"/><feComposite operator="in" result="composite2" in2="composite1"/><feComposite operator="in" result="composite3" in2="composite2"/></filter><linearGradient id="b" y2="-14.176" gradientUnits="userSpaceOnUse" x2="11.393" gradientTransform="matrix(.92908 0 0 .92877 1.0321 -.65741)" y1="-14.176" x1="-8.5137"><stop offset="0"/><stop stop-color="#fff" offset="1"/></linearGradient><mask id="a" maskUnits="userSpaceOnUse"><rect transform="matrix(-.83626 .76803 -.73779 -.86549 -1.2799 -1.0136)" height="18.549" filter="url(#c)" width="18.495" y="-23.098" x="-6.8778" fill="url(#b)"/></mask></defs><path opacity=".75245" d="m16.886 2.3823c-3.218-3.1936-8.4165-3.1736-11.61 0.0439-3.1934 3.2178-5.276 16.97-5.276 16.97s13.736-2.1864 16.929-5.4043c3.1933-3.2175 3.1734-8.4163-0.04385-11.609zm-9.0213 9.0897c-1.802-1.7889-1.813-4.6991-0.0241-6.5015 1.7885-1.802 4.6982-1.8126 6.5002-0.02415 1.8024 1.7889 1.8134 4.6982 0.02495 6.5002-1.7885 1.8029-4.6986 1.8139-6.5011 0.02501z" mask="url(#a)" transform="matrix(1.1975 0 0 1.1973 2.3126 2.3223)" filter="url(#d)"/></svg>
|
До Ширина: | Высота: | Размер: 2.3 KiB |
Двоичные данные
img/icons/marker_anonPerson.png
До Ширина: | Высота: | Размер: 2.4 KiB |
Двоичные данные
img/icons/personBackground.png
До Ширина: | Высота: | Размер: 2.3 KiB |
Двоичные данные
img/icons/trace_off.png
До Ширина: | Высота: | Размер: 587 B |
Двоичные данные
img/icons/trace_on.png
До Ширина: | Высота: | Размер: 1012 B |
Двоичные данные
img/maps.png
До Ширина: | Высота: | Размер: 632 B |
|
@ -1,2 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 100 100" xml:space="preserve" height="32" width="32" version="1.1" y="0px" x="0px" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" viewBox="0 0 32 32"><path d="m16.274 1c-5.674 0-10.274 4.6006-10.274 10.274 0 5.674 10.274 19.726 10.274 19.726s10.274-14.052 10.274-19.726c0-5.6734-4.601-10.274-10.274-10.274zm0 16.027c-3.1775 0-5.7529-2.5754-5.7529-5.7537 0-3.1775 2.5754-5.7522 5.7529-5.7522 3.1782 0 5.7529 2.5747 5.7529 5.7522 0.00074 3.1783-2.5747 5.7537-5.7529 5.7537z" fill="#fff"/></svg>
|
До Ширина: | Высота: | Размер: 710 B |
|
@ -1,996 +0,0 @@
|
|||
/*
|
||||
* ----------------------------- JSTORAGE -------------------------------------
|
||||
* Simple local storage wrapper to save data on the browser side, supporting
|
||||
* all major browsers - IE6+, Firefox2+, Safari4+, Chrome4+ and Opera 10.5+
|
||||
*
|
||||
* Author: Andris Reinman, andris.reinman@gmail.com
|
||||
* Project homepage: www.jstorage.info
|
||||
*
|
||||
* Licensed under Unlicense:
|
||||
*
|
||||
* This is free and unencumbered software released into the public domain.
|
||||
*
|
||||
* Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
* distribute this software, either in source code form or as a compiled
|
||||
* binary, for any purpose, commercial or non-commercial, and by any
|
||||
* means.
|
||||
*
|
||||
* In jurisdictions that recognize copyright laws, the author or authors
|
||||
* of this software dedicate any and all copyright interest in the
|
||||
* software to the public domain. We make this dedication for the benefit
|
||||
* of the public at large and to the detriment of our heirs and
|
||||
* successors. We intend this dedication to be an overt act of
|
||||
* relinquishment in perpetuity of all present and future rights to this
|
||||
* software under copyright law.
|
||||
*
|
||||
* 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 BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
* OTHER LIABILITY, 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.
|
||||
*
|
||||
* For more information, please refer to <http://unlicense.org/>
|
||||
*/
|
||||
|
||||
/* global ActiveXObject: false */
|
||||
/* jshint browser: true */
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var
|
||||
/* jStorage version */
|
||||
JSTORAGE_VERSION = '0.4.12',
|
||||
|
||||
/* detect a dollar object or create one if not found */
|
||||
$ = window.jQuery || window.$ || (window.$ = {}),
|
||||
|
||||
/* check for a JSON handling support */
|
||||
JSON = {
|
||||
parse: window.JSON && (window.JSON.parse || window.JSON.decode) ||
|
||||
String.prototype.evalJSON && function(str) {
|
||||
return String(str).evalJSON();
|
||||
} ||
|
||||
$.parseJSON ||
|
||||
$.evalJSON,
|
||||
stringify: Object.toJSON ||
|
||||
window.JSON && (window.JSON.stringify || window.JSON.encode) ||
|
||||
$.toJSON
|
||||
};
|
||||
|
||||
// Break if no JSON support was found
|
||||
if (typeof JSON.parse !== 'function' || typeof JSON.stringify !== 'function') {
|
||||
throw new Error('No JSON support found, include //cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js to page');
|
||||
}
|
||||
|
||||
var
|
||||
/* This is the object, that holds the cached values */
|
||||
_storage = {
|
||||
__jstorage_meta: {
|
||||
CRC32: {}
|
||||
}
|
||||
},
|
||||
|
||||
/* Actual browser storage (localStorage or globalStorage['domain']) */
|
||||
_storage_service = {
|
||||
jStorage: '{}'
|
||||
},
|
||||
|
||||
/* DOM element for older IE versions, holds userData behavior */
|
||||
_storage_elm = null,
|
||||
|
||||
/* How much space does the storage take */
|
||||
_storage_size = 0,
|
||||
|
||||
/* which backend is currently used */
|
||||
_backend = false,
|
||||
|
||||
/* onchange observers */
|
||||
_observers = {},
|
||||
|
||||
/* timeout to wait after onchange event */
|
||||
_observer_timeout = false,
|
||||
|
||||
/* last update time */
|
||||
_observer_update = 0,
|
||||
|
||||
/* pubsub observers */
|
||||
_pubsub_observers = {},
|
||||
|
||||
/* skip published items older than current timestamp */
|
||||
_pubsub_last = +new Date(),
|
||||
|
||||
/* Next check for TTL */
|
||||
_ttl_timeout,
|
||||
|
||||
/**
|
||||
* XML encoding and decoding as XML nodes can't be JSON'ized
|
||||
* XML nodes are encoded and decoded if the node is the value to be saved
|
||||
* but not if it's as a property of another object
|
||||
* Eg. -
|
||||
* $.jStorage.set('key', xmlNode); // IS OK
|
||||
* $.jStorage.set('key', {xml: xmlNode}); // NOT OK
|
||||
*/
|
||||
_XMLService = {
|
||||
|
||||
/**
|
||||
* Validates a XML node to be XML
|
||||
* based on jQuery.isXML function
|
||||
*/
|
||||
isXML: function(elm) {
|
||||
var documentElement = (elm ? elm.ownerDocument || elm : 0).documentElement;
|
||||
return documentElement ? documentElement.nodeName !== 'HTML' : false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Encodes a XML node to string
|
||||
* based on http://www.mercurytide.co.uk/news/article/issues-when-working-ajax/
|
||||
*/
|
||||
encode: function(xmlNode) {
|
||||
if (!this.isXML(xmlNode)) {
|
||||
return false;
|
||||
}
|
||||
try { // Mozilla, Webkit, Opera
|
||||
return new XMLSerializer().serializeToString(xmlNode);
|
||||
} catch (E1) {
|
||||
try { // IE
|
||||
return xmlNode.xml;
|
||||
} catch (E2) {}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Decodes a XML node from string
|
||||
* loosely based on http://outwestmedia.com/jquery-plugins/xmldom/
|
||||
*/
|
||||
decode: function(xmlString) {
|
||||
var dom_parser = ('DOMParser' in window && (new DOMParser()).parseFromString) ||
|
||||
(window.ActiveXObject && function(_xmlString) {
|
||||
var xml_doc = new ActiveXObject('Microsoft.XMLDOM');
|
||||
xml_doc.async = 'false';
|
||||
xml_doc.loadXML(_xmlString);
|
||||
return xml_doc;
|
||||
}),
|
||||
resultXML;
|
||||
if (!dom_parser) {
|
||||
return false;
|
||||
}
|
||||
resultXML = dom_parser.call('DOMParser' in window && (new DOMParser()) || window, xmlString, 'text/xml');
|
||||
return this.isXML(resultXML) ? resultXML : false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
////////////////////////// PRIVATE METHODS ////////////////////////
|
||||
|
||||
/**
|
||||
* Initialization function. Detects if the browser supports DOM Storage
|
||||
* or userData behavior and behaves accordingly.
|
||||
*/
|
||||
function _init() {
|
||||
/* Check if browser supports localStorage */
|
||||
var localStorageReallyWorks = false;
|
||||
if ('localStorage' in window) {
|
||||
try {
|
||||
window.localStorage.setItem('_tmptest', 'tmpval');
|
||||
localStorageReallyWorks = true;
|
||||
window.localStorage.removeItem('_tmptest');
|
||||
} catch (BogusQuotaExceededErrorOnIos5) {
|
||||
// Thanks be to iOS5 Private Browsing mode which throws
|
||||
// QUOTA_EXCEEDED_ERRROR DOM Exception 22.
|
||||
}
|
||||
}
|
||||
|
||||
if (localStorageReallyWorks) {
|
||||
try {
|
||||
if (window.localStorage) {
|
||||
_storage_service = window.localStorage;
|
||||
_backend = 'localStorage';
|
||||
_observer_update = _storage_service.jStorage_update;
|
||||
}
|
||||
} catch (E3) { /* Firefox fails when touching localStorage and cookies are disabled */ }
|
||||
}
|
||||
/* Check if browser supports globalStorage */
|
||||
else if ('globalStorage' in window) {
|
||||
try {
|
||||
if (window.globalStorage) {
|
||||
if (window.location.hostname == 'localhost') {
|
||||
_storage_service = window.globalStorage['localhost.localdomain'];
|
||||
} else {
|
||||
_storage_service = window.globalStorage[window.location.hostname];
|
||||
}
|
||||
_backend = 'globalStorage';
|
||||
_observer_update = _storage_service.jStorage_update;
|
||||
}
|
||||
} catch (E4) { /* Firefox fails when touching localStorage and cookies are disabled */ }
|
||||
}
|
||||
/* Check if browser supports userData behavior */
|
||||
else {
|
||||
_storage_elm = document.createElement('link');
|
||||
if (_storage_elm.addBehavior) {
|
||||
|
||||
/* Use a DOM element to act as userData storage */
|
||||
_storage_elm.style.behavior = 'url(#default#userData)';
|
||||
|
||||
/* userData element needs to be inserted into the DOM! */
|
||||
document.getElementsByTagName('head')[0].appendChild(_storage_elm);
|
||||
|
||||
try {
|
||||
_storage_elm.load('jStorage');
|
||||
} catch (E) {
|
||||
// try to reset cache
|
||||
_storage_elm.setAttribute('jStorage', '{}');
|
||||
_storage_elm.save('jStorage');
|
||||
_storage_elm.load('jStorage');
|
||||
}
|
||||
|
||||
var data = '{}';
|
||||
try {
|
||||
data = _storage_elm.getAttribute('jStorage');
|
||||
} catch (E5) {}
|
||||
|
||||
try {
|
||||
_observer_update = _storage_elm.getAttribute('jStorage_update');
|
||||
} catch (E6) {}
|
||||
|
||||
_storage_service.jStorage = data;
|
||||
_backend = 'userDataBehavior';
|
||||
} else {
|
||||
_storage_elm = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Load data from storage
|
||||
_load_storage();
|
||||
|
||||
// remove dead keys
|
||||
_handleTTL();
|
||||
|
||||
// start listening for changes
|
||||
_setupObserver();
|
||||
|
||||
// initialize publish-subscribe service
|
||||
_handlePubSub();
|
||||
|
||||
// handle cached navigation
|
||||
if ('addEventListener' in window) {
|
||||
window.addEventListener('pageshow', function(event) {
|
||||
if (event.persisted) {
|
||||
_storageObserver();
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload data from storage when needed
|
||||
*/
|
||||
function _reloadData() {
|
||||
var data = '{}';
|
||||
|
||||
if (_backend == 'userDataBehavior') {
|
||||
_storage_elm.load('jStorage');
|
||||
|
||||
try {
|
||||
data = _storage_elm.getAttribute('jStorage');
|
||||
} catch (E5) {}
|
||||
|
||||
try {
|
||||
_observer_update = _storage_elm.getAttribute('jStorage_update');
|
||||
} catch (E6) {}
|
||||
|
||||
_storage_service.jStorage = data;
|
||||
}
|
||||
|
||||
_load_storage();
|
||||
|
||||
// remove dead keys
|
||||
_handleTTL();
|
||||
|
||||
_handlePubSub();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a storage change observer
|
||||
*/
|
||||
function _setupObserver() {
|
||||
if (_backend == 'localStorage' || _backend == 'globalStorage') {
|
||||
if ('addEventListener' in window) {
|
||||
window.addEventListener('storage', _storageObserver, false);
|
||||
} else {
|
||||
document.attachEvent('onstorage', _storageObserver);
|
||||
}
|
||||
} else if (_backend == 'userDataBehavior') {
|
||||
setInterval(_storageObserver, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired on any kind of data change, needs to check if anything has
|
||||
* really been changed
|
||||
*/
|
||||
function _storageObserver() {
|
||||
var updateTime;
|
||||
// cumulate change notifications with timeout
|
||||
clearTimeout(_observer_timeout);
|
||||
_observer_timeout = setTimeout(function() {
|
||||
|
||||
if (_backend == 'localStorage' || _backend == 'globalStorage') {
|
||||
updateTime = _storage_service.jStorage_update;
|
||||
} else if (_backend == 'userDataBehavior') {
|
||||
_storage_elm.load('jStorage');
|
||||
try {
|
||||
updateTime = _storage_elm.getAttribute('jStorage_update');
|
||||
} catch (E5) {}
|
||||
}
|
||||
|
||||
if (updateTime && updateTime != _observer_update) {
|
||||
_observer_update = updateTime;
|
||||
_checkUpdatedKeys();
|
||||
}
|
||||
|
||||
}, 25);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the data and checks if any keys are changed
|
||||
*/
|
||||
function _checkUpdatedKeys() {
|
||||
var oldCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32)),
|
||||
newCrc32List;
|
||||
|
||||
_reloadData();
|
||||
newCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32));
|
||||
|
||||
var key,
|
||||
updated = [],
|
||||
removed = [];
|
||||
|
||||
for (key in oldCrc32List) {
|
||||
if (oldCrc32List.hasOwnProperty(key)) {
|
||||
if (!newCrc32List[key]) {
|
||||
removed.push(key);
|
||||
continue;
|
||||
}
|
||||
if (oldCrc32List[key] != newCrc32List[key] && String(oldCrc32List[key]).substr(0, 2) == '2.') {
|
||||
updated.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (key in newCrc32List) {
|
||||
if (newCrc32List.hasOwnProperty(key)) {
|
||||
if (!oldCrc32List[key]) {
|
||||
updated.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_fireObservers(updated, 'updated');
|
||||
_fireObservers(removed, 'deleted');
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires observers for updated keys
|
||||
*
|
||||
* @param {Array|String} keys Array of key names or a key
|
||||
* @param {String} action What happened with the value (updated, deleted, flushed)
|
||||
*/
|
||||
function _fireObservers(keys, action) {
|
||||
keys = [].concat(keys || []);
|
||||
|
||||
var i, j, len, jlen;
|
||||
|
||||
if (action == 'flushed') {
|
||||
keys = [];
|
||||
for (var key in _observers) {
|
||||
if (_observers.hasOwnProperty(key)) {
|
||||
keys.push(key);
|
||||
}
|
||||
}
|
||||
action = 'deleted';
|
||||
}
|
||||
for (i = 0, len = keys.length; i < len; i++) {
|
||||
if (_observers[keys[i]]) {
|
||||
for (j = 0, jlen = _observers[keys[i]].length; j < jlen; j++) {
|
||||
_observers[keys[i]][j](keys[i], action);
|
||||
}
|
||||
}
|
||||
if (_observers['*']) {
|
||||
for (j = 0, jlen = _observers['*'].length; j < jlen; j++) {
|
||||
_observers['*'][j](keys[i], action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Publishes key change to listeners
|
||||
*/
|
||||
function _publishChange() {
|
||||
var updateTime = (+new Date()).toString();
|
||||
|
||||
if (_backend == 'localStorage' || _backend == 'globalStorage') {
|
||||
try {
|
||||
_storage_service.jStorage_update = updateTime;
|
||||
} catch (E8) {
|
||||
// safari private mode has been enabled after the jStorage initialization
|
||||
_backend = false;
|
||||
}
|
||||
} else if (_backend == 'userDataBehavior') {
|
||||
_storage_elm.setAttribute('jStorage_update', updateTime);
|
||||
_storage_elm.save('jStorage');
|
||||
}
|
||||
|
||||
_storageObserver();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the data from the storage based on the supported mechanism
|
||||
*/
|
||||
function _load_storage() {
|
||||
/* if jStorage string is retrieved, then decode it */
|
||||
if (_storage_service.jStorage) {
|
||||
try {
|
||||
_storage = JSON.parse(String(_storage_service.jStorage));
|
||||
} catch (E6) {
|
||||
_storage_service.jStorage = '{}';
|
||||
}
|
||||
} else {
|
||||
_storage_service.jStorage = '{}';
|
||||
}
|
||||
_storage_size = _storage_service.jStorage ? String(_storage_service.jStorage).length : 0;
|
||||
|
||||
if (!_storage.__jstorage_meta) {
|
||||
_storage.__jstorage_meta = {};
|
||||
}
|
||||
if (!_storage.__jstorage_meta.CRC32) {
|
||||
_storage.__jstorage_meta.CRC32 = {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This functions provides the 'save' mechanism to store the jStorage object
|
||||
*/
|
||||
function _save() {
|
||||
_dropOldEvents(); // remove expired events
|
||||
try {
|
||||
_storage_service.jStorage = JSON.stringify(_storage);
|
||||
// If userData is used as the storage engine, additional
|
||||
if (_storage_elm) {
|
||||
_storage_elm.setAttribute('jStorage', _storage_service.jStorage);
|
||||
_storage_elm.save('jStorage');
|
||||
}
|
||||
_storage_size = _storage_service.jStorage ? String(_storage_service.jStorage).length : 0;
|
||||
} catch (E7) { /* probably cache is full, nothing is saved this way*/ }
|
||||
}
|
||||
|
||||
/**
|
||||
* Function checks if a key is set and is string or numberic
|
||||
*
|
||||
* @param {String} key Key name
|
||||
*/
|
||||
function _checkKey(key) {
|
||||
if (typeof key != 'string' && typeof key != 'number') {
|
||||
throw new TypeError('Key name must be string or numeric');
|
||||
}
|
||||
if (key == '__jstorage_meta') {
|
||||
throw new TypeError('Reserved key name');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes expired keys
|
||||
*/
|
||||
function _handleTTL() {
|
||||
var curtime, i, TTL, CRC32, nextExpire = Infinity,
|
||||
changed = false,
|
||||
deleted = [];
|
||||
|
||||
clearTimeout(_ttl_timeout);
|
||||
|
||||
if (!_storage.__jstorage_meta || typeof _storage.__jstorage_meta.TTL != 'object') {
|
||||
// nothing to do here
|
||||
return;
|
||||
}
|
||||
|
||||
curtime = +new Date();
|
||||
TTL = _storage.__jstorage_meta.TTL;
|
||||
|
||||
CRC32 = _storage.__jstorage_meta.CRC32;
|
||||
for (i in TTL) {
|
||||
if (TTL.hasOwnProperty(i)) {
|
||||
if (TTL[i] <= curtime) {
|
||||
delete TTL[i];
|
||||
delete CRC32[i];
|
||||
delete _storage[i];
|
||||
changed = true;
|
||||
deleted.push(i);
|
||||
} else if (TTL[i] < nextExpire) {
|
||||
nextExpire = TTL[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set next check
|
||||
if (nextExpire != Infinity) {
|
||||
_ttl_timeout = setTimeout(_handleTTL, Math.min(nextExpire - curtime, 0x7FFFFFFF));
|
||||
}
|
||||
|
||||
// save changes
|
||||
if (changed) {
|
||||
_save();
|
||||
_publishChange();
|
||||
_fireObservers(deleted, 'deleted');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there's any events on hold to be fired to listeners
|
||||
*/
|
||||
function _handlePubSub() {
|
||||
var i, len;
|
||||
if (!_storage.__jstorage_meta.PubSub) {
|
||||
return;
|
||||
}
|
||||
var pubelm,
|
||||
_pubsubCurrent = _pubsub_last,
|
||||
needFired = [];
|
||||
|
||||
for (i = len = _storage.__jstorage_meta.PubSub.length - 1; i >= 0; i--) {
|
||||
pubelm = _storage.__jstorage_meta.PubSub[i];
|
||||
if (pubelm[0] > _pubsub_last) {
|
||||
_pubsubCurrent = pubelm[0];
|
||||
needFired.unshift(pubelm);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = needFired.length - 1; i >= 0; i--) {
|
||||
_fireSubscribers(needFired[i][1], needFired[i][2]);
|
||||
}
|
||||
|
||||
_pubsub_last = _pubsubCurrent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires all subscriber listeners for a pubsub channel
|
||||
*
|
||||
* @param {String} channel Channel name
|
||||
* @param {Mixed} payload Payload data to deliver
|
||||
*/
|
||||
function _fireSubscribers(channel, payload) {
|
||||
if (_pubsub_observers[channel]) {
|
||||
for (var i = 0, len = _pubsub_observers[channel].length; i < len; i++) {
|
||||
// send immutable data that can't be modified by listeners
|
||||
try {
|
||||
_pubsub_observers[channel][i](channel, JSON.parse(JSON.stringify(payload)));
|
||||
} catch (E) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove old events from the publish stream (at least 2sec old)
|
||||
*/
|
||||
function _dropOldEvents() {
|
||||
if (!_storage.__jstorage_meta.PubSub) {
|
||||
return;
|
||||
}
|
||||
|
||||
var retire = +new Date() - 2000;
|
||||
|
||||
for (var i = 0, len = _storage.__jstorage_meta.PubSub.length; i < len; i++) {
|
||||
if (_storage.__jstorage_meta.PubSub[i][0] <= retire) {
|
||||
// deleteCount is needed for IE6
|
||||
_storage.__jstorage_meta.PubSub.splice(i, _storage.__jstorage_meta.PubSub.length - i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_storage.__jstorage_meta.PubSub.length) {
|
||||
delete _storage.__jstorage_meta.PubSub;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish payload to a channel
|
||||
*
|
||||
* @param {String} channel Channel name
|
||||
* @param {Mixed} payload Payload to send to the subscribers
|
||||
*/
|
||||
function _publish(channel, payload) {
|
||||
if (!_storage.__jstorage_meta) {
|
||||
_storage.__jstorage_meta = {};
|
||||
}
|
||||
if (!_storage.__jstorage_meta.PubSub) {
|
||||
_storage.__jstorage_meta.PubSub = [];
|
||||
}
|
||||
|
||||
_storage.__jstorage_meta.PubSub.unshift([+new Date(), channel, payload]);
|
||||
|
||||
_save();
|
||||
_publishChange();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* JS Implementation of MurmurHash2
|
||||
*
|
||||
* SOURCE: https://github.com/garycourt/murmurhash-js (MIT licensed)
|
||||
*
|
||||
* @author <a href='mailto:gary.court@gmail.com'>Gary Court</a>
|
||||
* @see http://github.com/garycourt/murmurhash-js
|
||||
* @author <a href='mailto:aappleby@gmail.com'>Austin Appleby</a>
|
||||
* @see http://sites.google.com/site/murmurhash/
|
||||
*
|
||||
* @param {string} str ASCII only
|
||||
* @param {number} seed Positive integer only
|
||||
* @return {number} 32-bit positive integer hash
|
||||
*/
|
||||
|
||||
function murmurhash2_32_gc(str, seed) {
|
||||
var
|
||||
l = str.length,
|
||||
h = seed ^ l,
|
||||
i = 0,
|
||||
k;
|
||||
|
||||
while (l >= 4) {
|
||||
k =
|
||||
((str.charCodeAt(i) & 0xff)) |
|
||||
((str.charCodeAt(++i) & 0xff) << 8) |
|
||||
((str.charCodeAt(++i) & 0xff) << 16) |
|
||||
((str.charCodeAt(++i) & 0xff) << 24);
|
||||
|
||||
k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
|
||||
k ^= k >>> 24;
|
||||
k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
|
||||
|
||||
h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k;
|
||||
|
||||
l -= 4;
|
||||
++i;
|
||||
}
|
||||
|
||||
switch (l) {
|
||||
case 3:
|
||||
h ^= (str.charCodeAt(i + 2) & 0xff) << 16;
|
||||
/* falls through */
|
||||
case 2:
|
||||
h ^= (str.charCodeAt(i + 1) & 0xff) << 8;
|
||||
/* falls through */
|
||||
case 1:
|
||||
h ^= (str.charCodeAt(i) & 0xff);
|
||||
h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
|
||||
}
|
||||
|
||||
h ^= h >>> 13;
|
||||
h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
|
||||
h ^= h >>> 15;
|
||||
|
||||
return h >>> 0;
|
||||
}
|
||||
|
||||
////////////////////////// PUBLIC INTERFACE /////////////////////////
|
||||
|
||||
$.jStorage = {
|
||||
/* Version number */
|
||||
version: JSTORAGE_VERSION,
|
||||
|
||||
/**
|
||||
* Sets a key's value.
|
||||
*
|
||||
* @param {String} key Key to set. If this value is not set or not
|
||||
* a string an exception is raised.
|
||||
* @param {Mixed} value Value to set. This can be any value that is JSON
|
||||
* compatible (Numbers, Strings, Objects etc.).
|
||||
* @param {Object} [options] - possible options to use
|
||||
* @param {Number} [options.TTL] - optional TTL value, in milliseconds
|
||||
* @return {Mixed} the used value
|
||||
*/
|
||||
set: function(key, value, options) {
|
||||
_checkKey(key);
|
||||
|
||||
options = options || {};
|
||||
|
||||
// undefined values are deleted automatically
|
||||
if (typeof value == 'undefined') {
|
||||
this.deleteKey(key);
|
||||
return value;
|
||||
}
|
||||
|
||||
if (_XMLService.isXML(value)) {
|
||||
value = {
|
||||
_is_xml: true,
|
||||
xml: _XMLService.encode(value)
|
||||
};
|
||||
} else if (typeof value == 'function') {
|
||||
return undefined; // functions can't be saved!
|
||||
} else if (value && typeof value == 'object') {
|
||||
// clone the object before saving to _storage tree
|
||||
value = JSON.parse(JSON.stringify(value));
|
||||
}
|
||||
|
||||
_storage[key] = value;
|
||||
|
||||
_storage.__jstorage_meta.CRC32[key] = '2.' + murmurhash2_32_gc(JSON.stringify(value), 0x9747b28c);
|
||||
|
||||
this.setTTL(key, options.TTL || 0); // also handles saving and _publishChange
|
||||
|
||||
_fireObservers(key, 'updated');
|
||||
return value;
|
||||
},
|
||||
|
||||
/**
|
||||
* Looks up a key in cache
|
||||
*
|
||||
* @param {String} key - Key to look up.
|
||||
* @param {mixed} def - Default value to return, if key didn't exist.
|
||||
* @return {Mixed} the key value, default value or null
|
||||
*/
|
||||
get: function(key, def) {
|
||||
_checkKey(key);
|
||||
if (key in _storage) {
|
||||
if (_storage[key] && typeof _storage[key] == 'object' && _storage[key]._is_xml) {
|
||||
return _XMLService.decode(_storage[key].xml);
|
||||
} else {
|
||||
return _storage[key];
|
||||
}
|
||||
}
|
||||
return typeof(def) == 'undefined' ? null : def;
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes a key from cache.
|
||||
*
|
||||
* @param {String} key - Key to delete.
|
||||
* @return {Boolean} true if key existed or false if it didn't
|
||||
*/
|
||||
deleteKey: function(key) {
|
||||
_checkKey(key);
|
||||
if (key in _storage) {
|
||||
delete _storage[key];
|
||||
// remove from TTL list
|
||||
if (typeof _storage.__jstorage_meta.TTL == 'object' &&
|
||||
key in _storage.__jstorage_meta.TTL) {
|
||||
delete _storage.__jstorage_meta.TTL[key];
|
||||
}
|
||||
|
||||
delete _storage.__jstorage_meta.CRC32[key];
|
||||
|
||||
_save();
|
||||
_publishChange();
|
||||
_fireObservers(key, 'deleted');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets a TTL for a key, or remove it if ttl value is 0 or below
|
||||
*
|
||||
* @param {String} key - key to set the TTL for
|
||||
* @param {Number} ttl - TTL timeout in milliseconds
|
||||
* @return {Boolean} true if key existed or false if it didn't
|
||||
*/
|
||||
setTTL: function(key, ttl) {
|
||||
var curtime = +new Date();
|
||||
_checkKey(key);
|
||||
ttl = Number(ttl) || 0;
|
||||
if (key in _storage) {
|
||||
|
||||
if (!_storage.__jstorage_meta.TTL) {
|
||||
_storage.__jstorage_meta.TTL = {};
|
||||
}
|
||||
|
||||
// Set TTL value for the key
|
||||
if (ttl > 0) {
|
||||
_storage.__jstorage_meta.TTL[key] = curtime + ttl;
|
||||
} else {
|
||||
delete _storage.__jstorage_meta.TTL[key];
|
||||
}
|
||||
|
||||
_save();
|
||||
|
||||
_handleTTL();
|
||||
|
||||
_publishChange();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets remaining TTL (in milliseconds) for a key or 0 when no TTL has been set
|
||||
*
|
||||
* @param {String} key Key to check
|
||||
* @return {Number} Remaining TTL in milliseconds
|
||||
*/
|
||||
getTTL: function(key) {
|
||||
var curtime = +new Date(),
|
||||
ttl;
|
||||
_checkKey(key);
|
||||
if (key in _storage && _storage.__jstorage_meta.TTL && _storage.__jstorage_meta.TTL[key]) {
|
||||
ttl = _storage.__jstorage_meta.TTL[key] - curtime;
|
||||
return ttl || 0;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes everything in cache.
|
||||
*
|
||||
* @return {Boolean} Always true
|
||||
*/
|
||||
flush: function() {
|
||||
_storage = {
|
||||
__jstorage_meta: {
|
||||
CRC32: {}
|
||||
}
|
||||
};
|
||||
_save();
|
||||
_publishChange();
|
||||
_fireObservers(null, 'flushed');
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a read-only copy of _storage
|
||||
*
|
||||
* @return {Object} Read-only copy of _storage
|
||||
*/
|
||||
storageObj: function() {
|
||||
function F() {}
|
||||
F.prototype = _storage;
|
||||
return new F();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an index of all used keys as an array
|
||||
* ['key1', 'key2',..'keyN']
|
||||
*
|
||||
* @return {Array} Used keys
|
||||
*/
|
||||
index: function() {
|
||||
var index = [],
|
||||
i;
|
||||
for (i in _storage) {
|
||||
if (_storage.hasOwnProperty(i) && i != '__jstorage_meta') {
|
||||
index.push(i);
|
||||
}
|
||||
}
|
||||
return index;
|
||||
},
|
||||
|
||||
/**
|
||||
* How much space in bytes does the storage take?
|
||||
*
|
||||
* @return {Number} Storage size in chars (not the same as in bytes,
|
||||
* since some chars may take several bytes)
|
||||
*/
|
||||
storageSize: function() {
|
||||
return _storage_size;
|
||||
},
|
||||
|
||||
/**
|
||||
* Which backend is currently in use?
|
||||
*
|
||||
* @return {String} Backend name
|
||||
*/
|
||||
currentBackend: function() {
|
||||
return _backend;
|
||||
},
|
||||
|
||||
/**
|
||||
* Test if storage is available
|
||||
*
|
||||
* @return {Boolean} True if storage can be used
|
||||
*/
|
||||
storageAvailable: function() {
|
||||
return !!_backend;
|
||||
},
|
||||
|
||||
/**
|
||||
* Register change listeners
|
||||
*
|
||||
* @param {String} key Key name
|
||||
* @param {Function} callback Function to run when the key changes
|
||||
*/
|
||||
listenKeyChange: function(key, callback) {
|
||||
_checkKey(key);
|
||||
if (!_observers[key]) {
|
||||
_observers[key] = [];
|
||||
}
|
||||
_observers[key].push(callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove change listeners
|
||||
*
|
||||
* @param {String} key Key name to unregister listeners against
|
||||
* @param {Function} [callback] If set, unregister the callback, if not - unregister all
|
||||
*/
|
||||
stopListening: function(key, callback) {
|
||||
_checkKey(key);
|
||||
|
||||
if (!_observers[key]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!callback) {
|
||||
delete _observers[key];
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = _observers[key].length - 1; i >= 0; i--) {
|
||||
if (_observers[key][i] == callback) {
|
||||
_observers[key].splice(i, 1);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Subscribe to a Publish/Subscribe event stream
|
||||
*
|
||||
* @param {String} channel Channel name
|
||||
* @param {Function} callback Function to run when the something is published to the channel
|
||||
*/
|
||||
subscribe: function(channel, callback) {
|
||||
channel = (channel || '').toString();
|
||||
if (!channel) {
|
||||
throw new TypeError('Channel not defined');
|
||||
}
|
||||
if (!_pubsub_observers[channel]) {
|
||||
_pubsub_observers[channel] = [];
|
||||
}
|
||||
_pubsub_observers[channel].push(callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Publish data to an event stream
|
||||
*
|
||||
* @param {String} channel Channel name
|
||||
* @param {Mixed} payload Payload to deliver
|
||||
*/
|
||||
publish: function(channel, payload) {
|
||||
channel = (channel || '').toString();
|
||||
if (!channel) {
|
||||
throw new TypeError('Channel not defined');
|
||||
}
|
||||
|
||||
_publish(channel, payload);
|
||||
},
|
||||
|
||||
/**
|
||||
* Reloads the data from browser storage
|
||||
*/
|
||||
reInit: function() {
|
||||
_reloadData();
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes reference from global objects and saves it as jStorage
|
||||
*
|
||||
* @param {Boolean} option if needed to save object as simple 'jStorage' in windows context
|
||||
*/
|
||||
noConflict: function(saveInGlobal) {
|
||||
delete window.$.jStorage;
|
||||
|
||||
if (saveInGlobal) {
|
||||
window.jStorage = this;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize jStorage
|
||||
_init();
|
||||
|
||||
})();
|
1894
js/script.js
|
@ -1,9 +0,0 @@
|
|||
[main]
|
||||
host = https://www.transifex.com
|
||||
lang_map = ja_JP: ja
|
||||
|
||||
[nextcloud.maps]
|
||||
file_filter = <lang>/maps.po
|
||||
source_file = templates/maps.pot
|
||||
source_lang = en
|
||||
type = PO
|
|
@ -1,8 +0,0 @@
|
|||
OC.L10N.register(
|
||||
"maps",
|
||||
{
|
||||
"Maps" : "خرائط",
|
||||
"left" : "يسار",
|
||||
"right" : "يمين"
|
||||
},
|
||||
"nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;");
|
|
@ -1,6 +0,0 @@
|
|||
{ "translations": {
|
||||
"Maps" : "خرائط",
|
||||
"left" : "يسار",
|
||||
"right" : "يمين"
|
||||
},"pluralForm" :"nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
OC.L10N.register(
|
||||
"maps",
|
||||
{
|
||||
"Maps" : "Mapes",
|
||||
"left" : "Esquierda",
|
||||
"right" : "Derecha"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
|
@ -1,6 +0,0 @@
|
|||
{ "translations": {
|
||||
"Maps" : "Mapes",
|
||||
"left" : "Esquierda",
|
||||
"right" : "Derecha"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
OC.L10N.register(
|
||||
"maps",
|
||||
{
|
||||
"Maps" : "Xəritələr",
|
||||
"left" : "Sol",
|
||||
"right" : "Sağ"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
|
@ -1,6 +0,0 @@
|
|||
{ "translations": {
|
||||
"Maps" : "Xəritələr",
|
||||
"left" : "Sol",
|
||||
"right" : "Sağ"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
OC.L10N.register(
|
||||
"maps",
|
||||
{
|
||||
"Maps" : "Карти",
|
||||
"left" : "ляво",
|
||||
"right" : "дясно"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
|
@ -1,6 +0,0 @@
|
|||
{ "translations": {
|
||||
"Maps" : "Карти",
|
||||
"left" : "ляво",
|
||||
"right" : "дясно"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
OC.L10N.register(
|
||||
"maps",
|
||||
{
|
||||
"Maps" : "মানচিত্র",
|
||||
"left" : "বাম",
|
||||
"right" : "ডান"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
|
@ -1,6 +0,0 @@
|
|||
{ "translations": {
|
||||
"Maps" : "মানচিত্র",
|
||||
"left" : "বাম",
|
||||
"right" : "ডান"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
OC.L10N.register(
|
||||
"maps",
|
||||
{
|
||||
"Maps" : "Mapes",
|
||||
"left" : "esquerra",
|
||||
"right" : "dreta"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
|
@ -1,6 +0,0 @@
|
|||
{ "translations": {
|
||||
"Maps" : "Mapes",
|
||||
"left" : "esquerra",
|
||||
"right" : "dreta"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
OC.L10N.register(
|
||||
"maps",
|
||||
{
|
||||
"Maps" : "Mapy",
|
||||
"left" : "vlevo",
|
||||
"right" : "vpravo"
|
||||
},
|
||||
"nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;");
|
|
@ -1,6 +0,0 @@
|
|||
{ "translations": {
|
||||
"Maps" : "Mapy",
|
||||
"left" : "vlevo",
|
||||
"right" : "vpravo"
|
||||
},"pluralForm" :"nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
OC.L10N.register(
|
||||
"maps",
|
||||
{
|
||||
"Maps" : "Kort",
|
||||
"left" : "venstre",
|
||||
"right" : "højre"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
|
@ -1,6 +0,0 @@
|
|||
{ "translations": {
|
||||
"Maps" : "Kort",
|
||||
"left" : "venstre",
|
||||
"right" : "højre"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
OC.L10N.register(
|
||||
"maps",
|
||||
{
|
||||
"Maps" : "Karten",
|
||||
"left" : "Links",
|
||||
"right" : "Rechts"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
|
@ -1,6 +0,0 @@
|
|||
{ "translations": {
|
||||
"Maps" : "Karten",
|
||||
"left" : "Links",
|
||||
"right" : "Rechts"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
OC.L10N.register(
|
||||
"maps",
|
||||
{
|
||||
"Maps" : "Karten",
|
||||
"left" : "Links",
|
||||
"right" : "Rechts"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
|
@ -1,6 +0,0 @@
|
|||
{ "translations": {
|
||||
"Maps" : "Karten",
|
||||
"left" : "Links",
|
||||
"right" : "Rechts"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
OC.L10N.register(
|
||||
"maps",
|
||||
{
|
||||
"Maps" : "Χάρτες",
|
||||
"left" : "αριστερά",
|
||||
"right" : "δεξιά"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
|
@ -1,6 +0,0 @@
|
|||
{ "translations": {
|
||||
"Maps" : "Χάρτες",
|
||||
"left" : "αριστερά",
|
||||
"right" : "δεξιά"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
OC.L10N.register(
|
||||
"maps",
|
||||
{
|
||||
"Maps" : "Maps",
|
||||
"left" : "left",
|
||||
"right" : "right"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
|
@ -1,6 +0,0 @@
|
|||
{ "translations": {
|
||||
"Maps" : "Maps",
|
||||
"left" : "left",
|
||||
"right" : "right"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
OC.L10N.register(
|
||||
"maps",
|
||||
{
|
||||
"Maps" : "Mapoj",
|
||||
"left" : "maldekstro",
|
||||
"right" : "dekstro"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
|
@ -1,6 +0,0 @@
|
|||
{ "translations": {
|
||||
"Maps" : "Mapoj",
|
||||
"left" : "maldekstro",
|
||||
"right" : "dekstro"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
OC.L10N.register(
|
||||
"maps",
|
||||
{
|
||||
"Maps" : "Mapas",
|
||||
"left" : "izquierda",
|
||||
"right" : "derecha"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
|
@ -1,6 +0,0 @@
|
|||
{ "translations": {
|
||||
"Maps" : "Mapas",
|
||||
"left" : "izquierda",
|
||||
"right" : "derecha"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
OC.L10N.register(
|
||||
"maps",
|
||||
{
|
||||
"Maps" : "Mapas",
|
||||
"left" : "izquierda",
|
||||
"right" : "derecha"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
|
@ -1,6 +0,0 @@
|
|||
{ "translations": {
|
||||
"Maps" : "Mapas",
|
||||
"left" : "izquierda",
|
||||
"right" : "derecha"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
OC.L10N.register(
|
||||
"maps",
|
||||
{
|
||||
"Maps" : "Kaardid",
|
||||
"left" : "vasakul",
|
||||
"right" : "paremal"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
|
@ -1,6 +0,0 @@
|
|||
{ "translations": {
|
||||
"Maps" : "Kaardid",
|
||||
"left" : "vasakul",
|
||||
"right" : "paremal"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
OC.L10N.register(
|
||||
"maps",
|
||||
{
|
||||
"Maps" : "Mapak",
|
||||
"left" : "ezkerra",
|
||||
"right" : "eskuina"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
|
@ -1,6 +0,0 @@
|
|||
{ "translations": {
|
||||
"Maps" : "Mapak",
|
||||
"left" : "ezkerra",
|
||||
"right" : "eskuina"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
OC.L10N.register(
|
||||
"maps",
|
||||
{
|
||||
"Maps" : "نقشهها",
|
||||
"left" : "چپ",
|
||||
"right" : "راست"
|
||||
},
|
||||
"nplurals=1; plural=0;");
|
|
@ -1,6 +0,0 @@
|
|||
{ "translations": {
|
||||
"Maps" : "نقشهها",
|
||||
"left" : "چپ",
|
||||
"right" : "راست"
|
||||
},"pluralForm" :"nplurals=1; plural=0;"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
OC.L10N.register(
|
||||
"maps",
|
||||
{
|
||||
"Maps" : "Kartat",
|
||||
"left" : "vasen",
|
||||
"right" : "oikea"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
|
@ -1,6 +0,0 @@
|
|||
{ "translations": {
|
||||
"Maps" : "Kartat",
|
||||
"left" : "vasen",
|
||||
"right" : "oikea"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
OC.L10N.register(
|
||||
"maps",
|
||||
{
|
||||
"Maps" : "Cartes",
|
||||
"left" : "gauche",
|
||||
"right" : "droite"
|
||||
},
|
||||
"nplurals=2; plural=(n > 1);");
|
|
@ -1,6 +0,0 @@
|
|||
{ "translations": {
|
||||
"Maps" : "Cartes",
|
||||
"left" : "gauche",
|
||||
"right" : "droite"
|
||||
},"pluralForm" :"nplurals=2; plural=(n > 1);"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
OC.L10N.register(
|
||||
"maps",
|
||||
{
|
||||
"Maps" : "Mapas",
|
||||
"left" : "esquerda",
|
||||
"right" : "dereita"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
|
@ -1,6 +0,0 @@
|
|||
{ "translations": {
|
||||
"Maps" : "Mapas",
|
||||
"left" : "esquerda",
|
||||
"right" : "dereita"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
OC.L10N.register(
|
||||
"maps",
|
||||
{
|
||||
"Maps" : "מפות",
|
||||
"left" : "שמאל",
|
||||
"right" : "ימין"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
|
@ -1,6 +0,0 @@
|
|||
{ "translations": {
|
||||
"Maps" : "מפות",
|
||||
"left" : "שמאל",
|
||||
"right" : "ימין"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
OC.L10N.register(
|
||||
"maps",
|
||||
{
|
||||
"Maps" : "Karte",
|
||||
"left" : "lijevo",
|
||||
"right" : "Desno"
|
||||
},
|
||||
"nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;");
|
|
@ -1,6 +0,0 @@
|
|||
{ "translations": {
|
||||
"Maps" : "Karte",
|
||||
"left" : "lijevo",
|
||||
"right" : "Desno"
|
||||
},"pluralForm" :"nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
OC.L10N.register(
|
||||
"maps",
|
||||
{
|
||||
"Maps" : "Térképek",
|
||||
"left" : "bal",
|
||||
"right" : "jobb"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
|
@ -1,6 +0,0 @@
|
|||
{ "translations": {
|
||||
"Maps" : "Térképek",
|
||||
"left" : "bal",
|
||||
"right" : "jobb"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
OC.L10N.register(
|
||||
"maps",
|
||||
{
|
||||
"Maps" : "Peta",
|
||||
"left" : "kiri",
|
||||
"right" : "kanan"
|
||||
},
|
||||
"nplurals=1; plural=0;");
|
|
@ -1,6 +0,0 @@
|
|||
{ "translations": {
|
||||
"Maps" : "Peta",
|
||||
"left" : "kiri",
|
||||
"right" : "kanan"
|
||||
},"pluralForm" :"nplurals=1; plural=0;"
|
||||
}
|