diff --git a/.htaccess b/.htaccess new file mode 100644 index 000000000..9a040817d --- /dev/null +++ b/.htaccess @@ -0,0 +1,9 @@ +Options +FollowSymlinks -MultiViews -Indexes + +RewriteEngine on + +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^rest/v1/(.*)$ rest.php/$1 [L] + +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^(.*)$ index.php/$1 [L] diff --git a/README.txt b/README.txt new file mode 100644 index 000000000..8460ec6ae --- /dev/null +++ b/README.txt @@ -0,0 +1,13 @@ +ONKI Light, version 0.3 + +Controlled vocabularies are used by indexers describing documents and +searchers looking for suitable keywords. ONKI Light is a web-based tool +providing vocabulary services, such as concept lookup via term searches and +hierarchical browsing. Vocabularies are accessed via SPARQL endpoints +containing SKOS vocabularies. + +For further information see the online documentation on Google Code: +http://code.google.com/p/onki-light/ + +Installation: http://code.google.com/p/onki-light/wiki/Installation +Release notes: http://code.google.com/p/onki-light/wiki/ReleaseNotes diff --git a/composer.json b/composer.json new file mode 100644 index 000000000..2dd762646 --- /dev/null +++ b/composer.json @@ -0,0 +1,16 @@ +{ + "require": { + "components/jquery": "1.10.*", + "components/jqueryui": "1.10.*", + "easyrdf/easyrdf": "0.8.*", + "twig/twig": "1.13.*", + "twig/extensions": "1.*", + "twitter/bootstrap": "3.0.0", + "willdurand/negotiation": "1.3.1", + "ml/json-ld": "1.*" + }, + "require-dev": { + "umpirsky/twig-gettext-extractor": "1.1.*", + "phpdocumentor/phpdocumentor": "2.*" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 000000000..ce402f7f7 --- /dev/null +++ b/composer.lock @@ -0,0 +1,3764 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" + ], + "hash": "74477ea2115415ecd12b6db8dbe6c7f9", + "packages": [ + { + "name": "components/jquery", + "version": "1.10.2", + "source": { + "type": "git", + "url": "https://github.com/components/jquery.git", + "reference": "6b2390db24ba3490ca75251eec4888f7342bf4da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/components/jquery/zipball/6b2390db24ba3490ca75251eec4888f7342bf4da", + "reference": "6b2390db24ba3490ca75251eec4888f7342bf4da", + "shasum": "" + }, + "require": { + "robloach/component-installer": "*" + }, + "type": "component", + "extra": { + "component": { + "scripts": [ + "jquery.js" + ], + "files": [ + "jquery.min.js", + "jquery-migrate.js", + "jquery-migrate.min.js" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Resig", + "email": "jeresig@gmail.com", + "homepage": "http://ejohn.org/" + } + ], + "description": "jQuery JavaScript Library", + "homepage": "http://jquery.com", + "time": "2013-07-04 15:59:41" + }, + { + "name": "components/jqueryui", + "version": "1.10.4", + "source": { + "type": "git", + "url": "https://github.com/components/jqueryui.git", + "reference": "51bc3549dd6530a18f43be45c3e8ae520805b9e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/components/jqueryui/zipball/51bc3549dd6530a18f43be45c3e8ae520805b9e4", + "reference": "51bc3549dd6530a18f43be45c3e8ae520805b9e4", + "shasum": "" + }, + "require": { + "components/jquery": ">=1.8" + }, + "type": "component", + "extra": { + "component": { + "name": "jquery-ui", + "scripts": [ + "ui/jquery-ui.js" + ], + "files": [ + "ui/**", + "themes/**" + ], + "shim": { + "deps": [ + "jquery" + ], + "exports": "jQuery" + } + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "jQuery UI Team", + "homepage": "http://jqueryui.com/about" + }, + { + "name": "Joern Zaefferer", + "email": "joern.zaefferer@gmail.com", + "homepage": "http://bassistance.de" + }, + { + "name": "Scott Gonzalez", + "email": "scott.gonzalez@gmail.com", + "homepage": "http://scottgonzalez.com" + }, + { + "name": "Kris Borchers", + "email": "kris.borchers@gmail.com", + "homepage": "http://krisborchers.com" + }, + { + "name": "Corey Frang", + "email": "gnarf37@gmail.com", + "homepage": "http://gnarf.net", + "role": "Developer" + }, + { + "name": "Mike Sherov", + "email": "mike.sherov@gmail.com", + "homepage": "http://mike.sherov.com" + }, + { + "name": "TJ VanToll", + "email": "tj.vantoll@gmail.com", + "homepage": "http://tjvantoll.com" + } + ], + "description": "jQuery UI is a curated set of user interface interactions, effects, widgets, and themes built on top of the jQuery JavaScript Library. Whether you're building highly interactive web applications or you just need to add a date picker to a form control, jQuery UI is the perfect choice.", + "time": "2014-02-19 12:22:40" + }, + { + "name": "easyrdf/easyrdf", + "version": "0.8.0", + "source": { + "type": "git", + "url": "https://github.com/njh/easyrdf.git", + "reference": "3e43ab7274004e9f4192e06b9fc147781e1f85c2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/njh/easyrdf/zipball/3e43ab7274004e9f4192e06b9fc147781e1f85c2", + "reference": "3e43ab7274004e9f4192e06b9fc147781e1f85c2", + "shasum": "" + }, + "require": { + "php": ">=5.2.8" + }, + "replace": { + "njh/easyrdf": "self.version" + }, + "require-dev": { + "phpunit/phpunit": ">=3.5.15", + "sami/sami": "dev-master", + "squizlabs/php_codesniffer": ">=1.4.3" + }, + "suggest": { + "ml/json-ld": "dev-master" + }, + "type": "library", + "autoload": { + "psr-0": { + "EasyRdf_": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nicholas Humfrey", + "email": "njh@aelius.com", + "homepage": "http://www.aelius.com/njh/", + "role": "Developer" + } + ], + "description": "EasyRdf is a PHP library designed to make it easy to consume and produce RDF.", + "homepage": "http://www.easyrdf.org/", + "keywords": [ + "Linked Data", + "RDF", + "Semantic Web", + "Turtle", + "rdfa", + "sparql" + ], + "time": "2013-12-30 22:31:37" + }, + { + "name": "kriswallsmith/assetic", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/kriswallsmith/assetic.git", + "reference": "735cffd3982c6e8cdebe292d5db39d077f65890f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kriswallsmith/assetic/zipball/735cffd3982c6e8cdebe292d5db39d077f65890f", + "reference": "735cffd3982c6e8cdebe292d5db39d077f65890f", + "shasum": "" + }, + "require": { + "php": ">=5.3.1", + "symfony/process": "~2.1" + }, + "require-dev": { + "cssmin/cssmin": "*", + "joliclic/javascript-packer": "*", + "kamicane/packager": "*", + "leafo/lessphp": "*", + "leafo/scssphp": "*", + "leafo/scssphp-compass": "*", + "mrclay/minify": "*", + "phpunit/phpunit": "~3.7", + "ptachoire/cssembed": "*", + "twig/twig": "~1.6" + }, + "suggest": { + "leafo/lessphp": "Assetic provides the integration with the lessphp LESS compiler", + "leafo/scssphp": "Assetic provides the integration with the scssphp SCSS compiler", + "leafo/scssphp-compass": "Assetic provides the integration with the SCSS compass plugin", + "ptachoire/cssembed": "Assetic provides the integration with phpcssembed to embed data uris", + "twig/twig": "Assetic provides the integration with the Twig templating engine" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-0": { + "Assetic": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kris Wallsmith", + "email": "kris.wallsmith@gmail.com", + "homepage": "http://kriswallsmith.net/" + } + ], + "description": "Asset Management for PHP", + "homepage": "https://github.com/kriswallsmith/assetic", + "keywords": [ + "assets", + "compression", + "minification" + ], + "time": "2013-07-19 00:03:27" + }, + { + "name": "ml/iri", + "version": "1.1.4", + "target-dir": "ML/IRI", + "source": { + "type": "git", + "url": "https://github.com/lanthaler/IRI.git", + "reference": "cbd44fa913e00ea624241b38cefaa99da8d71341" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lanthaler/IRI/zipball/cbd44fa913e00ea624241b38cefaa99da8d71341", + "reference": "cbd44fa913e00ea624241b38cefaa99da8d71341", + "shasum": "" + }, + "require": { + "lib-pcre": ">=4.0", + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "ML\\IRI": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Markus Lanthaler", + "email": "mail@markus-lanthaler.com", + "homepage": "http://www.markus-lanthaler.com", + "role": "Developer" + } + ], + "description": "IRI handling for PHP", + "homepage": "http://www.markus-lanthaler.com", + "keywords": [ + "URN", + "iri", + "uri", + "url" + ], + "time": "2014-01-21 13:43:39" + }, + { + "name": "ml/json-ld", + "version": "1.0.0", + "target-dir": "ML/JsonLD", + "source": { + "type": "git", + "url": "https://github.com/lanthaler/JsonLD.git", + "reference": "7fa16d5c0cacee0a184683d8b3c2172a84842997" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lanthaler/JsonLD/zipball/7fa16d5c0cacee0a184683d8b3c2172a84842997", + "reference": "7fa16d5c0cacee0a184683d8b3c2172a84842997", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ml/iri": ">=1.1.1", + "php": ">=5.3.0" + }, + "require-dev": { + "json-ld/tests": "@dev" + }, + "type": "library", + "autoload": { + "psr-0": { + "ML\\JsonLD": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Markus Lanthaler", + "email": "mail@markus-lanthaler.com", + "homepage": "http://www.markus-lanthaler.com", + "role": "Developer" + } + ], + "description": "JSON-LD Processor for PHP", + "homepage": "http://www.markus-lanthaler.com", + "keywords": [ + "JSON-LD", + "jsonld" + ], + "time": "2014-03-19 22:57:09" + }, + { + "name": "robloach/component-installer", + "version": "0.0.12", + "source": { + "type": "git", + "url": "https://github.com/RobLoach/component-installer.git", + "reference": "1864f25db21fc173e02a359f646acd596c1b0460" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/RobLoach/component-installer/zipball/1864f25db21fc173e02a359f646acd596c1b0460", + "reference": "1864f25db21fc173e02a359f646acd596c1b0460", + "shasum": "" + }, + "require": { + "kriswallsmith/assetic": "1.*", + "php": ">=5.3.2" + }, + "require-dev": { + "composer/composer": "1.*" + }, + "type": "composer-installer", + "extra": { + "class": "ComponentInstaller\\Installer" + }, + "autoload": { + "psr-0": { + "ComponentInstaller": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rob Loach", + "email": "robloach@gmail.com", + "homepage": "http://robloach.net" + } + ], + "description": "Allows installation of Components via Composer.", + "time": "2013-08-31 23:46:48" + }, + { + "name": "symfony/process", + "version": "v2.4.4", + "target-dir": "Symfony/Component/Process", + "source": { + "type": "git", + "url": "https://github.com/symfony/Process.git", + "reference": "8721f1476d5d38a43c7d6ccb6435b351cf8f3bb7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Process/zipball/8721f1476d5d38a43c7d6ccb6435b351cf8f3bb7", + "reference": "8721f1476d5d38a43c7d6ccb6435b351cf8f3bb7", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Process\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "http://symfony.com", + "time": "2014-04-27 13:34:57" + }, + { + "name": "twig/extensions", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/fabpot/Twig-extensions.git", + "reference": "f91a82ec225e5bb108e01a0f93c9be04f84dcfa0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fabpot/Twig-extensions/zipball/f91a82ec225e5bb108e01a0f93c9be04f84dcfa0", + "reference": "f91a82ec225e5bb108e01a0f93c9be04f84dcfa0", + "shasum": "" + }, + "require": { + "twig/twig": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Twig_Extensions_": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Common additional features for Twig that do not directly belong in core", + "homepage": "https://github.com/fabpot/Twig-extensions", + "keywords": [ + "debug", + "i18n", + "text" + ], + "time": "2013-10-18 19:37:15" + }, + { + "name": "twig/twig", + "version": "v1.13.2", + "source": { + "type": "git", + "url": "https://github.com/fabpot/Twig.git", + "reference": "v1.13.2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fabpot/Twig/zipball/v1.13.2", + "reference": "v1.13.2", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.13-dev" + } + }, + "autoload": { + "psr-0": { + "Twig_": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "http://twig.sensiolabs.org", + "keywords": [ + "templating" + ], + "time": "2013-08-03 15:35:31" + }, + { + "name": "twitter/bootstrap", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/twbs/bootstrap.git", + "reference": "e8a1df5f060bf7e6631554648e0abde150aedbe4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twbs/bootstrap/zipball/e8a1df5f060bf7e6631554648e0abde150aedbe4", + "reference": "e8a1df5f060bf7e6631554648e0abde150aedbe4", + "shasum": "" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "sugeng tigefa", + "email": "tigefa@gmail.com" + }, + { + "name": "Jacob Thornton", + "email": "jacobthornton@gmail.com" + } + ], + "description": "Sleek, intuitive, and powerful mobile first front-end framework for faster and easier web development.", + "homepage": "http://getbootstrap.com", + "keywords": [ + "bootstrap", + "css" + ], + "time": "2013-08-19 21:18:20" + }, + { + "name": "willdurand/negotiation", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/willdurand/Negotiation.git", + "reference": "e46ea1145b94b38a3e32073a24a8b79936a90121" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/willdurand/Negotiation/zipball/e46ea1145b94b38a3e32073a24a8b79936a90121", + "reference": "e46ea1145b94b38a3e32073a24a8b79936a90121", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-0": { + "Negotiation": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "William DURAND", + "email": "william.durand1@gmail.com", + "homepage": "http://www.willdurand.fr" + } + ], + "description": "Content Negotiation tools for PHP provided as a standalone library.", + "homepage": "http://williamdurand.fr/Negotiation/", + "keywords": [ + "accept", + "content", + "format", + "header", + "negotiation" + ], + "time": "2014-02-06 23:14:23" + } + ], + "packages-dev": [ + { + "name": "cilex/cilex", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/Cilex/Cilex.git", + "reference": "7acd965a609a56d0345e8b6071c261fbdb926cb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Cilex/Cilex/zipball/7acd965a609a56d0345e8b6071c261fbdb926cb5", + "reference": "7acd965a609a56d0345e8b6071c261fbdb926cb5", + "shasum": "" + }, + "require": { + "cilex/console-service-provider": "1.*", + "php": ">=5.3.3", + "pimple/pimple": "~1.0", + "symfony/finder": "~2.1", + "symfony/process": "~2.1" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*", + "symfony/validator": "~2.1" + }, + "suggest": { + "monolog/monolog": ">=1.0.0", + "symfony/validator": ">=1.0.0", + "symfony/yaml": ">=1.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "Cilex": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "description": "The PHP micro-framework for Command line tools based on the Symfony2 Components", + "homepage": "http://cilex.github.com", + "keywords": [ + "cli", + "microframework" + ], + "time": "2014-03-29 14:03:13" + }, + { + "name": "cilex/console-service-provider", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/Cilex/console-service-provider.git", + "reference": "25ee3d1875243d38e1a3448ff94bdf944f70d24e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Cilex/console-service-provider/zipball/25ee3d1875243d38e1a3448ff94bdf944f70d24e", + "reference": "25ee3d1875243d38e1a3448ff94bdf944f70d24e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "pimple/pimple": "1.*@dev", + "symfony/console": "~2.1" + }, + "require-dev": { + "cilex/cilex": "1.*@dev", + "silex/silex": "1.*@dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "Cilex\\Provider\\Console": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "description": "Console Service Provider", + "keywords": [ + "cilex", + "console", + "pimple", + "service-provider", + "silex" + ], + "time": "2012-12-19 10:50:58" + }, + { + "name": "doctrine/annotations", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "40db0c96985aab2822edbc4848b3bd2429e02670" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/40db0c96985aab2822edbc4848b3bd2429e02670", + "reference": "40db0c96985aab2822edbc4848b3bd2429e02670", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "php": ">=5.3.2" + }, + "require-dev": { + "doctrine/cache": "1.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Annotations\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonathan H. Wage", + "email": "jonwage@gmail.com", + "homepage": "http://www.jwage.com/", + "role": "Creator" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com", + "homepage": "http://www.instaclick.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "http://jmsyst.com", + "role": "Developer of wrapped JMSSerializerBundle" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "time": "2013-06-16 21:33:03" + }, + { + "name": "doctrine/lexer", + "version": "v1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "2f708a85bb3aab5d99dab8be435abd73e0b18acb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/2f708a85bb3aab5d99dab8be435abd73e0b18acb", + "reference": "2f708a85bb3aab5d99dab8be435abd73e0b18acb", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "autoload": { + "psr-0": { + "Doctrine\\Common\\Lexer\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com", + "homepage": "http://www.instaclick.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh", + "role": "Developer of wrapped JMSSerializerBundle" + } + ], + "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "lexer", + "parser" + ], + "time": "2013-01-12 18:59:04" + }, + { + "name": "dompdf/dompdf", + "version": "v0.6.1", + "source": { + "type": "git", + "url": "https://github.com/dompdf/dompdf.git", + "reference": "cf7d8a0a27270418850cc7d7ea532159e5eeb3eb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/cf7d8a0a27270418850cc7d7ea532159e5eeb3eb", + "reference": "cf7d8a0a27270418850cc7d7ea532159e5eeb3eb", + "shasum": "" + }, + "require": { + "phenx/php-font-lib": "0.2.*" + }, + "type": "library", + "autoload": { + "classmap": [ + "include/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL" + ], + "authors": [ + { + "name": "Fabien Ménager", + "email": "fabien.menager@gmail.com" + }, + { + "name": "Brian Sweeney", + "email": "eclecticgeek@gmail.com" + } + ], + "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", + "homepage": "https://github.com/dompdf/dompdf", + "time": "2014-03-11 01:59:52" + }, + { + "name": "erusev/parsedown", + "version": "0.9.4", + "source": { + "type": "git", + "url": "https://github.com/erusev/parsedown.git", + "reference": "d29ff18299210b52a75a631a70963e7c8b35b04f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/d29ff18299210b52a75a631a70963e7c8b35b04f", + "reference": "d29ff18299210b52a75a631a70963e7c8b35b04f", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-0": { + "Parsedown": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Emanuil Rusev", + "email": "hello@erusev.com", + "homepage": "http://erusev.com" + } + ], + "description": "Parser for Markdown.", + "homepage": "http://parsedown.org", + "keywords": [ + "markdown", + "parser" + ], + "time": "2014-02-06 12:16:14" + }, + { + "name": "jms/metadata", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/metadata.git", + "reference": "88ffa28bc987e4c26229fc84a2e541b6ed4e1459" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/metadata/zipball/88ffa28bc987e4c26229fc84a2e541b6ed4e1459", + "reference": "88ffa28bc987e4c26229fc84a2e541b6ed4e1459", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "doctrine/cache": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5.x-dev" + } + }, + "autoload": { + "psr-0": { + "Metadata\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "http://jmsyst.com", + "role": "Developer of wrapped JMSSerializerBundle" + } + ], + "description": "Class/method/property metadata management in PHP", + "keywords": [ + "annotations", + "metadata", + "xml", + "yaml" + ], + "time": "2013-11-05 23:02:36" + }, + { + "name": "jms/parser-lib", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/parser-lib.git", + "reference": "c509473bc1b4866415627af0e1c6cc8ac97fa51d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/parser-lib/zipball/c509473bc1b4866415627af0e1c6cc8ac97fa51d", + "reference": "c509473bc1b4866415627af0e1c6cc8ac97fa51d", + "shasum": "" + }, + "require": { + "phpoption/phpoption": ">=0.9,<2.0-dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "JMS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache2" + ], + "description": "A library for easily creating recursive-descent parsers.", + "time": "2012-11-18 18:08:43" + }, + { + "name": "jms/serializer", + "version": "0.16.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/serializer.git", + "reference": "c8a171357ca92b6706e395c757f334902d430ea9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/c8a171357ca92b6706e395c757f334902d430ea9", + "reference": "c8a171357ca92b6706e395c757f334902d430ea9", + "shasum": "" + }, + "require": { + "doctrine/annotations": "1.*", + "jms/metadata": "~1.1", + "jms/parser-lib": "1.*", + "php": ">=5.3.2", + "phpcollection/phpcollection": "~0.1" + }, + "require-dev": { + "doctrine/orm": "~2.1", + "doctrine/phpcr-odm": "~1.0.1", + "jackalope/jackalope-doctrine-dbal": "1.0.*", + "propel/propel1": "~1.7", + "symfony/filesystem": "2.*", + "symfony/form": "~2.1", + "symfony/translation": "~2.0", + "symfony/validator": "~2.0", + "symfony/yaml": "2.*", + "twig/twig": ">=1.8,<2.0-dev" + }, + "suggest": { + "symfony/yaml": "Required if you'd like to serialize data to YAML format." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.15-dev" + } + }, + "autoload": { + "psr-0": { + "JMS\\Serializer": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache2" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "http://jmsyst.com", + "role": "Developer of wrapped JMSSerializerBundle" + } + ], + "description": "Library for (de-)serializing data of any complexity; supports XML, JSON, and YAML.", + "homepage": "http://jmsyst.com/libs/serializer", + "keywords": [ + "deserialization", + "jaxb", + "json", + "serialization", + "xml" + ], + "time": "2014-03-18 08:39:00" + }, + { + "name": "knplabs/knp-menu", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/KnpLabs/KnpMenu.git", + "reference": "f8e867268f63f561c1adadd6cbb5d8524f921873" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/KnpLabs/KnpMenu/zipball/f8e867268f63f561c1adadd6cbb5d8524f921873", + "reference": "f8e867268f63f561c1adadd6cbb5d8524f921873", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "pimple/pimple": "*", + "silex/silex": "1.0.*", + "twig/twig": ">=1.2,<2.0-dev" + }, + "suggest": { + "pimple/pimple": "for the built-in implementations of the menu provider and renderer provider", + "silex/silex": "for the integration with your silex application", + "twig/twig": "for the TwigRenderer and the integration with your templates" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-0": { + "Knp\\Menu\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christophe Coevoet", + "email": "stof@notk.org" + }, + { + "name": "KnpLabs", + "homepage": "http://knplabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://github.com/KnpLabs/KnpMenu/contributors" + } + ], + "description": "An object oriented menu library", + "homepage": "http://knplabs.com", + "keywords": [ + "menu", + "tree" + ], + "time": "2012-06-10 16:20:40" + }, + { + "name": "monolog/monolog", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "65026b610f8c19e61d7242f600530677b0466aac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/65026b610f8c19e61d7242f600530677b0466aac", + "reference": "65026b610f8c19e61d7242f600530677b0466aac", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "require-dev": { + "aws/aws-sdk-php": "~2.4, >2.4.8", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "phpunit/phpunit": "~3.7.0", + "raven/raven": "~0.5", + "ruflin/elastica": "0.90.*" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "raven/raven": "Allow sending log messages to a Sentry server", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be", + "role": "Developer" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "time": "2014-04-24 13:29:03" + }, + { + "name": "nikic/php-parser", + "version": "v0.9.4", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "1e5e280ae88a27effa2ae4aa2bd088494ed8594f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1e5e280ae88a27effa2ae4aa2bd088494ed8594f", + "reference": "1e5e280ae88a27effa2ae4aa2bd088494ed8594f", + "shasum": "" + }, + "require": { + "php": ">=5.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.9-dev" + } + }, + "autoload": { + "psr-0": { + "PHPParser": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "time": "2013-08-25 17:11:40" + }, + { + "name": "phenx/php-font-lib", + "version": "0.2.2", + "source": { + "type": "git", + "url": "https://github.com/PhenX/php-font-lib.git", + "reference": "c30c7fc00a6b0d863e9bb4c5d5dd015298b2dc82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PhenX/php-font-lib/zipball/c30c7fc00a6b0d863e9bb4c5d5dd015298b2dc82", + "reference": "c30c7fc00a6b0d863e9bb4c5d5dd015298b2dc82", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "classes/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL" + ], + "authors": [ + { + "name": "Fabien Ménager", + "email": "fabien.menager@gmail.com" + } + ], + "description": "A library to read, parse, export and make subsets of different types of font files.", + "homepage": "https://github.com/PhenX/php-font-lib", + "time": "2014-02-01 15:22:28" + }, + { + "name": "phpcollection/phpcollection", + "version": "0.4.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-collection.git", + "reference": "b8bf55a0a929ca43b01232b36719f176f86c7e83" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-collection/zipball/b8bf55a0a929ca43b01232b36719f176f86c7e83", + "reference": "b8bf55a0a929ca43b01232b36719f176f86c7e83", + "shasum": "" + }, + "require": { + "phpoption/phpoption": "1.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.3-dev" + } + }, + "autoload": { + "psr-0": { + "PhpCollection": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache2" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "http://jmsyst.com", + "role": "Developer of wrapped JMSSerializerBundle" + } + ], + "description": "General-Purpose Collection Library for PHP", + "keywords": [ + "collection", + "list", + "map", + "sequence", + "set" + ], + "time": "2014-03-11 13:46:42" + }, + { + "name": "phpdocumentor/fileset", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/Fileset.git", + "reference": "bfa78d8fa9763dfce6d0e5d3730c1d8ab25d34b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/Fileset/zipball/bfa78d8fa9763dfce6d0e5d3730c1d8ab25d34b0", + "reference": "bfa78d8fa9763dfce6d0e5d3730c1d8ab25d34b0", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/finder": "~2.1" + }, + "require-dev": { + "phpunit/phpunit": "~3.7" + }, + "type": "library", + "autoload": { + "psr-0": { + "phpDocumentor": [ + "src/", + "tests/unit/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Fileset component for collecting a set of files given directories and file paths", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "files", + "fileset", + "phpdoc" + ], + "time": "2013-08-06 21:07:42" + }, + { + "name": "phpdocumentor/graphviz", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/GraphViz.git", + "reference": "13595130b9bc185109f40f1b70f0b231f490f5fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/GraphViz/zipball/13595130b9bc185109f40f1b70f0b231f490f5fc", + "reference": "13595130b9bc185109f40f1b70f0b231f490f5fc", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~3.7" + }, + "type": "library", + "autoload": { + "psr-0": { + "phpDocumentor": [ + "src/", + "tests/unit" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "time": "2014-02-26 17:45:01" + }, + { + "name": "phpdocumentor/phpdocumentor", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/phpDocumentor2.git", + "reference": "bf9fa40f6d00412410025b2e16eb16c315eb0216" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/phpDocumentor2/zipball/bf9fa40f6d00412410025b2e16eb16c315eb0216", + "reference": "bf9fa40f6d00412410025b2e16eb16c315eb0216", + "shasum": "" + }, + "require": { + "cilex/cilex": "~1.0", + "dompdf/dompdf": "~0.6", + "erusev/parsedown": "~0.7", + "jms/serializer": "~0.12", + "knplabs/knp-menu": "~1.1", + "monolog/monolog": "~1.6", + "php": ">=5.3.3", + "phpdocumentor/fileset": "~1.0", + "phpdocumentor/graphviz": "~1.0", + "phpdocumentor/reflection": "~1.0", + "phpdocumentor/reflection-docblock": "~2.0", + "phpdocumentor/template-abstract": "~1.2", + "phpdocumentor/template-checkstyle": "~1.2", + "phpdocumentor/template-clean": "~1.0", + "phpdocumentor/template-new-black": "~1.3", + "phpdocumentor/template-old-ocean": "~1.3", + "phpdocumentor/template-responsive": "~1.3", + "phpdocumentor/template-responsive-twig": "~1.2", + "phpdocumentor/template-xml": "~1.0", + "phpdocumentor/template-zend": "~1.3", + "symfony/config": "~2.3", + "symfony/console": "~2.3", + "symfony/event-dispatcher": "~2.1", + "symfony/process": "~2.0", + "symfony/stopwatch": "~2.3", + "symfony/validator": "~2.2", + "twig/twig": "~1.3", + "zendframework/zend-cache": "2.1.*", + "zendframework/zend-config": "2.1.*", + "zendframework/zend-filter": "2.1.*", + "zendframework/zend-i18n": "2.1.*", + "zendframework/zend-serializer": "2.1.*", + "zendframework/zend-servicemanager": "2.1.*", + "zendframework/zend-stdlib": "2.1.*", + "zetacomponents/document": ">=1.3.1" + }, + "require-dev": { + "behat/behat": "~2.4", + "mikey179/vfsstream": "~1.2", + "mockery/mockery": ">=0.8.0", + "phpunit/phpunit": "~3.7", + "squizlabs/php_codesniffer": "~1.4", + "symfony/expression-language": "~2.4" + }, + "suggest": { + "ext-twig": "Enabling the twig extension improves the generation of twig based templates.", + "ext-xslcache": "Enabling the XSLCache extension improves the generation of xml based templates." + }, + "bin": [ + "bin/phpdoc.php", + "bin/phpdoc" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-develop": "2.5-dev" + } + }, + "autoload": { + "psr-0": { + "phpDocumentor": [ + "src/", + "tests/unit/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Documentation Generator for PHP", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "api", + "application", + "dga", + "documentation", + "phpdoc" + ], + "time": "2014-05-17 12:25:35" + }, + { + "name": "phpdocumentor/reflection", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/Reflection.git", + "reference": "df82db631acd60739c8796b3c6d5e4da970808f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/Reflection/zipball/df82db631acd60739c8796b3c6d5e4da970808f3", + "reference": "df82db631acd60739c8796b3c6d5e4da970808f3", + "shasum": "" + }, + "require": { + "nikic/php-parser": "0.9.4", + "php": ">=5.3.3", + "phpdocumentor/reflection-docblock": "2.*", + "psr/log": "~1.0" + }, + "require-dev": { + "behat/behat": "~2.4", + "mockery/mockery": ">=0.7.0", + "phpunit/phpunit": "~3.7" + }, + "type": "library", + "autoload": { + "psr-0": { + "phpDocumentor": [ + "src/", + "tests/unit/", + "tests/mocks/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Reflection library to do Static Analysis for PHP Projects", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2014-03-28 11:20:22" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "0bca477a34baea39add016af90046f002a175619" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/0bca477a34baea39add016af90046f002a175619", + "reference": "0bca477a34baea39add016af90046f002a175619", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*@stable" + }, + "suggest": { + "dflydev/markdown": "1.0.*", + "erusev/parsedown": "~0.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "phpDocumentor": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "time": "2014-03-28 09:21:30" + }, + { + "name": "phpdocumentor/template-abstract", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/template.abstract.git", + "reference": "43fa2db351d7a150803397721e778f9dd8a20b47" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/template.abstract/zipball/43fa2db351d7a150803397721e778f9dd8a20b47", + "reference": "43fa2db351d7a150803397721e778f9dd8a20b47", + "shasum": "" + }, + "require": { + "ext-xsl": "*", + "phpdocumentor/unified-asset-installer": "~1.1" + }, + "type": "phpdocumentor-template", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Simple bright template for phpDocumentor", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "documentation", + "phpdoc", + "template" + ], + "time": "2013-08-02 06:11:13" + }, + { + "name": "phpdocumentor/template-checkstyle", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/template.checkstyle.git", + "reference": "22a45684e737c8c3ec3f1a12edb7743b7a82ac8b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/template.checkstyle/zipball/22a45684e737c8c3ec3f1a12edb7743b7a82ac8b", + "reference": "22a45684e737c8c3ec3f1a12edb7743b7a82ac8b", + "shasum": "" + }, + "require": { + "ext-xsl": "*", + "phpdocumentor/unified-asset-installer": "~1.1" + }, + "type": "phpdocumentor-template", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Checkstyle XML output template for phpDocumentor2", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "documentation", + "phpdoc", + "template" + ], + "time": "2013-08-01 19:43:19" + }, + { + "name": "phpdocumentor/template-clean", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/template.clean.git", + "reference": "78f2048c5ecd62f0b79dbac093687d78a66d1806" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/template.clean/zipball/78f2048c5ecd62f0b79dbac093687d78a66d1806", + "reference": "78f2048c5ecd62f0b79dbac093687d78a66d1806", + "shasum": "" + }, + "require": { + "phpdocumentor/unified-asset-installer": "~1.1" + }, + "type": "phpdocumentor-template", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A clean, responsive modern template for phpDocumentor for Twig aimed at usability", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "documentation", + "phpdoc", + "responsive", + "template" + ], + "time": "2014-03-29 08:22:15" + }, + { + "name": "phpdocumentor/template-new-black", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/template.new_black.git", + "reference": "be38beba2b2674be292f32f88efe8a60c658a139" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/template.new_black/zipball/be38beba2b2674be292f32f88efe8a60c658a139", + "reference": "be38beba2b2674be292f32f88efe8a60c658a139", + "shasum": "" + }, + "require": { + "ext-xsl": "*", + "phpdocumentor/template-abstract": "1.*", + "phpdocumentor/unified-asset-installer": "~1.1" + }, + "type": "phpdocumentor-template", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Web 2.0 template with dark sidebar for phpDocumentor", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "documentation", + "phpdoc", + "template" + ], + "time": "2013-08-02 06:16:30" + }, + { + "name": "phpdocumentor/template-old-ocean", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/template.old_ocean.git", + "reference": "3a0e2bcced4045a694d53b4607aad04e99d78489" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/template.old_ocean/zipball/3a0e2bcced4045a694d53b4607aad04e99d78489", + "reference": "3a0e2bcced4045a694d53b4607aad04e99d78489", + "shasum": "" + }, + "require": { + "ext-xsl": "*", + "phpdocumentor/unified-asset-installer": "~1.1" + }, + "type": "phpdocumentor-template", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Blue template with high contrast for the foreground", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "documentation", + "phpdoc", + "template" + ], + "time": "2013-08-02 06:21:07" + }, + { + "name": "phpdocumentor/template-responsive", + "version": "1.3.3", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/template.responsive.git", + "reference": "26f895a2ed3148e1686ae4d802f65a3ef04c04e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/template.responsive/zipball/26f895a2ed3148e1686ae4d802f65a3ef04c04e1", + "reference": "26f895a2ed3148e1686ae4d802f65a3ef04c04e1", + "shasum": "" + }, + "require": { + "ext-xsl": "*", + "phpdocumentor/unified-asset-installer": "~1.1" + }, + "type": "phpdocumentor-template", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Responsive modern template for phpDocumentor", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "documentation", + "phpdoc", + "template" + ], + "time": "2014-03-29 08:55:54" + }, + { + "name": "phpdocumentor/template-responsive-twig", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/template.responsive-twig.git", + "reference": "cd6d82be6a4626d865fd01d40aad170cea08db0a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/template.responsive-twig/zipball/cd6d82be6a4626d865fd01d40aad170cea08db0a", + "reference": "cd6d82be6a4626d865fd01d40aad170cea08db0a", + "shasum": "" + }, + "require": { + "phpdocumentor/unified-asset-installer": "~1.1" + }, + "type": "phpdocumentor-template", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Responsive modern template for phpDocumentor for Twig", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "documentation", + "phpdoc", + "template" + ], + "time": "2014-03-30 21:02:00" + }, + { + "name": "phpdocumentor/template-xml", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/mvriel/template.xml.git", + "reference": "a372713be8ee99b16497e2580592e474ff51190c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mvriel/template.xml/zipball/a372713be8ee99b16497e2580592e474ff51190c", + "reference": "a372713be8ee99b16497e2580592e474ff51190c", + "shasum": "" + }, + "require": { + "phpdocumentor/unified-asset-installer": "~1.1" + }, + "type": "phpdocumentor-template", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generates an XML representation of the project's structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "documentation", + "phpdoc", + "template" + ], + "time": "2013-08-01 20:23:32" + }, + { + "name": "phpdocumentor/template-zend", + "version": "1.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/template.zend.git", + "reference": "75913288bfd73d3bf4c1b1179c3963f3431e7a9d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/template.zend/zipball/75913288bfd73d3bf4c1b1179c3963f3431e7a9d", + "reference": "75913288bfd73d3bf4c1b1179c3963f3431e7a9d", + "shasum": "" + }, + "require": { + "ext-xsl": "*", + "phpdocumentor/template-abstract": "1.*", + "phpdocumentor/unified-asset-installer": "~1.1" + }, + "type": "phpdocumentor-template", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Official Zend Framework Template for phpDocumentor2", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "ZendFramework", + "documentation", + "phpdoc", + "template", + "zend", + "zf" + ], + "time": "2013-12-05 08:51:57" + }, + { + "name": "phpdocumentor/unified-asset-installer", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/UnifiedAssetInstaller.git", + "reference": "241fb036268cd9da7d76da3db66e3eda66259c52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/UnifiedAssetInstaller/zipball/241fb036268cd9da7d76da3db66e3eda66259c52", + "reference": "241fb036268cd9da7d76da3db66e3eda66259c52", + "shasum": "" + }, + "require": { + "composer-plugin-api": "1.0.0" + }, + "require-dev": { + "composer/composer": "~1.0@dev", + "phpunit/phpunit": "~3.7" + }, + "type": "composer-installer", + "extra": { + "class": "\\phpDocumentor\\Composer\\UnifiedAssetInstaller" + }, + "autoload": { + "psr-0": { + "phpDocumentor\\Composer": [ + "src/", + "test/unit/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Asset installer for phpDocumentor", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "assets", + "installer", + "plugins", + "templates" + ], + "time": "2013-09-09 06:13:02" + }, + { + "name": "phpoption/phpoption", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "5d099bcf0393908bf4ad69cc47dafb785d51f7f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/5d099bcf0393908bf4ad69cc47dafb785d51f7f5", + "reference": "5d099bcf0393908bf4ad69cc47dafb785d51f7f5", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-0": { + "PhpOption\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache2" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "http://jmsyst.com", + "role": "Developer of wrapped JMSSerializerBundle" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "time": "2014-01-09 22:37:17" + }, + { + "name": "pimple/pimple", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/fabpot/Pimple.git", + "reference": "2019c145fe393923f3441b23f29bbdfaa5c58c4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fabpot/Pimple/zipball/2019c145fe393923f3441b23f29bbdfaa5c58c4d", + "reference": "2019c145fe393923f3441b23f29bbdfaa5c58c4d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-0": { + "Pimple": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + } + ], + "description": "Pimple is a simple Dependency Injection Container for PHP 5.3", + "homepage": "http://pimple.sensiolabs.org", + "keywords": [ + "container", + "dependency injection" + ], + "time": "2013-11-22 08:30:29" + }, + { + "name": "psr/log", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", + "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-0": { + "Psr\\Log\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2012-12-21 11:40:51" + }, + { + "name": "symfony/config", + "version": "v2.4.4", + "target-dir": "Symfony/Component/Config", + "source": { + "type": "git", + "url": "https://github.com/symfony/Config.git", + "reference": "2effc67af6f21a0d267210b72d0b0b691d113528" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Config/zipball/2effc67af6f21a0d267210b72d0b0b691d113528", + "reference": "2effc67af6f21a0d267210b72d0b0b691d113528", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/filesystem": "~2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Config\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "http://symfony.com", + "time": "2014-04-22 08:11:06" + }, + { + "name": "symfony/console", + "version": "v2.4.4", + "target-dir": "Symfony/Component/Console", + "source": { + "type": "git", + "url": "https://github.com/symfony/Console.git", + "reference": "2e452005b1e1d003d23702d227e23614679eb5ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Console/zipball/2e452005b1e1d003d23702d227e23614679eb5ca", + "reference": "2e452005b1e1d003d23702d227e23614679eb5ca", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/event-dispatcher": "~2.1" + }, + "suggest": { + "symfony/event-dispatcher": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Console\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "http://symfony.com", + "time": "2014-04-27 13:34:57" + }, + { + "name": "symfony/event-dispatcher", + "version": "v2.4.4", + "target-dir": "Symfony/Component/EventDispatcher", + "source": { + "type": "git", + "url": "https://github.com/symfony/EventDispatcher.git", + "reference": "e539602e5455aa086c0e81e604745af7789e4d8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/e539602e5455aa086c0e81e604745af7789e4d8a", + "reference": "e539602e5455aa086c0e81e604745af7789e4d8a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/dependency-injection": "~2.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "http://symfony.com", + "time": "2014-04-16 10:34:31" + }, + { + "name": "symfony/filesystem", + "version": "v2.4.4", + "target-dir": "Symfony/Component/Filesystem", + "source": { + "type": "git", + "url": "https://github.com/symfony/Filesystem.git", + "reference": "a3af8294bcce4a7c1b2892363b0c9d8109affad4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Filesystem/zipball/a3af8294bcce4a7c1b2892363b0c9d8109affad4", + "reference": "a3af8294bcce4a7c1b2892363b0c9d8109affad4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Filesystem\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "http://symfony.com", + "time": "2014-04-16 10:34:31" + }, + { + "name": "symfony/finder", + "version": "v2.4.4", + "target-dir": "Symfony/Component/Finder", + "source": { + "type": "git", + "url": "https://github.com/symfony/Finder.git", + "reference": "25e1e7d5e7376f8a92ae3b1d714d956edf33a730" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Finder/zipball/25e1e7d5e7376f8a92ae3b1d714d956edf33a730", + "reference": "25e1e7d5e7376f8a92ae3b1d714d956edf33a730", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Finder\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "http://symfony.com", + "time": "2014-04-27 13:34:57" + }, + { + "name": "symfony/form", + "version": "v2.4.4", + "target-dir": "Symfony/Component/Form", + "source": { + "type": "git", + "url": "https://github.com/symfony/Form.git", + "reference": "ba7a4852f3124929b270ecd783635e267c379e52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Form/zipball/ba7a4852f3124929b270ecd783635e267c379e52", + "reference": "ba7a4852f3124929b270ecd783635e267c379e52", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/event-dispatcher": "~2.1", + "symfony/intl": "~2.3", + "symfony/options-resolver": "~2.1", + "symfony/property-access": "~2.3" + }, + "require-dev": { + "symfony/http-foundation": "~2.2", + "symfony/security-csrf": "~2.4", + "symfony/validator": "~2.2" + }, + "suggest": { + "symfony/framework-bundle": "For templating with PHP.", + "symfony/security-csrf": "For protecting forms against CSRF attacks.", + "symfony/twig-bridge": "For templating with Twig.", + "symfony/validator": "For form validation." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Form\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Form Component", + "homepage": "http://symfony.com", + "time": "2014-04-18 20:37:09" + }, + { + "name": "symfony/icu", + "version": "v1.2.1", + "target-dir": "Symfony/Component/Icu", + "source": { + "type": "git", + "url": "https://github.com/symfony/Icu.git", + "reference": "98e197da54df1f966dd5e8a4992135703569c987" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Icu/zipball/98e197da54df1f966dd5e8a4992135703569c987", + "reference": "98e197da54df1f966dd5e8a4992135703569c987", + "shasum": "" + }, + "require": { + "lib-icu": ">=4.4", + "php": ">=5.3.3", + "symfony/intl": "~2.3" + }, + "type": "library", + "autoload": { + "psr-0": { + "Symfony\\Component\\Icu\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Contains an excerpt of the ICU data and classes to load it.", + "homepage": "http://symfony.com", + "keywords": [ + "icu", + "intl" + ], + "time": "2013-10-04 10:06:38" + }, + { + "name": "symfony/intl", + "version": "v2.4.4", + "target-dir": "Symfony/Component/Intl", + "source": { + "type": "git", + "url": "https://github.com/symfony/Intl.git", + "reference": "1f1d463ffafbf912f2d3b00fb9e1163185e70971" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Intl/zipball/1f1d463ffafbf912f2d3b00fb9e1163185e70971", + "reference": "1f1d463ffafbf912f2d3b00fb9e1163185e70971", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/icu": "~1.0-RC" + }, + "require-dev": { + "symfony/filesystem": ">=2.1" + }, + "suggest": { + "ext-intl": "to use the component with locales other than \"en\"" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Intl\\": "" + }, + "classmap": [ + "Symfony/Component/Intl/Resources/stubs" + ], + "files": [ + "Symfony/Component/Intl/Resources/stubs/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch", + "homepage": "http://wiedler.ch/igor/" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Eriksen Costa", + "email": "eriksen.costa@infranology.com.br" + } + ], + "description": "A PHP replacement layer for the C intl extension that includes additional data from the ICU library.", + "homepage": "http://symfony.com", + "keywords": [ + "i18n", + "icu", + "internationalization", + "intl", + "l10n", + "localization" + ], + "time": "2014-04-18 20:37:09" + }, + { + "name": "symfony/options-resolver", + "version": "v2.4.4", + "target-dir": "Symfony/Component/OptionsResolver", + "source": { + "type": "git", + "url": "https://github.com/symfony/OptionsResolver.git", + "reference": "de261aa6d0e5d21583f5ae7e68f3d95f90bbd771" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/OptionsResolver/zipball/de261aa6d0e5d21583f5ae7e68f3d95f90bbd771", + "reference": "de261aa6d0e5d21583f5ae7e68f3d95f90bbd771", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\OptionsResolver\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony OptionsResolver Component", + "homepage": "http://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "time": "2014-04-16 10:34:31" + }, + { + "name": "symfony/property-access", + "version": "v2.4.4", + "target-dir": "Symfony/Component/PropertyAccess", + "source": { + "type": "git", + "url": "https://github.com/symfony/PropertyAccess.git", + "reference": "0456222bc00c40c1365065b603f7c397fb9a7134" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/PropertyAccess/zipball/0456222bc00c40c1365065b603f7c397fb9a7134", + "reference": "0456222bc00c40c1365065b603f7c397fb9a7134", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\PropertyAccess\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony PropertyAccess Component", + "homepage": "http://symfony.com", + "keywords": [ + "access", + "array", + "extraction", + "index", + "injection", + "object", + "property", + "property path", + "reflection" + ], + "time": "2014-04-18 20:37:09" + }, + { + "name": "symfony/routing", + "version": "v2.4.4", + "target-dir": "Symfony/Component/Routing", + "source": { + "type": "git", + "url": "https://github.com/symfony/Routing.git", + "reference": "74229f66e16bce6d2415ca44d4756f8e7ea880f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Routing/zipball/74229f66e16bce6d2415ca44d4756f8e7ea880f8", + "reference": "74229f66e16bce6d2415ca44d4756f8e7ea880f8", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "psr/log": "~1.0", + "symfony/config": "~2.2", + "symfony/expression-language": "~2.4", + "symfony/yaml": "~2.0" + }, + "suggest": { + "doctrine/annotations": "For using the annotation loader", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/expression-language": "For using expression matching", + "symfony/yaml": "For using the YAML loader" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Routing\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Routing Component", + "homepage": "http://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "time": "2014-04-23 14:04:21" + }, + { + "name": "symfony/security-core", + "version": "v2.4.4", + "target-dir": "Symfony/Component/Security/Core", + "source": { + "type": "git", + "url": "https://github.com/symfony/security-core.git", + "reference": "1fbe80d10b7a5bcb619abe3e4cfede795d9958dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/security-core/zipball/1fbe80d10b7a5bcb619abe3e4cfede795d9958dc", + "reference": "1fbe80d10b7a5bcb619abe3e4cfede795d9958dc", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "ircmaxell/password-compat": "1.0.*", + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.1", + "symfony/expression-language": "~2.4", + "symfony/http-foundation": "~2.4", + "symfony/validator": "~2.2" + }, + "suggest": { + "ircmaxell/password-compat": "For using the BCrypt password encoder in PHP <5.5", + "symfony/event-dispatcher": "", + "symfony/expression-language": "For using the expression voter", + "symfony/http-foundation": "", + "symfony/validator": "For using the user password constraint" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Security\\Core\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Security Component - Core Library", + "homepage": "http://symfony.com", + "time": "2014-04-23 14:04:21" + }, + { + "name": "symfony/security-csrf", + "version": "v2.4.4", + "target-dir": "Symfony/Component/Security/Csrf", + "source": { + "type": "git", + "url": "https://github.com/symfony/security-csrf.git", + "reference": "920c60129db785b4454d254b638f332dc290c7cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/security-csrf/zipball/920c60129db785b4454d254b638f332dc290c7cb", + "reference": "920c60129db785b4454d254b638f332dc290c7cb", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/security-core": "~2.4" + }, + "require-dev": { + "symfony/http-foundation": "~2.1" + }, + "suggest": { + "symfony/http-foundation": "For using the class SessionTokenStorage." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Security\\Csrf\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Security Component - CSRF Library", + "homepage": "http://symfony.com", + "time": "2014-04-16 10:34:42" + }, + { + "name": "symfony/stopwatch", + "version": "v2.4.4", + "target-dir": "Symfony/Component/Stopwatch", + "source": { + "type": "git", + "url": "https://github.com/symfony/Stopwatch.git", + "reference": "343bcc0360f2c22f371884b8f6a9fee8d1aa431a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Stopwatch/zipball/343bcc0360f2c22f371884b8f6a9fee8d1aa431a", + "reference": "343bcc0360f2c22f371884b8f6a9fee8d1aa431a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Stopwatch\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Stopwatch Component", + "homepage": "http://symfony.com", + "time": "2014-04-18 20:37:09" + }, + { + "name": "symfony/translation", + "version": "v2.4.4", + "target-dir": "Symfony/Component/Translation", + "source": { + "type": "git", + "url": "https://github.com/symfony/Translation.git", + "reference": "d2c73ffa4a5ba1fa0c5d93f43b68331dffe898c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Translation/zipball/d2c73ffa4a5ba1fa0c5d93f43b68331dffe898c5", + "reference": "d2c73ffa4a5ba1fa0c5d93f43b68331dffe898c5", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/config": "~2.0", + "symfony/yaml": "~2.2" + }, + "suggest": { + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Translation\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Translation Component", + "homepage": "http://symfony.com", + "time": "2014-04-18 21:02:05" + }, + { + "name": "symfony/twig-bridge", + "version": "v2.4.4", + "target-dir": "Symfony/Bridge/Twig", + "source": { + "type": "git", + "url": "https://github.com/symfony/TwigBridge.git", + "reference": "c34d6f5a808c55139dcf403ab8977e2e1cfbbf32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/TwigBridge/zipball/c34d6f5a808c55139dcf403ab8977e2e1cfbbf32", + "reference": "c34d6f5a808c55139dcf403ab8977e2e1cfbbf32", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/security-csrf": "~2.4", + "twig/twig": "~1.12" + }, + "require-dev": { + "symfony/expression-language": "~2.4", + "symfony/form": "~2.2", + "symfony/http-kernel": "~2.2", + "symfony/routing": "~2.2", + "symfony/security": "~2.4", + "symfony/stopwatch": "~2.2", + "symfony/templating": "~2.1", + "symfony/translation": "~2.2", + "symfony/yaml": "~2.0" + }, + "suggest": { + "symfony/expression-language": "For using the ExpressionExtension", + "symfony/form": "For using the FormExtension", + "symfony/http-kernel": "For using the HttpKernelExtension", + "symfony/routing": "For using the RoutingExtension", + "symfony/security": "For using the SecurityExtension", + "symfony/stopwatch": "For using the StopwatchExtension", + "symfony/templating": "For using the TwigEngine", + "symfony/translation": "For using the TranslationExtension", + "symfony/yaml": "For using the YamlExtension" + }, + "type": "symfony-bridge", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Bridge\\Twig\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Twig Bridge", + "homepage": "http://symfony.com", + "time": "2014-04-18 20:37:09" + }, + { + "name": "symfony/validator", + "version": "v2.4.4", + "target-dir": "Symfony/Component/Validator", + "source": { + "type": "git", + "url": "https://github.com/symfony/Validator.git", + "reference": "5bbcdcc520bc7fb3826abb44020880f14c9c03a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Validator/zipball/5bbcdcc520bc7fb3826abb44020880f14c9c03a7", + "reference": "5bbcdcc520bc7fb3826abb44020880f14c9c03a7", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/property-access": "~2.2", + "symfony/translation": "~2.0" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/cache": "~1.0", + "symfony/config": "~2.2", + "symfony/http-foundation": "~2.1", + "symfony/intl": "~2.3", + "symfony/yaml": "~2.0" + }, + "suggest": { + "doctrine/annotations": "For using the annotation mapping. You will also need doctrine/cache.", + "doctrine/cache": "For using the default cached annotation reader", + "symfony/config": "", + "symfony/http-foundation": "", + "symfony/intl": "", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Validator\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Validator Component", + "homepage": "http://symfony.com", + "time": "2014-04-27 13:34:57" + }, + { + "name": "umpirsky/twig-gettext-extractor", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/umpirsky/Twig-Gettext-Extractor.git", + "reference": "1.1.3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/umpirsky/Twig-Gettext-Extractor/zipball/1.1.3", + "reference": "1.1.3", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/filesystem": ">=2.0,<3.0", + "symfony/form": ">=2.0,<3.0", + "symfony/routing": ">=2.0,<3.0", + "symfony/translation": ">=2.0,<3.0", + "symfony/twig-bridge": ">=2.0,<3.0", + "twig/extensions": "1.0.*", + "twig/twig": ">=1.2.0,<2.0-dev" + }, + "require-dev": { + "symfony/config": "2.1.*" + }, + "bin": [ + "twig-gettext-extractor" + ], + "type": "application", + "autoload": { + "psr-0": { + "Twig\\Gettext": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Саша Стаменковић", + "email": "umpirsky@gmail.com", + "homepage": "http://umpirsky.com" + } + ], + "description": "The Twig Gettext Extractor is Poedit friendly tool which extracts translations from twig templates.", + "time": "2013-02-14 16:41:48" + }, + { + "name": "zendframework/zend-cache", + "version": "2.1.6", + "target-dir": "Zend/Cache", + "source": { + "type": "git", + "url": "https://github.com/zendframework/Component_ZendCache.git", + "reference": "560355160f06cdc3ef549a7eef843af3bead7e39" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/Component_ZendCache/zipball/560355160f06cdc3ef549a7eef843af3bead7e39", + "reference": "560355160f06cdc3ef549a7eef843af3bead7e39", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "zendframework/zend-eventmanager": "self.version", + "zendframework/zend-servicemanager": "self.version", + "zendframework/zend-stdlib": "self.version" + }, + "require-dev": { + "zendframework/zend-serializer": "self.version" + }, + "suggest": { + "ext-apc": "APC >= 3.1.6 to use the APC storage adapter", + "ext-dba": "DBA, to use the DBA storage adapter", + "ext-memcached": "Memcached >= 1.0.0 to use the Memcached storage adapter", + "ext-wincache": "WinCache, to use the WinCache storage adapter", + "zendframework/zend-serializer": "Zend\\Serializer component", + "zendframework/zend-session": "Zend\\Session component" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev", + "dev-develop": "2.3-dev" + } + }, + "autoload": { + "psr-0": { + "Zend\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "provides a generic way to cache any data", + "keywords": [ + "cache", + "zf2" + ], + "time": "2014-03-03 23:00:17" + }, + { + "name": "zendframework/zend-config", + "version": "2.1.6", + "target-dir": "Zend/Config", + "source": { + "type": "git", + "url": "https://github.com/zendframework/Component_ZendConfig.git", + "reference": "a31c3980cf7ec88418a931e9cf4ba21079f47a08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/Component_ZendConfig/zipball/a31c3980cf7ec88418a931e9cf4ba21079f47a08", + "reference": "a31c3980cf7ec88418a931e9cf4ba21079f47a08", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "zendframework/zend-stdlib": "self.version" + }, + "suggest": { + "zendframework/zend-json": "Zend\\Json to use the Json reader or writer classes", + "zendframework/zend-servicemanager": "Zend\\ServiceManager for use with the Config Factory to retrieve reader and writer instances" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev", + "dev-develop": "2.3-dev" + } + }, + "autoload": { + "psr-0": { + "Zend\\Config\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "provides a nested object property based user interface for accessing this configuration data within application code", + "keywords": [ + "config", + "zf2" + ], + "time": "2014-01-02 18:00:10" + }, + { + "name": "zendframework/zend-eventmanager", + "version": "2.1.6", + "target-dir": "Zend/EventManager", + "source": { + "type": "git", + "url": "https://github.com/zendframework/Component_ZendEventManager.git", + "reference": "89368704bb37303fba64c3ddd6bce0506aa7187c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/Component_ZendEventManager/zipball/89368704bb37303fba64c3ddd6bce0506aa7187c", + "reference": "89368704bb37303fba64c3ddd6bce0506aa7187c", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "zendframework/zend-stdlib": "self.version" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev", + "dev-develop": "2.3-dev" + } + }, + "autoload": { + "psr-0": { + "Zend\\EventManager\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "keywords": [ + "eventmanager", + "zf2" + ], + "time": "2014-01-04 13:00:14" + }, + { + "name": "zendframework/zend-filter", + "version": "2.1.6", + "target-dir": "Zend/Filter", + "source": { + "type": "git", + "url": "https://github.com/zendframework/Component_ZendFilter.git", + "reference": "8ceece474b29d079e86976dbd3efffe6064b3d72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/Component_ZendFilter/zipball/8ceece474b29d079e86976dbd3efffe6064b3d72", + "reference": "8ceece474b29d079e86976dbd3efffe6064b3d72", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "zendframework/zend-stdlib": "self.version" + }, + "require-dev": { + "zendframework/zend-crypt": "self.version" + }, + "suggest": { + "zendframework/zend-crypt": "Zend\\Crypt component", + "zendframework/zend-i18n": "Zend\\I18n component", + "zendframework/zend-uri": "Zend\\Uri component for UriNormalize filter", + "zendframework/zend-validator": "Zend\\Validator component" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev", + "dev-develop": "2.3-dev" + } + }, + "autoload": { + "psr-0": { + "Zend\\Filter\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "provides a set of commonly needed data filters", + "keywords": [ + "filter", + "zf2" + ], + "time": "2014-03-03 21:00:06" + }, + { + "name": "zendframework/zend-i18n", + "version": "2.1.6", + "target-dir": "Zend/I18n", + "source": { + "type": "git", + "url": "https://github.com/zendframework/Component_ZendI18n.git", + "reference": "10f56e0869761d62699782e4dd04eb77262cc353" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/Component_ZendI18n/zipball/10f56e0869761d62699782e4dd04eb77262cc353", + "reference": "10f56e0869761d62699782e4dd04eb77262cc353", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "zendframework/zend-stdlib": "self.version" + }, + "suggest": { + "ext-intl": "Required for most features of Zend\\I18n; included in default builds of PHP", + "zendframework/zend-eventmanager": "You should install this package to use the events in the translator", + "zendframework/zend-filter": "You should install this package to use the provided filters", + "zendframework/zend-resources": "Translation resources", + "zendframework/zend-validator": "You should install this package to use the provided validators", + "zendframework/zend-view": "You should install this package to use the provided view helpers" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev", + "dev-develop": "2.3-dev" + } + }, + "autoload": { + "psr-0": { + "Zend\\I18n\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "keywords": [ + "i18n", + "zf2" + ], + "time": "2014-01-04 13:00:19" + }, + { + "name": "zendframework/zend-json", + "version": "2.1.6", + "target-dir": "Zend/Json", + "source": { + "type": "git", + "url": "https://github.com/zendframework/Component_ZendJson.git", + "reference": "dd8a8239a7c08c7449a6ea219da3e2369bd90d92" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/Component_ZendJson/zipball/dd8a8239a7c08c7449a6ea219da3e2369bd90d92", + "reference": "dd8a8239a7c08c7449a6ea219da3e2369bd90d92", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "zendframework/zend-stdlib": "self.version" + }, + "suggest": { + "zendframework/zend-server": "Zend\\Server component" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev", + "dev-develop": "2.3-dev" + } + }, + "autoload": { + "psr-0": { + "Zend\\Json\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "provides convenience methods for serializing native PHP to JSON and decoding JSON to native PHP", + "keywords": [ + "json", + "zf2" + ], + "time": "2014-03-06 18:00:05" + }, + { + "name": "zendframework/zend-math", + "version": "2.1.6", + "target-dir": "Zend/Math", + "source": { + "type": "git", + "url": "https://github.com/zendframework/Component_ZendMath.git", + "reference": "b982ee2edafd4075b22372596ab2e2fdd0f6424e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/Component_ZendMath/zipball/b982ee2edafd4075b22372596ab2e2fdd0f6424e", + "reference": "b982ee2edafd4075b22372596ab2e2fdd0f6424e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-bcmath": "If using the bcmath functionality", + "ext-gmp": "If using the gmp functionality", + "ircmaxell/random-lib": "Fallback random byte generator for Zend\\Math\\Rand if OpenSSL/Mcrypt extensions are unavailable", + "zendframework/zend-servicemanager": ">= current version, if using the BigInteger::factory functionality" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev", + "dev-develop": "2.3-dev" + } + }, + "autoload": { + "psr-0": { + "Zend\\Math\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "keywords": [ + "math", + "zf2" + ], + "time": "2014-03-05 18:00:06" + }, + { + "name": "zendframework/zend-serializer", + "version": "2.1.6", + "target-dir": "Zend/Serializer", + "source": { + "type": "git", + "url": "https://github.com/zendframework/Component_ZendSerializer.git", + "reference": "d76b931d3ffa842a496c9fa319bbe285b5ddfade" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/Component_ZendSerializer/zipball/d76b931d3ffa842a496c9fa319bbe285b5ddfade", + "reference": "d76b931d3ffa842a496c9fa319bbe285b5ddfade", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "zendframework/zend-json": "self.version", + "zendframework/zend-math": "self.version", + "zendframework/zend-stdlib": "self.version" + }, + "suggest": { + "zendframework/zend-servicemanager": "To support plugin manager support" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev", + "dev-develop": "2.3-dev" + } + }, + "autoload": { + "psr-0": { + "Zend\\Serializer\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "provides an adapter based interface to simply generate storable representation of PHP types by different facilities, and recover", + "keywords": [ + "serializer", + "zf2" + ], + "time": "2014-01-02 18:00:26" + }, + { + "name": "zendframework/zend-servicemanager", + "version": "2.1.6", + "target-dir": "Zend/ServiceManager", + "source": { + "type": "git", + "url": "https://github.com/zendframework/Component_ZendServiceManager.git", + "reference": "de182a20dfdcf978c49570514103c7477ef16e4f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/Component_ZendServiceManager/zipball/de182a20dfdcf978c49570514103c7477ef16e4f", + "reference": "de182a20dfdcf978c49570514103c7477ef16e4f", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "zendframework/zend-di": "Zend\\Di component" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev", + "dev-develop": "2.3-dev" + } + }, + "autoload": { + "psr-0": { + "Zend\\ServiceManager\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "keywords": [ + "servicemanager", + "zf2" + ], + "time": "2014-03-03 21:00:04" + }, + { + "name": "zendframework/zend-stdlib", + "version": "2.1.6", + "target-dir": "Zend/Stdlib", + "source": { + "type": "git", + "url": "https://github.com/zendframework/Component_ZendStdlib.git", + "reference": "e646729f2274f4552b6a92e38d8e458efe08ebc5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/Component_ZendStdlib/zipball/e646729f2274f4552b6a92e38d8e458efe08ebc5", + "reference": "e646729f2274f4552b6a92e38d8e458efe08ebc5", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "zendframework/zend-eventmanager": "To support aggregate hydrator usage", + "zendframework/zend-servicemanager": "To support hydrator plugin manager usage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev", + "dev-develop": "2.3-dev" + } + }, + "autoload": { + "psr-0": { + "Zend\\Stdlib\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "keywords": [ + "stdlib", + "zf2" + ], + "time": "2014-01-04 13:00:28" + }, + { + "name": "zetacomponents/base", + "version": "1.8", + "source": { + "type": "git", + "url": "https://github.com/zetacomponents/Base.git", + "reference": "52ca69c1de55f3fa4f595779e5bc831da7ee176c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zetacomponents/Base/zipball/52ca69c1de55f3fa4f595779e5bc831da7ee176c", + "reference": "52ca69c1de55f3fa4f595779e5bc831da7ee176c", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "apache2" + ], + "authors": [ + { + "name": "Sergey Alexeev" + }, + { + "name": "Sebastian Bergmann" + }, + { + "name": "Jan Borsodi" + }, + { + "name": "Raymond Bosman" + }, + { + "name": "Frederik Holljen" + }, + { + "name": "Kore Nordmann" + }, + { + "name": "Derick Rethans" + }, + { + "name": "Vadym Savchuk" + }, + { + "name": "Tobias Schlitt" + }, + { + "name": "Alexandru Stanoi" + } + ], + "description": "The Base package provides the basic infrastructure that all packages rely on. Therefore every component relies on this package.", + "homepage": "https://github.com/zetacomponents", + "time": "2009-12-21 12:14:16" + }, + { + "name": "zetacomponents/document", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/zetacomponents/Document.git", + "reference": "688abfde573cf3fe0730f82538fbd7aa9fc95bc8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zetacomponents/Document/zipball/688abfde573cf3fe0730f82538fbd7aa9fc95bc8", + "reference": "688abfde573cf3fe0730f82538fbd7aa9fc95bc8", + "shasum": "" + }, + "require": { + "zetacomponents/base": "*" + }, + "require-dev": { + "zetacomponents/unit-test": "dev-master" + }, + "type": "library", + "autoload": { + "classmap": [ + "src" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Sebastian Bergmann" + }, + { + "name": "Kore Nordmann" + }, + { + "name": "Derick Rethans" + }, + { + "name": "Tobias Schlitt" + }, + { + "name": "Alexandru Stanoi" + } + ], + "description": "The Document components provides a general conversion framework for different semantic document markup languages like XHTML, Docbook, RST and similar.", + "homepage": "https://github.com/zetacomponents", + "time": "2013-12-19 11:40:00" + } + ], + "aliases": [ + + ], + "minimum-stability": "stable", + "stability-flags": [ + + ], + "platform": [ + + ], + "platform-dev": [ + + ] +} diff --git a/composer.phar b/composer.phar new file mode 100644 index 000000000..e65ddbac4 Binary files /dev/null and b/composer.phar differ diff --git a/config.inc.dist b/config.inc.dist new file mode 100644 index 000000000..ab11d2b10 --- /dev/null +++ b/config.inc.dist @@ -0,0 +1,41 @@ + 'fi_FI.utf8', + 'sv' => 'sv_SE.utf8', + 'en' => 'en_GB.utf8' +); + +// default SPARQL endpoint +define("DEFAULT_ENDPOINT", "http://sparql.onki.fi/onki-light/sparql"); + +// how many items (maximum) to retrieve in search methods by default +define("DEFAULT_SEARCH_LIMIT", 100); + +// how many items (maximum) to retrieve in transitive property queries +define("DEFAULT_TRANSITIVE_LIMIT", 1000); + +// a default location for Twig template rendering +define("TEMPLATE_CACHE", "/tmp/skosmos-template-cache"); + +// default sparql-query extension +define("DEFAULT_SPARQL_DIALECT", "Generic"); + +// default email address to send the feedback +define("FEEDBACK_ADDRESS", ""); + +// whether or not to log caught exceptions +define ("LOG_CAUGHT_EXCEPTIONS", FALSE); + +# customize the service name +define("SERVICE_NAME", "Skosmos"); + +// customize the service logo by pointing to your logo here. +define("SERVICE_LOGO", "./resource/pics/logo.png"); + +// customize the css by adding your own stylesheet +define("CUSTOM_CSS", "resource/css/stylesheet.css"); diff --git a/controller/Controller.php b/controller/Controller.php new file mode 100644 index 000000000..7e2a70116 --- /dev/null +++ b/controller/Controller.php @@ -0,0 +1,77 @@ +getMessage(); + exit(); +} + +require_once 'model/Model.php'; + +/** + * Handles all the requests from the user and changes the view accordingly. + */ +class Controller +{ + /** + * The controller has to know the model to access the data stored there. + * @param $model contains the Model object. + */ + public $model; + + protected $negotiator; + + protected $languages; + + /** + * Initializes the Model object. + */ + public function __construct() + { + $this->model = new Model(); + $this->negotiator = new \Negotiation\FormatNegotiator(); + + // Specify the location of the translation tables + bindtextdomain('onkiLight', 'resource/translations'); + bind_textdomain_codeset('onkiLight', 'UTF-8'); + + // Choose domain for translations + textdomain('onkiLight'); + + // Build arrays of language information, with 'locale' and 'name' keys + global $LANGUAGES; // global setting defined in config.inc + $this->languages = array(); + foreach ($LANGUAGES as $langcode => $locale) { + $this->languages[$langcode] = array('locale' => $locale); + $this->setLanguageProperties($langcode); + $this->languages[$langcode]['name'] = gettext('in_this_language'); + } + } + + /** + * Sets the locale language properties from the parameter (used by gettext and some Model classes). + * @param string $lang language parameter eg. 'fi' for Finnish. + */ + public function setLanguageProperties($lang) + { + if (array_key_exists($lang, $this->languages)) { + $locale = $this->languages[$lang]['locale']; + putenv("LC_ALL=$locale"); + setlocale(LC_ALL, $locale); + } else { + trigger_error("Unsupported language '$lang', not setting locale", E_USER_WARNING); + } + } + +} diff --git a/controller/RestController.php b/controller/RestController.php new file mode 100644 index 000000000..010648854 --- /dev/null +++ b/controller/RestController.php @@ -0,0 +1,751 @@ +getBest($_SERVER['HTTP_ACCEPT'], $priorities) : null; + $format = $best != null ? $best->getValue() : $priorities[0]; + header("Content-type: $format"); + header("Vary: Accept"); // inform caches that we made a choice based on Accept header + echo json_encode($data); + } + } + + /** + * Gets a Vocabulary object. If not found, aborts with a HTTP 404 error. + * @param string $vocabId identifier string for the vocabulary eg. 'yso'. + * @return object Vocabulary object + */ + + private function getVocabulary($vocabId) + { + try { + return $this->model->getVocabulary($vocabId); + } catch (Exception $e) { + return $this->return_error(404, "Not Found", "Vocabulary id '$vocabId' not found."); + } + } + + /** + * Parses and returns the uri parameter. Returns and error if the parameter is missing. + */ + private function parseURI() + { + if (isset($_GET['uri'])) return $_GET['uri']; + return $this->return_error(400, "Bad Request", "uri parameter missing"); + } + + /** + * Parses and returns the limit parameter. Returns and error if the parameter is missing. + */ + private function parseLimit() + { + $limit = isset($_GET['limit']) ? + intval($_GET['limit']) : DEFAULT_TRANSITIVE_LIMIT; + if ($limit <= 0) + $this->return_error(400, "Bad Request", "Invalid limit parameter"); + + return $limit; + } + + /** + * Determine the language to use, from the lang URL parameter (if set), + * otherwise from the default language of the current vocabulary. + * As a side effect, set this language as current language. + * @param string $vocabId identifier string for the vocabulary eg. 'yso'. + * @return string current language eg. 'en'. + */ + private function getAndSetLanguage($vocabId) + { + $lang = isset($_GET['lang']) ? + $_GET['lang'] : + $this->getVocabulary($vocabId)->getDefaultLanguage(); + $this->setLanguageProperties($lang); + + return $lang; + } + +/** Global REST methods **/ + + /** + * Returns all the vocabularies available on the server in a json object. + */ + public function vocabularies() + { + if (!isset($_GET['lang'])) + return $this->return_error(400, "Bad Request", "lang parameter missing"); + $lang = $_GET['lang']; + $this->setLanguageProperties($lang); + + $vocabs = array(); + foreach ($this->model->getVocabularies() as $voc) { + $vocabs[$voc->getId()] = $voc->getTitle(); + } + ksort($vocabs); + $results = array(); + foreach ($vocabs as $id => $title) { + $results[] = array( + 'uri' => $id, + 'id' => $id, + 'title' => $title); + } + + /* encode the results in a JSON-LD compatible array */ + $ret = array( + '@context' => array( + 'onki' => 'http://schema.onki.fi/onki#', + 'title' => array('@id'=>'rdfs:label', '@language'=>$lang), + 'vocabularies' => 'onki:hasVocabulary', + 'id' => 'onki:vocabularyIdentifier', + 'uri' => '@id', + ), + 'uri' => '', + 'vocabularies' => $results, + ); + + return $this->return_json($ret); + } + + /** + * Performs the search function calls. And wraps the result in a json-ld object. + * @param string $vocab identifier string for the vocabulary eg. 'yso'. + */ + public function search($vocab=null) + { + if(isset($_GET['query'])) + $term = $_GET['query']; + else + return $this->return_error(400, "Bad Request", "query parameter missing"); + if (isset($_GET['maxhits']) && (!is_numeric($_GET['maxhits']) || $_GET['maxhits'] <= 0)) { + return $this->return_error(400, "Bad Request", "maxhits parameter is invalid"); + } + if (isset($_GET['offset']) && (!is_numeric($_GET['offset']) || $_GET['offset'] < 0)) { + return $this->return_error(400, "Bad Request", "offset parameter is invalid"); + } + + $vocid = isset($_GET['vocab']) ? $_GET['vocab'] : $vocab; # optional + $lang = isset($_GET['lang']) ? $_GET['lang'] : null; # optional + $type = isset($_GET['type']) ? $_GET['type'] : 'skos:Concept'; + $parent = isset($_GET['parent']) ? $_GET['parent'] : null; + $group = isset($_GET['group']) ? $_GET['group'] : null; + + // convert to vocids array to support multi-vocabulary search + $vocids = !empty($vocid) ? explode(' ', $vocid) : null; + + $maxhits = isset($_GET['maxhits']) ? ($_GET['maxhits']) : null; # optional + $offset = isset($_GET['offset']) ? ($_GET['offset']) : 0; # optional + $results = $this->model->searchConcepts($term, $vocids, $lang, $type, $parent, $group, $offset, $maxhits); + // before serializing to JSON, get rid of the Vocabulary object that came with each resource + foreach ($results as &$res) { + unset($res['voc']); + } + + $ret = array( + '@context' => array( + 'skos' => 'http://www.w3.org/2004/02/skos/core#', + 'onki' => 'http://schema.onki.fi/onki#', + 'uri' => '@id', + 'type' => '@type', + 'results' => array( + '@id' => 'onki:results', + '@container' => '@list', + ), + 'prefLabel' => 'skos:prefLabel', + 'altLabel' => 'skos:altLabel', + 'hiddenLabel' => 'skos:hiddenLabel', + ), + 'uri' => '', + 'results' => $results, + ); + + if ($lang) + $ret['@context']['@language'] = $lang; + + return $this->return_json($ret); + } + +/** Vocabulary-specific methods **/ + + /** + * Loads the vocabulary metadata. And wraps the result in a json-ld object. + * @param string $vocabId identifier string for the vocabulary eg. 'yso'. + */ + public function vocabularyInformation($vocabId) + { + $lang = $this->getAndSetLanguage($vocabId); + $vocab = $this->getVocabulary($vocabId); + + /* encode the results in a JSON-LD compatible array */ + $conceptschemes = array(); + foreach ($vocab->getConceptSchemes() as $uri => $csdata) { + $csdata['uri'] = $uri; + $csdata['type'] = 'skos:ConceptScheme'; + $conceptschemes[] = $csdata; + } + + $ret = array( + '@context' => array( + 'skos' => 'http://www.w3.org/2004/02/skos/core#', + 'onki' => 'http://schema.onki.fi/onki#', + 'dct' => 'http://purl.org/dc/terms/', + 'uri' => '@id', + 'type' => '@type', + 'title' => 'rdfs:label', + 'conceptschemes' => 'onki:hasConceptScheme', + 'id' => 'onki:vocabularyIdentifier', + 'defaultLanguage' => 'onki:defaultLanguage', + 'languages' => 'onki:language', + 'label' => 'rdfs:label', + 'prefLabel' => 'skos:prefLabel', + 'title' => 'dct:title', + '@language' => $lang, + ), + 'uri' => '', + 'id' => $vocabId, + 'title' => $vocab->getTitle(), + 'defaultLanguage' => $vocab->getDefaultLanguage(), + 'languages' => $vocab->getLanguages(), + 'conceptschemes' => $conceptschemes, + ); + + return $this->return_json($ret); + } + + /** + * Loads the vocabulary type metadata. And wraps the result in a json-ld object. + * @param string $vocabId identifier string for the vocabulary eg. 'yso'. + */ + public function types($vocabId) + { + $lang = $this->getAndSetLanguage($vocabId); + $vocab = $this->getVocabulary($vocabId); + + /* encode the results in a JSON-LD compatible array */ + $types = array(); + foreach ($vocab->getTypes() as $uri => $typedata) { + $type = array_merge(array('uri' => $uri), $typedata); + $types[] = $type; + } + + $ret = array( + '@context' => array( + 'skos' => 'http://www.w3.org/2004/02/skos/core#', + 'onki' => 'http://schema.onki.fi/onki#', + 'uri' => '@id', + 'type' => '@type', + 'label' => 'rdfs:label', + 'superclass' => array('@id' => 'rdfs:subClassOf', '@type' => '@id'), + 'types' => 'onki:hasType', + '@language' => $lang, + ), + 'uri' => '', + 'types' => $types, + ); + + return $this->return_json($ret); + } + + /** + * Used for finding terms by their exact prefLabel. Wraps the result in a json-ld object. + * @param string $vocid identifier string for the vocabulary eg. 'yso'. + */ + public function lookup($vocid) + { + if(isset($_GET['label'])) + $label = $_GET['label']; + else + return $this->return_error(400, "Bad Request", "label parameter missing"); + $lang = isset($_GET['lang']) ? $_GET['lang'] : null; # optional + $vocab = $this->getVocabulary($vocid); + if ($label == '') $this->return_error(400, 'Bad Request', 'empty label'); + + $results = $this->model->searchConcepts($label, $vocid, $lang); + + $hits = array(); + // case 1: exact match on preferred label + foreach ($results as $res) + if ($res['prefLabel'] == $label) + $hits[] = $res; + + // case 2: case-insensitive match on preferred label + if (sizeof($hits) == 0) { // not yet found + foreach ($results as $res) + if (strtolower($res['prefLabel']) == strtolower($label)) + $hits[] = $res; + } + + // case 3: exact match on alternate label + if (sizeof($hits) == 0) { // not yet found + foreach ($results as $res) + if (isset($res['altLabel']) && $res['altLabel'] == $label) + $hits[] = $res; + } + + // case 4: case-insensitive match on alternate label + if (sizeof($hits) == 0) { // not yet found + foreach ($results as $res) + if (isset($res['altLabel']) && strtolower($res['altLabel']) == strtolower($label)) + $hits[] = $res; + } + + if (sizeof($hits) == 0) // no matches found + return $this->return_error(404, 'Not Found', "Could not find label '$label'"); + + // did find some matches! + // get rid of Vocabulary objects + foreach($hits as &$res) + unset($res['voc']); + + $ret = array( + '@context' => array( + 'skos' => 'http://www.w3.org/2004/02/skos/core#', + 'onki' => 'http://schema.onki.fi/onki#', + 'results' => array( + '@id' => 'onki:results', + ), + 'uri' => '@id', + 'prefLabel' => 'skos:prefLabel', + 'altLabel' => 'skos:altLabel', + 'hiddenLabel' => 'skos:hiddenLabel', + ), + 'uri' => '', + 'results' => $hits, + ); + if ($lang) + $ret['@context']['@language'] = $lang; + + return $this->return_json($ret); + } + + /** + * Queries the top concepts of a vocabulary and wraps the results in a json-ld object. + * @param string $vocabId identifier string for the vocabulary eg. 'yso'. + * @return object json-ld object + */ + public function topConcepts($vocabId) + { + $lang = $this->getAndSetLanguage($vocabId); + $vocab = $this->getVocabulary($vocabId); + $scheme = isset($_GET['scheme']) ? $_GET['scheme'] : $vocab->getDefaultConceptScheme(); + + /* encode the results in a JSON-LD compatible array */ + $topconcepts = $vocab->getTopConcepts($scheme); + $results = array(); + foreach ($topconcepts as $uri => $label) { + $results[] = array('uri'=>$uri, 'label'=>$label); + } + + $ret = array( + '@context' => array( + 'skos' => 'http://www.w3.org/2004/02/skos/core#', + 'onki' => 'http://schema.onki.fi/onki#', + 'uri' => '@id', + 'topconcepts' => 'skos:hasTopConcept', + 'label' => 'skos:prefLabel', + '@language' => $lang, + ), + 'uri' => $scheme, + 'topconcepts' => $results, + ); + + return $this->return_json($ret); + } + + /** + * Download a concept as json-ld or redirect to download the whole vocabulary. + * @param string $vocab vocabulary identifier. + * @return object json-ld formatted concept. + */ + public function data($vocab) + { + if (isset($_GET['uri'])) { + $uri = $_GET['uri']; + } else { // whole vocabulary - redirect to download URL + $url = $this->getVocabulary($vocab)->getDataURL(); + if (!$url) + return $this->return_error('404', 'Not Found', "No download source URL known for vocabulary $vocab"); + header("Location: " . $url); + + return; + } + + if (isset($_GET['format'])) { + $format = $_GET['format']; + if (!in_array($format, explode(' ', self::$SUPPORTED_MIME_TYPES))) + return $this->return_error(400, 'Bad Request', "Unsupported format. Supported MIME types are: " . self::$SUPPORTED_MIME_TYPES); + } else { + header('Vary: Accept'); // inform caches that a decision was made based on Accept header + $priorities = explode(' ', self::$SUPPORTED_MIME_TYPES); + $best = $this->negotiator->getBest($_SERVER['HTTP_ACCEPT'], $priorities); + $format = $best != null ? $best->getValue() : $priorities[0]; + } + $results = $this->getVocabulary($vocab)->getRDF($uri, $format); + + if ($format == 'application/ld+json' || $format == 'application/json') { + // further compact JSON-LD document using a context + $context = array( + 'skos' => 'http://www.w3.org/2004/02/skos/core#', + 'isothes' => 'http://purl.org/iso25964/skos-thes#', + 'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#', + 'owl' => 'http://www.w3.org/2002/07/owl#', + 'dct' => 'http://purl.org/dc/terms/', + 'dc11' => 'http://purl.org/dc/elements/1.1/', + 'uri' => '@id', + 'type' => '@type', + 'lang' => '@language', + 'value' => '@value', + 'graph' => '@graph', + 'label' => 'rdfs:label', + 'prefLabel' => 'skos:prefLabel', + 'altLabel' => 'skos:altLabel', + 'hiddenLabel' => 'skos:hiddenLabel', + 'broader' => 'skos:broader', + 'narrower' => 'skos:narrower', + 'related' => 'skos:related', + 'inScheme' => 'skos:inScheme', + ); + $compact_jsonld = \ML\JsonLD\JsonLD::compact($results, json_encode($context)); + $results = \ML\JsonLD\JsonLD::toString($compact_jsonld); + } + + header("Content-type: " . $format); + echo $results; + } + + /** + * Used for querying labels for a uri. + * @param string $vocid vocabulary identifier eg. 'yso'. + * @return object json-ld wrapped labels. + */ + public function label($vocid) + { + $lang = $this->getAndSetLanguage($vocid); + $vocab = $this->getVocabulary($vocid); + $uri = $this->parseURI(); + + $results = $vocab->getConceptLabel($uri, $lang); + if ($results === NULL) + return $this->return_error('404', 'Not Found', "Could not find concept <$uri>"); + + $ret = array( + '@context' => array( + 'skos' => 'http://www.w3.org/2004/02/skos/core#', + 'uri' => '@id', + 'prefLabel' => 'skos:prefLabel', + '@language' => $lang, + ), + 'uri' => $uri, + ); + + if (isset($results[$lang])) + $ret['prefLabel'] = $results[$lang]; + + return $this->return_json($ret); + } + + /** + * Used for querying broader relations for a concept. + * @param string $vocabId vocabulary identifier eg. 'yso'. + * @return object json-ld wrapped broader concept uris and labels. + */ + public function broader($vocabId) + { + $lang = $this->getAndSetLanguage($vocabId); + $vocab = $this->getVocabulary($vocabId); + $uri = $this->parseURI(); + + $results = array(); + $broaders = $vocab->getConceptBroaders($uri); + if ($broaders === NULL) + return $this->return_error('404', 'Not Found', "Could not find concept <$uri>"); + foreach ($broaders as $object => $vals) { + $results[] = array('uri'=>$object, 'prefLabel'=>$vals['label']); + } + + $ret = array( + '@context' => array( + 'skos' => 'http://www.w3.org/2004/02/skos/core#', + 'uri' => '@id', + 'prefLabel' => 'skos:prefLabel', + 'broader' => 'skos:broader', + '@language' => $lang, + ), + 'uri' => $uri, + 'broader' => $results, + ); + + return $this->return_json($ret); + } + + /** + * Used for querying broader transitive relations for a concept. + * @param string $vocabId vocabulary identifier eg. 'yso'. + * @return object json-ld wrapped broader transitive concept uris and labels. + */ + public function broaderTransitive($vocabId) + { + $lang = $this->getAndSetLanguage($vocabId); + $vocab = $this->getVocabulary($vocabId); + $uri = $this->parseURI(); + $limit = $this->parseLimit(); + + $results = array(); + $broaders = $vocab->getConceptTransitiveBroaders($uri, $limit); + if ($broaders === NULL) + return $this->return_error('404', 'Not Found', "Could not find concept <$uri>"); + foreach ($broaders as $buri => $vals) { + $result = array('uri'=>$buri, 'prefLabel'=>$vals['label']); + if (isset($vals['direct'])) { + $result['broader'] = array(); + foreach ($vals['direct'] as $duri => $dval) { + $result['broader'][] = $duri; + } + } + $results[$buri] = $result; + } + + $ret = array( + '@context' => array( + 'skos' => 'http://www.w3.org/2004/02/skos/core#', + 'uri' => '@id', + 'type' => '@type', + 'prefLabel' => 'skos:prefLabel', + 'broader' => array('@id'=>'skos:broader','@type'=>'@id'), + 'broaderTransitive' => array('@id'=>'skos:broaderTransitive','@container'=>'@index'), + '@language' => $lang, + ), + 'uri' => $uri, + 'broaderTransitive' => $results, + ); + + return $this->return_json($ret); + } + + /** + * Used for querying narrower relations for a concept. + * @param string $vocabId vocabulary identifier eg. 'yso'. + * @return object json-ld wrapped narrower concept uris and labels. + */ + public function narrower($vocabId) + { + $lang = $this->getAndSetLanguage($vocabId); + $vocab = $this->getVocabulary($vocabId); + $uri = $this->parseURI(); + + $results = array(); + $narrowers = $vocab->getConceptNarrowers($uri); + if ($narrowers === NULL) + return $this->return_error('404', 'Not Found', "Could not find concept <$uri>"); + foreach ($narrowers as $object => $vals) { + $results[] = array('uri'=>$object, 'prefLabel'=>$vals['label']); + } + + $ret = array( + '@context' => array( + 'skos' => 'http://www.w3.org/2004/02/skos/core#', + 'uri' => '@id', + 'prefLabel' => 'skos:prefLabel', + 'narrower' => 'skos:narrower', + '@language' => $lang, + ), + 'uri' => $uri, + 'narrower' => $results, + ); + + return $this->return_json($ret); + } + + /** + * Used for querying narrower transitive relations for a concept. + * @param string $vocabId vocabulary identifier eg. 'yso'. + * @return object json-ld wrapped narrower transitive concept uris and labels. + */ + public function narrowerTransitive($vocabId) + { + $lang = $this->getAndSetLanguage($vocabId); + $vocab = $this->getVocabulary($vocabId); + $uri = $this->parseURI(); + $limit = $this->parseLimit(); + isset($_GET['limit']) ? + intval($_GET['limit']) : DEFAULT_TRANSITIVE_LIMIT; + if ($limit <= 0) $this->return_error(400, "Bad Request", "Invalid limit parameter"); + + $results = array(); + $narrowers = $vocab->getConceptTransitiveNarrowers($uri, $limit); + if ($narrowers === NULL) + return $this->return_error('404', 'Not Found', "Could not find concept <$uri>"); + foreach ($narrowers as $nuri => $vals) { + $result = array('uri'=>$nuri, 'prefLabel'=>$vals['label']); + if (isset($vals['direct'])) { + $result['narrower'] = array(); + foreach ($vals['direct'] as $duri => $dval) { + $result['narrower'][] = $duri; + } + } + $results[$nuri] = $result; + } + + $ret = array( + '@context' => array( + 'skos' => 'http://www.w3.org/2004/02/skos/core#', + 'uri' => '@id', + 'type' => '@type', + 'prefLabel' => 'skos:prefLabel', + 'narrower' => array('@id'=>'skos:narrower','@type'=>'@id'), + 'narrowerTransitive' => array('@id'=>'skos:narrowerTransitive','@container'=>'@index'), + '@language' => $lang, + ), + 'uri' => $uri, + 'narrowerTransitive' => $results, + ); + + return $this->return_json($ret); + } + + /** + * Used for querying broader transitive relations + * and some narrowers for a concept in the hierarchy view. + * @param string $vocabId vocabulary identifier eg. 'yso'. + * @return object json-ld wrapped hierarchical concept uris and labels. + */ + public function hierarchy($vocabId) + { + $lang = $this->getAndSetLanguage($vocabId); + $vocab = $this->getVocabulary($vocabId); + $uri = $this->parseURI(); + + $results = $this->getVocabulary($vocabId)->getConceptHierarchy($uri); + if ($results === NULL) + return $this->return_error('404', 'Not Found', "Could not find concept <$uri>"); + + $ret = array( + '@context' => array( + 'skos' => 'http://www.w3.org/2004/02/skos/core#', + 'onki' => 'http://schema.onki.fi/onki#', + 'uri' => '@id', + 'type' => '@type', + 'prefLabel' => 'skos:prefLabel', + 'narrower' => array('@id'=>'skos:narrower','@type'=>'@id'), + 'broader' => array('@id'=>'skos:broader','@type'=>'@id'), + 'broaderTransitive' => array('@id'=>'skos:broaderTransitive','@container'=>'@index'), + 'top' => array('@id'=>'skos:topConceptOf','@type'=>'@id'), + 'hasChildren' => 'onki:hasChildren', + '@language' => $lang, + ), + 'uri' => $uri, + 'broaderTransitive' => $results, + ); + + return $this->return_json($ret); + } + + /** + * Used for querying narrower relations for a concept in the hierarchy view. + * @param string $vocabId vocabulary identifier eg. 'yso'. + * @return object json-ld wrapped narrower concept uris and labels. + */ + public function children($vocabId) + { + $lang = $this->getAndSetLanguage($vocabId); + $vocab = $this->getVocabulary($vocabId); + $uri = $this->parseURI(); + + $children = $vocab->getConceptChildren($uri); + if ($children === NULL) + return $this->return_error('404', 'Not Found', "Could not find concept <$uri>"); + + $ret = array( + '@context' => array( + 'skos' => 'http://www.w3.org/2004/02/skos/core#', + 'uri' => '@id', + 'type' => '@type', + 'prefLabel' => 'skos:prefLabel', + 'narrower' => 'skos:narrower', + 'hasChildren' => 'onki:hasChildren', + '@language' => $lang, + ), + 'uri' => $uri, + 'narrower' => $children, + ); + + return $this->return_json($ret); + } + + /** + * Used for querying narrower relations for a concept in the hierarchy view. + * @param string $vocabId vocabulary identifier eg. 'yso'. + * @return object json-ld wrapped hierarchical concept uris and labels. + */ + public function related($vocabId) + { + $lang = $this->getAndSetLanguage($vocabId); + $vocab = $this->getVocabulary($vocabId); + $uri = $this->parseURI(); + + $results = array(); + $related = $vocab->getConceptRelateds($uri); + if ($related === NULL) + return $this->return_error('404', 'Not Found', "Could not find concept <$uri>"); + foreach ($related as $uri => $vals) { + $results[] = array('uri'=>$uri, 'prefLabel'=>$vals['label']); + } + + $ret = array( + '@context' => array( + 'skos' => 'http://www.w3.org/2004/02/skos/core#', + 'uri' => '@id', + 'type' => '@type', + 'prefLabel' => 'skos:prefLabel', + 'related' => 'skos:related', + '@language' => $lang, + ), + 'uri' => $uri, + 'related' => $results, + ); + + return $this->return_json($ret); + } +} diff --git a/controller/WebController.php b/controller/WebController.php new file mode 100644 index 000000000..5093cafd4 --- /dev/null +++ b/controller/WebController.php @@ -0,0 +1,660 @@ +request_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + $this->parts = rtrim($_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME'], 'php.xedni'); + $this->path_fix = $path_fix; + + // initialize Twig templates + $tmp_dir = TEMPLATE_CACHE; + + // check if the cache pointed by config.inc exists, if not we create it. + if (!file_exists($tmp_dir)) mkdir($tmp_dir); + + // specify where to look for templates and cache + $loader = new Twig_Loader_Filesystem('view'); + // initialize Twig environment + $this->twig = new Twig_Environment($loader, array('cache' => $tmp_dir, + 'auto_reload' => true, 'debug' => true)); + $this->twig->addExtension(new Twig_Extensions_Extension_I18n()); + //ENABLES DUMP() method for easy and fun debugging! + $this->twig->addExtension(new Twig_Extension_Debug()); + // used for setting the base href for the relative urls + $this->twig->addGlobal("CurrentUrl", $_SERVER["REQUEST_URI"]); + // setting the service name string from the config.inc + $this->twig->addGlobal("ServiceName", SERVICE_NAME); + // setting the service logo location from the config.inc + if (defined('SERVICE_LOGO')) + $this->twig->addGlobal("ServiceLogo", SERVICE_LOGO); + // setting the service custom css file from the config.inc + if (defined('CUSTOM_CSS')) + $this->twig->addGlobal("ServiceCustomCss", CUSTOM_CSS); + // setting the list of properties to be displayed in the search results + $this->twig->addGlobal("PreferredProperties", array('skos:prefLabel', 'skos:narrower', 'skos:broader', 'onki:memberOf', 'skos:altLabel', 'skos:related')); + + // register a Twig filter for generating URLs for vocabulary resources (concepts and groups) + $controller = $this; // for use by anonymous function below + $urlFilter = new Twig_SimpleFilter('link_url', function ($uri, $vocab, $lang, $type='page') use ($controller) { + // $vocab can either be null, a vocabulary id (string) or a Vocabulary object + if ($vocab === null) { + // target vocabulary is unknown, best bet is to link to the plain URI + return $uri; + } elseif (is_string($vocab)) { + $vocid = $vocab; + $vocab = $controller->model->getVocabulary($vocid); + } else { + $vocid = $vocab->getId(); + } + // case 1: URI within vocabulary namespace: use only local name + $localname = $vocab->getLocalName($uri); + if ($localname != $uri) { // check that the prefix stripping worked + + return $controller->path_fix . "$vocid/$lang/$type/$localname"; + } + + // case 2: URI outside vocabulary namespace; pass the full URI as parameter instead + return $controller->path_fix . "$vocid/$lang/$type/?uri=" . urlencode($uri); + }); + $this->twig->addFilter($urlFilter); + + $tplDir = 'view'; + + // iterate over all your templates + foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($tplDir), + RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + // force compilation + $path = explode('.', $file); + $ext = end($path); + if ($ext == 'twig') + $this->twig->loadTemplate(str_replace($tplDir . '/', '', $file)); + } + } + + /** + * Guess the language of the user. Return a language string that is one + * of the supported languages defined in the $LANGUAGES setting, e.g. "fi". + * @param string $vocab_id identifier for the vocabulary eg. 'yso'. + * @return string returns the language choice as a numeric string value + */ + public function guessLanguage($vocab_id=null) + { + // 1. select language based on ONKI_LANGUAGE cookie + if (isset($_COOKIE['ONKI_LANGUAGE'])) + return $_COOKIE['ONKI_LANGUAGE']; + + // 2. if vocabulary given, select based on the default language of the vocabulary + if ($vocab_id) { + $vocab = $this->model->getVocabulary($vocab_id); + + return $vocab->getDefaultLanguage(); + } + + // 3. select language based on Accept-Language header + header('Vary: Accept-Language'); // inform caches that a decision was made based on Accept header + $this->negotiator = new \Negotiation\LanguageNegotiator(); + $langcodes = array_keys($this->languages); + $acceptLanguage = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : ''; + $bestLang = $this->negotiator->getBest($acceptLanguage, $langcodes); + if (isset($bestLang) && in_array($bestLang, $langcodes)) + return $bestLang->getValue(); + + // show default site or prompt for language + return $langcodes[0]; + } + + /** + * Loads and renders the view containing all the vocabularies. + * @param string $lang language parameter eg. 'fi' for Finnish. + */ + public function invokeVocabularies($lang) + { + // set language parameters for gettext + $this->setLanguageProperties($lang); + // load template + $template = $this->twig->loadTemplate('light.twig'); + // set template variables + $requestUri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + $vocabList = $this->model->getVocabularyList(); + // render template + echo $template + ->render( + array('vocab_list' => $vocabList, + 'path_fix' => $this->path_fix, 'languages' => $this->languages, 'front_page' => True, + 'lang' => $lang, 'parts' => $this->parts, 'request_uri' => $this->request_uri)); + } + + /** + * Invokes the concept page of a single concept in a specific vocabulary. + * @param string $vocab_id contains the name of the vocabulary in question. + * @param string $lang language parameter eg. 'fi' for Finnish. + * @param string $uri localname or uri of concept. + */ + public function invokeVocabularyConcept($vocab_id, $lang, $uri) + { + $this->setLanguageProperties($lang); + $template = $this->twig->loadTemplate('concept-info.twig'); + try { + $vocab = $this->model->getVocabulary($vocab_id); + } catch (Exception $e) { + header("HTTP/1.0 404 Not Found"); + if (LOG_CAUGHT_EXCEPTIONS) + error_log('Caught exception: ' . $e->getMessage()); + echo $template->render(array('path_fix' => $this->path_fix, 'languages' => $this->languages, + 'lang' => $lang, 'vocab_id' => $vocab_id, 'request_uri' => $this->request_uri)); + exit(); + } + + $lang_msg = null; + $newlang = $this->verifyVocabularyLanguage($lang, $vocab->getLanguages()); + if ($newlang !== null) { + $lang = $newlang; + // translate this string here and now to avoid the wrong language issue in the template.. + $lang_msg = gettext("language_changed_message"); + $this->setLanguageProperties($lang); + } + + $vocab = $this->model->getVocabulary($vocab_id); + $uri = $vocab->getConceptURI($uri); // make sure it's a full URI + $results = $vocab->getConceptInfo($uri); + $crumbs = $this->model->getBreadCrumbs($vocab, $lang, $uri); + echo $template->render(Array( + 'search_results' => $results, + 'vocab' => $vocab, + 'vocab_id' => $vocab_id, + 'path_fix' => $this->path_fix, + 'languages' => $this->languages, + 'parts' => $this->parts, + 'lang' => $lang, + 'request_uri' => $this->request_uri, + 'bread_crumbs' => $crumbs['breadcrumbs'], + 'combined' => $crumbs['combined'], + 'lang_changed' => $lang_msg) + ); + } + + /** + * Invokes the feedback page with information of the users current vocabulary. + * @param string $vocab_id used for the default setting of the dropdown menu. + * @param string $lang language parameter eg. 'fi' for Finnish. + */ + public function invokeFeedbackForm($lang, $vocab_id = null) + { + $template = $this->twig->loadTemplate('feedback.twig'); + $this->setLanguageProperties($lang); + $vocabList = $this->model->getVocabularyList(false); + try { + $vocab = (isset($vocab_id)) ? $this->model->getVocabulary($vocab_id) : ""; + } catch (Exception $e) { + header("HTTP/1.0 404 Not Found"); + if (LOG_CAUGHT_EXCEPTIONS) + error_log('Caught exception: ' . $e->getMessage()); + echo $template->render(array('path_fix' => $this->path_fix, + 'languages' => $this->languages, + 'lang' => $lang, 'vocab_id' => $vocab_id, + 'vocabList' => $vocabList)); + + return; + } + $feedback_sent = False; + $feedback_msg = null; + if (isset($_POST['message'])) { + $feedback_sent = True; + $feedback_msg = $_POST['message']; + } + $feedback_name = (isset($_POST['name'])) ? $_POST['name'] : null; + $feedback_email = (isset($_POST['email'])) ? $_POST['email'] : null; + $feedback_vocab = (isset($_POST['vocab'])) ? $_POST['vocab'] : null; + + if ($feedback_sent) + $this->sendFeedback($feedback_msg, $feedback_name, $feedback_email, $feedback_vocab); + + if ($vocab_id == null) + $vocab_id = 'Feedback'; + + echo $template + ->render( + array('path_fix' => $this->path_fix, + 'languages' => $this->languages, + 'lang' => $lang, + 'vocab_id' => $vocab_id, + 'vocab' => $vocab, + 'vocabList' => $vocabList, + 'feedback_sent' => $feedback_sent, + 'parts' => $this->parts, + 'request_uri' => $this->request_uri + )); + } + + /** + * Sends the user entered message through the php's mailer. + * @param string $message only required parameter is the actual message. + * @param string $fromName senders own name. + * @param string $fromEmail senders email adress. + * @param string $fromVocab which vocabulary is the feedback related to. + */ + public function sendFeedback($message, $fromName = null, $fromEmail = null, $fromVocab = null) + { + $to = FEEDBACK_ADDRESS; + if ($fromVocab != null) + $message = 'Feedback from vocab: ' . strtoupper($fromVocab) . "
" . $message; + $subject = "ONKI-light feedback"; + $headers = "MIME-Version: 1.0″ . '\r\n"; + $headers .= "Content-type: text/html; charset=UTF-8" . "\r\n"; + $headers .= "From: $fromName <$fromEmail>" . "\r\n" . 'X-Mailer: PHP/' . phpversion(); + $params = '-f osma.suominen@aalto.fi'; + mail($to, $subject, $message, $headers, $params) or die("Failure"); + } + + /** + * Invokes the about page for the ONKI Light service. + * @param string $lang language parameter eg. 'fi' for Finnish. + */ + public function invokeAboutPage($lang = 'en') + { + $template = $this->twig->loadTemplate('about.twig'); + $this->setLanguageProperties($lang); + $vocab_id = 'About'; + $url = $_SERVER['HTTP_HOST']; + echo $template + ->render(array('path_fix' => $this->path_fix, 'languages' => $this->languages, + 'lang' => $lang, 'vocab_id' => $vocab_id, + 'server_instance' => $url, 'request_uri' => $this->request_uri)); + } + + /** + * Invokes the search for concepts in all the availible ontologies. + * @param string $vocab_id contains the name of the vocabulary in question. + * @param string $lang language parameter eg. 'fi' for Finnish. + */ + public function invokeGlobalSearch($lang) + { + $template = $this->twig->loadTemplate('vocab-search-listing.twig'); + $this->setLanguageProperties($lang); + + $term = htmlspecialchars(isset($_GET['q']) ? $_GET['q'] : ""); + $search_lang = (isset($_GET['lang'])) ? $_GET['lang'] : $lang; + $offset = (isset($_GET['offset']) && is_numeric($_GET['offset']) && $_GET['offset'] >= 0) ? $_GET['offset'] : 0; + if ($offset > 0) { + $rest = 1; + } else { + $rest = null; + } + $term = trim($term); // surrounding whitespace is not considered significant + $sterm = strpos($term, "*") === FALSE ? $term . "*" : $term; // default to prefix search + + $vocabs = !empty($_GET['vocabs']) ? $_GET['vocabs'] : null; # optional + // convert to vocids array to support multi-vocabulary search + $vocids = $vocabs !== null ? explode(' ', $vocabs) : null; + + $counts = count($this->model->searchConcepts($sterm, $vocids, $search_lang, null, null, null, 0, 0)); + $search_results = $this->model->searchConceptsAndInfo($sterm, $vocids, $search_lang, $offset); + $uri_parts = $_SERVER['REQUEST_URI']; + $vocabList = $this->model->getVocabularyList(); + + echo $template->render( + array('path_fix' => $this->path_fix, + 'search_count' => $counts, + 'languages' => $this->languages, + 'lang' => $lang, + 'search_results' => $search_results, + 'term' => $term, + 'lang_count' => $search_lang, + 'rest' => $rest, 'parts' => $this->parts, 'global_search' => True, 'uri_parts' => $uri_parts, + 'request_uri' => $this->request_uri, + 'vocab_list' => $vocabList + + )); + } + + /** + * Invokes the search for a single vocabs concepts. + * @param string $vocab_id contains the name of the vocabulary in question. + * @param string $lang language parameter eg. 'fi' for Finnish. + */ + public function invokeVocabularySearch($vocab_id, $lang) + { + $template = $this->twig->loadTemplate('vocab-search-listing.twig'); + $this->setLanguageProperties($lang); + try { + $vocab = $this->model->getVocabulary($vocab_id); + } catch (Exception $e) { + header("HTTP/1.0 404 Not Found"); + if (LOG_CAUGHT_EXCEPTIONS) + error_log('Caught exception: ' . $e->getMessage()); + echo $template->render(array('path_fix' => $this->path_fix, + 'languages' => $this->languages, + 'lang' => $lang, 'vocab_id' => $vocab_id)); + + return; + } + $term = urldecode(isset($_GET['q']) ? $_GET['q'] : ""); + $search_lang = (isset($_GET['lang'])) ? $_GET['lang'] : $lang; + $offset = (isset($_GET['offset']) && is_numeric($_GET['offset']) && $_GET['offset'] >= 0) ? $_GET['offset'] : 0; + if ($offset > 0) { + $rest = 1; + $template = $this->twig->loadTemplate('vocab-search-listing.twig'); + } else { + $rest = null; + } + $term = trim($term); // surrounding whitespace is not considered significant + $sterm = strpos($term, "*") === FALSE ? $term . "*" : $term; // default to prefix search + try { + $counts = count($this->model->searchConcepts($sterm, $vocab_id, $search_lang, null, null, null, 0, 0)); + $search_results = $this->model->searchConceptsAndInfo($sterm, $vocab_id, $search_lang, $offset, 20, $lang); + } catch (Exception $e) { + header("HTTP/1.0 404 Not Found"); + if (LOG_CAUGHT_EXCEPTIONS) + error_log('Caught exception: ' . $e->getMessage()); + echo $template->render( + array('path_fix' => $this->path_fix, + 'languages' => $this->languages, + 'lang' => $lang, + 'vocab_id' => $vocab_id, + 'vocab' => $vocab, + 'term' => $term, + 'lang_count' => $search_lang, + 'rest' => $rest, 'parts' => $this->parts)); + return; + } + $uri_parts = $_SERVER['REQUEST_URI']; + echo $template->render( + array('path_fix' => $this->path_fix, + 'languages' => $this->languages, + 'lang' => $lang, + 'vocab_id' => $vocab_id, + 'vocab' => $vocab, + 'parts' => $this->parts, + 'search_results' => $search_results, + 'search_count' => $counts, + 'term' => $term, + 'lang_count' => $search_lang, + 'rest' => $rest, 'parts' => $this->parts, 'uri_parts' => $uri_parts, + 'request_uri' => $this->request_uri + + )); + } + + /** + * Invokes the alphabetical listing for a specific vocabulary. + * @param string $vocab_id contains the name of the vocabulary in question. + * @param string $lang language parameter eg. 'fi' for Finnish. + * @param string $letter eg. 'R'. + */ + public function invokeAlphabeticalIndex($vocab_id, $lang, $letter = 'A') + { + $this->setLanguageProperties($lang); + $template = $this->twig->loadTemplate('alphabetical-index.twig'); + + try { + $vocab = $this->model->getVocabulary($vocab_id); + } catch (Exception $e) { + header("HTTP/1.0 404 Not Found"); + $template = $this->twig->loadTemplate('concept-info.twig'); + if (LOG_CAUGHT_EXCEPTIONS) + error_log('Caught exception: ' . $e->getMessage()); + echo $template->render(array('path_fix' => $this->path_fix, + 'languages' => $this->languages, + 'lang' => $lang, 'vocab_id' => $vocab_id)); + + return; + } + + $lang_msg = $this->verifyVocabularyLanguage($lang, $vocab->getLanguages()) ? null : gettext("language_changed_message"); + $all_at_once = $vocab->getAlphabeticalFull(); + if (!$all_at_once) + $search_results = $this->model->searchConcepts($letter . "*", $vocab_id, $lang, null, null, null, 0, 0, false); + else + $search_results = $this->model->searchConcepts("FullAlphabeticalIndex", $vocab_id, $lang, null, null, null, 0, 0, false); + + $controller = $this; // for use by anonymous function below + echo $template + ->render( + array('path_fix' => $this->path_fix, + 'languages' => $this->languages, + 'lang' => $lang, + 'vocab_id' => $vocab_id, + 'vocab' => $vocab, + 'search_results' => $search_results, + 'search_letter' => $letter, + 'letter' => $letter, + 'parts' => $this->parts, + 'all_letters' => $all_at_once, + 'request_uri' => $this->request_uri, + 'lang_changed' => $lang_msg + )); + } + + /** + * Invokes the vocabulary group index page template. + * @param string $vocab_id vocabulary identifier eg. 'yso'. + * @param string $lang language parameter eg. 'fi' for Finnish. + */ + public function invokeGroupIndex($vocab_id, $lang) + { + $this->setLanguageProperties($lang); + $template = $this->twig->loadTemplate('group-index.twig'); + try { + $vocab = $this->model->getVocabulary($vocab_id); + } catch (Exception $e) { + header("HTTP/1.0 404 Not Found"); + $template = $this->twig->loadTemplate('concept-info.twig'); + if (LOG_CAUGHT_EXCEPTIONS) + error_log('Caught exception: ' . $e->getMessage()); + echo $template->render(array('path_fix' => $this->path_fix, + 'languages' => $this->languages, + 'lang' => $lang, 'vocab_id' => $vocab_id)); + + return; + } + $groups = $vocab->listConceptGroups(); + echo $template + ->render( + array('path_fix' => $this->path_fix, + 'languages' => $this->languages, + 'lang' => $lang, + 'vocab_id' => $vocab_id, + 'vocab' => $vocab, + 'groups' => $groups, + 'parts' => $this->parts, + 'request_uri' => $this->request_uri + )); + } + + /** + * Invokes the vocabulary group contents page template. + * @param string $vocab_id vocabulary identifier eg. 'yso'. + * @param string $lang language parameter eg. 'fi' for Finnish. + * @param string $group group URI. + */ + public function invokeGroupContents($vocab_id, $lang, $group) + { + $this->setLanguageProperties($lang); + $template = $this->twig->loadTemplate('group-contents.twig'); + try { + $vocab = $this->model->getVocabulary($vocab_id); + } catch (Exception $e) { + header("HTTP/1.0 404 Not Found"); + $template = $this->twig->loadTemplate('concept-info.twig'); + if (LOG_CAUGHT_EXCEPTIONS) + error_log('Caught exception: ' . $e->getMessage()); + echo $template->render(array('path_fix' => $this->path_fix, + 'languages' => $this->languages, + 'lang' => $lang, 'vocab_id' => $vocab_id)); + + return; + } + $groupuri = $vocab->getConceptURI($group); + $groups = $vocab->listConceptGroups(); + $contents = $vocab->listConceptGroupContents($groupuri); + $group_name = $groups[$groupuri]; + echo $template + ->render( + array('path_fix' => $this->path_fix, + 'languages' => $this->languages, + 'lang' => $lang, + 'vocab_id' => $vocab_id, + 'vocab' => $vocab, + 'contents' => $contents, + 'parts' => $this->parts, + 'label' => $group_name, + 'request_uri' => $this->request_uri + )); + } + + /** + * Loads and renders the view containing a specific vocabulary. + * @param string $vocab_id contains the name of the vocabulary in question. + * @param string $lang language parameter eg. 'fi' for Finnish. + * @param string $letter letter parameter eg. 'R'. + */ + public function invokeVocabularyHome($vocab_id, $lang, $letter = 'A') + { + // set language parameters for gettext + $this->setLanguageProperties($lang); + + try { + $vocab = $this->model->getVocabulary($vocab_id); + } catch (Exception $e) { + header("HTTP/1.0 404 Not Found"); + header("HTTP/1.0 404 Not Found"); + $template = $this->twig->loadTemplate('concept-info'); + if (LOG_CAUGHT_EXCEPTIONS) + error_log('Caught exception: ' . $e->getMessage()); + echo $template->render(array('path_fix' => $this->path_fix, + 'languages' => $this->languages, + 'lang' => $lang, 'vocab_id' => $vocab_id)); + + return; + } + + $vocab_stats = $this->model->getVocabulary($vocab_id)->getStatistics(); + + $lang_msg = null; + $lang_support = true; + $newlang = $this->verifyVocabularyLanguage($lang, $vocab->getLanguages()); + if ($newlang !== null) { + $lang = $newlang; + $lang_support = false; + // translate this string here and now to avoid the wrong language issue in the template.. + $lang_msg = gettext("language_changed_message"); + $this->setLanguageProperties($lang); + } + + $all_at_once = $vocab->getAlphabeticalFull(); + if (!$all_at_once) + $search_results = $this->model->searchConcepts($letter . "*", $vocab_id, $lang, null, null, null, 0, 0); + else + $search_results = $this->model->searchConcepts("FullAlphabeticalIndex", $vocab_id, $lang, null, null, null, 0, 0); + + // load template + $template = $this->twig->loadTemplate('vocab.twig'); + + echo $template + ->render( + array('path_fix' => $this->path_fix, + 'languages' => $this->languages, + 'lang' => $lang, + 'vocab' => $vocab, + 'parts' => $this->parts, + 'vocab_id' => $vocab_id, + 'lang_supported' => $lang_support, + 'request_uri' => $this->request_uri, + 'search_results' => $search_results, + 'vocab_stats' => $vocab_stats, + 'all_letters' => $all_at_once, + 'lang_changed' => $lang_msg)); + } + + /** + * Invokes a very generic errorpage. + */ + public function invokeGenericErrorPage() + { + $lang = $this->guessLanguage(); + $this->setLanguageProperties($lang); + header("HTTP/1.0 404 Not Found"); + $template = $this->twig->loadTemplate('error-page.twig'); + echo $template + ->render( + array('path_fix' => $this->path_fix, + 'languages' => $this->languages, + 'lang' => $lang, + 'parts' => $this->parts, + 'requested_page' => $_SERVER['REQUEST_URI'])); + } + + /** + * Verify that the requested language is supported by the vocabulary. If not, returns + * another language supported by the vocabulary. + * @param string $lang language to set + * @param array $supported_languages languages supported by the vocabulary + * @return string language tag supported by the vocabulary, or null if the given one is supported + */ + + private function verifyVocabularyLanguage($lang, $vocab_languages) + { + $lang_support = in_array($lang, $vocab_languages); + // If not just pick a suitable replacement from the supported languages of the ontology + if ($lang_support) { + return null; + } else { + foreach ($this->languages as $langcode => $langdata) { + if (in_array($langcode, $vocab_languages)) { + $lang = $langcode; + } + } + + return $lang; + } + + } + +} diff --git a/index.php b/index.php new file mode 100644 index 000000000..20aaa4780 --- /dev/null +++ b/index.php @@ -0,0 +1,97 @@ +getMessage(); + exit(); +} + +$path = $_SERVER['PATH_INFO']; // for example "/ysa/fi" +$parts = explode('/', $path); +$path_fix = (isset($parts)) ? str_repeat("../", sizeof($parts) - 2) : ""; +if (isset($_GET['base_path'])) + $path_fix = (isset($parts)) ? str_repeat("../", $_GET['base_path']): ""; + +require_once 'controller/WebController.php'; + +$controller = new WebController($path_fix); +if (sizeof($parts) <= 2) { + // if language code missing, redirect to guessed language + // in any case, redirect to / + $lang = sizeof($parts) == 2 && $parts[1] != '' ? $parts[1] : $controller->guessLanguage(); + header("Location: " . $lang . "/"); +} else { + if (array_key_exists($parts[1], $LANGUAGES)) { // global pages + $lang = $parts[1]; + if ($parts[2] == '') { + $controller->invokeVocabularies($lang); + } elseif ($parts[2] == 'about') { + $controller->invokeAboutPage($lang); + } elseif ($parts[2] == 'feedback') { + $controller->invokeFeedbackForm($lang); + } elseif ($parts[2] == 'search') { + $controller->invokeGlobalSearch($lang); + } else { + $controller->invokeGenericErrorPage(); + } + } else { // vocabulary-specific pages + $vocab = $parts[1]; + if (sizeof($parts) == 3) { // language code missing + $lang = $controller->guessLanguage(); + header("Location: " . $lang . "/"); + } else { + if (array_key_exists($parts[2], $LANGUAGES)) { + $lang = $parts[2]; + if ($parts[3] == '') { + $controller->invokeVocabularyHome($vocab, $lang); + } elseif ($parts[3] == 'feedback') { + $controller->invokeFeedbackForm($lang, $vocab); + } elseif ($parts[3] == 'search') { + $controller->invokeVocabularySearch($vocab, $lang); + } elseif ($parts[3] == 'index') { + if (sizeof($parts) == 4) { // no letter + $controller->invokeAlphabeticalIndex($vocab, $lang); + } else { // letter given + $controller->invokeAlphabeticalIndex($vocab, $lang, $parts[4]); + } + } elseif ($parts[3] == 'page') { + if (isset($_GET['uri'])) { + $controller->invokeVocabularyConcept($vocab, $lang, $_GET['uri']); + } elseif (sizeof($parts) == 5) { + $controller->invokeVocabularyConcept($vocab, $lang, $parts[4]); + } else { + $controller->invokeGenericErrorPage(); + } + } elseif ($parts[3] == 'groups') { + if (sizeof($parts) == 4) { + if (isset($_GET['uri'])) { + $controller->invokeGroupContents($vocab, $lang, $_GET['uri']); + } else { + $controller->invokeGroupIndex($vocab, $lang); + } + } else { + $controller->invokeGroupContents($vocab, $lang, $parts[4]); + } + } else { + $controller->invokeGenericErrorPage(); + } + } else { // language code missing, redirect to some language version + $lang = $controller->guessLanguage($vocab); + $pattern = '|' . preg_quote("/$vocab/") . '|'; + $location = preg_replace($pattern, "/$vocab/$lang/", $_SERVER['REQUEST_URI'], 1); + header("Location: $location"); + } + } + } +} diff --git a/lib/jsTree/default/d.gif b/lib/jsTree/default/d.gif new file mode 100644 index 000000000..08eb95018 Binary files /dev/null and b/lib/jsTree/default/d.gif differ diff --git a/lib/jsTree/default/d.png b/lib/jsTree/default/d.png new file mode 100644 index 000000000..d43db2e60 Binary files /dev/null and b/lib/jsTree/default/d.png differ diff --git a/lib/jsTree/default/style.css b/lib/jsTree/default/style.css new file mode 100644 index 000000000..a12351ef2 --- /dev/null +++ b/lib/jsTree/default/style.css @@ -0,0 +1,72 @@ +/* + * jsTree default theme 1.0 + * Supported features: dots/no-dots, icons/no-icons, focused, loading + * Supported plugins: ui (hovered, clicked), checkbox, contextmenu, search + */ + +.jstree-default li, +.jstree-default ins { background-image:url("d.png"); background-repeat:no-repeat; background-color:transparent; } +.jstree-default li { background-position:-90px 0; background-repeat:repeat-y; } +.jstree-default li.jstree-last { background:transparent; } +.jstree-default .jstree-open > ins { background-position:-72px 0; } +.jstree-default .jstree-closed > ins { background-position:-54px 0; } +.jstree-default .jstree-leaf > ins { background-position:-36px 0; } + +.jstree-default .jstree-hovered { background:#e7f4f9; border:1px solid #d8f0fa; padding:0 2px 0 1px; } +.jstree-default .jstree-clicked { background:#beebff; border:1px solid #99defd; padding:0 2px 0 1px; } +.jstree-default a .jstree-icon { background-position:-56px -19px; } +.jstree-default a.jstree-loading .jstree-icon { background:url("throbber.gif") center center no-repeat !important; } + +.jstree-default .jstree-no-dots li, +.jstree-default .jstree-no-dots .jstree-leaf > ins { background:transparent; } +.jstree-default .jstree-no-dots .jstree-open > ins { background-position:-18px 0; } +.jstree-default .jstree-no-dots .jstree-closed > ins { background-position:0 0; } + +.jstree-default .jstree-no-icons a .jstree-icon { display:none; } + +.jstree-default .jstree-search { font-style:italic; } + +.jstree-default .jstree-no-icons .jstree-checkbox { display:inline-block; } +.jstree-default .jstree-no-checkboxes .jstree-checkbox { display:none !important; } +.jstree-default .jstree-checked > a > .jstree-checkbox { background-position:-38px -19px; } +.jstree-default .jstree-unchecked > a > .jstree-checkbox { background-position:-2px -19px; } +.jstree-default .jstree-undetermined > a > .jstree-checkbox { background-position:-20px -19px; } +.jstree-default .jstree-checked > a > .jstree-checkbox:hover { background-position:-38px -37px; } +.jstree-default .jstree-unchecked > a > .jstree-checkbox:hover { background-position:-2px -37px; } +.jstree-default .jstree-undetermined > a > .jstree-checkbox:hover { background-position:-20px -37px; } + +#vakata-dragged.jstree-default ins { background:transparent !important; } +#vakata-dragged.jstree-default .jstree-ok { background:url("d.png") -2px -53px no-repeat !important; } +#vakata-dragged.jstree-default .jstree-invalid { background:url("d.png") -18px -53px no-repeat !important; } +#jstree-marker.jstree-default { background:url("d.png") -41px -57px no-repeat !important; text-indent:-100px; } + +.jstree-default a.jstree-search { color:aqua; } +.jstree-default .jstree-locked a { color:silver; cursor:default; } + +#vakata-contextmenu.jstree-default-context, +#vakata-contextmenu.jstree-default-context li ul { background:#f0f0f0; border:1px solid #979797; -moz-box-shadow: 1px 1px 2px #999; -webkit-box-shadow: 1px 1px 2px #999; box-shadow: 1px 1px 2px #999; } +#vakata-contextmenu.jstree-default-context li { } +#vakata-contextmenu.jstree-default-context a { color:black; } +#vakata-contextmenu.jstree-default-context a:hover, +#vakata-contextmenu.jstree-default-context .vakata-hover > a { padding:0 5px; background:#e8eff7; border:1px solid #aecff7; color:black; -moz-border-radius:2px; -webkit-border-radius:2px; border-radius:2px; } +#vakata-contextmenu.jstree-default-context li.jstree-contextmenu-disabled a, +#vakata-contextmenu.jstree-default-context li.jstree-contextmenu-disabled a:hover { color:silver; background:transparent; border:0; padding:1px 4px; } +#vakata-contextmenu.jstree-default-context li.vakata-separator { background:white; border-top:1px solid #e0e0e0; margin:0; } +#vakata-contextmenu.jstree-default-context li ul { margin-left:-4px; } + +/* IE6 BEGIN */ +.jstree-default li, +.jstree-default ins, +#vakata-dragged.jstree-default .jstree-invalid, +#vakata-dragged.jstree-default .jstree-ok, +#jstree-marker.jstree-default { _background-image:url("d.gif"); } +.jstree-default .jstree-open ins { _background-position:-72px 0; } +.jstree-default .jstree-closed ins { _background-position:-54px 0; } +.jstree-default .jstree-leaf ins { _background-position:-36px 0; } +.jstree-default a ins.jstree-icon { _background-position:-56px -19px; } +#vakata-contextmenu.jstree-default-context ins { _display:none; } +#vakata-contextmenu.jstree-default-context li { _zoom:1; } +.jstree-default .jstree-undetermined a .jstree-checkbox { _background-position:-20px -19px; } +.jstree-default .jstree-checked a .jstree-checkbox { _background-position:-38px -19px; } +.jstree-default .jstree-unchecked a .jstree-checkbox { _background-position:-2px -19px; } +/* IE6 END */ diff --git a/lib/jsTree/default/throbber.gif b/lib/jsTree/default/throbber.gif new file mode 100644 index 000000000..5b33f7e54 Binary files /dev/null and b/lib/jsTree/default/throbber.gif differ diff --git a/model/Breadcrumb.php b/model/Breadcrumb.php new file mode 100644 index 000000000..575ce1f77 --- /dev/null +++ b/model/Breadcrumb.php @@ -0,0 +1,52 @@ +narrowerConcepts = array(); + $this->uri = $uri; + $this->prefLabel = $prefLabel; + } + + /** + * Hides the prefLabel and stores the value as a hiddenLabel. + */ + public function hideLabel() + { + if(!$this->hiddenLabel) + $this->hiddenLabel = $this->prefLabel; + $this->prefLabel = '...'; + } + + /** + * Used for adding narrower relationships to a Breadcrumb concept + * @param array $concept + */ + public function addNarrower($concept) + { + $this->narrowerConcepts[$concept->uri] = $concept; + } +} diff --git a/model/Concept.php b/model/Concept.php new file mode 100644 index 000000000..15a7f8f92 --- /dev/null +++ b/model/Concept.php @@ -0,0 +1,620 @@ +order = array("rdf:type", "skos:definition", "skos:broader", "skos:narrower", "skos:related", "skos:altLabel", "onki:memberOf", "skos:note", "skos:scopeNote", "skos:historyNote", "rdfs:comment", "dc11:source", "dc:source", "skos:prefLabel"); + $this->graph = $graph; + } + + /** + * Returns the concept uri. + * @return string + */ + public function getUri() + { + return $this->resource->getUri(); + } + + /** + * Returns the skos:prefLabels in other languages than the ui language. + * @return string + */ + public function getForeignPrefLabels() + { + return $this->getForeignLabels('skos:prefLabel'); + } + + /** + * Returns the skos:altLabels in other languages than the ui language. + * @return string + */ + public function getForeignAltLabels() + { + return $this->getForeignLabels('skos:altLabel'); + } + + /** + * Returns a label for the concept in the ui language or if not possible in any language. + * @return string + */ + public function getLabel() + { + // 1. label in current language + if ($this->resource->label($this->lang) !== null) + return $this->resource->label($this->lang)->getValue(); + // 2. label in any language + if ($this->resource->label() !== null) + return $this->resource->label()->getValue() . " (" . $this->resource->label()->getLang() . ")"; + // empty + return ""; + } + + /** + * Returns the vocabulary identifier string or null if that is not available. + * @return string + */ + public function getVocab() + { + return $this->vocab ? $this->vocab->getId() : null; + } + + /** + * Setter for the $foundby property. + * @param string $label label that was matched + * @param string $type type of match: 'alt', 'hidden', or 'lang' + */ + public function setFoundBy($label, $type) + { + $this->foundby = $label; + $this->foundbytype = $type; + } + + /** + * Getter for the $foundby property. + * @return string + */ + public function getFoundBy() + { + return $this->foundby; + } + + /** + * Getter for the $foundbytype property. + * @return string + */ + public function getFoundByType() + { + return $this->foundbytype; + } + + public function getMappingProperties() + { + $properties = array(); + + $members_array = array(); + $long_uris = $this->resource->propertyUris(); + + foreach ($long_uris as &$prop) { + if (EasyRdf_Namespace::shorten($prop)) // shortening property labels if possible + $prop = $sprop = EasyRdf_Namespace::shorten($prop); + else + $sprop = "<$prop>"; // EasyRdf requires full URIs to be in angle brackets + + // Iterating through every resource and adding these to the data object. + foreach ($this->resource->allResources($sprop) as $val) { + $label = null; + $label_lang = null; + $exvocab = null; + + if ($prop === 'skos:exactMatch' || $prop === 'skos:narrowMatch' || $prop === 'skos:broadMatch' || $prop === 'owl:sameAs' || $prop === 'skos:closeMatch') { + $exuri = $val->getUri(); + $exvoc = $this->model->guessVocabularyFromURI($exuri); + if ($exvoc) { + $label_lang = $exvoc->getDefaultLanguage(); + $label = $this->getExternalLabel($exvoc, $exuri, $label_lang); + $exvocab = $exvoc->getId(); + } + if (!$exvoc || !$label) { + $label = $val->shorten() ? $val->shorten() : $exuri; + $label_lang = $this->lang; + $exvocab = null; + } + } else { + break; + } + $prop_info = $this->getPropertyParam($val, $prop); + if ($prop_info['label'] == null) { + $prop_info['label'] = $label; + $prop_info['lang'] = $label_lang; + $prop_info['exvocab'] = $exvocab; + } + if ($prop_info['label'] !== null) { + $properties[$prop_info['prop']][] = new ConceptPropertyValue( + $prop_info['prop'], + $prop_info['concept_uri'], + $prop_info['vocab'], + $prop_info['lang'], + $prop_info['label'], + $prop_info['exvocab'] + ); + } + } + } + + // sorting the properties to a order preferred in the ONKI concept page. + $properties = $this->arbitrarySort($properties); + + // clean up: remove unwanted properties + foreach ($this->DELETED_PROPERTIES as $prop) { + if (isset($properties[$prop])) + unset($properties[$prop]); + } + + $ret = array(); + foreach ($properties as $prop => $values) { + $propres = new EasyRdf_Resource($prop, $this->graph); + $proplabel = $propres->label($this->lang); // current language + if (!$proplabel) $proplabel = $propres->label(); // any language + $propobj = new ConceptProperty($prop, $proplabel, $values); + if ($propobj->getLabel()) // only display properties for which we have a label + $ret[] = $propobj; + } + + return $ret; + } + + /** + * Iterates over all the properties of the concept and returns those in an array. + * @return array + */ + public function getProperties() + { + $properties = array(); + + $members_array = array(); + $long_uris = $this->resource->propertyUris(); + $duplicates = array(); + + foreach ($long_uris as &$prop) { + if (EasyRdf_Namespace::shorten($prop)) // shortening property labels if possible + $prop = $sprop = EasyRdf_Namespace::shorten($prop); + else + $sprop = "<$prop>"; // EasyRdf requires full URIs to be in angle brackets + + // searching for subproperties of literals too + foreach ($this->graph->allResources($prop, 'rdfs:subPropertyOf') as $subi) { + $suburi = EasyRdf_Namespace::shorten($subi->getUri()); + if (!isset($suburi)) + $suburi = $subi->getUri(); + $duplicates[$suburi] = $prop; + } + + // Iterating through every literal and adding these to the data object. + foreach ($this->resource->allLiterals($sprop) as $val) { + if ($val->getLang() == $this->lang || $val->getLang() === null) { + $properties[$prop][] = new ConceptPropertyValue($prop, null, null, $val->getLang(), $val->getValue()); + } + } + + // Iterating through every resource and adding these to the data object. + foreach ($this->resource->allResources($sprop) as $val) { + $label = null; + $label_lang = null; + $exvocab = null; + + if ($this->vocab !== null && $this->vocab->getArrayClassURI() !== null && $prop === 'skos:narrower') { + $collections = $this->graph->resourcesMatching('skos:member', $val); + if (sizeof($collections) > 0) { // if the narrower concept is part of some collection + foreach ($collections as $coll) { + $members_array = array_merge($this->getCollectionMembers($coll, $this->resource), $members_array); + } + continue; + } + } + if ($prop === 'skos:exactMatch' || $prop === 'skos:narrowMatch' || $prop === 'skos:broadMatch' || $prop === 'owl:sameAs' || $prop === 'skos:closeMatch') { + break; + } elseif ($prop === 'rdf:type') { + $exuri = $val->getUri(); + $exvoc = $this->model->guessVocabularyFromURI($exuri); + if ($exvoc) { + $label_lang = $exvoc->getDefaultLanguage(); + $label = $this->getExternalLabel($exvoc, $exuri, $label_lang); + $exvocab = $exvoc->getId(); + } + if (!$exvoc || !$label) { + $label = $val->shorten() ? $val->shorten() : $exuri; + $label_lang = $this->lang; + $exvocab = null; + } + } + $prop_info = $this->getPropertyParam($val, $prop); + if ($prop_info['label'] == null) { + $prop_info['label'] = $label; + $prop_info['lang'] = $label_lang; + $prop_info['exvocab'] = $exvocab; + } + if ($prop_info['label'] !== null) { + $properties[$prop_info['prop']][] = new ConceptPropertyValue( + $prop_info['prop'], + $prop_info['concept_uri'], + $prop_info['vocab'], + $prop_info['lang'], + $prop_info['label'], + $prop_info['exvocab'] + ); + } + } + } + + // finding out if the concept is a member of some group + $reverseResources = $this->graph->resourcesMatching('skos:member', $this->resource); + if (isset($reverseResources)) { + $arrayClassURI = $this->vocab !== null ? $this->vocab->getArrayClassURI() : null; + $arrayClass = $arrayClassURI !== null ? EasyRdf_Namespace::shorten($arrayClassURI) : null; + foreach ($reverseResources as $reverseResource) { + $property = in_array($arrayClass, $reverseResource->types()) ? "onki:memberOfArray" : "onki:memberOf" ; + $exvocab = $this->model->guessVocabularyFromURI($reverseResource->getUri())->getId(); + $reverseUri = $reverseResource->getUri(null); + $label = $reverseResource->label($this->lang) ? $reverseResource->label($this->lang) : $reverseResource->label(); + $properties[$property][] = new ConceptPropertyValue($property, $reverseUri, $exvocab, $label->getLang(), $label->getValue()); + } + } + + // if skos:narrower properties are actually groups we need to remove duplicates. + foreach ($members_array as $topConcept) { + $topProp = new ConceptPropertyValue('skos:narrower', $topConcept['parts'], $topConcept['vocab'], $topConcept['lang'], $topConcept['label'], $exvocab = null); + $properties['skos:narrower'][] = $topProp; + foreach ($topConcept['sub_members'] as $subMember) { + $topProp->addSubMember('onki:sub', $subMember['label'], $subMember['parts'], $subMember['vocab'], $subMember['lang'], $subMember['external']); + } + } + + // clean up: remove unwanted properties + foreach ($this->DELETED_PROPERTIES as $prop) { + if (isset($properties[$prop])) + unset($properties[$prop]); + } + + // sorting the properties to a order preferred in the ONKI concept page. + $properties = $this->arbitrarySort($properties); + + $propertyValues = array(); + + $ret = array(); + foreach ($properties as $prop => $values) { + $propres = new EasyRdf_Resource($prop, $this->graph); + $proplabel = $propres->label($this->lang); // current language + if (!$proplabel) $proplabel = $propres->label(); // any language + foreach ($values as $value) { + $vallabel = $value->getLabel(); + if (!is_string($vallabel)) continue; + $propertyValues[$vallabel][] = $propres->getUri(); + } + $propobj = new ConceptProperty($prop, $proplabel, $values); + if ($propobj->getLabel()) // only display properties for which we have a label + $ret[$prop] = $propobj; + } + + foreach ($propertyValues as $value => $propnames) { + // if the value of prefLabel and rdfs:label are the same we can remove rdfs:label as it's redundant + if (in_array('skos:prefLabel', $propnames) && in_array('rdfs:label', $propnames)) { + unset($ret['rdfs:label']); + } + // if there are multiple properties with the same string value. + if (count($propnames) > 1) { + foreach( $propnames as $property) + // if there is a more accurate property delete the more generic one. + if (isset($duplicates[$property])) { + unset($ret[$property]); + } + } + } + + // unsetting the prefLabel last to detect and remove properties with the same value. + unset($ret['skos:prefLabel']); + + return $ret; + } + + /** + * Returns an array of a resources property + * @param EasyRdf_Resource $val + * @param string $prop identifier eg. 'skos:narrower'. + * @return array + */ + private function getPropertyParam($val, $prop = null) + { + $exvocab = $this->model->guessVocabularyFromURI($val->getUri()); + if (isset($exvocab)) + $exvocid = $exvocab->getId(); + $ret = array(); + + if ($val->label($this->lang) !== null) { // current language + $ret['label'] = $val->label($this->lang)->getValue(); + $ret['lang'] = $this->lang; + } elseif ($val->label() !== null) { // any language + $label = $val->label(); + $ret['label'] = $label->getValue(); + $ret['lang'] = $label->getLang(); + } else { + $ret['label'] = null; + $ret['lang'] = null; + } + + $ret['concept_uri'] = $val->getUri(); + $ret['vocab'] = $this->getVocab(); + $ret['prop'] = $prop; + $ret['exvocab'] = isset($exvocid) ? $exvocid : null; + + return $ret; + } + + /** + * Gets the members of a specific collection. + * @param $coll + * @param EasyRdf_Resource $resource + * @return array + */ + private function getCollectionMembers($coll, $resource) + { + $members_array = Array(); + $coll_info = $this->getPropertyParam($coll); + $external = false; + if (strstr($coll_info['concept_uri'], 'http')) // for identifying concepts that are found with a uri not consistent with the current vocabulary + $external = true; + $members_array[$coll->getUri()] = array('type' => 'resource', 'label' => $coll_info['label'], 'lang' => $coll_info['lang'], + 'uri' => $coll_info['concept_uri'], 'vocab' => $coll_info['vocab'], 'parts' => $coll->getUri(), 'external' => $external); + $narrowers = $resource->allResources('skos:narrower'); + foreach ($coll->allResources('skos:member') as $member) { + foreach ($narrowers as $narrower) { + if ($narrower->getUri() === $member->getUri()) { // found a narrower concept that is a member of this collection + $narrow_info = $this->getPropertyParam($narrower); + $external = false; + if (strstr($narrow_info['concept_uri'], 'http')) // for identifying concepts that are found with a uri not consistent with the current vocabulary + $external = true; + if ($narrow_info['label'] == null) { // fixes json encoded unicode characters causing labels to disappear in afo + $narrow_info['label'] = ('"' . $narrow_info['concept_uri'] . '"'); + $narrow_info['label'] = json_decode($narrow_info['label']); + $narrow_info['concept_uri'] = $narrow_info['label']; + $narrow_info['label'] = strtr($narrow_info['label'], '_', ' '); + } + $members_array[$coll->getUri()]['sub_members'][] = array('type' => 'resource', 'label' => $narrow_info['label'], 'lang' => $narrow_info['lang'], + 'uri' => $narrow_info['concept_uri'], 'vocab' => $narrow_info['vocab'], 'parts' => $narrower->getUri(), 'external' => $external); + } + } + } + + return $members_array; + } + + /** + * Gets the values for the property in question in all other languages than the ui language. + * @param string $prop property identifier eg. 'skos:prefLabel' + */ + private function getForeignLabels($prop) + { + global $LANGUAGES; + $labels = array(); + foreach ($this->resource->allLiterals($prop) as $lit) { + if ($lit->getLang() != $this->lang) + $labels[$lit->getLang()][] = $lit->getValue(); + } + // sorting the labels by the language defined in the configuration. + $order = array_keys($LANGUAGES); + $ordered = array(); + foreach($order as $key) + if (array_key_exists($key, $labels)) { + $ordered[$key] = $labels[$key]; + unset($labels[$key]); + } + + return $ordered + $labels; + } + +} + +/** + * Class for handling concept properties. + */ +class ConceptProperty +{ + /** stores the property type */ + private $prop; + /** stores the property label */ + private $label; + /** stores the property values */ + private $values; + + /** + * Label parameter seems to be optional in this phase. + * @param string $prop property type eg. 'rdf:type'. + * @param string $label + * @param array $values contains ConceptPropertyValues + */ + public function __construct($prop, $label, $values) + { + $this->prop = $prop; + $this->label = $label; + $this->values = $values; + } + + /** + * Gets the gettext translation for a property or returns the identifier as a fallback. + */ + public function getLabel() + { + // first see if we have a translation + $label = gettext($this->prop); + if ($label != $this->prop) return $label; + // if not, see if there was a label for the property in the graph + if ($this->label) return $this->label; + // when no label is found, don't show the property at all + return null; + } + + /** + * Returns a gettext translation for the property tooltip. + * @return string + */ + public function getDescription() + { + $helpprop = $this->prop . "_help"; + + return gettext($helpprop); // can't use string constant, it'd be picked up by xgettext + } + + /** + * Returns an array of the property values. + * @return array containing ConceptPropertyValue objects. + */ + public function getValues() + { + return $this->values; + } + + /** + * Returns property type as a string. + * @return string eg. 'rdf:type'. + */ + public function getType() + { + return $this->prop; + } +} + +/** + * Class for handling concept property values. + */ +class ConceptPropertyValue +{ + /** language code of the value literal */ + private $lang; + /** if the concept is inherited from a another vocabulary store that identifier here */ + private $exvocab; + /** property type */ + private $type; + /** literal value of the property */ + private $label; + /** uri of the concept the property value belongs to */ + private $uri; + /** vocabulary that the concept belongs to */ + private $vocab; + /** if the property is a subProperty of a another property */ + private $parentProperty; + private $submembers; + + public function __construct($prop, $uri, $vocab, $lang, $label, $exvocab = null, $parent = null) + { + $this->submembers = array(); + $this->lang = $lang; + $this->exvocab = $exvocab; + $this->type = $prop; + $this->label = $label; + $this->uri = $uri; + $this->vocab = $vocab; + $this->parentProperty = $parent; + } + + public function __toString() + { + if ($this->label === null) + return ""; + return $this->label; + } + + public function getLang() + { + return $this->lang; + } + + public function getExVocab() + { + return $this->exvocab; + } + + public function getType() + { + return $this->type; + } + + public function getLabel() + { + return $this->label; + } + + public function getUri() + { + return $this->uri; + } + + public function getParent() + { + return $this->parentProperty; + } + + public function getVocab() + { + return $this->vocab; + } + + public function addSubMember($type, $label, $uri, $vocab, $lang, $exvocab = null) + { + $this->submembers[$label] = new ConceptPropertyValue($type, $uri, $vocab, $lang, $label, $exvocab = null); + $this->sortSubMembers(); + } + + public function getSubMembers() + { + if (empty($this->submembers)) + return null; + return $this->submembers; + } + + public function sortSubMembers() + { + if (!empty($this->submembers)) + ksort($this->submembers); + } + +} diff --git a/model/DataObject.php b/model/DataObject.php new file mode 100644 index 000000000..d60125777 --- /dev/null +++ b/model/DataObject.php @@ -0,0 +1,138 @@ +model = $model; + $this->resource = $resource; + } + + /** + * Generates and makes a query into a external vocabulary for an exact + * match for a particular concept. + * @param Vocabulary $exvoc external vocabulary to query + * @param string $exuri resource URI + * @param string $lang language of label to query for + * @return string label, or null if not found in vocabulary + */ + protected function getExternalLabel($exvoc, $exuri, $lang) + { + if ($exvoc) { + $exsparql = $exvoc->getSparql(); + $results = $exsparql->queryLabel($exuri, $lang); + + return isset($results[$lang]) ? $results[$lang] : null; + } else { + return null; + } + } + + /** + * Sorting the result list to a arbitrary order defined below in mycompare() + * @param array $sortable + */ + protected function arbitrarySort($sortable) + { + // sorting the result list to a arbitrary order defined below in mycompare() + if ($sortable == null) + return $sortable; + uksort($sortable, array($this, 'mycompare')); + foreach ($sortable as $prop => $vals) { + usort($sortable[$prop], 'strcoll'); + } + + return $sortable; + } + + /** + * Compares the given objects and returns -1 or 1 depending which ought to be first. + * $order defines the priorities of the different properties possible in the array. + * @param string $a the first item to be compared + * @param string $b the second item to be compared + */ + protected function mycompare($a, $b) + { + if ($a == $b) { + return 0; + } + $order = $this->order; + $position = array_search($a, $order); + $position2 = array_search($b, $order); + + //if both are in the $order, then sort according to their order in $order... + if ($position2 !== false && $position !== false) { + return ($position < $position2) ? -1 : 1; + } + //if only one is in $order, then sort to put the one in $order first... + if ($position !== false) { + return -1; + } + if ($position2 !== false) { + return 1; + } + + //if neither in $order, then a simple alphabetic sort... + return ($a < $b) ? -1 : 1; + } + + /** + * Gets the values for the property in question in all other languages than the ui language. + * @param string $prop property identifier eg. 'skos:prefLabel' + */ + private function getForeignLabels($prop) + { + $labels = array(); + foreach ($this->resource->allLiterals($prop) as $lit) { + if ($lit->getLang() != $this->lang) + $labels[$lit->getLang()][] = $lit->getValue(); + } + + return $labels; + } + + /** + * Getter function to retrieve property values. + * @param string $name + */ + public function __get($name) + { + if ($name == 'lang') { + // get language from locale, same as used by gettext, set by Controller + $this->lang = substr(getenv("LC_ALL"), 0, 2); + + return $this->lang; + } + $trace = debug_backtrace(); + trigger_error( + 'Undefined property via __get(): ' . $name . + ' in ' . $trace[0]['file'] . + ' on line ' . $trace[0]['line'], + E_USER_NOTICE); + + return null; + } + +} diff --git a/model/Model.php b/model/Model.php new file mode 100644 index 000000000..04a55c215 --- /dev/null +++ b/model/Model.php @@ -0,0 +1,438 @@ +graph = apc_fetch($key); + if ($this->graph === FALSE) { // was not found in cache + $this->graph = new EasyRdf_Graph(); + $this->graph->parse(file_get_contents(VOCABULARIES_FILE)); + apc_store($key, $this->graph); + } + } else { // APC not available, parse on every request + $this->graph = new EasyRdf_Graph(); + $this->graph->parse(file_get_contents(VOCABULARIES_FILE)); + } + } catch (Exception $e) { + echo "Error: " . $e->getMessage(); + exit(); + } + } + + /** + * Return all the vocabularies available. + * @param boolean $categories wheter you want everything included in a subarray of + * a category. + */ + public function getVocabularyList($categories = true) + { + $cats = $this->getVocabularyCategories(); + $ret = array(); + foreach ($cats as $cat) { + $catlabel = $cat->getTitle(); + + // find all the vocabs in this category + $vocs = $cat->getVocabularies(); + + if (sizeof($vocs) > 0 && $categories) + $ret[$catlabel] = $vocs; + elseif (sizeof($vocs) > 0) + $ret = array_merge($vocs, $ret); + } + + return $ret; + } + + /** + * Makes a query for the transitive broaders of a concept and returns the concepts hierarchy processed for the view. + * @param string $vocab + * @param string $lang + * @param string $uri + */ + public function getBreadCrumbs($vocab, $lang, $uri) + { + $broaders = $vocab->getConceptTransitiveBroaders($uri, 1000, true); + $this->getCrumbs($broaders, $uri); + $crumbs['combined'] = $this->combineCrumbs(); + $crumbs['breadcrumbs'] = $this->crumbs; + + return $crumbs; + } + + /** + * Takes the crumbs as a parameter and combines the crumbs if the path they form is too long. + * @return array + */ + public function combineCrumbs() + { + $combined = array(); + foreach ($this->crumbs as $pathKey => $path) { + $firstToCombine = true; + $combinedPath = array(); + foreach ($path as $crumbKey => $crumb) { + if ($crumb->prefLabel === '...') { + array_push($combinedPath, $crumb); + if ($firstToCombine) { + $firstToCombine = false; + } else { + unset($this->crumbs[$pathKey][$crumbKey]); + } + } + } + $combined[] = $combinedPath; + } + + return $combined; + } + + /** + * Recursive function for building the breadcrumb paths for the view. + * @param array $bT contains the results of the broaderTransitive query. + * @param string $uri + * @param array $path + */ + public function getCrumbs($bT, $uri, $path=null) + { + if(!isset($path)) + $path = array(); + if (isset($bT[$uri]['direct'])) { + foreach ($bT[$uri]['direct'] as $broaderUri => $broaderLabel) { + $newpath = array_merge($path, array(new Breadcrumb($uri, $bT[$uri]['label']))); + if ($uri !== $broaderUri) + $this->getCrumbs($bT, $broaderUri, $newpath); + } + } else { // we have reached the end of a path and we need to start a new row in the 'stack' + if (isset($bT[$uri])) + $path = array_merge($path, array(new Breadcrumb($uri, $bT[$uri]['label']))); + $index = 1; + $length = sizeof($path); + $limit = $length - 5; + foreach ($path as $crumb) { + if ($length > 5 && $index > $length-$limit) { // displays 5 concepts closest to the concept. + $crumb->hideLabel(); + } + $index++; + } + $this->crumbs[] = array_reverse($path); + } + } + + /** + * Makes a SPARQL-query to the endpoint that retrieves concept + * references as it's search results. + * @param string $term the term that is looked for eg. 'cat'. + * @param mixed $vocids vocabulary id eg. 'yso', array of such ids for multi-vocabulary search, or null for global search. + * @param string $lang language parameter eg. 'fi' for Finnish. + * @param string $type limit search to concepts of the given type + * @param string $parent limit search to concepts which have the given concept as parent in the transitive broader hierarchy + * @param string $group limit search to concepts which are in the given group + * @param int $offset optional parameter for search offset. + * @param int $limit optional paramater for maximum amount of results. + * @param boolean $hidden include matches on hidden labels (default: true). + */ + public function searchConcepts($term, $vocids, $lang, $type = null, $parent=null, $group=null, $offset = 0, $limit = DEFAULT_SEARCH_LIMIT, $hidden = true) + { + $term = trim($term); + if ($term == "" || $term == "*") + return array(); // don't even try to search for empty prefix + else if ($term == "FullAlphabeticalIndex") + $term = "*"; + + // make vocids an array in every case + if ($vocids === null) $vocids = array(); + if (!is_array($vocids)) $vocids = array($vocids); + $vocabs = array(); + foreach ($vocids as $vocid) + $vocabs[] = $this->getVocabulary($vocid); + + if (sizeof($vocids) == 1) { // search within vocabulary + $voc = $vocabs[0]; + $sparql = $voc->getSparql(); + $arrayClass = $voc->getArrayClassURI(); + } else { // multi-vocabulary or global search + $voc = null; + $arrayClass = null; + $sparql = $this->getDefaultSparql(); + } + if (!$type) $type = 'skos:Concept'; + + $results = $sparql->queryConcepts($term, $vocabs, $lang, $limit, $offset, $arrayClass, $type, $parent, $group, $hidden); + $ret = array(); + + foreach ($results as $hit) { + if (sizeof($vocids) == 1) { + $hit['vocab'] = $vocids[0]; + } else { + try { + $voc = $this->getVocabularyByGraph($hit['graph']); + $hit['vocab'] = $voc->getId(); + } catch (Exception $e) { + trigger_error($e->getMessage(), E_USER_WARNING); + $voc = null; + $hit['vocab'] = "???"; + } + } + unset($hit['graph']); + + $hit['voc'] = $voc; + + // if uri is a external vocab uri that is included in the current vocab + $realvoc = $this->guessVocabularyFromURI($hit['uri']); + if ($realvoc != $voc) { + unset($hit['localname']); + $hit['exvocab'] = $realvoc != null ? $realvoc->getId() : "???"; + } + + $ret[] = $hit; + } + + return $ret; + } + + /** + * Function for performing a search for concepts and their data fields. + * @param string $term searchterm eg. 'cat' + * @param mixed $vocids vocabulary id eg. 'yso', array of such ids for multi-vocabulary search, or null for global search. + * @param string $lang eg. 'fi' + * @param integer $offset used for offsetting the result set eg. '20' + * @param integer $limit upper count for the search results eg. '10' + * @param string $ui_lang used for determining if the searchresult isn't the same language as the ui. + */ + public function searchConceptsAndInfo($term, $vocids, $lang, $offset = 0, $limit = 20, $ui_lang=null) + { + // make vocids an array in every case + if ($vocids === null) $vocids = array(); + if (!is_array($vocids)) $vocids = array($vocids); + + $hits = $this->searchConcepts($term, $vocids, $lang, null, null, null, $offset, $limit); + + $uris = array(); + foreach ($hits as $hit) + $uris[] = $hit['uri']; + if (sizeof($vocids) == 1) { + $voc = $this->getVocabulary($vocids[0]); + $sparql = $voc->getSparql(); + $arrayClass = $voc->getArrayClassURI(); + } else { + $arrayClass = null; + $sparql = $this->getDefaultSparql(); + } + $ret = $sparql->queryConceptInfo($uris, $arrayClass, $lang, sizeof($vocids) == 1 ? $vocids[0] : null); + + // For marking that the concept has been found through an alternative label, hidden + // label or a label in another language + foreach ($hits as $idx => $hit) { + if (isset($hit['altLabel']) && isset($ret[$idx])) + $ret[$idx]->setFoundBy($hit['altLabel'], 'alt'); + if (isset($hit['hiddenLabel']) && isset($ret[$idx])) + $ret[$idx]->setFoundBy($hit['hiddenLabel'], 'hidden'); + if ($ui_lang && isset($hit['lang']) && $hit['lang'] !== $ui_lang) + $ret[$idx]->setFoundBy($hit['prefLabel'] . ' (' . $hit['lang'] . ')', 'lang'); + } + + return $ret; + } + + /** + * Creates dataobjects from an input array. + * @param string $class the type of class eg. 'Vocabulary'. + * @param array $resarr contains the EasyRdf_Resources. + */ + private function createDataObjects($class, $resarr) + { + $ret = array(); + foreach ($resarr as $res) + $ret[] = new $class($this, $res); + + return $ret; + } + + /** + * Returns the cached vocabularies. + * @return array of Vocabulary dataobjects + */ + public function getVocabularies() + { + if ($this->all_vocabularies == null) { // initialize cache + $vocs = $this->graph->allOfType('onki:Vocabulary'); + $this->all_vocabularies = $this->createDataObjects("Vocabulary", $vocs); + foreach ($this->all_vocabularies as $voc) { + // register vocabulary ids as RDF namespace prefixes + $prefix = preg_replace('/\W+/', '', $voc->getId()); // strip non-word characters + try { + if ($prefix != '' && EasyRdf_Namespace::get($prefix) === null) // if not already defined + EasyRdf_Namespace::set($prefix, $voc->getUriSpace()); + } catch (Exception $e) { + // not valid as namespace identifier, ignore + } + } + } + + return $this->all_vocabularies; + } + + /** + * Returns the cached vocabularies from a category. + * @param EasyRdf_Resource $cat the category in question + * @return array of vocabulary dataobjects + */ + public function getVocabulariesInCategory($cat) + { + $vocs = $this->graph->resourcesMatching('dc:subject', $cat); + + return $this->createDataObjects("Vocabulary", $vocs); + } + + /** + * Creates dataobjects of all the different vocabulary categories (Health etc.). + * @return array of Dataobjects of the type VocabularyCategory. + */ + public function getVocabularyCategories() + { + $cats = $this->graph->allOfType('skos:Concept'); + + return $this->createDataObjects("VocabularyCategory", $cats); + } + + /** + * Returns a single cached vocabulary. + * @param string $vocid the vocabulary id eg. 'mesh'. + * @return vocabulary dataobject + */ + public function getVocabulary($vocid) + { + $vocs = $this->getVocabularies(); + foreach ($vocs as $voc) { + if ($voc->getId() == $vocid) { + return $voc; + } + } + throw new Exception("Vocabulary id '$vocid' not found in configuration."); + } + + /** + * Return the vocabulary that is stored in the given graph on the given endpoint. + * + * @param $graph string graph URI + * @param $endpoint string endpoint URL (default SPARQL endpoint if omitted) + * @return Vocabulary vocabulary of this URI, or null if not found + */ + public function getVocabularyByGraph($graph, $endpoint = DEFAULT_ENDPOINT) + { + if ($this->vocabularies_by_graph == null) { // initialize cache + $this->vocabularies_by_graph = array(); + foreach ($this->getVocabularies() as $voc) { + $key = json_encode(array($voc->getGraph(), $voc->getEndpoint())); + $this->vocabularies_by_graph[$key] = $voc; + } + } + + $key = json_encode(array($graph,$endpoint)); + if (array_key_exists($key, $this->vocabularies_by_graph)) + return $this->vocabularies_by_graph[$key]; + else + throw new Exception( "no vocabulary found for graph $graph and endpoint $endpoint"); + } + + /** + * Guess which vocabulary a URI originates from, based on the declared + * vocabulary URI spaces. + * + * @param $uri string URI to search + * @return Vocabulary vocabulary of this URI, or null if not found + */ + public function guessVocabularyFromURI($uri) + { + if ($this->vocabularies_by_urispace == null) { // initialize cache + $this->vocabularies_by_urispace = array(); + foreach ($this->getVocabularies() as $voc) { + $this->vocabularies_by_urispace[$voc->getUriSpace()] = $voc; + } + } + + // try to guess the URI space and look it up in the cache + $res = new EasyRdf_Resource($uri); + $namespace = substr($uri, 0, -strlen($res->localName())); + if (array_key_exists($namespace, $this->vocabularies_by_urispace)) { + return $this->vocabularies_by_urispace[$namespace]; + } + + // didn't work, try to match with each URI space separately + foreach ($this->vocabularies_by_urispace as $urispace => $voc) + if (strpos($uri, $urispace) === 0) return $voc; + + // not found + return null; + } + + /** + * Returns a SPARQL endpoint object. + * @param string $dialect eg. 'JenaText'. + * @param string $endpoint url address of endpoint + * @param string $graph uri for the target graph. + */ + public function getSparqlImplementation($dialect, $endpoint, $graph) + { + $classname = $dialect . "Sparql"; + + return new $classname($endpoint, $graph, $this); exit(); + } + + /** + * Returns a SPARQL endpoint object using the default implementation set in the config.inc. + */ + public function getDefaultSparql() + { + return $this->getSparqlImplementation(DEFAULT_SPARQL_DIALECT, DEFAULT_ENDPOINT, '?graph'); + } + +} diff --git a/model/Vocabulary.php b/model/Vocabulary.php new file mode 100644 index 000000000..fed92709c --- /dev/null +++ b/model/Vocabulary.php @@ -0,0 +1,525 @@ +resource->getURI()); + if (count($uriparts) != 1) + // hash namespace + return $uriparts[1]; + // slash namespace + $uriparts = explode("/", $voc->getURI()); + + return $uriparts[count($uriparts) - 1]; + } + + /** + * Returns the human readable vocabulary title. + * @return string the title of the vocabulary + */ + public function getTitle() + { + $literal = $this->resource->getLiteral('dc:title', $this->lang); + if ($literal) + return $literal->getValue(); + // not found with selected language, try any language + return $this->resource->getLiteral('dc:title')->getValue(); + } + + /** + * Get the languages supported by this vocabulary + * @return array languages supported by this vocabulary (as language tag strings) + */ + public function getLanguages() + { + $langs = $this->resource->allLiterals('onki:language'); + $ret = array(); + foreach ($langs as $lang) { + $ret[] = $lang->getValue(); + } + + return $ret; + } + + /** + * Get the default language of this vocabulary + * @return string default language, e.g. 'en' + */ + + public function getDefaultLanguage() + { + $deflang = $this->resource->getLiteral('onki:defaultLanguage'); + if ($deflang) return $deflang->getValue(); + $langs = $this->getLanguages(); + if (sizeof($langs) > 1) + trigger_error("Default language for vocabulary '" . $this->getId() . "' unknown, choosing '$langs[0]'.", E_USER_WARNING); + + return $langs[0]; + } + + /** + * Get the SPARQL endpoint URL for this vocabulary + * + * @return string endpoint URL + */ + public function getEndpoint() + { + return $this->resource->get('void:sparqlEndpoint')->getUri(); + } + + /** + * Get the SPARQL graph URI for this vocabulary + * + * @return string graph URI + */ + public function getGraph() + { + $graph = $this->resource->get('onki:sparqlGraph'); + if ($graph) + $graph = $graph->getUri(); + + return $graph; + } + + /** + * Get the SPARQL implementation for this vocabulary + * + * @return Sparql SPARQL object + */ + public function getSparql() + { + $endpoint = $this->getEndpoint(); + $graph = $this->getGraph(); + $dialect = $this->resource->get('onki:sparqlDialect'); + $dialect = $dialect ? $dialect->getValue() : DEFAULT_SPARQL_DIALECT; + + return $this->model->getSparqlImplementation($dialect, $endpoint, $graph); + } + + /** + * Get the URI space of concepts in this vocabulary. + * + * @return string full URI of concept + */ + public function getUriSpace() + { + if ($this->urispace == null) // initialize cache + $this->urispace = $this->resource->getLiteral('void:uriSpace')->getValue(); + + return $this->urispace; + } + + /** + * Get the full URI of a concept in a vocabulary. If the passed local + * name is already a full URI, return it unchanged. + * + * @param $lname string local name of concept + * @return string full URI of concept + */ + public function getConceptURI($lname) + { + if (strpos($lname, 'http') === 0) return $lname; // already a full URI + return $this->getUriSpace() . $lname; + } + + /** + * Asks the sparql implementation to make a label query for a uri. + * @param string $uri + * @param string $lang + */ + public function getConceptLabel($uri, $lang) + { + return $this->getSparql()->queryLabel($uri,$lang); + } + + /** + * Get the localname of a concept in the vocabulary. If the URI is not + * in the URI space of this vocabulary, return the full URI. + * + * @param $uri string full URI of concept + * @return string local name of concept, or original full URI if the local name cannot be determined + */ + public function getLocalName($uri) + { + return str_replace($this->getUriSpace(), "", $uri); + } + + /** + * Wether the alphabetical index is small enough to be shown all at once. + * @return boolean true if all concepts can be shown at once. + */ + public function getAlphabeticalFull() + { + $val = $this->resource->getLiteral('onki:fullAlphabeticalIndex'); + if ($val) + return (boolean) $val->getValue(); + return false; + } + + /** + * Retrieves all the information about the Vocabulary + * from the SPARQL-endpoint. + */ + public function getInfo() + { + $ret = array(); + + // get metadata from vocabulary configuration file + foreach ($this->resource->properties() as $prop) { + foreach ($this->resource->allLiterals($prop, $this->lang) as $val) { + $ret[$prop][] = $val->getValue(); + } + foreach ($this->resource->allResources($prop) as $val) { + $label = $val->label($this->lang); + if ($label) { + $ret[$prop][] = $label->getValue(); + } + } + } + + // also include ConceptScheme metadata from SPARQL endpoint + $cs = $this->getDefaultConceptScheme(); + + // query everything the endpoint knows about the ConceptScheme + $sparql = $this->getSparql(); + $result = $sparql->queryConceptScheme($cs); + $cs = $result->resource($cs); + $this->order = array("dc:title","dc11:title","skos:prefLabel","rdfs:label","dc:subject", "dc11:subject", "dc:description", "dc11:description","dc:publisher","dc11:publisher","dc:creator","dc11:creator","dc:contributor", "dc:language", "dc11:language","owl:versionInfo","dc:source", "dc11:source"); + + foreach ($cs->properties() as $prop) { + foreach ($cs->allLiterals($prop, $this->lang) as $val) { + $ret[$prop][] = $val->getValue(); + } + if (!isset($ret[$prop]) || sizeof($ret[$prop]) == 0) { // not found with language tag + foreach ($cs->allLiterals($prop, null) as $val) { + $ret[$prop][] = $val->getValue(); + } + } + foreach ($cs->allResources($prop) as $val) { + $label = $val->label($this->lang); + if ($label) { + $ret[$prop][] = $label->getValue(); + } else { + $exvocab = $this->model->guessVocabularyFromURI($val->getURI()); + $exlabel = $this->getExternalLabel($exvocab, $val->getURI(), $this->lang); + $ret[$prop][] = isset($exlabel) ? $exlabel : $val->getURI(); + } + } + } + if (isset($ret['owl:versionInfo'])) { // if version info availible for vocabulary convert it to a more readable format + $ret['owl:versionInfo'][0] = $this->parseVersionInfo($ret['owl:versionInfo'][0]); + } + // remove duplicate values + foreach (array_keys($ret) as $prop) + $ret[$prop] = array_unique($ret[$prop]); + $ret = $this->arbitrarySort($ret); + + // filtering multiple labels + if (isset($ret['dc:title'])) + unset($ret['dc11:title'],$ret['skos:prefLabel'],$ret['rdfs:label']); + else if(isset($ret['dc11:title'])) + unset($ret['skos:prefLabel'],$ret['rdfs:label']); + else if(isset($ret['skos:prefLabel'])) + unset($ret['rdfs:label']); + + return $ret; + } + + /** + * Return all types (RDFS/OWL classes) present in the vocabulary. + * @return array Array with URIs (string) as key and array of (label, superclassURI) as value + */ + + public function getTypes() + { + return $this->getSparql()->queryTypes($this->lang); + } + + /** + * Return all concept schemes in the vocabulary. + * @return array Array with concept scheme URIs (string) as keys and labels (string) as values + */ + + public function getConceptSchemes() + { + return $this->getSparql()->queryConceptSchemes($this->lang); + } + + /** + * Return the URI of the default concept scheme of this vocabulary. If the onki:mainConceptScheme property is set in the + * vocabulary configuration, that will be returned. Otherwise an arbitrary concept scheme will be returned. + * @return string concept scheme URI + */ + + public function getDefaultConceptScheme() + { + $conceptScheme = $this->resource->get("onki:mainConceptScheme"); + if ($conceptScheme) return $conceptScheme->getUri(); + + // mainConceptScheme not explicitly set, guess it + foreach ($this->getConceptSchemes() as $uri => $csdata) { + $conceptScheme = $uri; // actually pick the last one + } + + return $conceptScheme; + } + + /** + * Return the top concepts of a concept scheme in the vocabulary. + * @param string $conceptScheme URI of concept scheme whose top concepts to return. If not set, + * the default concept scheme of the vocabulary will be used. + * @return array Array with concept URIs (string) as keys and labels (string) as values + */ + + public function getTopConcepts($conceptScheme=null) + { + if (!$conceptScheme) + $conceptScheme = $this->getDefaultConceptScheme(); + + return $this->getSparql()->queryTopConcepts($conceptScheme, $this->lang); + } + + /** + * Tries to parse version, date and time from sparql version information into a readable format. + * @param string $version + * @return string + */ + private function parseVersionInfo($version) + { + $parts = explode(' ', $version); + if ($parts[0] != '$Id:') return $version; // don't know how to parse + $rev = $parts[2]; + $datestr = $parts[3] . ' ' . $parts[4]; + + return "$datestr (r$rev)"; + } + + /** + * Counts the statistics of the vocabulary. + * @return array of the concept counts in different languages + */ + public function getStatistics() + { + $sparql = $this->getSparql(); + $ret = array(); + // find the number of concepts + $ret['concepts'] = $sparql->countConcepts(); + // count the number of different types of concepts in all languages + $ret['terms'] = $sparql->countLangConcepts($this->getLanguages()); + + return $ret; + } + + /** + * get the URL from which the vocabulary data can be downloaded + */ + public function getDataURL() + { + $val = $this->resource->getResource("void:dataDump"); + if ($val) + return $val->getURI(); + return false; + } + + /** + * Returns the class URI used for concept groups in this vocabulary, + * or null if not set. + * @return string group class URI or null + */ + + public function getGroupClassURI() + { + $val = $this->resource->getResource("onki:groupClass"); + if ($val) + return $val->getURI(); + return null; + } + + /** + * Returns the class URI used for thesaurus arrays in this vocabulary, + * or null if not set. + * @return string array class URI or null + */ + + public function getArrayClassURI() + { + $val = $this->resource->getResource("onki:arrayClass"); + if ($val) + return $val->getURI(); + return null; + } + + /** + * Returns a boolean value set in the vocabularies.ttl config. + * @return boolean + */ + public function getShowHierarchy() + { + $val = $this->resource->getLiteral("onki:showTopConcepts"); + if ($val) + return (boolean) $val->getValue(); + return false; + } + + + /** + * Gets the parent concepts of a concept and child concepts for all of those. + * @param string $uri + */ + public function getConceptHierarchy($uri) + { + return $this->getSparql()->queryParentList($uri, $this->lang); + } + + /** + * Gets the child relations of a concept and whether these children have more children. + * @param string $uri + */ + public function getConceptChildren($uri) + { + return $this->getSparql()->queryChildren($uri, $this->lang); + } + + /** + * Gets the skos:narrower relations of a concept. + * @param string $uri + */ + public function getConceptNarrowers($uri) + { + return $this->getSparql()->queryProperty($uri, 'skos:narrower', $this->lang); + } + + /** + * Gets the skos:narrowerTransitive relations of a concept. + * @param string $uri + * @param integer $limit + */ + public function getConceptTransitiveNarrowers($uri, $limit) + { + return $this->getSparql()->queryTransitiveProperty($uri, 'skos:narrower',$this->lang,$limit); + } + + /** + * Gets the skos:broader relations of a concept. + * @param string $uri + * @param string $lang language identifier. + */ + public function getConceptBroaders($uri, $lang='fi') + { + return $this->getSparql()->queryProperty($uri, 'skos:broader', $this->lang); + } + + /** + * Gets the skos:broaderTransitive relations of a concept. + * @param string $uri + * @param integer $limit + * @param boolean $any set to true if you want to have a label even in case of a correct language one missing. + */ + public function getConceptTransitiveBroaders($uri, $limit, $any=false) + { + return $this->getSparql()->queryTransitiveProperty($uri, 'skos:broader', $this->lang, $limit, $any); + } + + /** + * Gets all the skos:related concepts of a concept. + * @param string $uri + */ + public function getConceptRelateds($uri) + { + return $this->getSparql()->queryProperty($uri,'skos:related', $this->lang); + } + + /** + * returns concept's RDF in downloadable format + * @param string $uri + * @param string $format is the format in which you want to get the result, currently this function supports + * text/turtle, application/rdf+xml and application/json + */ + public function getRDF($uri, $format) + { + $sparql = $this->getSparql(); + + if ($format == 'text/turtle') { + $retform = 'turtle'; + $serialiser = new EasyRdf_Serialiser_Turtle(); + } elseif ($format == 'application/ld+json' || $format == 'application/json') { + $retform = 'jsonld'; // serve JSON-LD for both JSON-LD and plain JSON requests + $serialiser = new EasyRdf_Serialiser_JsonLd(); + } else { + $retform = 'rdfxml'; + $serialiser = new EasyRdf_Serialiser_RdfXml(); + } + + $result = $sparql->queryConceptInfo($uri, $this->getArrayClassURI(), null, null, true); + + return $serialiser->serialise($result, $retform); + } + + /** + * Makes a query into the sparql endpoint for a concept. + * @param string $uri the full URI of the concept + * @return array + */ + public function getConceptInfo($uri) + { + $sparql = $this->getSparql(); + + return $sparql->queryConceptInfo($uri, $this->getArrayClassURI(), $this->lang, $this->getId()); + } + + /** + * Lists the different concept groups available in the vocabulary. + * @return array + */ + public function listConceptGroups() + { + $ret = array(); + $gclass = $this->getGroupClassURI(); + if ($gclass === null) return $ret; // no group class defined, so empty result + $groups = $this->getSparql()->listConceptGroups($gclass, $this->lang); + foreach ($groups as $uri => $label) { + $ret[$uri] = $label; + } + + return $ret; + } + + /** + * Lists the concepts available in the concept group. + * @param $clname + * @return array + */ + public function listConceptGroupContents($glname) + { + $ret = array(); + $gclass = $this->getGroupClassURI(); + if ($gclass === null) return $ret; // no group class defined, so empty result + $group = $this->getConceptURI($glname); + $contents = $this->getSparql()->listConceptGroupContents($gclass, $group, $this->lang); + foreach ($contents as $uri => $label) { + $ret[$uri] = $label; + } + + return $ret; + } + +} diff --git a/model/VocabularyCategory.php b/model/VocabularyCategory.php new file mode 100644 index 000000000..5adfe1753 --- /dev/null +++ b/model/VocabularyCategory.php @@ -0,0 +1,29 @@ +model->getVocabulariesInCategory($this->resource); + } + + /** + * Returns the title of the category. + */ + public function getTitle() + { + return $this->resource->label($this->lang)->getValue(); + } + +} diff --git a/model/VocabularyDataObject.php b/model/VocabularyDataObject.php new file mode 100644 index 000000000..c1933ee45 --- /dev/null +++ b/model/VocabularyDataObject.php @@ -0,0 +1,32 @@ +vocab = $vocab; + } + +} diff --git a/model/sparql/BigdataSparql.php b/model/sparql/BigdataSparql.php new file mode 100644 index 000000000..d19d78a88 --- /dev/null +++ b/model/sparql/BigdataSparql.php @@ -0,0 +1,33 @@ +"; + if ($type == 'literal') $val = "'$val'"; + $constants[] = "($val)"; + } + $values = implode(" ", $constants); + + return "BINDINGS $varname { $values }"; + } + +} diff --git a/model/sparql/GenericSparql.php b/model/sparql/GenericSparql.php new file mode 100644 index 000000000..68d0ceee3 --- /dev/null +++ b/model/sparql/GenericSparql.php @@ -0,0 +1,917 @@ +setHeaders('Cache-Control', $val); + EasyRdf_Http::setDefaultHttpClient($httpclient); // actually redundant.. + } + + // create the EasyRDF SPARQL client instance to use + $this->client = new EasyRdf_Sparql_Client($endpoint); + $this->graph = $graph; + $this->model = $model; + + // set graphClause so that it can be used by all queries + if ($this->isDefaultEndpoint()) // default endpoint; query any graph (and catch it in a variable) + $this->graphClause = "GRAPH $graph"; + elseif ($graph) // query a specific graph + $this->graphClause = "GRAPH <$graph>"; + else // query the default graph + $this->graphClause = ""; + } + + /** + * Return true if this is the default SPARQL endpoint, used as the facade to query + * all vocabularies. + */ + + private function isDefaultEndpoint() + { + return $this->graph[0] == '?'; + } + + /** + * If there is no vocabulary id available use this to guess it from the uri. + * @param string $uri + */ + private function guessVocabID($uri) + { + try { + $exvoc = $this->model->guessVocabularyFromURI($uri); + } catch (Exception $e) { + trigger_error($e->getMessage(), E_USER_WARNING); + + return "???"; + } + $exvocab = $exvoc->getId(); + + return $exvocab; + } + + /** + * Returns the graph instance + * @return object EasyRDF graph instance. + */ + public function getGraph() + { + return $this->graph; + } + + /** + * Used for counting number of concepts in a vocabulary. + * @return int number of concepts in this vocabulary + */ + public function countConcepts() + { + $gc = $this->graphClause; + $query = <<client->query($query); + foreach ($result as $row) { + return $row->c->getValue(); + } + } + + /** + * Counts the number of concepts in a easyRDF graph with a specific language. + * @param array $langs Languages to query for + * @return Array containing count of concepts for each language and property. + */ + public function countLangConcepts($langs) + { + $gc = $this->graphClause; + $ret = array(); + + $props = array('skos:prefLabel', 'skos:altLabel', 'skos:hiddenLabel'); + $values_lang = $this->formatValues('?lang', $langs, 'literal'); + $values_prop = $this->formatValues('?prop', $props, null); + + $query = <<client->query($query); + // set default count to zero; overridden below if query found labels + foreach ($langs as $lang) { + foreach ($props as $prop) { + $ret[$lang][$prop] = 0; + } + } + foreach ($result as $row) { + if (isset($row->lang) && isset($row->prop) && isset($row->count)) + $ret[$row->lang->getValue()][$row->prop->shorten()] = + $row->count->getValue(); + } + ksort($ret); + + return $ret; + } + + /** + * Formats a VALUES clause (SPARQL 1.1) which states that the variable should be bound to one + * of the constants given. + * @param string $varname variable name, e.g. "?uri" + * @param array $values the values + * @param string $type type of values: "uri", "literal" or null (determines quoting style) + */ + protected function formatValues($varname, $values, $type = null) + { + $constants = array(); + foreach ($values as $val) { + if ($type == 'uri') $val = "<$val>"; + if ($type == 'literal') $val = "'$val'"; + $constants[] = "($val)"; + } + $values = implode(" ", $constants); + + return "VALUES ($varname) { $values }"; + } + + /** + * Returns information (as a graph) for one or more concept URIs + * @param mixed $uris concept URI (string) or array of URIs + * @param string $arrayClass the URI for thesaurus array class, or null if not used + * @param string $lang eg. 'en'. + * @param string $vocid eg. 'yso'. + * @param boolean $rest + * @return EasyRDF_Graph query result graph + */ + public function queryConceptInfo($uris, $arrayClass = null, $lang = null, $vocid = null, $rest = false) + { + $gc = $this->graphClause; + + // if just a single URI is given, put it in an array regardless + if (!is_array($uris)) + $uris = array($uris); + + $values = $this->formatValues('?uri', $uris, 'uri'); + + if (!$arrayClass) { + $construct = $optional = ""; + } else { + // add information that can be used to format narrower concepts by + // the array they belong to ("milk by source animal" use case) + $construct = "?uri skos:narrower ?n . ?x skos:member ?n . ?x skos:prefLabel ?xl . "; + $optional = "OPTIONAL { + ?uri skos:narrower ?n . + ?x skos:member ?n . + ?x a <$arrayClass> . + ?x skos:prefLabel ?xl . + }"; + } + $query = <<client->query($query); + if ($rest) + return $result; + + if ($result->isEmpty()) + return; + + $conceptArray = array(); + foreach ($uris as $uri) { + $conc = $result->resource($uri); + $vocab = isset($vocid) ? $this->model->getVocabulary($vocid) : $this->model->guessVocabularyFromUri($uri); + $conceptArray[] = new Concept($this->model, $vocab, $conc, $result); + } + + return $conceptArray; + } + + /** + * Retrieve information about types from the endpoint + * @param string $lang + * @return array Array with URIs (string) as key and array of (label, superclassURI) as value + */ + + public function queryTypes($lang) + { + $gc = $this->graphClause; + $query = <<client->query($query) as $row) { + $type = array(); + if (isset($row->label)) $type['label'] = $row->label->getValue(); + if (isset($row->superclass)) $type['superclass'] = $row->superclass->getUri(); + $result[$row->type->getURI()] = $type; + } + + return $result; + } + + /** + * Retrieves conceptScheme information from the endpoint. + * @param string $cs concept scheme URI + * @return EasyRDF_Graph query result graph + */ + public function queryConceptScheme($cs) + { + $gc = $this->graphClause; + $query = << ?property ?value . + } WHERE { + $gc { + <$cs> ?property ?value . + FILTER (?property != skos:hasTopConcept) + } + } +EOQ; + + return $this->client->query($query); + } + + /** + * return a list of skos:ConceptScheme instances in the given graph + * @param string $lang language of labels + * @return array Array with concept scheme URIs (string) as keys and labels (string) as values + */ + public function queryConceptSchemes($lang) + { + $gc = $this->graphClause; + $query = <<client->query($query) as $row) { + $cs = array(); + if (isset($row->label)) + $cs['label'] = $row->label->getValue(); + if (isset($row->prefLabel)) + $cs['prefLabel'] = $row->prefLabel->getValue(); + if (isset($row->title)) + $cs['title'] = $row->title->getValue(); + $ret[$row->cs->getURI()] = $cs; + } + + return $ret; + } + + /** + * Make a text query condition that narrows the amount of search + * results in term searches. This is a stub implementation, + * intended to be overridden in subclasses to enable the use of + * tet indexes in SPARQL dialects that support them. + * + * @param string $term search term + * @return string SPARQL text search clause + */ + protected function createTextQueryCondition($term) + { + return '# generic SPARQL dialect, no text index support'; + } + + /** + * Query for concepts using a search term. + * @param string $term search term + * @param array $vocabs array of Vocabulary objects to search; empty for global search + * @param string $lang language code + * @param int $limit maximum number of hits to retrieve; 0 for unlimited + * @param int $offset offset of results to retrieve; 0 for beginning of list + * @param string $arrayClass the URI for thesaurus array class, or null if not used + * @param string $type limit search to concepts of the given type + * @param string $parent limit search to concepts which have the given concept as parent in the transitive broader hierarchy + * @param string $group limit search to concepts which are in the given group + * @param boolean $hidden include matches on hidden labels (default: true) + * @return array query result object + */ + public function queryConcepts($term, $vocabs, $lang, $limit, $offset, $arrayClass, $type, $parent=null, $group=null, $hidden=true) + { + $gc = $this->graphClause; + $limit = ($limit) ? 'LIMIT ' . $limit : ''; + $offset = ($offset) ? 'OFFSET ' . $offset : ''; + $type = EasyRdf_Namespace::expand($type); + + // extra types to query, if using thesaurus arrays + $extratypes = $arrayClass ? "UNION { ?s rdf:type <$arrayClass> }" : ""; + + // extra conditions for label language, if specified + $labelcond_match = ($lang) ? "&& langMatches(lang(?match), '$lang')" : ""; + $labelcond_label = ($lang) ? "&& langMatches(lang(?label), '$lang')" : ""; + + // extra conditions for parent and group, if specified + $parentcond = ($parent) ? "?s skos:broader+ <$parent> ." : ""; + $groupcond = ($group) ? "<$group> skos:member ?s ." : ""; + + $orderextra = $this->isDefaultEndpoint() ? $this->graph : ''; + + # make VALUES clauses + $props = array('skos:prefLabel','skos:altLabel'); + if ($hidden) $props[] = 'skos:hiddenLabel'; + $values_prop = $this->formatValues('?prop', $props); + + if ($this->isDefaultEndpoint() && sizeof($vocabs) > 0) { + $graphs = array(); + foreach ($vocabs as $voc) { + $graphs[] = $voc->getGraph(); + } + $values_graph = $this->formatValues('?graph', $graphs, 'uri'); + } else { + $values_graph = ""; + } + + + while (strpos($term, '**') !== false) + $term = str_replace('**', '*', $term); // removes futile asterisks + + $use_regex = false; + + if ($term == '0-9*') { + $term = '[0-9].*'; + $use_regex = true; + } elseif ($term == '!*') { + $term = '[^\\\\p{L}\\\\p{N}].*'; + $use_regex = true; + } + + # make text query clause + $textcond = $use_regex ? '# regex in use' : $this->createTextQueryCondition($term); + + # use appropriate matching function depending on query type: =, strstarts, strends or full regex + if (!$use_regex && preg_match('/^[^\*]+$/', $term)) { // exact query + $term = str_replace('\\', '\\\\', $term); // quote slashes + $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes + $filtercond = "lcase(str(?match)) = '$term'"; + } elseif (!$use_regex && preg_match('/^[^\*]+\*$/', $term)) { // prefix query + $term = substr($term, 0, -1); // remove the final asterisk + $term = str_replace('\\', '\\\\', $term); // quote slashes + $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes + $filtercond = "strstarts(lcase(str(?match)), '$term')" . // avoid matches on both altLabel and prefLabel + " && !(?match != ?label && strstarts(lcase(str(?label)), '$term'))"; + } elseif (!$use_regex && preg_match('/^\*[^\*]+$/', $term)) { // suffix query + $term = substr($term, 1); // remove the preceding asterisk + $term = str_replace('\\', '\\\\', $term); // quote slashes + $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes + $filtercond = "strends(lcase(str(?match)), '$term')"; + } else { // too complicated - have to use a regex + if (!$use_regex) { # not already formatted as a regex + # make sure regex metacharacters are not passed through + $term = str_replace('\\', '\\\\', preg_quote($term)); + $term = str_replace('\\\\*', '.*', $term); // convert asterisk to regex syntax + $term = str_replace('\'', '\\\'', $term); // ensure single quotes are quoted + } + $filtercond = "regex(str(?match), '^$term$', 'i')"; + } + + # order of graph clause and text query depends on whether we are performing global search + # global search: text query first, then process by graph + # local search: limit by graph first, then graph-specific text query + $graph_text = $this->isDefaultEndpoint() ? "$textcond \n $gc {" : "$gc { $textcond \n"; + + $query = << } $extratypes + { + $parentcond + $groupcond + ?s skos:prefLabel ?label . + ?s rdf:type ?type . + ?s ?prop ?match . + FILTER ( + $filtercond + $labelcond_match + && langMatches(lang(?label), lang(?match)) + ) + } + FILTER NOT EXISTS {?s owl:deprecated true } + } + BIND(IF(?prop = skos:altLabel, ?match, ?unbound) as ?alabel) + BIND(IF(?prop = skos:hiddenLabel, ?match, ?unbound) as ?hlabel) + $values_prop + } + + GROUP BY ?match ?s ?label ?alabel ?hlabel ?graph ?prop + ORDER BY lcase(str(?match)) lang(?match) $orderextra + $limit + $offset + $values_graph +EOQ; + + $results = $this->client->query($query); + $ret = array(); + $qnamecache = array(); // optimization to avoid expensive shorten() calls + + foreach ($results as $row) { + if (!isset($row->s)) continue; // don't break if query returns a single dummy result + + $hit = array(); + $hit['uri'] = $row->s->getUri(); + + if (isset($row->graph)) + $hit['graph'] = $row->graph->getUri(); + + foreach (explode(" ", $row->types->getValue()) as $typeuri) { + if (!array_key_exists($typeuri, $qnamecache)) { + $res = new EasyRdf_Resource($typeuri); + $qname = $res->shorten(); // returns null on failure + $qnamecache[$typeuri] = $qname != null ? $qname : $typeuri; + } + $hit['type'][] = $qnamecache[$typeuri]; + } + + $hit['localname'] = $row->s->localName(); + + $hit['prefLabel'] = $row->label->getValue(); + if ($row->label->getLang() != $lang) { + $hit['lang'] = $row->label->getLang(); + } + + if (isset($row->alabel)) + $hit['altLabel'] = $row->alabel->getValue(); + if (isset($row->hlabel)) + $hit['hiddenLabel'] = $row->hlabel->getValue(); + + + $ret[] = $hit; + } + + return $ret; + } + + /** + * Query for a prefLabel of a concept. + * @param string $uri + * @param string $lang + * @return array array of labels (key: lang, val: label), or null if concept doesn't exist + */ + public function queryLabel($uri, $lang) + { + $gc = $this->graphClause; + $labelcond_label = ($lang) ? "FILTER( langMatches(lang(?label), '$lang') )" : ""; + $query = << a skos:Concept . + OPTIONAL { + <$uri> skos:prefLabel ?label . + $labelcond_label + } + OPTIONAL { + <$uri> rdfs:label ?label . + $labelcond_label + } + } + } +EOQ; + $result = $this->client->query($query); + $ret = array(); + foreach ($result as $row) { + if (!isset($row->label)) + return array(); // existing concept but no labels + $ret[$row->label->getLang()] = $row->label->getValue(); + } + + if (sizeof($ret) > 0) + return $ret; // existing concept, with label(s) + else + return null; // nonexistent concept + } + + + /** + * Query a single property of a concept. + * @param string $uri + * @param string $prop the name of the property eg. 'skos:broader'. + * @param string $lang + * @param boolean $anylang if you want a label even when it isn't available in the language you requested. + * @return array array of property values (key: URI, val: label), or null if concept doesn't exist + */ + public function queryProperty($uri, $prop, $lang, $anylang = false) + { + $uri = is_array($uri) ? $uri[0] : $uri; + $gc = $this->graphClause; + $anylang = $anylang ? "OPTIONAL { ?object skos:prefLabel ?label }" : ""; + + $query = << a skos:Concept . + OPTIONAL { + <$uri> $prop ?object . + OPTIONAL { + ?object skos:prefLabel ?label . + FILTER (langMatches(lang(?label), "$lang")) + } + OPTIONAL { + ?object skos:prefLabel ?label . + FILTER (lang(?label) = "") + } + $anylang + } + } + } +EOQ; + $result = $this->client->query($query); + $ret = array(); + foreach ($result as $row) { + if (!isset($row->object)) + return array(); // existing concept but no properties + if (isset($row->label)) { + if ($row->label->getLang() === $lang || array_key_exists($row->object->getUri(), $ret) === false) + $ret[$row->object->getUri()]['label'] = $row->label->getValue(); + } else { + $ret[$row->object->getUri()]['label'] = null; + } + } + if (sizeof($ret) > 0) + return $ret; // existing concept, with properties + else + return null; // nonexistent concept + } + + /** + * Query a single transitive property of a concept. + * @param string $uri + * @param string $prop the name of the property eg. 'skos:broader'. + * @param string $lang + * @param integer $limit + * @param boolean $anylang if you want a label even when it isn't available in the language you requested. + * @return array array of property values (key: URI, val: label), or null if concept doesn't exist + */ + public function queryTransitiveProperty($uri, $prop, $lang, $limit, $anylang=false) + { + $uri = is_array($uri) ? $uri[0] : $uri; + $gc = $this->graphClause; + $filter = $anylang ? "" : "FILTER (langMatches(lang(?label), \"$lang\"))"; + $query = << a skos:Concept . + OPTIONAL { + <$uri> $prop* ?object . + OPTIONAL { + ?object skos:prefLabel ?label . + $filter + } + } + } + } + LIMIT $limit +EOQ; + $result = $this->client->query($query); + $ret = array(); + foreach ($result as $row) { + if (!isset($row->object)) + return array(); // existing concept but no properties + if (isset($row->label)) { + $val = array('label'=>$row->label->getValue()); + if ($row->label->getLang() !== $lang) + $val['label'] .= ' (' . $row->label->getLang() . ')'; + } else { + $val = array('label'=>null); + } + $direct = $this->queryProperty($row->object->getUri(), $prop, $lang, $anylang); + if (sizeof($direct) > 0) $val['direct'] = $direct; + // Preventing labels in a non preferred language overriding the preferred language. + if (isset($row->label) && $row->label->getLang() === $lang || array_key_exists($row->object->getUri(), $ret) === false) + $ret[$row->object->getUri()] = $val; + } + if (sizeof($ret) > 0) + return $ret; // existing concept, with properties + else + return null; // nonexistent concept + } + + /** + * Query the narrower concepts of a concept. + * @param string $uri + * @param string $lang + * @return array array of arrays describing each child concept, or null if concept doesn't exist + */ + public function queryChildren($uri, $lang) + { + $uri = is_array($uri) ? $uri[0] : $uri; + $gc = $this->graphClause; + $query = << a skos:Concept . + OPTIONAL { + <$uri> skos:narrower ?child . + OPTIONAL { + ?child skos:prefLabel ?label . + FILTER (langMatches(lang(?label), "$lang")) + } + BIND ( EXISTS { ?child skos:narrower ?a . } AS ?grandchildren ) + } + } + } +EOQ; + $ret = array(); + $result = $this->client->query($query); + foreach ($result as $row) { + if (!isset($row->child)) + return array(); // existing concept but no children + $ret[] = array( + 'uri' => $row->child->getUri(), + 'prefLabel' => isset($row->label) ? $row->label->getValue() : null, + 'hasChildren' => filter_var($row->grandchildren->getValue(), FILTER_VALIDATE_BOOLEAN), + ); + } + if (sizeof($ret) > 0) + return $ret; // existing concept, with children + else + return null; // nonexistent concept + } + + /** + * Query the top concepts of a vocabulary. + * @param string $conceptScheme + * @param string $lang + */ + public function queryTopConcepts($conceptScheme='?concept', $lang) + { + $gc = $this->graphClause; + $query = << . + ?top skos:prefLabel ?label . + FILTER (langMatches(lang(?label), "$lang")) + } + } +EOQ; + $result = $this->client->query($query); + $ret = array(); + foreach ($result as $row) { + if (isset($row->top) && isset($row->label)) { + $ret[$row->top->getUri()] = $row->label->getValue(); + } + } + + return $ret; + } + + /** + * Query for finding the hierarchy for a concept. + * @param string $uri concept uri. + * @param string $lang + * @return an array for the REST controller to encode. + */ + public function queryParentList($uri, $lang) + { + $orig_uri = $uri; + $gc = $this->graphClause; + $query = << a skos:Concept . + OPTIONAL { + <$uri> skos:broader* ?broad . + OPTIONAL { + ?broad skos:prefLabel ?label . + FILTER (langMatches(lang(?label), "$lang")) + } + OPTIONAL { ?broad skos:broader ?parent . } + OPTIONAL { ?broad skos:narrower ?children . + OPTIONAL { ?children skos:prefLabel ?childlabel . + FILTER (langMatches(lang(?childlabel), "$lang")) + } + } + BIND ( EXISTS { ?children skos:narrower ?a . } AS ?grandchildren ) + OPTIONAL { ?broad skos:topConceptOf ?topcs . } + } + } + } + GROUP BY ?broad ?label ?parent ?member ?children ?childlabel ?grandchildren +EOQ; + $result = $this->client->query($query); + $ret = array(); + foreach ($result as $row) { + if (!isset($row->broad)) + return array(); // existing concept but no broaders + $uri = $row->broad->getUri(); + if (!isset($ret[$uri])) { + $ret[$uri] = array('uri'=>$uri); + } + if (isset($row->exact)) { + $ret[$uri]['exact'] = $row->exact->getUri(); + } + if (isset($row->top)) { + $ret[$uri]['top'] = $row->top->getUri(); + } + if (isset($row->children)) { + if(!isset($ret[$uri]['narrower'])) + $ret[$uri]['narrower'] = array(); + $child_arr = array( + 'uri' => $row->children->getUri(), + 'label' => isset($row->childlabel) ? $row->childlabel->getValue() : null, + 'hasChildren' => filter_var($row->grandchildren->getValue(), FILTER_VALIDATE_BOOLEAN), + ); + if(!in_array($child_arr, $ret[$uri]['narrower'])) + $ret[$uri]['narrower'][] = $child_arr; + } + $ret[$uri]['prefLabel'] = isset($row->label) ? $row->label->getValue() : null; + if (isset($row->parent) && (isset($ret[$uri]['broader']) && !in_array($row->parent->getUri(), $ret[$uri]['broader']))) { + $ret[$uri]['broader'][] = $row->parent->getUri(); + } elseif (isset($row->parent) && !isset($ret[$uri]['broader'])) { + $ret[$uri]['broader'][] = $row->parent->getUri(); + } + } + // querying the 'leaf' concepts information too. + $result = $this->queryConceptInfo($orig_uri, false, $lang); //conceptDAO + if (isset($result)) { + $result = $result[0]; + $ret[$result->getUri()]['prefLabel'] = $result->getLabel(); + } + if (sizeof($ret) > 0) + return $ret; // existing concept, with children + else + return null; // nonexistent concept + } + + /** + * return a list of concept group instances, sorted by label + * @param string $groupClass URI of concept group class + * @param string $lang language of labels to return + * @return array Result array with group URI as key and group label as value + */ + public function listConceptGroups($groupClass, $lang) + { + $gc = $this->graphClause; + $query = << . + { ?group skos:prefLabel ?label } UNION { ?group rdfs:label ?label } + FILTER (langMatches(lang(?label), '$lang')) + } + } ORDER BY lcase(?label) +EOQ; + $ret = array(); + $result = $this->client->query($query); + foreach ($result as $row) { + $ret[$row->group->getURI()] = $row->label->getValue(); + } + + return $ret; + } + + /** + * return a list of concepts in a concept group + * @param string $groupClass URI of concept group class + * @param string $group URI of the concept group instance + * @param string $lang language of labels to return + * @return array Result array with concept URI as key and concept label as value + */ + public function listConceptGroupContents($groupClass, $group, $lang) + { + $gc = $this->graphClause; + $query = << a <$groupClass> . + <$group> skos:member ?conc . + ?conc skos:prefLabel ?label . + FILTER (langMatches(lang(?label), '$lang')) + } + } ORDER BY lcase(?label) +EOQ; + $ret = array(); + $result = $this->client->query($query); + foreach ($result as $row) { + $ret[$row->conc->getURI()] = $row->label->getValue(); + } + + return $ret; + } +} diff --git a/model/sparql/JenaTextSparql.php b/model/sparql/JenaTextSparql.php new file mode 100644 index 000000000..e1c34e9c9 --- /dev/null +++ b/model/sparql/JenaTextSparql.php @@ -0,0 +1,49 @@ +MAX_N) }"; + } +} diff --git a/phpdoc.sh b/phpdoc.sh new file mode 100644 index 000000000..94e0db5e3 --- /dev/null +++ b/phpdoc.sh @@ -0,0 +1,2 @@ +phpdoc -f index.php -f rest.php -d controller -d model --template="responsive-twig" + diff --git a/resource/css/bootstrap-multiselect.css b/resource/css/bootstrap-multiselect.css new file mode 100755 index 000000000..4d8bd5c8b --- /dev/null +++ b/resource/css/bootstrap-multiselect.css @@ -0,0 +1 @@ +.multiselect-container{position:absolute;list-style-type:none;margin:0;padding:0}.multiselect-container .input-group{margin:5px}.multiselect-container>li{padding:0}.multiselect-container>li>a.multiselect-all label{font-weight:700}.multiselect-container>li>label.multiselect-group{margin:0;padding:3px 20px;height:100%;font-weight:700}.multiselect-container>li>a{padding:0}.multiselect-container>li>a>label{margin:0;height:100%;cursor:pointer;font-weight:400;padding:3px 20px 3px 40px}.multiselect-container>li>a>label.radio,.multiselect-container>li>a>label.checkbox{margin:0}.multiselect-container>li>a>label>input[type=checkbox]{margin-bottom:5px}.btn-group>.btn-group:nth-child(2)>.multiselect.btn{border-top-left-radius:4px;border-bottom-left-radius:4px} \ No newline at end of file diff --git a/resource/css/combine.css b/resource/css/combine.css new file mode 100644 index 000000000..4f525ebd0 --- /dev/null +++ b/resource/css/combine.css @@ -0,0 +1,839 @@ +@charset "utf-8"; +@import 'fira.css'; +@import '../../vendor/components/jqueryui/themes/cupertino/jquery-ui.min.css'; + +@media print { /* Quick style sheet for printing. */ + body * { + visibility: hidden; + } + + /* Everything under the print class elements will be printed. */ + .print,.print * { + visibility: visible; + } + + /* Position elements at the beginning of the page. */ + .print { + position: absolute; + left: 0; + top: 0; + } +} + +a:link,a:visited,a:active,a:hover,a:focus { + /* Improves link handling by removing focus borders. Sorry keyboard users! */ + text-decoration: none; + border: 0; + outline: none; +} + +a:hover { + text-decoration: underline; + /* All text links have underlining when hovered. */ +} + +table { + border-spacing: 0; + table-layout: fixed; + word-wrap: break-word; +} + +img { + border: none; +} + +/* Font definitions + *****************************************/ +h1 { + font-family: 'Fira Sans' !important; + font-size: 27px !important; + font-weight: 400 !important; +} + +h2 { + font-family: 'Fira Sans Bold' !important; + font-style: bold !important; + font-size: 16px !important; + font-weight: 600 !important; +} + +a.navigation-font { + font-family: 'Fira Sans'; + font-size: 16px; + margin-top: auto; + margin-bottom: auto; + margin: auto 10px; +} + +.top-row a.navigation-font { + color: #394554; +} + +p { + font-family: 'Fira Sans'; + font-size: 16px; +} + +.versal { + font-family: 'Fira Sans'; + font-size: 18px; +} + +.versal-bold { + font-family: 'Fira Sans Bold'; + font-size: 18px; +} + +/* top-bar -- logo, navi and language sel. + *****************************************/ +#navigation { + height: 50px; + min-width: 0px; + max-width: 530px; + width: auto; + overflow: hidden; + float: right; + line-height: 70px; +} + +#navigation > div > a > img { + margin-bottom: 5px; +} + +#navigation .link-bg { + height: 60px; + float: left; +} + +#language { + height: 50px; + min-width: 200px; + max-width: 350px; + width: auto; + padding-left: 0px; + text-align: right; + overflow: hidden; + float: right; + line-height: 70px; +} + +.language-selection { + margin: auto 3px; + z-index: 0; +} + +#top-bar-content { + background-color: #ffffff; + text-align: left; + //background: none repeat scroll 0 0 rgba(24, 51, 75, 0.30); + box-shadow: 0 0px 12px rgba(0, 0, 0, 0.25); + //background-color: #181818; + } + +/* header-bar -- header, info, search-tab. + *****************************************/ + +#header-bar-content { + display: block; + background-color: #75889f; + height: 50px; + max-width: 1020px; + margin: 0 auto; +} + +.navbar-inverse { + //background: url("../pics/pattern5.png"); + background-color: rgb(184,192,199) !important; /* Old browsers */ + background: -moz-linear-gradient(top, rgba(255,255,255,1) 0%, rgba(229,229,229,1) 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,1)), color-stop(100%,rgba(229,229,229,1))); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, rgba(255,255,255,1) 0%,rgba(229,229,229,1) 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, rgba(255,255,255,1) 0%,rgba(229,229,229,1) 100%); /* Opera 11.10+ */ + background: -ms-linear-gradient(top, rgba(255,255,255,1) 0%,rgba(229,229,229,1) 100%); /* IE10+ */ + background: linear-gradient(to bottom, rgba(255,255,255,1) 0%,rgba(229,229,229,1) 100%); /* W3C */ +} + +#header-bar-page-title { + overflow: hidden; + text-align: left; + line-height: 30px; + width: 100px; + float: left; +} + +.header-bar-page-title-text { + float: left; + line-height: 40px; + margin-left: 18px; +} + +.header-bar-page-title-text h3 { + margin-top: 0px; + margin-bottom: 0px; + line-height: 40px; +} + +/* footer -- the bottom bar of the page. + *****************************************/ +#footer { + min-height: 10px; +} + +/* search-from-all-vocabularies + * on the frontpage below the infobar + *****************************************/ + +#search-wide * { + vertical-align: middle; +} + +#search-wide h3 { + float: left; + line-height: 29px; + width: 245px; +} + +#center-container { + margin: auto; +} + +#right-container { + display: block; + float: left; +} + +#search-from-vocabulary * { + vertical-align: middle; +} + +#search-all-text { + width: 260px; + text-align: right; + float: left; +} + +.search-parameter-highlight { + display: inline-block; + word-wrap: break-word; + margin-left: 4px; + max-width: 570px; + line-height: 18px; +} + +#search-vocab-parameter { + display: block; + border: 1px solid #666666; + border-radius: 0; + box-shadow: 1px 1px 1 #444444 inset; + border-right: none; + background: none repeat scroll 0 0 rgba(0, 0, 0, 0.20); + font-family: Tahoma, Geneva, sans-serif; + font-size: 14px; + font-weight: bold; + padding-left: 8px !important; +} + +.navbar-left { + margin: 0px !important; +} + +#search-vocab-parameter::-moz-placeholder { + color: #FFFFFF; +} + +.search-vocab-container { + float: left; + margin: auto; +} + +#search-from-all-vocabularies-radio-buttons { + display: none; + width: 110px; + margin: 0 0 0px 15px; +} + +#lang-dropdown-toggle { + background-color: #555555; + border-color: #666666; + border-right: none; + border-radius: 0 !important; + color: white; + padding-left: 6px !important; + padding-right: 6px !important; +} + +.search-all-vocabs { + width: 245px; + float: left; + line-height: 29px; +} + +#search-all-button { + background-color: #555555; + border-left: none; + border-color: #666666; + border-radius: 0 !important; +} + +.input-group-btn { + width: 50px !important; +} + +/* vocabulary-list + *****************************************/ +#vocabulary-list { + background-color: #ffffff; + max-width: 615px !important; + border-top: 2px solid #394554; +} + +.book-logo { + float: left; + margin: 0 15px 10px -15px; +} + +#vocabulary-list table { + margin-bottom: 0px; +} + +#vocabulary-list th, #vocabulary-list td { + border-top: none !important; + padding: 0 8px !important; +} + +#vocabulary-list th { + text-align: right !important; + width: 150px !important; + max-width: 150px !important; +} + +.vocab-link { + font-size: 16px; +} + +/* vocabulary-info + *****************************************/ +#vocab-info { + font-size: 16px; + margin: auto; + max-width: 700px; +} + +.vocab-info-literals th { + width: 110px; +} + +/* styling classes + *****************************************/ +.boxed { + border-left: none; + border-right: none; + background-color: #FFFFFF; +} + +.middle { + margin: auto 0 !important; +} + +.spinner { + display:inline-block; + width: 25px; + height: 25px; + margin-left: 5px; + vertical-align: middle; + background:url("../pics/spinner.gif") no-repeat transparent; +} + +.spinner-text { + color: #428BCA; + font-size: 16px; + line-height: 30px; +} + +/* feedback-form layout + *****************************************/ +#feedback-form { + display: inline-block; + margin-top: 20px; + width: 450px; + border: 1px solid #DBDBDB; + border-radius: 10px; + background-color: #FAFAF9; + margin-left: 0; +} + +#feedback-form h1 { + float: right; + font-size: 173px; + margin-right: 10px; + color: #666666; + margin-top: -60px; +} + +#feedback-content { + width: 300px; + margin: 20px; +} + +#feedback-content #send-feedback { + width: auto; + margin-top: 15px; +} + +#feedback-fields input { + width: 100%; +} + +.feedback-field-label { + margin-top: 7px; + display: block +} + +#feedback-fields #message { + width: 400px; + height: 250px; + resize: none; +} + +#feedback-label { + padding-top: 20px; + padding-bottom: 20px; + text-align: center; + width:100%; +} + +.error { + color: red; +} + +/* about-page + *****************************************/ +#about-content { + margin: 15px 15px; +} + +#about-content p { + margin-top: 10px; +} + +#about-developers, #about-help, #about-seco, #about-header-text { + margin-top: 10px; +} + +#about-logo { + float: right; + margin-top: -150px; + margin-right: 30px; +} + +#about-organization { + margin: 30px auto; +} + +/* concept-info + *****************************************/ + +.concept-background { + display: block; + height: 500px; + position: absolute; +} + +.concept-info { + margin-top: 15px; + margin-bottom: 15px; +} + +.uri-input-box { + font-size: 15px; +} + +.preflabel-input-box { + background: transparent; + border: 0px solid white; + min-width: 580px; +} + +.subvalue { + margin-left: 15px; +} + +.bottomtext { + width: 100%; + display: inline-block; + float: left; + margin-left: 5px; +} + +#vocab-content { + margin: auto; + position: relative; + max-width: 800px; +} + +.download-wrapper { + margin: auto; + min-width: 450px; +} + +.download-wrapper h4 { + display: inline-block; +} + +.deprecated-alert { + margin-top: 10px; + margin-left: 20px; + color: #D95F8A; +} + +.deprecated span, .deprecated h4, .deprecated h3, .deprecated a { + color: #A6A6A6 !important; +} + +/* vocabulary search listing + *****************************************/ +.search-results-property-table { + font-size: 14px; + text-align: left; + border-collapse: collapse; + margin: 0 !important; +} + +.search-results-property-table ul { + list-style-type: none; + padding: 0px; +} + +.search-results-property-table td { + border: 0 !important; + padding: 4px !important; +} + +td.col-md-4 { + width: 310px !important; +} + +.helper-text { + margin-left: 15px !important; +} + +.search-results-property-table p { + margin: 0; +} + +.concept-headers { + padding-bottom: 5px; + border-bottom: 1px dotted #BFBFBF; +} + +.search-results-property-table tr>td { + vertical-align: top; +} + +.concept-appendix { + background-color: #EFEFEF; +} + +.result-label { + padding-left: 13px; +} + +div.boxed { + padding: 0 !important; +} + +span.searchword { + color: #D95F8A; +} + +.loading-text { + width: 635px; + text-align: center; + padding-top: 20px; + float: left; + margin: auto; +} + +.result-count-text { + text-align: center; + display: inline-block; + width: 615px; +} + +.propertyvaluelabel { + margin-left: 15px; +} + +/* alphabetical vocabulary listing + *****************************************/ +.alphabet-header h5 { + margin-top: 2px; + margin-bottom: 2px; +} + +.alphabetical-headers { + margin: auto; + text-align: center; +} + +#alphabetical-menu { + background-color: #eaf6f4; + margin-right: 20px; + max-width: 320px; +} + +#alphabetical-menu .wide { + margin-top: 10px; +} + +#alphabetical-menu .highlight { + color: black; + font-weight: bold; +} + +.alphabetical-search-results { + float: left; +} + +.alphabetical-search-results li { + list-style-type: none; + line-height: 15px; + font-size: 14px; +} + +.activated-concept { + font-weight: bold; + color: #D95F8A !important; +} + +/* group index & contents + **********************/ + +#group-listing-container ul, #group-listing-container li, +#group-contents-listing-container ul, #group-contents-listing-container li { + list-style-type: none; + margin: 0; + padding: 0; +} + +#group-listing-container li { + margin-top: 0.8em; } + + +/* content -- the page content area style. + *****************************************/ +#maincontent { + clear: both; + display: block; + width: 100%; + margin: 0 auto; +} + +/* Autocomplete + *********************************/ +.ui-autocomplete { + max-height:300px; + min-width: 300px; + max-width: 300px; + overflow-y: auto; + border-top-right-radius: 0px !important; + border-top-left-radius: 0px !important; +} + +.autocomplete-vocab { + float: right; +} + +/* jstree + ***************/ +.jstree a { color: #428BCA !important; font-size: 14px !important; white-space: normal !important; display: inline !important; } +#jstree-leaf-proper { font-weight: bold !important; color: #D95F8A !important; } +#jstree-vocablabel { color:#A6A6A6 !important; } +.jstree li { /*text-indent: -20px;*/ margin-left: 10px !important; vertical-align: top; } +.jstree-hovered { background: none !important; border: none !important; padding: 1px 2px !important; } + +/* hierarchy navigation + **************/ +.jstree-no-icons { + margin-left: -5px !important; +} + +/* mouseover symbols + ***************/ +.mouseover-help-icon { + background-image: url(../pics/question.png); + background-repeat: no-repeat; + padding-right: 16px; + margin-left: 3px; +} + +.centered { + text-align: center; +} + +#crumb-container { + margin: 15px 15px 0 15px; +} + +.bread-crumb { + font-size: 10px !important; +} + +.hidden-breadcrumb { + font-size: 10px !important; + display: none; +} + +.container { + max-width: 1020px !important; + padding: 0px !important; +} + +.navbar-inverse, .navbar-form { + border-width: 0 !important; + margin-top: 0px !important; + padding: 0 !important; +} + +.navbar-form { + box-shadow: none !important; + padding-top: 3px !important; +} + +#wrapper { + padding-left: 0; +} + +#page-wrapper { + width: 100%; + padding: 5px 0px; +} + +/* Edit Below to Customize Widths > 768px */ +@media (min-width:768px) { + + body { + background-color: rgba(213,202,171,0.3); + } + + /* Wrappers */ + + #page-wrapper { + padding: 0; + } + + /* Side Nav */ + + .side-nav { + height: 100%; + max-width: 400px; + //border-right: 1px solid #DBDBDB; + background-color: #eaf6f4; + overflow-y: auto; + } + + /* Bootstrap Default Overrides - Customized Dropdowns for the Side Nav */ + + .side-nav>li.dropdown>ul.dropdown-menu { + position: relative; + min-width: 30%; + margin: 0; + padding: 0; + border: none; + border-radius: 0; + background-color: transparent; + box-shadow: none; + -webkit-box-shadow: none; + } + + .side-nav>li.dropdown>ul.dropdown-menu>li>a { + color: #999999; + padding: 15px 15px 15px 25px; + } + + .side-nav>li.dropdown>ul.dropdown-menu>li>a:hover, + .side-nav>li.dropdown>ul.dropdown-menu>li>a.active, + .side-nav>li.dropdown>ul.dropdown-menu>li>a:focus { + color: #fff; + background-color: #080808; + } + + .side-nav>li>a { + width: 30%; + } + + .navbar-inverse .navbar-nav>li>a:hover, + .navbar-inverse .navbar-nav>li>a:focus { + background-color: #080808; + } + + .ui-front { + z-index: 2000 !important; + } + + .search-vocab-text { + display: inline-block !important; + line-height: 40px; + margin-top: 3px; + float: left; + } + + .headerbar-left { + position: fixed; + } + + .headerbar-right { + float: right; + } + + .navbar { + border-bottom: 0 !important; + } + + .navbar-collapse { + padding-left: 0px !important; + } + + .pagination > li > a, .pagination > li > span { + border: 0 !important; + background-color: transparent !important; + padding: 4px 8px !important; + } + + .pagination > li:last-child > a, .pagination > li:last-child > span { + border-radius: 0 !important; + } + + .pagination > li:first-child > a { + border-radius: 0 !important; + margin-left: -1px !important; + } + + .pagination { margin: 10px 0 0 0 !important;} + + .nav-tabs { + border-bottom: none !important; + } + + .nav-tabs > li > a { + background-color: #bbe3de; + border-radius: 0 !important; + margin-right: 0 !important; + } + + .nav-tabs > li > a:hover { + background-color: #ffffff !important; + } + + .nav-tabs > li.active > a { + background-color: #eaf6f4 !important; + } + + .language-selection { + margin: auto 11px; + } + + .search-vocab-parameter { + width: 250px; + } + +} diff --git a/resource/css/fira.css b/resource/css/fira.css new file mode 100644 index 000000000..b8125f54a --- /dev/null +++ b/resource/css/fira.css @@ -0,0 +1,66 @@ +@font-face{ + font-family: 'Fira Sans'; + src: url('../fonts/eot/FiraSans-Light.eot'); + src: local('Fira Sans Light'), + url('../fonts/eot/FiraSans-Light.eot') format('embedded-opentype'), + url('../fonts/woff/FiraSans-Light.woff') format('woff'), + url('../fonts/ttf/firasans-light.ttf') format('truetype'); + font-weight: 300; + font-style: normal; +} + +@font-face{ + font-family: 'Fira Sans'; + src: url('../fonts/eot/FiraSans-Regular.eot'); + src: local('Fira Sans Regular'), + url('../fonts/eot/FiraSans-Regular.eot') format('embedded-opentype'), + url('../fonts/woff/FiraSans-Regular.woff') format('woff'), + url('../fonts/ttf/FiraSans-Regular.ttf') format('truetype'); + font-weight: 400; + font-style: normal; +} + +@font-face{ + font-family: 'Fira Sans'; + src: url('../fonts/eot/FiraSans-Medium.eot'); + src: local('Fira Sans Medium'), + url('../fonts/eot/FiraSans-Medium.eot') format('embedded-opentype'), + url('../fonts/woff/FiraSans-Medium.woff') format('woff'), + url('../fonts/ttf/FiraSans-Medium.ttf') format('truetype'); + font-weight: 500; + font-style: normal; +} + +@font-face{ + font-family: 'Fira Sans'; + src: url('../fonts/eot/FiraSans-Bold.eot'); + src: local('Fira Sans Bold'), + url('../fonts/eot/FiraSans-Bold.eot') format('embedded-opentype'), + url('../fonts/woff/FiraSans-Bold.woff') format('woff'), + url('../fonts/ttf/FiraSans-Bold.ttf') format('truetype'); + font-weight: 600; + font-style: normal; +} + +@font-face{ + font-family: 'Fira Mono'; + src: url('../fonts/eot/FiraMono-Regular.eot'); + src: local('Fira Mono'), + url('../fonts/eot/FiraMono-Regular.eot') format('embedded-opentype'), + url('../fonts/woff/FiraMono-Regular.woff') format('woff'), + url('../fonts/ttf/FiraMono-Regular.ttf') format('truetype'); + font-weight: 400; + font-style: normal; +} + +@font-face{ + font-family: 'Fira Mono'; + src: url('../fonts/eot/FiraMono-Bold.eot'); + src: local('Fira Mono Bold'), + url('../fonts/eot/FiraMono-Bold.eot') format('embedded-opentype'), + url('../fonts/woff/FiraMono-Bold.woff') format('woff'), + url('../fonts/ttf/FiraMono-Bold.ttf') format('truetype'); + font-weight: 600; + font-style: normal; +} + diff --git a/resource/css/jquery.mCustomScrollbar.css b/resource/css/jquery.mCustomScrollbar.css new file mode 100644 index 000000000..af4ff0077 --- /dev/null +++ b/resource/css/jquery.mCustomScrollbar.css @@ -0,0 +1,559 @@ +/* basic scrollbar styling */ +/* vertical scrollbar */ +.mCSB_container{ + width:auto; + margin-right:30px; + overflow:hidden; +} +.mCSB_container.mCS_no_scrollbar{ + margin-right:0; +} +.mCS_disabled>.mCustomScrollBox>.mCSB_container.mCS_no_scrollbar, +.mCS_destroyed>.mCustomScrollBox>.mCSB_container.mCS_no_scrollbar{ + margin-right:30px; +} +.mCustomScrollBox>.mCSB_scrollTools{ + width:16px; + height:100%; + top:0; + right:0; +} +.mCSB_scrollTools .mCSB_draggerContainer{ + position:absolute; + top:0; + left:0; + bottom:0; + right:0; + height:auto; +} +.mCSB_scrollTools a+.mCSB_draggerContainer{ + margin:20px 0; +} +.mCSB_scrollTools .mCSB_draggerRail{ + width:2px; + height:100%; + margin:0 auto; + -webkit-border-radius:10px; + -moz-border-radius:10px; + border-radius:10px; +} +.mCSB_scrollTools .mCSB_dragger{ + cursor:pointer; + width:100%; + height:30px; +} +.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ + width:4px; + height:100%; + margin:0 auto; + -webkit-border-radius:10px; + -moz-border-radius:10px; + border-radius:10px; + text-align:center; +} +.mCSB_scrollTools .mCSB_buttonUp, +.mCSB_scrollTools .mCSB_buttonDown{ + display:block; + position:relative; + height:20px; + overflow:hidden; + margin:0 auto; + cursor:pointer; +} +.mCSB_scrollTools .mCSB_buttonDown{ + top:100%; + margin-top:-40px; +} +/* horizontal scrollbar */ +.mCSB_horizontal>.mCSB_container{ + height:auto; + margin-right:0; + margin-bottom:30px; + overflow:hidden; +} +.mCSB_horizontal>.mCSB_container.mCS_no_scrollbar{ + margin-bottom:0; +} +.mCS_disabled>.mCSB_horizontal>.mCSB_container.mCS_no_scrollbar, +.mCS_destroyed>.mCSB_horizontal>.mCSB_container.mCS_no_scrollbar{ + margin-right:0; + margin-bottom:30px; +} +.mCSB_horizontal.mCustomScrollBox>.mCSB_scrollTools{ + width:100%; + height:16px; + top:auto; + right:auto; + bottom:0; + left:0; + overflow:hidden; +} +.mCSB_horizontal>.mCSB_scrollTools a+.mCSB_draggerContainer{ + margin:0 20px; +} +.mCSB_horizontal>.mCSB_scrollTools .mCSB_draggerRail{ + width:100%; + height:2px; + margin:7px 0; + -webkit-border-radius:10px; + -moz-border-radius:10px; + border-radius:10px; +} +.mCSB_horizontal>.mCSB_scrollTools .mCSB_dragger{ + width:30px; + height:100%; +} +.mCSB_horizontal>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ + width:100%; + height:4px; + margin:6px auto; + -webkit-border-radius:10px; + -moz-border-radius:10px; + border-radius:10px; +} +.mCSB_horizontal>.mCSB_scrollTools .mCSB_buttonLeft, +.mCSB_horizontal>.mCSB_scrollTools .mCSB_buttonRight{ + display:block; + position:relative; + width:20px; + height:100%; + overflow:hidden; + margin:0 auto; + cursor:pointer; + float:left; +} +.mCSB_horizontal>.mCSB_scrollTools .mCSB_buttonRight{ + margin-left:-40px; + float:right; +} +.mCustomScrollBox{ + -ms-touch-action:none; /*MSPointer events - direct all pointer events to js*/ +} + +/* default scrollbar colors and backgrounds (default theme) */ +.mCustomScrollBox>.mCSB_scrollTools{ + opacity:0.75; + filter:"alpha(opacity=75)"; -ms-filter:"alpha(opacity=75)"; /* old ie */ +} +.mCustomScrollBox:hover>.mCSB_scrollTools{ + opacity:1; + filter:"alpha(opacity=100)"; -ms-filter:"alpha(opacity=100)"; /* old ie */ +} +.mCSB_scrollTools .mCSB_draggerRail{ + background:#000; /* rgba fallback */ + background:rgba(0,0,0,0.4); + filter:"alpha(opacity=40)"; -ms-filter:"alpha(opacity=40)"; /* old ie */ +} +.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ + background:#fff; /* rgba fallback */ + background:rgba(255,255,255,0.75); + filter:"alpha(opacity=75)"; -ms-filter:"alpha(opacity=75)"; /* old ie */ +} +.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ + background:rgba(255,255,255,0.85); + filter:"alpha(opacity=85)"; -ms-filter:"alpha(opacity=85)"; /* old ie */ +} +.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, +.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ + background:rgba(255,255,255,0.9); + filter:"alpha(opacity=90)"; -ms-filter:"alpha(opacity=90)"; /* old ie */ +} +.mCSB_scrollTools .mCSB_buttonUp, +.mCSB_scrollTools .mCSB_buttonDown, +.mCSB_scrollTools .mCSB_buttonLeft, +.mCSB_scrollTools .mCSB_buttonRight{ + background-image:url(../mCSB_buttons.png); + background-repeat:no-repeat; + opacity:0.4; + filter:"alpha(opacity=40)"; -ms-filter:"alpha(opacity=40)"; /* old ie */ +} +.mCSB_scrollTools .mCSB_buttonUp{ + background-position:0 0; + /* + sprites locations are 0 0/-16px 0/-32px 0/-48px 0 (light) and -80px 0/-96px 0/-112px 0/-128px 0 (dark) + */ +} +.mCSB_scrollTools .mCSB_buttonDown{ + background-position:0 -20px; + /* + sprites locations are 0 -20px/-16px -20px/-32px -20px/-48px -20px (light) and -80px -20px/-96px -20px/-112px -20px/-128px -20px (dark) + */ +} +.mCSB_scrollTools .mCSB_buttonLeft{ + background-position:0 -40px; + /* + sprites locations are 0 -40px/-20px -40px/-40px -40px/-60px -40px (light) and -80px -40px/-100px -40px/-120px -40px/-140px -40px (dark) + */ +} +.mCSB_scrollTools .mCSB_buttonRight{ + background-position:0 -56px; + /* + sprites locations are 0 -56px/-20px -56px/-40px -56px/-60px -56px (light) and -80px -56px/-100px -56px/-120px -56px/-140px -56px (dark) + */ +} +.mCSB_scrollTools .mCSB_buttonUp:hover, +.mCSB_scrollTools .mCSB_buttonDown:hover, +.mCSB_scrollTools .mCSB_buttonLeft:hover, +.mCSB_scrollTools .mCSB_buttonRight:hover{ + opacity:0.75; + filter:"alpha(opacity=75)"; -ms-filter:"alpha(opacity=75)"; /* old ie */ +} +.mCSB_scrollTools .mCSB_buttonUp:active, +.mCSB_scrollTools .mCSB_buttonDown:active, +.mCSB_scrollTools .mCSB_buttonLeft:active, +.mCSB_scrollTools .mCSB_buttonRight:active{ + opacity:0.9; + filter:"alpha(opacity=90)"; -ms-filter:"alpha(opacity=90)"; /* old ie */ +} + +/*scrollbar themes*/ +/*dark (dark colored scrollbar)*/ +.mCS-dark>.mCSB_scrollTools .mCSB_draggerRail{ + background:#000; /* rgba fallback */ + background:rgba(0,0,0,0.15); +} +.mCS-dark>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ + background:#000; /* rgba fallback */ + background:rgba(0,0,0,0.75); +} +.mCS-dark>.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ + background:rgba(0,0,0,0.85); +} +.mCS-dark>.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, +.mCS-dark>.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ + background:rgba(0,0,0,0.9); +} +.mCS-dark>.mCSB_scrollTools .mCSB_buttonUp{ + background-position:-80px 0; +} +.mCS-dark>.mCSB_scrollTools .mCSB_buttonDown{ + background-position:-80px -20px; +} +.mCS-dark>.mCSB_scrollTools .mCSB_buttonLeft{ + background-position:-80px -40px; +} +.mCS-dark>.mCSB_scrollTools .mCSB_buttonRight{ + background-position:-80px -56px; +} +/*light-2*/ +.mCS-light-2>.mCSB_scrollTools .mCSB_draggerRail{ + width:4px; + background:#fff; /* rgba fallback */ + background:rgba(255,255,255,0.1); + -webkit-border-radius:1px; + -moz-border-radius:1px; + border-radius:1px; +} +.mCS-light-2>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ + width:4px; + background:#fff; /* rgba fallback */ + background:rgba(255,255,255,0.75); + -webkit-border-radius:1px; + -moz-border-radius:1px; + border-radius:1px; +} +.mCS-light-2.mCSB_horizontal>.mCSB_scrollTools .mCSB_draggerRail{ + width:100%; + height:4px; + margin:6px 0; +} +.mCS-light-2.mCSB_horizontal>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ + width:100%; + height:4px; + margin:6px auto; +} +.mCS-light-2>.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ + background:rgba(255,255,255,0.85); +} +.mCS-light-2>.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, +.mCS-light-2>.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ + background:rgba(255,255,255,0.9); +} +.mCS-light-2>.mCSB_scrollTools .mCSB_buttonUp{ + background-position:-32px 0; +} +.mCS-light-2>.mCSB_scrollTools .mCSB_buttonDown{ + background-position:-32px -20px; +} +.mCS-light-2>.mCSB_scrollTools .mCSB_buttonLeft{ + background-position:-40px -40px; +} +.mCS-light-2>.mCSB_scrollTools .mCSB_buttonRight{ + background-position:-40px -56px; +} +/*dark-2*/ +.mCS-dark-2>.mCSB_scrollTools .mCSB_draggerRail{ + width:4px; + background:#000; /* rgba fallback */ + background:rgba(0,0,0,0.1); + -webkit-border-radius:1px; + -moz-border-radius:1px; + border-radius:1px; +} +.mCS-dark-2>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ + width:4px; + background:#000; /* rgba fallback */ + background:rgba(0,0,0,0.75); + -webkit-border-radius:1px; + -moz-border-radius:1px; + border-radius:1px; +} +.mCS-dark-2.mCSB_horizontal>.mCSB_scrollTools .mCSB_draggerRail{ + width:100%; + height:4px; + margin:6px 0; +} +.mCS-dark-2.mCSB_horizontal>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ + width:100%; + height:4px; + margin:6px auto; +} +.mCS-dark-2>.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ + background:rgba(0,0,0,0.85); +} +.mCS-dark-2>.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, +.mCS-dark-2>.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ + background:rgba(0,0,0,0.9); +} +.mCS-dark-2>.mCSB_scrollTools .mCSB_buttonUp{ + background-position:-112px 0; +} +.mCS-dark-2>.mCSB_scrollTools .mCSB_buttonDown{ + background-position:-112px -20px; +} +.mCS-dark-2>.mCSB_scrollTools .mCSB_buttonLeft{ + background-position:-120px -40px; +} +.mCS-dark-2>.mCSB_scrollTools .mCSB_buttonRight{ + background-position:-120px -56px; +} +/*light-thick*/ +.mCS-light-thick>.mCSB_scrollTools .mCSB_draggerRail{ + width:4px; + background:#fff; /* rgba fallback */ + background:rgba(255,255,255,0.1); + -webkit-border-radius:2px; + -moz-border-radius:2px; + border-radius:2px; +} +.mCS-light-thick>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ + width:6px; + background:#fff; /* rgba fallback */ + background:rgba(255,255,255,0.75); + -webkit-border-radius:2px; + -moz-border-radius:2px; + border-radius:2px; +} +.mCS-light-thick.mCSB_horizontal>.mCSB_scrollTools .mCSB_draggerRail{ + width:100%; + height:4px; + margin:6px 0; +} +.mCS-light-thick.mCSB_horizontal>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ + width:100%; + height:6px; + margin:5px auto; +} +.mCS-light-thick>.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ + background:rgba(255,255,255,0.85); +} +.mCS-light-thick>.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, +.mCS-light-thick>.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ + background:rgba(255,255,255,0.9); +} +.mCS-light-thick>.mCSB_scrollTools .mCSB_buttonUp{ + background-position:-16px 0; +} +.mCS-light-thick>.mCSB_scrollTools .mCSB_buttonDown{ + background-position:-16px -20px; +} +.mCS-light-thick>.mCSB_scrollTools .mCSB_buttonLeft{ + background-position:-20px -40px; +} +.mCS-light-thick>.mCSB_scrollTools .mCSB_buttonRight{ + background-position:-20px -56px; +} +/*dark-thick*/ +.mCS-dark-thick>.mCSB_scrollTools .mCSB_draggerRail{ + width:4px; + background:#000; /* rgba fallback */ + background:rgba(0,0,0,0.1); + -webkit-border-radius:2px; + -moz-border-radius:2px; + border-radius:2px; +} +.mCS-dark-thick>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ + width:6px; + background:#000; /* rgba fallback */ + background:rgba(0,0,0,0.75); + -webkit-border-radius:2px; + -moz-border-radius:2px; + border-radius:2px; +} +.mCS-dark-thick.mCSB_horizontal>.mCSB_scrollTools .mCSB_draggerRail{ + width:100%; + height:4px; + margin:6px 0; +} +.mCS-dark-thick.mCSB_horizontal>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ + width:100%; + height:6px; + margin:5px auto; +} +.mCS-dark-thick>.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ + background:rgba(0,0,0,0.85); +} +.mCS-dark-thick>.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, +.mCS-dark-thick>.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ + background:rgba(0,0,0,0.9); +} +.mCS-dark-thick>.mCSB_scrollTools .mCSB_buttonUp{ + background-position:-96px 0; +} +.mCS-dark-thick>.mCSB_scrollTools .mCSB_buttonDown{ + background-position:-96px -20px; +} +.mCS-dark-thick>.mCSB_scrollTools .mCSB_buttonLeft{ + background-position:-100px -40px; +} +.mCS-dark-thick>.mCSB_scrollTools .mCSB_buttonRight{ + background-position:-100px -56px; +} +/*light-thin*/ +.mCS-light-thin>.mCSB_scrollTools .mCSB_draggerRail{ + background:#fff; /* rgba fallback */ + background:rgba(255,255,255,0.1); +} +.mCS-light-thin>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ + width:2px; +} +.mCS-light-thin.mCSB_horizontal>.mCSB_scrollTools .mCSB_draggerRail{ + width:100%; +} +.mCS-light-thin.mCSB_horizontal>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ + width:100%; + height:2px; + margin:7px auto; +} +/*dark-thin*/ +.mCS-dark-thin>.mCSB_scrollTools .mCSB_draggerRail{ + background:#000; /* rgba fallback */ + background:rgba(0,0,0,0.15); +} +.mCS-dark-thin>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ + width:2px; + background:#000; /* rgba fallback */ + background:rgba(0,0,0,0.75); +} +.mCS-dark-thin.mCSB_horizontal>.mCSB_scrollTools .mCSB_draggerRail{ + width:100%; +} +.mCS-dark-thin.mCSB_horizontal>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ + width:100%; + height:2px; + margin:7px auto; +} +.mCS-dark-thin>.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ + background:rgba(0,0,0,0.85); +} +.mCS-dark-thin>.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, +.mCS-dark-thin>.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ + background:rgba(0,0,0,0.9); +} +.mCS-dark-thin>.mCSB_scrollTools .mCSB_buttonUp{ + background-position:-80px 0; +} +.mCS-dark-thin>.mCSB_scrollTools .mCSB_buttonDown{ + background-position:-80px -20px; +} +.mCS-dark-thin>.mCSB_scrollTools .mCSB_buttonLeft{ + background-position:-80px -40px; +} +.mCS-dark-thin>.mCSB_scrollTools .mCSB_buttonRight{ + background-position:-80px -56px; +} + +/* custom styling */ +/* content_1 scrollbar */ +.content_1>.mCustomScrollBox>.mCSB_scrollTools{ + height:96%; + top:2%; +} +/* content_2 scrollbar */ +.content_2 .mCSB_scrollTools .mCSB_draggerRail{ + width:6px; + box-shadow:1px 1px 1px rgba(255,255,255,0.1); +} +.content_2 .mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ + background:rgba(255,255,255,0.4); + filter:"alpha(opacity=40)"; -ms-filter:"alpha(opacity=40)"; /* old ie */ +} +.content_2 .mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ + background:rgba(255,255,255,0.5); + filter:"alpha(opacity=50)"; -ms-filter:"alpha(opacity=50)"; /* old ie */ +} +.content_2 .mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, +.content_2 .mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ + background:rgba(255,255,255,0.6); + filter:"alpha(opacity=60)"; -ms-filter:"alpha(opacity=60)"; /* old ie */ +} +/* content_3 scrollbar */ +.content_3>.mCustomScrollBox>.mCSB_scrollTools{ + height:94%; + top:3%; +} +.content_3 .mCSB_scrollTools .mCSB_draggerRail{ + width:0; + border-right:1px dashed #09C; +} +.content_3 .mCSB_scrollTools .mCSB_dragger{ + height:11px; +} +.content_3 .mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ + width:11px; + -webkit-border-radius:11px; + -moz-border-radius:11px; + border-radius:11px; + background:#09C; +} +/* content_4 scrollbar */ +.content_4>.mCustomScrollBox>.mCSB_scrollTools{ + height:94%; + top:3%; +} +.content_4 .mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ + width:8px; + -webkit-border-radius:2px; + -moz-border-radius:2px; + border-radius:2px; + background:#d0b9a0; + -webkit-box-shadow:1px 1px 5px rgba(0,0,0,0.5); + -moz-box-shadow:1px 1px 5px rgba(0,0,0,0.5); + box-shadow:1px 1px 5px rgba(0,0,0,0.5); +} +.content_4 .mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar, +.content_4 .mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ + background:#dfcdb9; +} +.content_4 .mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, +.content_4 .mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ + -webkit-box-shadow:0 0 3px rgba(0,0,0,0.5); + -moz-box-shadow:0 0 3px rgba(0,0,0,0.5); + box-shadow:0 0 3px rgba(0,0,0,0.5); +} +/* content_5 scrollbar */ +.content_5>.mCustomScrollBox>.mCSB_scrollTools{ + width: 98%; + margin: 0 1%; +} +/* content_6 scrollbar */ +.content_6>.mCustomScrollBox>.mCSB_scrollTools{ + width:88%; + margin: 0 6%; +} +/* content_8 scrollbar */ +.content_8 .mCSB_scrollTools .mCSB_draggerRail{ + width:0px; + border-left:1px solid rgba(0,0,0,0.8); + border-right:1px solid rgba(255,255,255,0.2); +} \ No newline at end of file diff --git a/resource/css/styles.css b/resource/css/styles.css new file mode 100644 index 000000000..056182b8c --- /dev/null +++ b/resource/css/styles.css @@ -0,0 +1,1130 @@ +@import 'fira.css'; +@import '../../vendor/components/jqueryui/themes/cupertino/jquery-ui.min.css'; + +html, body { + margin: 0; + padding: 0; + height: 100%; + min-width: 100%; + background-color: #d5dbde !important; +} + +ul { + list-style: none; +} + +@media print { /* Quick style sheet for printing. */ + body * { + visibility: hidden; + } + + /* Everything under the print class elements will be printed. */ + .print,.print * { + visibility: visible; + } + + /* Position elements at the beginning of the page. */ + .print { + position: absolute; + left: 0; + top: 0; + } +} + +section { + position: relative; + width: 95%; + margin: 0 auto; + margin-bottom: 20px; +} + +.spinner { + display:inline-block; + width: 25px; + height: 25px; + margin-left: 5px; + vertical-align: middle; + background:url("../pics/spinner.gif") no-repeat transparent; +} + +/* topbar styles + ***************************/ + +.topbar { + height: 50px; + margin-bottom: 20px; +} + +.topbar > a { + position: absolute; +} + +.topbar-white { + background-color: #ffffff; +} + +#service-name { + left: 30px; + margin: 0; + position: absolute; + line-height: 50px; +} + +.topbar-white > #language, .topbar-white > #navigation { + background: #ffffff; +} + +#language { + position: absolute; + right: 0; + height: 50px; + width: auto; + padding-left: 0px; + text-align: right; + overflow: hidden; + line-height: 70px; +} + +#navigation { + right: 180px; + padding-right: 10px; + position: absolute; + height: 50px; + min-width: 0px; + max-width: 530px; + width: auto; + overflow: hidden; + float: right; + line-height: 70px; +} + +#navigation *, #language * { + margin: auto 4px; +} + +#navigation .link-bg { + height: 60px; + float: left; +} + +/* headerbar stuff + *************************/ + +#lang-dropdown-toggle .caret, .multiselect span > .caret { + border-top-color: #ffffff !important; +} + +.multiselect-container > li.active > a { + color: #ffffff !important; +} + +.headerbar { + position: relative; + width: 95%; + height: 50px; + margin: 0 auto; + margin-bottom: 20px; +} + +.headerbar-coloured { + background-color: #b2bac0; +} + +.header-left { + float: left; +} + +.header-left > h1 { + position: absolute; + line-height: 50px; + margin: 0 0 0 15px; +} + +.header-left > h1 > a { + display: block; + color: #ffffff !important; + height: 50px; + overflow: hidden; +} + +.header-float { + float: right; +} + +.header-float > .btn-group { + vertical-align: baseline; + float: left; +} + +.headerbar-coloured > .header-float > p { + color: #ffffff !important; +} + +.topbar a.navigation-font:hover { + text-decoration: underline; +} + +.navbar-form { + display: inline-block; + padding: 0 !important; + margin: 5px 0 !important; +} + +.search-result-listing { + background-color: #ffffff; +} + +.search-result-listing > .search-result:last-of-type { + margin-bottom: 10px; +} + +#search-from-all-vocabularies-radio-buttons { + display: none; +} + +#search-all-button { + background-color: #394554; + border: medium none !important; + border-radius: 0 !important; + height: 40px; +} + +.search-vocab-text { + display: inline-block !important; + float: left; + line-height: 52px; + margin-right: 4px; +} + +.search-count { + text-align: right; +} + +.search-result-listing .no-results { + margin: 15px; +} + +.search-result-listing .search-count { + text-align: left; + margin-left: 15px; +} + +.search-result-listing .prefLabel { + font-size: 14px !important; + font-weight: bold !important; +} + +.search-result-listing .search-result { + margin: 20px 15px ; +} + +.search-result-listing { + padding: 5px 0; +} + +/* +.search-result-listing .search-result:hover, .search-result a:hover{ + background-color: #F8F8F8; + text-decoration: none; +} + +.search-result-listing .search-result:hover { + border: 1px solid #EDEDED; +} +*/ + +.search-result > div > span.uri-input-box { + font-size: 12px; + color: #006621; +} + +.search-result { + border: 1px solid transparent; +} + +#lang-dropdown-toggle, .multiselect { + background-color: #394554 !important; + border: medium none; + border-radius: 0 !important; + box-shadow: none !important; + color: #FFFFFF !important; + height: 40px; + padding-left: 6px !important; + padding-right: 6px !important; +} + +.multiselect { + border: 0 !important; + margin: 5px 0 !important; +} + +.multiselect span { + float: right; +} + +#selected-vocabs { + display: none; +} + +.navbar-left > .btn-group { + float: left; +} + +#search-field { + background-color: #FFFFFF; + border: medium none; + border-radius: 0; + display: block; + font-family: Tahoma,Geneva,sans-serif; + font-size: 14px; + font-weight: bold; + height: 40px; + padding-left: 8px !important; +} + +.input-group { + display: table; +} + +.form-control { + display: table-cell; +} + +.input-group-btn { + width: 50px !important; +} + +/* front page stuff + ***************************/ +.welcome-box, .right-box, #vocabulary-list { + vertical-align: top; +} + +.welcome-box { + background-color: #ffffff; + padding: 15px; + display: inline-block; +} + +.welcome-box > h1, .right-box > h1 { + margin-top: 0; +} + +.right-box { + background-color: #ffffff; + width: 240px !important; + padding: 15px; + position: absolute; + right: 0; + top: 0; +} + +.right-box { + display: none; +} +.vocabulary-separator { + border-bottom: 2px solid #666666 !important; +} + +.book-logo { + display: none; + float: left; +} + +#vocabulary-list { + background-color: #ffffff !important; + display: inline-block; +} + +#vocabulary-list > div > h2:first-of-type { + padding-top: 15px; +} + +#vocabulary-list > div > h2 { + margin-top: 0 !important; + margin-bottom: 0px; + padding-left: 10px; +} + +.vocabularies { + margin-top: 15px; + padding: 0 10px; +} + +.vocabularies > table > tbody > tr > td, .vocabularies > table > tbody > tr > td > a { + padding-left: 0; + margin-left: 0; +} + +.vocabularies > table > tbody > tr > td > div { + width: 125px; +} + +.vocabularies > table > tbody > tr:last-child > td > div { + display: none !important; +} + +/* feedback page + ***************************/ +.feedback-logo { + position: absolute; + min-width: 84px; + display: block; + left: 200px; + border-bottom: 2px solid #394554; + padding-top: 80px; +} + +.feedback-box { + border-top: 2px solid #394554; +} + +.feedback-box * { + border-radius: 0 !important; + margin-bottom: 0; + box-shadow: none !important; +} + +.feedback-box > div > span { + display: block; + margin: 15px 0; +} + +.feedback-box input, .feedback-box select, .feedback-box textarea { + border: none; + color: #00748f !important; + margin-bottom: 10px; + background-color: #f6f7f8; + resize: none; +} +::-webkit-input-placeholder { /* WebKit browsers */ + color: #00748f !important; +} +:-moz-placeholder { /* Mozilla Firefox 4 to 18 */ + color: #00748f !important; +} +::-moz-placeholder { /* Mozilla Firefox 19+ */ + color: #00748f !important; +} +:-ms-input-placeholder { /* Internet Explorer 10+ */ + color: #00748f !important; +} + +#send-feedback { + background-color: #394554; + border: none; + float:right; + margin: 5px 0 15px 0; +} + +#send-feedback:hover { + background-color: #0098ff !important; +} + +/* about page + ***************************/ +#about { + width: 500px; + background-color: #ffffff; + padding-bottom: 15px; + margin-bottom: 15px; + border-top: 2px solid #394554; +} + +#about * { + margin: 15px; +} + +.special-container { + position: absolute; + left: 0px; + margin-left: 200px; + +} + +.credits { + display: none; + width: 500px; + background-color: #83CFC8 !important; + border-top: 2px solid #394554; +} + +/* sidebar styles + ***************************/ + +#sidebar { + position: absolute; + width: 320px; + height: 100%; +} + +.sidebar-grey { + background-color: #f6f7f8; + height: 100%; + height: calc(100% - 42px); + overflow-y: auto; +} + +.sidebar-grey ul, .sidebar-grey p { + padding-left: 0; +} + +#sidebar hr { + border: 1px solid #666666 !important; + margin-left: 15px; + margin-top: 15px; + width: 125px; +} + +.fixed { + position: fixed !important; + height: 95%; + height: calc(100% - 2px) !important; + top: 2px; +} + +hr { + border: 1px solid transparent !important; + background: url('../pics/stripe.png') !important; + margin-top: 5px !important; + margin-bottom: 15px !important; +} + +.alphabetical-search-results li, .group-container > p, .group-container > ul { + font-size: 14px; + line-height: 15px; + list-style-type: none; +} + +#alphabetical-menu { + margin: 0 0 0 15px; +} + +.activated-concept, .jstree-clicked { + color: #474b4f !important; + font-weight: 600 !important; + background-color: transparent !important; + border: 0 !important; +} + +.group-container { + margin: 10px 0 5px 15px; +} + +/* scrollbars + ****************************/ +.mCSB_container { + margin-right: 15px !important; +} + +.mCSB_scrollTools { + opacity: 1 !important; + width: 14px !important; +} + +.mCSB_draggerRail { + background-color: #b9c1c6 !important; + border-radius: 0 !important; + width: 14px !important; +} + +.mCSB_dragger { + background-color: #474b4f !important; + border-radius: 0 !important; + width: 14px !important; +} + +.mCSB_dragger_bar { + display: none !important; +} + +/* tables + ****************************/ + +th, td { + border-top: none !important; +} + +tr { + border-image: '../pics/stripe.png' 20 20 repeat; +} + +/* vocabulary + ****************************/ +.container { + background-color: #ffffff; +} + +/* alerts + ****************************/ +.alert { + background-color: #ed0d6c !important; + border-radius: 0 !important; + border: none !important; + color: white !important; +} + +#lang-info * { + color: #ffffff; +} + +#lang-info > h1 { + float: left; + font-size: 48px !important; + margin: -12px 15px 0 0; +} + +.errors { + color: red; +} + +/* concept + ****************************/ +.concept-info { + background-color: #ffffff; + margin-bottom: 20px; +} + +.concept-info li, .concept-info ul { + list-style: none; + padding-left: 0; + margin-bottom: 0; +} + +.concept-info div > table > tbody > tr > td:first-child { + max-width: 225px; + width: 225px; +} + +.concept-info p { + margin: 0 !important; +} + +.concept-appendix { + background-color: #edf0f2 !important; + padding: 15px; +} + +.concept-main { + margin: 0 15px 15px 15px; + padding-top: 15px; +} + +.concept-main > table, .concept-appendix > table { + margin-bottom: 0 !important; +} + +.hidden-breadcrumb { + display: none; +} + +.field-label { + vertical-align: top !important; +} + +.foundby { + margin-left: 225px; +} + +.preflabel-container, .preflabel-container hr { + margin: 10px 0 5px 0 !important; +} + +.preflabel-description { + display: inline-block; + width: 225px; + height: 100%; +} + +.shortened-property { + max-height: 24px; + overflow: hidden; + position: relative; + white-space: nowrap; +} + +.shortened-symbol { + display: inline-block; + height: 22px; + padding: 1px 0 0 2px; + right: 0; + position: absolute; + background-color: #ffffff; +} + +tr .property-hover { + margin-left: 5px; + margin-bottom: 2px; +} + +.subvalue { + margin-left: 15px; +} + +.property-divider { + position: absolute; + width: 14%; + border-bottom: 2px solid #666666; + margin: -20px 0 0 0 !important; +} + +.property-divider hr { + border-bottom: 2px solid #3bc2bd !important; + width: 125px; + position: absolute; +} + +.table th { + padding-left: 0 !important; +} + +.search-results-property-table { + padding: 0 15px !important; +} + +.search-results-property-table td:last-child { + border-bottom: 2px solid #d4edeb; +} + +.search-results-property-table > tbody > tr:last-child > td { + border-bottom: 0 !important; +} + +.search-results-property-table td { + padding: 5px 0 0 0 !important; +} + +#statistics { max-width: 450px; } + +#statistics .property-divider { + margin: 0 !important; +} + +#statistics td, #statistics th { + padding: 0 !important; +} + +.pagination > li > a, .pagination > li > span { + border: 0 !important; + background-color: transparent !important; + padding: 4px 8px !important; +} + +.pagination > li:last-child > a, .pagination > li:last-child > span { + border-radius: 0 !important; +} + +.pagination > li:first-child > a { + border-radius: 0 !important; + margin-left: -1px !important; +} + +.pagination { margin: 10px 5px 0 10px !important;} + +.nav-tabs, #groups > a { + background-color: #e0e4e7; + border-bottom: none !important; +} + +ul.nav-tabs > li { + margin-bottom: 0; + outline: none; +} + +.nav-tabs > li > a { + background-color: #e6e9eb; + border-radius: 0 !important; + border: 0 !important; + text-align: center; + margin-right: 0 !important; + outline: none; +} + +.nav-tabs > li { + width: 33%; +} + +#alpha { + width: 34% !important; +} + + +.nav-tabs > li > a:hover { + background-color: #ffffff !important; +} + +.nav-tabs > li.active > a { + background-color: #f6f7f8 !important; +} + +.content { + margin-left: 350px; +} + +/* Autocomplete stuff + *****************************************/ +textarea:focus, input[type="text"]:focus { + box-shadow: 0 0 0; + outline: 0 none; +} + +.ui-autocomplete { + max-height:300px; + overflow-y: auto; + background-image: none !important; + border-radius: 0px !important; +} + +.ui-state-focus { + border-radius: 0 !important; + background-image: none !important; + background-color: #ffffff !important; +} + +.autocomplete-vocab { + float: right; +} + +/* jsTree customization + *****************************************/ +.jstree-no-icons { + margin-left: -5px !important; +} + +.jstree li { + margin-left: 10px !important; +} + +/* Font definitions + *****************************************/ +li { + font-family: 'Fira Sans' !important; + font-size: 14px !important; +} + +h1, .prefLabel { + font-family: 'Fira Sans' !important; + font-size: 27px !important; + font-weight: 400 !important; +} + +h1#service-name { + font-size: 27px !important; + font-family: 'Fira Sans' !important; + font-weight: bold !important; +} + +a { + color: #00748f !important; +} + +h2 { + font-family: 'Fira Sans' !important; + font-style: bold !important; + font-size: 16px !important; +} + +a.navigation-font { + font-family: 'Fira Sans'; + font-size: 16px; + color: black; +} + +.topbar a.navigation-font { + color: #394554 !important; +} + +p { + font-family: 'Fira Sans'; + font-size: 14px; + color: #474b4f; +} + +.bread-crumb { + font-family: 'Fira Sans'; + font-size: 14px; +} + +.versal { + font-family: 'Fira Sans'; + font-size: 14px; + font-weight: 400 !important; + color: #474b4f; +} + +.versal-bold { + font-family: 'Fira Sans'; + font-size: 14px; + font-weight: bold; +} + +.replaced { + // font-style: italic; + text-decoration: line-through; +} + +a:link,a:visited,a:active,a:hover,a:focus { + /* Improves link handling by removing focus borders. Sorry keyboard users! */ + text-decoration: none; + border: 0; + outline: none; +} + +a:hover { + text-decoration: underline; + /* All text links have underlining when hovered. */ +} + +#service-logo { + display: inline-block; + max-width: 450px; +} + +/* Fix for a nasty chromium bug causing the fonts not to render before a redraw. + * https://code.google.com/p/chromium/issues/detail?id=336476 + ************************************/ + +body { + -webkit-animation-delay: 0.1s; + -webkit-animation-name: fontfix; + -webkit-animation-duration: 0.1s; + -webkit-animation-iteration-count: 1; + -webkit-animation-timing-function: linear; +} + +@-webkit-keyframes fontfix { + from { opacity: 1; } + to { opacity: 1; } +} + +/* mediaquery + ***********************************/ +@media (max-width: 400px) { + #navi1 { + display: none !important; + } +} + +@media (max-width: 500px) { + #navi4 { + display: none !important; + } +} + +@media (max-width: 550px) { + #navi3 { + display: none !important; + } +} + +@media (max-width: 640px) { + + .content { + overflow: hidden; + } + + .search-vocab-text{ + display: none !important; + } +} + +@media (max-width: 800px) { + section, .headerbar { + width: 95% !important; + } + + .navbar-form { + max-width: 300px !important; + } + + #service-logo { + display: none !important; + } + + .feedback-logo, .about-logo { + left: 10px !important; + } + + .feedback-box, #about { + margin-left: 120px !important; + width: 500px !important; + } + + p.search-vocab-text { + text-indent: 100%; + white-space: nowrap; + overflow: hidden; + } + + .ui-autocomplete { + max-width: 240px !important; + } + + .content { + margin-left: 300px; + } + + #sidebar { + width: 270px !important; + } + + .property-divider { + width: 17%; + } + + h1 { + font-size: 22px !important; + } + + #sidebar { + display: none; + } + + .right-box { + display: none; + } + + .content { + margin: 0 auto; + } + + .welcome-box { + display: none; + } + +} + +@media (min-width: 800px) { + section, .headerbar { + } + + .ui-autocomplete { + max-width: 250px !important; + } + + .nav > li > a { + padding: 10px 10px !important; + } + + .input-group { + max-width: 300px !important; + } + + #navigation > a { + margin: auto 10px; + } + + #service-logo { + position: absolute; + display: normal !important; + } + + .feedback-box, #about { + width: 500px !important; + } + + .content { + margin-left: 330px; + } + + #sidebar { + width: 310px !important; + } + + .property-divider { + width: 17%; + } + + .welcome-box { + margin-left: 150px !important; + width: 200px !important; + } + + #vocabulary-list { + position: absolute; + left: 375px; + width: auto !important; + } + + h1 { + font-size: 24px !important; + } + +} + +@media (min-width: 1260px) { + section, .headerbar { + width: 1240px !important; + } + + .right-box { + display: inline-block; + } + + .feedback-box { + width: 600px !important; + } + + .ui-autocomplete { + max-width: 310px !important; + } + + .content { + margin-left: 430px; + } + + input#search-field { + width: 260px !important; + } + + #vocabulary-list { + position: relative; + left: 20px; + display: inline-block; + width: 605px !important; + } + + #sidebar { + width: 400px !important; + } + + h1 { + font-size: 27px !important; + } + + li, .versal, .versal-bold, p, .search-result > .prefLabel{ + font-size: 16px !important; + } + + .search-result > div > span.uri-input-box { + font-size: 14px !important; + } + +} + +@media (min-width: 1520px) { + + #vocabulary-list { + position: relative; + display: inline-block; + width: 710px !important; + } + + .welcome-box { + margin-left: 0 !important; + width: 240px !important; + } + + #sidebar { + width: 400px !important; + } +} + + + + + diff --git a/resource/fonts/eot/FiraMono-Bold.eot b/resource/fonts/eot/FiraMono-Bold.eot new file mode 100755 index 000000000..f4a15ff39 Binary files /dev/null and b/resource/fonts/eot/FiraMono-Bold.eot differ diff --git a/resource/fonts/eot/FiraMono-Regular.eot b/resource/fonts/eot/FiraMono-Regular.eot new file mode 100755 index 000000000..7a0b52f04 Binary files /dev/null and b/resource/fonts/eot/FiraMono-Regular.eot differ diff --git a/resource/fonts/eot/FiraSans-Bold.eot b/resource/fonts/eot/FiraSans-Bold.eot new file mode 100755 index 000000000..58ca9daf5 Binary files /dev/null and b/resource/fonts/eot/FiraSans-Bold.eot differ diff --git a/resource/fonts/eot/FiraSans-BoldItalic.eot b/resource/fonts/eot/FiraSans-BoldItalic.eot new file mode 100755 index 000000000..62856adcf Binary files /dev/null and b/resource/fonts/eot/FiraSans-BoldItalic.eot differ diff --git a/resource/fonts/eot/FiraSans-Light.eot b/resource/fonts/eot/FiraSans-Light.eot new file mode 100755 index 000000000..154271d5c Binary files /dev/null and b/resource/fonts/eot/FiraSans-Light.eot differ diff --git a/resource/fonts/eot/FiraSans-LightItalic.eot b/resource/fonts/eot/FiraSans-LightItalic.eot new file mode 100755 index 000000000..67d94787d Binary files /dev/null and b/resource/fonts/eot/FiraSans-LightItalic.eot differ diff --git a/resource/fonts/eot/FiraSans-Medium.eot b/resource/fonts/eot/FiraSans-Medium.eot new file mode 100755 index 000000000..2ae0a766a Binary files /dev/null and b/resource/fonts/eot/FiraSans-Medium.eot differ diff --git a/resource/fonts/eot/FiraSans-MediumItalic.eot b/resource/fonts/eot/FiraSans-MediumItalic.eot new file mode 100755 index 000000000..a5ceae851 Binary files /dev/null and b/resource/fonts/eot/FiraSans-MediumItalic.eot differ diff --git a/resource/fonts/eot/FiraSans-Regular.eot b/resource/fonts/eot/FiraSans-Regular.eot new file mode 100755 index 000000000..a52c3ed49 Binary files /dev/null and b/resource/fonts/eot/FiraSans-Regular.eot differ diff --git a/resource/fonts/eot/FiraSans-RegularItalic.eot b/resource/fonts/eot/FiraSans-RegularItalic.eot new file mode 100755 index 000000000..6a0530364 Binary files /dev/null and b/resource/fonts/eot/FiraSans-RegularItalic.eot differ diff --git a/resource/fonts/eot/FiraSansSC-Bold.eot b/resource/fonts/eot/FiraSansSC-Bold.eot new file mode 100755 index 000000000..9c355e431 Binary files /dev/null and b/resource/fonts/eot/FiraSansSC-Bold.eot differ diff --git a/resource/fonts/eot/FiraSansSC-BoldItalic.eot b/resource/fonts/eot/FiraSansSC-BoldItalic.eot new file mode 100755 index 000000000..1f6af1c8d Binary files /dev/null and b/resource/fonts/eot/FiraSansSC-BoldItalic.eot differ diff --git a/resource/fonts/eot/FiraSansSC-Light.eot b/resource/fonts/eot/FiraSansSC-Light.eot new file mode 100755 index 000000000..9a757bba5 Binary files /dev/null and b/resource/fonts/eot/FiraSansSC-Light.eot differ diff --git a/resource/fonts/eot/FiraSansSC-LightItalic.eot b/resource/fonts/eot/FiraSansSC-LightItalic.eot new file mode 100755 index 000000000..55bca0ffc Binary files /dev/null and b/resource/fonts/eot/FiraSansSC-LightItalic.eot differ diff --git a/resource/fonts/eot/FiraSansSC-Medium.eot b/resource/fonts/eot/FiraSansSC-Medium.eot new file mode 100755 index 000000000..a11e5aa33 Binary files /dev/null and b/resource/fonts/eot/FiraSansSC-Medium.eot differ diff --git a/resource/fonts/eot/FiraSansSC-MediumItalic.eot b/resource/fonts/eot/FiraSansSC-MediumItalic.eot new file mode 100755 index 000000000..f9a802170 Binary files /dev/null and b/resource/fonts/eot/FiraSansSC-MediumItalic.eot differ diff --git a/resource/fonts/eot/FiraSansSC-Regular.eot b/resource/fonts/eot/FiraSansSC-Regular.eot new file mode 100755 index 000000000..c2fccac89 Binary files /dev/null and b/resource/fonts/eot/FiraSansSC-Regular.eot differ diff --git a/resource/fonts/eot/FiraSansSC-RegularItalic.eot b/resource/fonts/eot/FiraSansSC-RegularItalic.eot new file mode 100755 index 000000000..a36f247b2 Binary files /dev/null and b/resource/fonts/eot/FiraSansSC-RegularItalic.eot differ diff --git a/resource/fonts/ttf/FiraMono-Bold.ttf b/resource/fonts/ttf/FiraMono-Bold.ttf new file mode 100755 index 000000000..4b8b1cfbc Binary files /dev/null and b/resource/fonts/ttf/FiraMono-Bold.ttf differ diff --git a/resource/fonts/ttf/FiraMono-Regular.ttf b/resource/fonts/ttf/FiraMono-Regular.ttf new file mode 100755 index 000000000..5238c09ed Binary files /dev/null and b/resource/fonts/ttf/FiraMono-Regular.ttf differ diff --git a/resource/fonts/ttf/FiraSans-Bold.ttf b/resource/fonts/ttf/FiraSans-Bold.ttf new file mode 100755 index 000000000..c45687f80 Binary files /dev/null and b/resource/fonts/ttf/FiraSans-Bold.ttf differ diff --git a/resource/fonts/ttf/FiraSans-BoldItalic.ttf b/resource/fonts/ttf/FiraSans-BoldItalic.ttf new file mode 100755 index 000000000..43f8bade3 Binary files /dev/null and b/resource/fonts/ttf/FiraSans-BoldItalic.ttf differ diff --git a/resource/fonts/ttf/FiraSans-Light.ttf b/resource/fonts/ttf/FiraSans-Light.ttf new file mode 100755 index 000000000..9dc6a161e Binary files /dev/null and b/resource/fonts/ttf/FiraSans-Light.ttf differ diff --git a/resource/fonts/ttf/FiraSans-LightItalic.ttf b/resource/fonts/ttf/FiraSans-LightItalic.ttf new file mode 100755 index 000000000..5d2ee19f1 Binary files /dev/null and b/resource/fonts/ttf/FiraSans-LightItalic.ttf differ diff --git a/resource/fonts/ttf/FiraSans-Medium.ttf b/resource/fonts/ttf/FiraSans-Medium.ttf new file mode 100755 index 000000000..f7592fbc1 Binary files /dev/null and b/resource/fonts/ttf/FiraSans-Medium.ttf differ diff --git a/resource/fonts/ttf/FiraSans-MediumItalic.ttf b/resource/fonts/ttf/FiraSans-MediumItalic.ttf new file mode 100755 index 000000000..9ea5a3b2b Binary files /dev/null and b/resource/fonts/ttf/FiraSans-MediumItalic.ttf differ diff --git a/resource/fonts/ttf/FiraSans-Regular.ttf b/resource/fonts/ttf/FiraSans-Regular.ttf new file mode 100755 index 000000000..93561ae5a Binary files /dev/null and b/resource/fonts/ttf/FiraSans-Regular.ttf differ diff --git a/resource/fonts/ttf/FiraSans-RegularItalic.ttf b/resource/fonts/ttf/FiraSans-RegularItalic.ttf new file mode 100755 index 000000000..18aa44eda Binary files /dev/null and b/resource/fonts/ttf/FiraSans-RegularItalic.ttf differ diff --git a/resource/fonts/woff/FiraMono-Bold.woff b/resource/fonts/woff/FiraMono-Bold.woff new file mode 100755 index 000000000..e81f4ae5b Binary files /dev/null and b/resource/fonts/woff/FiraMono-Bold.woff differ diff --git a/resource/fonts/woff/FiraMono-Regular.woff b/resource/fonts/woff/FiraMono-Regular.woff new file mode 100755 index 000000000..0a04a4f1a Binary files /dev/null and b/resource/fonts/woff/FiraMono-Regular.woff differ diff --git a/resource/fonts/woff/FiraSans-Bold.woff b/resource/fonts/woff/FiraSans-Bold.woff new file mode 100755 index 000000000..415071c2b Binary files /dev/null and b/resource/fonts/woff/FiraSans-Bold.woff differ diff --git a/resource/fonts/woff/FiraSans-BoldItalic.woff b/resource/fonts/woff/FiraSans-BoldItalic.woff new file mode 100755 index 000000000..a4129c610 Binary files /dev/null and b/resource/fonts/woff/FiraSans-BoldItalic.woff differ diff --git a/resource/fonts/woff/FiraSans-Light.woff b/resource/fonts/woff/FiraSans-Light.woff new file mode 100755 index 000000000..f05e73a75 Binary files /dev/null and b/resource/fonts/woff/FiraSans-Light.woff differ diff --git a/resource/fonts/woff/FiraSans-LightItalic.woff b/resource/fonts/woff/FiraSans-LightItalic.woff new file mode 100755 index 000000000..43a73e887 Binary files /dev/null and b/resource/fonts/woff/FiraSans-LightItalic.woff differ diff --git a/resource/fonts/woff/FiraSans-Medium.woff b/resource/fonts/woff/FiraSans-Medium.woff new file mode 100755 index 000000000..562722774 Binary files /dev/null and b/resource/fonts/woff/FiraSans-Medium.woff differ diff --git a/resource/fonts/woff/FiraSans-MediumItalic.woff b/resource/fonts/woff/FiraSans-MediumItalic.woff new file mode 100755 index 000000000..f3814873b Binary files /dev/null and b/resource/fonts/woff/FiraSans-MediumItalic.woff differ diff --git a/resource/fonts/woff/FiraSans-Regular.woff b/resource/fonts/woff/FiraSans-Regular.woff new file mode 100755 index 000000000..9ff40445b Binary files /dev/null and b/resource/fonts/woff/FiraSans-Regular.woff differ diff --git a/resource/fonts/woff/FiraSans-RegularItalic.woff b/resource/fonts/woff/FiraSans-RegularItalic.woff new file mode 100755 index 000000000..074c220ed Binary files /dev/null and b/resource/fonts/woff/FiraSans-RegularItalic.woff differ diff --git a/resource/fonts/woff/FiraSansOT-Light.woff b/resource/fonts/woff/FiraSansOT-Light.woff new file mode 100644 index 000000000..9dd0f4747 Binary files /dev/null and b/resource/fonts/woff/FiraSansOT-Light.woff differ diff --git a/resource/fonts/woff/FiraSansSC-Bold.woff b/resource/fonts/woff/FiraSansSC-Bold.woff new file mode 100755 index 000000000..ade6ab1bc Binary files /dev/null and b/resource/fonts/woff/FiraSansSC-Bold.woff differ diff --git a/resource/fonts/woff/FiraSansSC-BoldItalic.woff b/resource/fonts/woff/FiraSansSC-BoldItalic.woff new file mode 100755 index 000000000..499385397 Binary files /dev/null and b/resource/fonts/woff/FiraSansSC-BoldItalic.woff differ diff --git a/resource/fonts/woff/FiraSansSC-Light.woff b/resource/fonts/woff/FiraSansSC-Light.woff new file mode 100755 index 000000000..d76b41dee Binary files /dev/null and b/resource/fonts/woff/FiraSansSC-Light.woff differ diff --git a/resource/fonts/woff/FiraSansSC-LightItalic.woff b/resource/fonts/woff/FiraSansSC-LightItalic.woff new file mode 100755 index 000000000..1d9ed2993 Binary files /dev/null and b/resource/fonts/woff/FiraSansSC-LightItalic.woff differ diff --git a/resource/fonts/woff/FiraSansSC-Medium.woff b/resource/fonts/woff/FiraSansSC-Medium.woff new file mode 100755 index 000000000..ac1298a0a Binary files /dev/null and b/resource/fonts/woff/FiraSansSC-Medium.woff differ diff --git a/resource/fonts/woff/FiraSansSC-MediumItalic.woff b/resource/fonts/woff/FiraSansSC-MediumItalic.woff new file mode 100755 index 000000000..b358a1b76 Binary files /dev/null and b/resource/fonts/woff/FiraSansSC-MediumItalic.woff differ diff --git a/resource/fonts/woff/FiraSansSC-Regular.woff b/resource/fonts/woff/FiraSansSC-Regular.woff new file mode 100755 index 000000000..13384b028 Binary files /dev/null and b/resource/fonts/woff/FiraSansSC-Regular.woff differ diff --git a/resource/fonts/woff/FiraSansSC-RegularItalic.woff b/resource/fonts/woff/FiraSansSC-RegularItalic.woff new file mode 100755 index 000000000..17eab7e02 Binary files /dev/null and b/resource/fonts/woff/FiraSansSC-RegularItalic.woff differ diff --git a/resource/js/bootstrap-multiselect.js b/resource/js/bootstrap-multiselect.js new file mode 100755 index 000000000..852042416 --- /dev/null +++ b/resource/js/bootstrap-multiselect.js @@ -0,0 +1,994 @@ +/** + * bootstrap-multiselect.js + * https://github.com/davidstutz/bootstrap-multiselect + * + * Copyright 2012 - 2014 David Stutz + * + * Dual licensed under the BSD-3-Clause and the Apache License, Version 2.0. + */ +!function($) { + + "use strict";// jshint ;_; + + if (typeof ko !== 'undefined' && ko.bindingHandlers && !ko.bindingHandlers.multiselect) { + ko.bindingHandlers.multiselect = { + + init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { + + var listOfSelectedItems = allBindingsAccessor().selectedOptions, + config = ko.utils.unwrapObservable(valueAccessor()); + + $(element).multiselect(config); + + if (isObservableArray(listOfSelectedItems)) { + // Subscribe to the selectedOptions: ko.observableArray + listOfSelectedItems.subscribe(function (changes) { + var addedArray = [], deletedArray = []; + changes.forEach(function (change) { + switch (change.status) { + case 'added': + addedArray.push(change.value); + break; + case 'deleted': + deletedArray.push(change.value); + break; + } + }); + if (addedArray.length > 0) { + $(element).multiselect('select', addedArray); + }; + if (deletedArray.length > 0) { + $(element).multiselect('deselect', deletedArray); + }; + }, null, "arrayChange"); + } + }, + + update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { + + var listOfItems = allBindingsAccessor().options, + ms = $(element).data('multiselect'), + config = ko.utils.unwrapObservable(valueAccessor()); + + if (isObservableArray(listOfItems)) { + // Subscribe to the options: ko.observableArray incase it changes later + listOfItems.subscribe(function (theArray) { + $(element).multiselect('rebuild'); + }); + } + + if (!ms) { + $(element).multiselect(config); + } + else { + ms.updateOriginalOptions(); + } + } + }; + } + + function isObservableArray(obj) { + return ko.isObservable(obj) && !(obj.destroyAll === undefined); + } + + /** + * Constructor to create a new multiselect using the given select. + * + * @param {jQuery} select + * @param {Object} options + * @returns {Multiselect} + */ + function Multiselect(select, options) { + + this.options = this.mergeOptions(options); + this.$select = $(select); + + // Initialization. + // We have to clone to create a new reference. + this.originalOptions = this.$select.clone()[0].options; + this.query = ''; + this.searchTimeout = null; + + this.options.multiple = this.$select.attr('multiple') === "multiple"; + this.options.onChange = $.proxy(this.options.onChange, this); + this.options.onDropdownShow = $.proxy(this.options.onDropdownShow, this); + this.options.onDropdownHide = $.proxy(this.options.onDropdownHide, this); + + // Build select all if enabled. + this.buildContainer(); + this.buildButton(); + this.buildSelectAll(); + this.buildDropdown(); + this.buildDropdownOptions(); + this.buildFilter(); + + this.updateButtonText(); + this.updateSelectAll(); + + this.$select.hide().after(this.$container); + }; + + Multiselect.prototype = { + + defaults: { + /** + * Default text function will either print 'None selected' in case no + * option is selected or a list of the selected options up to a length of 3 selected options. + * + * @param {jQuery} options + * @param {jQuery} select + * @returns {String} + */ + buttonText: function(options, select) { + if (options.length === 0) { + return this.nonSelectedText + ' '; + } + else { + if (options.length > this.numberDisplayed) { + return options.length + ' ' + this.nSelectedText + ' '; + } + else { + var selected = ''; + options.each(function() { + var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).html(); + + selected += label + ', '; + }); + return selected.substr(0, selected.length - 2) + ' '; + } + } + }, + /** + * Updates the title of the button similar to the buttonText function. + * @param {jQuery} options + * @param {jQuery} select + * @returns {@exp;selected@call;substr} + */ + buttonTitle: function(options, select) { + if (options.length === 0) { + return this.nonSelectedText; + } + else { + var selected = ''; + options.each(function () { + selected += $(this).text() + ', '; + }); + return selected.substr(0, selected.length - 2); + } + }, + /** + * Create a label. + * + * @param {jQuery} element + * @returns {String} + */ + label: function(element){ + return $(element).attr('label') || $(element).html(); + }, + /** + * Triggered on change of the multiselect. + * Not triggered when selecting/deselecting options manually. + * + * @param {jQuery} option + * @param {Boolean} checked + */ + onChange : function(option, checked) { + + }, + /** + * Triggered when the dropdown is shown. + * + * @param {jQuery} event + */ + onDropdownShow: function(event) { + + }, + /** + * Triggered when the dropdown is hidden. + * + * @param {jQuery} event + */ + onDropdownHide: function(event) { + + }, + buttonClass: 'btn btn-default', + dropRight: false, + selectedClass: 'active', + buttonWidth: 'auto', + buttonContainer: '
', + // Maximum height of the dropdown menu. + // If maximum height is exceeded a scrollbar will be displayed. + maxHeight: false, + checkboxName: 'multiselect', + includeSelectAllOption: false, + includeSelectAllIfMoreThan: 0, + selectAllText: ' Select all', + selectAllValue: 'multiselect-all', + enableFiltering: false, + enableCaseInsensitiveFiltering: false, + filterPlaceholder: 'Search', + // possible options: 'text', 'value', 'both' + filterBehavior: 'text', + preventInputChangeEvent: false, + nonSelectedText: 'None selected', + nSelectedText: 'selected', + numberDisplayed: 3, + templates: { + button: '', + ul: '', + filter: '
', + li: '
  • ', + divider: '
  • ', + liGroup: '
  • ' + } + }, + + constructor: Multiselect, + + /** + * Builds the container of the multiselect. + */ + buildContainer: function() { + this.$container = $(this.options.buttonContainer); + this.$container.on('show.bs.dropdown', this.options.onDropdownShow); + this.$container.on('hide.bs.dropdown', this.options.onDropdownHide); + }, + + /** + * Builds the button of the multiselect. + */ + buildButton: function() { + this.$button = $(this.options.templates.button).addClass(this.options.buttonClass); + + // Adopt active state. + if (this.$select.prop('disabled')) { + this.disable(); + } + else { + this.enable(); + } + + // Manually add button width if set. + if (this.options.buttonWidth && this.options.buttonWidth !== 'auto') { + this.$button.css({ + 'width' : this.options.buttonWidth + }); + } + + // Keep the tab index from the select. + var tabindex = this.$select.attr('tabindex'); + if (tabindex) { + this.$button.attr('tabindex', tabindex); + } + + this.$container.prepend(this.$button); + }, + + /** + * Builds the ul representing the dropdown menu. + */ + buildDropdown: function() { + + // Build ul. + this.$ul = $(this.options.templates.ul); + + if (this.options.dropRight) { + this.$ul.addClass('pull-right'); + } + + // Set max height of dropdown menu to activate auto scrollbar. + if (this.options.maxHeight) { + // TODO: Add a class for this option to move the css declarations. + this.$ul.css({ + 'max-height': this.options.maxHeight + 'px', + 'overflow-y': 'auto', + 'overflow-x': 'hidden' + }); + } + + this.$container.append(this.$ul); + }, + + /** + * Build the dropdown options and binds all nessecary events. + * Uses createDivider and createOptionValue to create the necessary options. + */ + buildDropdownOptions: function() { + + this.$select.children().each($.proxy(function(index, element) { + + // Support optgroups and options without a group simultaneously. + var tag = $(element).prop('tagName') + .toLowerCase(); + + if (tag === 'optgroup') { + this.createOptgroup(element); + } + else if (tag === 'option') { + + if ($(element).data('role') === 'divider') { + this.createDivider(); + } + else { + this.createOptionValue(element); + } + + } + + // Other illegal tags will be ignored. + }, this)); + + // Bind the change event on the dropdown elements. + $('li input', this.$ul).on('change', $.proxy(function(event) { + var $target = $(event.target); + + var checked = $target.prop('checked') || false; + var isSelectAllOption = $target.val() === this.options.selectAllValue; + + // Apply or unapply the configured selected class. + if (this.options.selectedClass) { + if (checked) { + $target.parents('li') + .addClass(this.options.selectedClass); + } + else { + $target.parents('li') + .removeClass(this.options.selectedClass); + } + } + + // Get the corresponding option. + var value = $target.val(); + var $option = this.getOptionByValue(value); + + var $optionsNotThis = $('option', this.$select).not($option); + var $checkboxesNotThis = $('input', this.$container).not($target); + + if (isSelectAllOption) { + var values = []; + + // Select the visible checkboxes except the "select-all" and possible divider. + var availableInputs = $('li input[value!="' + this.options.selectAllValue + '"][data-role!="divider"]', this.$ul).filter(':visible'); + for (var i = 0, j = availableInputs.length; i < j; i++) { + values.push(availableInputs[i].value); + } + + if (checked) { + this.select(values); + } + else { + this.deselect(values); + } + } + + if (checked) { + $option.prop('selected', true); + + if (this.options.multiple) { + // Simply select additional option. + $option.prop('selected', true); + } + else { + // Unselect all other options and corresponding checkboxes. + if (this.options.selectedClass) { + $($checkboxesNotThis).parents('li').removeClass(this.options.selectedClass); + } + + $($checkboxesNotThis).prop('checked', false); + $optionsNotThis.prop('selected', false); + + // It's a single selection, so close. + this.$button.click(); + } + + if (this.options.selectedClass === "active") { + $optionsNotThis.parents("a").css("outline", ""); + } + } + else { + // Unselect option. + $option.prop('selected', false); + } + + this.$select.change(); + this.options.onChange($option, checked); + + this.updateButtonText(); + this.updateSelectAll(); + + if(this.options.preventInputChangeEvent) { + return false; + } + }, this)); + + $('li a', this.$ul).on('touchstart click', function(event) { + event.stopPropagation(); + + var $target = $(event.target); + + if (event.shiftKey) { + var checked = $target.prop('checked') || false; + + if (checked) { + var prev = $target.parents('li:last') + .siblings('li[class="active"]:first'); + + var currentIdx = $target.parents('li') + .index(); + var prevIdx = prev.index(); + + if (currentIdx > prevIdx) { + $target.parents("li:last").prevUntil(prev).each( + function() { + $(this).find("input:first").prop("checked", true) + .trigger("change"); + } + ); + } + else { + $target.parents("li:last").nextUntil(prev).each( + function() { + $(this).find("input:first").prop("checked", true) + .trigger("change"); + } + ); + } + } + } + + $target.blur(); + }); + + // Keyboard support. + this.$container.on('keydown', $.proxy(function(event) { + if ($('input[type="text"]', this.$container).is(':focus')) { + return; + } + if ((event.keyCode === 9 || event.keyCode === 27) + && this.$container.hasClass('open')) { + + // Close on tab or escape. + this.$button.click(); + } + else { + var $items = $(this.$container).find("li:not(.divider):visible a"); + + if (!$items.length) { + return; + } + + var index = $items.index($items.filter(':focus')); + + // Navigation up. + if (event.keyCode === 38 && index > 0) { + index--; + } + // Navigate down. + else if (event.keyCode === 40 && index < $items.length - 1) { + index++; + } + else if (!~index) { + index = 0; + } + + var $current = $items.eq(index); + $current.focus(); + + if (event.keyCode === 32 || event.keyCode === 13) { + var $checkbox = $current.find('input'); + + $checkbox.prop("checked", !$checkbox.prop("checked")); + $checkbox.change(); + } + + event.stopPropagation(); + event.preventDefault(); + } + }, this)); + }, + + /** + * Create an option using the given select option. + * + * @param {jQuery} element + */ + createOptionValue: function(element) { + if ($(element).is(':selected')) { + $(element).prop('selected', true); + } + + // Support the label attribute on options. + var label = this.options.label(element); + var value = $(element).val(); + var inputType = this.options.multiple ? "checkbox" : "radio"; + + var $li = $(this.options.templates.li); + $('label', $li).addClass(inputType); + $('label', $li).append(''); + + var selected = $(element).prop('selected') || false; + var $checkbox = $('input', $li); + $checkbox.val(value); + + if (value === this.options.selectAllValue) { + $checkbox.parent().parent() + .addClass('multiselect-all'); + } + + $('label', $li).append(" " + label); + + this.$ul.append($li); + + if ($(element).is(':disabled')) { + $checkbox.attr('disabled', 'disabled') + .prop('disabled', true) + .parents('li') + .addClass('disabled'); + } + + $checkbox.prop('checked', selected); + + if (selected && this.options.selectedClass) { + $checkbox.parents('li') + .addClass(this.options.selectedClass); + } + }, + + /** + * Creates a divider using the given select option. + * + * @param {jQuery} element + */ + createDivider: function(element) { + var $divider = $(this.options.templates.divider); + this.$ul.append($divider); + }, + + /** + * Creates an optgroup. + * + * @param {jQuery} group + */ + createOptgroup: function(group) { + var groupName = $(group).prop('label'); + + // Add a header for the group. + var $li = $(this.options.templates.liGroup); + $('label', $li).text(groupName); + + this.$ul.append($li); + + if ($(group).is(':disabled')) { + $li.addClass('disabled'); + } + + // Add the options of the group. + $('option', group).each($.proxy(function(index, element) { + this.createOptionValue(element); + }, this)); + }, + + /** + * Build the selct all. + * Checks if a select all ahs already been created. + */ + buildSelectAll: function() { + var alreadyHasSelectAll = this.hasSelectAll(); + + if (!alreadyHasSelectAll && this.options.includeSelectAllOption && this.options.multiple + && $('option[data-role!="divider"]', this.$select).length > this.options.includeSelectAllIfMoreThan) { + + // Check whether to add a divider after the select all. + if (this.options.includeSelectAllDivider) { + this.$select.prepend(''); + } + }, + + /** + * Builds the filter. + */ + buildFilter: function() { + + // Build filter if filtering OR case insensitive filtering is enabled and the number of options exceeds (or equals) enableFilterLength. + if (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering) { + var enableFilterLength = Math.max(this.options.enableFiltering, this.options.enableCaseInsensitiveFiltering); + + if (this.$select.find('option').length >= enableFilterLength) { + + this.$filter = $(this.options.templates.filter); + $('input', this.$filter).attr('placeholder', this.options.filterPlaceholder); + this.$ul.prepend(this.$filter); + + this.$filter.val(this.query).on('click', function(event) { + event.stopPropagation(); + }).on('input keydown', $.proxy(function(event) { + // This is useful to catch "keydown" events after the browser has updated the control. + clearTimeout(this.searchTimeout); + + this.searchTimeout = this.asyncFunction($.proxy(function() { + + if (this.query !== event.target.value) { + this.query = event.target.value; + + $.each($('li', this.$ul), $.proxy(function(index, element) { + var value = $('input', element).val(); + var text = $('label', element).text(); + + var filterCandidate = ''; + if ((this.options.filterBehavior === 'text')) { + filterCandidate = text; + } + else if ((this.options.filterBehavior === 'value')) { + filterCandidate = value; + } + else if (this.options.filterBehavior === 'both') { + filterCandidate = text + '\n' + value; + } + + if (value !== this.options.selectAllValue && text) { + // by default lets assume that element is not + // interesting for this search + var showElement = false; + + if (this.options.enableCaseInsensitiveFiltering && filterCandidate.toLowerCase().indexOf(this.query.toLowerCase()) > -1) { + showElement = true; + } + else if (filterCandidate.indexOf(this.query) > -1) { + showElement = true; + } + + if (showElement) { + $(element).show(); + } + else { + $(element).hide(); + } + } + }, this)); + } + + // TODO: check whether select all option needs to be updated. + }, this), 300, this); + }, this)); + } + } + }, + + /** + * Unbinds the whole plugin. + */ + destroy: function() { + this.$container.remove(); + this.$select.show(); + this.$select.data('multiselect', null); + }, + + /** + * Refreshs the multiselect based on the selected options of the select. + */ + refresh: function() { + $('option', this.$select).each($.proxy(function(index, element) { + var $input = $('li input', this.$ul).filter(function() { + return $(this).val() === $(element).val(); + }); + + if ($(element).is(':selected')) { + $input.prop('checked', true); + + if (this.options.selectedClass) { + $input.parents('li') + .addClass(this.options.selectedClass); + } + } + else { + $input.prop('checked', false); + + if (this.options.selectedClass) { + $input.parents('li') + .removeClass(this.options.selectedClass); + } + } + + if ($(element).is(":disabled")) { + $input.attr('disabled', 'disabled') + .prop('disabled', true) + .parents('li') + .addClass('disabled'); + } + else { + $input.prop('disabled', false) + .parents('li') + .removeClass('disabled'); + } + }, this)); + + this.updateButtonText(); + this.updateSelectAll(); + }, + + /** + * Select all options of the given values. + * + * @param {Array} selectValues + */ + select: function(selectValues) { + if(!$.isArray(selectValues)) { + selectValues = [selectValues]; + } + + for (var i = 0; i < selectValues.length; i++) { + var value = selectValues[i]; + + var $option = this.getOptionByValue(value); + var $checkbox = this.getInputByValue(value); + + if (this.options.selectedClass) { + $checkbox.parents('li') + .addClass(this.options.selectedClass); + } + + $checkbox.prop('checked', true); + $option.prop('selected', true); + } + + this.updateButtonText(); + }, + + /** + * Clears all selected items + * + */ + clearSelection: function () { + + var selected = this.getSelected(); + + if (selected.length) { + + var arry = []; + + for (var i = 0; i < selected.length; i = i + 1) { + arry.push(selected[i].value); + } + + this.deselect(arry); + this.$select.change(); + } + }, + + /** + * Deselects all options of the given values. + * + * @param {Array} deselectValues + */ + deselect: function(deselectValues) { + if(!$.isArray(deselectValues)) { + deselectValues = [deselectValues]; + } + + for (var i = 0; i < deselectValues.length; i++) { + + var value = deselectValues[i]; + + var $option = this.getOptionByValue(value); + var $checkbox = this.getInputByValue(value); + + if (this.options.selectedClass) { + $checkbox.parents('li') + .removeClass(this.options.selectedClass); + } + + $checkbox.prop('checked', false); + $option.prop('selected', false); + } + + this.updateButtonText(); + }, + + /** + * Rebuild the plugin. + * Rebuilds the dropdown, the filter and the select all option. + */ + rebuild: function() { + this.$ul.html(''); + + // Remove select all option in select. + $('option[value="' + this.options.selectAllValue + '"]', this.$select).remove(); + + // Important to distinguish between radios and checkboxes. + this.options.multiple = this.$select.attr('multiple') === "multiple"; + + this.buildSelectAll(); + this.buildDropdownOptions(); + this.buildFilter(); + + this.updateButtonText(); + this.updateSelectAll(); + }, + + /** + * The provided data will be used to build the dropdown. + * + * @param {Array} dataprovider + */ + dataprovider: function(dataprovider) { + var optionDOM = ""; + dataprovider.forEach(function (option) { + optionDOM += ''; + }); + + this.$select.html(optionDOM); + this.rebuild(); + }, + + /** + * Enable the multiselect. + */ + enable: function() { + this.$select.prop('disabled', false); + this.$button.prop('disabled', false) + .removeClass('disabled'); + }, + + /** + * Disable the multiselect. + */ + disable: function() { + this.$select.prop('disabled', true); + this.$button.prop('disabled', true) + .addClass('disabled'); + }, + + /** + * Set the options. + * + * @param {Array} options + */ + setOptions: function(options) { + this.options = this.mergeOptions(options); + }, + + /** + * Merges the given options with the default options. + * + * @param {Array} options + * @returns {Array} + */ + mergeOptions: function(options) { + return $.extend(true, {}, this.defaults, options); + }, + + /** + * Checks whether a select all option is present. + * + * @returns {Boolean} + */ + hasSelectAll: function() { + return $('option[value="' + this.options.selectAllValue + '"]', this.$select).length > 0; + }, + + /** + * Updates the select all option based on the currently selected options. + */ + updateSelectAll: function() { + if (this.hasSelectAll()) { + var selected = this.getSelected(); + + if (selected.length === $('option:not([data-role=divider])', this.$select).length - 1) { + this.select(this.options.selectAllValue); + } + else { + this.deselect(this.options.selectAllValue); + } + } + }, + + /** + * Update the button text and its title based on the currently selected options. + */ + updateButtonText: function() { + var options = this.getSelected(); + + // First update the displayed button text. + $('button', this.$container).html(this.options.buttonText(options, this.$select)); + + // Now update the title attribute of the button. + $('button', this.$container).attr('title', this.options.buttonTitle(options, this.$select)); + + }, + + /** + * Get all selected options. + * + * @returns {jQUery} + */ + getSelected: function() { + return $('option[value!="' + this.options.selectAllValue + '"]:selected', this.$select).filter(function() { + return $(this).prop('selected'); + }); + }, + + /** + * Gets a select option by its value. + * + * @param {String} value + * @returns {jQuery} + */ + getOptionByValue: function (value) { + + var options = $('option', this.$select); + var valueToCompare = value.toString(); + + for (var i = 0; i < options.length; i = i + 1) { + var option = options[i]; + if (option.value === valueToCompare) { + return $(option); + } + } + }, + + /** + * Get the input (radio/checkbox) by its value. + * + * @param {String} value + * @returns {jQuery} + */ + getInputByValue: function (value) { + + var checkboxes = $('li input', this.$ul); + var valueToCompare = value.toString(); + + for (var i = 0; i < checkboxes.length; i = i + 1) { + var checkbox = checkboxes[i]; + if (checkbox.value === valueToCompare) { + return $(checkbox); + } + } + }, + + /** + * Used for knockout integration. + */ + updateOriginalOptions: function() { + this.originalOptions = this.$select.clone()[0].options; + }, + + asyncFunction: function(callback, timeout, self) { + var args = Array.prototype.slice.call(arguments, 3); + return setTimeout(function() { + callback.apply(self || window, args); + }, timeout); + } + }; + + $.fn.multiselect = function(option, parameter) { + return this.each(function() { + var data = $(this).data('multiselect'); + var options = typeof option === 'object' && option; + + // Initialize the multiselect. + if (!data) { + data = new Multiselect(this, options); + $(this).data('multiselect', data); + } + + // Call multiselect method. + if (typeof option === 'string') { + data[option](parameter); + + if (option === 'destroy') { + $(this).data('multiselect', false); + } + } + }); + }; + + $.fn.multiselect.Constructor = Multiselect; + + $(function() { + $("select[data-role=multiselect]").multiselect(); + }); + +}(window.jQuery); diff --git a/resource/js/config.js b/resource/js/config.js new file mode 100644 index 000000000..8b5c4f516 --- /dev/null +++ b/resource/js/config.js @@ -0,0 +1,15 @@ +/* Copyright (c) 2012 Aalto University and University of Helsinki + MIT License + see LICENSE.txt for more information +*/ + +// delay for the autocomplete widget's activation +var autocomplete_delay = 400; +// how many letters the user has to input before the widget activates. +var autocomplete_activation = 2; + +// how many results are loaded by default in rest when the waypoint thingy activates / waypoint is reached +var waypoint_results = 20; + +// override this if you'd like to use the rest-api from some other ONKI Light-server instance. +var rest_base_url = path_fix + 'rest/v1/'; diff --git a/resource/js/docready.js b/resource/js/docready.js new file mode 100644 index 000000000..879d3b624 --- /dev/null +++ b/resource/js/docready.js @@ -0,0 +1,720 @@ +/* + * Copyright (c) 2012 Aalto University and University of Helsinki + * MIT License + * see LICENSE.txt for more information + */ + +function customAutocomplete() { + $.ui.autocomplete.prototype._renderItem = function (ul, item) { + var label = item.label; + var vocab = ''; + if (label.indexOf('@') != -1) { + vocab = label.substring(label.indexOf('@'), label.length); + item.label = label.substring(0, label.indexOf('@')); + } + var output = item.label + '' + vocab + ''; + return $("
  • ") + .append($("").html(output)) + .appendTo(ul); + }; +} + +$(function() { // DOCUMENT READY + + var selectedVocabs = []; + var vocabSelectionString = readCookie('SKOSMOS_SELECTED') ? readCookie('SKOSMOS_SELECTED') : ''; + $('#selected-vocabs').val(vocabSelectionString); + + function shortenProperties() { + $properties = $('.property'); + for (var i = 0; i < $properties.length; i++) { + var $property = $($properties[i]); + if ($property.height() > 24) { + $property.addClass('shortened-property'); + var count = $property.children('.versal').length; + var uri = $property.siblings('.prefLabel')[0].href; + var shortened = '... (' + count +')'; + $property.append(shortened); + } + } + } + + shortenProperties(); + + /* + * Moving the sidenav scrollbar towards the current concept. Aiming the current + * concept at vertical center of the container. Each concept needs 18px height. + */ + $(document).ajaxComplete(function(event, xhr, settings) { + if (settings.url.indexOf('groups') !== -1 || settings.url.indexOf('index') !== -1) { + $('.sidebar-grey').removeClass(function(index, classes) { + var elementClasses = classes.split(' '); + var removeThese = []; + + $.each(elementClasses, function() { + if(this.match(/jstree*/)) + removeThese.push(this); + }); + return removeThese.join(' '); + }); + } + $('.sidebar-grey').mCustomScrollbar('update'); + // Sidenav actions only happen when doing other queries than the autocomplete. + if (settings.url.indexOf('index') !== -1 || settings.url.indexOf('groups') !== -1 || settings.url.indexOf('hierarchy') !== -1) { + countAndSetOffset(); + $(".sidebar-grey").mCustomScrollbar({'scrollInertia': 0}); + if (settings.url.indexOf('hierarchy') !== -1) + $(".sidebar-grey").mCustomScrollbar('scrollTo', scrollToConcept()); + } + }); + + function scrollToConcept() { + var containerHeight = $('.sidebar-grey').height(); + var conceptCount = Math.floor((containerHeight * 0.66) / 18); + var scrollAmount = 18 * conceptCount; + return $('#jstree-leaf-proper')[0].offsetTop-scrollAmount; + } + + // if on the search results page and there is only one result + if ($('.concept-info').length === 1) { + invokeParentTree(getTreeConfiguration()); + } + + // if we are on the vocab front page initialize the hierarchy view with a top concept. + $('#hier-trigger').click(function () { + if($('.uri-input-box').length === 0) { // if on the vocabulary front page + $('.active').removeClass('active'); + $('#hier-trigger').parent().addClass('active'); + var $content = $('.sidebar-grey'); + $content.empty(); + $content.append('
    '); + invokeParentTree(getTreeConfiguration(true)); + $('#hier-trigger').attr('href', '#'); + return false; + } + var uri = $('.uri-input-box').html(); + var redirectUrl = 'http://' + base_url + vocab + '/' + lang + '/page/' + uri.split('/')[uri.split('/').length-1]; + window.location.replace(encodeURI(redirectUrl)); + }); + + var textColor = $('.search-parameter-highlight').css('color'); + countAndSetOffset(); + + if(parts.indexOf('search') == -1) { // Disabled if on the search results page. + /* + * Event handler for clicking the preflabel and making a selection of it for copy pasting. + */ + $(document).on('click','.prefLabel', + function() { + var text = $('.prefLabel')[0]; + var range; + if (document.body.createTextRange) { // ms + range = document.body.createTextRange(); + range.moveToElementText(text); + range.select(); + } else if (window.getSelection) { // moz, opera, webkit + var selection = window.getSelection(); + range = document.createRange(); + range.selectNodeContents(text); + selection.removeAllRanges(); + selection.addRange(range); + } + return false; + } + ); + + } + + $(document).on('click','.uri-input-box', + function() { + var $clicked = $(this); + var text = $clicked[0]; + var range; + if (document.body.createTextRange) { // ms + range = document.body.createTextRange(); + range.moveToElementText(text); + range.select(); + } else if (window.getSelection) { // moz, opera, webkit + var selection = window.getSelection(); + range = document.createRange(); + range.selectNodeContents(text); + selection.removeAllRanges(); + selection.addRange(range); + } + return false; + } + ); + + // Calculates and sets how many vertical pixels the sidebar height should be at the current scroll position. + function countAndSetOffset() { + if ($('#sidebar').length && !$('#sidebar').hasClass('fixed')) { + var yOffset = window.innerHeight - ( $('#sidebar').offset().top - pageYOffset); + $('#sidebar').css('height', yOffset); + } + } + + // Debounce function from underscore.js + function debounce(a,b,c){var d;return function(){var e=this,f=arguments;clearTimeout(d),d=setTimeout(function(){d=null,c||a.apply(e,f)},b),c&&!d&&a.apply(e,f)}} + + var sidebarResizer = debounce(function() { + countAndSetOffset(); + }, 40); + + // Event handler for mutilating the sidebar css when the user scrolls the headerbar out of the view. + if ($('.sidebar-grey').length > 0) { + $(window).on('scroll', sidebarResizer); + + var sidebarFixed = false; + // the handler listens to headerbars position so it works correctly after the sidebar is hidden/shown again. + $('.headerbar').waypoint(function(direction) { + if (!sidebarFixed && direction == 'down') { + sidebarFixed = true; + $('#sidebar').addClass('fixed'); + } else { + sidebarFixed = false; + $('#sidebar').removeClass('fixed'); + } + }, { offset: -60 }); // when the headerbars bottom margin is at the top of the screen + } + + // Event handler for restoring the DOM back to normal after the focus is lost from the prefLabel. + $(':not(.search-parameter-highlight)').click( + function(){ + $('#temp-textarea').remove(); + $('.search-parameter-highlight').css({'background': 'transparent', 'color': textColor}); + } + ); + + // event handling the breadcrumb expansion + $(document).on('click', '.expand-crumbs', + function(event){ + var clicked = $(event.currentTarget); + clicked.parent().find('.hidden-breadcrumb').removeClass('hidden-breadcrumb').addClass('bread-crumb'); + clicked.next().remove(); // Removing and the following > symbol. + clicked.remove(); // and the clickable '...' element + return false; + } + ); + + // event handler for clicking the hierarchy concepts + $(document).on('click', '.jstree-no-icons a', + function(event) { + event.preventDefault(); + var base_path = path_fix.length / 3; + var clicked = $(this); + var $content = $('.content'); + var targetUrl = 'http://' + base_url + vocab + '/' + lang + '/page/'; + var parameters = $.param({'uri' : event.target.href, 'base_path' : base_path}); + $('#hier-trigger').attr('href', targetUrl + '?uri=' + event.target.href); + $.ajax({ + url : targetUrl, + data: parameters, + success : function(data) { + $('#jstree-leaf-proper').attr('id', ''); + clicked.attr('id', 'jstree-leaf-proper'); + $content.empty(); + var title = $(data).filter('title').text(); + var response = $('.content', data).html(); + document.title = title; + if (window.history.pushState) + window.history.pushState(null, null, encodeURI(targetUrl + '?uri=' + event.target.href)); + $content.append(response); + } + }); + return false; + } + ); + + // event handler for clicking the alphabetical/group index concepts + $(document).on('click', '.side-navi a', + function(event) { + var base_path = path_fix.length / 3; + var clicked = $(this); + $('.activated-concept').removeClass('activated-concept'); + clicked.addClass('activated-concept'); + var $content = $('.content'); + var targetUrl = event.target.href; + var parameters = $.param({'base_path' : base_path}); + var hierButton = '
  • Hierarkia
  • '; + $.ajax({ + url : targetUrl, + data: parameters, + success : function(data) { + $content.empty(); + var title = $(data).filter('title').text(); + var response = $('.content', data).html(); + document.title = title; + if (window.history.pushState) + window.history.pushState(null, null, encodeURI(event.target.href)); + $content.append(response); + if (!$('#hierarchy').length) + $('#alpha').after(hierButton); + $('#hier-trigger').attr('href', event.target.href); + } + }); + return false; + } + ); + + // event handler for clicking the alphabetical index tab + $(document).on('click', '.nav-tabs a[href$="index"]', + function(event) { + var base_path = path_fix.length / 3; + $('.active').removeClass('active'); + var clicked = $(this); + clicked.parent().addClass('active'); + var $content = $('.sidebar-grey'); + var targetUrl = event.target.href; + var parameters = $.param({'base_path' : base_path}); + $.ajax({ + url : targetUrl, + data: parameters, + success : function(data) { + $content.empty(); + var title = $(data).filter('title').text(); + var response = $('.sidebar-grey', data).html(); + $content.append(response); + $('.nav').scrollTop(0); + if (window.history.pushState) + window.history.pushState(null, null, encodeURI(event.target.href)); + document.title = title; + } + }); + return false; + } + ); + + // event handler for clicking the group index tab + $(document).on('click', '.nav-tabs a[href$="groups"]', + function(event) { + var base_path = path_fix.length / 3; + $('.active').removeClass('active'); + var clicked = $(this); + clicked.parent().addClass('active'); + var $content = $('.sidebar-grey'); + var targetUrl = event.target.href; + var parameters = $.param({'base_path' : base_path}); + $.ajax({ + url : targetUrl, + data: parameters, + success : function(data) { + $content.empty(); + var title = $(data).filter('title').text(); + var response = $('.sidebar-grey', data).html(); + $content.append(response); + $('.nav').scrollTop(0); + if (window.history.pushState) + window.history.pushState(null, null, encodeURI(event.target.href)); + document.title = title; + } + }); + return false; + } + ); + + // event handler for clicking groups + $(document).on('click','.group-index > li > a', + function(event) { + var base_path = path_fix.length / 3; + var clicked = $(this); + var $content = $('.sidebar-grey'); + var targetUrl = event.target.href; + var parameters = $.param({'base_path' : base_path}); + $.ajax({ + url : targetUrl, + data: parameters, + success : function(data) { + $content.empty(); + var title = $(data).filter('title').text(); + var response = $('.sidebar-grey', data).html(); + $content.append(response); + $('.nav').scrollTop(0); + if (window.history.pushState) + window.history.pushState(null, null, encodeURI(event.target.href)); + document.title = title; + } + }); + return false; + } + ); + + // event handler for the alphabetical index letters + $(document).on('click','.pagination > li > a', + function(event) { + if ($('.alphabet-header').length === 0) { + var $pagination = $('.pagination'); + $pagination.after('
    '+ loading_text + '
    '); + var base_path = path_fix.length / 3; + var $content = $('.sidebar-grey'); + var targetUrl = event.target.href; + var parameters = $.param({'base_path' : base_path}); + $.ajax({ + url : targetUrl, + data: parameters, + success : function(data) { + $content.empty(); + var title = $(data).filter('title').text(); + var response = $('.sidebar-grey', data).html(); + $content.append(response); + $('.nav').scrollTop(0); + if (window.history.pushState) + window.history.pushState(null, null, encodeURI(event.target.href)); + document.title = title; + } + }); + } else { + var selectedLetter = $(event.target).text().trim(); + if (document.getElementsByName(selectedLetter).length === 0) + return false; + var offset = $('li[name=' + selectedLetter + ']').offset().top - $('body').offset().top - 5; + $('.nav').scrollTop(offset); + } + return false; + } + ); + + $('.property-mouseover').tooltip().on('click', + function() { + if ($(this).siblings('.tooltip').length === 0) + $(this).tooltip('show'); + else + $(this).tooltip('hide'); + return false; + } + ); + + $(document).on('click', '.tooltip', + function(event) { + $(event.target).parent().siblings('.property-mouseover').tooltip('hide'); + } + ); + + // Generates property helpers as p elements or removes the helper text if it's clicked again. + $(document).on('click','.property-click', + function(event) { + var $property = $(this); + if ($property.children('.tooltip').length === 0) + $property.children('.property-mouseover').tooltip('show'); + else + $property.children('.property-mouseover').tooltip('hide'); + } + ); + + $(document).on('mouseenter','.property-hover', + function(event) { + var $property = $(this); + if ($property.siblings('.tooltip').length === 0) + $property.siblings('.property-mouseover').tooltip('show'); + } + ); + + $(document).on('mouseleave','.property-hover', + function(event) { + var $property = $(this); + if ($property.siblings('.tooltip').length !== 0) + $property.siblings('.property-mouseover').tooltip('hide'); + } + ); + + // sets the language cookie for 365 days + function setLangCookie(lang) { + createCookie('ONKI_LANGUAGE', lang, 365); + } + + // Event handlers for the language selection links for setting the cookie + $('#language a').each( function(index, el) { + $(el).click(function() { + var langCode = el.id.substr(el.id.indexOf("-") + 1); + setLangCookie(langCode); + }); + }); + + // Setting the language parameters according to the cookie if found. + var search_lang = readCookie('ONKI_SEARCH_LANG'); + + // taking the url parameters given by the controller + // into parts used for determining if we are on the search listings + parts = parts.split('/'); // splits pathname, e.g. + + var rest_url = rest_base_url; + if (rest_url.indexOf('..') == -1 && rest_url.indexOf('http') == -1) { rest_url = encodeURI(location.protocol + '//' + rest_url); } + + // qlang is used in REST queries as a parameter. it is either + // - "&lang=" when searching in a specific language + // - "" when searching in all languages + var qlang = search_lang; + + // setting the focus to the search box on default + $("#search-field").focus(); + + if (search_lang === 'anything' || !search_lang || (typeof getUrlParams().lang !== 'undefined' && getUrlParams().lang === '')) { + $('#lang-dropdown-toggle').html($('.lang-button-all').html() + ' '); + $('#lang-input').val(''); + qlang = ""; + } else { + var langPretty = $('a[hreflang=' + lang + ']').html(); + search_lang = lang; + if (!langPretty) + langPretty = $('a[hreflang="anything"]').html(); + $('#lang-dropdown-toggle').html(langPretty + ' '); + qlang = lang; + } + + $('.lang-button').click(function() { + qlang = $(this)[0].attributes.hreflang.value; + if (qlang === '') + qlang = 'anything'; + $('#lang-dropdown-toggle').html($(this).html() + ' '); + $('#lang-input').val(qlang); + createCookie('ONKI_SEARCH_LANG', qlang, 365); + }); + + $('.lang-button-all').on('click', function() { + qlang = ""; + createCookie('ONKI_SEARCH_LANG', 'anything', 365); + $('#lang-input').val(''); + $('#lang-dropdown-toggle').html($('.lang-button-all').html() + ' '); + }); + + $('.lang-button, .lang-button-all').click(function() { + $('#search-field').focus(); + }); + + var searchTerm = ""; + // calls for another function to highlight search term in the labels. + if (getUrlParams().q) { + localSearchHighlight(decodeURI(getUrlParams().q.replace(/\*/g, ''))); + searchTerm = getUrlParams().q; + } + + var NoResultsLabel = [ { + "label" : noResultsTranslation, + "vocab" : "" + } ]; + + // disables the button with an empty search form + $('#search-field').keyup(function() { + var empty = false; + $('#search-field').each(function() { + if ($(this).val().length === 0) { + empty = true; + } + }); + + if (empty) { + $('#search-all-button').attr('disabled', 'disabled'); + } else { + $('#search-all-button').attr('disabled', false); + } + }); + + // activates jquery autocomplete for the search fields + var autoC = $("#search-field").autocomplete({ + source : function(request, response) { + // default to prefix search when no wildcards were used + var term = request.term.trim(); // surrounding whitespace is not significant + term = term.indexOf("*") >= 0 ? term : term + "*"; + var vocabString = $('.multiselect').length ? vocabSelectionString : vocab; + var parameters = $.param({'query' : term, 'vocab' : vocabString, 'lang' : qlang}); + $.ajax({ + url : rest_url + 'search', + data: parameters, + dataType : "json", + success : function(data) { + if (data.results.length === 0) { + response(NoResultsLabel); + } + else { + response($ + .map( + data.results + .filter(function(item) { + // either we are performing a local search + // or the concept is native to the vocabulary + return (vocab !== "" || !item.exvocab); + }), + function(item) { + var name = (item.altLabel ? item.altLabel + + " \u2192 " + + item.prefLabel : item.prefLabel); + if(item.hiddenLabel) + name = item.hiddenLabel + " \u2192 " + item.prefLabel; + item.label = name; + if (item.vocab && item.vocab != vocab) // if performing global search include vocabid + item.label += ' @' + item.vocab + ' '; + if (item.exvocab && item.exvocab != vocab) + item.label += ' @' + item.exvocab + ' '; + if (item.lang && item.lang !== lang) // if the label is not in the ui lang + item.label += ' @ ' + item.lang; + return item; + })); + } + } + }); + }, + delay : autocomplete_delay, // time (in milliseconds) + // before autocomplete + // activates i.e. sends a + // request to the REST + // interface + minLength : autocomplete_activation, + appendTo: "#header-bar-content", + + select : function(event, ui) { // what happens when + // user clicks/uses autocomplete + var localname = ui.item.localname; + if (ui.item.exvocab) { + localname = "?uri=" + ui.item.uri; + } + if (ui.item.label === NoResultsLabel[0].label) { + event.preventDefault(); + return false; + } + // replaced complex logic with path_fix that should always work. + if (ui.item.type && ui.item.type.indexOf('Collection') !== -1) { + location.href = encodeURI(path_fix + ui.item.vocab + '/' + lang + '/groups/' + localname); + } else { + location.href = encodeURI(path_fix + ui.item.vocab + '/' + lang + '/page/' + localname); + } + }, + focus : function(event, ui) { + return false; // Prevent the widget from inserting the value. + } + }).bind('focus', function() { + $('#search-field').autocomplete('search'); + }); + + $("button#send-feedback").button({ + icons : { + primary : "ui-icon-mail-closed" + } + }); + + // Some form validation for the feedback form + $("#send-feedback") + .click( + function() { + $(".error").remove(); + var emailMessageVal = $("#message").val(); + var emailAddress = $("#email").val(); + var requiredFields = true; + $('.errors').remove(); + if (emailAddress === '' || emailAddress.indexOf('@') === -1) { + $("#email").before('' + missing_value + ''); + requiredFields = false; + } + if (emailMessageVal === '') { + $("#message").before('' + missing_value + ''); + requiredFields = false; + } + return requiredFields; + }); + + // Initializes the waypoints plug-in used for the search listings. + var $loading = $("

    " + loading_text + "…

    "); + var $trigger = $('.search-result:nth-last-of-type(4)'); + var options = { offset : '100%', continuous: false, triggerOnce: true }; + var offcount = 1; + var number_of_hits = document.getElementsByClassName("search-result").length; + var $ready = $("

    " + results + " " + number_of_hits + " " + results_disp +"

    "); + + if (parts[parts.length-1].indexOf('search') !== -1 && number_of_hits !== 0) { // if we are in the search page with some results + if (number_of_hits < waypoint_results * offcount) { + $('.search-result-listing').append($ready); + } + else { + $trigger.waypoint(function() { waypointCallback(); }, options); + } + } + + function waypointCallback() { + var number_of_hits = document.getElementsByClassName("search-result").length; + if (number_of_hits >= waypoint_results * offcount) + $('.content').append($loading); + var parameters = $.param({'q' : searchTerm, 'vocabs' : vocabSelectionString, 'offset' : offcount * waypoint_results, 'lang' : getUrlParams().lang}); + $.ajax({ + url : window.location.pathname, + data : parameters, + success : function(data) { + $loading.detach(); + if ($(data).find('.search-result').length === 0) { + $('.search-result-listing').append($ready); + return false; + } + $('.search-result-listing').append($(data).find('.search-result')); + number_of_hits = $('.uri-input-box').length; + $ready = $("

    " + results + " " + document.getElementsByClassName("search-result").length + " " + results_disp +"

    "); + offcount++; + if (getUrlParams().q) { + localSearchHighlight(decodeURI(getUrlParams().q.replace(/\*/g, ""))); + } + shortenProperties(); + $('.search-result:nth-last-of-type(4)').waypoint(function() { waypointCallback(); }, options ); + } + }); + } + + // activating the custom autocomplete + customAutocomplete(); + + function updateVocabParam() { + vocabSelectionString = ''; + $vocabs = $('li.active input'); + $.each($vocabs, + function(index, ob) { + if (ob.value === 'multiselect-all') { + $('input[value=multiselect-all]', $('.multiselect-all')).click(); + return false; + } + vocabSelectionString += ob.value; + if (index < $vocabs.length - 1) + vocabSelectionString += ' '; + }); + // sets the selected vocabularies cookie for the frontpage search. + createCookie('SKOSMOS_SELECTED', vocabSelectionString, 365); + $('#selected-vocabs').val(vocabSelectionString); + } + + // preselecting the vocabularies from the cookie for the multiselect dropdown plugin. + $.each(vocabSelectionString.split(' '), function(index, vocabId) { + $('option[value="' + vocabId + '"]').prop('selected', 'true'); + }); + + $('.multiselect').multiselect({ + buttonText: function(options) { + if (options.length === 0 || vocabSelectionString === '') + return '' + all_vocabs + ' '; + else { + if (options.length > this.numberDisplayed) { + return '' + options.length + ' ' + n_selected + ' '; + } + else { + var selected = ''; + options.each(function() { + var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).html(); + + selected += label + ', '; + }); + return '' + selected.substr(0, selected.length - 2) + ' '; + } + } + }, + numberDisplayed: 2, + buttonWidth: '155px', + includeSelectAllOption: true, + selectAllText: all_vocabs, + onChange: function(element, checked) { + vocabId = element[0].value; + if (checked && selectedVocabs[vocabId] === undefined) + selectedVocabs[vocabId] = vocabId; + else if (selectedVocabs[vocabId] !== undefined) { + delete selectedVocabs[vocabId]; + } + this.vocabSelectionString = updateVocabParam(); + }, + maxHeight: 300 + }); + + // activating the custom scrollbars + $(".sidebar-grey").mCustomScrollbar({'scrollInertia': 0}); +}); diff --git a/resource/js/hierarchy.js b/resource/js/hierarchy.js new file mode 100644 index 000000000..8be4cf152 --- /dev/null +++ b/resource/js/hierarchy.js @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2012 Aalto University and University of Helsinki + * MIT License + * see LICENSE.txt for more information + */ +var treeIndex = {}; +var urlToUri = {}; +var rest = rest_base_url; + +/* + * For legacy browsers that don't natively support Object.size(). + * @param {Object} obj + */ +Object.size = function (obj) { + var size = 0, key; + for (key in obj) { + if (obj.hasOwnProperty(key)) { + size += 1; + } + } + return size; +}; + +function getNode(uri) { return treeIndex[uri]; } + +function setNode(node) { treeIndex[node.uri] = node; storeUri(node); } + +function storeUri(node) { urlToUri[node.data.attr.href] = node.uri; } + +/* + * Initializes jsTree with the json formatted data for a concept and it's parents. + * Also binds a window.location.href redirect to clicking a node. + * @param {Object} tree + */ +function invokeParentTree(tree) { + var treeObject = $('.sidebar-grey'); + treeObject.jstree(tree).on('loaded.jstree', function() { + treeObject.bind('select_node.jstree', function (event, data) { + /* + * preventing redirect recursion with the initially_select option and + * at the same time stopping the user from refreshing the current page by clicking the concepts name in the hierarchy. + */ + if (data.rslt.obj[0].children[1].id != 'jstree-leaf-proper') { + data.inst.open_node(data.rslt.obj); + } + }); + }); +} + +/* + * Creates a concept object from the data returned by a rest query. + * @param + * @param + */ +function createConceptObject(conceptUri, conceptData) { + var prefLabel = conceptData.prefLabel; // the json narrower response has a different structure. + newNode = { + data: { "title" : prefLabel, "attr" : { "href" : conceptUri } }, + uri: conceptUri, + parents: conceptData.broader, + attr: {}, + children: [] + }; + // if we are at a top concepts page we want to highlight that node and mark it as to be initially opened. + if (newNode.uri === $('.uri-input-box').html()) { newNode.data.attr.id = 'jstree-leaf-proper'; } + + if (conceptData.narrower /* && !conceptData.narrower[0] */) { // filtering out the ones that don't have labels + var childArray = []; + for (var child in conceptData.narrower) { + var conceptObject = conceptData.narrower[child]; + var hasChildren = conceptObject.hasChildren; + var childObject = { + data: { "title" : conceptObject.label, "attr" : { "href" : conceptData.narrower[child].uri } }, + uri: conceptData.narrower[child].uri, + parents: conceptUri, + children: [], + state: "closed" + }; + if (child === $('.uri-input-box').html()) { childObject.data.attr.id = 'jstree-leaf-proper'; } + // if the childConcept hasn't got any children the state is not needed. + if (hasChildren === false) { + delete childObject.state; + } + if(!childArray[childObject.uri]) + childArray.push(childObject); + storeUri(childObject); + } + newNode.children.push(childArray); + } + + return newNode; +} + +/* + * For building a parent hierarchy tree from the leaf concept to the ontology/vocabulary root. + * @param {String} uri + * @param {Object} parentData + */ +function buildParentTree(uri, parentData) { + if (parentData === undefined || parentData === null) { return; } + + var loopIndex = 0, // for adding the last concept as a root if no better candidates have been found. + currentNode, + rootNode; + + for(var conceptUri in parentData) { + var previousNode = currentNode, + branchHelper, + exactMatchFound; + currentNode = createConceptObject(conceptUri, parentData[conceptUri]); + /* if a node has the property topConceptOf set it as the root node. + * Or just setting the last node as a root if nothing else has been found + */ + if (parentData[conceptUri].top || ( loopIndex == Object.size(parentData)-1) && !rootNode) { + if (!rootNode) { + branchHelper = currentNode; + } + rootNode = currentNode; + } + if (exactMatchFound) { // combining branches if we have met a exact match during the previous iteration. + currentNode.children.push(branchHelper); + branchHelper = undefined; + exactMatchFound = false; + } + // here we have iterated far enough to find the merging point of the trees. + if (branchHelper && parentData[branchHelper.uri].exact === currentNode.uri) { + exactMatchFound = true; + } + setNode(currentNode); + loopIndex++; + } + + // Iterating over the nodes to make sure all concepts have their children set. + appendChildrenToParents(); + return rootNode; +} + +function vocabRoot(topConcepts) { + var topArray = []; + for (var i = 0; i < topConcepts.length; i++) { + var conceptData = topConcepts[i]; + var childObject = { + data: { "title" : conceptData.label, "attr" : { "href" : conceptData.uri } }, + uri: conceptData.uri, + children: [], + state: "closed" + }; + setNode(childObject); + topArray.push(childObject); + } + return topArray; +} + +/* + * Iterates through the tree and fixes all the parents by adding references to their child concepts. + */ +function appendChildrenToParents() { + for (var j in treeIndex) { + var current = treeIndex[j]; + for (var index in current.parents) { + var parentNode = getNode(current.parents[index]); + if (parentNode !== current) + if (parentNode && $.inArray(current, parentNode.children) === -1) { + for(var sibling in parentNode.children[0]) { + if(parentNode.children[0][sibling].uri == current.uri){ + // if the concept has already been added remove the previous one since this one is more accurate. + parentNode.children[0].splice(sibling, 1); + } + } + parentNode.children.push(current); + } + } + } +} + +function createObjectsFromNarrowers(narrowerResponse) { + var childArray = []; + for (var child in narrowerResponse.narrower) { + var conceptObject = narrowerResponse.narrower[child]; + var hasChildren = conceptObject.hasChildren; + var childObject = { + data: { "title" : conceptObject.prefLabel, "attr" : { "href" : conceptObject.uri } }, + uri: conceptObject.uri, + parents: narrowerResponse.uri, + children: [], + state: "closed" + }; + if (hasChildren === false) { + delete childObject.state; + } + setNode(childObject); + childArray.push(childObject); + } + return childArray; +} + +/* + * Gives you the ONKI default jsTree configuration. + */ +function getTreeConfiguration(root) { + var childResponse = false; + var nodeId = ''; + var jsonData = { + json_data: { + ajax: { + type: 'GET', + data: function (node) { + if (node == -1 && root) + return $.param({'lang' : lang}); + else if(node != -1) { + nodeId = urlToUri[node[0].children[1].href]; + if (!nodeId) { + nodeId = node[0].children[1].href; + } + return $.param({'uri' : nodeId, 'lang' : lang}); + } else { + nodeId = $('.uri-input-box').html(); // using the real uri of the concept from the view. + return $.param({'uri' : nodeId, 'lang' : lang}); + } + }, + url: function (node) { + if (node == -1 && root) { + return (rest_base_url + vocab + '/topConcepts'); + } else if(node != -1) { + return (rest_base_url + vocab + '/children'); + } else { + nodeId = $('.uri-input-box').html(); // using the real uri of the concept from the view. + return (rest_base_url + vocab + '/hierarchy'); + } + }, + success: function (response) { + if (response.broaderTransitive) { // the default hierarchy query that fires when a page loads. + return buildParentTree(nodeId, response.broaderTransitive); + } else if(response.topconcepts) { + return vocabRoot(response.topconcepts); + } else { + return createObjectsFromNarrowers(response); + } + return (nodeId.indexOf('http') == -1 /* || top_concepts !== '' */) ? ret : ret.children; // or is for the vocabulary top concept hierarchy. + }, + }, + }, + core: { animation: 0, initially_open: ['#jstree-leaf-proper'], strings: { loading : jstree_loading, new_node : 'New node' } }, + ui: { initially_select: ['#jstree-leaf-proper'] }, + themes: {} + }; + jsonData.plugins = ['themes', 'json_data', 'ui', 'sort']; + jsonData.themes = { + theme: 'default', + url: path_fix + 'lib/jsTree/default/style.css', + icons: false, + dots: true + }; + + return jsonData; +} + diff --git a/resource/js/jquery.jstree.js b/resource/js/jquery.jstree.js new file mode 100644 index 000000000..baba5e8a1 --- /dev/null +++ b/resource/js/jquery.jstree.js @@ -0,0 +1,4551 @@ +/* + * jsTree 1.0-rc3 + * http://jstree.com/ + * + * Copyright (c) 2010 Ivan Bozhanov (vakata.com) + * + * Licensed same as jquery - under the terms of either the MIT License or the GPL Version 2 License + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * $Date: 2011-02-09 01:17:14 +0200 (ср, 09 февр 2011) $ + * $Revision: 236 $ + */ + +/*jslint browser: true, onevar: true, undef: true, bitwise: true, strict: true */ +/*global window : false, clearInterval: false, clearTimeout: false, document: false, setInterval: false, setTimeout: false, jQuery: false, navigator: false, XSLTProcessor: false, DOMParser: false, XMLSerializer: false*/ + +"use strict"; + +// top wrapper to prevent multiple inclusion (is this OK?) +(function () { if(jQuery && jQuery.jstree) { return; } + var is_ie6 = false, is_ie7 = false, is_ff2 = false; + +/* + * jsTree core + */ +(function ($) { + // Common functions not related to jsTree + // decided to move them to a `vakata` "namespace" + $.vakata = {}; + // CSS related functions + $.vakata.css = { + get_css : function(rule_name, delete_flag, sheet) { + rule_name = rule_name.toLowerCase(); + var css_rules = sheet.cssRules || sheet.rules, + j = 0; + do { + if(css_rules.length && j > css_rules.length + 5) { return false; } + if(css_rules[j].selectorText && css_rules[j].selectorText.toLowerCase() == rule_name) { + if(delete_flag === true) { + if(sheet.removeRule) { sheet.removeRule(j); } + if(sheet.deleteRule) { sheet.deleteRule(j); } + return true; + } + else { return css_rules[j]; } + } + } + while (css_rules[++j]); + return false; + }, + add_css : function(rule_name, sheet) { + if($.jstree.css.get_css(rule_name, false, sheet)) { return false; } + if(sheet.insertRule) { sheet.insertRule(rule_name + ' { }', 0); } else { sheet.addRule(rule_name, null, 0); } + return $.vakata.css.get_css(rule_name); + }, + remove_css : function(rule_name, sheet) { + return $.vakata.css.get_css(rule_name, true, sheet); + }, + add_sheet : function(opts) { + var tmp = false, is_new = true; + if(opts.str) { + if(opts.title) { tmp = $("style[id='" + opts.title + "-stylesheet']")[0]; } + if(tmp) { is_new = false; } + else { + tmp = document.createElement("style"); + tmp.setAttribute('type',"text/css"); + if(opts.title) { tmp.setAttribute("id", opts.title + "-stylesheet"); } + } + if(tmp.styleSheet) { + if(is_new) { + document.getElementsByTagName("head")[0].appendChild(tmp); + tmp.styleSheet.cssText = opts.str; + } + else { + tmp.styleSheet.cssText = tmp.styleSheet.cssText + " " + opts.str; + } + } + else { + tmp.appendChild(document.createTextNode(opts.str)); + document.getElementsByTagName("head")[0].appendChild(tmp); + } + return tmp.sheet || tmp.styleSheet; + } + if(opts.url) { + if(document.createStyleSheet) { + try { tmp = document.createStyleSheet(opts.url); } catch (e) { } + } + else { + tmp = document.createElement('link'); + tmp.rel = 'stylesheet'; + tmp.type = 'text/css'; + tmp.media = "all"; + tmp.href = opts.url; + document.getElementsByTagName("head")[0].appendChild(tmp); + return tmp.styleSheet; + } + } + } + }; + + // private variables + var instances = [], // instance array (used by $.jstree.reference/create/focused) + focused_instance = -1, // the index in the instance array of the currently focused instance + plugins = {}, // list of included plugins + prepared_move = {}; // for the move_node function + + // jQuery plugin wrapper (thanks to jquery UI widget function) + $.fn.jstree = function (settings) { + var isMethodCall = (typeof settings == 'string'), // is this a method call like $().jstree("open_node") + args = Array.prototype.slice.call(arguments, 1), + returnValue = this; + + // if a method call execute the method on all selected instances + if(isMethodCall) { + if(settings.substring(0, 1) == '_') { return returnValue; } + this.each(function() { + var instance = instances[$.data(this, "jstree_instance_id")], + methodValue = (instance && $.isFunction(instance[settings])) ? instance[settings].apply(instance, args) : instance; + if(typeof methodValue !== "undefined" && (settings.indexOf("is_") === 0 || (methodValue !== true && methodValue !== false))) { returnValue = methodValue; return false; } + }); + } + else { + this.each(function() { + // extend settings and allow for multiple hashes and $.data + var instance_id = $.data(this, "jstree_instance_id"), + a = [], + b = settings ? $.extend({}, true, settings) : {}, + c = $(this), + s = false, + t = []; + a = a.concat(args); + if(c.data("jstree")) { a.push(c.data("jstree")); } + b = a.length ? $.extend.apply(null, [true, b].concat(a)) : b; + + // if an instance already exists, destroy it first + if(typeof instance_id !== "undefined" && instances[instance_id]) { instances[instance_id].destroy(); } + // push a new empty object to the instances array + instance_id = parseInt(instances.push({}),10) - 1; + // store the jstree instance id to the container element + $.data(this, "jstree_instance_id", instance_id); + // clean up all plugins + b.plugins = $.isArray(b.plugins) ? b.plugins : $.jstree.defaults.plugins.slice(); + b.plugins.unshift("core"); + // only unique plugins + b.plugins = b.plugins.sort().join(",,").replace(/(,|^)([^,]+)(,,\2)+(,|$)/g,"$1$2$4").replace(/,,+/g,",").replace(/,$/,"").split(","); + + // extend defaults with passed data + s = $.extend(true, {}, $.jstree.defaults, b); + s.plugins = b.plugins; + $.each(plugins, function (i, val) { + if($.inArray(i, s.plugins) === -1) { s[i] = null; delete s[i]; } + else { t.push(i); } + }); + s.plugins = t; + + // push the new object to the instances array (at the same time set the default classes to the container) and init + instances[instance_id] = new $.jstree._instance(instance_id, $(this).addClass("jstree jstree-" + instance_id), s); + // init all activated plugins for this instance + $.each(instances[instance_id]._get_settings().plugins, function (i, val) { instances[instance_id].data[val] = {}; }); + $.each(instances[instance_id]._get_settings().plugins, function (i, val) { if(plugins[val]) { plugins[val].__init.apply(instances[instance_id]); } }); + // initialize the instance + setTimeout(function() { if(instances[instance_id]) { instances[instance_id].init(); } }, 0); + }); + } + // return the jquery selection (or if it was a method call that returned a value - the returned value) + return returnValue; + }; + // object to store exposed functions and objects + $.jstree = { + defaults : { + plugins : [] + }, + _focused : function () { return instances[focused_instance] || null; }, + _reference : function (needle) { + // get by instance id + if(instances[needle]) { return instances[needle]; } + // get by DOM (if still no luck - return null + var o = $(needle); + if(!o.length && typeof needle === "string") { o = $("#" + needle); } + if(!o.length) { return null; } + return instances[o.closest(".jstree").data("jstree_instance_id")] || null; + }, + _instance : function (index, container, settings) { + // for plugins to store data in + this.data = { core : {} }; + this.get_settings = function () { return $.extend(true, {}, settings); }; + this._get_settings = function () { return settings; }; + this.get_index = function () { return index; }; + this.get_container = function () { return container; }; + this.get_container_ul = function () { return container.children("ul:eq(0)"); }; + this._set_settings = function (s) { + settings = $.extend(true, {}, settings, s); + }; + }, + _fn : { }, + plugin : function (pname, pdata) { + pdata = $.extend({}, { + __init : $.noop, + __destroy : $.noop, + _fn : {}, + defaults : false + }, pdata); + plugins[pname] = pdata; + + $.jstree.defaults[pname] = pdata.defaults; + $.each(pdata._fn, function (i, val) { + val.plugin = pname; + val.old = $.jstree._fn[i]; + $.jstree._fn[i] = function () { + var rslt, + func = val, + args = Array.prototype.slice.call(arguments), + evnt = new $.Event("before.jstree"), + rlbk = false; + + if(this.data.core.locked === true && i !== "unlock" && i !== "is_locked") { return; } + + // Check if function belongs to the included plugins of this instance + do { + if(func && func.plugin && $.inArray(func.plugin, this._get_settings().plugins) !== -1) { break; } + func = func.old; + } while(func); + if(!func) { return; } + + // context and function to trigger events, then finally call the function + if(i.indexOf("_") === 0) { + rslt = func.apply(this, args); + } + else { + rslt = this.get_container().triggerHandler(evnt, { "func" : i, "inst" : this, "args" : args, "plugin" : func.plugin }); + if(rslt === false) { return; } + if(typeof rslt !== "undefined") { args = rslt; } + + rslt = func.apply( + $.extend({}, this, { + __callback : function (data) { + this.get_container().triggerHandler( i + '.jstree', { "inst" : this, "args" : args, "rslt" : data, "rlbk" : rlbk }); + }, + __rollback : function () { + rlbk = this.get_rollback(); + return rlbk; + }, + __call_old : function (replace_arguments) { + return func.old.apply(this, (replace_arguments ? Array.prototype.slice.call(arguments, 1) : args ) ); + } + }), args); + } + + // return the result + return rslt; + }; + $.jstree._fn[i].old = val.old; + $.jstree._fn[i].plugin = pname; + }); + }, + rollback : function (rb) { + if(rb) { + if(!$.isArray(rb)) { rb = [ rb ]; } + $.each(rb, function (i, val) { + instances[val.i].set_rollback(val.h, val.d); + }); + } + } + }; + // set the prototype for all instances + $.jstree._fn = $.jstree._instance.prototype = {}; + + // load the css when DOM is ready + $(function() { + // code is copied from jQuery ($.browser is deprecated + there is a bug in IE) + var u = navigator.userAgent.toLowerCase(), + v = (u.match( /.+?(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1], + css_string = '' + + '.jstree ul, .jstree li { display:block; margin:0 0 0 0; padding:0 0 0 0; list-style-type:none; } ' + + '.jstree li { display:block; min-height:18px; line-height:18px; white-space:nowrap; margin-left:18px; min-width:18px; } ' + + '.jstree-rtl li { margin-left:0; margin-right:18px; } ' + + '.jstree > ul > li { margin-left:0px; } ' + + '.jstree-rtl > ul > li { margin-right:0px; } ' + + '.jstree ins { display:inline-block; text-decoration:none; width:18px; height:18px; margin:0 0 0 0; padding:0; } ' + + '.jstree a { display:inline-block; line-height:16px; height:16px; color:black; white-space:nowrap; text-decoration:none; padding:1px 2px; margin:0; } ' + + '.jstree a:focus { outline: none; } ' + + '.jstree a > ins { height:16px; width:16px; } ' + + '.jstree a > .jstree-icon { margin-right:3px; } ' + + '.jstree-rtl a > .jstree-icon { margin-left:3px; margin-right:0; } ' + + 'li.jstree-open > ul { display:block; } ' + + 'li.jstree-closed > ul { display:none; } '; + // Correct IE 6 (does not support the > CSS selector) + if(/msie/.test(u) && parseInt(v, 10) == 6) { + is_ie6 = true; + + // fix image flicker and lack of caching + try { + document.execCommand("BackgroundImageCache", false, true); + } catch (err) { } + + css_string += '' + + '.jstree li { height:18px; margin-left:0; margin-right:0; } ' + + '.jstree li li { margin-left:18px; } ' + + '.jstree-rtl li li { margin-left:0px; margin-right:18px; } ' + + 'li.jstree-open ul { display:block; } ' + + 'li.jstree-closed ul { display:none !important; } ' + + '.jstree li a { display:inline; border-width:0 !important; padding:0px 2px !important; } ' + + '.jstree li a ins { height:16px; width:16px; margin-right:3px; } ' + + '.jstree-rtl li a ins { margin-right:0px; margin-left:3px; } '; + } + // Correct IE 7 (shifts anchor nodes onhover) + if(/msie/.test(u) && parseInt(v, 10) == 7) { + is_ie7 = true; + css_string += '.jstree li a { border-width:0 !important; padding:0px 2px !important; } '; + } + // correct ff2 lack of display:inline-block + if(!/compatible/.test(u) && /mozilla/.test(u) && parseFloat(v, 10) < 1.9) { + is_ff2 = true; + css_string += '' + + '.jstree ins { display:-moz-inline-box; } ' + + '.jstree li { line-height:12px; } ' + // WHY?? + '.jstree a { display:-moz-inline-box; } ' + + '.jstree .jstree-no-icons .jstree-checkbox { display:-moz-inline-stack !important; } '; + /* this shouldn't be here as it is theme specific */ + } + // the default stylesheet + $.vakata.css.add_sheet({ str : css_string, title : "jstree" }); + }); + + // core functions (open, close, create, update, delete) + $.jstree.plugin("core", { + __init : function () { + this.data.core.locked = false; + this.data.core.to_open = this.get_settings().core.initially_open; + this.data.core.to_load = this.get_settings().core.initially_load; + }, + defaults : { + html_titles : false, + animation : 500, + initially_open : [], + initially_load : [], + open_parents : true, + notify_plugins : true, + rtl : false, + load_open : false, + strings : { + loading : "Loading ...", + new_node : "New node", + multiple_selection : "Multiple selection" + } + }, + _fn : { + init : function () { + this.set_focus(); + if(this._get_settings().core.rtl) { + this.get_container().addClass("jstree-rtl").css("direction", "rtl"); + } + this.get_container().html(""); + this.data.core.li_height = this.get_container_ul().find("li.jstree-closed, li.jstree-leaf").eq(0).height() || 18; + + this.get_container() + .delegate("li > ins", "click.jstree", $.proxy(function (event) { + var trgt = $(event.target); + // if(trgt.is("ins") && event.pageY - trgt.offset().top < this.data.core.li_height) { this.toggle_node(trgt); } + this.toggle_node(trgt); + }, this)) + .bind("mousedown.jstree", $.proxy(function () { + this.set_focus(); // This used to be setTimeout(set_focus,0) - why? + }, this)) + .bind("dblclick.jstree", function (event) { + var sel; + if(document.selection && document.selection.empty) { document.selection.empty(); } + else { + if(window.getSelection) { + sel = window.getSelection(); + try { + sel.removeAllRanges(); + sel.collapse(); + } catch (err) { } + } + } + }); + if(this._get_settings().core.notify_plugins) { + this.get_container() + .bind("load_node.jstree", $.proxy(function (e, data) { + var o = this._get_node(data.rslt.obj), + t = this; + if(o === -1) { o = this.get_container_ul(); } + if(!o.length) { return; } + o.find("li").each(function () { + var th = $(this); + if(th.data("jstree")) { + $.each(th.data("jstree"), function (plugin, values) { + if(t.data[plugin] && $.isFunction(t["_" + plugin + "_notify"])) { + t["_" + plugin + "_notify"].call(t, th, values); + } + }); + } + }); + }, this)); + } + if(this._get_settings().core.load_open) { + this.get_container() + .bind("load_node.jstree", $.proxy(function (e, data) { + var o = this._get_node(data.rslt.obj), + t = this; + if(o === -1) { o = this.get_container_ul(); } + if(!o.length) { return; } + o.find("li.jstree-open:not(:has(ul))").each(function () { + t.load_node(this, $.noop, $.noop); + }); + }, this)); + } + this.__callback(); + this.load_node(-1, function () { this.loaded(); this.reload_nodes(); }); + }, + destroy : function () { + var i, + n = this.get_index(), + s = this._get_settings(), + _this = this; + + $.each(s.plugins, function (i, val) { + try { plugins[val].__destroy.apply(_this); } catch(err) { } + }); + this.__callback(); + // set focus to another instance if this one is focused + if(this.is_focused()) { + for(i in instances) { + if(instances.hasOwnProperty(i) && i != n) { + instances[i].set_focus(); + break; + } + } + } + // if no other instance found + if(n === focused_instance) { focused_instance = -1; } + // remove all traces of jstree in the DOM (only the ones set using jstree*) and cleans all events + this.get_container() + .unbind(".jstree") + .undelegate(".jstree") + .removeData("jstree_instance_id") + .find("[class^='jstree']") + .andSelf() + .attr("class", function () { return this.className.replace(/jstree[^ ]*|$/ig,''); }); + $(document) + .unbind(".jstree-" + n) + .undelegate(".jstree-" + n); + // remove the actual data + instances[n] = null; + delete instances[n]; + }, + + _core_notify : function (n, data) { + if(data.opened) { + this.open_node(n, false, true); + } + }, + + lock : function () { + this.data.core.locked = true; + this.get_container().children("ul").addClass("jstree-locked").css("opacity","0.7"); + this.__callback({}); + }, + unlock : function () { + this.data.core.locked = false; + this.get_container().children("ul").removeClass("jstree-locked").css("opacity","1"); + this.__callback({}); + }, + is_locked : function () { return this.data.core.locked; }, + save_opened : function () { + var _this = this; + this.data.core.to_open = []; + this.get_container_ul().find("li.jstree-open").each(function () { + if(this.id) { _this.data.core.to_open.push("#" + this.id.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:")); } + }); + this.__callback(_this.data.core.to_open); + }, + save_loaded : function () { }, + reload_nodes : function (is_callback) { + var _this = this, + done = true, + current = [], + remaining = []; + if(!is_callback) { + this.data.core.reopen = false; + this.data.core.refreshing = true; + this.data.core.to_open = $.map($.makeArray(this.data.core.to_open), function (n) { return "#" + n.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:"); }); + this.data.core.to_load = $.map($.makeArray(this.data.core.to_load), function (n) { return "#" + n.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:"); }); + if(this.data.core.to_open.length) { + this.data.core.to_load = this.data.core.to_load.concat(this.data.core.to_open); + } + } + if(this.data.core.to_load.length) { + $.each(this.data.core.to_load, function (i, val) { + if(val == "#") { return true; } + if($(val).length) { current.push(val); } + else { remaining.push(val); } + }); + if(current.length) { + this.data.core.to_load = remaining; + $.each(current, function (i, val) { + if(!_this._is_loaded(val)) { + _this.load_node(val, function () { _this.reload_nodes(true); }, function () { _this.reload_nodes(true); }); + done = false; + } + }); + } + } + if(this.data.core.to_open.length) { + $.each(this.data.core.to_open, function (i, val) { + _this.open_node(val, false, true); + }); + } + if(done) { + // TODO: find a more elegant approach to syncronizing returning requests + if(this.data.core.reopen) { clearTimeout(this.data.core.reopen); } + this.data.core.reopen = setTimeout(function () { _this.__callback({}, _this); }, 50); + this.data.core.refreshing = false; + this.reopen(); + } + }, + reopen : function () { + var _this = this; + if(this.data.core.to_open.length) { + $.each(this.data.core.to_open, function (i, val) { + _this.open_node(val, false, true); + }); + } + this.__callback({}); + }, + refresh : function (obj) { + var _this = this; + this.save_opened(); + if(!obj) { obj = -1; } + obj = this._get_node(obj); + if(!obj) { obj = -1; } + if(obj !== -1) { obj.children("UL").remove(); } + else { this.get_container_ul().empty(); } + this.load_node(obj, function () { _this.__callback({ "obj" : obj}); _this.reload_nodes(); }); + }, + // Dummy function to fire after the first load (so that there is a jstree.loaded event) + loaded : function () { + this.__callback(); + }, + // deal with focus + set_focus : function () { + if(this.is_focused()) { return; } + var f = $.jstree._focused(); + if(f) { f.unset_focus(); } + + this.get_container().addClass("jstree-focused"); + focused_instance = this.get_index(); + this.__callback(); + }, + is_focused : function () { + return focused_instance == this.get_index(); + }, + unset_focus : function () { + if(this.is_focused()) { + this.get_container().removeClass("jstree-focused"); + focused_instance = -1; + } + this.__callback(); + }, + + // traverse + _get_node : function (obj) { + var $obj = $(obj, this.get_container()); + if($obj.is(".jstree") || obj == -1) { return -1; } + $obj = $obj.closest("li", this.get_container()); + return $obj.length ? $obj : false; + }, + _get_next : function (obj, strict) { + obj = this._get_node(obj); + if(obj === -1) { return this.get_container().find("> ul > li:first-child"); } + if(!obj.length) { return false; } + if(strict) { return (obj.nextAll("li").size() > 0) ? obj.nextAll("li:eq(0)") : false; } + + if(obj.hasClass("jstree-open")) { return obj.find("li:eq(0)"); } + else if(obj.nextAll("li").size() > 0) { return obj.nextAll("li:eq(0)"); } + else { return obj.parentsUntil(".jstree","li").next("li").eq(0); } + }, + _get_prev : function (obj, strict) { + obj = this._get_node(obj); + if(obj === -1) { return this.get_container().find("> ul > li:last-child"); } + if(!obj.length) { return false; } + if(strict) { return (obj.prevAll("li").length > 0) ? obj.prevAll("li:eq(0)") : false; } + + if(obj.prev("li").length) { + obj = obj.prev("li").eq(0); + while(obj.hasClass("jstree-open")) { obj = obj.children("ul:eq(0)").children("li:last"); } + return obj; + } + else { var o = obj.parentsUntil(".jstree","li:eq(0)"); return o.length ? o : false; } + }, + _get_parent : function (obj) { + obj = this._get_node(obj); + if(obj == -1 || !obj.length) { return false; } + var o = obj.parentsUntil(".jstree", "li:eq(0)"); + return o.length ? o : -1; + }, + _get_children : function (obj) { + obj = this._get_node(obj); + if(obj === -1) { return this.get_container().children("ul:eq(0)").children("li"); } + if(!obj.length) { return false; } + return obj.children("ul:eq(0)").children("li"); + }, + get_path : function (obj, id_mode) { + var p = [], + _this = this; + obj = this._get_node(obj); + if(obj === -1 || !obj || !obj.length) { return false; } + obj.parentsUntil(".jstree", "li").each(function () { + p.push( id_mode ? this.id : _this.get_text(this) ); + }); + p.reverse(); + p.push( id_mode ? obj.attr("id") : this.get_text(obj) ); + return p; + }, + + // string functions + _get_string : function (key) { + return this._get_settings().core.strings[key] || key; + }, + + is_open : function (obj) { obj = this._get_node(obj); return obj && obj !== -1 && obj.hasClass("jstree-open"); }, + is_closed : function (obj) { obj = this._get_node(obj); return obj && obj !== -1 && obj.hasClass("jstree-closed"); }, + is_leaf : function (obj) { obj = this._get_node(obj); return obj && obj !== -1 && obj.hasClass("jstree-leaf"); }, + correct_state : function (obj) { + obj = this._get_node(obj); + if(!obj || obj === -1) { return false; } + obj.removeClass("jstree-closed jstree-open").addClass("jstree-leaf").children("ul").remove(); + this.__callback({ "obj" : obj }); + }, + // open/close + open_node : function (obj, callback, skip_animation) { + obj = this._get_node(obj); + if(!obj.length) { return false; } + if(!obj.hasClass("jstree-closed")) { if(callback) { callback.call(); } return false; } + var s = skip_animation || is_ie6 ? 0 : this._get_settings().core.animation, + t = this; + if(!this._is_loaded(obj)) { + obj.children("a").addClass("jstree-loading"); + this.load_node(obj, function () { t.open_node(obj, callback, skip_animation); }, callback); + } + else { + if(this._get_settings().core.open_parents) { + obj.parentsUntil(".jstree",".jstree-closed").each(function () { + t.open_node(this, false, true); + }); + } + if(s) { obj.children("ul").css("display","none"); } + obj.removeClass("jstree-closed").addClass("jstree-open").children("a").removeClass("jstree-loading"); + if(s) { obj.children("ul").stop(true, true).slideDown(s, function () { this.style.display = ""; t.after_open(obj); }); } + else { t.after_open(obj); } + this.__callback({ "obj" : obj }); + if(callback) { callback.call(); } + } + }, + after_open : function (obj) { this.__callback({ "obj" : obj }); }, + close_node : function (obj, skip_animation) { + obj = this._get_node(obj); + var s = skip_animation || is_ie6 ? 0 : this._get_settings().core.animation, + t = this; + if(!obj.length || !obj.hasClass("jstree-open")) { return false; } + if(s) { obj.children("ul").attr("style","display:block !important"); } + obj.removeClass("jstree-open").addClass("jstree-closed"); + if(s) { obj.children("ul").stop(true, true).slideUp(s, function () { this.style.display = ""; t.after_close(obj); }); } + else { t.after_close(obj); } + this.__callback({ "obj" : obj }); + }, + after_close : function (obj) { this.__callback({ "obj" : obj }); }, + toggle_node : function (obj) { + obj = this._get_node(obj); + if(obj.hasClass("jstree-closed")) { return this.open_node(obj); } + if(obj.hasClass("jstree-open")) { return this.close_node(obj); } + }, + open_all : function (obj, do_animation, original_obj) { + obj = obj ? this._get_node(obj) : -1; + if(!obj || obj === -1) { obj = this.get_container_ul(); } + if(original_obj) { + obj = obj.find("li.jstree-closed"); + } + else { + original_obj = obj; + if(obj.is(".jstree-closed")) { obj = obj.find("li.jstree-closed").andSelf(); } + else { obj = obj.find("li.jstree-closed"); } + } + var _this = this; + obj.each(function () { + var __this = this; + if(!_this._is_loaded(this)) { _this.open_node(this, function() { _this.open_all(__this, do_animation, original_obj); }, !do_animation); } + else { _this.open_node(this, false, !do_animation); } + }); + // so that callback is fired AFTER all nodes are open + if(original_obj.find('li.jstree-closed').length === 0) { this.__callback({ "obj" : original_obj }); } + }, + close_all : function (obj, do_animation) { + var _this = this; + obj = obj ? this._get_node(obj) : this.get_container(); + if(!obj || obj === -1) { obj = this.get_container_ul(); } + obj.find("li.jstree-open").andSelf().each(function () { _this.close_node(this, !do_animation); }); + this.__callback({ "obj" : obj }); + }, + clean_node : function (obj) { + obj = obj && obj != -1 ? $(obj) : this.get_container_ul(); + obj = obj.is("li") ? obj.find("li").andSelf() : obj.find("li"); + obj.removeClass("jstree-last") + .filter("li:last-child").addClass("jstree-last").end() + .filter(":has(li)") + .not(".jstree-open").removeClass("jstree-leaf").addClass("jstree-closed"); + obj.not(".jstree-open, .jstree-closed").addClass("jstree-leaf").children("ul").remove(); + this.__callback({ "obj" : obj }); + }, + // rollback + get_rollback : function () { + this.__callback(); + return { i : this.get_index(), h : this.get_container().children("ul").clone(true), d : this.data }; + }, + set_rollback : function (html, data) { + this.get_container().empty().append(html); + this.data = data; + this.__callback(); + }, + // Dummy functions to be overwritten by any datastore plugin included + load_node : function (obj, s_call, e_call) { this.__callback({ "obj" : obj }); }, + _is_loaded : function (obj) { return true; }, + + // Basic operations: create + create_node : function (obj, position, js, callback, is_loaded) { + obj = this._get_node(obj); + position = typeof position === "undefined" ? "last" : position; + var d = $("
  • "), + s = this._get_settings().core, + tmp; + + if(obj !== -1 && !obj.length) { return false; } + if(!is_loaded && !this._is_loaded(obj)) { this.load_node(obj, function () { this.create_node(obj, position, js, callback, true); }); return false; } + + this.__rollback(); + + if(typeof js === "string") { js = { "data" : js }; } + if(!js) { js = {}; } + if(js.attr) { d.attr(js.attr); } + if(js.metadata) { d.data(js.metadata); } + if(js.state) { d.addClass("jstree-" + js.state); } + if(!js.data) { js.data = this._get_string("new_node"); } + if(!$.isArray(js.data)) { tmp = js.data; js.data = []; js.data.push(tmp); } + $.each(js.data, function (i, m) { + tmp = $(""); + if($.isFunction(m)) { m = m.call(this, js); } + if(typeof m == "string") { tmp.attr('href','#')[ s.html_titles ? "html" : "text" ](m); } + else { + if(!m.attr) { m.attr = {}; } + if(!m.attr.href) { m.attr.href = '#'; } + tmp.attr(m.attr)[ s.html_titles ? "html" : "text" ](m.title); + if(m.language) { tmp.addClass(m.language); } + } + tmp.prepend(" "); + if(!m.icon && js.icon) { m.icon = js.icon; } + if(m.icon) { + if(m.icon.indexOf("/") === -1) { tmp.children("ins").addClass(m.icon); } + else { tmp.children("ins").css("background","url('" + m.icon + "') center center no-repeat"); } + } + d.append(tmp); + }); + d.prepend(" "); + if(obj === -1) { + obj = this.get_container(); + if(position === "before") { position = "first"; } + if(position === "after") { position = "last"; } + } + switch(position) { + case "before": obj.before(d); tmp = this._get_parent(obj); break; + case "after" : obj.after(d); tmp = this._get_parent(obj); break; + case "inside": + case "first" : + if(!obj.children("ul").length) { obj.append("