start rework from app store template

This commit is contained in:
Vinzenz Rosenkranz 2017-08-23 13:51:37 +02:00
Родитель f71fad3751
Коммит ca18c55129
538 изменённых файлов: 2434 добавлений и 32592 удалений

Просмотреть файл

@ -1,3 +1,3 @@
{
"directory": "vendor"
"directory": "js/vendor"
}

70
.gitignore поставляемый Normal file
Просмотреть файл

@ -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

Просмотреть файл

@ -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>

Просмотреть файл

@ -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/>.

174
Makefile Normal file
Просмотреть файл

@ -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

Просмотреть файл

@ -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>

13
appinfo/info_old.xml Normal file
Просмотреть файл

@ -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'],
]
];

41
appinfo/routes_old.php Normal file
Просмотреть файл

@ -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'),
)));

Просмотреть файл

@ -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;
}
}
}
}

10
css/style.css Normal file
Просмотреть файл

@ -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);
}
}

56
img/app.svg Normal file
Просмотреть файл

@ -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

996
js/3rdparty/jstorage/jstorage.js поставляемый
Просмотреть файл

@ -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();
})();

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -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;"
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше