summaryrefslogtreecommitdiff
path: root/www/wiki/vendor/onoi
diff options
context:
space:
mode:
authorYaco <franco@reevo.org>2020-06-04 11:01:00 -0300
committerYaco <franco@reevo.org>2020-06-04 11:01:00 -0300
commitfc7369835258467bf97eb64f184b93691f9a9fd5 (patch)
treedaabd60089d2dd76d9f5fb416b005fbe159c799d /www/wiki/vendor/onoi
first commit
Diffstat (limited to 'www/wiki/vendor/onoi')
-rw-r--r--www/wiki/vendor/onoi/blob-store/.gitignore10
-rw-r--r--www/wiki/vendor/onoi/blob-store/.scrutinizer.yml18
-rw-r--r--www/wiki/vendor/onoi/blob-store/.travis.yml22
-rw-r--r--www/wiki/vendor/onoi/blob-store/LICENSE340
-rw-r--r--www/wiki/vendor/onoi/blob-store/README.md119
-rw-r--r--www/wiki/vendor/onoi/blob-store/composer.json35
-rw-r--r--www/wiki/vendor/onoi/blob-store/phpunit.xml.dist27
-rw-r--r--www/wiki/vendor/onoi/blob-store/src/BlobStore.php223
-rw-r--r--www/wiki/vendor/onoi/blob-store/src/Container.php188
-rw-r--r--www/wiki/vendor/onoi/blob-store/tests/bootstrap.php27
-rw-r--r--www/wiki/vendor/onoi/blob-store/tests/phpunit/Integration/BlobStoreIntegrationTestCase.php106
-rw-r--r--www/wiki/vendor/onoi/blob-store/tests/phpunit/Integration/DoctrineRedisIntegrationTest.php45
-rw-r--r--www/wiki/vendor/onoi/blob-store/tests/phpunit/Integration/FixedInMemoryIntegrationTest.php28
-rw-r--r--www/wiki/vendor/onoi/blob-store/tests/phpunit/Unit/BlobStoreTest.php245
-rw-r--r--www/wiki/vendor/onoi/blob-store/tests/phpunit/Unit/ContainerTest.php146
-rw-r--r--www/wiki/vendor/onoi/blob-store/tests/travis/run-tests.sh19
-rw-r--r--www/wiki/vendor/onoi/blob-store/tests/travis/upload-coverage-report.sh10
-rw-r--r--www/wiki/vendor/onoi/cache/.gitignore10
-rw-r--r--www/wiki/vendor/onoi/cache/.scrutinizer.yml18
-rw-r--r--www/wiki/vendor/onoi/cache/.travis.yml25
-rw-r--r--www/wiki/vendor/onoi/cache/LICENSE340
-rw-r--r--www/wiki/vendor/onoi/cache/README.md107
-rw-r--r--www/wiki/vendor/onoi/cache/composer.json35
-rw-r--r--www/wiki/vendor/onoi/cache/phpunit.xml.dist27
-rw-r--r--www/wiki/vendor/onoi/cache/src/Cache.php74
-rw-r--r--www/wiki/vendor/onoi/cache/src/CacheFactory.php116
-rw-r--r--www/wiki/vendor/onoi/cache/src/CompositeCache.php147
-rw-r--r--www/wiki/vendor/onoi/cache/src/DoctrineCache.php85
-rw-r--r--www/wiki/vendor/onoi/cache/src/FixedInMemoryLruCache.php177
-rw-r--r--www/wiki/vendor/onoi/cache/src/MediaWikiCache.php137
-rw-r--r--www/wiki/vendor/onoi/cache/src/NullCache.php63
-rw-r--r--www/wiki/vendor/onoi/cache/src/ZendCache.php139
-rw-r--r--www/wiki/vendor/onoi/cache/tests/bootstrap.php27
-rw-r--r--www/wiki/vendor/onoi/cache/tests/phpunit/Integration/CacheIntegrationTestCase.php41
-rw-r--r--www/wiki/vendor/onoi/cache/tests/phpunit/Integration/CompositeCacheIntegrationTest.php39
-rw-r--r--www/wiki/vendor/onoi/cache/tests/phpunit/Integration/DoctrineCacheIntegrationTest.php29
-rw-r--r--www/wiki/vendor/onoi/cache/tests/phpunit/Integration/MediaWikiCacheIntegrationTest.php29
-rw-r--r--www/wiki/vendor/onoi/cache/tests/phpunit/Integration/ZendCacheIntegrationTest.php39
-rw-r--r--www/wiki/vendor/onoi/cache/tests/phpunit/Unit/CacheFactoryTest.php143
-rw-r--r--www/wiki/vendor/onoi/cache/tests/phpunit/Unit/CompositeCacheTest.php251
-rw-r--r--www/wiki/vendor/onoi/cache/tests/phpunit/Unit/DoctrineCacheTest.php115
-rw-r--r--www/wiki/vendor/onoi/cache/tests/phpunit/Unit/FixedInMemoryLruCacheTest.php152
-rw-r--r--www/wiki/vendor/onoi/cache/tests/phpunit/Unit/MediaWikiCacheTest.php180
-rw-r--r--www/wiki/vendor/onoi/cache/tests/phpunit/Unit/NullCacheTest.php83
-rw-r--r--www/wiki/vendor/onoi/cache/tests/phpunit/Unit/ZendCacheTest.php196
-rw-r--r--www/wiki/vendor/onoi/cache/tests/travis/install-cache.sh50
-rw-r--r--www/wiki/vendor/onoi/cache/tests/travis/install-mediawiki.sh41
-rw-r--r--www/wiki/vendor/onoi/cache/tests/travis/run-tests.sh19
-rw-r--r--www/wiki/vendor/onoi/cache/tests/travis/upload-coverage-report.sh10
-rw-r--r--www/wiki/vendor/onoi/callback-container/.gitignore10
-rw-r--r--www/wiki/vendor/onoi/callback-container/.scrutinizer.yml18
-rw-r--r--www/wiki/vendor/onoi/callback-container/.travis.yml23
-rw-r--r--www/wiki/vendor/onoi/callback-container/LICENSE340
-rw-r--r--www/wiki/vendor/onoi/callback-container/README.md91
-rw-r--r--www/wiki/vendor/onoi/callback-container/composer.json36
-rw-r--r--www/wiki/vendor/onoi/callback-container/docs/usage.md93
-rw-r--r--www/wiki/vendor/onoi/callback-container/phpunit.xml.dist24
-rw-r--r--www/wiki/vendor/onoi/callback-container/src/CallbackContainer.php24
-rw-r--r--www/wiki/vendor/onoi/callback-container/src/CallbackContainerBuilder.php298
-rw-r--r--www/wiki/vendor/onoi/callback-container/src/CallbackContainerFactory.php49
-rw-r--r--www/wiki/vendor/onoi/callback-container/src/ContainerBuilder.php53
-rw-r--r--www/wiki/vendor/onoi/callback-container/src/ContainerRegistry.php27
-rw-r--r--www/wiki/vendor/onoi/callback-container/src/Exception/FileNotFoundException.php15
-rw-r--r--www/wiki/vendor/onoi/callback-container/src/Exception/InvalidParameterTypeException.php15
-rw-r--r--www/wiki/vendor/onoi/callback-container/src/Exception/ServiceAliasAssignmentException.php24
-rw-r--r--www/wiki/vendor/onoi/callback-container/src/Exception/ServiceAliasCrossAssignmentException.php26
-rw-r--r--www/wiki/vendor/onoi/callback-container/src/Exception/ServiceAliasMismatchException.php24
-rw-r--r--www/wiki/vendor/onoi/callback-container/src/Exception/ServiceCircularReferenceException.php24
-rw-r--r--www/wiki/vendor/onoi/callback-container/src/Exception/ServiceNotFoundException.php15
-rw-r--r--www/wiki/vendor/onoi/callback-container/src/Exception/ServiceTypeMismatchException.php26
-rw-r--r--www/wiki/vendor/onoi/callback-container/src/NullContainerBuilder.php76
-rw-r--r--www/wiki/vendor/onoi/callback-container/src/ServiceRegistry.php52
-rw-r--r--www/wiki/vendor/onoi/callback-container/src/ServicesManager.php104
-rw-r--r--www/wiki/vendor/onoi/callback-container/tests/bootstrap.php27
-rw-r--r--www/wiki/vendor/onoi/callback-container/tests/phpunit/Fixtures/FakeCallbackContainer.php59
-rw-r--r--www/wiki/vendor/onoi/callback-container/tests/phpunit/Fixtures/fakeCallbackFromFile.php63
-rw-r--r--www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/CallbackContainerBuilderTest.php533
-rw-r--r--www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/CallbackContainerFactoryTest.php65
-rw-r--r--www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/Exception/ServiceAliasAssignmentExceptionTest.php31
-rw-r--r--www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/Exception/ServiceAliasCrossAssignmentExceptionTest.php31
-rw-r--r--www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/Exception/ServiceAliasMismatchExceptionTest.php31
-rw-r--r--www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/Exception/ServiceCircularReferenceExceptionTest.php31
-rw-r--r--www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/Exception/ServiceTypeMismatchExceptionTest.php31
-rw-r--r--www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/NullContainerBuilderTest.php67
-rw-r--r--www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/ServicesManagerTest.php126
-rw-r--r--www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/SpyLogger.php40
-rw-r--r--www/wiki/vendor/onoi/callback-container/tests/travis/run-tests.sh15
-rw-r--r--www/wiki/vendor/onoi/callback-container/tests/travis/upload-coverage-report.sh10
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/.gitignore10
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/.scrutinizer.yml18
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/.travis.yml17
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/LICENSE340
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/README.md133
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/composer.json35
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/phpunit.xml.dist27
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/src/DispatchContext.php87
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/src/Dispatcher/GenericEventDispatcher.php152
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/src/EventDispatcher.php36
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/src/EventDispatcherAwareTrait.php27
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/src/EventDispatcherFactory.php89
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/src/EventListener.php36
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/src/EventListenerCollection.php22
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/src/Exception/EventNotDispatchableException.php24
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/src/Listener/GenericCallbackEventListener.php84
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/src/Listener/GenericEventListenerCollection.php82
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/src/Listener/NullEventListener.php32
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/src/Subscriber.php44
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/tests/bootstrap.php25
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Integration/EventRegistryDispatchTest.php138
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/DispatchContextTest.php91
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/Dispatcher/GenericEventDispatcherTest.php262
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/EventDispatcherFactoryTest.php86
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/Exception/EventNotDispatchableExceptionTest.php26
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/Listener/GenericCallbackEventListenerTest.php124
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/Listener/GenericEventListenerCollectionTest.php103
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/Listener/NullEventListenerTest.php50
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/tests/travis/run-tests.sh15
-rw-r--r--www/wiki/vendor/onoi/event-dispatcher/tests/travis/upload-coverage-report.sh10
-rw-r--r--www/wiki/vendor/onoi/http-request/.gitignore10
-rw-r--r--www/wiki/vendor/onoi/http-request/.scrutinizer.yml27
-rw-r--r--www/wiki/vendor/onoi/http-request/.travis.yml18
-rw-r--r--www/wiki/vendor/onoi/http-request/LICENSE340
-rw-r--r--www/wiki/vendor/onoi/http-request/README.md148
-rw-r--r--www/wiki/vendor/onoi/http-request/composer.json40
-rw-r--r--www/wiki/vendor/onoi/http-request/phpunit.xml.dist32
-rw-r--r--www/wiki/vendor/onoi/http-request/src/CachedCurlRequest.php143
-rw-r--r--www/wiki/vendor/onoi/http-request/src/CurlRequest.php155
-rw-r--r--www/wiki/vendor/onoi/http-request/src/Exception/BadHttpResponseException.php36
-rw-r--r--www/wiki/vendor/onoi/http-request/src/Exception/HttpConnectionException.php23
-rw-r--r--www/wiki/vendor/onoi/http-request/src/HttpRequest.php94
-rw-r--r--www/wiki/vendor/onoi/http-request/src/HttpRequestFactory.php85
-rw-r--r--www/wiki/vendor/onoi/http-request/src/MultiCurlRequest.php238
-rw-r--r--www/wiki/vendor/onoi/http-request/src/NullRequest.php70
-rw-r--r--www/wiki/vendor/onoi/http-request/src/RequestResponse.php85
-rw-r--r--www/wiki/vendor/onoi/http-request/src/SocketRequest.php309
-rw-r--r--www/wiki/vendor/onoi/http-request/tests/bootstrap.php27
-rw-r--r--www/wiki/vendor/onoi/http-request/tests/phpunit/Integration/RequestToExternalUrlTest.php59
-rw-r--r--www/wiki/vendor/onoi/http-request/tests/phpunit/Integration/RequestToPhpHttpdTest.php185
-rw-r--r--www/wiki/vendor/onoi/http-request/tests/phpunit/Integration/http-public/fast.php4
-rw-r--r--www/wiki/vendor/onoi/http-request/tests/phpunit/Integration/http-public/plain.php4
-rw-r--r--www/wiki/vendor/onoi/http-request/tests/phpunit/Integration/http-public/slow.php4
-rw-r--r--www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/CachedCurlRequestTest.php185
-rw-r--r--www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/CurlRequestTest.php107
-rw-r--r--www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/Exception/BadHttpResponseExceptionTest.php82
-rw-r--r--www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/Exception/HttpConnectionExceptionTest.php37
-rw-r--r--www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/HttpRequestFactoryTest.php82
-rw-r--r--www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/MockHttpStreamWrapper.php88
-rw-r--r--www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/MockedHttpStreamSocketRequestTest.php153
-rw-r--r--www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/MultiCurlRequestTest.php190
-rw-r--r--www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/NullRequestTest.php73
-rw-r--r--www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/RequestResponseTest.php60
-rw-r--r--www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/SocketRequestTest.php224
-rw-r--r--www/wiki/vendor/onoi/http-request/tests/travis/run-tests.sh15
-rw-r--r--www/wiki/vendor/onoi/http-request/tests/travis/upload-coverage-report.sh10
-rw-r--r--www/wiki/vendor/onoi/message-reporter/LICENSE340
-rw-r--r--www/wiki/vendor/onoi/message-reporter/Makefile22
-rw-r--r--www/wiki/vendor/onoi/message-reporter/README.md147
-rw-r--r--www/wiki/vendor/onoi/message-reporter/src/CallbackMessageReporter.php20
-rw-r--r--www/wiki/vendor/onoi/message-reporter/src/MessageReporter.php22
-rw-r--r--www/wiki/vendor/onoi/message-reporter/src/MessageReporterAware.php19
-rw-r--r--www/wiki/vendor/onoi/message-reporter/src/MessageReporterAwareTrait.php22
-rw-r--r--www/wiki/vendor/onoi/message-reporter/src/MessageReporterFactory.php62
-rw-r--r--www/wiki/vendor/onoi/message-reporter/src/NullMessageReporter.php20
-rw-r--r--www/wiki/vendor/onoi/message-reporter/src/ObservableMessageReporter.php73
-rw-r--r--www/wiki/vendor/onoi/message-reporter/src/SpyMessageReporter.php52
-rw-r--r--www/wiki/vendor/onoi/shared-resources/.gitignore11
-rw-r--r--www/wiki/vendor/onoi/shared-resources/LICENSE340
-rw-r--r--www/wiki/vendor/onoi/shared-resources/README.md115
-rw-r--r--www/wiki/vendor/onoi/shared-resources/Resources.php316
-rw-r--r--www/wiki/vendor/onoi/shared-resources/composer.json36
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/bootstrap/bootstrap.4.mediawiki.tab.css10
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/bootstrap/bootstrap.4.tab.css300
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/bootstrap/bootstrap.4.tab.js259
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/bootstrap/bootstrap.4.util.js175
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/clipboard/clipboard.js778
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/jStorage/jstorage.js996
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/jquery.async/jquery.async.js69
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/jquery.blockUI/jquery.blockUI.js620
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/dataTables.responsive.css178
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/dataTables.responsive.js1255
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/dataTables.search.js77
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/dataTables.searchHighlight.css11
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/dataTables.searchHighlight.js87
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/jquery.dataTables.css459
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/jquery.dataTables.js15345
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/jquery.dataTables.min.js167
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/jquery.highlight/jquery.highlight.js151
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/jquery.qtip/core/jquery.qtip.css124
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/jquery.qtip/core/jquery.qtip.js2016
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/jquery.qtip/extended/jquery.qtip.css557
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/jquery.qtip/extended/jquery.qtip.js2330
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/jquery.rangeSlider/ion.rangeSlider.css146
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/jquery.rangeSlider/ion.rangeSlider.js2307
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/jquery.rangeSlider/ion.rangeSlider.skinFlat.css106
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/jquery.rangeSlider/ion.rangeSlider.skinModern.css116
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/localForage/localforage.js2313
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/localForage/localforage.min.js7
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/md5/jquery.md5.js275
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/md5/md5.js279
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/onoi.blobstore.js173
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/onoi.clipboard.js29
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/onoi.util.css21
-rw-r--r--www/wiki/vendor/onoi/shared-resources/res/onoi.util.js98
-rw-r--r--www/wiki/vendor/onoi/tesa/.gitignore10
-rw-r--r--www/wiki/vendor/onoi/tesa/.scrutinizer.yml18
-rw-r--r--www/wiki/vendor/onoi/tesa/.travis.yml21
-rw-r--r--www/wiki/vendor/onoi/tesa/LICENSE340
-rw-r--r--www/wiki/vendor/onoi/tesa/README.md118
-rw-r--r--www/wiki/vendor/onoi/tesa/composer.json33
-rw-r--r--www/wiki/vendor/onoi/tesa/phpunit.xml.dist27
-rw-r--r--www/wiki/vendor/onoi/tesa/src/CharacterExaminer.php62
-rw-r--r--www/wiki/vendor/onoi/tesa/src/LanguageDetector/LanguageDetector.php22
-rw-r--r--www/wiki/vendor/onoi/tesa/src/LanguageDetector/NullLanguageDetector.php24
-rw-r--r--www/wiki/vendor/onoi/tesa/src/LanguageDetector/TextCatLanguageDetector.php65
-rw-r--r--www/wiki/vendor/onoi/tesa/src/Normalizer.php103
-rw-r--r--www/wiki/vendor/onoi/tesa/src/Sanitizer.php178
-rw-r--r--www/wiki/vendor/onoi/tesa/src/SanitizerFactory.php257
-rw-r--r--www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/ArrayStopwordAnalyzer.php45
-rw-r--r--www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/CdbStopwordAnalyzer.php126
-rw-r--r--www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/NullStopwordAnalyzer.php24
-rw-r--r--www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/README.md40
-rw-r--r--www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/StopwordAnalyzer.php22
-rw-r--r--www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/de.cdbbin0 -> 12724 bytes
-rw-r--r--www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/en.cdbbin0 -> 32148 bytes
-rw-r--r--www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/es.cdbbin0 -> 7487 bytes
-rw-r--r--www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/fr.cdbbin0 -> 5435 bytes
-rw-r--r--www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/ja.cdbbin0 -> 5472 bytes
-rw-r--r--www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/pt-br.cdbbin0 -> 5759 bytes
-rw-r--r--www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/pt.cdbbin0 -> 6393 bytes
-rw-r--r--www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/zh.cdbbin0 -> 18352 bytes
-rw-r--r--www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/de.json366
-rw-r--r--www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/en.json1009
-rw-r--r--www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/es.json185
-rw-r--r--www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/fr.json122
-rw-r--r--www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/ja.json115
-rw-r--r--www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/pt-br.json132
-rw-r--r--www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/pt.json151
-rw-r--r--www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/zh.json5
-rw-r--r--www/wiki/vendor/onoi/tesa/src/Synonymizer/NullSynonymizer.php26
-rw-r--r--www/wiki/vendor/onoi/tesa/src/Synonymizer/Synonymizer.php22
-rw-r--r--www/wiki/vendor/onoi/tesa/src/Tokenizer/CJKSimpleCharacterRegExTokenizer.php84
-rw-r--r--www/wiki/vendor/onoi/tesa/src/Tokenizer/GenericRegExTokenizer.php87
-rw-r--r--www/wiki/vendor/onoi/tesa/src/Tokenizer/IcuWordBoundaryTokenizer.php133
-rw-r--r--www/wiki/vendor/onoi/tesa/src/Tokenizer/JaCompoundGroupTokenizer.php228
-rw-r--r--www/wiki/vendor/onoi/tesa/src/Tokenizer/JaTinySegmenterTokenizer.php267
-rw-r--r--www/wiki/vendor/onoi/tesa/src/Tokenizer/NGramTokenizer.php129
-rw-r--r--www/wiki/vendor/onoi/tesa/src/Tokenizer/PunctuationRegExTokenizer.php85
-rw-r--r--www/wiki/vendor/onoi/tesa/src/Tokenizer/Tokenizer.php46
-rw-r--r--www/wiki/vendor/onoi/tesa/src/Tokenizer/model/rwcp.model.json45
-rw-r--r--www/wiki/vendor/onoi/tesa/src/Transliterator.php302
-rw-r--r--www/wiki/vendor/onoi/tesa/tests/bootstrap.php30
-rw-r--r--www/wiki/vendor/onoi/tesa/tests/phpunit/Fixtures/StopwordAnalyzer/invalid.json5
-rw-r--r--www/wiki/vendor/onoi/tesa/tests/phpunit/Fixtures/StopwordAnalyzer/missingindex.json4
-rw-r--r--www/wiki/vendor/onoi/tesa/tests/phpunit/Integration/CombinedNGramTokenizerTest.php109
-rw-r--r--www/wiki/vendor/onoi/tesa/tests/phpunit/Integration/CombinedSanitizerTextStopwordTest.php102
-rw-r--r--www/wiki/vendor/onoi/tesa/tests/phpunit/Integration/JaTokenizerTest.php118
-rw-r--r--www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/CharacterExaminerTest.php75
-rw-r--r--www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/LanguageDetector/NullLanguageDetectorTest.php35
-rw-r--r--www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/LanguageDetector/TextCatLanguageDetectorTest.php49
-rw-r--r--www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/NormalizerTest.php75
-rw-r--r--www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/SanitizerFactoryTest.php194
-rw-r--r--www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/SanitizerTest.php222
-rw-r--r--www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/StopwordAnalyzer/ArrayStopwordAnalyzerTest.php64
-rw-r--r--www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/StopwordAnalyzer/CdbStopwordAnalyzerTest.php168
-rw-r--r--www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/StopwordAnalyzer/NullStopwordAnalyzerTest.php35
-rw-r--r--www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/CJKSimpleCharacterRegExTokenizerTest.php87
-rw-r--r--www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/GenericRegExTokenizerTest.php94
-rw-r--r--www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/IcuWordBoundaryTokenizerTest.php173
-rw-r--r--www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/JaCompoundGroupTokenizerTest.php90
-rw-r--r--www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/JaTinySegmenterTokenizerTest.php191
-rw-r--r--www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/NGramTokenizerTest.php224
-rw-r--r--www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/PunctuationRegExTokenizerTest.php117
-rw-r--r--www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/TransliteratorTest.php162
-rw-r--r--www/wiki/vendor/onoi/tesa/tests/travis/run-tests.sh15
-rw-r--r--www/wiki/vendor/onoi/tesa/tests/travis/upload-coverage-report.sh10
275 files changed, 53839 insertions, 0 deletions
diff --git a/www/wiki/vendor/onoi/blob-store/.gitignore b/www/wiki/vendor/onoi/blob-store/.gitignore
new file mode 100644
index 00000000..09ec1c53
--- /dev/null
+++ b/www/wiki/vendor/onoi/blob-store/.gitignore
@@ -0,0 +1,10 @@
+*~
+*.kate-swp
+
+!.*
+.idea/
+
+composer.phar
+composer.lock
+
+vendor/ \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/blob-store/.scrutinizer.yml b/www/wiki/vendor/onoi/blob-store/.scrutinizer.yml
new file mode 100644
index 00000000..9358405a
--- /dev/null
+++ b/www/wiki/vendor/onoi/blob-store/.scrutinizer.yml
@@ -0,0 +1,18 @@
+filter:
+ excluded_paths:
+ - 'vendor/*'
+
+tools:
+ php_mess_detector:
+ config:
+ controversial_rules: { superglobals: false }
+ php_cpd: true
+ php_pdepend: true
+ php_code_coverage: false
+ php_code_sniffer: true
+ php_cs_fixer: true
+ php_loc: true
+ php_analyzer: true
+ sensiolabs_security_checker: true
+ external_code_coverage:
+ timeout: '900'
diff --git a/www/wiki/vendor/onoi/blob-store/.travis.yml b/www/wiki/vendor/onoi/blob-store/.travis.yml
new file mode 100644
index 00000000..dccf9bb5
--- /dev/null
+++ b/www/wiki/vendor/onoi/blob-store/.travis.yml
@@ -0,0 +1,22 @@
+language: php
+
+matrix:
+ include:
+ - env: TYPE=coverage
+ php: 5.6
+ - env: DOCTRINE=1.4.0;
+ php: 5.4
+ - env: TYPE=UNIT;
+ php: hhvm
+
+services:
+ - redis-server
+
+before_script:
+ - if [ -e ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini ]; then echo "extension = redis.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi
+
+script:
+ - bash ./tests/travis/run-tests.sh
+
+after_success:
+ - bash ./tests/travis/upload-coverage-report.sh
diff --git a/www/wiki/vendor/onoi/blob-store/LICENSE b/www/wiki/vendor/onoi/blob-store/LICENSE
new file mode 100644
index 00000000..d6a93266
--- /dev/null
+++ b/www/wiki/vendor/onoi/blob-store/LICENSE
@@ -0,0 +1,340 @@
+GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ {description}
+ Copyright (C) {year} {fullname}
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ {signature of Ty Coon}, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
+
diff --git a/www/wiki/vendor/onoi/blob-store/README.md b/www/wiki/vendor/onoi/blob-store/README.md
new file mode 100644
index 00000000..97b2b4eb
--- /dev/null
+++ b/www/wiki/vendor/onoi/blob-store/README.md
@@ -0,0 +1,119 @@
+# Blob store
+
+[![Build Status](https://secure.travis-ci.org/onoi/blob-store.svg?branch=master)](http://travis-ci.org/onoi/blob-store)
+[![Code Coverage](https://scrutinizer-ci.com/g/onoi/blob-store/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/onoi/blob-store/?branch=master)
+[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/onoi/blob-store/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/onoi/blob-store/?branch=master)
+[![Latest Stable Version](https://poser.pugx.org/onoi/blob-store/version.png)](https://packagist.org/packages/onoi/blob-store)
+[![Packagist download count](https://poser.pugx.org/onoi/blob-store/d/total.png)](https://packagist.org/packages/onoi/blob-store)
+[![Dependency Status](https://www.versioneye.com/php/onoi:blob-store/badge.png)](https://www.versioneye.com/php/onoi:blob-store)
+
+A simple interface to manage schema-free temporal persistent key/values. This was part of
+the [Semantic MediaWiki][smw] code base and is now being deployed as independent library.
+
+It is suggested to use either redis, riak, or mongodb as back-end provider depending on the
+use case.
+
+## Requirements
+
+- PHP 5.3 or later
+- Onoi/Cache ~1.1
+
+## Installation
+
+The recommended installation method for this library is by either adding
+the dependency to your [composer.json][composer].
+
+```json
+{
+ "require": {
+ "onoi/blob-store": "~1.2"
+ }
+}
+```
+
+## Usage
+
+```php
+class Foo {
+
+ private $blobStore;
+
+ public function __construct( BlobStore $blobStore ) {
+ $this->blobStore = $blobStore;
+ }
+
+ public function doSomethingFor( $id ) {
+ $container = $this->blobStore->read( md5( $id ) );
+
+ $container->set( 'one', array( new \stdClass, 'Text' ) );
+
+ $container->append(
+ 'one',
+ new \stdClass
+ );
+
+ $container->delete( 'two' );
+
+ $this->blobStore->save( $container );
+ }
+}
+```
+```php
+$cacheFactory = new CacheFactory();
+
+$compositeCache = $cacheFactory->newCompositeCache( array(
+ $cacheFactory->newFixedInMemoryLruCache(),
+ $cacheFactory->newDoctrineCache( new \Doctrine\Common\Cache\RedisCache( ... ) )
+) );
+
+or
+
+$compositeCache = $cacheFactory->newCompositeCache( array(
+ $cacheFactory->newFixedInMemoryLruCache(),
+ $cacheFactory->newMediaWikiCache( \ObjectCache::getInstance( 'redis' ) )
+) );
+
+$blobStore = new BlobStore( 'foo', $compositeCache );
+
+$instance = new Foo( $blobStore );
+$instance->doSomethingFor( 'bar' );
+```
+
+When creating an instance a namespace is required to specify the context of the
+storage in case the `BlobStore` is used for different use cases.
+
+## Contribution and support
+
+If you want to contribute work to the project please subscribe to the
+developers mailing list and have a look at the [contribution guidelinee](/CONTRIBUTING.md). A list of people who have made contributions in the past can be found [here][contributors].
+
+* [File an issue](https://github.com/onoi/blob-store/issues)
+* [Submit a pull request](https://github.com/onoi/blob-store/pulls)
+
+### Tests
+
+The library provides unit tests that covers the core-functionality normally run by the [continues integration platform][travis]. Tests can also be executed manually using the `composer phpunit` command from the root directory.
+
+### Release notes
+
+* 1.2.0 (2016-03-19)
+ - Added `Container::addToLinkedList` to maintain a linked list of interdependent
+ containers (if the original container is removed then all linked containers will be
+ expunged as well)
+
+* 1.1.0 (2015-06-13)
+ - Removed tracking of internal ID list
+ - Added `Container::setExpiryInSeconds`
+
+* 1.0.0 (2015-06-02)
+ - Initial release
+
+## License
+
+[GNU General Public License 2.0 or later][license].
+
+[composer]: https://getcomposer.org/
+[contributors]: https://github.com/onoi/blob-store/graphs/contributors
+[license]: https://www.gnu.org/copyleft/gpl.html
+[travis]: https://travis-ci.org/onoi/blob-store
+[smw]: https://github.com/SemanticMediaWiki/SemanticMediaWiki/
diff --git a/www/wiki/vendor/onoi/blob-store/composer.json b/www/wiki/vendor/onoi/blob-store/composer.json
new file mode 100644
index 00000000..4d31a8fd
--- /dev/null
+++ b/www/wiki/vendor/onoi/blob-store/composer.json
@@ -0,0 +1,35 @@
+{
+ "name": "onoi/blob-store",
+ "type": "library",
+ "description": "A simple interface to manage schema-free temporal persistent key/values",
+ "keywords": [
+ "messages"
+ ],
+ "homepage": "https://github.com/onoi/blob-store",
+ "license": "GPL-2.0+",
+ "authors": [
+ {
+ "name": "mwjames"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.2",
+ "onoi/cache": "~1.1"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Onoi\\BlobStore\\": "src/"
+ }
+ },
+ "config": {
+ "process-timeout": 0
+ },
+ "scripts":{
+ "phpunit": "phpunit -c phpunit.xml.dist"
+ }
+}
diff --git a/www/wiki/vendor/onoi/blob-store/phpunit.xml.dist b/www/wiki/vendor/onoi/blob-store/phpunit.xml.dist
new file mode 100644
index 00000000..027df78c
--- /dev/null
+++ b/www/wiki/vendor/onoi/blob-store/phpunit.xml.dist
@@ -0,0 +1,27 @@
+<phpunit backupGlobals="false"
+ backupStaticAttributes="false"
+ bootstrap="tests/bootstrap.php"
+ cacheTokens="false"
+ colors="true"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ stopOnError="false"
+ stopOnFailure="false"
+ stopOnIncomplete="false"
+ stopOnSkipped="false"
+ verbose="true">
+ <testsuites>
+ <testsuite name="onoi-blob-store-unit">
+ <directory>tests/phpunit/Unit</directory>
+ </testsuite>
+ <testsuite name="onoi-blob-store-integration">
+ <directory>tests/phpunit/Integration</directory>
+ </testsuite>
+ </testsuites>
+ <filter>
+ <whitelist addUncoveredFilesFromWhitelist="true">
+ <directory suffix=".php">src</directory>
+ </whitelist>
+ </filter>
+</phpunit>
diff --git a/www/wiki/vendor/onoi/blob-store/src/BlobStore.php b/www/wiki/vendor/onoi/blob-store/src/BlobStore.php
new file mode 100644
index 00000000..eac1802e
--- /dev/null
+++ b/www/wiki/vendor/onoi/blob-store/src/BlobStore.php
@@ -0,0 +1,223 @@
+<?php
+
+namespace Onoi\BlobStore;
+
+use Onoi\Cache\Cache;
+use Onoi\Cache\CacheFactory;
+use InvalidArgumentException;
+
+/**
+ * Pervasive value blob store that can be used to store and retrieve key values
+ * from a schema free "fast" data store such as redis. The connection to a back-end
+ * is handled by the Onoi\Cache interface to support different provider solutions.
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class BlobStore {
+
+ /**
+ * @var string
+ */
+ private $namespace;
+
+ /**
+ * @var string
+ */
+ private $namespacePrefix = 'blobstore';
+
+ /**
+ * @var Cache
+ */
+ private $cache;
+
+ /**
+ * @var Cache
+ */
+ private $internalCache;
+
+ /**
+ * @var boolean
+ */
+ private $usageState = true;
+
+ /**
+ * 0 = stored indefinitely until it is removed or dropped
+ *
+ * @var integer
+ */
+ private $expiry = 0;
+
+ /**
+ * @since 1.0
+ *
+ * @param string $namespace
+ * @param Cache $cache
+ */
+ public function __construct( $namespace, Cache $cache ) {
+
+ if ( !is_string( $namespace ) ) {
+ throw new InvalidArgumentException( "Expected the namespace to be a string" );
+ }
+
+ $this->namespace = $namespace;
+ $this->cache = $cache;
+
+ // It is only used internally therefore no injection required as it improves
+ // performance on long lists as seen in #1
+ $this->internalCache = CacheFactory::getInstance()->newFixedInMemoryLruCache( 500 );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @return boolean
+ */
+ public function canUse() {
+ return $this->usageState;
+ }
+
+ /**
+ * Specifies whether the instance can be generally used or not
+ *
+ * @since 1.0
+ *
+ * @param boolean $usageState
+ */
+ public function setUsageState( $usageState ) {
+ $this->usageState = (bool)$usageState;
+ }
+
+ /**
+ * Specifies the expiry / time to live for stored containers in seconds
+ *
+ * @since 1.0
+ *
+ * @param integer $expiry
+ */
+ public function setExpiryInSeconds( $expiry ) {
+ $this->expiry = $expiry;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param string $prefix
+ */
+ public function setNamespacePrefix( $prefix ) {
+ $this->namespacePrefix = $prefix;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param string $id
+ *
+ * @return boolean
+ */
+ public function exists( $id ) {
+ return $this->cache->contains( $this->getKey( $id ) );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @return array
+ */
+ public function getStats() {
+ return $this->cache->getStats() + array(
+ 'internalCache' => $this->internalCache->getStats()
+ );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param string $id
+ *
+ * @return Container
+ */
+ public function read( $id ) {
+
+ $id = $this->getKey( $id );
+
+ // If possible use the raw data from the internal cache
+ // without unserialization
+ if ( $this->internalCache->contains( $id ) ) {
+ $data = $this->internalCache->fetch( $id );
+ } elseif ( $this->cache->contains( $id ) ) {
+ $data = unserialize( $this->cache->fetch( $id ) );
+ $this->internalCache->save( $id, $data );
+ } else {
+ $data = array();
+ }
+
+ $container = new Container( $id, (array)$data );
+ $container->setExpiryInSeconds( $this->expiry );
+
+ return $container;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param Container $container
+ */
+ public function save( Container $container ) {
+
+ $this->internalCache->save(
+ $container->getId(),
+ $container->getData(),
+ $container->getExpiry()
+ );
+
+ $this->cache->save(
+ $container->getId(),
+ serialize( $container->getData() ),
+ $container->getExpiry()
+ );
+
+ unset( $container );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param string $id
+ */
+ public function delete( $id ) {
+
+ $container = $this->read( $id );
+
+ foreach ( $container->getLinkedList() as $lid ) {
+ $this->cache->delete( $this->getKey( $lid ) );
+ }
+
+ $this->cache->delete( $this->getKey( $id ) );
+ $this->internalCache->delete( $this->getKey( $id ) );
+ }
+
+ /**
+ * @since 1.0
+ */
+ public function drop() {
+ // After using the internal list for several production runs it seems
+ // difficult to keep track of ids especially when using long lists and
+ // not to increase overhead which can easily produced by 50K+ entries
+ // which would require to split the internal list
+ //
+ // Using an appropriate expiry seems more efficient
+ }
+
+ private function getKey( $id ) {
+
+ if ( !is_string( $id ) ) {
+ throw new InvalidArgumentException( "Expected the id to be a string" );
+ }
+
+ return $this->namespacePrefix . ':' . $this->namespace . ':' . $id;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/blob-store/src/Container.php b/www/wiki/vendor/onoi/blob-store/src/Container.php
new file mode 100644
index 00000000..fcd40fbb
--- /dev/null
+++ b/www/wiki/vendor/onoi/blob-store/src/Container.php
@@ -0,0 +1,188 @@
+<?php
+
+namespace Onoi\BlobStore;
+
+use InvalidArgumentException;
+
+/**
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class Container {
+
+ /**
+ * @var string
+ */
+ private $id;
+
+ /**
+ * @var array
+ */
+ private $data = array();
+
+ /**
+ * @var integer
+ */
+ private $expiry = 0;
+
+ /**
+ * @since 1.0
+ *
+ * @param string $id
+ * @param array $data
+ */
+ public function __construct( $id, array $data = array() ) {
+
+ if ( !is_string( $id ) ) {
+ throw new InvalidArgumentException( "Expected the id to be a string" );
+ }
+
+ $this->id = $id;
+ $this->data = $data;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @return string
+ */
+ public function getId() {
+ return $this->id;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @return array
+ */
+ public function getData() {
+ return $this->data;
+ }
+
+ /**
+ * @since 1.1
+ *
+ * @return array
+ */
+ public function getExpiry() {
+ return $this->expiry;
+ }
+
+ /**
+ * @since 1.1
+ *
+ * @return integer $expiry
+ */
+ public function setExpiryInSeconds( $expiry ) {
+ $this->expiry = (int)$expiry;
+ }
+
+ /**
+ * @since 1.1
+ *
+ * @return boolean
+ */
+ public function isEmpty() {
+ return $this->data === array();
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @return boolean
+ */
+ public function has( $key ) {
+ return isset( $this->data[$key] ) || array_key_exists( $key, $this->data );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param string $key
+ *
+ * @return mixed
+ */
+ public function get( $key ) {
+
+ if ( $this->has( $key ) ) {
+ return $this->data[$key];
+ }
+
+ return false;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param string $key
+ * @param mixed $value
+ */
+ public function set( $key, $value ) {
+ $this->data[$key] = $value;
+ }
+
+ /**
+ * Extend/append/merge the data with an existing storage item for the same
+ * key
+ *
+ * @since 1.0
+ *
+ * @param string $key
+ * @param mixed $value
+ */
+ public function append( $key, $value ) {
+
+ if ( !$this->has( $key ) ) {
+ $this->data[$key] = array();
+ }
+
+ if ( !is_array( $value ) ) {
+ $value = array( $value );
+ }
+
+ $this->data[$key] = array_merge(
+ (array)$this->data[$key],
+ $value
+ );
+ }
+
+ /**
+ * @since 1.2
+ *
+ * @param integer|string $hash
+ */
+ public function addToLinkedList( $hash ) {
+
+ if ( !isset( $this->data['@linkedList'] ) ) {
+ $this->data['@linkedList'] = array();
+ }
+
+ $this->data['@linkedList'][$hash] = true;
+ }
+
+ /**
+ * @since 1.2
+ *
+ * @param array
+ */
+ public function getLinkedList() {
+
+ if ( !isset( $this->data['@linkedList'] ) ) {
+ return array();
+ }
+
+ return array_keys( $this->data['@linkedList'] );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param string $key
+ */
+ public function delete( $key ) {
+ unset( $this->data[$key] );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/blob-store/tests/bootstrap.php b/www/wiki/vendor/onoi/blob-store/tests/bootstrap.php
new file mode 100644
index 00000000..d7b2546a
--- /dev/null
+++ b/www/wiki/vendor/onoi/blob-store/tests/bootstrap.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+
+if ( PHP_SAPI !== 'cli' ) {
+ die( 'Not an entry point' );
+}
+
+error_reporting( E_ALL | E_STRICT );
+ini_set( 'display_errors', 1 );
+
+if ( is_readable( $path = __DIR__ . '/../vendor/autoload.php' ) ) {
+ print( "\nUsing the blobstore vendor autoloader ...\n\n" );
+} elseif ( is_readable( $path = __DIR__ . '/../../../autoload.php' ) ) {
+ print( "\nUsing another local vendor autoloader ...\n\n" );
+} else {
+ die( 'The test suite requires a Composer based deployement.' );
+}
+
+$autoLoader = require $path;
+$autoLoader->addPsr4( 'Onoi\\BlobStore\\Tests\\', __DIR__ . '/phpunit/Unit' );
+$autoLoader->addPsr4( 'Onoi\\BlobStore\\Tests\\Integration\\', __DIR__ . '/phpunit/Integration' );
diff --git a/www/wiki/vendor/onoi/blob-store/tests/phpunit/Integration/BlobStoreIntegrationTestCase.php b/www/wiki/vendor/onoi/blob-store/tests/phpunit/Integration/BlobStoreIntegrationTestCase.php
new file mode 100644
index 00000000..e6949ee8
--- /dev/null
+++ b/www/wiki/vendor/onoi/blob-store/tests/phpunit/Integration/BlobStoreIntegrationTestCase.php
@@ -0,0 +1,106 @@
+<?php
+
+namespace Onoi\BlobStore\Tests\Integration;
+
+use Onoi\BlobStore\BlobStore;
+
+/**
+ * @group onoi-blobstore
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+abstract class BlobStoreIntegrationTestCase extends \PHPUnit_Framework_TestCase {
+
+ protected $cache;
+
+ public function testStorageAndRetrieval() {
+
+ $blobStore = new BlobStore( 'Foo', $this->cache );
+ $container = $blobStore->read( 'bar' );
+
+ $some = new \stdClass;
+ $some->foo = 42;
+
+ $container->set( 'one', $some );
+
+ $this->assertEquals(
+ $some,
+ $container->get( 'one' )
+ );
+
+ $blobStore->save( $container );
+
+ $container = $blobStore->read( 'foobar' );
+ $container->set( 'two', 'anotherText' );
+
+ $container->append(
+ 'two',
+ $blobStore->read( 'bar' )->get( 'one' )
+ );
+
+ $container->append(
+ 'two',
+ $some
+ );
+
+ $this->assertEquals(
+ array( 'anotherText', $some, $some ),
+ $container->get( 'two' )
+ );
+ }
+
+ public function testDeleteSingleContainer() {
+
+ $blobStore = new BlobStore( 'Foo', $this->cache );
+
+ $container = $blobStore->read( 'foobar' );
+ $container->set( 'one', 42 );
+ $blobStore->save( $container );
+
+ $this->assertTrue(
+ $blobStore->exists( 'foobar' )
+ );
+
+ $blobStore->delete( 'foobar' );
+
+ $this->assertFalse(
+ $blobStore->exists( 'foobar' )
+ );
+ }
+
+ public function testExpiry() {
+
+ $blobStore = new BlobStore( 'Foo', $this->cache );
+ $blobStore->setExpiryInSeconds( 4 );
+
+ $container = $blobStore->read( 'bar' );
+ $container->set( 'one', 1001 );
+ $blobStore->save( $container );
+
+ $container = $blobStore->read( 'foo' );
+ $container->set( 'one', 42 );
+ $blobStore->save( $container );
+
+ $this->assertTrue(
+ $blobStore->exists( 'bar' )
+ );
+
+ $this->assertTrue(
+ $blobStore->exists( 'foo' )
+ );
+
+ sleep( 5 );
+
+ $this->assertFalse(
+ $blobStore->exists( 'bar' )
+ );
+
+ $this->assertFalse(
+ $blobStore->exists( 'foo' )
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/blob-store/tests/phpunit/Integration/DoctrineRedisIntegrationTest.php b/www/wiki/vendor/onoi/blob-store/tests/phpunit/Integration/DoctrineRedisIntegrationTest.php
new file mode 100644
index 00000000..7533833e
--- /dev/null
+++ b/www/wiki/vendor/onoi/blob-store/tests/phpunit/Integration/DoctrineRedisIntegrationTest.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Onoi\BlobStore\Tests\Integration;
+
+use Onoi\Cache\CacheFactory;
+use Doctrine\Common\Cache\RedisCache;
+
+/**
+ * @group onoi-blobstore
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class DoctrineRedisIntegrationTest extends BlobStoreIntegrationTestCase {
+
+ protected function setUp() {
+
+ if ( !class_exists( '\Onoi\Cache\CacheFactory' ) ) {
+ $this->markTestSkipped( 'CacheFactory is not available' );
+ }
+
+ if ( !class_exists( '\Redis' ) ) {
+ $this->markTestSkipped( 'Requires redis php-class/extension to be available' );
+ }
+
+ $redis = new \Redis();
+
+ if ( !$redis->connect( '127.0.0.1' ) ) {
+ $this->markTestSkipped( 'Cannot connect to redis' );
+ }
+
+ if ( !class_exists( '\Doctrine\Common\Cache\RedisCache' ) ) {
+ $this->markTestSkipped( 'RedisCache is not available' );
+ }
+
+ $redisCache = new RedisCache();
+ $redisCache->setRedis( $redis );
+
+ $cacheFactory = new CacheFactory();
+ $this->cache = $cacheFactory->newDoctrineCache( $redisCache );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/blob-store/tests/phpunit/Integration/FixedInMemoryIntegrationTest.php b/www/wiki/vendor/onoi/blob-store/tests/phpunit/Integration/FixedInMemoryIntegrationTest.php
new file mode 100644
index 00000000..ec82fcd8
--- /dev/null
+++ b/www/wiki/vendor/onoi/blob-store/tests/phpunit/Integration/FixedInMemoryIntegrationTest.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Onoi\BlobStore\Tests\Integration;
+
+use Onoi\BlobStore\BlobStore;
+use Onoi\Cache\CacheFactory;
+
+/**
+ * @group onoi-blobstore
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class FixedInMemoryIntegrationTest extends BlobStoreIntegrationTestCase {
+
+ protected function setUp() {
+
+ if ( !class_exists( '\Onoi\Cache\CacheFactory' ) ) {
+ $this->markTestSkipped( 'CacheFactory is not available' );
+ }
+
+ $cacheFactory = new CacheFactory();
+ $this->cache = $cacheFactory->newFixedInMemoryLruCache( 500 );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/blob-store/tests/phpunit/Unit/BlobStoreTest.php b/www/wiki/vendor/onoi/blob-store/tests/phpunit/Unit/BlobStoreTest.php
new file mode 100644
index 00000000..aea0b8e7
--- /dev/null
+++ b/www/wiki/vendor/onoi/blob-store/tests/phpunit/Unit/BlobStoreTest.php
@@ -0,0 +1,245 @@
+<?php
+
+namespace Onoi\BlobStore\Tests;
+
+use Onoi\BlobStore\BlobStore;
+use Onoi\BlobStore\Container;
+
+/**
+ * @covers \Onoi\BlobStore\BlobStore
+ *
+ * @group onoi-blobstore
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class BlobStoreTest extends \PHPUnit_Framework_TestCase {
+
+ private $cache;
+
+ protected function setUp() {
+
+ $this->cache = $this->getMockBuilder( '\Onoi\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+ }
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\BlobStore\BlobStore',
+ new BlobStore( 'Foo', $this->cache )
+ );
+ }
+
+ public function testInvalidNamespaceInConstructorThrowsException() {
+
+ $this->setExpectedException( 'InvalidArgumentException' );
+
+ new BlobStore(
+ array(),
+ $this->cache
+ );
+ }
+
+ public function testInvalidKeyForContainerThrowsException() {
+
+ $instance = new BlobStore(
+ 'Foo',
+ $this->cache
+ );
+
+ $this->setExpectedException( 'InvalidArgumentException' );
+ $instance->read( array() );
+ }
+
+ public function testCanUse() {
+
+ $instance = new BlobStore( 'Foo', $this->cache );
+
+ $this->assertTrue(
+ $instance->canUse()
+ );
+
+ $instance->setUsageState( false );
+
+ $this->assertFalse(
+ $instance->canUse()
+ );
+ }
+
+ public function testReadContainerForUnknownId() {
+
+ $container = new Container( 'blobstore:Foo:bar', array() );
+
+ $instance = new BlobStore( 'Foo', $this->cache );
+
+ $this->assertInstanceOf(
+ '\Onoi\BlobStore\Container',
+ $instance->read( 'bar' )
+ );
+
+ $this->assertEquals(
+ $container,
+ $instance->read( 'bar' )
+ );
+ }
+
+ public function testReadContainerForKnownId() {
+
+ $container = new Container( 'coffee:Foo:bar', array() );
+
+ $this->cache->expects( $this->once() )
+ ->method( 'contains' )
+ ->with(
+ $this->equalTo( 'coffee:Foo:bar' ) )
+ ->will( $this->returnValue( true ) );
+
+ $this->cache->expects( $this->once() )
+ ->method( 'fetch' )
+ ->will( $this->returnValue( serialize( array() ) ) );
+
+ $instance = new BlobStore( 'Foo', $this->cache );
+ $instance->setNamespacePrefix( 'coffee' );
+
+ $this->assertEquals(
+ $container,
+ $instance->read( 'bar' )
+ );
+ }
+
+ public function testReadContainerForKnownIdToAlwaysReturnArrayType() {
+
+ $container = new Container( 'blobstore:Foo:bar', array( false ) );
+
+ $this->cache->expects( $this->once() )
+ ->method( 'contains' )
+ ->with(
+ $this->equalTo( 'blobstore:Foo:bar' ) )
+ ->will( $this->returnValue( true ) );
+
+ $this->cache->expects( $this->once() )
+ ->method( 'fetch' )
+ ->will( $this->returnValue( serialize( false ) ) );
+
+ $instance = new BlobStore( 'Foo', $this->cache );
+
+ $this->assertEquals(
+ $container,
+ $instance->read( 'bar' )
+ );
+ }
+
+ public function testGetStats() {
+
+ $this->cache->expects( $this->once() )
+ ->method( 'getStats' )
+ ->will( $this->returnValue( array() ) );
+
+ $instance = new BlobStore( 'Foo', $this->cache );
+ $instance->getStats();
+ }
+
+ public function testSaveContainer() {
+
+ $container = array( 'Foobar', new \stdClass, array() );
+
+ $this->cache->expects( $this->once() )
+ ->method( 'save' )
+ ->with(
+ $this->equalTo( 'Foo:bar' ),
+ $this->equalTo( serialize( $container ) ),
+ $this->equalTo( 0 ) );
+
+ $container = new Container( 'Foo:bar', $container );
+
+ $instance = new BlobStore( 'Foo', $this->cache );
+ $instance->save( $container );
+ }
+
+ public function testSaveContainerWithExpiry() {
+
+ $container = array( 'Foobar', new \stdClass, array() );
+
+ $this->cache->expects( $this->once() )
+ ->method( 'save' )
+ ->with(
+ $this->equalTo( 'Foo:bar' ),
+ $this->anything(),
+ $this->equalTo( 42 ) );
+
+ $container = new Container( 'Foo:bar', $container );
+ $container->setExpiryInSeconds( 42 );
+
+ $instance = new BlobStore( 'Foo', $this->cache );
+
+ $instance->save( $container );
+ }
+
+ public function testTransferExpiry() {
+
+ $instance = new BlobStore( 'Foo', $this->cache );
+ $instance->setExpiryInSeconds( 42 );
+
+ $container = $instance->read( 'Bar' );
+
+ $this->assertEquals(
+ 42,
+ $container->getExpiry()
+ );
+ }
+
+ public function testDeleteContainerForSpecificId() {
+
+ $this->cache->expects( $this->once() )
+ ->method( 'delete' )
+ ->with(
+ $this->equalTo( 'blobstore:Foo:bar' ) );
+
+ $instance = new BlobStore( 'Foo', $this->cache );
+ $instance->delete( 'bar' );
+ }
+
+ public function testDropDoesNothing() {
+
+ $this->cache->expects( $this->never() )
+ ->method( 'fetch' );
+
+ $this->cache->expects( $this->never() )
+ ->method( 'delete' );
+
+ $instance = new BlobStore( 'Foo', $this->cache );
+ $instance->drop();
+ }
+
+ public function testDeleteMembersOfLinkedListAsWell() {
+
+ $linkedContainer = serialize(
+ array( '@linkedList' => array( 'a42b' => true ) )
+ );
+
+ $this->cache->expects( $this->once() )
+ ->method( 'contains' )
+ ->with( $this->equalTo( 'blobstore:Foo:Bar' ) )
+ ->will( $this->returnValue( true ) );
+
+ $this->cache->expects( $this->once() )
+ ->method( 'fetch' )
+ ->will( $this->returnValue( $linkedContainer ) );
+
+ $this->cache->expects( $this->at( 2 ) )
+ ->method( 'delete' )
+ ->with( $this->equalTo( 'blobstore:Foo:a42b' ) );
+
+ $this->cache->expects( $this->at( 3 ) )
+ ->method( 'delete' )
+ ->with( $this->equalTo( 'blobstore:Foo:Bar' ) );
+
+ $instance = new BlobStore( 'Foo', $this->cache );
+
+ $instance->delete( 'Bar' );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/blob-store/tests/phpunit/Unit/ContainerTest.php b/www/wiki/vendor/onoi/blob-store/tests/phpunit/Unit/ContainerTest.php
new file mode 100644
index 00000000..beaae421
--- /dev/null
+++ b/www/wiki/vendor/onoi/blob-store/tests/phpunit/Unit/ContainerTest.php
@@ -0,0 +1,146 @@
+<?php
+
+namespace Onoi\BlobStore\Tests;
+
+use Onoi\BlobStore\Container;
+
+/**
+ * @covers \Onoi\BlobStore\Container
+ *
+ * @group onoi-blobstore
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class ContainerTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\BlobStore\Container',
+ new Container( 'Foo', array() )
+ );
+ }
+
+ public function testTryToConstructForInvalidIdThrowsException() {
+ $this->setExpectedException( 'InvalidArgumentException' );
+ new Container( array(), array() );
+ }
+
+ public function testGetIdAndData() {
+
+ $expected = array( 'Bar' => new \stdClass );
+
+ $instance = new Container(
+ 'Foo',
+ $expected
+ );
+
+ $this->assertEquals(
+ 'Foo',
+ $instance->getId()
+ );
+
+ $this->assertEquals(
+ $expected,
+ $instance->getData()
+ );
+
+ $this->assertFalse(
+ $instance->isEmpty()
+ );
+ }
+
+ public function testExpiry() {
+
+ $instance = new Container(
+ 'Foo',
+ array()
+ );
+
+ $instance->setExpiryInSeconds( 42 );
+
+ $this->assertSame(
+ 42,
+ $instance->getExpiry()
+ );
+ }
+
+ public function testGetAndSet() {
+
+ $expected = array( 'Bar' => new \stdClass );
+
+ $instance = new Container(
+ 'Foo',
+ $expected
+ );
+
+ $this->assertFalse(
+ $instance->get( 'foobar' )
+ );
+
+ $this->assertEquals(
+ new \stdClass,
+ $instance->get( 'Bar' )
+ );
+
+ $instance->set( 'Bar', 42 );
+
+ $this->assertEquals(
+ 42,
+ $instance->get( 'Bar' )
+ );
+
+ $instance->append( 'Bar', 1001 );
+
+ $this->assertEquals(
+ array( 42 , 1001 ),
+ $instance->get( 'Bar' )
+ );
+
+ $instance->append( 'foobar', 1001 );
+
+ $this->assertEquals(
+ array( 1001 ),
+ $instance->get( 'foobar' )
+ );
+ }
+
+ public function testHasAndDelete() {
+
+ $expected = array( 'Bar' => new \stdClass );
+
+ $instance = new Container(
+ 'Foo',
+ $expected
+ );
+
+ $this->assertTrue(
+ $instance->has( 'Bar' )
+ );
+
+ $instance->delete( 'Bar' );
+
+ $this->assertFalse(
+ $instance->has( 'Bar' )
+ );
+ }
+
+ public function testAddToLinkedList() {
+
+ $instance = new Container(
+ 'Foo'
+ );
+
+ $instance->addToLinkedList( 'Bar' );
+ $instance->addToLinkedList( 'Bar' );
+
+ $this->assertEquals(
+ array( 'Bar' ),
+ $instance->getLinkedList()
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/blob-store/tests/travis/run-tests.sh b/www/wiki/vendor/onoi/blob-store/tests/travis/run-tests.sh
new file mode 100644
index 00000000..b01e6b5f
--- /dev/null
+++ b/www/wiki/vendor/onoi/blob-store/tests/travis/run-tests.sh
@@ -0,0 +1,19 @@
+#! /bin/bash
+set -ex
+
+BASE_PATH=$(pwd)
+
+composer install
+composer dump-autoload
+composer validate --no-interaction
+
+if [ "$TYPE" == "coverage" ]
+then
+ composer phpunit -- --coverage-clover $BASE_PATH/build/coverage.clover
+elif [ "$DOCTRINE" != "" ]
+then
+ composer require 'doctrine/cache='$DOCTRINE --prefer-source --update-with-dependencies
+ composer phpunit
+else
+ composer phpunit
+fi
diff --git a/www/wiki/vendor/onoi/blob-store/tests/travis/upload-coverage-report.sh b/www/wiki/vendor/onoi/blob-store/tests/travis/upload-coverage-report.sh
new file mode 100644
index 00000000..aff95cf7
--- /dev/null
+++ b/www/wiki/vendor/onoi/blob-store/tests/travis/upload-coverage-report.sh
@@ -0,0 +1,10 @@
+#! /bin/bash
+set -ex
+
+BASE_PATH=$(pwd)
+
+if [ "$TYPE" == "coverage" ]
+then
+ wget https://scrutinizer-ci.com/ocular.phar
+ php ocular.phar code-coverage:upload --format=php-clover $BASE_PATH/build/coverage.clover
+fi \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/cache/.gitignore b/www/wiki/vendor/onoi/cache/.gitignore
new file mode 100644
index 00000000..09ec1c53
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/.gitignore
@@ -0,0 +1,10 @@
+*~
+*.kate-swp
+
+!.*
+.idea/
+
+composer.phar
+composer.lock
+
+vendor/ \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/cache/.scrutinizer.yml b/www/wiki/vendor/onoi/cache/.scrutinizer.yml
new file mode 100644
index 00000000..dfdcb235
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/.scrutinizer.yml
@@ -0,0 +1,18 @@
+filter:
+ excluded_paths:
+ - 'vendor/*'
+
+tools:
+ php_mess_detector:
+ config:
+ controversial_rules: { superglobals: false }
+ php_cpd: true
+ php_pdepend: true
+ php_code_coverage: false
+ php_code_sniffer: true
+ php_cs_fixer: true
+ php_loc: true
+ php_analyzer: true
+ sensiolabs_security_checker: true
+ external_code_coverage:
+ timeout: 600
diff --git a/www/wiki/vendor/onoi/cache/.travis.yml b/www/wiki/vendor/onoi/cache/.travis.yml
new file mode 100644
index 00000000..f52a67ac
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/.travis.yml
@@ -0,0 +1,25 @@
+# http://docs.travis-ci.com/user/workers/container-based-infrastructure/
+sudo: false
+
+language: php
+
+matrix:
+ include:
+ - env: DB=mysql; MW=master; DOCTRINE=1.4.0; ZENDCACHE=2.3.7; TYPE=coverage
+ php: 5.6
+ - env: ZENDCACHE=2.3.7;
+ php: 5.4
+ - env: TYPE=UNIT;
+ php: '7'
+ - env: TYPE=UNIT;
+ php: hhvm
+
+install:
+ - bash ./tests/travis/install-mediawiki.sh
+ - bash ./tests/travis/install-cache.sh
+
+script:
+ - bash ./tests/travis/run-tests.sh
+
+after_success:
+ - bash ./tests/travis/upload-coverage-report.sh
diff --git a/www/wiki/vendor/onoi/cache/LICENSE b/www/wiki/vendor/onoi/cache/LICENSE
new file mode 100644
index 00000000..d6a93266
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/LICENSE
@@ -0,0 +1,340 @@
+GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ {description}
+ Copyright (C) {year} {fullname}
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ {signature of Ty Coon}, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
+
diff --git a/www/wiki/vendor/onoi/cache/README.md b/www/wiki/vendor/onoi/cache/README.md
new file mode 100644
index 00000000..081635d8
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/README.md
@@ -0,0 +1,107 @@
+# Cache
+
+[![Build Status](https://secure.travis-ci.org/onoi/cache.svg?branch=master)](http://travis-ci.org/onoi/cache)
+[![Code Coverage](https://scrutinizer-ci.com/g/onoi/cache/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/onoi/cache/?branch=master)
+[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/onoi/cache/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/onoi/cache/?branch=master)
+[![Latest Stable Version](https://poser.pugx.org/onoi/cache/version.png)](https://packagist.org/packages/onoi/cache)
+[![Packagist download count](https://poser.pugx.org/onoi/cache/d/total.png)](https://packagist.org/packages/onoi/cache)
+[![Dependency Status](https://www.versioneye.com/php/onoi:cache/badge.png)](https://www.versioneye.com/php/onoi:cache)
+
+A minimalistic cache adapter interface that was part of the [Semantic MediaWiki][smw] code base and
+is now being deployed as independent library.
+
+- Support for MediaWiki's `BagOStuff` cache interface
+- Support for the `Doctrine` cache interface
+- Support for the `Zend` cache (ZF2) interface
+- Provides a `FixedInMemoryLruCache` array [LRU][lru] cache without any external cache provider dependency
+- Provides a `CompositeCache` to combine different cache instances and allow access through
+ hierarchical iteration on a first-come first-served basis
+
+## Requirements
+
+PHP 5.3 / HHVM 3.3 or later
+
+## Installation
+
+The recommended installation method for this library is by either adding
+the dependency to your [composer.json][composer].
+
+```json
+{
+ "require": {
+ "onoi/cache": "~1.1"
+ }
+}
+```
+
+## Usage
+
+```php
+class Foo {
+
+ private $cache = null;
+
+ public function __constructor( Onoi\Cache\Cache $cache ) {
+ $this->cache = $cache;
+ }
+
+ public function doSomething( $id ) {
+
+ if ( $this->cache->contains( $id ) ) {
+ // do something
+ }
+ }
+}
+```
+```php
+$cacheFactory = new CacheFactory();
+
+$instance = new Foo( $cacheFactory->newFixedInMemoryLruCache( 500 ) );
+$instance->doSomething( 'bar' );
+
+$compositeCache = $cacheFactory->newCompositeCache( array(
+ $cacheFactory->newFixedInMemoryLruCache( 500 ),
+ $cacheFactory->newMediaWikiCache( new \SqlBagOStuf() ),
+ $cacheFactory->newDoctrineCache( new \Doctrine\Common\Cache\FileCache( '/C/Foo' ) )
+) );
+
+$instance = new Foo( $compositeCache );
+$instance->doSomething( 'bar' );
+```
+
+## Contribution and support
+
+If you want to contribute work to the project please subscribe to the
+developers mailing list and have a look at the [contribution guidelinee](/CONTRIBUTING.md). A list of people who have made contributions in the past can be found [here][contributors].
+
+* [File an issue](https://github.com/onoi/cache/issues)
+* [Submit a pull request](https://github.com/onoi/cache/pulls)
+
+### Tests
+
+The library provides unit tests that covers the core-functionality normally run by the [continues integration platform][travis]. Tests can also be executed manually using the `composer phpunit` command from the root directory.
+
+### Release notes
+
+- 1.2.0 (2015-06-02)
+ - Added `Cache::getName`
+ - Removed deprecated `FixedInMemoryCache`
+
+- 1.1.0 (2015-03-29)
+ - Added `NullCache`
+ - Added `ZendCache`
+ - Renamed `FixedInMemoryCache` to `FixedInMemoryLruCache`
+
+- 1.0.0 (2015-01-16)
+ - Initial release
+
+## License
+
+[GNU General Public License 2.0 or later][license].
+
+[composer]: https://getcomposer.org/
+[contributors]: https://github.com/onoi/cache/graphs/contributors
+[license]: https://www.gnu.org/copyleft/gpl.html
+[travis]: https://travis-ci.org/onoi/cache
+[smw]: https://github.com/SemanticMediaWiki/SemanticMediaWiki/
+[lru]: https://en.wikipedia.org/wiki/Least_Recently_Used
diff --git a/www/wiki/vendor/onoi/cache/composer.json b/www/wiki/vendor/onoi/cache/composer.json
new file mode 100644
index 00000000..b770a129
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/composer.json
@@ -0,0 +1,35 @@
+{
+ "name": "onoi/cache",
+ "type": "library",
+ "description": "A minimalistic cache adapter interface library",
+ "keywords": [
+ "cache"
+ ],
+ "homepage": "https://github.com/onoi/cache",
+ "license": "GPL-2.0+",
+ "authors": [
+ {
+ "name": "mwjames",
+ "homepage": "https://github.com/mwjames"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Onoi\\Cache\\": "src/"
+ }
+ },
+ "config": {
+ "process-timeout": 0
+ },
+ "scripts":{
+ "phpunit": "phpunit -c phpunit.xml.dist"
+ }
+}
diff --git a/www/wiki/vendor/onoi/cache/phpunit.xml.dist b/www/wiki/vendor/onoi/cache/phpunit.xml.dist
new file mode 100644
index 00000000..9c968e06
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/phpunit.xml.dist
@@ -0,0 +1,27 @@
+<phpunit backupGlobals="false"
+ backupStaticAttributes="false"
+ bootstrap="tests/bootstrap.php"
+ cacheTokens="false"
+ colors="true"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ stopOnError="false"
+ stopOnFailure="false"
+ stopOnIncomplete="false"
+ stopOnSkipped="false"
+ verbose="true">
+ <testsuites>
+ <testsuite name="onoi-cache-unit">
+ <directory>tests/phpunit/Unit</directory>
+ </testsuite>
+ <testsuite name="onoi-cache-integration">
+ <directory>tests/phpunit/Integration</directory>
+ </testsuite>
+ </testsuites>
+ <filter>
+ <whitelist addUncoveredFilesFromWhitelist="true">
+ <directory suffix=".php">src</directory>
+ </whitelist>
+ </filter>
+</phpunit>
diff --git a/www/wiki/vendor/onoi/cache/src/Cache.php b/www/wiki/vendor/onoi/cache/src/Cache.php
new file mode 100644
index 00000000..b58efaab
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/src/Cache.php
@@ -0,0 +1,74 @@
+<?php
+
+namespace Onoi\Cache;
+
+/**
+ * Specifying a common interface to access a cache instance and is modelled after
+ * the Doctrine\Common\Cache interface
+ *
+ * @note Moved from SMW\Cache\Cache@2.1
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+interface Cache {
+
+ /**
+ * Returns a cache item or false if no entry was found
+ *
+ * @since 1.0
+ *
+ * @param string $id
+ *
+ * @return mixed|false
+ */
+ public function fetch( $id );
+
+ /**
+ * Whether an entry is available for the given id
+ *
+ * @since 1.0
+ *
+ * @param string $id
+ *
+ * @return boolean
+ */
+ public function contains( $id );
+
+ /**
+ * @since 1.0
+ *
+ * @param string $id
+ * @param mixed $data
+ * @param integer $ttl
+ *
+ * @return mixed
+ */
+ public function save( $id, $data, $ttl = 0 );
+
+ /**
+ * @since 1.0
+ *
+ * @param string $id
+ *
+ * @return boolean
+ */
+ public function delete( $id );
+
+ /**
+ * @since 1.0
+ *
+ * @return array|null
+ */
+ public function getStats();
+
+ /**
+ * @since 1.2
+ *
+ * @return string
+ */
+ public function getName();
+
+}
diff --git a/www/wiki/vendor/onoi/cache/src/CacheFactory.php b/www/wiki/vendor/onoi/cache/src/CacheFactory.php
new file mode 100644
index 00000000..6395c3e5
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/src/CacheFactory.php
@@ -0,0 +1,116 @@
+<?php
+
+namespace Onoi\Cache;
+
+use Doctrine\Common\Cache\Cache as DoctrineCacheClient;
+use Zend\Cache\Storage\StorageInterface;
+use BagOStuff;
+
+/**
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class CacheFactory {
+
+ /**
+ * @var CacheFactory
+ */
+ private static $instance = null;
+
+ /**
+ * @since 1.0
+ *
+ * @return CacheFactory
+ */
+ public static function getInstance() {
+
+ if ( self::$instance === null ) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * @since 1.0
+ */
+ public static function clear() {
+ self::$instance = null;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param BagOStuff $cache
+ *
+ * @return MediaWikiCache
+ */
+ public function newMediaWikiCache( BagOStuff $cache ) {
+ return new MediaWikiCache( $cache );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param DoctrineCacheClient $cache
+ *
+ * @return DoctrineCache
+ */
+ public function newDoctrineCache( DoctrineCacheClient $cache ) {
+ return new DoctrineCache( $cache );
+ }
+
+ /**
+ * @since 1.1
+ *
+ * @param integer $cacheSize
+ *
+ * @return FixedInMemoryLruCache
+ */
+ public function newFixedInMemoryLruCache( $cacheSize = 500 ) {
+ return new FixedInMemoryLruCache( $cacheSize );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @deprecated since 1.1, use CacheFactory::newFixedInMemoryLruCache
+ */
+ public function newFixedInMemoryCache( $cacheSize = 500 ) {
+ return $this->newFixedInMemoryLruCache( $cacheSize );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param Cache[] $caches
+ *
+ * @return CompositeCache
+ */
+ public function newCompositeCache( array $caches ) {
+ return new CompositeCache( $caches );
+ }
+
+ /**
+ * @since 1.1
+ *
+ * @return NullCache
+ */
+ public function newNullCache() {
+ return new NullCache();
+ }
+
+ /**
+ * @since 1.1
+ *
+ * @param StorageInterface $cache
+ *
+ * @return ZendCache
+ */
+ public function newZendCache( StorageInterface $cache ) {
+ return new ZendCache( $cache );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/cache/src/CompositeCache.php b/www/wiki/vendor/onoi/cache/src/CompositeCache.php
new file mode 100644
index 00000000..d4c38939
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/src/CompositeCache.php
@@ -0,0 +1,147 @@
+<?php
+
+namespace Onoi\Cache;
+
+use RuntimeException;
+
+/**
+ * Combines different cache instances into a combinatory cache
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class CompositeCache implements Cache {
+
+ /**
+ * @var array
+ */
+ private $caches = array();
+
+ /**
+ * @note The access hierarchy is determined by the order of the invoked cache
+ * instances and it is assumed that the faster cache is accessible first.
+ *
+ * @since 1.0
+ *
+ * @param Cache[] $caches
+ */
+ public function __construct( array $caches ) {
+
+ foreach ( $caches as $key => $cache ) {
+
+ if ( !is_int( $key ) ) {
+ throw new RuntimeException( 'Associative key is not permitted' );
+ }
+
+ if ( !$cache instanceOf Cache ) {
+ throw new RuntimeException( 'The composite contains an invalid cache instance' );
+ }
+ }
+
+ $this->caches = $caches;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function fetch( $id ) {
+
+ reset( $this->caches );
+
+ foreach ( $this->caches as $key => $cache ) {
+
+ if ( !$cache->contains( $id ) ) {
+ continue;
+ }
+
+ $data = $cache->fetch( $id );
+
+ // The data were not available in a previous instance and it is assumed
+ // that a preceding cache is faster (in-memory lookup etc.) than the
+ // current instance therefore the content is stored one level up for
+ // the next lookup
+ if ( $key > 0 ) {
+ $this->caches[ $key - 1 ]->save( $id, $data );
+ }
+
+ return $data;
+ }
+
+ return false;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function contains( $id ) {
+
+ foreach ( $this->caches as $cache ) {
+ if ( $cache->contains( $id ) ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function save( $id, $data, $ttl = 0 ) {
+ foreach ( $this->caches as $cache ) {
+ $cache->save( $id, $data, $ttl );
+ }
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function delete( $id ) {
+ foreach ( $this->caches as $cache ) {
+ $cache->delete( $id );
+ }
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function getStats() {
+
+ $stats = array();
+
+ foreach ( $this->caches as $cache ) {
+ $stats[$cache->getName()] = $cache->getStats();
+ }
+
+ return $stats;
+ }
+
+ /**
+ * @since 1.2
+ *
+ * {@inheritDoc}
+ */
+ public function getName() {
+
+ $name = '';
+
+ foreach ( $this->caches as $cache ) {
+ $name = $name . '::' . $cache->getName();
+ }
+
+ return __CLASS__ . $name;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/cache/src/DoctrineCache.php b/www/wiki/vendor/onoi/cache/src/DoctrineCache.php
new file mode 100644
index 00000000..32b5be2e
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/src/DoctrineCache.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace Onoi\Cache;
+
+use Doctrine\Common\Cache\Cache as DoctrineCacheClient;
+
+/**
+ * Doctrine Cache decorator
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class DoctrineCache implements Cache {
+
+ /**
+ * @var DoctrineCacheClient
+ */
+ private $cache = null;
+
+ /**
+ * @since 1.0
+ *
+ * @param DoctrineCacheClient $cache
+ */
+ public function __construct( DoctrineCacheClient $cache ) {
+ $this->cache = $cache;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function fetch( $id ) {
+ return $this->cache->fetch( $id );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function contains( $id ) {
+ return $this->cache->contains( $id );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function save( $id, $data, $ttl = 0 ) {
+ $this->cache->save( $id, $data, $ttl );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function delete( $id ) {
+ return $this->cache->delete( $id );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function getStats() {
+ return $this->cache->getStats();
+ }
+
+ /**
+ * @since 1.2
+ *
+ * {@inheritDoc}
+ */
+ public function getName() {
+ return __CLASS__ . '::' . get_class( $this->cache );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/cache/src/FixedInMemoryLruCache.php b/www/wiki/vendor/onoi/cache/src/FixedInMemoryLruCache.php
new file mode 100644
index 00000000..b9318e35
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/src/FixedInMemoryLruCache.php
@@ -0,0 +1,177 @@
+<?php
+
+namespace Onoi\Cache;
+
+/**
+ * Implements a simple LRU (Least Recently Used) algorithm for a fixed in-memory
+ * hashmap
+ *
+ * @note For a size of more than 10K it is suggested to use PHP's SplFixedArray
+ * instead as it is optimized for large array sets
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ */
+class FixedInMemoryLruCache implements Cache {
+
+ /**
+ * @var array
+ */
+ private $cache = array();
+
+ /**
+ * @var array
+ */
+ private $ttl = array();
+
+ /**
+ * @var integer
+ */
+ private $maxCacheCount;
+
+ /**
+ * @var integer
+ */
+ private $count = 0;
+
+ /**
+ * @var integer
+ */
+ private $cacheInserts = 0;
+
+ /**
+ * @var integer
+ */
+ private $cacheDeletes = 0;
+
+ /**
+ * @var integer
+ */
+ private $cacheHits = 0;
+
+ /**
+ * @var integer
+ */
+ private $cacheMisses = 0;
+
+ /**
+ * @since 1.0
+ *
+ * @param integer $maxCacheCount
+ */
+ public function __construct( $maxCacheCount = 500 ) {
+ $this->maxCacheCount = (int)$maxCacheCount;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function contains( $id ) {
+
+ $contains = isset( $this->cache[ $id ] ) || array_key_exists( $id, $this->cache );
+
+ if ( !$contains ) {
+ return false;
+ }
+
+ if ( $this->ttl[ $id ] > 0 && $this->ttl[ $id ] <= microtime( true ) ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function fetch( $id ) {
+
+ if ( $this->contains( $id ) ) {
+ $this->cacheHits++;
+ return $this->moveToMostRecentlyUsedPosition( $id );
+ }
+
+ $this->cacheMisses++;
+ return false;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function save( $id, $value, $ttl = 0 ) {
+
+ $this->count++;
+ $this->cacheInserts++;
+
+ if ( $this->contains( $id ) ) {
+ $this->count--;
+ $this->moveToMostRecentlyUsedPosition( $id );
+ } elseif ( $this->count > $this->maxCacheCount ) {
+ $this->count--;
+ reset( $this->cache );
+ unset( $this->cache[ key( $this->cache ) ] );
+ }
+
+ $this->cache[ $id ] = $value;
+ $this->ttl[ $id ] = $ttl > 0 ? microtime( true ) + $ttl : 0;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function delete( $id ) {
+
+ if ( $this->contains( $id ) ) {
+ $this->count--;
+ $this->cacheDeletes++;
+ unset( $this->cache[ $id ] );
+ unset( $this->ttl[ $id ] );
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function getStats() {
+ return array(
+ 'inserts' => $this->cacheInserts,
+ 'deletes' => $this->cacheDeletes,
+ 'max' => $this->maxCacheCount,
+ 'count' => $this->count,
+ 'hits' => $this->cacheHits,
+ 'misses' => $this->cacheMisses
+ );
+ }
+
+ /**
+ * @since 1.2
+ *
+ * {@inheritDoc}
+ */
+ public function getName() {
+ return __CLASS__;
+ }
+
+ private function moveToMostRecentlyUsedPosition( $id ) {
+
+ $value = $this->cache[ $id ];
+ unset( $this->cache[ $id ] );
+ $this->cache[ $id ] = $value;
+
+ return $value;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/cache/src/MediaWikiCache.php b/www/wiki/vendor/onoi/cache/src/MediaWikiCache.php
new file mode 100644
index 00000000..81ca12d7
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/src/MediaWikiCache.php
@@ -0,0 +1,137 @@
+<?php
+
+namespace Onoi\Cache;
+
+use BagOStuff;
+
+/**
+ * MediaWiki BagOStuff decorator
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class MediaWikiCache implements Cache {
+
+ /**
+ * @var BagOStuff
+ */
+ private $cache = null;
+
+ /**
+ * @note MediaWiki's BagOStuff doesn't know any has/contains function therefore
+ * we need to use an internal array the fetch and temporarily store the results
+ * to ensure no expensive lookups occur for the same key
+ *
+ * @var array
+ */
+ private $inMemoryCache = array();
+
+ /**
+ * @var integer
+ */
+ private $cacheInserts = 0;
+
+ /**
+ * @var integer
+ */
+ private $cacheDeletes = 0;
+
+ /**
+ * @var integer
+ */
+ private $cacheHits = 0;
+
+ /**
+ * @var integer
+ */
+ private $cacheMisses = 0;
+
+ /**
+ * @since 1.0
+ *
+ * @param BagOStuff $cache
+ */
+ public function __construct( BagOStuff $cache ) {
+ $this->cache = $cache;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function fetch( $id ) {
+
+ if ( $this->contains( $id ) ) {
+ $this->cacheHits++;
+ return $this->inMemoryCache[ $id ];
+ }
+
+ $this->cacheMisses++;
+ return false;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function contains( $id ) {
+
+ if ( isset ( $this->inMemoryCache[ $id ] ) || array_key_exists( $id, $this->inMemoryCache ) ) {
+ return true;
+ }
+
+ $this->inMemoryCache[ $id ] = $this->cache->get( $id );
+
+ return !$this->inMemoryCache[ $id ] ? false : true;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function save( $id, $data, $ttl = 0 ) {
+ $this->cacheInserts++;
+ $this->cache->set( $id, $data, $ttl );
+ unset( $this->inMemoryCache[ $id ] );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function delete( $id ) {
+ $this->cacheDeletes++;
+ $this->cache->delete( $id );
+ unset( $this->inMemoryCache[ $id ] );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function getStats() {
+ return array(
+ 'inserts' => $this->cacheInserts,
+ 'deletes' => $this->cacheDeletes,
+ 'hits' => $this->cacheHits,
+ 'misses' => $this->cacheMisses
+ );
+ }
+
+ /**
+ * @since 1.2
+ *
+ * {@inheritDoc}
+ */
+ public function getName() {
+ return __CLASS__ . '::' . get_class( $this->cache );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/cache/src/NullCache.php b/www/wiki/vendor/onoi/cache/src/NullCache.php
new file mode 100644
index 00000000..7a535464
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/src/NullCache.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace Onoi\Cache;
+
+/**
+ * @license GNU GPL v2+
+ * @since 1.1
+ *
+ * @author mwjames
+ */
+class NullCache implements Cache {
+
+ /**
+ * @since 1.1
+ *
+ * {@inheritDoc}
+ */
+ public function fetch( $id ) {
+ return false;
+ }
+
+ /**
+ * @since 1.1
+ *
+ * {@inheritDoc}
+ */
+ public function contains( $id ) {
+ return false;
+ }
+
+ /**
+ * @since 1.1
+ *
+ * {@inheritDoc}
+ */
+ public function save( $id, $data, $ttl = 0 ) {}
+
+ /**
+ * @since 1.1
+ *
+ * {@inheritDoc}
+ */
+ public function delete( $id ) {}
+
+ /**
+ * @since 1.1
+ *
+ * {@inheritDoc}
+ */
+ public function getStats() {
+ return array();
+ }
+
+ /**
+ * @since 1.2
+ *
+ * {@inheritDoc}
+ */
+ public function getName() {
+ return '';
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/cache/src/ZendCache.php b/www/wiki/vendor/onoi/cache/src/ZendCache.php
new file mode 100644
index 00000000..183c4422
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/src/ZendCache.php
@@ -0,0 +1,139 @@
+<?php
+
+namespace Onoi\Cache;
+
+use Zend\Cache\Storage\StorageInterface;
+
+/**
+ * ZF2 Cache adapter
+ *
+ * @note http://framework.zend.com/manual/current/en/modules/zend.cache.storage.adapter.html
+ *
+ * @license GNU GPL v2+
+ * @since 1.1
+ *
+ * @author mwjames
+ */
+class ZendCache implements Cache {
+
+ /**
+ * @var StorageInterface
+ */
+ private $cache = null;
+
+ /**
+ * @var integer
+ */
+ private $cacheInserts = 0;
+
+ /**
+ * @var integer
+ */
+ private $cacheDeletes = 0;
+
+ /**
+ * @var integer
+ */
+ private $cacheHits = 0;
+
+ /**
+ * @var integer
+ */
+ private $cacheMisses = 0;
+
+ /**
+ * @since 1.1
+ *
+ * @param StorageInterface $cache
+ */
+ public function __construct( StorageInterface $cache ) {
+ $this->cache = $cache;
+ }
+
+ /**
+ * @since 1.1
+ *
+ * {@inheritDoc}
+ */
+ public function fetch( $id ) {
+
+ if ( $this->contains( $id ) ) {
+ $this->cacheHits++;
+ return $this->cache->getItem( $id );
+ }
+
+ $this->cacheMisses++;
+ return false;
+ }
+
+ /**
+ * @since 1.1
+ *
+ * {@inheritDoc}
+ */
+ public function contains( $id ) {
+ return $this->cache->hasItem( $id );
+ }
+
+ /**
+ * @note ZF2 doesn't support per-item ttl storage therefore we use
+ * https://github.com/zendframework/zf2/pull/5386#issuecomment-43191005
+ *
+ * - ttl with 0 means an item never expires
+ *
+ * @since 1.1
+ *
+ * {@inheritDoc}
+ */
+ public function save( $id, $data, $ttl = 0 ) {
+
+ $options = $this->cache->getOptions();
+ $oldTtl = $options->getTtl( $ttl );
+ $options->setTtl( $ttl );
+
+ try {
+ $this->cacheInserts++;
+ $this->cache->setItem( $id, $data );
+ } catch ( \Exception $e ) {
+ // Don't re-throw any exception, the consumer
+ // should not care about this
+ $this->cacheInserts--;
+ }
+
+ $options->setTtl( $oldTtl );
+ }
+
+ /**
+ * @since 1.1
+ *
+ * {@inheritDoc}
+ */
+ public function delete( $id ) {
+ $this->cacheDeletes++;
+ return $this->cache->removeItem( $id );
+ }
+
+ /**
+ * @since 1.1
+ *
+ * {@inheritDoc}
+ */
+ public function getStats() {
+ return array(
+ 'inserts' => $this->cacheInserts,
+ 'deletes' => $this->cacheDeletes,
+ 'hits' => $this->cacheHits,
+ 'misses' => $this->cacheMisses
+ );
+ }
+
+ /**
+ * @since 1.2
+ *
+ * {@inheritDoc}
+ */
+ public function getName() {
+ return __CLASS__ . '::' . get_class( $this->cache );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/cache/tests/bootstrap.php b/www/wiki/vendor/onoi/cache/tests/bootstrap.php
new file mode 100644
index 00000000..cc95b6bf
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/tests/bootstrap.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+
+error_reporting( E_ALL | E_STRICT );
+date_default_timezone_set( 'UTC' );
+
+if ( php_sapi_name() !== 'cli' ) {
+ die( 'Not an entry point' );
+}
+
+if ( is_readable( $path = __DIR__ . '/../vendor/autoload.php' ) ) {
+ print( "\nUsing the cache vendor autoloader ...\n\n" );
+} elseif ( is_readable( $path = __DIR__ . '/../../../autoload.php' ) ) {
+ print( "\nUsing another local vendor autoloader ...\n\n" );
+} else {
+ die( 'The test suite requires a Composer based deployement.' );
+}
+
+$autoLoader = require $path;
+$autoLoader->addPsr4( 'Onoi\\Cache\\Tests\\', __DIR__ . '/phpunit/Unit' );
+$autoLoader->addPsr4( 'Onoi\\Cache\\Tests\\Integration\\', __DIR__ . '/phpunit/Integration' );
diff --git a/www/wiki/vendor/onoi/cache/tests/phpunit/Integration/CacheIntegrationTestCase.php b/www/wiki/vendor/onoi/cache/tests/phpunit/Integration/CacheIntegrationTestCase.php
new file mode 100644
index 00000000..6c48c823
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/tests/phpunit/Integration/CacheIntegrationTestCase.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Onoi\Cache\Tests\Integration;
+
+/**
+ * @group onoi-cache
+ *
+ * @license GNU GPL v2+
+ * @since 1.2
+ *
+ * @author mwjames
+ */
+abstract class CacheIntegrationTestCase extends \PHPUnit_Framework_TestCase {
+
+ protected $cache;
+
+ public function testStorageAndRetrieval() {
+
+ $this->assertFalse(
+ $this->cache->contains( 'Foo' )
+ );
+
+ $this->cache->save( 'Foo', 'Bar', 42 );
+
+ $this->assertTrue(
+ $this->cache->contains( 'Foo' )
+ );
+
+ $this->assertEquals(
+ 'Bar',
+ $this->cache->fetch( 'Foo' )
+ );
+
+ $this->cache->delete( 'Foo' );
+
+ $this->assertFalse(
+ $this->cache->contains( 'Foo' )
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/cache/tests/phpunit/Integration/CompositeCacheIntegrationTest.php b/www/wiki/vendor/onoi/cache/tests/phpunit/Integration/CompositeCacheIntegrationTest.php
new file mode 100644
index 00000000..5c3e03c0
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/tests/phpunit/Integration/CompositeCacheIntegrationTest.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Onoi\Cache\Tests\Integration;
+
+use HashBagOStuff;
+use Doctrine\Common\Cache\ArrayCache;
+use Onoi\Cache\CacheFactory;
+
+/**
+ * @group onoi-cache
+ *
+ * @license GNU GPL v2+
+ * @since 1.2
+ *
+ * @author mwjames
+ */
+class CompositeCacheIntegrationTest extends CacheIntegrationTestCase {
+
+ protected $cache;
+
+ protected function setUp() {
+
+ $cache = array();
+ $cacheFactory = new CacheFactory();
+
+ $cache[] = $cacheFactory->newFixedInMemoryLruCache();
+
+ if ( class_exists( '\HashBagOStuff' ) ) {
+ $cache[] = $cacheFactory->newMediaWikiCache( new HashBagOStuff() );
+ }
+
+ if ( class_exists( '\Doctrine\Common\Cache\ArrayCache' ) ) {
+ $cache[] = $cacheFactory->newDoctrineCache( new ArrayCache() );
+ }
+
+ $this->cache = $cacheFactory->newCompositeCache( $cache );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/cache/tests/phpunit/Integration/DoctrineCacheIntegrationTest.php b/www/wiki/vendor/onoi/cache/tests/phpunit/Integration/DoctrineCacheIntegrationTest.php
new file mode 100644
index 00000000..48240061
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/tests/phpunit/Integration/DoctrineCacheIntegrationTest.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Onoi\Cache\Tests\Integration;
+
+use Doctrine\Common\Cache\ArrayCache;
+use Onoi\Cache\DoctrineCache;
+
+/**
+ * @group onoi-cache
+ *
+ * @license GNU GPL v2+
+ * @since 1.1
+ *
+ * @author mwjames
+ */
+class DoctrineCacheIntegrationTest extends CacheIntegrationTestCase {
+
+ protected $cache;
+
+ protected function setUp() {
+
+ if ( !class_exists( '\Doctrine\Common\Cache\ArrayCache' ) ) {
+ $this->markTestSkipped( 'ArrayCache is not available' );
+ }
+
+ $this->cache = new DoctrineCache( new ArrayCache() );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/cache/tests/phpunit/Integration/MediaWikiCacheIntegrationTest.php b/www/wiki/vendor/onoi/cache/tests/phpunit/Integration/MediaWikiCacheIntegrationTest.php
new file mode 100644
index 00000000..a6b52aa5
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/tests/phpunit/Integration/MediaWikiCacheIntegrationTest.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Onoi\Cache\Tests\Integration;
+
+use HashBagOStuff;
+use Onoi\Cache\MediaWikiCache;
+
+/**
+ * @group onoi-cache
+ *
+ * @license GNU GPL v2+
+ * @since 1.1
+ *
+ * @author mwjames
+ */
+class MediaWikiCacheIntegrationTest extends CacheIntegrationTestCase {
+
+ protected $cache;
+
+ protected function setUp() {
+
+ if ( !class_exists( '\HashBagOStuff' ) ) {
+ $this->markTestSkipped( 'HashBagOStuff is not available' );
+ }
+
+ $this->cache = new MediaWikiCache( new HashBagOStuff() );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/cache/tests/phpunit/Integration/ZendCacheIntegrationTest.php b/www/wiki/vendor/onoi/cache/tests/phpunit/Integration/ZendCacheIntegrationTest.php
new file mode 100644
index 00000000..c8136bf6
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/tests/phpunit/Integration/ZendCacheIntegrationTest.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Onoi\Cache\Tests\Integration;
+
+use Zend\Cache\StorageFactory;
+use Onoi\Cache\ZendCache;
+
+/**
+ * @group onoi-cache
+ *
+ * @license GNU GPL v2+
+ * @since 1.1
+ *
+ * @author mwjames
+ */
+class ZendCacheIntegrationTest extends CacheIntegrationTestCase {
+
+ protected $cache;
+
+ protected function setUp() {
+
+ if ( !class_exists( '\Zend\Cache\StorageFactory' ) ) {
+ $this->markTestSkipped( 'StorageFactory is not available' );
+ }
+
+ $memoryCache = StorageFactory::factory( array(
+ 'adapter' => array(
+ 'name' => 'memory',
+ 'options' => array( 'ttl' => 100 ),
+ ),
+ 'plugins' => array(
+ 'exception_handler' => array( 'throw_exceptions' => false ),
+ ),
+ ) );
+
+ $this->cache = new ZendCache( $memoryCache );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/cache/tests/phpunit/Unit/CacheFactoryTest.php b/www/wiki/vendor/onoi/cache/tests/phpunit/Unit/CacheFactoryTest.php
new file mode 100644
index 00000000..bcf1eb88
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/tests/phpunit/Unit/CacheFactoryTest.php
@@ -0,0 +1,143 @@
+<?php
+
+namespace Onoi\Cache\Tests;
+
+use Onoi\Cache\CacheFactory;
+
+/**
+ * @covers \Onoi\Cache\CacheFactory
+ *
+ * @group onoi-cache
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class CacheFactoryTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Cache\CacheFactory',
+ new CacheFactory()
+ );
+
+ $this->assertInstanceOf(
+ '\Onoi\Cache\CacheFactory',
+ CacheFactory::getInstance()
+ );
+ }
+
+ public function testClear() {
+
+ $instance = CacheFactory::getInstance();
+
+ $this->assertSame(
+ $instance,
+ CacheFactory::getInstance()
+ );
+
+ $instance->clear();
+
+ $this->assertNotSame(
+ $instance,
+ CacheFactory::getInstance()
+ );
+ }
+
+ public function testCanConstructMediaWikiCache() {
+
+ if ( !class_exists( '\BagOstuff' ) ) {
+ $this->markTestSkipped( 'BagOstuff interface is not available' );
+ }
+
+ $cache = $this->getMockBuilder( '\BagOstuff' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $instance = new CacheFactory();
+
+ $this->assertInstanceOf(
+ '\Onoi\Cache\MediaWikiCache',
+ $instance->newMediaWikiCache( $cache )
+ );
+ }
+
+ public function testCanConstructDoctrineCache() {
+
+ if ( !interface_exists( '\Doctrine\Common\Cache\Cache' ) ) {
+ $this->markTestSkipped( 'Doctrine cache interface is not available' );
+ }
+
+ $cache = $this->getMockBuilder( '\Doctrine\Common\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $instance = new CacheFactory();
+
+ $this->assertInstanceOf(
+ '\Onoi\Cache\DoctrineCache',
+ $instance->newDoctrineCache( $cache )
+ );
+ }
+
+ public function testCanConstructFixedInMemoryLruCache() {
+
+ $instance = new CacheFactory();
+
+ $this->assertInstanceOf(
+ '\Onoi\Cache\FixedInMemoryLruCache',
+ $instance->newFixedInMemoryLruCache( 1 )
+ );
+
+ // Legacy
+ $this->assertInstanceOf(
+ '\Onoi\Cache\FixedInMemoryLruCache',
+ $instance->newFixedInMemoryCache( 1 )
+ );
+ }
+
+ public function testCanConstructCompositeCache() {
+
+ $instance = new CacheFactory();
+
+ $cache = array(
+ $instance->newFixedInMemoryCache()
+ );
+
+ $this->assertInstanceOf(
+ '\Onoi\Cache\CompositeCache',
+ $instance->newCompositeCache( $cache )
+ );
+ }
+
+ public function testCanConstructNullCache() {
+
+ $instance = new CacheFactory();
+
+ $this->assertInstanceOf(
+ '\Onoi\Cache\NullCache',
+ $instance->newNullCache()
+ );
+ }
+
+ public function testCanConstructZendCache() {
+
+ if ( !interface_exists( '\Zend\Cache\Storage\StorageInterface' ) ) {
+ $this->markTestSkipped( 'StorageInterface is not available' );
+ }
+
+ $cache = $this->getMockBuilder( '\Zend\Cache\Storage\StorageInterface' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $instance = new CacheFactory();
+
+ $this->assertInstanceOf(
+ '\Onoi\Cache\ZendCache',
+ $instance->newZendCache( $cache )
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/cache/tests/phpunit/Unit/CompositeCacheTest.php b/www/wiki/vendor/onoi/cache/tests/phpunit/Unit/CompositeCacheTest.php
new file mode 100644
index 00000000..4ea2bdb7
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/tests/phpunit/Unit/CompositeCacheTest.php
@@ -0,0 +1,251 @@
+<?php
+
+namespace Onoi\Cache\Tests;
+
+use Onoi\Cache\CompositeCache;
+
+/**
+ * @covers \Onoi\Cache\CompositeCache
+ *
+ * @group onoi-cache
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class CompositeCacheTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $cache = $this->getMockBuilder( '\Onoi\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->assertInstanceOf(
+ '\Onoi\Cache\CompositeCache',
+ new CompositeCache( array( $cache ) )
+ );
+ }
+
+ public function testConstructForInvalidArrayKeyThrowsException() {
+
+ $cache = $this->getMockBuilder( '\Onoi\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->setExpectedException( 'RuntimeException' );
+ new CompositeCache( array( 'cache' => $cache ) );
+ }
+
+ public function testConstructForInvalidCacheInstanceThrowsException() {
+
+ $this->setExpectedException( 'RuntimeException' );
+ new CompositeCache( array( 'Foo' ) );
+ }
+
+ public function testGetName() {
+
+ $first = $this->getMockBuilder( '\Onoi\Cache\ZendCache' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $second = $this->getMockBuilder( '\Onoi\Cache\DoctrineCache' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $instance = new CompositeCache( array( $first, $second ) );
+
+ $this->assertInternalType(
+ 'string',
+ $instance->getName()
+ );
+ }
+
+ public function testSave() {
+
+ $first = $this->getMockBuilder( '\Onoi\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $first->expects( $this->once() )
+ ->method( 'save' )
+ ->with(
+ $this->equalTo( 'Foo' ),
+ $this->anything() );
+
+ $second = $this->getMockBuilder( '\Onoi\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $second->expects( $this->once() )
+ ->method( 'save' )
+ ->with(
+ $this->equalTo( 'Foo' ),
+ $this->anything() );
+
+ $instance = new CompositeCache( array( $first, $second ) );
+ $instance->save( 'Foo', 'Bar' );
+ }
+
+ public function testDelete() {
+
+ $first = $this->getMockBuilder( '\Onoi\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $first->expects( $this->once() )
+ ->method( 'delete' )
+ ->with(
+ $this->equalTo( 'Foo' ) );
+
+ $second = $this->getMockBuilder( '\Onoi\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $second->expects( $this->once() )
+ ->method( 'delete' )
+ ->with(
+ $this->equalTo( 'Foo' ) );
+
+ $instance = new CompositeCache( array( $first, $second ) );
+ $instance->delete( 'Foo' );
+ }
+
+ public function testGetStats() {
+
+ $first = $this->getMockBuilder( '\Onoi\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $first->expects( $this->once() )
+ ->method( 'getStats' );
+
+ $second = $this->getMockBuilder( '\Onoi\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $second->expects( $this->once() )
+ ->method( 'getStats' );
+
+ $instance = new CompositeCache( array( $first, $second ) );
+
+ $this->assertInternalType(
+ 'array',
+ $instance->getStats( 'Foo' )
+ );
+ }
+
+ public function testContainsIsTrueForFirstCache() {
+
+ $first = $this->getMockBuilder( '\Onoi\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $first->expects( $this->once() )
+ ->method( 'contains' )
+ ->with(
+ $this->equalTo( 'Foo' ) )
+ ->will( $this->returnValue( true ) );
+
+ $second = $this->getMockBuilder( '\Onoi\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $second->expects( $this->never() )
+ ->method( 'contains' );
+
+ $instance = new CompositeCache( array( $first, $second ) );
+
+ $this->assertTrue(
+ $instance->contains( 'Foo' )
+ );
+ }
+
+ public function testContainsIsFalseForFirstCache() {
+
+ $first = $this->getMockBuilder( '\Onoi\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $first->expects( $this->once() )
+ ->method( 'contains' );
+
+ $second = $this->getMockBuilder( '\Onoi\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $second->expects( $this->once() )
+ ->method( 'contains' )
+ ->with(
+ $this->equalTo( 'Foo' ) )
+ ->will( $this->returnValue( false ) );
+
+ $instance = new CompositeCache( array( $first, $second ) );
+
+ $this->assertFalse(
+ $instance->contains( 'Foo' )
+ );
+ }
+
+ public function testFetchToReturnNoResultForFirstCache() {
+
+ $first = $this->getMockBuilder( '\Onoi\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $first->expects( $this->once() )
+ ->method( 'contains' );
+
+ $first->expects( $this->once() )
+ ->method( 'save' )
+ ->with(
+ $this->equalTo( 'Foo' ),
+ $this->anything() );
+
+ $second = $this->getMockBuilder( '\Onoi\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $second->expects( $this->once() )
+ ->method( 'contains' )
+ ->will( $this->returnValue( true ) );
+
+ $second->expects( $this->once() )
+ ->method( 'fetch' )
+ ->with(
+ $this->equalTo( 'Foo' ) )
+ ->will( $this->returnValue( 'Bar' ) );
+
+ $instance = new CompositeCache( array( $first, $second ) );
+
+ $this->assertEquals(
+ 'Bar',
+ $instance->fetch( 'Foo' )
+ );
+ }
+
+ public function testFetchForToReturnNoResultForAll() {
+
+ $first = $this->getMockBuilder( '\Onoi\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $first->expects( $this->once() )
+ ->method( 'contains' );
+
+ $second = $this->getMockBuilder( '\Onoi\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $second->expects( $this->once() )
+ ->method( 'contains' );
+
+ $instance = new CompositeCache( array( $first, $second ) );
+
+ $this->assertFalse(
+ $instance->fetch( 'Foo' )
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/cache/tests/phpunit/Unit/DoctrineCacheTest.php b/www/wiki/vendor/onoi/cache/tests/phpunit/Unit/DoctrineCacheTest.php
new file mode 100644
index 00000000..9ce7fcf9
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/tests/phpunit/Unit/DoctrineCacheTest.php
@@ -0,0 +1,115 @@
+<?php
+
+namespace Onoi\Cache\Tests;
+
+use Onoi\Cache\DoctrineCache;
+
+/**
+ * @covers \Onoi\Cache\DoctrineCache
+ *
+ * @group onoi-cache
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class DoctrineCacheTest extends \PHPUnit_Framework_TestCase {
+
+ private $cache;
+
+ protected function setUp() {
+ parent::setUp();
+
+ if ( !interface_exists( '\Doctrine\Common\Cache\Cache' ) ) {
+ $this->markTestSkipped( 'Doctrine cache interface is not available' );
+ }
+
+ $this->cache = $this->getMockBuilder( '\Doctrine\Common\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+ }
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Cache\DoctrineCache',
+ new DoctrineCache( $this->cache )
+ );
+ }
+
+ public function testGetName() {
+
+ $instance = new DoctrineCache( $this->cache );
+
+ $this->assertInternalType(
+ 'string',
+ $instance->getName()
+ );
+ }
+
+ public function testSave() {
+
+ $this->cache->expects( $this->once() )
+ ->method( 'save' )
+ ->with(
+ $this->equalTo( 'Foo' ),
+ $this->anything(),
+ $this->equalTo( 42 ) );
+
+ $instance = new DoctrineCache( $this->cache );
+ $instance->save( 'Foo', 'Bar', 42 );
+ }
+
+ public function testDelete() {
+
+ $this->cache->expects( $this->once() )
+ ->method( 'delete' )
+ ->with(
+ $this->equalTo( 'Foo' ) );
+
+ $instance = new DoctrineCache( $this->cache );
+ $instance->delete( 'Foo' );
+ }
+
+ public function testContains() {
+
+ $this->cache->expects( $this->once() )
+ ->method( 'contains' )
+ ->with(
+ $this->equalTo( 'Foo' ) );
+
+ $instance = new DoctrineCache( $this->cache );
+ $instance->contains( 'Foo' );
+ }
+
+ public function testFetch() {
+
+ $this->cache->expects( $this->once() )
+ ->method( 'fetch' )
+ ->with(
+ $this->equalTo( 'Foo' ) )
+ ->will( $this->returnValue( 'Bar' ) );
+
+ $instance = new DoctrineCache( $this->cache );
+
+ $this->assertEquals(
+ 'Bar',
+ $instance->fetch( 'Foo' )
+ );
+ }
+
+ public function testGetStats() {
+
+ $this->cache->expects( $this->once() )
+ ->method( 'getStats' );
+
+ $instance = new DoctrineCache( $this->cache );
+
+ $this->assertEquals(
+ null,
+ $instance->getStats()
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/cache/tests/phpunit/Unit/FixedInMemoryLruCacheTest.php b/www/wiki/vendor/onoi/cache/tests/phpunit/Unit/FixedInMemoryLruCacheTest.php
new file mode 100644
index 00000000..fb85db01
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/tests/phpunit/Unit/FixedInMemoryLruCacheTest.php
@@ -0,0 +1,152 @@
+<?php
+
+namespace Onoi\Cache\Tests;
+
+use Onoi\Cache\FixedInMemoryLruCache;
+use Onoi\Cache\FixedInMemoryCache;
+
+/**
+ * @covers \Onoi\Cache\FixedInMemoryLruCache
+ *
+ * @group onoi-cache
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class FixedInMemoryLruCacheTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Cache\FixedInMemoryLruCache',
+ new FixedInMemoryLruCache()
+ );
+ }
+
+ public function testGetName() {
+
+ $instance = new FixedInMemoryLruCache( 5 );
+
+ $this->assertInternalType(
+ 'string',
+ $instance->getName()
+ );
+ }
+
+ public function testItemRemoval() {
+
+ $instance = new FixedInMemoryLruCache( 5 );
+
+ $instance->save( 'foo', array( 'foo' ) );
+ $instance->save( 42, null );
+
+ $this->assertTrue( $instance->contains( 'foo' ) );
+ $this->assertTrue( $instance->contains( 42 ) );
+
+ $stats = $instance->getStats();
+ $this->assertEquals(
+ 2,
+ $stats['count']
+ );
+
+ $instance->delete( 'foo' );
+
+ $this->assertFalse( $instance->contains( 'foo' ) );
+ $this->assertFalse( $instance->delete( 'foo' ) );
+
+ $stats = $instance->getStats();
+
+ $this->assertEquals(
+ 1,
+ $stats['count']
+ );
+
+ $this->assertEquals(
+ 1,
+ $stats['deletes']
+ );
+ }
+
+ public function testLeastRecentlyUsedShiftForLimitedCacheSize() {
+
+ $instance = new FixedInMemoryLruCache( 5 );
+ $instance->save( 'abcde', array( 'abcde' ) );
+
+ $this->assertEquals(
+ array( 'abcde' ),
+ $instance->fetch( 'abcde' )
+ );
+
+ foreach ( array( 'éèêë', 'アイウエオ', 'АБВГД', 'αβγδε', '12345' ) as $alphabet ) {
+ $instance->save( $alphabet, array( $alphabet ) );
+ }
+
+ // 'éèêë' was added and removes 'abcde' from the cache
+ $this->assertFalse( $instance->fetch( 'abcde' ) );
+
+ $stats = $instance->getStats();
+
+ $this->assertEquals(
+ 5,
+ $stats['count']
+ );
+
+ $this->assertEquals(
+ 6,
+ $stats['inserts']
+ );
+
+ // 'éèêë' moves to the top (last postion as most recently used) and
+ // 'アイウエオ' becomes the next LRU candidate
+ $this->assertEquals(
+ array( 'éèêë' ),
+ $instance->fetch( 'éèêë' )
+ );
+
+ $instance->save( '@#$%&', '@#$%&' );
+ $this->assertFalse( $instance->fetch( 'アイウエオ' ) );
+
+ // АБВГД would be the next LRU slot but setting it again will move it to MRU
+ // and push αβγδε into the next LRU position
+ $instance->save( 'АБВГД', 'АБВГД' );
+
+ $instance->save( '-+=<>', '-+=<>' );
+ $this->assertFalse( $instance->fetch( 'αβγδε' ) );
+
+ $stats = $instance->getStats();
+
+ $this->assertEquals(
+ 5,
+ $stats['count']
+ );
+ }
+
+ public function testFetchTtlBasedItem() {
+
+ $instance = new FixedInMemoryLruCache( 5 );
+
+ $instance->save( 'foo', 'Bar', 3 );
+
+ $this->assertTrue(
+ $instance->contains( 'foo' )
+ );
+
+ $this->assertEquals(
+ 'Bar',
+ $instance->fetch( 'foo' )
+ );
+
+ sleep( 4 );
+
+ $this->assertFalse(
+ $instance->contains( 'foo' )
+ );
+
+ $this->assertFalse(
+ $instance->fetch( 'foo' )
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/cache/tests/phpunit/Unit/MediaWikiCacheTest.php b/www/wiki/vendor/onoi/cache/tests/phpunit/Unit/MediaWikiCacheTest.php
new file mode 100644
index 00000000..5e1179c6
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/tests/phpunit/Unit/MediaWikiCacheTest.php
@@ -0,0 +1,180 @@
+<?php
+
+namespace Onoi\Cache\Tests;
+
+use Onoi\Cache\MediaWikiCache;
+
+/**
+ * @covers \Onoi\Cache\MediaWikiCache
+ *
+ * @group onoi-cache
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class MediaWikiCacheTest extends \PHPUnit_Framework_TestCase {
+
+ private $cache;
+
+ protected function setUp() {
+ parent::setUp();
+
+ if ( !class_exists( '\BagOstuff' ) ) {
+ $this->markTestSkipped( 'BagOstuff interface is not available' );
+ }
+
+ $this->cache = $this->getMockBuilder( '\BagOstuff' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+ }
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Cache\MediaWikiCache',
+ new MediaWikiCache( $this->cache )
+ );
+ }
+
+ public function testGetName() {
+
+ $instance = new MediaWikiCache( $this->cache );
+
+ $this->assertInternalType(
+ 'string',
+ $instance->getName()
+ );
+ }
+
+ public function testSave() {
+
+ $this->cache->expects( $this->once() )
+ ->method( 'set' )
+ ->with(
+ $this->equalTo( 'Foo' ),
+ $this->anything(),
+ $this->equalTo( 42 ) );
+
+ $instance = new MediaWikiCache( $this->cache );
+ $instance->save( 'Foo', 'Bar', 42 );
+
+ $this->assertFalse(
+ $instance->contains( 'Foo' )
+ );
+ }
+
+ public function testDelete() {
+
+ $this->cache->expects( $this->once() )
+ ->method( 'delete' )
+ ->with(
+ $this->equalTo( 'Foo' ) );
+
+ $instance = new MediaWikiCache( $this->cache );
+ $instance->delete( 'Foo' );
+
+ $this->assertFalse(
+ $instance->contains( 'Foo' )
+ );
+
+ $expected = array(
+ 'inserts' => 0,
+ 'deletes' => 1,
+ 'hits' => 0,
+ 'misses' => 0
+ );
+
+ $this->assertEquals(
+ $expected,
+ $instance->getStats()
+ );
+ }
+
+ public function testContains() {
+
+ $this->cache->expects( $this->once() )
+ ->method( 'get' )
+ ->with(
+ $this->equalTo( 'Foo' ) );
+
+ $instance = new MediaWikiCache( $this->cache );
+ $instance->contains( 'Foo' );
+
+ // Internally the access is cached
+ $this->assertTrue(
+ $instance->contains( 'Foo' )
+ );
+ }
+
+ public function testFetch() {
+
+ $this->cache->expects( $this->once() )
+ ->method( 'get' )
+ ->with(
+ $this->equalTo( 'Foo' ) )
+ ->will( $this->returnValue( 'Bar' ) );
+
+ $instance = new MediaWikiCache( $this->cache );
+
+ $this->assertEquals(
+ 'Bar',
+ $instance->fetch( 'Foo' )
+ );
+
+ // Internally the access is cached
+ $this->assertTrue(
+ $instance->contains( 'Foo' )
+ );
+ }
+
+ public function testFetchForFalse() {
+
+ $this->cache->expects( $this->once() )
+ ->method( 'get' );
+
+ $instance = new MediaWikiCache( $this->cache );
+
+ $this->assertFalse(
+ $instance->fetch( 'Bar' )
+ );
+
+ $expected = array(
+ 'inserts' => 0,
+ 'deletes' => 0,
+ 'hits' => 0,
+ 'misses' => 1
+ );
+
+ $this->assertEquals(
+ $expected,
+ $instance->getStats()
+ );
+ }
+
+ public function testGetStats() {
+
+ $this->cache->expects( $this->once() )
+ ->method( 'get' )
+ ->with(
+ $this->equalTo( 'Foo' ) )
+ ->will( $this->returnValue( 'Bar' ) );
+
+ $instance = new MediaWikiCache( $this->cache );
+ $instance->fetch( 'Foo' );
+
+ $expected = array(
+ 'inserts' => 0,
+ 'deletes' => 0,
+ 'hits' => 1,
+ 'misses' => 0
+ );
+
+ $this->assertEquals(
+ $expected,
+ $instance->getStats()
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/cache/tests/phpunit/Unit/NullCacheTest.php b/www/wiki/vendor/onoi/cache/tests/phpunit/Unit/NullCacheTest.php
new file mode 100644
index 00000000..4929a23e
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/tests/phpunit/Unit/NullCacheTest.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace Onoi\Cache\Tests;
+
+use Onoi\Cache\NullCache;
+
+/**
+ * @covers \Onoi\Cache\NullCache
+ *
+ * @group onoi-cache
+ *
+ * @license GNU GPL v2+
+ * @since 1.1
+ *
+ * @author mwjames
+ */
+class NullCacheTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Cache\NullCache',
+ new NullCache()
+ );
+ }
+
+ public function testGetName() {
+
+ $instance = new NullCache();
+
+ $this->assertEmpty(
+ $instance->getName()
+ );
+ }
+
+ public function testSave() {
+
+ $instance = new NullCache();
+ $instance->save( 'Foo', 'Bar', 42 );
+
+ $this->assertFalse(
+ $instance->contains( 'Foo' )
+ );
+ }
+
+ public function testDelete() {
+
+ $instance = new NullCache();
+ $instance->delete( 'Foo' );
+
+ $this->assertFalse(
+ $instance->contains( 'Foo' )
+ );
+ }
+
+ public function testContains() {
+
+ $instance = new NullCache();
+
+ $this->assertFalse(
+ $instance->contains( 'Foo' )
+ );
+ }
+
+ public function testFetch() {
+
+ $instance = new NullCache();
+
+ $this->assertFalse(
+ $instance->fetch( 'Foo' )
+ );
+ }
+
+ public function testGetStats() {
+
+ $instance = new NullCache();
+
+ $this->assertEmpty(
+ $instance->getStats()
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/cache/tests/phpunit/Unit/ZendCacheTest.php b/www/wiki/vendor/onoi/cache/tests/phpunit/Unit/ZendCacheTest.php
new file mode 100644
index 00000000..80564cbf
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/tests/phpunit/Unit/ZendCacheTest.php
@@ -0,0 +1,196 @@
+<?php
+
+namespace Onoi\Cache\Tests;
+
+use Onoi\Cache\ZendCache;
+
+/**
+ * @covers \Onoi\Cache\ZendCache
+ *
+ * @group onoi-cache
+ *
+ * @license GNU GPL v2+
+ * @since 1.1
+ *
+ * @author mwjames
+ */
+class ZendCacheTest extends \PHPUnit_Framework_TestCase {
+
+ private $cache;
+
+ protected function setUp() {
+ parent::setUp();
+
+ if ( !interface_exists( '\Zend\Cache\Storage\StorageInterface' ) ) {
+ $this->markTestSkipped( 'Zend StorageInterface is not available' );
+ }
+
+ $this->cache = $this->getMockBuilder( '\Zend\Cache\Storage\StorageInterface' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+ }
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Cache\ZendCache',
+ new ZendCache( $this->cache )
+ );
+ }
+
+ public function testGetName() {
+
+ $instance = new ZendCache( $this->cache );
+
+ $this->assertInternalType(
+ 'string',
+ $instance->getName()
+ );
+ }
+
+ public function testSave() {
+
+ $adapterOptions = $this->getMockBuilder( '\Zend\Cache\Storage\Adapter\AdapterOptions' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->cache->expects( $this->any() )
+ ->method( 'getOptions' )
+ ->will( $this->returnValue( $adapterOptions ) );
+
+ $this->cache->expects( $this->once() )
+ ->method( 'setItem' )
+ ->with(
+ $this->equalTo( 'Foo' ),
+ $this->equalTo( 'Bar' ) );
+
+ $instance = new ZendCache( $this->cache );
+ $instance->save( 'Foo', 'Bar', 42 );
+
+ $expected = array(
+ 'inserts' => 1,
+ 'deletes' => 0,
+ 'hits' => 0,
+ 'misses' => 0
+ );
+
+ $this->assertEquals(
+ $expected,
+ $instance->getStats()
+ );
+ }
+
+ public function testSaveBySetItemThrowingException() {
+
+ $adapterOptions = $this->getMockBuilder( '\Zend\Cache\Storage\Adapter\AdapterOptions' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->cache->expects( $this->any() )
+ ->method( 'getOptions' )
+ ->will( $this->returnValue( $adapterOptions ) );
+
+ $this->cache->expects( $this->once() )
+ ->method( 'setItem' )
+ ->with(
+ $this->equalTo( 'Foo' ),
+ $this->equalTo( 'Bar' ) )
+ ->will( $this->throwException( new \Exception() ) );
+
+ $instance = new ZendCache( $this->cache );
+ $instance->save( 'Foo', 'Bar', 42 );
+
+ $expected = array(
+ 'inserts' => 0,
+ 'deletes' => 0,
+ 'hits' => 0,
+ 'misses' => 0
+ );
+
+ $this->assertEquals(
+ $expected,
+ $instance->getStats()
+ );
+ }
+
+ public function testDelete() {
+
+ $this->cache->expects( $this->once() )
+ ->method( 'removeItem' )
+ ->with(
+ $this->equalTo( 'Foo' ) );
+
+ $instance = new ZendCache( $this->cache );
+ $instance->delete( 'Foo' );
+ }
+
+ public function testContains() {
+
+ $this->cache->expects( $this->once() )
+ ->method( 'hasItem' )
+ ->with(
+ $this->equalTo( 'Foo' ) );
+
+ $instance = new ZendCache( $this->cache );
+ $instance->contains( 'Foo' );
+ }
+
+ public function testFetch() {
+
+ $this->cache->expects( $this->once() )
+ ->method( 'hasItem' )
+ ->will( $this->returnValue( true ) );
+
+ $this->cache->expects( $this->once() )
+ ->method( 'getItem' )
+ ->with(
+ $this->equalTo( 'Foo' ) )
+ ->will( $this->returnValue( 'Bar' ) );
+
+ $instance = new ZendCache( $this->cache );
+
+ $this->assertEquals(
+ 'Bar',
+ $instance->fetch( 'Foo' )
+ );
+ }
+
+ public function testFetchForNonExistingItem() {
+
+ $this->cache->expects( $this->once() )
+ ->method( 'hasItem' )
+ ->will( $this->returnValue( false ) );
+
+ $this->cache->expects( $this->never() )
+ ->method( 'getItem' );
+
+ $instance = new ZendCache( $this->cache );
+
+ $this->assertFalse(
+ $instance->fetch( 'Foo' )
+ );
+
+ $expected = array(
+ 'inserts' => 0,
+ 'deletes' => 0,
+ 'hits' => 0,
+ 'misses' => 1
+ );
+
+ $this->assertEquals(
+ $expected,
+ $instance->getStats()
+ );
+ }
+
+ public function testGetStats() {
+
+ $instance = new ZendCache( $this->cache );
+
+ $this->assertInternalType(
+ 'array',
+ $instance->getStats()
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/cache/tests/travis/install-cache.sh b/www/wiki/vendor/onoi/cache/tests/travis/install-cache.sh
new file mode 100644
index 00000000..dc676811
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/tests/travis/install-cache.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+set -ex
+
+BASE_PATH=$(pwd)
+MW_INSTALL_PATH=$BASE_PATH/../mw
+
+cd $MW_INSTALL_PATH
+
+if [ "$MW" != "" ]
+then
+ echo -e "Running MW root composer install build on $TRAVIS_BRANCH \n"
+fi
+
+if [ "$CACHE" != "" ]
+then
+ composer require 'onoi/cache='$CACHE --prefer-source --update-with-dependencies
+else
+ composer init --stability dev
+ composer require onoi/cache "dev-master" --prefer-source --dev
+
+ cd vendor/onoi/cache
+
+ # Pull request number, "false" if it's not a pull request
+ # After the install via composer an additional get fetch is carried out to
+ # update th repository to make sure that the latests code changes are
+ # deployed for testing
+ if [ "$TRAVIS_PULL_REQUEST" != "false" ]
+ then
+ git fetch origin +refs/pull/"$TRAVIS_PULL_REQUEST"/merge:
+ git checkout -qf FETCH_HEAD
+ else
+ git fetch origin "$TRAVIS_BRANCH"
+ git checkout -qf FETCH_HEAD
+ fi
+
+fi
+
+if [ "$DOCTRINE" != "" ]
+then
+ cd $MW_INSTALL_PATH
+
+ composer require 'doctrine/cache='$DOCTRINE --prefer-source --update-with-dependencies
+fi
+
+if [ "$ZENDCACHE" != "" ]
+then
+ cd $MW_INSTALL_PATH
+
+ composer require 'zendframework/zend-cache='$ZENDCACHE --prefer-source --update-with-dependencies
+fi
diff --git a/www/wiki/vendor/onoi/cache/tests/travis/install-mediawiki.sh b/www/wiki/vendor/onoi/cache/tests/travis/install-mediawiki.sh
new file mode 100644
index 00000000..8a14898b
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/tests/travis/install-mediawiki.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+set -ex
+
+cd ..
+
+## Use sha (master@5cc1f1d) to download a particular commit to avoid breakages
+## introduced by MediaWiki core
+if [[ "$MW" == *@* ]]
+then
+ arrMw=(${MW//@/ })
+ MW=${arrMw[0]}
+ SOURCE=${arrMw[1]}
+else
+ MW=$MW
+ SOURCE=$MW
+fi
+
+if [ "$MW" != "" ]
+then
+ wget https://github.com/wikimedia/mediawiki/archive/$SOURCE.tar.gz -O $MW.tar.gz
+
+ tar -zxf $MW.tar.gz
+ mv mediawiki-* mw
+
+ cd mw
+
+ composer install
+
+ mysql -e 'create database its_a_mw;'
+ php maintenance/install.php --dbtype $DB --dbuser root --dbname its_a_mw --dbpath $(pwd) --pass nyan TravisWiki admin --scriptpath /TravisWiki
+
+ echo 'error_reporting(E_ALL| E_STRICT);' >> LocalSettings.php
+ echo 'ini_set("display_errors", 1);' >> LocalSettings.php
+ echo '$wgShowExceptionDetails = true;' >> LocalSettings.php
+ echo '$wgDevelopmentWarnings = true;' >> LocalSettings.php
+ echo "putenv( 'MW_INSTALL_PATH=$(pwd)' );" >> LocalSettings.php
+
+ php maintenance/update.php --quick
+else
+ mkdir mw
+fi
diff --git a/www/wiki/vendor/onoi/cache/tests/travis/run-tests.sh b/www/wiki/vendor/onoi/cache/tests/travis/run-tests.sh
new file mode 100644
index 00000000..59c4b281
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/tests/travis/run-tests.sh
@@ -0,0 +1,19 @@
+#! /bin/bash
+set -ex
+
+BASE_PATH=$(pwd)
+MW_INSTALL_PATH=$BASE_PATH/../mw
+
+cd $MW_INSTALL_PATH/vendor/onoi/cache
+
+if [ "$TYPE" == "coverage" ]
+then
+ if [ "$MW" != "" ]
+ then
+ php ../../../tests/phpunit/phpunit.php -c phpunit.xml.dist --coverage-clover $BASE_PATH/build/coverage.clover
+ else
+ composer phpunit -- --coverage-clover $BASE_PATH/build/coverage.clover
+ fi
+else
+ composer phpunit
+fi
diff --git a/www/wiki/vendor/onoi/cache/tests/travis/upload-coverage-report.sh b/www/wiki/vendor/onoi/cache/tests/travis/upload-coverage-report.sh
new file mode 100644
index 00000000..aff95cf7
--- /dev/null
+++ b/www/wiki/vendor/onoi/cache/tests/travis/upload-coverage-report.sh
@@ -0,0 +1,10 @@
+#! /bin/bash
+set -ex
+
+BASE_PATH=$(pwd)
+
+if [ "$TYPE" == "coverage" ]
+then
+ wget https://scrutinizer-ci.com/ocular.phar
+ php ocular.phar code-coverage:upload --format=php-clover $BASE_PATH/build/coverage.clover
+fi \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/callback-container/.gitignore b/www/wiki/vendor/onoi/callback-container/.gitignore
new file mode 100644
index 00000000..09ec1c53
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/.gitignore
@@ -0,0 +1,10 @@
+*~
+*.kate-swp
+
+!.*
+.idea/
+
+composer.phar
+composer.lock
+
+vendor/ \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/callback-container/.scrutinizer.yml b/www/wiki/vendor/onoi/callback-container/.scrutinizer.yml
new file mode 100644
index 00000000..dfdcb235
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/.scrutinizer.yml
@@ -0,0 +1,18 @@
+filter:
+ excluded_paths:
+ - 'vendor/*'
+
+tools:
+ php_mess_detector:
+ config:
+ controversial_rules: { superglobals: false }
+ php_cpd: true
+ php_pdepend: true
+ php_code_coverage: false
+ php_code_sniffer: true
+ php_cs_fixer: true
+ php_loc: true
+ php_analyzer: true
+ sensiolabs_security_checker: true
+ external_code_coverage:
+ timeout: 600
diff --git a/www/wiki/vendor/onoi/callback-container/.travis.yml b/www/wiki/vendor/onoi/callback-container/.travis.yml
new file mode 100644
index 00000000..ad94b3a9
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/.travis.yml
@@ -0,0 +1,23 @@
+# http://docs.travis-ci.com/user/workers/container-based-infrastructure/
+sudo: false
+
+language: php
+
+matrix:
+ include:
+ - env: TYPE=coverage
+ php: 5.6
+ - env: TYPE=UNIT;
+ php: 5.5
+ - env: TYPE=UNIT;
+ php: hhvm
+ - env: TYPE=UNIT;
+ php: 7
+ - env: TYPE=UNIT;
+ php: 7.1
+
+script:
+ - bash ./tests/travis/run-tests.sh
+
+after_success:
+ - bash ./tests/travis/upload-coverage-report.sh
diff --git a/www/wiki/vendor/onoi/callback-container/LICENSE b/www/wiki/vendor/onoi/callback-container/LICENSE
new file mode 100644
index 00000000..d6a93266
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/LICENSE
@@ -0,0 +1,340 @@
+GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ {description}
+ Copyright (C) {year} {fullname}
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ {signature of Ty Coon}, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
+
diff --git a/www/wiki/vendor/onoi/callback-container/README.md b/www/wiki/vendor/onoi/callback-container/README.md
new file mode 100644
index 00000000..f5d02d33
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/README.md
@@ -0,0 +1,91 @@
+# Callback container
+
+[![Build Status](https://secure.travis-ci.org/onoi/callback-container.svg?branch=master)](http://travis-ci.org/onoi/callback-container)
+[![Code Coverage](https://scrutinizer-ci.com/g/onoi/callback-container/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/onoi/callback-container/?branch=master)
+[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/onoi/callback-container/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/onoi/callback-container/?branch=master)
+[![Latest Stable Version](https://poser.pugx.org/onoi/callback-container/version.png)](https://packagist.org/packages/onoi/callback-container)
+[![Packagist download count](https://poser.pugx.org/onoi/callback-container/d/total.png)](https://packagist.org/packages/onoi/callback-container)
+[![Dependency Status](https://www.versioneye.com/php/onoi:callback-container/badge.png)](https://www.versioneye.com/php/onoi:callback-container)
+
+A simple object instantiator to lazy load registered callback handlers. Part of the
+code base has been extracted from [Semantic MediaWiki][smw] and is now being
+deployed as independent library.
+
+## Requirements
+
+* PHP 5.5
+* HHVM 3.5 or later
+
+## Installation
+
+The recommended installation method for this library is to add
+the dependency to your [composer.json][composer].
+
+```json
+{
+ "require": {
+ "onoi/callback-container": "~2.0"
+ }
+}
+```
+
+## Usage
+
+```php
+use Onoi\CallbackContainer\CallbackContainerFactory;
+
+$callbackContainerFactory = new CallbackContainerFactory();
+$containerBuilder = $callbackContainerFactory->newCallbackContainerBuilder();
+
+$containerBuilder->registerCallbackContainer( ... );
+
+$service = $containerBuilder->create( ... );
+$service = $containerBuilder->singleton( ... );
+
+```
+
+This [document](/docs/usage.md) contains detailed examples on how to use the `CallbackContainerFactory`
+and `ContainerBuilder`.
+
+## Contribution and support
+
+If you want to contribute work to the project please subscribe to the
+developers mailing list and have a look at the [contribution guidelinee](/CONTRIBUTING.md). A list
+of people who have made contributions in the past can be found [here][contributors].
+
+* [File an issue](https://github.com/onoi/callback-container/issues)
+* [Submit a pull request](https://github.com/onoi/callback-container/pulls)
+
+### Tests
+
+The library provides unit tests that covers the core-functionality normally run by the
+[continues integration platform][travis]. Tests can also be executed manually using the
+`composer phpunit` command from the root directory.
+
+## Release notes
+
+- 2.0.0 (2017-02-18)
+ - Requires PHP 5.5+
+ - Added `CallbackContainerFactory`
+ - Added `CallbackContainerBuilder::registerAlias`
+ - Added `CallbackContainerBuilder::registerFromFile` to allow loading callback
+ definitions from a file
+
+- 1.1.0 (2016-04-30)
+ - Fixed issue in `registeredObject` for when a singleton override contained a `null` argument
+ - Deprecated the `CallbackLoader` interface in favour of the `CallbackInstantiator` interface
+ - Deprecated the `NullCallbackLoader` class in favour of the `NullCallbackInstantiator` class
+
+- 1.0.0 (2015-09-08)
+ - Added the `CallbackContainer` and `CallbackLoader` interface
+ - Added the `DeferredCallbackLoader` and `NullCallbackLoader` implementation
+
+## License
+
+[GNU General Public License 2.0 or later][license].
+
+[composer]: https://getcomposer.org/
+[contributors]: https://github.com/onoi/callback-container/graphs/contributors
+[license]: https://www.gnu.org/copyleft/gpl.html
+[travis]: https://travis-ci.org/onoi/callback-container
+[smw]: https://github.com/SemanticMediaWiki/SemanticMediaWiki/
diff --git a/www/wiki/vendor/onoi/callback-container/composer.json b/www/wiki/vendor/onoi/callback-container/composer.json
new file mode 100644
index 00000000..08c42eb3
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/composer.json
@@ -0,0 +1,36 @@
+{
+ "name": "onoi/callback-container",
+ "type": "library",
+ "description": "A very simple callback container/builder library",
+ "keywords": [
+ "container"
+ ],
+ "homepage": "https://github.com/onoi/callback-container",
+ "license": "GPL-2.0+",
+ "authors": [
+ {
+ "name": "mwjames",
+ "homepage": "https://github.com/mwjames"
+ }
+ ],
+ "require": {
+ "php": ">=5.5",
+ "psr/log": "~1.0"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Onoi\\CallbackContainer\\": "src/"
+ }
+ },
+ "config": {
+ "process-timeout": 0
+ },
+ "scripts":{
+ "phpunit": "phpunit -c phpunit.xml.dist"
+ }
+}
diff --git a/www/wiki/vendor/onoi/callback-container/docs/usage.md b/www/wiki/vendor/onoi/callback-container/docs/usage.md
new file mode 100644
index 00000000..28d26340
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/docs/usage.md
@@ -0,0 +1,93 @@
+
+- `CallbackContainerFactory` provides the general access to the classes of this library
+- `CallbackContainer` is an interface that describes a container to be registered
+ with a `ContainerBuilder`
+- `ContainerBuilder` is an interface that describes the implementation details for
+ creating concrete instances of a service definition and resolving the dependency
+ tree provided by the `ContainerRegistry`
+- `CallbackContainerBuilder` provides an implementation of the `ContainerBuilder`
+ interface
+
+## Using a container
+
+```php
+class FooCallbackContainer implements CallbackContainer {
+
+ public function register( ContainerBuilder $containerBuilder ) {
+ $this->addCallbackHandlers( $containerBuilder);
+ }
+
+ private function addCallbackHandlers( $containerBuilder ) {
+
+ $containerBuilder->registerCallback( 'Foo', function( ContainerBuilder $containerBuilder, array $input ) {
+ $containerBuilder->registerExpectedReturnType( 'Foo', '\stdClass' );
+
+ $stdClass = new \stdClass;
+ $stdClass->input = $input;
+
+ return $stdClass;
+ } );
+ }
+}
+```
+```php
+use Onoi\CallbackContainer\CallbackContainerFactory;
+
+$callbackContainerFactory = new CallbackContainerFactory();
+$containerBuilder = $callbackContainerFactory->newCallbackContainerBuilder();
+
+$containerBuilder->registerCallbackContainer( new FooCallbackContainer() );
+
+$instance = $containerBuilder->create(
+ 'Foo',
+ array( 'a', 'b' )
+);
+
+$instance = $containerBuilder->singleton(
+ 'Foo',
+ array( 'aa', 'bb' )
+);
+```
+
+## Using a service file
+
+```php
+return array(
+
+ /**
+ * @return Closure
+ */
+ 'SomeServiceFromFile' => function( $containerBuilder ) {
+ return new \stdClass;
+ },
+
+ /**
+ * @return Closure
+ */
+ 'AnotherServiceFromFile' => function( $containerBuilder, $argument1, $argument2 ) {
+ $containerBuilder->registerExpectedReturnType( 'AnotherServiceFromFile', '\stdClass' )
+ $containerBuilder->registerAlias( 'AnotherServiceFromFile', 'ServiceAliasFromFile' )
+
+ $service = $containerBuilder->create( 'SomeServiceFromFile' );
+ $service->argument1 = $argument1;
+ $service->argument2 = $argument2;
+
+ return $service;
+ }
+);
+```
+```php
+use Onoi\CallbackContainer\CallbackContainerFactory;
+
+$callbackContainerFactory = new CallbackContainerFactory();
+$containerBuilder = $callbackContainerFactory->newCallbackContainerBuilder();
+
+$containerBuilder->registerFromFile( __DIR__ . '/Foo.php' );
+$someServiceFromFile = $containerBuilder->create( 'SomeServiceFromFile' );
+$anotherServiceFromFile = $containerBuilder->create( 'AnotherServiceFromFile', 'Foo', 'Bar' );
+```
+
+### ContainerRegistry::registerExpectedReturnType
+
+If a callback handler is registered with an expected return type then any
+mismatch of a returning instance will throw a `ServiceTypeMismatchException`.
diff --git a/www/wiki/vendor/onoi/callback-container/phpunit.xml.dist b/www/wiki/vendor/onoi/callback-container/phpunit.xml.dist
new file mode 100644
index 00000000..7a4a8712
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/phpunit.xml.dist
@@ -0,0 +1,24 @@
+<phpunit backupGlobals="false"
+ backupStaticAttributes="false"
+ bootstrap="tests/bootstrap.php"
+ cacheTokens="false"
+ colors="true"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ stopOnError="false"
+ stopOnFailure="false"
+ stopOnIncomplete="false"
+ stopOnSkipped="false"
+ verbose="true">
+ <testsuites>
+ <testsuite name="callback-container-unit">
+ <directory>tests/phpunit/Unit</directory>
+ </testsuite>
+ </testsuites>
+ <filter>
+ <whitelist addUncoveredFilesFromWhitelist="true">
+ <directory suffix=".php">src</directory>
+ </whitelist>
+ </filter>
+</phpunit>
diff --git a/www/wiki/vendor/onoi/callback-container/src/CallbackContainer.php b/www/wiki/vendor/onoi/callback-container/src/CallbackContainer.php
new file mode 100644
index 00000000..7c111927
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/src/CallbackContainer.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Onoi\CallbackContainer;
+
+/**
+ * Interface describing a container to be registered with a ContainerBuilder.
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+interface CallbackContainer {
+
+ /**
+ * @since 1.0
+ *
+ * @param ContainerBuilder $containerBuilder
+ *
+ * @return null|array
+ */
+ public function register( ContainerBuilder $containerBuilder );
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/src/CallbackContainerBuilder.php b/www/wiki/vendor/onoi/callback-container/src/CallbackContainerBuilder.php
new file mode 100644
index 00000000..711b99df
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/src/CallbackContainerBuilder.php
@@ -0,0 +1,298 @@
+<?php
+
+namespace Onoi\CallbackContainer;
+
+use Closure;
+use Onoi\CallbackContainer\Exception\ServiceTypeMismatchException;
+use Onoi\CallbackContainer\Exception\ServiceCircularReferenceException;
+use Onoi\CallbackContainer\Exception\InvalidParameterTypeException;
+use Onoi\CallbackContainer\Exception\FileNotFoundException;
+use Onoi\CallbackContainer\Exception\ServiceNotFoundException;
+use Onoi\CallbackContainer\Exception\ServiceAliasCrossAssignmentException;
+use Onoi\CallbackContainer\Exception\ServiceAliasAssignmentException;
+use Onoi\CallbackContainer\Exception\ServiceAliasMismatchException;
+
+/**
+ * @license GNU GPL v2+
+ * @since 2.0
+ *
+ * @author mwjames
+ */
+class CallbackContainerBuilder implements ContainerBuilder {
+
+ /**
+ * @var array
+ */
+ protected $registry = array();
+
+ /**
+ * @var array
+ */
+ protected $singletons = array();
+
+ /**
+ * @var array
+ */
+ protected $expectedReturnTypeByHandler = array();
+
+ /**
+ * @var array
+ */
+ protected $aliases = array();
+
+ /**
+ * @var array
+ */
+ protected $recursiveMarker = array();
+
+ /**
+ * @since 2.0
+ *
+ * {@inheritDoc}
+ */
+ public function __construct( CallbackContainer $callbackContainer = null ) {
+ if ( $callbackContainer !== null ) {
+ $this->registerCallbackContainer( $callbackContainer );
+ }
+ }
+
+ /**
+ * @since 2.0
+ *
+ * {@inheritDoc}
+ */
+ public function registerCallbackContainer( CallbackContainer $callbackContainer ) {
+ $this->register( $callbackContainer->register( $this ) );
+ }
+
+ /**
+ * @since 2.0
+ *
+ * {@inheritDoc}
+ */
+ public function registerFromFile( $file ) {
+
+ if ( !is_readable( ( $file = str_replace( array( '\\', '/' ), DIRECTORY_SEPARATOR, $file ) ) ) ) {
+ throw new FileNotFoundException( "Cannot access or read {$file}" );
+ }
+
+ $this->register( require $file );
+ }
+
+ /**
+ * @since 2.0
+ *
+ * {@inheritDoc}
+ */
+ public function registerCallback( $serviceName, callable $callback ) {
+
+ if ( !is_string( $serviceName ) ) {
+ throw new InvalidParameterTypeException( "Expected a string" );
+ }
+
+ $this->registry[$serviceName] = $callback;
+ }
+
+ /**
+ * If you are not running PHPUnit or for that matter any other testing
+ * environment then you are not suppose to use this function.
+ *
+ * @since 2.0
+ *
+ * {@inheritDoc}
+ */
+ public function registerObject( $serviceName, $instance ) {
+
+ if ( !is_string( $serviceName ) ) {
+ throw new InvalidParameterTypeException( "Expected a string" );
+ }
+
+ if ( isset( $this->aliases[$serviceName] ) ) {
+ throw new ServiceAliasMismatchException( $serviceName );
+ }
+
+ unset( $this->singletons[$serviceName] );
+
+ $this->registry[$serviceName] = $instance;
+ $this->singletons[$serviceName]['#'] = $instance;
+ }
+
+ /**
+ * @since 2.0
+ *
+ * {@inheritDoc}
+ */
+ public function registerExpectedReturnType( $serviceName, $type ) {
+
+ if ( !is_string( $serviceName ) || !is_string( $type ) ) {
+ throw new InvalidParameterTypeException( "Expected a string" );
+ }
+
+ $this->expectedReturnTypeByHandler[$serviceName] = $type;
+ }
+
+ /**
+ * @since 2.0
+ *
+ * {@inheritDoc}
+ */
+ public function registerAlias( $serviceName, $alias ) {
+
+ if ( !is_string( $serviceName ) || !is_string( $alias ) ) {
+ throw new InvalidParameterTypeException( "Expected a string" );
+ }
+
+ if ( isset( $this->registry[$alias] ) ) {
+ throw new ServiceAliasAssignmentException( $alias );
+ }
+
+ if ( isset( $this->aliases[$alias] ) && $this->aliases[$alias] !== $serviceName ) {
+ throw new ServiceAliasCrossAssignmentException( $serviceName, $alias, $this->aliases[$alias] );
+ }
+
+ $this->aliases[$alias] = $serviceName;
+ }
+
+ /**
+ * @since 2.0
+ *
+ * {@inheritDoc}
+ */
+ public function isRegistered( $serviceName ) {
+
+ if ( is_string( $serviceName ) && isset( $this->aliases[$serviceName] ) ) {
+ $serviceName = $this->aliases[$serviceName];
+ }
+
+ return isset( $this->registry[$serviceName] );
+ }
+
+ /**
+ * @since 2.0
+ *
+ * {@inheritDoc}
+ */
+ public function create( $serviceName ) {
+
+ if ( is_string( $serviceName ) && isset( $this->aliases[$serviceName] ) ) {
+ $serviceName = $this->aliases[$serviceName];
+ }
+
+ return $this->getReturnValueFromCallbackHandlerFor( $serviceName, func_get_args() );
+ }
+
+ /**
+ * @since 2.0
+ *
+ * {@inheritDoc}
+ */
+ public function singleton( $serviceName ) {
+
+ if ( is_string( $serviceName ) && isset( $this->aliases[$serviceName] ) ) {
+ $serviceName = $this->aliases[$serviceName];
+ }
+
+ return $this->getReturnValueFromSingletonFor( $serviceName, func_get_args() );
+ }
+
+ /**
+ * @since 2.0
+ *
+ * @param string $serviceName
+ */
+ public function deregister( $serviceName ) {
+ unset( $this->registry[$serviceName] );
+ unset( $this->singletons[$serviceName] );
+ unset( $this->expectedReturnTypeByHandler[$serviceName] );
+
+ foreach ( $this->aliases as $alias => $service ) {
+ if ( $service === $serviceName ) {
+ unset( $this->aliases[$alias] );
+ }
+ }
+ }
+
+ private function register( $serviceDefinitions ) {
+
+ if ( !is_array( $serviceDefinitions ) ) {
+ return;
+ }
+
+ foreach ( $serviceDefinitions as $serviceName => $callback ) {
+ if ( is_callable( $callback ) ) {
+ $this->registry[$serviceName] = $callback;
+ }
+ }
+ }
+
+ private function addRecursiveMarkerFor( $serviceName ) {
+
+ if ( !is_string( $serviceName ) ) {
+ throw new InvalidParameterTypeException( "Expected a string" );
+ }
+
+ if ( !isset( $this->recursiveMarker[$serviceName] ) ) {
+ $this->recursiveMarker[$serviceName] = 0;
+ }
+
+ $this->recursiveMarker[$serviceName]++;
+
+ if ( $this->recursiveMarker[$serviceName] > 1 ) {
+ throw new ServiceCircularReferenceException( $serviceName );
+ }
+ }
+
+ private function getReturnValueFromCallbackHandlerFor( $serviceName, $parameters ) {
+
+ $this->addRecursiveMarkerFor( $serviceName );
+
+ if ( !isset( $this->registry[$serviceName] ) ) {
+ throw new ServiceNotFoundException( "$serviceName is an unknown service." );
+ }
+
+ // Remove the ServiceName
+ array_shift( $parameters );
+
+ // Shift the ContainerBuilder to the first position in the parameter list
+ array_unshift( $parameters, $this );
+ $service = $this->registry[$serviceName];
+
+ $instance = is_callable( $service ) ? call_user_func_array( $service, $parameters ) : $service;
+ $this->recursiveMarker[$serviceName]--;
+
+ if ( !isset( $this->expectedReturnTypeByHandler[$serviceName] ) || is_a( $instance, $this->expectedReturnTypeByHandler[$serviceName] ) ) {
+ return $instance;
+ }
+
+ throw new ServiceTypeMismatchException( $serviceName, $this->expectedReturnTypeByHandler[$serviceName], ( is_object( $instance ) ? get_class( $instance ) : $instance ) );
+ }
+
+ private function getReturnValueFromSingletonFor( $serviceName, $parameters ) {
+
+ $instance = null;
+ $fingerprint = $parameters !== array() ? md5( json_encode( $parameters ) ) : '#';
+
+ $this->addRecursiveMarkerFor( $serviceName );
+
+ if ( isset( $this->singletons[$serviceName][$fingerprint] ) ) {
+ $service = $this->singletons[$serviceName][$fingerprint];
+ $instance = is_callable( $service ) ? call_user_func( $service ) : $service;
+ }
+
+ $this->recursiveMarker[$serviceName]--;
+
+ if ( $instance !== null && ( !isset( $this->expectedReturnTypeByHandler[$serviceName] ) || is_a( $instance, $this->expectedReturnTypeByHandler[$serviceName] ) ) ) {
+ return $instance;
+ }
+
+ $instance = $this->getReturnValueFromCallbackHandlerFor( $serviceName, $parameters );
+
+ $this->singletons[$serviceName][$fingerprint] = function() use ( $instance ) {
+ static $singleton;
+ return $singleton = $singleton === null ? $instance : $singleton;
+ };
+
+ return $instance;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/src/CallbackContainerFactory.php b/www/wiki/vendor/onoi/callback-container/src/CallbackContainerFactory.php
new file mode 100644
index 00000000..933aadd1
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/src/CallbackContainerFactory.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Onoi\CallbackContainer;
+
+/**
+ * @license GNU GPL v2+
+ * @since 2.0
+ *
+ * @author mwjames
+ */
+class CallbackContainerFactory {
+
+ /**
+ * @since 2.0
+ *
+ * @param CallbackContainer|null $callbackContainer
+ *
+ * @return CallbackContainerBuilder
+ */
+ public function newCallbackContainerBuilder( CallbackContainer $callbackContainer = null ) {
+ return new CallbackContainerBuilder( $callbackContainer );
+ }
+
+ /**
+ * @since 2.0
+ *
+ * @return NullContainerBuilder
+ */
+ public function newNullContainerBuilder() {
+ return new NullContainerBuilder();
+ }
+
+ /**
+ * @since 2.0
+ *
+ * @param ContainerBuilder|null $containerBuilder
+ *
+ * @return ServicesManager
+ */
+ public function newServicesManager( ContainerBuilder $containerBuilder = null ) {
+
+ if ( $containerBuilder === null ) {
+ $containerBuilder = $this->newCallbackContainerBuilder();
+ }
+
+ return new ServicesManager( $containerBuilder );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/src/ContainerBuilder.php b/www/wiki/vendor/onoi/callback-container/src/ContainerBuilder.php
new file mode 100644
index 00000000..2230b594
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/src/ContainerBuilder.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Onoi\CallbackContainer;
+
+use Closure;
+
+/**
+ * Interface to describe the implementation details for creating concrete instances
+ * of a service definition and resolving the dependency tree provided by the
+ * `ContainerRegistry`.
+ *
+ * @license GNU GPL v2+
+ * @since 1.2
+ *
+ * @author mwjames
+ */
+interface ContainerBuilder extends ServiceRegistry, ContainerRegistry {
+
+ /**
+ * @since 1.2
+ *
+ * @param string $serviceName
+ *
+ * @return boolean
+ */
+ public function isRegistered( $serviceName );
+
+ /**
+ * Returns a new instance for each call to a requested service.
+ *
+ * @since 1.1
+ *
+ * @param string $serviceName
+ *
+ * @return mixed
+ * @throws RuntimeException
+ */
+ public function create( $serviceName );
+
+ /**
+ * Returns a singleton instance for a requested service that relies on the
+ * same argument fingerprint.
+ *
+ * @since 1.0
+ *
+ * @param string $serviceName
+ *
+ * @return mixed
+ * @throws RuntimeException
+ */
+ public function singleton( $serviceName );
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/src/ContainerRegistry.php b/www/wiki/vendor/onoi/callback-container/src/ContainerRegistry.php
new file mode 100644
index 00000000..7f6d6bec
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/src/ContainerRegistry.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Onoi\CallbackContainer;
+
+/**
+ * @license GNU GPL v2+
+ * @since 2.0
+ *
+ * @author mwjames
+ */
+interface ContainerRegistry {
+
+ /**
+ * @since 1.1
+ *
+ * @param CallbackContainer $callbackContainer
+ */
+ public function registerCallbackContainer( CallbackContainer $callbackContainer );
+
+ /**
+ * @since 2.0
+ *
+ * @param string $file
+ */
+ public function registerFromFile( $file );
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/src/Exception/FileNotFoundException.php b/www/wiki/vendor/onoi/callback-container/src/Exception/FileNotFoundException.php
new file mode 100644
index 00000000..dd37c9ac
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/src/Exception/FileNotFoundException.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Onoi\CallbackContainer\Exception;
+
+use RuntimeException;
+
+/**
+ * @license GNU GPL v2+
+ * @since 2.0
+ *
+ * @author mwjames
+ */
+class FileNotFoundException extends RuntimeException {
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/src/Exception/InvalidParameterTypeException.php b/www/wiki/vendor/onoi/callback-container/src/Exception/InvalidParameterTypeException.php
new file mode 100644
index 00000000..31249054
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/src/Exception/InvalidParameterTypeException.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Onoi\CallbackContainer\Exception;
+
+use InvalidArgumentException;
+
+/**
+ * @license GNU GPL v2+
+ * @since 2.0
+ *
+ * @author mwjames
+ */
+class InvalidParameterTypeException extends InvalidArgumentException {
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/src/Exception/ServiceAliasAssignmentException.php b/www/wiki/vendor/onoi/callback-container/src/Exception/ServiceAliasAssignmentException.php
new file mode 100644
index 00000000..8a8b37aa
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/src/Exception/ServiceAliasAssignmentException.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Onoi\CallbackContainer\Exception;
+
+use RuntimeException;
+
+/**
+ * @license GNU GPL v2+
+ * @since 2.0
+ *
+ * @author mwjames
+ */
+class ServiceAliasAssignmentException extends RuntimeException {
+
+ /**
+ * @since 2.0
+ *
+ * @param string $alias
+ */
+ public function __construct( $alias ) {
+ parent::__construct( "`{$alias}` is already used as service and can therefore not be used as alias." );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/src/Exception/ServiceAliasCrossAssignmentException.php b/www/wiki/vendor/onoi/callback-container/src/Exception/ServiceAliasCrossAssignmentException.php
new file mode 100644
index 00000000..76bc0280
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/src/Exception/ServiceAliasCrossAssignmentException.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Onoi\CallbackContainer\Exception;
+
+use RuntimeException;
+
+/**
+ * @license GNU GPL v2+
+ * @since 2.0
+ *
+ * @author mwjames
+ */
+class ServiceAliasCrossAssignmentException extends RuntimeException {
+
+ /**
+ * @since 2.0
+ *
+ * @param string $service
+ * @param string $alias
+ * @param string $assignedService
+ */
+ public function __construct( $service, $alias, $assignedService ) {
+ parent::__construct( "{$alias} alias cannot be reassigned to the {$service} service as it has already been assigned to {$assignedService}." );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/src/Exception/ServiceAliasMismatchException.php b/www/wiki/vendor/onoi/callback-container/src/Exception/ServiceAliasMismatchException.php
new file mode 100644
index 00000000..701e8d71
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/src/Exception/ServiceAliasMismatchException.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Onoi\CallbackContainer\Exception;
+
+use RuntimeException;
+
+/**
+ * @license GNU GPL v2+
+ * @since 2.0
+ *
+ * @author mwjames
+ */
+class ServiceAliasMismatchException extends RuntimeException {
+
+ /**
+ * @since 2.0
+ *
+ * @param string $alias
+ */
+ public function __construct( $alias ) {
+ parent::__construct( "`{$alias}` should not be used as service." );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/src/Exception/ServiceCircularReferenceException.php b/www/wiki/vendor/onoi/callback-container/src/Exception/ServiceCircularReferenceException.php
new file mode 100644
index 00000000..9bbbcead
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/src/Exception/ServiceCircularReferenceException.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Onoi\CallbackContainer\Exception;
+
+use RuntimeException;
+
+/**
+ * @license GNU GPL v2+
+ * @since 2.0
+ *
+ * @author mwjames
+ */
+class ServiceCircularReferenceException extends RuntimeException {
+
+ /**
+ * @since 2.0
+ *
+ * @param string $service
+ */
+ public function __construct( $service ) {
+ parent::__construct( "Oh boy, your execution chain for $service caused a circular reference." );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/src/Exception/ServiceNotFoundException.php b/www/wiki/vendor/onoi/callback-container/src/Exception/ServiceNotFoundException.php
new file mode 100644
index 00000000..0faa7596
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/src/Exception/ServiceNotFoundException.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Onoi\CallbackContainer\Exception;
+
+use InvalidArgumentException;
+
+/**
+ * @license GNU GPL v2+
+ * @since 2.0
+ *
+ * @author mwjames
+ */
+class ServiceNotFoundException extends InvalidArgumentException {
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/src/Exception/ServiceTypeMismatchException.php b/www/wiki/vendor/onoi/callback-container/src/Exception/ServiceTypeMismatchException.php
new file mode 100644
index 00000000..0bdf0414
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/src/Exception/ServiceTypeMismatchException.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Onoi\CallbackContainer\Exception;
+
+use RuntimeException;
+
+/**
+ * @license GNU GPL v2+
+ * @since 2.0
+ *
+ * @author mwjames
+ */
+class ServiceTypeMismatchException extends RuntimeException {
+
+ /**
+ * @since 2.0
+ *
+ * @param string $service
+ * @param string $expectedType
+ * @param string $actualType
+ */
+ public function __construct( $service, $expectedType, $actualType ) {
+ parent::__construct( "Expected " . $expectedType . " type for {$service} but it did not match " . $actualType );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/src/NullContainerBuilder.php b/www/wiki/vendor/onoi/callback-container/src/NullContainerBuilder.php
new file mode 100644
index 00000000..2c9ee291
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/src/NullContainerBuilder.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace Onoi\CallbackContainer;
+
+/**
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class NullContainerBuilder implements ContainerBuilder {
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function registerCallback( $serviceName, callable $callback ) {}
+
+ /**
+ * @since 1.1
+ *
+ * {@inheritDoc}
+ */
+ public function registerCallbackContainer( CallbackContainer $callbackContainer ) {}
+
+ /**
+ * @since 2.0
+ *
+ * {@inheritDoc}
+ */
+ public function registerFromFile( $file ) {}
+
+ /**
+ * @since 2.0
+ *
+ * {@inheritDoc}
+ */
+ public function registerObject( $serviceName, $instance ) {}
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function registerExpectedReturnType( $serviceName, $type ) {}
+
+ /**
+ * @since 2.0
+ *
+ * {@inheritDoc}
+ */
+ public function registerAlias( $serviceName, $alias ) {}
+
+ /**
+ * @since 1.2
+ *
+ * {@inheritDoc}
+ */
+ public function isRegistered( $serviceName ) { return false; }
+
+ /**
+ * @since 1.1
+ *
+ * {@inheritDoc}
+ */
+ public function create( $serviceName ) {}
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function singleton( $handlerName ) {}
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/src/ServiceRegistry.php b/www/wiki/vendor/onoi/callback-container/src/ServiceRegistry.php
new file mode 100644
index 00000000..3a09c65a
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/src/ServiceRegistry.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace Onoi\CallbackContainer;
+
+use Closure;
+
+/**
+ * @license GNU GPL v2+
+ * @since 2.0
+ *
+ * @author mwjames
+ */
+interface ServiceRegistry {
+
+ /**
+ * @since 1.0
+ *
+ * @param string $serviceName
+ * @param callable $callback
+ */
+ public function registerCallback( $serviceName, callable $callback );
+
+ /**
+ * @since 2.0
+ *
+ * @param string $serviceName
+ * @param mixed $instance
+ */
+ public function registerObject( $serviceName, $instance );
+
+ /**
+ * Registers the expected return type of an instance that is called either
+ * via ContainerBuilder::create or ContainerBuilder::singleton.
+ *
+ * @since 1.0
+ *
+ * @param string $serviceName
+ * @param string $type
+ */
+ public function registerExpectedReturnType( $serviceName, $type );
+
+ /**
+ * Registers an alias for an existing service
+ *
+ * @since 1.0
+ *
+ * @param string $serviceName
+ * @param string $alias
+ */
+ public function registerAlias( $serviceName, $alias );
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/src/ServicesManager.php b/www/wiki/vendor/onoi/callback-container/src/ServicesManager.php
new file mode 100644
index 00000000..c028d76b
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/src/ServicesManager.php
@@ -0,0 +1,104 @@
+<?php
+
+namespace Onoi\CallbackContainer;
+
+use Onoi\CallbackContainer\Exception\ServiceNotFoundException;
+
+/**
+ * Convenience class to handle services isolated from an active ContainerBuilder
+ * instance.
+ *
+ * @license GNU GPL v2+
+ * @since 2.0
+ *
+ * @author mwjames
+ */
+class ServicesManager {
+
+ /**
+ * @var ContainerBuilder
+ */
+ private $containerBuilder;
+
+ /**
+ * @since 2.0
+ *
+ * @param ContainerBuilder $containerBuilder
+ */
+ public function __construct( ContainerBuilder $containerBuilder ) {
+ $this->containerBuilder = $containerBuilder;
+ }
+
+ /**
+ * @since 2.0
+ *
+ * @param string $serviceName
+ * @param mixed $service
+ * @param string|null $expectedReturnType
+ */
+ public function add( $serviceName, $service, $expectedReturnType = null ) {
+
+ if ( !is_callable( $service ) ) {
+ $service = function() use( $service ) {
+ return $service;
+ };
+ }
+
+ $this->containerBuilder->registerCallback( $serviceName, $service );
+
+ if ( $expectedReturnType !== null ) {
+ $this->containerBuilder->registerExpectedReturnType( $serviceName, $expectedReturnType );
+ }
+ }
+
+ /**
+ * @since 2.0
+ *
+ * @param string $serviceName
+ *
+ * @return boolean
+ */
+ public function has( $serviceName ) {
+ return $this->containerBuilder->isRegistered( $serviceName );
+ }
+
+ /**
+ * @since 2.0
+ *
+ * @param string $serviceName
+ *
+ * @return mixed
+ * @throws ServiceNotFoundException
+ */
+ public function get( $serviceName ) {
+
+ if ( !$this->containerBuilder->isRegistered( $serviceName ) ) {
+ throw new ServiceNotFoundException( "$serviceName is an unknown service." );
+ }
+
+ $parameters = func_get_args();
+ array_unshift( $parameters, $serviceName );
+
+ return call_user_func_array( array( $this->containerBuilder, 'create' ), $parameters );
+ }
+
+ /**
+ * @since 2.0
+ *
+ * @param string $serviceName
+ */
+ public function remove( $serviceName ) {
+ $this->containerBuilder->deregister( $serviceName );
+ }
+
+ /**
+ * @since 2.0
+ *
+ * @param string $serviceName
+ * @param mixed $service
+ */
+ public function replace( $serviceName, $service ) {
+ $this->containerBuilder->registerObject( $serviceName, $service );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/tests/bootstrap.php b/www/wiki/vendor/onoi/callback-container/tests/bootstrap.php
new file mode 100644
index 00000000..1b0ca2ea
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/tests/bootstrap.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+
+error_reporting( E_ALL | E_STRICT );
+date_default_timezone_set( 'UTC' );
+
+if ( PHP_SAPI !== 'cli' ) {
+ die( 'Not an entry point' );
+}
+
+if ( is_readable( $path = __DIR__ . '/../vendor/autoload.php' ) ) {
+ print( "\nUsing the callback-container vendor autoloader ...\n\n" );
+} elseif ( is_readable( $path = __DIR__ . '/../../../autoload.php' ) ) {
+ print( "\nUsing another local vendor autoloader ...\n\n" );
+} else {
+ die( 'The test suite requires a Composer based deployement.' );
+}
+
+$autoLoader = require $path;
+$autoLoader->addPsr4( 'Onoi\\CallbackContainer\\Tests\\', __DIR__ . '/phpunit/Unit' );
+$autoLoader->addPsr4( 'Onoi\\CallbackContainer\\Fixtures\\', __DIR__ . '/phpunit/Fixtures' );
diff --git a/www/wiki/vendor/onoi/callback-container/tests/phpunit/Fixtures/FakeCallbackContainer.php b/www/wiki/vendor/onoi/callback-container/tests/phpunit/Fixtures/FakeCallbackContainer.php
new file mode 100644
index 00000000..cd73b742
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/tests/phpunit/Fixtures/FakeCallbackContainer.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Onoi\CallbackContainer\Fixtures;
+
+use Onoi\CallbackContainer\CallbackContainer;
+use Onoi\CallbackContainer\ContainerBuilder;
+
+/**
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class FakeCallbackContainer implements CallbackContainer {
+
+ public function register( ContainerBuilder $containerBuilder ) {
+ $this->addCallbackHandlers( $containerBuilder );
+
+ return array(
+ 'service.one' => array( $this, 'getServiceOne' ),
+ 'isNotCallable' => 'thereforeWontRegister'
+ );
+ }
+
+ private function addCallbackHandlers( $containerBuilder ) {
+
+ $containerBuilder->registerCallback( 'Foo', function( $containerBuilder ) {
+ return new \stdClass;
+ } );
+
+ $containerBuilder->registerExpectedReturnType( 'Foo', '\stdClass' );
+
+ $containerBuilder->registerCallback( 'FooWithArgument', function( $containerBuilder, $argument ) {
+ $containerBuilder->registerExpectedReturnType( 'FooWithArgument', '\stdClass' );
+
+ $stdClass = new \stdClass;
+ $stdClass->argument = $argument;
+
+ return $stdClass;
+ } );
+
+ $containerBuilder->registerCallback( 'FooWithNullArgument', array( $this, 'newFooWithNullArgument' ) );
+ }
+
+ public static function getServiceOne( $containerBuilder, $argument = null ) {
+ return $containerBuilder->singleton( 'FooWithNullArgument', $argument );
+ }
+
+ public static function newFooWithNullArgument( $containerBuilder, $argument = null ) {
+ $containerBuilder->registerExpectedReturnType( 'FooWithNullArgument', '\stdClass' );
+
+ $stdClass = new \stdClass;
+ $stdClass->argument = $argument;
+ $stdClass->argumentWithArgument = $containerBuilder->create( 'FooWithArgument', $argument );
+
+ return $stdClass;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/tests/phpunit/Fixtures/fakeCallbackFromFile.php b/www/wiki/vendor/onoi/callback-container/tests/phpunit/Fixtures/fakeCallbackFromFile.php
new file mode 100644
index 00000000..de11390e
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/tests/phpunit/Fixtures/fakeCallbackFromFile.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace Onoi\CallbackContainer\Tests;
+
+/**
+ * @codeCoverageIgnore
+ *
+ * Expected registration form:
+ *
+ * return array(
+ * 'SomeService' => function( $containerBuilder ) { ... }
+ * )
+ *
+ * @license GNU GPL v2+
+ * @since 2.0
+ *
+ * @author mwjames
+ */
+return array(
+
+ /**
+ * @return Closure
+ */
+ 'SomeStdClassFromFile' => function( $containerBuilder ) {
+ return new \stdClass;
+ },
+
+ /**
+ * @return Closure
+ */
+ 'AnotherStdClassFromFileWithInterFactory' => function( $containerBuilder ) {
+ $containerBuilder->registerExpectedReturnType( 'AnotherStdClassFromFile', '\stdClass' );
+ return $containerBuilder->create( 'SomeStdClassFromFile' );
+ },
+
+ /**
+ * @return Closure
+ */
+ 'AnotherStdClassFromFileWithInterFactoryAndArgument' => function( $containerBuilder, $arg ) {
+ $instance = $containerBuilder->create( 'AnotherStdClassFromFileWithInterFactory' );
+ $instance->argument = $arg;
+ return $instance;
+ },
+
+ /**
+ * @return Closure
+ */
+ 'serviceCallFromFileWithCircularReference' => function( $containerBuilder ) {
+ return $containerBuilder->create( 'serviceFromFileWithForcedCircularReference' );
+ },
+
+ /**
+ * @return Closure
+ */
+ 'serviceFromFileWithForcedCircularReference' => function( $containerBuilder ) {
+ return $containerBuilder->create( 'serviceCallFromFileWithCircularReference' );
+ },
+
+ /**
+ * @return string
+ */
+ 'InvalidDefinition' => 'Foo'
+); \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/CallbackContainerBuilderTest.php b/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/CallbackContainerBuilderTest.php
new file mode 100644
index 00000000..b6f614f5
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/CallbackContainerBuilderTest.php
@@ -0,0 +1,533 @@
+<?php
+
+namespace Onoi\CallbackContainer\Tests;
+
+use Onoi\CallbackContainer\CallbackContainerBuilder;
+use Onoi\CallbackContainer\Fixtures\FakeCallbackContainer;
+
+/**
+ * @covers \Onoi\CallbackContainer\CallbackContainerBuilder
+ * @group onoi-callback-container
+ *
+ * @license GNU GPL v2+
+ * @since 1.2
+ *
+ * @author mwjames
+ */
+class CallbackContainerBuilderTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\CallbackContainer\CallbackContainerBuilder',
+ new CallbackContainerBuilder()
+ );
+ }
+
+ public function testCanConstructWithCallbackContainer() {
+
+ $callbackContainer = $this->getMockBuilder( '\Onoi\CallbackContainer\CallbackContainer' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $callbackContainer->expects( $this->once() )
+ ->method( 'register' );
+
+ $this->assertInstanceOf(
+ '\Onoi\CallbackContainer\CallbackContainerBuilder',
+ new CallbackContainerBuilder( $callbackContainer )
+ );
+ }
+
+ public function testRegisterCallback() {
+
+ $instance = new CallbackContainerBuilder();
+
+ $instance->registerCallback( 'Foo', function() {
+ return new \stdClass;
+ } );
+
+ $this->assertEquals(
+ new \stdClass,
+ $instance->create( 'Foo' )
+ );
+
+ $this->assertEquals(
+ new \stdClass,
+ $instance->singleton( 'Foo' )
+ );
+
+ $this->assertTrue(
+ $instance->isRegistered( 'Foo' )
+ );
+ }
+
+ public function testDeregisterCallback() {
+
+ $instance = new CallbackContainerBuilder();
+
+ $instance->registerCallback( 'Foo', function() {
+ return 'abc';
+ } );
+
+ $instance->registerAlias( 'Foo', 'Foobar' );
+
+ $this->assertTrue(
+ $instance->isRegistered( 'Foo' )
+ );
+
+ $instance->deregister( 'Foo' );
+
+ $this->assertFalse(
+ $instance->isRegistered( 'Foo' )
+ );
+
+ $this->assertFalse(
+ $instance->isRegistered( 'Foobar' )
+ );
+ }
+
+ public function testLoadCallbackHandlerWithExpectedReturnType() {
+
+ $instance = new CallbackContainerBuilder();
+
+ $instance->registerCallback( 'Foo', function() {
+ return new \stdClass;
+ } );
+
+ $instance->registerExpectedReturnType( 'Foo', '\stdClass' );
+
+ $this->assertEquals(
+ new \stdClass,
+ $instance->create( 'Foo' )
+ );
+ }
+
+ public function testLoadCallbackHandlerWithoutExpectedReturnType() {
+
+ $instance = new CallbackContainerBuilder();
+
+ $instance->registerCallback( 'Foo', function() {
+ return 'abc';
+ } );
+
+ $this->assertEquals(
+ 'abc',
+ $instance->create( 'Foo' )
+ );
+ }
+
+ public function testRegisterCallbackContainer() {
+
+ $instance = new CallbackContainerBuilder();
+ $instance->registerCallbackContainer( new FakeCallbackContainer() );
+
+ $this->assertEquals(
+ new \stdClass,
+ $instance->create( 'Foo' )
+ );
+
+ $this->assertEquals(
+ new \stdClass,
+ $instance->singleton( 'Foo' )
+ );
+ }
+
+ public function testRegisterFromFile() {
+
+ $instance = new CallbackContainerBuilder();
+ $instance->registerFromFile( __DIR__ . '/../Fixtures/fakeCallbackFromFile.php' );
+
+ $this->assertEquals(
+ new \stdClass,
+ $instance->create( 'SomeStdClassFromFile' )
+ );
+ }
+
+ public function testRegisterFromFileWithInterFactory() {
+
+ $instance = new CallbackContainerBuilder();
+ $instance->registerFromFile( __DIR__ . '/../Fixtures/fakeCallbackFromFile.php' );
+
+ $this->assertEquals(
+ new \stdClass,
+ $instance->create( 'AnotherStdClassFromFileWithInterFactory' )
+ );
+ }
+
+ public function testRegisterFromFileWithInterFactoryAndArgument() {
+
+ $instance = new CallbackContainerBuilder();
+ $instance->registerFromFile( __DIR__ . '/../Fixtures/fakeCallbackFromFile.php' );
+
+ $this->assertEquals(
+ 123,
+ $instance->create( 'AnotherStdClassFromFileWithInterFactoryAndArgument', 123 )->argument
+ );
+ }
+
+ public function testRegisterFromFileWithCircularReferenceThrowsException() {
+
+ $instance = new CallbackContainerBuilder();
+ $instance->registerFromFile( __DIR__ . '/../Fixtures/fakeCallbackFromFile.php' );
+
+ $this->setExpectedException( 'Onoi\CallbackContainer\Exception\ServiceCircularReferenceException' );
+ $instance->create( 'serviceFromFileWithForcedCircularReference' );
+ }
+
+ public function testRegisterObject() {
+
+ $expected = new \stdClass;
+
+ $instance = new CallbackContainerBuilder();
+
+ $instance->registerExpectedReturnType( 'Foo', '\stdClass' );
+ $instance->registerObject( 'Foo', $expected );
+
+ $this->assertEquals(
+ $expected,
+ $instance->create( 'Foo' )
+ );
+
+ $this->assertEquals(
+ $expected,
+ $instance->singleton( 'Foo' )
+ );
+ }
+
+ public function testInjectInstanceForExistingRegisteredCallbackHandler() {
+
+ $stdClass = $this->getMockBuilder( '\stdClass' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $instance = new CallbackContainerBuilder( new FakeCallbackContainer() );
+ $instance->singleton( 'Foo' );
+
+ $instance->registerObject( 'Foo', $stdClass );
+
+ $this->assertSame(
+ $stdClass,
+ $instance->create( 'Foo' )
+ );
+
+ $this->assertSame(
+ $stdClass,
+ $instance->singleton( 'Foo' )
+ );
+ }
+
+ public function testOverrideSingletonInstanceOnRegisteredCallbackHandlerWithArguments() {
+
+ $argument = $this->getMockBuilder( '\stdClass' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $instance = new CallbackContainerBuilder(
+ new FakeCallbackContainer()
+ );
+
+ $this->assertSame(
+ $instance->singleton( 'FooWithNullArgument', $argument ),
+ $instance->singleton( 'service.one', $argument )
+ );
+
+ $override = $instance->singleton( 'FooWithNullArgument', null );
+
+ $this->assertNotSame(
+ $override,
+ $instance->singleton( 'FooWithNullArgument', $argument )
+ );
+
+ $instance->registerObject( 'FooWithNullArgument', $override );
+
+ $this->assertSame(
+ $override,
+ $instance->singleton( 'FooWithNullArgument', $argument )
+ );
+
+ $this->assertSame(
+ $override,
+ $instance->singleton( 'FooWithNullArgument', null )
+ );
+ }
+
+ public function testLoadParameterizedCallbackHandler() {
+
+ $instance = new CallbackContainerBuilder();
+
+ $instance->registerCallback( 'Foo', function( $containerBuilder, $a, $b, $c ) {
+ $stdClass = new \stdClass;
+ $stdClass->a = $a;
+ $stdClass->b = $b;
+ $stdClass->c = $c;
+
+ return $stdClass;
+ } );
+
+ $instance->registerExpectedReturnType( 'Foo', '\stdClass' );
+
+ $object = new \stdClass;
+ $object->extra = 123;
+
+ $this->assertEquals(
+ 'abc',
+ $instance->create( 'Foo', 'abc', 123, $object )->a
+ );
+
+ $this->assertEquals(
+ $object,
+ $instance->create( 'Foo', 'abc', 123, $object )->c
+ );
+ }
+
+ public function testRecursiveBuildToLoadParameterizedCallbackHandler() {
+
+ $instance = new CallbackContainerBuilder();
+
+ $instance->registerCallback( 'Foo', function( $containerBuilder, $a, $b = null, $c ) {
+ $stdClass = new \stdClass;
+ $stdClass->a = $a;
+ $stdClass->c = $c;
+
+ return $stdClass;
+ } );
+
+ $instance->registerExpectedReturnType( 'Foo', '\stdClass' );
+
+ $instance->registerCallback( 'Bar', function( $containerBuilder, $a, $b, $c ) use( $instance ) {
+ return $instance->create( 'Foo', $a, $b, $c );
+ } );
+
+ $instance->registerExpectedReturnType( 'Bar', '\stdClass' );
+
+ $object = new \stdClass;
+ $object->extra = 123;
+
+ $this->assertSame(
+ $object,
+ $instance->create( 'Bar', 'abc', null, $object )->c
+ );
+ }
+
+ public function testSingleton() {
+
+ $instance = new CallbackContainerBuilder();
+
+ $instance->registerCallback( 'Foo', function() {
+ return new \stdClass;
+ } );
+
+ $instance->registerExpectedReturnType( 'Foo', '\stdClass' );
+
+ $singleton = $instance->singleton( 'Foo' );
+
+ $this->assertSame(
+ $singleton,
+ $instance->singleton( 'Foo' )
+ );
+ }
+
+ public function testFingerprintedParameterizedSingletonCallbackHandler() {
+
+ $instance = new CallbackContainerBuilder();
+
+ $instance->registerCallback( 'Foo', function( $containerBuilder, $a, array $b ) {
+ $stdClass = new \stdClass;
+ $stdClass->a = $a;
+ $stdClass->b = $b;
+
+ return $stdClass;
+ } );
+
+ $instance->registerExpectedReturnType( 'Foo', '\stdClass' );
+
+ $this->assertSame(
+ $instance->singleton( 'Foo', 'abc', array( 'def' ) ),
+ $instance->singleton( 'Foo', 'abc', array( 'def' ) )
+ );
+
+ $this->assertNotSame(
+ $instance->singleton( 'Foo', 'abc', array( '123' ) ),
+ $instance->singleton( 'Foo', 'abc', array( 'def' ) )
+ );
+ }
+
+ public function testRegisterAlias() {
+
+ $instance = new CallbackContainerBuilder();
+
+ $instance->registerCallback( 'Foo', function( $containerBuilder ) {
+ return new \stdClass;
+ } );
+
+ $instance->registerAlias( 'Foo', 'Foobar' );
+ $instance->registerExpectedReturnType( 'Foo', '\stdClass' );
+
+ $this->assertTrue(
+ $instance->isRegistered( 'Foobar' )
+ );
+
+ $this->assertInstanceOf(
+ '\stdClass',
+ $instance->create( 'Foobar' )
+ );
+
+ $this->assertInstanceOf(
+ '\stdClass',
+ $instance->singleton( 'Foobar' )
+ );
+ }
+
+ public function testRegisterAliasOnExistingServiceNameThrowsException() {
+
+ $instance = new CallbackContainerBuilder();
+
+ $this->setExpectedException( '\Onoi\CallbackContainer\Exception\InvalidParameterTypeException' );
+ $instance->registerAlias( 'Foo', 123 );
+ }
+
+ public function testRegisterAliasOnInvalidNameThrowsException() {
+
+ $instance = new CallbackContainerBuilder();
+
+ $instance->registerCallback( 'Foo', function() {
+ return new \stdClass;
+ } );
+
+ $this->setExpectedException( '\Onoi\CallbackContainer\Exception\ServiceAliasAssignmentException' );
+ $instance->registerAlias( 'Foo', 'Foo' );
+ }
+
+ public function testRegisterAliasOnCrossedServiceAssignmentThrowsException() {
+
+ $instance = new CallbackContainerBuilder();
+
+ $instance->registerCallback( 'Foo', function() {
+ return new \stdClass;
+ } );
+
+ $instance->registerAlias( 'Foo', 'Foobar' );
+
+ $this->setExpectedException( '\Onoi\CallbackContainer\Exception\ServiceAliasCrossAssignmentException' );
+ $instance->registerAlias( 'Foo2', 'Foobar' );
+ }
+
+ public function testRegisterObjectWithAliasThrowsException() {
+
+ $instance = new CallbackContainerBuilder();
+
+ $instance->registerAlias( 'Foo', 'Foobar' );
+
+ $this->setExpectedException( '\Onoi\CallbackContainer\Exception\ServiceAliasMismatchException' );
+ $instance->registerObject( 'Foobar', new \stdClass );
+ }
+
+ public function testUnregisteredServiceOnCreateThrowsException() {
+
+ $instance = new CallbackContainerBuilder();
+
+ $this->setExpectedException( '\Onoi\CallbackContainer\Exception\ServiceNotFoundException' );
+ $instance->create( 'Foo' );
+ }
+
+ public function testUnregisteredServiceOnSingletonThrowsException() {
+
+ $instance = new CallbackContainerBuilder();
+
+ $this->setExpectedException( '\Onoi\CallbackContainer\Exception\ServiceNotFoundException' );
+ $instance->singleton( 'Foo' );
+ }
+
+ public function testCreateFromCallbackWithTypeMismatchThrowsException() {
+
+ $instance = new CallbackContainerBuilder();
+
+ $instance->registerCallback( 'Foo', function() {
+ return new \stdClass;
+ } );
+
+ $instance->registerExpectedReturnType( 'Foo', 'Bar' );
+
+ $this->setExpectedException( 'RuntimeException' );
+ $instance->create( 'Foo' );
+ }
+
+ public function testCreateWithInvalidNameForCallbackHandlerOnLoadThrowsException() {
+
+ $instance = new CallbackContainerBuilder();
+
+ $this->setExpectedException( 'InvalidArgumentException' );
+ $instance->create( new \stdClass );
+ }
+
+ public function testSingletonWithInvalidNameForCallbackHandlerOnSingletonThrowsException() {
+
+ $instance = new CallbackContainerBuilder();
+
+ $this->setExpectedException( 'InvalidArgumentException' );
+ $instance->singleton( new \stdClass );
+ }
+
+ public function testCreateOnCallbackHandlerWithCircularReferenceThrowsException() {
+
+ $instance = new CallbackContainerBuilder();
+
+ $this->setExpectedException( 'RuntimeException' );
+
+ $instance->registerCallback( 'Foo', function() use ( $instance ) {
+ return $instance->create( 'Foo' );
+ } );
+
+ $instance->registerExpectedReturnType( 'Foo', '\stdClass' );
+ $instance->create( 'Foo' );
+ }
+
+ public function testSingletonOnCallbackHandlerWithCircularReferenceThrowsException() {
+
+ $instance = new CallbackContainerBuilder();
+
+ $this->setExpectedException( 'RuntimeException' );
+
+ $instance->registerCallback( 'Foo', function() use ( $instance ) {
+ return $instance->singleton( 'Foo' );
+ } );
+
+ $instance->singleton( 'Foo' );
+ }
+
+ public function testRegisterCallbackWithInvalidNameThrowsException() {
+
+ $instance = new CallbackContainerBuilder();
+
+ $this->setExpectedException( 'InvalidArgumentException' );
+ $instance->registerCallback( new \stdClass, function() {
+ return new \stdClass;
+ } );
+ }
+
+ public function testRegisterObjectWithInvalidNameThrowsException() {
+
+ $instance = new CallbackContainerBuilder();
+
+ $this->setExpectedException( 'InvalidArgumentException' );
+ $instance->registerObject( new \stdClass, new \stdClass );
+ }
+
+ public function testRegisterExpectedReturnTypeWithInvalidTypeThrowsException() {
+
+ $instance = new CallbackContainerBuilder();
+
+ $this->setExpectedException( 'InvalidArgumentException' );
+ $instance->registerExpectedReturnType( new \stdClass, 'Bar' );
+ }
+
+ public function testRegisterFromWithInvalidFileThrowsException() {
+
+ $instance = new CallbackContainerBuilder();
+
+ $this->setExpectedException( 'RuntimeException' );
+ $instance->registerFromFile( 'Foo' );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/CallbackContainerFactoryTest.php b/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/CallbackContainerFactoryTest.php
new file mode 100644
index 00000000..67efa640
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/CallbackContainerFactoryTest.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Onoi\CallbackContainer\Tests;
+
+use Onoi\CallbackContainer\CallbackContainerFactory;
+
+/**
+ * @covers \Onoi\CallbackContainer\CallbackContainerFactory
+ * @group onoi-callback-container
+ *
+ * @license GNU GPL v2+
+ * @since 2.0
+ *
+ * @author mwjames
+ */
+class CallbackContainerFactoryTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\CallbackContainer\CallbackContainerFactory',
+ new CallbackContainerFactory()
+ );
+ }
+
+ public function testCanConstructCallbackContainerBuilder() {
+
+ $instance = new CallbackContainerFactory();
+
+ $this->assertInstanceOf(
+ '\Onoi\CallbackContainer\CallbackContainerBuilder',
+ $instance->newCallbackContainerBuilder()
+ );
+ }
+
+ public function testCanConstructNullContainerBuilder() {
+
+ $instance = new CallbackContainerFactory();
+
+ $this->assertInstanceOf(
+ '\Onoi\CallbackContainer\NullContainerBuilder',
+ $instance->newNullContainerBuilder()
+ );
+ }
+
+ public function testCanConstructServicesManager() {
+
+ $instance = new CallbackContainerFactory();
+
+ $this->assertInstanceOf(
+ '\Onoi\CallbackContainer\ServicesManager',
+ $instance->newServicesManager()
+ );
+
+ $containerBuilder = $this->getMockBuilder( '\Onoi\CallbackContainer\ContainerBuilder' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->assertInstanceOf(
+ '\Onoi\CallbackContainer\ServicesManager',
+ $instance->newServicesManager( $containerBuilder )
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/Exception/ServiceAliasAssignmentExceptionTest.php b/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/Exception/ServiceAliasAssignmentExceptionTest.php
new file mode 100644
index 00000000..166840d0
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/Exception/ServiceAliasAssignmentExceptionTest.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Onoi\CallbackContainer\Tests\Exception;
+
+use Onoi\CallbackContainer\Exception\ServiceAliasAssignmentException;
+
+/**
+ * @covers \Onoi\CallbackContainer\Exception\ServiceAliasAssignmentException
+ * @group onoi-callback-container
+ *
+ * @license GNU GPL v2+
+ * @since 2.0
+ *
+ * @author mwjames
+ */
+class ServiceAliasAssignmentExceptionTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\CallbackContainer\Exception\ServiceAliasAssignmentException',
+ new ServiceAliasAssignmentException( 'foo' )
+ );
+
+ $this->assertInstanceOf(
+ '\RuntimeException',
+ new ServiceAliasAssignmentException( 'foo' )
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/Exception/ServiceAliasCrossAssignmentExceptionTest.php b/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/Exception/ServiceAliasCrossAssignmentExceptionTest.php
new file mode 100644
index 00000000..513351a9
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/Exception/ServiceAliasCrossAssignmentExceptionTest.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Onoi\CallbackContainer\Tests\Exception;
+
+use Onoi\CallbackContainer\Exception\ServiceAliasCrossAssignmentException;
+
+/**
+ * @covers \Onoi\CallbackContainer\Exception\ServiceAliasCrossAssignmentException
+ * @group onoi-callback-container
+ *
+ * @license GNU GPL v2+
+ * @since 2.0
+ *
+ * @author mwjames
+ */
+class ServiceAliasCrossAssignmentExceptionTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\CallbackContainer\Exception\ServiceAliasCrossAssignmentException',
+ new ServiceAliasCrossAssignmentException( 'foo', 'Bar', 'Foobar' )
+ );
+
+ $this->assertInstanceOf(
+ '\RuntimeException',
+ new ServiceAliasCrossAssignmentException( 'foo', 'Bar', 'Foobar' )
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/Exception/ServiceAliasMismatchExceptionTest.php b/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/Exception/ServiceAliasMismatchExceptionTest.php
new file mode 100644
index 00000000..0c65b9e5
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/Exception/ServiceAliasMismatchExceptionTest.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Onoi\CallbackContainer\Tests\Exception;
+
+use Onoi\CallbackContainer\Exception\ServiceAliasMismatchException;
+
+/**
+ * @covers \Onoi\CallbackContainer\Exception\ServiceAliasMismatchException
+ * @group onoi-callback-container
+ *
+ * @license GNU GPL v2+
+ * @since 2.0
+ *
+ * @author mwjames
+ */
+class ServiceAliasMismatchExceptionTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\CallbackContainer\Exception\ServiceAliasMismatchException',
+ new ServiceAliasMismatchException( 'foo' )
+ );
+
+ $this->assertInstanceOf(
+ '\RuntimeException',
+ new ServiceAliasMismatchException( 'foo' )
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/Exception/ServiceCircularReferenceExceptionTest.php b/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/Exception/ServiceCircularReferenceExceptionTest.php
new file mode 100644
index 00000000..19f9d80d
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/Exception/ServiceCircularReferenceExceptionTest.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Onoi\CallbackContainer\Tests\Exception;
+
+use Onoi\CallbackContainer\Exception\ServiceCircularReferenceException;
+
+/**
+ * @covers \Onoi\CallbackContainer\Exception\ServiceCircularReferenceException
+ * @group onoi-callback-container
+ *
+ * @license GNU GPL v2+
+ * @since 2.0
+ *
+ * @author mwjames
+ */
+class ServiceCircularReferenceExceptionTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\CallbackContainer\Exception\ServiceCircularReferenceException',
+ new ServiceCircularReferenceException( 'foo' )
+ );
+
+ $this->assertInstanceOf(
+ '\RuntimeException',
+ new ServiceCircularReferenceException( 'foo' )
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/Exception/ServiceTypeMismatchExceptionTest.php b/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/Exception/ServiceTypeMismatchExceptionTest.php
new file mode 100644
index 00000000..a5da93ec
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/Exception/ServiceTypeMismatchExceptionTest.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Onoi\CallbackContainer\Tests\Exception;
+
+use Onoi\CallbackContainer\Exception\ServiceTypeMismatchException;
+
+/**
+ * @covers \Onoi\CallbackContainer\Exception\ServiceTypeMismatchException
+ * @group onoi-callback-container
+ *
+ * @license GNU GPL v2+
+ * @since 2.0
+ *
+ * @author mwjames
+ */
+class ServiceTypeMismatchExceptionTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\CallbackContainer\Exception\ServiceTypeMismatchException',
+ new ServiceTypeMismatchException( 'Foo', 'Bar', 'Foobar' )
+ );
+
+ $this->assertInstanceOf(
+ '\RuntimeException',
+ new ServiceTypeMismatchException( 'Foo', 'Bar', 'Foobar' )
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/NullContainerBuilderTest.php b/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/NullContainerBuilderTest.php
new file mode 100644
index 00000000..881a6859
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/NullContainerBuilderTest.php
@@ -0,0 +1,67 @@
+<?php
+
+namespace Onoi\CallbackContainer\Tests;
+
+use Onoi\CallbackContainer\NullContainerBuilder;
+
+/**
+ * @covers \Onoi\CallbackContainer\NullContainerBuilder
+ * @group onoi-callback-container
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class NullContainerBuilderTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\CallbackContainer\NullContainerBuilder',
+ new NullContainerBuilder()
+ );
+ }
+
+ public function testInterfaceMethods() {
+
+ $instance = new NullContainerBuilder();
+
+ $this->assertNull(
+ $instance->create( 'Foo' )
+ );
+
+ $this->assertNull(
+ $instance->singleton( 'Foo' )
+ );
+
+ $this->assertNull(
+ $instance->registerExpectedReturnType( 'Foo', 'bar' )
+ );
+
+ $this->assertNull(
+ $instance->registerAlias( 'Foo', 'bar' )
+ );
+
+ $this->assertNull(
+ $instance->registerObject( 'Foo', 'bar' )
+ );
+
+ $this->assertNull(
+ $instance->registerCallback( 'Foo', function() {} )
+ );
+
+ $this->assertNull(
+ $instance->registerFromFile( 'File' )
+ );
+
+ $callbackContainer = $this->getMockBuilder( '\Onoi\CallbackContainer\CallbackContainer' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->assertNull(
+ $instance->registerCallbackContainer( $callbackContainer )
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/ServicesManagerTest.php b/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/ServicesManagerTest.php
new file mode 100644
index 00000000..cf8b6058
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/ServicesManagerTest.php
@@ -0,0 +1,126 @@
+<?php
+
+namespace Onoi\CallbackContainer\Tests;
+
+use Onoi\CallbackContainer\ServicesManager;
+use Onoi\CallbackContainer\CallbackContainerFactory;
+
+/**
+ * @covers \Onoi\CallbackContainer\ServicesManager
+ * @group onoi-callback-container
+ *
+ * @license GNU GPL v2+
+ * @since 2.0
+ *
+ * @author mwjames
+ */
+class ServicesManagerTest extends \PHPUnit_Framework_TestCase {
+
+ private $servicesManager;
+
+ protected function setUp() {
+ parent::setUp();
+
+ $callbackContainerFactory = new CallbackContainerFactory();
+ $this->servicesManager = $callbackContainerFactory->newServicesManager();
+ }
+
+ public function testCanConstruct() {
+
+ $containerBuilder = $this->getMockBuilder( '\Onoi\CallbackContainer\ContainerBuilder' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->assertInstanceOf(
+ '\Onoi\CallbackContainer\ServicesManager',
+ new ServicesManager( $containerBuilder )
+ );
+ }
+
+ public function testAddServiceWithScalarType() {
+
+ $instance = $this->servicesManager;
+ $instance->add( 'Foo', 123 );
+
+ $this->assertTrue(
+ $instance->has( 'Foo' )
+ );
+
+ $this->assertSame(
+ 123,
+ $instance->get( 'Foo' )
+ );
+ }
+
+ public function testAddServiceWithObjectType() {
+
+ $instance = $this->servicesManager;
+ $instance->add( 'Foo', $this );
+
+ $this->assertTrue(
+ $instance->has( 'Foo' )
+ );
+
+ $this->assertSame(
+ $this,
+ $instance->get( 'Foo' )
+ );
+ }
+
+ public function testRemoveService() {
+
+ $instance = $this->servicesManager;
+ $instance->add( 'Foo', $this );
+
+ $this->assertTrue(
+ $instance->has( 'Foo' )
+ );
+
+ $instance->remove( 'Foo' );
+
+ $this->assertFalse(
+ $instance->has( 'Foo' )
+ );
+ }
+
+ public function testOverrideUntypedService() {
+
+ $instance = $this->servicesManager;
+ $instance->add( 'Foo', $this );
+
+ $this->assertTrue(
+ $instance->has( 'Foo' )
+ );
+
+ $instance->replace( 'Foo', 123 );
+
+ $this->assertSame(
+ 123,
+ $instance->get( 'Foo' )
+ );
+ }
+
+ public function testTryToOverrideTypedServiceWithIncompatibleTypeThrowsException() {
+
+ $instance = $this->servicesManager;
+ $instance->add( 'Foo', $this, '\PHPUnit_Framework_TestCase' );
+
+ $this->assertTrue(
+ $instance->has( 'Foo' )
+ );
+
+ $instance->replace( 'Foo', 123 );
+
+ $this->setExpectedException( '\Onoi\CallbackContainer\Exception\ServiceTypeMismatchException' );
+ $instance->get( 'Foo' );
+ }
+
+ public function testTryToAccessToUnknownServiceThrowsException() {
+
+ $instance = $this->servicesManager;
+
+ $this->setExpectedException( '\Onoi\CallbackContainer\Exception\ServiceNotFoundException' );
+ $instance->get( 'Foo' );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/SpyLogger.php b/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/SpyLogger.php
new file mode 100644
index 00000000..9564e26f
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/tests/phpunit/Unit/SpyLogger.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Onoi\CallbackContainer\Tests;
+
+use Psr\Log\AbstractLogger;
+
+/**
+ * @group onoi-callback-container
+ *
+ * @license GNU GPL v2+
+ * @since 2.0
+ *
+ * @author mwjames
+ */
+class SpyLogger extends AbstractLogger {
+
+ /**
+ * @var array
+ */
+ private $logs = array();
+
+ /**
+ * @since 2.0
+ *
+ * {@inheritDoc}
+ */
+ public function log( $level, $message, array $context = array() ) {
+ $this->logs[] = array( $level, $message, $context );
+ }
+
+ /**
+ * @since 2.0
+ *
+ * @return array
+ */
+ public function getLogs() {
+ return $this->logs;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/callback-container/tests/travis/run-tests.sh b/www/wiki/vendor/onoi/callback-container/tests/travis/run-tests.sh
new file mode 100644
index 00000000..dfe3b37e
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/tests/travis/run-tests.sh
@@ -0,0 +1,15 @@
+#! /bin/bash
+set -ex
+
+BASE_PATH=$(pwd)
+
+composer install
+composer dump-autoload
+composer validate --no-interaction
+
+if [ "$TYPE" == "coverage" ]
+then
+ composer phpunit -- --coverage-clover $BASE_PATH/build/coverage.clover
+else
+ composer phpunit
+fi
diff --git a/www/wiki/vendor/onoi/callback-container/tests/travis/upload-coverage-report.sh b/www/wiki/vendor/onoi/callback-container/tests/travis/upload-coverage-report.sh
new file mode 100644
index 00000000..aff95cf7
--- /dev/null
+++ b/www/wiki/vendor/onoi/callback-container/tests/travis/upload-coverage-report.sh
@@ -0,0 +1,10 @@
+#! /bin/bash
+set -ex
+
+BASE_PATH=$(pwd)
+
+if [ "$TYPE" == "coverage" ]
+then
+ wget https://scrutinizer-ci.com/ocular.phar
+ php ocular.phar code-coverage:upload --format=php-clover $BASE_PATH/build/coverage.clover
+fi \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/event-dispatcher/.gitignore b/www/wiki/vendor/onoi/event-dispatcher/.gitignore
new file mode 100644
index 00000000..09ec1c53
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/.gitignore
@@ -0,0 +1,10 @@
+*~
+*.kate-swp
+
+!.*
+.idea/
+
+composer.phar
+composer.lock
+
+vendor/ \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/event-dispatcher/.scrutinizer.yml b/www/wiki/vendor/onoi/event-dispatcher/.scrutinizer.yml
new file mode 100644
index 00000000..9358405a
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/.scrutinizer.yml
@@ -0,0 +1,18 @@
+filter:
+ excluded_paths:
+ - 'vendor/*'
+
+tools:
+ php_mess_detector:
+ config:
+ controversial_rules: { superglobals: false }
+ php_cpd: true
+ php_pdepend: true
+ php_code_coverage: false
+ php_code_sniffer: true
+ php_cs_fixer: true
+ php_loc: true
+ php_analyzer: true
+ sensiolabs_security_checker: true
+ external_code_coverage:
+ timeout: '900'
diff --git a/www/wiki/vendor/onoi/event-dispatcher/.travis.yml b/www/wiki/vendor/onoi/event-dispatcher/.travis.yml
new file mode 100644
index 00000000..17b668e3
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/.travis.yml
@@ -0,0 +1,17 @@
+# http://docs.travis-ci.com/user/workers/container-based-infrastructure/
+sudo: false
+
+language: php
+
+matrix:
+ include:
+ - env: TYPE=coverage
+ php: 5.6
+ - env: TYPE=UNIT;
+ php: 5.6
+
+script:
+ - bash ./tests/travis/run-tests.sh
+
+after_success:
+ - bash ./tests/travis/upload-coverage-report.sh
diff --git a/www/wiki/vendor/onoi/event-dispatcher/LICENSE b/www/wiki/vendor/onoi/event-dispatcher/LICENSE
new file mode 100644
index 00000000..d6a93266
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/LICENSE
@@ -0,0 +1,340 @@
+GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ {description}
+ Copyright (C) {year} {fullname}
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ {signature of Ty Coon}, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
+
diff --git a/www/wiki/vendor/onoi/event-dispatcher/README.md b/www/wiki/vendor/onoi/event-dispatcher/README.md
new file mode 100644
index 00000000..c1ffcde8
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/README.md
@@ -0,0 +1,133 @@
+# Event dispatcher
+
+[![Build Status](https://secure.travis-ci.org/onoi/event-dispatcher.svg?branch=master)](http://travis-ci.org/onoi/event-dispatcher)
+[![Code Coverage](https://scrutinizer-ci.com/g/onoi/event-dispatcher/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/onoi/event-dispatcher/?branch=master)
+[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/onoi/event-dispatcher/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/onoi/event-dispatcher/?branch=master)
+[![Latest Stable Version](https://poser.pugx.org/onoi/event-dispatcher/version.png)](https://packagist.org/packages/onoi/event-dispatcher)
+[![Packagist download count](https://poser.pugx.org/onoi/event-dispatcher/d/total.png)](https://packagist.org/packages/onoi/event-dispatcher)
+
+A minimalistic event dispatcher (observer) interface that was part of the [Semantic MediaWiki][smw] code base and
+is now being deployed as independent library.
+
+## Requirements
+
+PHP 5.3/HHVM 3.3 or later
+
+## Installation
+
+The recommended installation method for this library is by either adding
+the dependency to your [composer.json][composer].
+
+```json
+{
+ "require": {
+ "onoi/event-dispatcher": "~1.0"
+ }
+}
+```
+
+## Usage
+
+```php
+class BarListener implements EventListener {
+
+ public function execute( DispatchContext $dispatchContext = null ) {
+ // Do something
+ }
+
+ public function isPropagationStopped() {
+ return false;
+ }
+}
+```
+```php
+class ListenerCollectionRegistry implements EventListenerCollection {
+
+ private $eventListenerCollection;
+
+ public function __construct( EventListenerCollection $eventListenerCollection ) {
+ $this->eventListenerCollection = $eventListenerCollection;
+ }
+
+ public function getCollection() {
+ return $this->addToListenerCollection()->getCollection();
+ }
+
+ private function addToListenerCollection() {
+
+ $this->eventListenerCollection->registerCallback( 'do.something', function() {
+ // Do something
+ } );
+
+ $this->eventListenerCollection->registerListener( 'notify.bar', new BarListener() );
+
+ return $this->eventListenerCollection;
+ }
+}
+```
+```php
+$eventDispatcherFactory = new EventDispatcherFactory();
+
+$listenerCollectionRegistry = new ListenerCollectionRegistry(
+ $eventDispatcherFactory->newGenericEventListenerCollection()
+);
+
+$eventDispatcher = $eventDispatcherFactory->newGenericEventDispatcher();
+$eventDispatcher->addListenerCollection( $listenerCollectionRegistry );
+
+class Foo {
+
+ use EventDispatcherAwareTrait;
+
+ public function doSomething() {
+
+ // No context
+ $this->eventDispatcher->dispatch( 'do.something' );
+
+ $dispatchContext = new DispatchContext();
+ $dispatchContext->set( 'dosomethingelse', new \stdClass );
+
+ // Using `DispatchContext`
+ $this->eventDispatcher->dispatch( 'notify.bar', $dispatchContext );
+
+ // Using an array as context which is later converted into
+ // a `DispatchContext`
+ $this->eventDispatcher->dispatch( 'notify.foo', [ 'Bar' => 123 ] );
+ }
+}
+
+$instance = new Foo();
+$instance->setEventDispatcher( $eventDispatcher );
+$instance->doSomething();
+```
+
+## Contribution and support
+
+If you want to contribute work to the project please subscribe to the
+developers mailing list and have a look at the [contribution guidelinee](/CONTRIBUTING.md). A list of people who have made contributions in the past can be found [here][contributors].
+
+* [File an issue](https://github.com/onoi/event-dispatcher/issues)
+* [Submit a pull request](https://github.com/onoi/event-dispatcher/pulls)
+
+### Tests
+
+The library provides unit tests that covers the core-functionality normally run by the [continues integration platform][travis]. Tests can also be executed manually using the PHPUnit configuration file found in the root directory.
+
+### Release notes
+
+- 1.1.0 (2019-01-27)
+ - Allowed `EventDispatcher::dispatch` to take an array as context object
+ - Added the `EventNotDispatchableException` and `Subscriber` interface
+ - Added the `EventDispatcherAwareTrait` class
+
+* 1.0.0 initial release (2015-03-25)
+
+## License
+
+[GNU General Public License 2.0 or later][license].
+
+[composer]: https://getcomposer.org/
+[contributors]: https://github.com/onoi/event-dispatcher/graphs/contributors
+[license]: https://www.gnu.org/copyleft/gpl.html
+[travis]: https://travis-ci.org/onoi/event-dispatcher
+[smw]: https://github.com/SemanticMediaWiki/SemanticMediaWiki/
diff --git a/www/wiki/vendor/onoi/event-dispatcher/composer.json b/www/wiki/vendor/onoi/event-dispatcher/composer.json
new file mode 100644
index 00000000..3c52ff1e
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/composer.json
@@ -0,0 +1,35 @@
+{
+ "name": "onoi/event-dispatcher",
+ "type": "library",
+ "description": "A minimalistic interface to relay generic events to registered listeners",
+ "keywords": [
+ "events",
+ "listener"
+ ],
+ "homepage": "https://github.com/onoi/event-dispatcher",
+ "license": "GPL-2.0-or-later",
+ "authors": [
+ {
+ "name": "James Hong Kong"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Onoi\\EventDispatcher\\": "src/"
+ }
+ },
+ "config": {
+ "process-timeout": 0
+ },
+ "scripts":{
+ "phpunit": "phpunit -c phpunit.xml.dist"
+ }
+}
diff --git a/www/wiki/vendor/onoi/event-dispatcher/phpunit.xml.dist b/www/wiki/vendor/onoi/event-dispatcher/phpunit.xml.dist
new file mode 100644
index 00000000..bdc57836
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/phpunit.xml.dist
@@ -0,0 +1,27 @@
+<phpunit backupGlobals="false"
+ backupStaticAttributes="false"
+ bootstrap="tests/bootstrap.php"
+ cacheTokens="false"
+ colors="true"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ stopOnError="false"
+ stopOnFailure="false"
+ stopOnIncomplete="false"
+ stopOnSkipped="false"
+ verbose="true">
+ <testsuites>
+ <testsuite name="onoi-event-dispatcher">
+ <directory>tests/phpunit/Unit</directory>
+ </testsuite>
+ <testsuite name="onoi-event-dispatcher-integration">
+ <directory>tests/phpunit/Integration</directory>
+ </testsuite>
+ </testsuites>
+ <filter>
+ <whitelist addUncoveredFilesFromWhitelist="true">
+ <directory suffix=".php">src</directory>
+ </whitelist>
+ </filter>
+</phpunit>
diff --git a/www/wiki/vendor/onoi/event-dispatcher/src/DispatchContext.php b/www/wiki/vendor/onoi/event-dispatcher/src/DispatchContext.php
new file mode 100644
index 00000000..5ed28656
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/src/DispatchContext.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Onoi\EventDispatcher;
+
+use InvalidArgumentException;
+
+/**
+ * Generic context that can be added during the dispatch process to be
+ * accessible to each invoked listener
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class DispatchContext {
+
+ /**
+ * @var array
+ */
+ private $container = array();
+
+ /**
+ * @since 1.1
+ *
+ * @param array $container
+ *
+ * @return DispatchContext
+ */
+ public static function newFromArray( array $container ) {
+ $dispatchContext = new DispatchContext();
+
+ foreach ( $container as $key => $value ) {
+ $dispatchContext->set( $key, $value );
+ }
+
+ return $dispatchContext;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param string $key
+ *
+ * @return boolean
+ */
+ public function has( $key ) {
+ return isset( $this->container[strtolower( $key )] );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param string $key
+ * @param mixed $value
+ */
+ public function set( $key, $value ) {
+ $this->container[strtolower( $key )] = $value;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param string $key
+ *
+ * @return mixed
+ * @throws InvalidArgumentException
+ */
+ public function get( $key ) {
+
+ if ( $this->has( $key ) ) {
+ return $this->container[strtolower( $key )];
+ }
+
+ throw new InvalidArgumentException( "{$key} is unknown" );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @return boolean
+ */
+ public function isPropagationStopped() {
+ return $this->has( 'propagationstop' ) ? $this->get( 'propagationstop' ) : false;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/event-dispatcher/src/Dispatcher/GenericEventDispatcher.php b/www/wiki/vendor/onoi/event-dispatcher/src/Dispatcher/GenericEventDispatcher.php
new file mode 100644
index 00000000..efeb20bf
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/src/Dispatcher/GenericEventDispatcher.php
@@ -0,0 +1,152 @@
+<?php
+
+namespace Onoi\EventDispatcher\Dispatcher;
+
+use Onoi\EventDispatcher\Subscriber;
+use Onoi\EventDispatcher\EventDispatcher;
+use Onoi\EventDispatcher\EventListener;
+use Onoi\EventDispatcher\EventListenerCollection;
+use Onoi\EventDispatcher\DispatchContext;
+use Onoi\EventDispatcher\Exception\EventNotDispatchableException;
+use InvalidArgumentException;
+use RuntimeException;
+use Traversable;
+
+/**
+ * Dispatches events to registered listeners
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class GenericEventDispatcher implements EventDispatcher, Subscriber {
+
+ /**
+ * @var array
+ */
+ private $dispatchableListeners = array();
+
+ /**
+ * @var boolean
+ */
+ private $throwOnMissingEvent = false;
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ *
+ * @throws RuntimeException
+ */
+ public function addListenerCollection( EventListenerCollection $listenerCollection ) {
+
+ $collection = $listenerCollection->getCollection();
+
+ if( !is_array( $collection ) && !$collection instanceof Traversable ) {
+ throw new RuntimeException( "Expected a traversable object" );
+ }
+
+ foreach ( $collection as $event => $listeners ) {
+ foreach ( $listeners as $listener ) {
+ $this->addListener( $event, $listener );
+ }
+ }
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ *
+ * @throws InvalidArgumentException
+ */
+ public function addListener( $event, EventListener $listener ) {
+
+ if ( !is_string( $event ) ) {
+ throw new InvalidArgumentException( "Expected a string" );
+ }
+
+ $event = strtolower( $event );
+
+ if ( !isset( $this->dispatchableListeners[$event] ) ) {
+ $this->dispatchableListeners[$event] = array();
+ }
+
+ $this->dispatchableListeners[$event][] = $listener;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function removeListener( $event, EventListener $listener = null ) {
+
+ $event = strtolower( $event );
+
+ if ( !$this->hasEvent( $event ) ) {
+ return;
+ }
+
+ if ( $listener !== null ) {
+ foreach ( $this->dispatchableListeners[$event] as $key => $dispatchableListener ) {
+ if ( $dispatchableListener == $listener ) {
+ unset( $this->dispatchableListeners[$event][$key] );
+ }
+ }
+ }
+
+ if ( $listener === null || $this->dispatchableListeners[$event] === array() ) {
+ unset( $this->dispatchableListeners[$event] );
+ }
+ }
+
+ /**
+ * @since 1.1
+ *
+ * @param boolean $throwOnMissingEvent
+ */
+ public function throwOnMissingEvent( $throwOnMissingEvent ) {
+ $this->throwOnMissingEvent = (bool)$throwOnMissingEvent;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function hasEvent( $event ) {
+ return isset( $this->dispatchableListeners[strtolower( $event )] );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function dispatch( $event, $dispatchContext = null ) {
+
+ $event = strtolower( $event );
+
+ if ( !$this->hasEvent( $event ) && $this->throwOnMissingEvent ) {
+ throw new EventNotDispatchableException( $event );
+ } elseif( !$this->hasEvent( $event ) ) {
+ return;
+ }
+
+ if ( is_array( $dispatchContext ) ) {
+ $dispatchContext = DispatchContext::newFromArray( $dispatchContext );
+ }
+
+ foreach ( $this->dispatchableListeners[$event] as $listener ) {
+
+ $listener->execute( $dispatchContext );
+
+ if ( $listener->isPropagationStopped() || ( $dispatchContext !== null && $dispatchContext->isPropagationStopped() ) ) {
+ break;
+ }
+ }
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/event-dispatcher/src/EventDispatcher.php b/www/wiki/vendor/onoi/event-dispatcher/src/EventDispatcher.php
new file mode 100644
index 00000000..151878d0
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/src/EventDispatcher.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Onoi\EventDispatcher;
+
+/**
+ * Dispatches events to registered listeners
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+interface EventDispatcher {
+
+ /**
+ * Whether an event identifier has been registered listeners or not
+ *
+ * @since 1.0
+ *
+ * @param string $event
+ *
+ * @return boolean
+ */
+ public function hasEvent( $event );
+
+ /**
+ * Notifies all listeners registered to an event identifier
+ *
+ * @since 1.0
+ *
+ * @param string $event
+ * @param DispatchContext|array|null $dispatchContext
+ */
+ public function dispatch( $event, $dispatchContext = null );
+
+}
diff --git a/www/wiki/vendor/onoi/event-dispatcher/src/EventDispatcherAwareTrait.php b/www/wiki/vendor/onoi/event-dispatcher/src/EventDispatcherAwareTrait.php
new file mode 100644
index 00000000..8c9bb112
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/src/EventDispatcherAwareTrait.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Onoi\EventDispatcher;
+
+/**
+ * @license GNU GPL v2+
+ * @since 1.1
+ *
+ * @author mwjames
+ */
+trait EventDispatcherAwareTrait {
+
+ /**
+ * @var EventDispatcher
+ */
+ protected $eventDispatcher;
+
+ /**
+ * @since 1.1
+ *
+ * @param EventDispatcher $eventDispatcher
+ */
+ public function setEventDispatcher( EventDispatcher $eventDispatcher ) {
+ $this->eventDispatcher = $eventDispatcher;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/event-dispatcher/src/EventDispatcherFactory.php b/www/wiki/vendor/onoi/event-dispatcher/src/EventDispatcherFactory.php
new file mode 100644
index 00000000..260ed84a
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/src/EventDispatcherFactory.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Onoi\EventDispatcher;
+
+use Onoi\EventDispatcher\Dispatcher\GenericEventDispatcher;
+use Onoi\EventDispatcher\Listener\NullEventListener;
+use Onoi\EventDispatcher\Listener\GenericCallbackEventListener;
+use Onoi\EventDispatcher\Listener\GenericEventListenerCollection;
+
+/**
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class EventDispatcherFactory {
+
+ /**
+ * @var EventDispatcherFactory
+ */
+ private static $instance = null;
+
+ /**
+ * @since 1.0
+ *
+ * @return EventDispatcherFactory
+ */
+ public static function getInstance() {
+
+ if ( self::$instance === null ) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * @since 1.0
+ */
+ public static function clear() {
+ self::$instance = null;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @return GenericEventDispatcher
+ */
+ public function newGenericEventDispatcher() {
+ return new GenericEventDispatcher();
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @return DispatchContext
+ */
+ public function newDispatchContext() {
+ return new DispatchContext();
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @return NullEventListener
+ */
+ public function newNullEventListener() {
+ return new NullEventListener();
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @return GenericCallbackEventListener
+ */
+ public function newGenericCallbackEventListener() {
+ return new GenericCallbackEventListener();
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @return GenericEventListenerCollection
+ */
+ public function newGenericEventListenerCollection() {
+ return new GenericEventListenerCollection();
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/event-dispatcher/src/EventListener.php b/www/wiki/vendor/onoi/event-dispatcher/src/EventListener.php
new file mode 100644
index 00000000..c86b157c
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/src/EventListener.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Onoi\EventDispatcher;
+
+/**
+ * Interface for objects that can be listen to
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+interface EventListener {
+
+ /**
+ * Execute a registered listener action
+ *
+ * @since 1.0
+ *
+ * @param DispatchContext|null $dispatchContext
+ */
+ public function execute( DispatchContext $dispatchContext = null );
+
+ /**
+ * Whether propagation of the event to be stopped after the execution.
+ *
+ * It influences the dispatch persistence state for succeeding listeners
+ * to continue with the execution process for the same event.
+ *
+ * @since 1.0
+ *
+ * @return boolean
+ */
+ public function isPropagationStopped();
+
+}
diff --git a/www/wiki/vendor/onoi/event-dispatcher/src/EventListenerCollection.php b/www/wiki/vendor/onoi/event-dispatcher/src/EventListenerCollection.php
new file mode 100644
index 00000000..5052d70b
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/src/EventListenerCollection.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Onoi\EventDispatcher;
+
+/**
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+interface EventListenerCollection {
+
+ /**
+ * Returns a collection of registered EventListeners
+ *
+ * @since 1.0
+ *
+ * @return EventListener[]
+ */
+ public function getCollection();
+
+}
diff --git a/www/wiki/vendor/onoi/event-dispatcher/src/Exception/EventNotDispatchableException.php b/www/wiki/vendor/onoi/event-dispatcher/src/Exception/EventNotDispatchableException.php
new file mode 100644
index 00000000..7e52db02
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/src/Exception/EventNotDispatchableException.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Onoi\EventDispatcher\Exception;
+
+use RuntimeException;
+
+/**
+ * @license GNU GPL v2+
+ * @since 1.1
+ *
+ * @author mwjames
+ */
+class EventNotDispatchableException extends RuntimeException {
+
+ /**
+ * @since 1.1
+ *
+ * @param string $event
+ */
+ public function __construct( $event ) {
+ parent::__construct( "Event delegation failed due to missing listeners for the `$event` event!" );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/event-dispatcher/src/Listener/GenericCallbackEventListener.php b/www/wiki/vendor/onoi/event-dispatcher/src/Listener/GenericCallbackEventListener.php
new file mode 100644
index 00000000..e1275d2f
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/src/Listener/GenericCallbackEventListener.php
@@ -0,0 +1,84 @@
+<?php
+
+namespace Onoi\EventDispatcher\Listener;
+
+use Onoi\EventDispatcher\EventListener;
+use Onoi\EventDispatcher\DispatchContext;
+use RuntimeException;
+
+/**
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class GenericCallbackEventListener implements EventListener {
+
+ /**
+ * @var array
+ */
+ protected $callbacks = array();
+
+ /**
+ * @var boolean
+ */
+ private $propagationStopState = false;
+
+ /**
+ * @since 1.0
+ *
+ * @param Closure|callable|null $callback
+ */
+ public function __construct( $callback = null ) {
+ if ( $callback !== null ) {
+ $this->registerCallback( $callback );
+ }
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param Closure|callable $callback
+ * @throws RuntimeException
+ */
+ public function registerCallback( $callback ) {
+
+ if ( !is_callable( $callback ) ) {
+ throw new RuntimeException( "Invoked object is not a valid callback or Closure" );
+ }
+
+ // While this does not build a real dependency chain, it allows for atomic
+ // event handling by following FIFO
+ $this->callbacks[] = $callback;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function execute( DispatchContext $dispatchContext = null ) {
+ foreach ( $this->callbacks as $callback ) {
+ call_user_func_array( $callback, array( $dispatchContext ) );
+ }
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param boolean $propagationStopState
+ */
+ public function setPropagationStopState( $propagationStopState ) {
+ $this->propagationStopState = (bool)$propagationStopState;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function isPropagationStopped() {
+ return $this->propagationStopState;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/event-dispatcher/src/Listener/GenericEventListenerCollection.php b/www/wiki/vendor/onoi/event-dispatcher/src/Listener/GenericEventListenerCollection.php
new file mode 100644
index 00000000..a2f05b94
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/src/Listener/GenericEventListenerCollection.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Onoi\EventDispatcher\Listener;
+
+use Onoi\EventDispatcher\EventListener;
+use Onoi\EventDispatcher\EventListenerCollection;
+use RuntimeException;
+use InvalidArgumentException;
+
+/**
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class GenericEventListenerCollection implements EventListenerCollection {
+
+ /**
+ * @var array
+ */
+ private $collection = array();
+
+ /**
+ * @since 1.0
+ *
+ * @param string $event
+ * @param EventListener $listener
+ *
+ * @throws InvalidArgumentException
+ */
+ public function registerListener( $event, EventListener $listener ) {
+
+ if ( !is_string( $event ) ) {
+ throw new InvalidArgumentException( "Event is not a string" );
+ }
+
+ $this->addToCollection( $event, $listener );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param string $event
+ * @param Closure|callable $callback
+ *
+ * @throws InvalidArgumentException
+ * @throws RuntimeException
+ */
+ public function registerCallback( $event, $callback ) {
+
+ if ( !is_string( $event ) ) {
+ throw new InvalidArgumentException( "Event is not a string" );
+ }
+
+ if ( !is_callable( $callback ) ) {
+ throw new RuntimeException( "Invoked object is not a valid callback or Closure" );
+ }
+
+ $this->addToCollection( $event, new GenericCallbackEventListener( $callback ) );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function getCollection() {
+ return $this->collection;
+ }
+
+ private function addToCollection( $event, EventListener $listener ) {
+
+ $event = strtolower( $event );
+
+ if ( !isset( $this->collection[$event] ) ) {
+ $this->collection[$event] = array();
+ }
+
+ $this->collection[$event][] = $listener;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/event-dispatcher/src/Listener/NullEventListener.php b/www/wiki/vendor/onoi/event-dispatcher/src/Listener/NullEventListener.php
new file mode 100644
index 00000000..04beac6f
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/src/Listener/NullEventListener.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Onoi\EventDispatcher\Listener;
+
+use Onoi\EventDispatcher\EventListener;
+use Onoi\EventDispatcher\DispatchContext;
+
+/**
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class NullEventListener implements EventListener {
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function execute( DispatchContext $dispatchContext = null ) {}
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function isPropagationStopped() {
+ return false;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/event-dispatcher/src/Subscriber.php b/www/wiki/vendor/onoi/event-dispatcher/src/Subscriber.php
new file mode 100644
index 00000000..fac18cc8
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/src/Subscriber.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Onoi\EventDispatcher;
+
+/**
+ * Register listeners
+ *
+ * @license GNU GPL v2+
+ * @since 1.1
+ *
+ * @author mwjames
+ */
+interface Subscriber {
+
+ /**
+ * Registers a collection of listeners
+ *
+ * @since 1.0
+ *
+ * @param EventListenerCollection $listenerCollection
+ */
+ public function addListenerCollection( EventListenerCollection $listenerCollection );
+
+ /**
+ * Registers a listener to a specific event identifier
+ *
+ * @since 1.0
+ *
+ * @param string $event
+ * @param EventListener|null $listener
+ */
+ public function addListener( $event, EventListener $listener );
+
+ /**
+ * Removes all or a specific listener that matches the event identifier
+ *
+ * @since 1.0
+ *
+ * @param string $event
+ * @param EventListener|null $listener
+ */
+ public function removeListener( $event, EventListener $listener = null );
+
+}
diff --git a/www/wiki/vendor/onoi/event-dispatcher/tests/bootstrap.php b/www/wiki/vendor/onoi/event-dispatcher/tests/bootstrap.php
new file mode 100644
index 00000000..b7131659
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/tests/bootstrap.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+
+error_reporting( E_ALL | E_STRICT );
+date_default_timezone_set( 'UTC' );
+
+if ( php_sapi_name() !== 'cli' ) {
+ die( 'Not an entry point' );
+}
+
+if ( !is_readable( $path = __DIR__ . '/../vendor/autoload.php' ) ) {
+ if ( !is_readable( $path = __DIR__ . '/../../../autoload.php' ) ) {
+ die( 'The test suite requires the Composer autoloader to be present' );
+ }
+}
+
+$autoLoader = require $path;
+$autoLoader->addPsr4( 'Onoi\\EventDispatcher\\Tests\\', __DIR__ . '/phpunit/Unit' );
+$autoLoader->addPsr4( 'Onoi\\EventDispatcher\\Tests\\Integration\\', __DIR__ . '/phpunit/Integration' );
diff --git a/www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Integration/EventRegistryDispatchTest.php b/www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Integration/EventRegistryDispatchTest.php
new file mode 100644
index 00000000..27c50b8a
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Integration/EventRegistryDispatchTest.php
@@ -0,0 +1,138 @@
+<?php
+
+namespace Onoi\EventDispatcher\Tests\Integration;
+
+use Onoi\EventDispatcher\EventDispatcherFactory;
+use Onoi\EventDispatcher\EventDispatcher;
+use Onoi\EventDispatcher\DispatchContext;
+use Onoi\EventDispatcher\EventListener;
+use Onoi\EventDispatcher\EventListenerCollection;
+
+/**
+ * @group onoi-event-dispatcher
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class EventRegistryDispatchTest extends \PHPUnit_Framework_TestCase {
+
+ public function testDispatchSomeEventsFromCollectionOfListenersWithoutPropagationStop() {
+
+ $mockTester = $this->getMockBuilder( '\stdClass' )
+ ->setMethods( array( 'doSomething', 'doSomethingElse' ) )
+ ->getMock();
+
+ $mockTester->expects( $this->once() )
+ ->method( 'doSomething' );
+
+ $mockTester->expects( $this->once() )
+ ->method( 'doSomethingElse' );
+
+ $eventDispatcherFactory = new EventDispatcherFactory();
+
+ $listenerCollectionRegistery = new ListenerCollectionRegistery(
+ $eventDispatcherFactory->newGenericEventListenerCollection()
+ );
+
+ $eventDispatcher = $eventDispatcherFactory->newGenericEventDispatcher();
+ $eventDispatcher->addListenerCollection( $listenerCollectionRegistery );
+
+ $dispatchContext = new DispatchContext();
+ $dispatchContext->set( 'mock', $mockTester );
+
+ $eventDispatcher->dispatch( 'do.something', $dispatchContext );
+ }
+
+ public function testDispatchSomeEventsFromCollectionOfListenersWithPropagationStop() {
+
+ $mockTester = $this->getMockBuilder( '\stdClass' )
+ ->setMethods( array( 'doSomething', 'doSomethingElse' ) )
+ ->getMock();
+
+ $mockTester->expects( $this->once() )
+ ->method( 'doSomething' );
+
+ $mockTester->expects( $this->never() ) // PropagationStop
+ ->method( 'doSomethingElse' );
+
+ $eventDispatcherFactory = new EventDispatcherFactory();
+
+ $listenerCollectionRegistery = new ListenerCollectionRegistery(
+ $eventDispatcherFactory->newGenericEventListenerCollection()
+ );
+
+ $eventDispatcher = $eventDispatcherFactory->newGenericEventDispatcher();
+ $eventDispatcher->addListenerCollection( $listenerCollectionRegistery );
+
+ $dispatchContext = new DispatchContext();
+ $dispatchContext->set( 'mock', $mockTester );
+ $dispatchContext->set( 'propagationstop', true );
+
+ $eventDispatcher->dispatch( 'do.something', $dispatchContext );
+ }
+
+ public function testDispatchSomeEventsThroughAdHocListener() {
+
+ $mockTester = $this->getMockBuilder( '\stdClass' )
+ ->setMethods( array( 'doSomething' ) )
+ ->getMock();
+
+ $mockTester->expects( $this->once() )
+ ->method( 'doSomething' );
+
+ $eventDispatcherFactory = new EventDispatcherFactory();
+ $eventDispatcher = $eventDispatcherFactory->newGenericEventDispatcher();
+
+ $eventDispatcher->addListener( 'notify.bar', new BarListener() );
+
+ $dispatchContext = new DispatchContext();
+ $dispatchContext->set( 'mock', $mockTester );
+
+ $eventDispatcher->dispatch( 'notify.bar', $dispatchContext );
+ $eventDispatcher->dispatch( 'try.notify.empty.listener' );
+ }
+
+}
+
+/**
+ * Example required for the test
+ */
+class ListenerCollectionRegistery implements EventListenerCollection {
+
+ private $eventListenerCollection;
+
+ public function __construct( EventListenerCollection $eventListenerCollection ) {
+ $this->eventListenerCollection = $eventListenerCollection;
+ }
+
+ public function getCollection() {
+ return $this->addToListenerCollection()->getCollection();
+ }
+
+ private function addToListenerCollection() {
+
+ $this->eventListenerCollection->registerCallback( 'do.something', function( DispatchContext $dispatchContext ) {
+ $dispatchContext->get( 'mock' )->doSomething();
+ } );
+
+ $this->eventListenerCollection->registerCallback( 'do.something', function( DispatchContext $dispatchContext ) {
+ $dispatchContext->get( 'mock' )->doSomethingElse();
+ } );
+
+ return $this->eventListenerCollection;
+ }
+
+}
+
+class BarListener implements EventListener {
+
+ public function execute( DispatchContext $dispatchContext = null ) {
+ $dispatchContext->get( 'mock' )->doSomething();
+ }
+
+ public function isPropagationStopped() {
+ return false;
+ }
+}
diff --git a/www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/DispatchContextTest.php b/www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/DispatchContextTest.php
new file mode 100644
index 00000000..27811135
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/DispatchContextTest.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Onoi\EventDispatcher\Tests;
+
+use Onoi\EventDispatcher\DispatchContext;
+
+/**
+ * @covers \Onoi\EventDispatcher\DispatchContext
+ *
+ * @group onoi-event-dispatcher
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class DispatchContextTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\EventDispatcher\DispatchContext',
+ new DispatchContext()
+ );
+ }
+
+ public function testRoundtrip() {
+
+ $instance = new DispatchContext();
+
+ $this->assertFalse(
+ $instance->has( 'FOO' )
+ );
+
+ $instance->set( 'foo', 'bar' );
+
+ $this->assertTrue(
+ $instance->has( 'FOO' )
+ );
+
+ $this->assertEquals(
+ 'bar',
+ $instance->get( 'FOO' )
+ );
+
+ $instance->set( 'foo', new \stdClass );
+
+ $this->assertEquals(
+ new \stdClass,
+ $instance->get( 'FOO' )
+ );
+ }
+
+ public function testNewFromArray() {
+
+ $instance = DispatchContext::newFromArray( [ 'FOO' => 123 ] );
+
+ $this->assertTrue(
+ $instance->has( 'FOO' )
+ );
+
+ $this->assertEquals(
+ 123,
+ $instance->get( 'foo' )
+ );
+ }
+
+ public function testChangePropagationState() {
+
+ $instance = new DispatchContext();
+
+ $this->assertFalse(
+ $instance->isPropagationStopped()
+ );
+
+ $instance->set( 'proPagationSTOP', true );
+
+ $this->assertTrue(
+ $instance->isPropagationStopped()
+ );
+ }
+
+ public function testUnknownKeyThrowsException() {
+
+ $instance = new DispatchContext();
+
+ $this->setExpectedException( 'InvalidArgumentException' );
+ $instance->get( 'FOO' );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/Dispatcher/GenericEventDispatcherTest.php b/www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/Dispatcher/GenericEventDispatcherTest.php
new file mode 100644
index 00000000..26ec4d8c
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/Dispatcher/GenericEventDispatcherTest.php
@@ -0,0 +1,262 @@
+<?php
+
+namespace Onoi\EventDispatcher\Tests;
+
+use Onoi\EventDispatcher\Dispatcher\GenericEventDispatcher;
+
+/**
+ * @covers \Onoi\EventDispatcher\Dispatcher\GenericEventDispatcher
+ *
+ * @group onoi-event-dispatcher
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class GenericEventDispatcherTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\EventDispatcher\EventDispatcher',
+ new GenericEventDispatcher()
+ );
+
+ $this->assertInstanceOf(
+ '\Onoi\EventDispatcher\Dispatcher\GenericEventDispatcher',
+ new GenericEventDispatcher()
+ );
+ }
+
+ public function testTryAddingListenerUsingInvalidEventIdentifierThrowsException() {
+
+ $instance = new GenericEventDispatcher();
+
+ $eventListener = $this->getMockBuilder( '\Onoi\EventDispatcher\EventListener' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->setExpectedException( 'InvalidArgumentException' );
+ $instance->addListener( new \stdClass, $eventListener );
+ }
+
+ public function testAddListener() {
+
+ $instance = new GenericEventDispatcher();
+
+ $eventListener = $this->getMockBuilder( '\Onoi\EventDispatcher\EventListener' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $instance->addListener( 'foo', $eventListener );
+
+ $this->assertTrue(
+ $instance->hasEvent( 'FOO' )
+ );
+ }
+
+ public function testRemoveSpecificListener() {
+
+ $instance = new GenericEventDispatcher();
+
+ $eventListener = $this->getMockBuilder( '\Onoi\EventDispatcher\EventListener' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $genericCallbackEventListener = $this->getMockBuilder( '\Onoi\EventDispatcher\Listener\GenericCallbackEventListener' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $instance->addListener( 'foo', $eventListener );
+ $instance->addListener( 'foo', $genericCallbackEventListener );
+
+ $this->assertTrue(
+ $instance->hasEvent( 'FOO' )
+ );
+
+ $instance->removeListener( 'foo', $eventListener );
+
+ $this->assertTrue(
+ $instance->hasEvent( 'FOO' )
+ );
+
+ $instance->removeListener( 'foo', $genericCallbackEventListener );
+
+ $this->assertFalse(
+ $instance->hasEvent( 'FOO' )
+ );
+ }
+
+ public function testRemoveAllListenerForSpecificEvent() {
+
+ $instance = new GenericEventDispatcher();
+
+ $eventListener = $this->getMockBuilder( '\Onoi\EventDispatcher\EventListener' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $genericCallbackEventListener = $this->getMockBuilder( '\Onoi\EventDispatcher\Listener\GenericCallbackEventListener' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $instance->addListener( 'foo', $eventListener );
+ $instance->addListener( 'foo', $genericCallbackEventListener );
+
+ $this->assertTrue(
+ $instance->hasEvent( 'FOO' )
+ );
+
+ $instance->removeListener( 'foo' );
+
+ $this->assertFalse(
+ $instance->hasEvent( 'FOO' )
+ );
+ }
+
+ public function testTryRemovalOfListenersForUnknownEvent() {
+
+ $instance = new GenericEventDispatcher();
+
+ $eventListener = $this->getMockBuilder( '\Onoi\EventDispatcher\EventListener' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $instance->addListener( 'foo', $eventListener );
+
+ $instance->removeListener( 'bar', $eventListener );
+
+ $this->assertTrue(
+ $instance->hasEvent( 'FOO' )
+ );
+ }
+
+ public function testDispatchEvent() {
+
+ $instance = new GenericEventDispatcher();
+
+ $eventListener = $this->getMockBuilder( '\Onoi\EventDispatcher\EventListener' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $eventListener->expects( $this->once() )
+ ->method( 'execute' );
+
+ $eventListener->expects( $this->once() )
+ ->method( 'isPropagationStopped' );
+
+ $instance->addListener( 'foo', $eventListener );
+ $instance->dispatch( 'foo' );
+ }
+
+ public function testDispatchEventWithContextToOverrideListenerPropagationStopState() {
+
+ $instance = new GenericEventDispatcher();
+
+ $dispatchContext = $this->getMockBuilder( '\Onoi\EventDispatcher\DispatchContext' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $dispatchContext->expects( $this->once() )
+ ->method( 'isPropagationStopped' )
+ ->will( $this->returnValue( true ) );
+
+ $eventListener = $this->getMockBuilder( '\Onoi\EventDispatcher\EventListener' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $eventListener->expects( $this->once() )
+ ->method( 'execute' )
+ ->with( $this->identicalTo( $dispatchContext ) );
+
+ $eventListener->expects( $this->once() )
+ ->method( 'isPropagationStopped' )
+ ->will( $this->returnValue( false ) );
+
+ $instance->addListener( 'foo', $eventListener );
+
+ $instance->dispatch( 'foo', $dispatchContext );
+ }
+
+ public function testDispatchFromListenerCollection() {
+
+ $instance = new GenericEventDispatcher();
+
+ $dispatchContext = $this->getMockBuilder( '\Onoi\EventDispatcher\DispatchContext' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $eventListener = $this->getMockBuilder( '\Onoi\EventDispatcher\EventListener' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $eventListener->expects( $this->once() )
+ ->method( 'execute' )
+ ->with( $this->identicalTo( $dispatchContext ) );
+
+ $eventListenerCollection = $this->getMockBuilder( '\Onoi\EventDispatcher\EventListenerCollection' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $eventListenerCollection->expects( $this->once() )
+ ->method( 'getCollection' )
+ ->will( $this->returnValue(
+ array(
+ 'foo' => array( $eventListener ),
+ 'bar' => array( $eventListener ) ) ) );
+
+ $instance->addListenerCollection( $eventListenerCollection );
+
+ $this->assertTrue(
+ $instance->hasEvent( 'FOO' )
+ );
+
+ $this->assertTrue(
+ $instance->hasEvent( 'bAr' )
+ );
+
+ $instance->dispatch( 'foo', $dispatchContext );
+ }
+
+ public function testDispatchListenerWithArrayContext() {
+
+ $instance = new GenericEventDispatcher();
+
+ $eventListener = $this->getMockBuilder( '\Onoi\EventDispatcher\EventListener' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $eventListener->expects( $this->once() )
+ ->method( 'execute' )
+ ->with( $this->callback( function( $dispatchContext ){ return $dispatchContext->get( 'Bar' ) == 123; } ) );
+
+ $instance->addListener( 'FOO', $eventListener );
+ $instance->dispatch( 'foo', [ 'Bar' => 123 ] );
+ }
+
+ public function testMissingListenerForEventThrowsException() {
+
+ $instance = new GenericEventDispatcher();
+ $instance->throwOnMissingEvent( true );
+
+ $this->setExpectedException( '\Onoi\EventDispatcher\Exception\EventNotDispatchableException' );
+ $instance->dispatch( 'foo' );
+ }
+
+ public function testRegisterNonTraversableCollectionThrowsException() {
+
+ $instance = new GenericEventDispatcher();
+
+ $eventListenerCollection = $this->getMockBuilder( '\Onoi\EventDispatcher\EventListenerCollection' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $eventListenerCollection->expects( $this->once() )
+ ->method( 'getCollection' )
+ ->will( $this->returnValue( false ) );
+
+ $this->setExpectedException( 'RuntimeException' );
+ $instance->addListenerCollection( $eventListenerCollection );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/EventDispatcherFactoryTest.php b/www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/EventDispatcherFactoryTest.php
new file mode 100644
index 00000000..808aa182
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/EventDispatcherFactoryTest.php
@@ -0,0 +1,86 @@
+<?php
+
+namespace Onoi\EventDispatcher\Tests;
+
+use Onoi\EventDispatcher\EventDispatcherFactory;
+
+/**
+ * @covers \Onoi\EventDispatcher\EventDispatcherFactory
+ *
+ * @group onoi-event-dispatcher
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class EventDispatcherFactoryTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $instance = new EventDispatcherFactory();
+
+ $this->assertInstanceOf(
+ '\Onoi\EventDispatcher\EventDispatcherFactory',
+ $instance
+ );
+
+ $this->assertInstanceOf(
+ '\Onoi\EventDispatcher\EventDispatcherFactory',
+ EventDispatcherFactory::getInstance()
+ );
+
+ EventDispatcherFactory::clear();
+ }
+
+ public function testCanConstructDispatchContext() {
+
+ $instance = new EventDispatcherFactory();
+
+ $this->assertInstanceOf(
+ '\Onoi\EventDispatcher\DispatchContext',
+ $instance->newDispatchContext()
+ );
+ }
+
+ public function testCanConstructGenericEventDispatcher() {
+
+ $instance = new EventDispatcherFactory();
+
+ $this->assertInstanceOf(
+ '\Onoi\EventDispatcher\Dispatcher\GenericEventDispatcher',
+ $instance->newGenericEventDispatcher()
+ );
+ }
+
+ public function testCanConstructNullEventListener() {
+
+ $instance = new EventDispatcherFactory();
+
+ $this->assertInstanceOf(
+ '\Onoi\EventDispatcher\Listener\NullEventListener',
+ $instance->newNullEventListener()
+ );
+ }
+
+ public function testCanConstructGenericCallbackEventListener() {
+
+ $instance = new EventDispatcherFactory();
+
+ $this->assertInstanceOf(
+ '\Onoi\EventDispatcher\Listener\GenericCallbackEventListener',
+ $instance->newGenericCallbackEventListener()
+ );
+ }
+
+ public function testCanConstructGenericEventListenerCollection() {
+
+ $instance = new EventDispatcherFactory();
+
+ $this->assertInstanceOf(
+ '\Onoi\EventDispatcher\Listener\GenericEventListenerCollection',
+ $instance->newGenericEventListenerCollection()
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/Exception/EventNotDispatchableExceptionTest.php b/www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/Exception/EventNotDispatchableExceptionTest.php
new file mode 100644
index 00000000..e2f49fb0
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/Exception/EventNotDispatchableExceptionTest.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Onoi\EventDispatcher\Tests\Exception;
+
+use Onoi\EventDispatcher\Exception\EventNotDispatchableException;
+
+/**
+ * @covers \Onoi\EventDispatcher\Exception\EventNotDispatchableException
+ * @group onoi-event-dispatcher
+ *
+ * @license GNU GPL v2+
+ * @since 1.1
+ *
+ * @author mwjames
+ */
+class EventNotDispatchableExceptionTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ EventNotDispatchableException::class,
+ new EventNotDispatchableException( 'foo' )
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/Listener/GenericCallbackEventListenerTest.php b/www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/Listener/GenericCallbackEventListenerTest.php
new file mode 100644
index 00000000..1c1d1730
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/Listener/GenericCallbackEventListenerTest.php
@@ -0,0 +1,124 @@
+<?php
+
+namespace Onoi\EventDispatcher\Tests\Listener;
+
+use Onoi\EventDispatcher\Listener\GenericCallbackEventListener;
+
+/**
+ * @covers \Onoi\EventDispatcher\Listener\GenericCallbackEventListener
+ *
+ * @group onoi-event-dispatcher
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class GenericCallbackEventListenerTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\EventDispatcher\EventListener',
+ new GenericCallbackEventListener()
+ );
+
+ $this->assertInstanceOf(
+ '\Onoi\EventDispatcher\Listener\GenericCallbackEventListener',
+ new GenericCallbackEventListener()
+ );
+ }
+
+ public function testTryRegisterNonCallbackThrowsException() {
+
+ $instance = new GenericCallbackEventListener();
+
+ $this->setExpectedException( 'RuntimeException' );
+ $instance->registerCallback( 'foo' );
+ }
+
+ public function testRegisterClosure() {
+
+ $instance = new GenericCallbackEventListener();
+
+ $testClass = $this->getMockBuilder( '\stdClass' )
+ ->disableOriginalConstructor()
+ ->setMethods( array( 'runTest' ) )
+ ->getMock();
+
+ $testClass->expects( $this->once() )
+ ->method( 'runTest' );
+
+ $callback = function() use( $testClass ) {
+ $testClass->runTest();
+ };
+
+ $instance->registerCallback( $callback );
+ $instance->execute();
+ }
+
+ public function testRegisterClosureViaConstrutor() {
+
+ $testClass = $this->getMockBuilder( '\stdClass' )
+ ->disableOriginalConstructor()
+ ->setMethods( array( 'runTest' ) )
+ ->getMock();
+
+ $testClass->expects( $this->once() )
+ ->method( 'runTest' );
+
+ $callback = function() use( $testClass ) {
+ $testClass->runTest();
+ };
+
+ $instance = new GenericCallbackEventListener( $callback );
+ $instance->execute();
+ }
+
+ public function testRegisterExecutableCallbackViaConstrutor() {
+
+ $mockTester = $this->getMockBuilder( '\stdClass' )
+ ->disableOriginalConstructor()
+ ->setMethods( array( 'runTest' ) )
+ ->getMock();
+
+ $mockTester->expects( $this->once() )
+ ->method( 'runTest' );
+
+ $instance = new GenericCallbackEventListener( array(
+ new FooMockTester( $mockTester ), 'invokedCallback' )
+ );
+
+ $instance->execute();
+ }
+
+ public function testPropagationState() {
+
+ $instance = new GenericCallbackEventListener();
+
+ $this->assertFalse(
+ $instance->isPropagationStopped()
+ );
+
+ $instance->setPropagationStopState( true );
+
+ $this->assertTrue(
+ $instance->isPropagationStopped()
+ );
+ }
+
+}
+
+class FooMockTester {
+
+ private $mockTester;
+
+ public function __construct( $mockTester ) {
+ $this->mockTester = $mockTester;
+ }
+
+ public function invokedCallback() {
+ $this->mockTester->runTest();
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/Listener/GenericEventListenerCollectionTest.php b/www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/Listener/GenericEventListenerCollectionTest.php
new file mode 100644
index 00000000..9e76428a
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/Listener/GenericEventListenerCollectionTest.php
@@ -0,0 +1,103 @@
+<?php
+
+namespace Onoi\EventDispatcher\Tests\Listener;
+
+use Onoi\EventDispatcher\Listener\GenericEventListenerCollection;
+use Onoi\EventDispatcher\Listener\GenericCallbackEventListener;
+
+/**
+ * @covers \Onoi\EventDispatcher\Listener\GenericEventListenerCollection
+ *
+ * @group onoi-event-dispatcher
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class GenericEventListenerCollectionTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\EventDispatcher\EventListenerCollection',
+ new GenericEventListenerCollection()
+ );
+
+ $this->assertInstanceOf(
+ '\Onoi\EventDispatcher\Listener\GenericEventListenerCollection',
+ new GenericEventListenerCollection()
+ );
+ }
+
+ public function testRegisterListener() {
+
+ $eventListener = $this->getMockBuilder( '\Onoi\EventDispatcher\EventListener' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $instance = new GenericEventListenerCollection();
+ $instance->registerListener( 'FOO', $eventListener );
+
+ $expected = array(
+ 'foo' => array( $eventListener )
+ );
+
+ $this->assertEquals(
+ $expected,
+ $instance->getCollection()
+ );
+ }
+
+ public function testTryRegisterListenerUsingInvalidEventIdentifierThrowsException() {
+
+ $eventListener = $this->getMockBuilder( '\Onoi\EventDispatcher\EventListener' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $instance = new GenericEventListenerCollection();
+
+ $this->setExpectedException( 'InvalidArgumentException' );
+ $instance->registerListener( new \stdClass, $eventListener );
+ }
+
+ public function testRegisterCallback() {
+
+ $callback = function() { return 'doSomething'; };
+
+ $instance = new GenericEventListenerCollection();
+ $instance->registerCallback( 'fOo', $callback );
+
+ $expected = array(
+ 'foo' => array( new GenericCallbackEventListener( $callback ) )
+ );
+
+ $this->assertEquals(
+ $expected,
+ $instance->getCollection()
+ );
+ }
+
+ public function testTryRegisterCallbackUsingInvalidEventIdentifierThrowsException() {
+
+ $callback = function() { return 'doSomething'; };
+
+ $instance = new GenericEventListenerCollection();
+
+ $this->setExpectedException( 'InvalidArgumentException' );
+ $instance->registerCallback( new \stdClass, $callback );
+ }
+
+ public function testTryRegisterCallbackUsingInvalidCallbackThrowsException() {
+
+ $eventListener = $this->getMockBuilder( '\Onoi\EventDispatcher\EventListener' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $instance = new GenericEventListenerCollection();
+
+ $this->setExpectedException( 'RuntimeException' );
+ $instance->registerCallback( 'foo', new \stdClass );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/Listener/NullEventListenerTest.php b/www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/Listener/NullEventListenerTest.php
new file mode 100644
index 00000000..e27f4b99
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/tests/phpunit/Unit/Listener/NullEventListenerTest.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Onoi\EventDispatcher\Tests\Listener;
+
+use Onoi\EventDispatcher\Listener\NullEventListener;
+
+/**
+ * @covers \Onoi\EventDispatcher\Listener\NullEventListener
+ *
+ * @group onoi-event-dispatcher
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class NullEventListenerTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\EventDispatcher\EventListener',
+ new NullEventListener()
+ );
+
+ $this->assertInstanceOf(
+ '\Onoi\EventDispatcher\Listener\NullEventListener',
+ new NullEventListener()
+ );
+ }
+
+ public function testExecute() {
+
+ $instance = new NullEventListener();
+
+ $this->assertNull(
+ $instance->execute()
+ );
+ }
+
+ public function testIsPropagationStopped() {
+
+ $instance = new NullEventListener();
+
+ $this->assertFalse(
+ $instance->isPropagationStopped()
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/event-dispatcher/tests/travis/run-tests.sh b/www/wiki/vendor/onoi/event-dispatcher/tests/travis/run-tests.sh
new file mode 100644
index 00000000..f63949a1
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/tests/travis/run-tests.sh
@@ -0,0 +1,15 @@
+#! /bin/bash
+set -ex
+
+BASE_PATH=$(pwd)
+
+if [ "$TYPE" == "coverage" ]
+then
+ composer dump-autoload
+ composer validate --no-interaction
+ composer phpunit -- --coverage-clover $BASE_PATH/build/coverage.clover
+else
+ composer dump-autoload
+ composer validate --no-interaction
+ composer phpunit
+fi
diff --git a/www/wiki/vendor/onoi/event-dispatcher/tests/travis/upload-coverage-report.sh b/www/wiki/vendor/onoi/event-dispatcher/tests/travis/upload-coverage-report.sh
new file mode 100644
index 00000000..aff95cf7
--- /dev/null
+++ b/www/wiki/vendor/onoi/event-dispatcher/tests/travis/upload-coverage-report.sh
@@ -0,0 +1,10 @@
+#! /bin/bash
+set -ex
+
+BASE_PATH=$(pwd)
+
+if [ "$TYPE" == "coverage" ]
+then
+ wget https://scrutinizer-ci.com/ocular.phar
+ php ocular.phar code-coverage:upload --format=php-clover $BASE_PATH/build/coverage.clover
+fi \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/http-request/.gitignore b/www/wiki/vendor/onoi/http-request/.gitignore
new file mode 100644
index 00000000..09ec1c53
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/.gitignore
@@ -0,0 +1,10 @@
+*~
+*.kate-swp
+
+!.*
+.idea/
+
+composer.phar
+composer.lock
+
+vendor/ \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/http-request/.scrutinizer.yml b/www/wiki/vendor/onoi/http-request/.scrutinizer.yml
new file mode 100644
index 00000000..59edd542
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/.scrutinizer.yml
@@ -0,0 +1,27 @@
+filter:
+ excluded_paths:
+ - 'vendor/*'
+
+tools:
+ php_mess_detector:
+ config:
+ controversial_rules: { superglobals: false }
+ php_sim: true
+ php_cpd: true
+ php_pdepend: true
+ php_code_coverage: false
+ php_code_sniffer: true
+ php_cs_fixer: true
+ php_loc: true
+ php_analyzer: true
+ sensiolabs_security_checker: true
+ external_code_coverage:
+ timeout: '900'
+
+checks:
+ php:
+ psr2_class_declaration: false
+ psr2_switch_declaration: false
+ sql_injection_vulnerabilities: true
+ security_vulnerabilities: true
+ no_eval: true \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/http-request/.travis.yml b/www/wiki/vendor/onoi/http-request/.travis.yml
new file mode 100644
index 00000000..d31f0798
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/.travis.yml
@@ -0,0 +1,18 @@
+language: php
+
+matrix:
+ include:
+ - env: TYPE=coverage
+ php: 5.6
+ - env: TYPE=UNIT;
+ php: 5.4
+ - env: TYPE=UNIT;
+ php: 5.3
+ - env: TYPE=UNIT;
+ php: hhvm
+
+script:
+ - bash ./tests/travis/run-tests.sh
+
+after_success:
+ - bash ./tests/travis/upload-coverage-report.sh
diff --git a/www/wiki/vendor/onoi/http-request/LICENSE b/www/wiki/vendor/onoi/http-request/LICENSE
new file mode 100644
index 00000000..d6a93266
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/LICENSE
@@ -0,0 +1,340 @@
+GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ {description}
+ Copyright (C) {year} {fullname}
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ {signature of Ty Coon}, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
+
diff --git a/www/wiki/vendor/onoi/http-request/README.md b/www/wiki/vendor/onoi/http-request/README.md
new file mode 100644
index 00000000..17c3a095
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/README.md
@@ -0,0 +1,148 @@
+# Http request
+
+[![Build Status](https://secure.travis-ci.org/onoi/http-request.svg?branch=master)](http://travis-ci.org/onoi/http-request)
+[![Code Coverage](https://scrutinizer-ci.com/g/onoi/http-request/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/onoi/http-request/?branch=master)
+[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/onoi/http-request/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/onoi/http-request/?branch=master)
+[![Latest Stable Version](https://poser.pugx.org/onoi/http-request/version.png)](https://packagist.org/packages/onoi/http-request)
+[![Packagist download count](https://poser.pugx.org/onoi/http-request/d/total.png)](https://packagist.org/packages/onoi/http-request)
+[![Dependency Status](https://www.versioneye.com/php/onoi:http-request/badge.png)](https://www.versioneye.com/php/onoi:http-request)
+
+A minimalistic http/curl request interface that was part of the [Semantic MediaWiki][smw] code base and
+is now being deployed as independent library.
+
+This library provides:
+
+- `HttpRequest` interface
+- `CurlRequest` as cURL implementation of the `HttpRequest`
+- `CachedCurlRequest` to support low-level caching on repeated `CurlRequest` requests
+- `MultiCurlRequest` to make use of the cURL multi stack feature
+- `SocketRequest` to create asynchronous socket connections
+
+## Requirements
+
+- PHP 5.3 or later
+
+## Installation
+
+The recommended installation method for this library is by adding the
+dependency to your [composer.json][composer].
+
+```json
+{
+ "require": {
+ "onoi/http-request": "~1.3"
+ }
+}
+```
+
+## Usage
+
+```php
+use Onoi\HttpRequest\CurlRequest;
+use Onoi\HttpRequest\Exception\BadHttpResponseException;
+use Onoi\HttpRequest\Exception\HttpConnectionException;
+
+class Foo {
+
+ private $curlRequest = null;
+
+ public function __constructor( CurlRequest $curlRequest ) {
+ $this->curlRequest = $curlRequest;
+ }
+
+ public function doMakeHttpRequestTo( $url ) {
+
+ $this->curlRequest->setOption( CURLOPT_URL, $url );
+
+ if ( !$this->curlRequest->ping() ) {
+ throw new HttpConnectionException( "Couldn't connect" );
+ }
+
+ $this->curlRequest->setOption( CURLOPT_RETURNTRANSFER, true );
+
+ $this->curlRequest->setOption( CURLOPT_HTTPHEADER, array(
+ 'Accept: application/x-turtle'
+ ) );
+
+ $response = $this->curlRequest->execute();
+
+ if ( $this->curlRequest->getLastErrorCode() == 0 ) {
+ return $response;
+ }
+
+ throw new BadHttpResponseException( $this->curlRequest );
+ }
+}
+```
+```php
+$httpRequestFactory = new HttpRequestFactory();
+
+$instance = new Foo( $httpRequestFactory->newCurlRequest() );
+$response = $instance->doMakeHttpRequestTo( 'http://example.org' );
+
+OR
+
+$cacheFactory = new CacheFactory();
+
+$compositeCache = $cacheFactory->newCompositeCache( array(
+ $cacheFactory->newFixedInMemoryLruCache( 500 ),
+ $cacheFactory->newDoctrineCache( new \Doctrine\Common\Cache\RedisCache() )
+) );
+
+$httpRequestFactory = new HttpRequestFactory( $compositeCache );
+$cachedCurlRequest = $httpRequestFactory->newCachedCurlRequest();
+
+// Responses for a request with the same signature (== same endpoint and same query
+// content) will be cached if the request was successful for a specified 1 h (3600 sec)
+$cachedCurlRequest->setOption( ONOI_HTTP_REQUEST_RESPONSECACHE_TTL, 60 * 60 );
+
+$instance = new Foo( $cachedCurlRequest );
+$response = $instance->doMakeHttpRequestTo( 'http://example.org' );
+```
+
+## Contribution and support
+
+If you want to contribute work to the project please subscribe to the
+developers mailing list and have a look at the [contribution guidelinee](/CONTRIBUTING.md). A list of people who have made contributions in the past can be found [here][contributors].
+
+* [File an issue](https://github.com/onoi/http-request/issues)
+* [Submit a pull request](https://github.com/onoi/http-request/pulls)
+
+### Tests
+
+The library provides unit tests that covers the core-functionality normally run by the [continues integration platform][travis]. Tests can also be executed manually using the PHPUnit configuration file found in the root directory.
+
+## Release notes
+
+* 1.3.1 (2016-01-14)
+ - Extended `SocketRequest` to match a possible TLS port
+
+* 1.3.0 (2015-11-23)
+ - Deprecated `CachedCurlRequest::setCachePrefix` and `CachedCurlRequest::setExpiryInSeconds` in favor of setting it via the option
+ `ONOI_HTTP_REQUEST_RESPONSECACHE_PREFIX` and `ONOI_HTTP_REQUEST_RESPONSECACHE_TTL` (any change in the expiry will auto-invalidate existing items in cache)
+ - Deprecated `CachedCurlRequest::isCached` in favor of `CachedCurlRequest::isFromCache`
+
+* 1.2.0 (2015-11-09)
+ - Added "wasAccepted" to the `SocketRequest` response output
+ - Added option `ONOI_HTTP_REQUEST_FOLLOWLOCATION` to support resetting the URL location in case of a `301` HTTP response during a `SocketRequest::ping` request
+
+* 1.1.0 (2015-09-12)
+ - Renamed `AsyncCurlRequest` to `MultiCurlRequest`
+ - Deprecated `MultiCurlRequest::setCallback` and to be replaced by `MultiCurlRequest::setOption( ONOI_HTTP_REQUEST_ON_COMPLETED_CALLBACK, ... )`
+ - Added `SocketRequest` to create asynchronous socket connections
+
+* 1.0.0 (2015-07-22, initial release)
+ - Added the `HttpRequest` interface
+ - Added the `CurlRequest` implementation
+ - Added the `CachedCurlRequest` to extend the `CurlRequest` implementation
+ - Added the `AsyncCurlRequest` implementation
+
+## License
+
+[GNU General Public License 2.0 or later][license].
+
+[composer]: https://getcomposer.org/
+[contributors]: https://github.com/onoi/http-request/graphs/contributors
+[license]: https://www.gnu.org/copyleft/gpl.html
+[travis]: https://travis-ci.org/onoi/http-request
+[smw]: https://github.com/SemanticMediaWiki/SemanticMediaWiki/
diff --git a/www/wiki/vendor/onoi/http-request/composer.json b/www/wiki/vendor/onoi/http-request/composer.json
new file mode 100644
index 00000000..4797d2f7
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/composer.json
@@ -0,0 +1,40 @@
+{
+ "name": "onoi/http-request",
+ "type": "library",
+ "description": "A minimalistic http/curl request interface library",
+ "keywords": [
+ "http request",
+ "curl"
+ ],
+ "homepage": "https://github.com/onoi/http-request",
+ "license": "GPL-2.0+",
+ "authors": [
+ {
+ "name": "mwjames",
+ "homepage": "https://github.com/mwjames"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.2",
+ "onoi/cache": "~1.2"
+ },
+ "suggest": {
+ "lib-curl": "Allows making CURL requests"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Onoi\\HttpRequest\\": "src/"
+ }
+ },
+ "config": {
+ "process-timeout": 0
+ },
+ "scripts":{
+ "phpunit": "phpunit -c phpunit.xml.dist"
+ }
+}
diff --git a/www/wiki/vendor/onoi/http-request/phpunit.xml.dist b/www/wiki/vendor/onoi/http-request/phpunit.xml.dist
new file mode 100644
index 00000000..de5e1ce7
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/phpunit.xml.dist
@@ -0,0 +1,32 @@
+<phpunit backupGlobals="false"
+ backupStaticAttributes="false"
+ bootstrap="tests/bootstrap.php"
+ cacheTokens="false"
+ colors="true"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ stopOnError="false"
+ stopOnFailure="false"
+ stopOnIncomplete="false"
+ stopOnSkipped="false"
+ verbose="true">
+ <testsuites>
+ <testsuite name="onoi-http-request-unit">
+ <directory>tests/phpunit/Unit</directory>
+ </testsuite>
+ <testsuite name="onoi-http-request-integration">
+ <directory>tests/phpunit/Integration</directory>
+ </testsuite>
+ </testsuites>
+ <filter>
+ <whitelist addUncoveredFilesFromWhitelist="true">
+ <directory suffix=".php">src</directory>
+ </whitelist>
+ </filter>
+ <php>
+ <const name="WEB_SERVER_HOST" value="localhost" />
+ <const name="WEB_SERVER_PORT" value="13499" />
+ <const name="WEB_SERVER_DOCROOT" value="/http-public" />
+ </php>
+</phpunit>
diff --git a/www/wiki/vendor/onoi/http-request/src/CachedCurlRequest.php b/www/wiki/vendor/onoi/http-request/src/CachedCurlRequest.php
new file mode 100644
index 00000000..5fe4facc
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/src/CachedCurlRequest.php
@@ -0,0 +1,143 @@
+<?php
+
+namespace Onoi\HttpRequest;
+
+use Onoi\Cache\Cache;
+
+/**
+ * Simple cache layer from the client-side to avoid repeated requests to
+ * the same target.
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class CachedCurlRequest extends CurlRequest {
+
+ /**
+ * Fixed constant
+ */
+ const CACHE_PREFIX = 'onoi:http:';
+
+ /**
+ * @var Cache
+ */
+ private $cache;
+
+ /**
+ * @var boolean
+ */
+ private $isFromCache = false;
+
+ /**
+ * @since 1.0
+ *
+ * @param resource $handle
+ * @param Cache $cache
+ */
+ public function __construct( $handle, Cache $cache ) {
+ parent::__construct( $handle );
+
+ $this->cache = $cache;
+ $this->setOption( ONOI_HTTP_REQUEST_RESPONSECACHE_TTL, 60 ); // 60 sec by default
+ $this->setOption( ONOI_HTTP_REQUEST_RESPONSECACHE_PREFIX, '' );
+ }
+
+ /**
+ * @deprecated since 1.3, use option ONOI_HTTP_REQUEST_RESPONSECACHE_TTL instead
+ * @since 1.0
+ *
+ * @param integer $expiry
+ */
+ public function setExpiryInSeconds( $expiry ) {
+ $this->setOption( ONOI_HTTP_REQUEST_RESPONSECACHE_TTL, (int)$expiry );
+ }
+
+ /**
+ * @deprecated since 1.3, use option ONOI_HTTP_REQUEST_RESPONSECACHE_PREFIX instead
+ * @since 1.0
+ *
+ * @param string $cachePrefix
+ */
+ public function setCachePrefix( $cachePrefix ) {
+ $this->setOption( ONOI_HTTP_REQUEST_RESPONSECACHE_PREFIX, (string)$cachePrefix );
+ }
+
+ /**
+ * @deprecated since 1.3, use CachedCurlRequest::isFromCache instead
+ * @since 1.0
+ *
+ * @return boolean
+ */
+ public function isCached() {
+ return $this->isFromCache();
+ }
+
+ /**
+ * @since 1.3
+ *
+ * @return boolean
+ */
+ public function isFromCache() {
+ return $this->isFromCache;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @return mixed
+ */
+ public function execute() {
+
+ list( $key, $expiry ) = $this->getKeysFromOptions();
+ $this->isFromCache = false;
+
+ if ( $this->cache->contains( $key ) ) {
+ $this->isFromCache = true;
+ return $this->cache->fetch( $key );
+ }
+
+ $response = parent::execute();
+
+ // Do not cache any failed response
+ if ( $this->getLastErrorCode() !== 0 ) {
+ return $response;
+ }
+
+ $this->cache->save(
+ $key,
+ $response,
+ $expiry
+ );
+
+ return $response;
+ }
+
+ private function getKeysFromOptions() {
+
+ // curl_init can provide the URL which will set the value to the
+ // CURLOPT_URL option, ensure to have the URL as part of the options
+ // independent from where/when it was set
+ $this->setOption(
+ CURLOPT_URL,
+ $this->getLastTransferInfo( CURLINFO_EFFECTIVE_URL )
+ );
+
+ // Avoid an unsorted order that would create unstable keys
+ ksort( $this->options );
+
+ $expiry = $this->getOption( ONOI_HTTP_REQUEST_RESPONSECACHE_TTL );
+ $prefix = $this->getOption( ONOI_HTTP_REQUEST_RESPONSECACHE_PREFIX );
+
+ $key = $prefix . self::CACHE_PREFIX . md5(
+ json_encode( $this->options )
+ );
+
+ // Reuse the handle but clear the options
+ $this->options = array();
+
+ return array( $key, $expiry );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/http-request/src/CurlRequest.php b/www/wiki/vendor/onoi/http-request/src/CurlRequest.php
new file mode 100644
index 00000000..afda0cd1
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/src/CurlRequest.php
@@ -0,0 +1,155 @@
+<?php
+
+namespace Onoi\HttpRequest;
+
+use InvalidArgumentException;
+
+/**
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class CurlRequest implements HttpRequest {
+
+ /**
+ * @var resource
+ */
+ private $handle;
+
+ /**
+ * @var array
+ */
+ protected $options = array();
+
+ /**
+ * @since 1.0
+ *
+ * @param resource $handle
+ */
+ public function __construct( $handle ) {
+
+ if ( get_resource_type( $handle ) !== 'curl' ) {
+ throw new InvalidArgumentException( "Expected a cURL resource type" );
+ }
+
+ $this->handle = $handle;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @return boolean
+ */
+ public function ping() {
+
+ if ( curl_getinfo( $this->handle, CURLINFO_EFFECTIVE_URL ) === '' ) {
+ return false;
+ }
+
+ // Copy the handle to avoid diluting the resource
+ $handle = curl_copy_handle( $this->handle );
+
+ curl_setopt_array( $handle, array(
+ CURLOPT_HEADER => false,
+ CURLOPT_RETURNTRANSFER, true,
+ CURLOPT_CONNECTTIMEOUT => 5,
+ CURLOPT_FRESH_CONNECT => false,
+ CURLOPT_FAILONERROR => true
+ ) );
+
+ curl_exec( $handle );
+
+ return curl_errno( $handle ) == 0;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param string $name
+ * @param mixed $value
+ */
+ public function setOption( $name, $value ) {
+
+ $this->options[$name] = $value;
+
+ // Internal ONOI options are not further relayed
+ if ( strpos( $name, 'ONOI_HTTP_REQUEST' ) !== false ) {
+ return;
+ }
+
+ curl_setopt(
+ $this->handle,
+ $name,
+ $value
+ );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param string $name
+ *
+ * @return mixed|null
+ */
+ public function getOption( $name ) {
+ return isset( $this->options[$name] ) ? $this->options[$name] : null;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param string|null $name
+ *
+ * @return mixed
+ */
+ public function getLastTransferInfo( $name = null ) {
+ return curl_getinfo( $this->handle, $name );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @return string
+ */
+ public function getLastError() {
+ return curl_error( $this->handle );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @return integer
+ */
+ public function getLastErrorCode() {
+ return curl_errno( $this->handle );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @return mixed
+ */
+ public function execute() {
+ $this->options = array();
+ return curl_exec( $this->handle );
+ }
+
+ /**
+ * Use __invoke to return the handle in order to avoid cluttering the
+ * interface
+ *
+ * @since 1.0
+ */
+ public function __invoke() {
+ return $this->handle;
+ }
+
+ /**
+ * @since 1.0
+ */
+ public function __destruct() {
+ curl_close( $this->handle );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/http-request/src/Exception/BadHttpResponseException.php b/www/wiki/vendor/onoi/http-request/src/Exception/BadHttpResponseException.php
new file mode 100644
index 00000000..03659800
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/src/Exception/BadHttpResponseException.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Onoi\HttpRequest\Exception;
+
+use Onoi\HttpRequest\HttpRequest;
+
+/**
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class BadHttpResponseException extends HttpConnectionException {
+
+ /**
+ * @since 1.0
+ *
+ * @param HttpRequest $httpRequest
+ */
+ public function __construct( HttpRequest $httpRequest ) {
+
+ $errorCode = $httpRequest->getLastErrorCode();
+
+ switch ( $errorCode ) {
+ case 22: // equals CURLE_HTTP_RETURNED_ERROR but this constant is not defined in PHP
+ $httpCode = $httpRequest->getLastTransferInfo( CURLINFO_HTTP_CODE );
+ $message = "HTTP request ended with $httpCode code\n";
+ break;
+ default:
+ $message = $httpRequest->getLastError();
+ }
+
+ parent::__construct( $message, $errorCode );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/http-request/src/Exception/HttpConnectionException.php b/www/wiki/vendor/onoi/http-request/src/Exception/HttpConnectionException.php
new file mode 100644
index 00000000..bb9247a7
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/src/Exception/HttpConnectionException.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Onoi\HttpRequest\Exception;
+
+/**
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class HttpConnectionException extends \Exception {
+
+ /**
+ * @since 1.0
+ *
+ * @param string $message
+ * @param integer $errorCode
+ */
+ public function __construct( $message = '', $errorCode = 0 ) {
+ parent::__construct( "Failed http connection with error $errorCode ($message).\n" );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/http-request/src/HttpRequest.php b/www/wiki/vendor/onoi/http-request/src/HttpRequest.php
new file mode 100644
index 00000000..b4f8db41
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/src/HttpRequest.php
@@ -0,0 +1,94 @@
+<?php
+
+namespace Onoi\HttpRequest;
+
+/**
+ * @since 1.1
+ *
+ * @{
+ */
+// @codeCoverageIgnoreStart
+define( 'ONOI_HTTP_REQUEST_URL', 'ONOI_HTTP_REQUEST_URL' );
+define( 'ONOI_HTTP_REQUEST_PROTOCOL_VERSION', 'ONOI_HTTP_REQUEST_PROTOCOL_VERSION' );
+define( 'ONOI_HTTP_REQUEST_METHOD', 'ONOI_HTTP_REQUEST_METHOD' ); // POST, GET, HEAD
+
+define( 'ONOI_HTTP_REQUEST_CONNECTION_TIMEOUT', 'ONOI_HTTP_REQUEST_CONNECTION_TIMEOUT' );
+define( 'ONOI_HTTP_REQUEST_CONNECTION_FAILURE_REPEAT', 'ONOI_HTTP_REQUEST_CONNECTION_FAILURE_REPEAT' );
+
+define( 'ONOI_HTTP_REQUEST_CONTENT_TYPE', 'ONOI_HTTP_REQUEST_CONTENT_TYPE' ); // "Content-Type: ..."
+define( 'ONOI_HTTP_REQUEST_CONTENT', 'ONOI_HTTP_REQUEST_CONTENT' );
+define( 'ONOI_HTTP_REQUEST_FOLLOWLOCATION', 'ONOI_HTTP_REQUEST_FOLLOWLOCATION' );
+
+define( 'ONOI_HTTP_REQUEST_SSL_VERIFYPEER', 'ONOI_HTTP_REQUEST_SSL_VERIFYPEER' ); // SSL setting true, false
+define( 'ONOI_HTTP_REQUEST_SOCKET_CLIENT_FLAGS', 'ONOI_HTTP_REQUEST_SOCKET_CLIENT_FLAGS' ); // STREAM_CLIENT_ASYNC_CONNECT | STREAM_CLIENT_PERSISTENT
+define( 'ONOI_HTTP_REQUEST_SOCKET_PORT', 'ONOI_HTTP_REQUEST_SOCKET_PORT' );
+
+define( 'ONOI_HTTP_REQUEST_ON_COMPLETED_CALLBACK', 'ONOI_HTTP_REQUEST_ON_COMPLETED_CALLBACK' );
+define( 'ONOI_HTTP_REQUEST_ON_FAILED_CALLBACK', 'ONOI_HTTP_REQUEST_ON_FAILED_CALLBACK' );
+
+define( 'ONOI_HTTP_REQUEST_RESPONSECACHE_PREFIX', 'ONOI_HTTP_REQUEST_RESPONSECACHE_PREFIX' );
+define( 'ONOI_HTTP_REQUEST_RESPONSECACHE_TTL', 'ONOI_HTTP_REQUEST_RESPONSECACHE_TTL' );
+
+// @codeCoverageIgnoreEnd
+/**@}
+ */
+
+/**
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+interface HttpRequest {
+
+ /**
+ * @since 1.0
+ *
+ * @return boolean
+ */
+ public function ping();
+
+ /**
+ * @since 1.0
+ *
+ * @param string $name
+ * @param mixed $value
+ */
+ public function setOption( $name, $value );
+
+ /**
+ * @since 1.0
+ *
+ * @param string $name
+ */
+ public function getOption( $name );
+
+ /**
+ * @since 1.0
+ *
+ * @param string|null $name
+ */
+ public function getLastTransferInfo( $name = null );
+
+ /**
+ * @since 1.0
+ *
+ * @return string
+ */
+ public function getLastError();
+
+ /**
+ * @since 1.0
+ *
+ * @return integer
+ */
+ public function getLastErrorCode();
+
+ /**
+ * @since 1.0
+ *
+ * @return mixed
+ */
+ public function execute();
+
+}
diff --git a/www/wiki/vendor/onoi/http-request/src/HttpRequestFactory.php b/www/wiki/vendor/onoi/http-request/src/HttpRequestFactory.php
new file mode 100644
index 00000000..e37fd83a
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/src/HttpRequestFactory.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace Onoi\HttpRequest;
+
+use Onoi\Cache\Cache;
+use Onoi\Cache\NullCache;
+
+/**
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class HttpRequestFactory {
+
+ /**
+ * @var Cache
+ */
+ private $cache = null;
+
+ /**
+ * @since 1.0
+ *
+ * @param Cache|null $cache
+ */
+ public function __construct( Cache $cache = null ) {
+ $this->cache = $cache;
+
+ if ( $this->cache === null ) {
+ $this->cache = new NullCache();
+ }
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @return NullRequest
+ */
+ public function newNullRequest() {
+ return new NullRequest();
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param string|null $url
+ *
+ * @return CurlRequest
+ */
+ public function newCurlRequest( $url = null ) {
+ return new CurlRequest( curl_init( $url ) );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param string|null $url
+ *
+ * @return CachedCurlRequest
+ */
+ public function newCachedCurlRequest( $url = null ) {
+ return new CachedCurlRequest( curl_init( $url ), $this->cache );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @return MultiCurlRequest
+ */
+ public function newMultiCurlRequest() {
+ return new MultiCurlRequest( curl_multi_init() );
+ }
+
+ /**
+ * @since 1.1
+ *
+ * @param string|null $url
+ *
+ * @return SocketRequest
+ */
+ public function newSocketRequest( $url = null ) {
+ return new SocketRequest( $url );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/http-request/src/MultiCurlRequest.php b/www/wiki/vendor/onoi/http-request/src/MultiCurlRequest.php
new file mode 100644
index 00000000..212d218c
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/src/MultiCurlRequest.php
@@ -0,0 +1,238 @@
+<?php
+
+namespace Onoi\HttpRequest;
+
+use InvalidArgumentException;
+use Closure;
+
+/**
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class MultiCurlRequest implements HttpRequest {
+
+ /**
+ * @var resource
+ */
+ private $handle;
+
+ /**
+ * @var array
+ */
+ protected $options = array();
+
+ /**
+ * @var CurlRequest[]
+ */
+ private $httpRequests = array();
+
+ /**
+ * @var integer
+ */
+ private $lastErrorCode = 0;
+
+ /**
+ * @since 1.0
+ *
+ * @param resource $handle
+ */
+ public function __construct( $handle ) {
+
+ if ( get_resource_type( $handle ) !== 'curl_multi' ) {
+ throw new InvalidArgumentException( "Expected a cURL multi resource type" );
+ }
+
+ $this->handle = $handle;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @return boolean
+ */
+ public function ping() {
+
+ $canConnect = false;
+
+ foreach ( $this->httpRequests as $httpRequest ) {
+ if ( !$httpRequest->ping() ) {
+ return false;
+ }
+
+ $canConnect = true;
+ }
+
+ return $canConnect;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param CurlRequest $httpRequest
+ */
+ public function addHttpRequest( CurlRequest $httpRequest ) {
+ $this->httpRequests[] = $httpRequest;
+ }
+
+ /**
+ * @since 1.0
+ * @deprecated since 1.1, use ONOI_HTTP_REQUEST_ON_COMPLETED_CALLBACK instead
+ *
+ * @param Closure $callback
+ */
+ public function setCallback( Closure $callback ) {
+ $this->setOption( ONOI_HTTP_REQUEST_ON_COMPLETED_CALLBACK, $callback );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param string $name
+ * @param mixed $value
+ */
+ public function setOption( $name, $value ) {
+
+ $this->options[$name] = $value;
+
+ // Internal ONOI options are not further relayed
+ if ( strpos( $name, 'ONOI_HTTP_REQUEST' ) !== false ) {
+ return;
+ }
+
+ curl_multi_setopt(
+ $this->handle,
+ $name,
+ $value
+ );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param string $name
+ *
+ * @return mixed|null
+ */
+ public function getOption( $name ) {
+ return isset( $this->options[$name] ) ? $this->options[$name] : null;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param string|null $name
+ *
+ * @return mixed
+ */
+ public function getLastTransferInfo( $name = null ) {
+ return curl_multi_info_read( $this->handle );
+ }
+
+ /**
+ * @note PHP 5.5.0
+ *
+ * @since 1.0
+ *
+ * @return string
+ */
+ public function getLastError() {
+ return function_exists( 'curl_multi_strerror' ) ? curl_multi_strerror( $this->lastErrorCode ) : '';
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @return integer
+ */
+ public function getLastErrorCode() {
+ return $this->lastErrorCode;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @return array|null
+ */
+ public function execute() {
+
+ // Register all handles and monitor that
+ // all requests are executed
+ $handleExecutionCounter = array();
+
+ foreach( $this->httpRequests as $httpRequest ) {
+ $httpRequest->setOption( CURLOPT_RETURNTRANSFER, true );
+ $handleExecutionCounter[(int) $httpRequest() ] = true;
+ curl_multi_add_handle( $this->handle, $httpRequest() );
+ }
+
+ $response = $this->doMakeMultiRequest( $handleExecutionCounter );
+
+ return $response;
+ }
+
+ private function doMakeMultiRequest( $handleExecutionCounter ) {
+
+ $active = null;
+ $responses = array();
+
+ // http://php.net/manual/en/function.curl-multi-init.php
+ // https://gist.github.com/Xeoncross/2362936
+ do {
+ $this->lastErrorCode = curl_multi_exec( $this->handle, $active );
+
+ // Wait for activity on any curl-connection
+ if ( curl_multi_select( $this->handle ) == -1 ) {
+ usleep( 100 );
+ }
+
+ } while ( $this->lastErrorCode == CURLM_CALL_MULTI_PERFORM );
+
+ while ( ( $active && $this->lastErrorCode == CURLM_OK ) || $handleExecutionCounter !== array() ) {
+
+ $requestResponse = $this->doCheckRequestResponse( $handleExecutionCounter );
+
+ if ( $requestResponse !== null ) {
+ $responses[] = $requestResponse;
+ }
+
+ // Continue to exec until curl is ready
+ do {
+ $this->lastErrorCode = curl_multi_exec( $this->handle, $active );
+ } while ( $this->lastErrorCode == CURLM_CALL_MULTI_PERFORM );
+ }
+
+ return $responses;
+ }
+
+ private function doCheckRequestResponse( &$handleExecutionCounter ) {
+
+ $requestResponse = null;
+
+ if ( ( $state = curl_multi_info_read( $this->handle ) ) && $state["msg"] == CURLMSG_DONE ) {
+
+ $requestResponse = new RequestResponse( array(
+ 'contents' => curl_multi_getcontent( $state['handle'] ),
+ 'info' => curl_getinfo( $state['handle'] )
+ ) );
+
+ unset( $handleExecutionCounter[(int) $state['handle']] );
+ curl_multi_remove_handle( $this->handle, $state['handle'] );
+ }
+
+ if ( is_callable( $this->getOption( ONOI_HTTP_REQUEST_ON_COMPLETED_CALLBACK ) ) && $requestResponse !== null ) {
+ call_user_func_array( $this->getOption( ONOI_HTTP_REQUEST_ON_COMPLETED_CALLBACK ), array( $requestResponse ) );
+ }
+
+ return $requestResponse;
+ }
+
+ /**
+ * @since 1.0
+ */
+ public function __destruct() {
+ curl_multi_close( $this->handle );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/http-request/src/NullRequest.php b/www/wiki/vendor/onoi/http-request/src/NullRequest.php
new file mode 100644
index 00000000..aeca8866
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/src/NullRequest.php
@@ -0,0 +1,70 @@
+<?php
+
+namespace Onoi\HttpRequest;
+
+/**
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class NullRequest implements HttpRequest {
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function ping() {
+ return false;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function setOption( $name, $value ) {}
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function getOption( $name ) {}
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function getLastTransferInfo( $name = null ) {
+ return null;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function getLastError() {
+ return '';
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function getLastErrorCode() {
+ return 0;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function execute() {}
+
+}
diff --git a/www/wiki/vendor/onoi/http-request/src/RequestResponse.php b/www/wiki/vendor/onoi/http-request/src/RequestResponse.php
new file mode 100644
index 00000000..e5d1c9ea
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/src/RequestResponse.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace Onoi\HttpRequest;
+
+use InvalidArgumentException;
+
+/**
+ * @license GNU GPL v2+
+ * @since 1.1
+ *
+ * @author mwjames
+ */
+class RequestResponse {
+
+ /**
+ * @var array
+ */
+ private $fields = array();
+
+ /**
+ * @since 1.1
+ */
+ public function __construct( array $fields = array() ) {
+ $this->fields = $fields;
+ }
+
+ /**
+ * @since 1.1
+ *
+ * @param string $key
+ * @param mixed $value
+ */
+ public function set( $key, $value ) {
+ $this->fields[$key] = $value;
+ }
+
+ /**
+ * @since 1.1
+ *
+ * @param string $key
+ *
+ * @return boolean
+ */
+ public function has( $key ) {
+ return isset( $this->fields[$key] ) || array_key_exists( $key, $this->fields );
+ }
+
+ /**
+ * @since 1.1
+ *
+ * @param string $key
+ *
+ * @return string
+ * @throws InvalidArgumentException
+ */
+ public function get( $key ) {
+
+ if ( $this->has( $key ) ) {
+ return $this->fields[$key];
+ }
+
+ throw new InvalidArgumentException( "{$key} is an unregistered option" );
+ }
+
+ /**
+ * @since 1.1
+ *
+ * @return array
+ */
+ public function getList() {
+ return $this->fields;
+ }
+
+ /**
+ * @since 1.4
+ *
+ * @param integer $flags
+ *
+ * @return string
+ */
+ public function asJsonString( $flags = 0 ) {
+ return json_encode( $this->fields, $flags );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/http-request/src/SocketRequest.php b/www/wiki/vendor/onoi/http-request/src/SocketRequest.php
new file mode 100644
index 00000000..1f049cf1
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/src/SocketRequest.php
@@ -0,0 +1,309 @@
+<?php
+
+namespace Onoi\HttpRequest;
+
+use InvalidArgumentException;
+use Closure;
+
+/**
+ * This class creates a remote socked connection to initiate an asynchronous
+ * http/https request.
+ *
+ * Once a connection is established and content has been posted, the request
+ * will close the connection. The receiving client is responsible for open
+ * a separate process and initiated an independent transaction.
+ *
+ * @license GNU GPL v2+
+ * @since 1.1
+ *
+ * @author mwjames
+ */
+class SocketRequest implements HttpRequest {
+
+ /**
+ * @var array
+ */
+ private $options = array();
+
+ /**
+ * @var integer
+ */
+ private $errno = 0;
+
+ /**
+ * @var string
+ */
+ private $errstr = '';
+
+ /**
+ * @var string
+ */
+ private $lastTransferInfo = '';
+
+ /**
+ * @var boolean
+ */
+ private $followedLocation = false;
+
+ /**
+ * @since 1.1
+ *
+ * @param string|null $url
+ */
+ public function __construct( $url = null ) {
+ $this->setOption( ONOI_HTTP_REQUEST_URL, $url );
+ $this->setOption( ONOI_HTTP_REQUEST_CONNECTION_TIMEOUT, 15 );
+ $this->setOption( ONOI_HTTP_REQUEST_CONNECTION_FAILURE_REPEAT, 2 );
+ $this->setOption( ONOI_HTTP_REQUEST_SOCKET_CLIENT_FLAGS, STREAM_CLIENT_ASYNC_CONNECT | STREAM_CLIENT_CONNECT );
+ $this->setOption( ONOI_HTTP_REQUEST_METHOD, 'POST' );
+ $this->setOption( ONOI_HTTP_REQUEST_CONTENT_TYPE, "application/x-www-form-urlencoded" );
+ $this->setOption( ONOI_HTTP_REQUEST_SSL_VERIFYPEER, false );
+ $this->setOption( ONOI_HTTP_REQUEST_FOLLOWLOCATION, true );
+ }
+
+ /**
+ * @since 1.1
+ *
+ * {@inheritDoc}
+ */
+ public function ping() {
+
+ if ( $this->getOption( ONOI_HTTP_REQUEST_URL ) === null ) {
+ return false;
+ }
+
+ $urlComponents = $this->getUrlComponents( $this->getOption( ONOI_HTTP_REQUEST_URL ) );
+
+ $resource = $this->getResourceFromSocketClient(
+ $urlComponents,
+ STREAM_CLIENT_CONNECT
+ );
+
+ if ( !$resource ) {
+ return false;
+ }
+
+ stream_set_timeout( $resource, $this->getOption( ONOI_HTTP_REQUEST_CONNECTION_TIMEOUT ) );
+
+ $httpMessage = (
+ "HEAD " . $urlComponents['path'] . " HTTP/1.1\r\n" .
+ "Host: " . $urlComponents['host'] . "\r\n" .
+ "Connection: Close\r\n\r\n"
+ );
+
+ $res = @fwrite( $resource, $httpMessage );
+
+ if ( $this->getOption( ONOI_HTTP_REQUEST_FOLLOWLOCATION ) && $res !== false ) {
+ $this->setOption( ONOI_HTTP_REQUEST_URL, $this->tryToFindFollowLocation( $resource, $urlComponents ) );
+ }
+
+ @fclose( $resource );
+
+ return $res !== false;
+ }
+
+ /**
+ * @since 1.1
+ *
+ * {@inheritDoc}
+ */
+ public function setOption( $name, $value ) {
+ $this->options[$name] = $value;
+ }
+
+ /**
+ * @since 1.1
+ *
+ * {@inheritDoc}
+ */
+ public function getOption( $name ) {
+ return isset( $this->options[$name] ) ? $this->options[$name] : null;
+ }
+
+ /**
+ * @since 1.1
+ *
+ * {@inheritDoc}
+ */
+ public function getLastTransferInfo( $name = null ) {
+ return $this->lastTransferInfo;
+ }
+
+ /**
+ * @since 1.1
+ *
+ * {@inheritDoc}
+ */
+ public function getLastError() {
+ return $this->errstr;
+ }
+
+ /**
+ * @since 1.1
+ *
+ * {@inheritDoc}
+ */
+ public function getLastErrorCode() {
+ return $this->errno;
+ }
+
+ /**
+ * @since 1.1
+ *
+ * {@inheritDoc}
+ */
+ public function execute() {
+
+ $urlComponents = $this->getUrlComponents( $this->getOption( ONOI_HTTP_REQUEST_URL ) );
+
+ $resource = $this->getResourceFromSocketClient(
+ $urlComponents,
+ $this->getOption( ONOI_HTTP_REQUEST_SOCKET_CLIENT_FLAGS )
+ );
+
+ // Defaults
+ $requestResponse = new RequestResponse( array(
+ 'host' => $urlComponents['host'],
+ 'port' => $urlComponents['port'],
+ 'path' => $urlComponents['path'],
+ 'responseMessage' => "$this->errstr ($this->errno)",
+ 'followedLocation' => false,
+ 'wasCompleted' => false,
+ 'wasAccepted' => false,
+ 'connectionFailure' => -1,
+ 'requestProcTime' => microtime( true )
+ ) );
+
+ $this->doMakeSocketRequest(
+ $urlComponents,
+ $resource,
+ $requestResponse
+ );
+
+ $this->postResponseToCallback(
+ $requestResponse
+ );
+
+ return $requestResponse->get( 'wasCompleted' );
+ }
+
+ protected function getResourceFromSocketClient( $urlComponents, $flags ) {
+
+ $context = stream_context_create();
+ stream_context_set_option( $context, 'ssl', 'verify_peer', $this->getOption( ONOI_HTTP_REQUEST_SSL_VERIFYPEER ) );
+
+ $resource = @stream_socket_client(
+ $urlComponents['host'] . ':'. $urlComponents['port'],
+ $this->errno,
+ $this->errstr,
+ $this->getOption( ONOI_HTTP_REQUEST_CONNECTION_TIMEOUT ),
+ $flags,
+ $context
+ );
+
+ return $resource;
+ }
+
+ private function doMakeSocketRequest( $urlComponents, $resource, RequestResponse &$requestResponse ) {
+
+ if ( !$resource ) {
+ return;
+ }
+
+ $requestCompleted = false;
+ stream_set_timeout( $resource, $this->getOption( ONOI_HTTP_REQUEST_CONNECTION_TIMEOUT ) );
+
+ $httpMessage = (
+ strtoupper( $this->getOption( ONOI_HTTP_REQUEST_METHOD ) ) . " " . $urlComponents['path'] . " HTTP/1.1\r\n" .
+ "Host: " . $urlComponents['host'] . "\r\n" .
+ "Content-Type: " . $this->getOption( ONOI_HTTP_REQUEST_CONTENT_TYPE ) . "\r\n" .
+ "Content-Length: " . strlen( $this->getOption( ONOI_HTTP_REQUEST_CONTENT ) ) . "\r\n" .
+ "Connection: Close\r\n\r\n" .
+ $this->getOption( ONOI_HTTP_REQUEST_CONTENT )
+ );
+
+ // Sometimes a response can fail (busy server, timeout etc.), try as for
+ // as many times the FAILURE_REPEAT option dictates
+ for ( $repeats = 0; $repeats < $this->getOption( ONOI_HTTP_REQUEST_CONNECTION_FAILURE_REPEAT ); $repeats++ ) {
+ if ( $requestCompleted = @fwrite( $resource, $httpMessage ) ) {
+ break;
+ }
+ }
+
+ // Fetch the acknowledge response
+ $this->lastTransferInfo = @fgets( $resource );
+ @fclose( $resource );
+
+ $requestResponse->set( 'responseMessage', $this->lastTransferInfo );
+ $requestResponse->set( 'followedLocation', $this->followedLocation );
+ $requestResponse->set( 'wasCompleted', (bool)$requestCompleted );
+ $requestResponse->set( 'wasAccepted', (bool)preg_match( '#^HTTP/\d\.\d 202 #', $this->lastTransferInfo ) );
+ $requestResponse->set( 'connectionFailure', $repeats );
+ }
+
+ private function getUrlComponents( $url ) {
+
+ $urlComponents = parse_url( $url );
+ $port = 80;
+
+ $urlComponents['scheme'] = isset( $urlComponents['scheme'] ) ? $urlComponents['scheme'] : '';
+ $urlComponents['host'] = isset( $urlComponents['host'] ) ? $urlComponents['host'] : '';
+ $urlComponents['path'] = isset( $urlComponents['path'] ) ? $urlComponents['path'] : '';
+
+ if ( isset( $urlComponents['scheme'] ) && $urlComponents['scheme'] == 'https' ) {
+ $urlComponents['host'] = "tls://" . $urlComponents['host'];
+ $port = 443;
+ }
+
+ if ( $this->getOption( ONOI_HTTP_REQUEST_SOCKET_PORT ) !== null ) {
+ $port = $this->getOption( ONOI_HTTP_REQUEST_SOCKET_PORT );
+ }
+
+ $urlComponents['port'] = isset( $urlComponents['port'] ) ? $urlComponents['port'] : $port;
+
+ return array(
+ 'scheme' => $urlComponents['scheme'],
+ 'host' => $urlComponents['host'],
+ 'port' => $urlComponents['port'],
+ 'path' => $urlComponents['path']
+ );
+ }
+
+ private function postResponseToCallback( RequestResponse $requestResponse ) {
+
+ $requestResponse->set( 'requestProcTime', microtime( true ) - $requestResponse->get( 'requestProcTime' ) );
+
+ if ( is_callable( $this->getOption( ONOI_HTTP_REQUEST_ON_COMPLETED_CALLBACK ) ) && $requestResponse->get( 'wasCompleted' ) ) {
+ call_user_func_array( $this->getOption( ONOI_HTTP_REQUEST_ON_COMPLETED_CALLBACK ), array( $requestResponse ) );
+ }
+
+ if ( is_callable( $this->getOption( ONOI_HTTP_REQUEST_ON_FAILED_CALLBACK ) ) && ( !$requestResponse->get( 'wasCompleted' ) || !$requestResponse->get( 'wasAccepted' ) ) ) {
+ call_user_func_array( $this->getOption( ONOI_HTTP_REQUEST_ON_FAILED_CALLBACK ), array( $requestResponse ) );
+ }
+ }
+
+ private function tryToFindFollowLocation( $resource, $urlComponents ) {
+
+ // http://stackoverflow.com/questions/3799134/how-to-get-final-url-after-following-http-redirections-in-pure-php
+
+ $response = '';
+ $host = str_replace( 'tls://', '', $urlComponents['host'] );
+
+ while( !feof( $resource ) ) $response .= fread( $resource, 8192 );
+
+ // Only try to match a 301 message (Moved Permanently)
+ if ( preg_match( '#^HTTP/\d\.\d 301 #', $response ) && preg_match('/^Location: (.+?)$/m', $response, $matches ) ) {
+ $this->followedLocation = true;
+
+ if ( substr( $matches[1], 0, 1 ) == "/" ) {
+ return $urlComponents['scheme'] . "://" . $host . trim( $matches[1] );
+ }
+
+ return trim( $matches[1] );
+ }
+
+ // Return the URL we know
+ return $this->getOption( ONOI_HTTP_REQUEST_URL );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/http-request/tests/bootstrap.php b/www/wiki/vendor/onoi/http-request/tests/bootstrap.php
new file mode 100644
index 00000000..fd2d7671
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/tests/bootstrap.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+
+error_reporting( E_ALL | E_STRICT );
+date_default_timezone_set( 'UTC' );
+
+if ( php_sapi_name() !== 'cli' ) {
+ die( 'Not an entry point' );
+}
+
+if ( is_readable( $path = __DIR__ . '/../vendor/autoload.php' ) ) {
+ print( "\nUsing the http-request vendor autoloader ...\n\n" );
+} elseif ( is_readable( $path = __DIR__ . '/../../../autoload.php' ) ) {
+ print( "\nUsing another local vendor autoloader ...\n\n" );
+} else {
+ die( 'The test suite requires a Composer based deployement.' );
+}
+
+$autoLoader = require $path;
+$autoLoader->addPsr4( 'Onoi\\HttpRequest\\Tests\\', __DIR__ . '/phpunit/Unit' );
+$autoLoader->addPsr4( 'Onoi\\HttpRequest\\Tests\\Integration\\', __DIR__ . '/phpunit/Integration' );
diff --git a/www/wiki/vendor/onoi/http-request/tests/phpunit/Integration/RequestToExternalUrlTest.php b/www/wiki/vendor/onoi/http-request/tests/phpunit/Integration/RequestToExternalUrlTest.php
new file mode 100644
index 00000000..0e9ed8eb
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/tests/phpunit/Integration/RequestToExternalUrlTest.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Onoi\HttpRequest\Tests\Integration;
+
+use Onoi\HttpRequest\CachedCurlRequest;
+use Onoi\HttpRequest\CurlRequest;
+use Onoi\Cache\FixedInMemoryLruCache;
+
+/**
+ * @group onoi-http-request
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class RequestToExternalUrlTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCachedRequestToExternalUrl() {
+
+ $target = 'http://example.org/';
+
+ $cache = new FixedInMemoryLruCache();
+
+ $instance = new CachedCurlRequest(
+ curl_init( $target ),
+ $cache
+ );
+
+ if ( !$instance->ping() ) {
+ $this->markTestSkipped( "Can't connect to " . $target );
+ }
+
+ $instance->setOption( CURLOPT_RETURNTRANSFER, true );
+ $instance->setOption( ONOI_HTTP_REQUEST_RESPONSECACHE_TTL, 42 );
+ $instance->setOption( ONOI_HTTP_REQUEST_RESPONSECACHE_PREFIX, 'foo' );
+
+ $this->assertInternalType(
+ 'string',
+ $instance->execute()
+ );
+
+ $this->assertFalse(
+ $instance->isFromCache()
+ );
+
+ // Repeated request
+ $instance->setOption( CURLOPT_RETURNTRANSFER, true );
+ $instance->setOption( ONOI_HTTP_REQUEST_RESPONSECACHE_TTL, 42 );
+ $instance->setOption( ONOI_HTTP_REQUEST_RESPONSECACHE_PREFIX, 'foo' );
+
+ $instance->execute();
+
+ $this->assertTrue(
+ $instance->isFromCache()
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/http-request/tests/phpunit/Integration/RequestToPhpHttpdTest.php b/www/wiki/vendor/onoi/http-request/tests/phpunit/Integration/RequestToPhpHttpdTest.php
new file mode 100644
index 00000000..b9a96ece
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/tests/phpunit/Integration/RequestToPhpHttpdTest.php
@@ -0,0 +1,185 @@
+<?php
+
+namespace Onoi\HttpRequest\Tests\Integration;
+
+use Onoi\HttpRequest\HttpRequestFactory;
+use Onoi\Cache\CacheFactory;
+use RuntimeException;
+
+/**
+ * @group onoi-http-request
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class RequestToPhpHttpdTest extends \PHPUnit_Framework_TestCase {
+
+ private static $pid;
+
+ /**
+ * @note Using the PHP in-build webserver to serve a slow/fast page response for
+ * validation of the async response
+ *
+ * Options defined in phpunit.xml.dist
+ * @see https://github.com/vgno/tech.vg.no-1812/blob/master/features/bootstrap/FeatureContext.php
+ */
+ public static function setUpBeforeClass() {
+
+ $command = sprintf( 'php -S %s:%d -t %s >/dev/null 2>&1 & echo $!',
+ WEB_SERVER_HOST,
+ WEB_SERVER_PORT,
+ WEB_SERVER_DOCROOT
+ );
+
+ $output = array();
+ exec( $command, $output );
+
+ self::$pid = (int) $output[0];
+ }
+
+ public static function tearDownAfterClass() {
+ // exec( 'kill ' . (int) self::$pid );
+ }
+
+ public function testQueuedResponse() {
+
+ $this->connectToHttpd();
+ $expectedToCount = 5;
+
+ $httpRequestFactory = new HttpRequestFactory();
+ $multiCurlRequest = $httpRequestFactory->newMultiCurlRequest();
+
+ for ( $i = 0; $i < $expectedToCount; $i++ ) {
+ $multiCurlRequest->addHttpRequest(
+ $httpRequestFactory->newCurlRequest( $this->getHttpdRequestUrl( $i ) )
+ );
+ }
+
+ $this->assertCount(
+ $expectedToCount,
+ $multiCurlRequest->execute()
+ );
+ }
+
+ public function testCachedMultiCurlQueuedResponse() {
+
+ $this->connectToHttpd();
+ $expectedToCount = 5;
+
+ $cacheFactory = new CacheFactory();
+
+ $httpRequestFactory = new HttpRequestFactory(
+ $cacheFactory->newFixedInMemoryLruCache()
+ );
+
+ $multiCurlRequest = $httpRequestFactory->newMultiCurlRequest();
+
+ for ( $i = 0; $i < $expectedToCount; $i++ ) {
+ $multiCurlRequest->addHttpRequest(
+ $httpRequestFactory->newCachedCurlRequest( $this->getHttpdRequestUrl( $i ) )
+ );
+ }
+
+ $this->assertCount(
+ $expectedToCount,
+ $multiCurlRequest->execute()
+ );
+ }
+
+ public function testMultiCurlForCallbackResponse() {
+
+ $this->connectToHttpd();
+ $expectedToCount = 5;
+
+ $asyncCallbackResponseMock = $this->getMockBuilder( '\stdClass' )
+ ->setMethods( array( 'run' ) )
+ ->getMock();
+
+ $asyncCallbackResponseMock->expects( $this->exactly( $expectedToCount ) )
+ ->method( 'run' );
+
+ $httpRequestFactory = new HttpRequestFactory();
+ $multiCurlRequest = $httpRequestFactory->newMultiCurlRequest();
+
+ $multiCurlRequest->setOption( ONOI_HTTP_REQUEST_ON_COMPLETED_CALLBACK, function( $requestResponse ) use ( $asyncCallbackResponseMock ) {
+ $asyncCallbackResponseMock->run( $requestResponse );
+ } );
+
+ for ( $i = 0; $i < $expectedToCount; $i++ ) {
+ $multiCurlRequest->addHttpRequest(
+ $httpRequestFactory->newCurlRequest( $this->getHttpdRequestUrl( $i ) )
+ );
+ }
+
+ $multiCurlRequest->execute();
+ }
+
+ public function testSocketResponse() {
+
+ $this->connectToHttpd();
+ $expectedToCount = 1;
+
+ $asyncCallbackResponseMock = $this->getMockBuilder( '\stdClass' )
+ ->setMethods( array( 'run' ) )
+ ->getMock();
+
+ $asyncCallbackResponseMock->expects( $this->exactly( $expectedToCount ) )
+ ->method( 'run' );
+
+ $httpRequestFactory = new HttpRequestFactory();
+ $socketRequest = $httpRequestFactory->newSocketRequest();
+
+ $socketRequest->setOption( ONOI_HTTP_REQUEST_CONNECTION_TIMEOUT, 5 );
+ $socketRequest->setOption( ONOI_HTTP_REQUEST_CONNECTION_FAILURE_REPEAT, 2 );
+
+ $socketRequest->setOption( ONOI_HTTP_REQUEST_METHOD, 'GET' );
+ $socketRequest->setOption( ONOI_HTTP_REQUEST_URL, WEB_SERVER_HOST . ':' . WEB_SERVER_PORT . '/plain.php' );
+
+ $socketRequest->ping();
+
+ $socketRequest->setOption( ONOI_HTTP_REQUEST_ON_FAILED_CALLBACK, function( $requestResponse ) use ( $asyncCallbackResponseMock ) {
+ $asyncCallbackResponseMock->run( $requestResponse );
+ } );
+
+ $socketRequest->execute();
+ }
+
+ private function getHttpdRequestUrl( $id ) {
+
+ // slow/fast example used from https://gist.github.com/Xeoncross/2362936
+
+ if ( $id % 2 ) {
+ return WEB_SERVER_HOST . ':' . WEB_SERVER_PORT . '/slow.php?id=' . $id;
+ }
+
+ return WEB_SERVER_HOST . ':' . WEB_SERVER_PORT . '/fast.php?id=' . $id;
+ }
+
+ private function connectToHttpd() {
+
+ $start = microtime(true);
+
+ // Try to connect
+ while ( microtime( true ) - $start <= 10 ) {
+ if ( $this->canConnectToHttpd() ) {
+ break;
+ }
+ }
+ }
+
+ private function canConnectToHttpd() {
+
+ $sp = @fsockopen( WEB_SERVER_HOST, WEB_SERVER_PORT );
+
+ if ( $sp === false ) {
+ return false;
+ }
+
+ fclose( $sp );
+
+ return true;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/http-request/tests/phpunit/Integration/http-public/fast.php b/www/wiki/vendor/onoi/http-request/tests/phpunit/Integration/http-public/fast.php
new file mode 100644
index 00000000..92176a32
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/tests/phpunit/Integration/http-public/fast.php
@@ -0,0 +1,4 @@
+<?php
+
+usleep( mt_rand( 10000, 200000 ) );
+print $_REQUEST['id'] . ' : ' . basename(__FILE__ ) . "\n"; \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/http-request/tests/phpunit/Integration/http-public/plain.php b/www/wiki/vendor/onoi/http-request/tests/phpunit/Integration/http-public/plain.php
new file mode 100644
index 00000000..f02861a9
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/tests/phpunit/Integration/http-public/plain.php
@@ -0,0 +1,4 @@
+<?php
+
+header( "HTTP/1.0 202 Accepted" );
+print 'REQUEST_METHOD: ' . $_SERVER['REQUEST_METHOD']; \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/http-request/tests/phpunit/Integration/http-public/slow.php b/www/wiki/vendor/onoi/http-request/tests/phpunit/Integration/http-public/slow.php
new file mode 100644
index 00000000..01f96a68
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/tests/phpunit/Integration/http-public/slow.php
@@ -0,0 +1,4 @@
+<?php
+
+usleep( mt_rand( 100000, 600000 ) );
+print $_REQUEST['id'] . ' : ' . basename(__FILE__) . "\n"; \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/CachedCurlRequestTest.php b/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/CachedCurlRequestTest.php
new file mode 100644
index 00000000..d62a0b69
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/CachedCurlRequestTest.php
@@ -0,0 +1,185 @@
+<?php
+
+namespace Onoi\HttpRequest\Tests;
+
+use Onoi\HttpRequest\CachedCurlRequest;
+
+/**
+ * @covers \Onoi\HttpRequest\CachedCurlRequest
+ * @group onoi-http-request
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class CachedCurlRequestTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $cache = $this->getMockBuilder( '\Onoi\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $instance = new CachedCurlRequest( curl_init(), $cache );
+
+ $this->assertInstanceOf(
+ '\Onoi\HttpRequest\CurlRequest',
+ $instance
+ );
+
+ $this->assertInstanceOf(
+ '\Onoi\HttpRequest\CachedCurlRequest',
+ $instance
+ );
+ }
+
+ public function testExecuteForRepeatedRequest() {
+
+ $cache = $this->getMockBuilder( '\Onoi\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->setMethods( array( 'contains', 'fetch' ) )
+ ->getMockForAbstractClass();
+
+ $cache->expects( $this->once() )
+ ->method( 'contains' )
+ ->with( $this->equalTo( 'foo:onoi:http:39aa03567d7d983dab1f1bdc5dc75e84' ) )
+ ->will( $this->returnValue( true ) );
+
+ $cache->expects( $this->once() )
+ ->method( 'fetch' )
+ ->will( $this->returnValue( 22 ) );
+
+ $instance = new CachedCurlRequest( curl_init(), $cache );
+
+ $instance->setCachePrefix( 'foo:' );
+ $instance->setOption( CURLOPT_RETURNTRANSFER, true );
+
+ $this->assertEquals(
+ 22,
+ $instance->execute()
+ );
+
+ $this->assertTrue(
+ $instance->isCached()
+ );
+ }
+
+ public function testDifferentOptionsToGenerateDifferentKeys() {
+
+ $cache = $this->getMockBuilder( '\Onoi\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->setMethods( array( 'contains' ) )
+ ->getMockForAbstractClass();
+
+ $cache->expects( $this->at( 0 ) )
+ ->method( 'contains' )
+ ->with( $this->equalTo( 'onoi:http:7ccbcfd552a597d67077d6cc037580ac' ) );
+
+ $cache->expects( $this->at( 1 ) )
+ ->method( 'contains' )
+ ->with( $this->equalTo( 'onoi:http:e2015ad4244c4663f10f305e299d5c4f' ) );
+
+ $instance = new CachedCurlRequest( curl_init(), $cache );
+
+ $instance->setOption( CURLOPT_RETURNTRANSFER, true );
+ $instance->execute();
+
+ $instance->setOption( CURLOPT_RETURNTRANSFER, false );
+ $instance->execute();
+ }
+
+ public function testToHaveTargetUrlAsPartOfTheCacheKey() {
+
+ $cache = $this->getMockBuilder( '\Onoi\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->setMethods( array( 'contains', 'save' ) )
+ ->getMockForAbstractClass();
+
+ $cache->expects( $this->at( 0 ) )
+ ->method( 'contains' )
+ ->with( $this->equalTo( 'onoi:http:236b194825be3d614ce5fc1b7763a278' ) );
+
+ $cache->expects( $this->at( 2 ) )
+ ->method( 'contains' )
+ ->with( $this->equalTo( 'onoi:http:823a603f972819c10d13f32b14460573' ) );
+
+ $instance = new CachedCurlRequest( curl_init( 'http://example.org' ), $cache );
+
+ $instance->setOption( CURLOPT_RETURNTRANSFER, true );
+ $instance->execute();
+
+ $instance->setOption( CURLOPT_RETURNTRANSFER, true );
+ $instance->setOption( CURLOPT_URL, 'http://example.org' );
+ $instance->execute();
+ }
+
+ public function testSaveResponse() {
+
+ $cache = $this->getMockBuilder( '\Onoi\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->setMethods( array( 'save', 'contains' ) )
+ ->getMockForAbstractClass();
+
+ $cache->expects( $this->once() )
+ ->method( 'save' )
+ ->with(
+ $this->equalTo( 'foo:onoi:http:b87e897ea862a410aff82e3122e2d955' ),
+ $this->anything(),
+ $this->equalTo( 42 ) );
+
+ $instance = new CachedCurlRequest(
+ curl_init(),
+ $cache
+ );
+
+ $instance->setOption( ONOI_HTTP_REQUEST_RESPONSECACHE_TTL, 42 );
+ $instance->setOption( ONOI_HTTP_REQUEST_RESPONSECACHE_PREFIX, 'foo:' );
+
+ $instance->setOption( CURLOPT_URL, 'http://example.org' );
+ $instance->setOption( CURLOPT_RETURNTRANSFER, true );
+
+ $instance->execute();
+ }
+
+ public function testDefinedConstants() {
+
+ $cache = $this->getMockBuilder( '\Onoi\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $constants = array(
+ 'ONOI_HTTP_REQUEST_RESPONSECACHE_PREFIX',
+ 'ONOI_HTTP_REQUEST_RESPONSECACHE_TTL'
+ );
+
+ $instance = new CachedCurlRequest( curl_init(), $cache );
+
+ foreach ( $constants as $constant ) {
+ $this->assertTrue( defined( $constant ) );
+ }
+ }
+
+ public function testDeprecatedFunctions() {
+
+ $cache = $this->getMockBuilder( '\Onoi\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $instance = new CachedCurlRequest( curl_init(), $cache );
+
+ $instance->setExpiryInSeconds( 42 );
+ $instance->setCachePrefix( 'Foo' );
+
+ $this->assertEquals(
+ 42,
+ $instance->getOption( ONOI_HTTP_REQUEST_RESPONSECACHE_TTL )
+ );
+
+ $this->assertEquals(
+ 'Foo',
+ $instance->getOption( ONOI_HTTP_REQUEST_RESPONSECACHE_PREFIX )
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/CurlRequestTest.php b/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/CurlRequestTest.php
new file mode 100644
index 00000000..5af5a892
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/CurlRequestTest.php
@@ -0,0 +1,107 @@
+<?php
+
+namespace Onoi\HttpRequest\Tests;
+
+use Onoi\HttpRequest\CurlRequest;
+
+/**
+ * @covers \Onoi\HttpRequest\CurlRequest
+ * @group onoi-http-request
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class CurlRequestTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $instance = new CurlRequest( curl_init() );
+
+ $this->assertInstanceOf(
+ '\Onoi\HttpRequest\CurlRequest',
+ $instance
+ );
+
+ $this->assertInstanceOf(
+ '\Onoi\HttpRequest\HttpRequest',
+ $instance
+ );
+ }
+
+ public function testWrongResourceTypeThrowsException() {
+ $this->setExpectedException( 'InvalidArgumentException' );
+ new CurlRequest( curl_multi_init() );
+ }
+
+ public function testPing() {
+
+ $instance = new CurlRequest( curl_init() );
+
+ $this->assertFalse(
+ $instance->ping()
+ );
+
+ $instance->setOption( CURLOPT_URL, 'http://example.org' );
+
+ $this->assertInternalType(
+ 'boolean',
+ $instance->ping()
+ );
+ }
+
+ public function testGetLastError() {
+
+ $instance = new CurlRequest( curl_init() );
+
+ $this->assertInternalType(
+ 'string',
+ $instance->getLastError()
+ );
+ }
+
+ public function testGetLastErrorCode() {
+
+ $instance = new CurlRequest( curl_init() );
+
+ $this->assertInternalType(
+ 'integer',
+ $instance->getLastErrorCode()
+ );
+ }
+
+ public function testExecuteForNullUrl() {
+
+ $instance = new CurlRequest( curl_init( null ) );
+ $instance->setOption( CURLOPT_RETURNTRANSFER, true );
+
+ $this->assertTrue(
+ $instance->getOption( CURLOPT_RETURNTRANSFER )
+ );
+
+ $this->assertFalse(
+ $instance->execute()
+ );
+
+ $this->assertNull(
+ $instance->getOption( CURLOPT_RETURNTRANSFER )
+ );
+
+ $this->assertEmpty(
+ $instance->getLastTransferInfo( CURLINFO_HTTP_CODE )
+ );
+ }
+
+ public function testSetOptionUsingOnoiSpecificConstantDoesNotCauseAnyFailureWithCurl_Setopt() {
+
+ $instance = new CurlRequest( curl_init( null ) );
+ $instance->setOption( ONOI_HTTP_REQUEST_RESPONSECACHE_TTL, 42 );
+
+ $this->assertEquals(
+ 42,
+ $instance->getOption( ONOI_HTTP_REQUEST_RESPONSECACHE_TTL )
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/Exception/BadHttpResponseExceptionTest.php b/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/Exception/BadHttpResponseExceptionTest.php
new file mode 100644
index 00000000..5a6646f6
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/Exception/BadHttpResponseExceptionTest.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Onoi\HttpRequest\Tests\Exception;
+
+use Onoi\HttpRequest\CurlRequest;
+use Onoi\HttpRequest\Exception\BadHttpResponseException;
+
+/**
+ * @covers \Onoi\HttpRequest\Exception\BadHttpResponseException
+ * @group onoi-http-request
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class BadHttpResponseExceptionTest extends \PHPUnit_Framework_TestCase {
+
+ private $httpRequest;
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->httpRequest = $this->getMockBuilder( '\Onoi\HttpRequest\HttpRequest' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+ }
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\HttpRequest\Exception\BadHttpResponseException',
+ new BadHttpResponseException( $this->httpRequest )
+ );
+
+ $this->assertInstanceOf(
+ '\Onoi\HttpRequest\Exception\HttpConnectionException',
+ new BadHttpResponseException( $this->httpRequest )
+ );
+ }
+
+ public function testHttpError() {
+
+ $this->httpRequest->expects( $this->once() )
+ ->method( 'getLastErrorCode' )
+ ->will( $this->returnValue( 22 ) );
+
+ $this->httpRequest->expects( $this->once() )
+ ->method( 'getLastTransferInfo' )
+ ->with( $this->equalTo( CURLINFO_HTTP_CODE ) );
+
+ new BadHttpResponseException( $this->httpRequest );
+ }
+
+ public function testLastError() {
+
+ $this->httpRequest->expects( $this->once() )
+ ->method( 'getLastErrorCode' )
+ ->will( $this->returnValue( 42 ) );
+
+ $this->httpRequest->expects( $this->once() )
+ ->method( 'getLastError' );
+
+ new BadHttpResponseException( $this->httpRequest );
+ }
+
+ public function testForCurlRequest() {
+
+ $curlRequest = new CurlRequest( curl_init( null ) );
+ $curlRequest->setOption( CURLOPT_RETURNTRANSFER, true );
+
+ $curlRequest->execute();
+
+ $e = new BadHttpResponseException( $curlRequest );
+
+ $this->assertContains(
+ 'error 3',
+ $e->getMessage()
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/Exception/HttpConnectionExceptionTest.php b/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/Exception/HttpConnectionExceptionTest.php
new file mode 100644
index 00000000..7da630f6
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/Exception/HttpConnectionExceptionTest.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Onoi\HttpRequest\Tests\Exception;
+
+use Onoi\HttpRequest\CurlRequest;
+use Onoi\HttpRequest\Exception\HttpConnectionException;
+
+/**
+ * @covers \Onoi\HttpRequest\Exception\HttpConnectionException
+ * @group onoi-http-request
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class HttpConnectionExceptionTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\HttpRequest\Exception\HttpConnectionException',
+ new HttpConnectionException()
+ );
+ }
+
+ public function testErrorMessage() {
+
+ $e = new HttpConnectionException( 'foo', 42 );
+
+ $this->assertContains(
+ 'foo',
+ $e->getMessage()
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/HttpRequestFactoryTest.php b/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/HttpRequestFactoryTest.php
new file mode 100644
index 00000000..9fe4767e
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/HttpRequestFactoryTest.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Onoi\HttpRequest\Tests;
+
+use Onoi\HttpRequest\HttpRequestFactory;
+
+/**
+ * @covers \Onoi\HttpRequest\HttpRequestFactory
+ * @group onoi-http-request
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class HttpRequestFactoryTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $instance = new HttpRequestFactory();
+
+ $this->assertInstanceOf(
+ '\Onoi\HttpRequest\HttpRequestFactory',
+ $instance
+ );
+ }
+
+ public function testCanConstructNullRequest() {
+
+ $instance = new HttpRequestFactory();
+
+ $this->assertInstanceOf(
+ '\Onoi\HttpRequest\NullRequest',
+ $instance->newNullRequest()
+ );
+ }
+
+ public function testCanConstructCurlRequest() {
+
+ $instance = new HttpRequestFactory();
+
+ $this->assertInstanceOf(
+ '\Onoi\HttpRequest\CurlRequest',
+ $instance->newCurlRequest()
+ );
+ }
+
+ public function testCanConstructCachedCurlRequest() {
+
+ $cache = $this->getMockBuilder( '\Onoi\Cache\Cache' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $instance = new HttpRequestFactory( $cache );
+
+ $this->assertInstanceOf(
+ '\Onoi\HttpRequest\CachedCurlRequest',
+ $instance->newCachedCurlRequest()
+ );
+ }
+
+ public function testCanConstructMultiCurlRequest() {
+
+ $instance = new HttpRequestFactory();
+
+ $this->assertInstanceOf(
+ '\Onoi\HttpRequest\MultiCurlRequest',
+ $instance->newMultiCurlRequest()
+ );
+ }
+
+ public function testCanConstructSocketRequest() {
+
+ $instance = new HttpRequestFactory();
+
+ $this->assertInstanceOf(
+ '\Onoi\HttpRequest\SocketRequest',
+ $instance->newSocketRequest()
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/MockHttpStreamWrapper.php b/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/MockHttpStreamWrapper.php
new file mode 100644
index 00000000..63082b0e
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/MockHttpStreamWrapper.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace Onoi\HttpRequest\Tests;
+
+/**
+ * @codeCoverageIgnore
+ * @see https://gist.github.com/ukolka/8448362
+ */
+class MockHttpStreamWrapper {
+
+ public static $mockBodyData = '';
+ public static $mockResponseCode = 'HTTP/1.1 200 OK';
+
+ public $context;
+ public $position = 0;
+ public $bodyData = 'test data';
+ public $responseCode = '';
+
+ protected $streamArray = array();
+ protected $options = array();
+ protected $isOpen = false;
+
+ /**
+ * StreamWrapper::stream_open
+ */
+ public function stream_open( $path, $mode, $options, &$opened_path ) {
+ $this->bodyData = self::$mockBodyData;
+ $this->responseCode = self::$mockResponseCode;
+ array_push( $this->streamArray, self::$mockResponseCode );
+ $this->isOpen = true;
+ return true;
+ }
+
+ /**
+ * StreamWrapper::stream_write
+ */
+ public function stream_write( $data ) {
+
+ if ( $this->isOpen ) {
+ return strlen( $data );
+ }
+
+ return 0;
+ }
+
+ /**
+ * StreamWrapper::stream_read
+ */
+ public function stream_read( $count ) {
+
+ if ( $this->position > strlen($this->bodyData)) {
+ return false;
+ }
+
+ $result = substr( $this->bodyData, $this->position, $count );
+ $this->position += $count;
+ return $result;
+ }
+
+ /**
+ * StreamWrapper::stream_eof
+ */
+ public function stream_eof() {
+ return $this->position >= strlen($this->bodyData);
+ }
+
+ /**
+ * StreamWrapper::stream_set_option
+ */
+ public function stream_set_option( $option, $arg1, $arg2 ) {
+ $this->options[$option] = array();
+ }
+
+ /**
+ * StreamWrapper::stream_stat
+ */
+ public function stream_stat() {
+ return array( 'wrapper_data' => array( 'test' ) );
+ }
+
+ /**
+ * StreamWrapper::stream_tell
+ */
+ public function stream_tell() {
+ return $this->position;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/MockedHttpStreamSocketRequestTest.php b/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/MockedHttpStreamSocketRequestTest.php
new file mode 100644
index 00000000..3174739a
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/MockedHttpStreamSocketRequestTest.php
@@ -0,0 +1,153 @@
+<?php
+
+namespace Onoi\HttpRequest\Tests;
+
+use Onoi\HttpRequest\SocketRequest;
+
+/**
+ * @covers \Onoi\HttpRequest\SocketRequest
+ * @group onoi-http-request
+ *
+ * @license GNU GPL v2+
+ * @since 1.2
+ *
+ * @author mwjames
+ */
+class MockedHttpStreamSocketRequestTest extends \PHPUnit_Framework_TestCase {
+
+ public function setUp() {
+ parent::setUp();
+
+ stream_wrapper_unregister( 'http' );
+ $return = stream_wrapper_register(
+ 'http',
+ 'Onoi\HttpRequest\Tests\MockHttpStreamWrapper'
+ );
+
+ if ( !$return ) {
+ $this->markTestSkipped( 'Skip test due to a failed stream wrapper protocol registration' );
+ }
+ }
+
+ public function getMockStream( $data, $code = 'HTTP/1.1 200 OK' ) {
+ MockHttpStreamWrapper::$mockBodyData = $data;
+ MockHttpStreamWrapper::$mockResponseCode = $code;
+
+ $context = stream_context_create(
+ array(
+ 'http' => array(
+ 'method' => 'GET'
+ )
+ )
+ );
+
+ return fopen( 'http://example.com', 'r', false, $context );
+ }
+
+ public function tearDown() {
+ stream_wrapper_restore('http');
+ }
+
+ /**
+ * @dataProvider locationProvider
+ */
+ public function testFollowLocation( $url, $urlComponent, $followLocation, $expectedUrl ) {
+
+ $instance = $this->getMockBuilder( '\Onoi\HttpRequest\SocketRequest' )
+ ->disableOriginalConstructor()
+ ->setMethods( array( 'getResourceFromSocketClient' ) )
+ ->getMock();
+
+ $instance->expects( $this->once() )
+ ->method( 'getResourceFromSocketClient' )
+ ->with(
+ $this->equalTo( $urlComponent ),
+ $this->anything() )
+ ->will( $this->returnValue(
+ $this->getMockStream( "HTTP/1.1 301 Moved \nLocation: " . $followLocation ) ) );
+
+ $instance->setOption( ONOI_HTTP_REQUEST_URL, $url );
+ $instance->setOption( ONOI_HTTP_REQUEST_FOLLOWLOCATION, true );
+
+ $instance->ping();
+
+ $this->assertEquals(
+ $expectedUrl,
+ $instance->getOption( ONOI_HTTP_REQUEST_URL )
+ );
+ }
+
+ public function testToReturnInvalidResource() {
+
+ $instance = $this->getMockBuilder( '\Onoi\HttpRequest\SocketRequest' )
+ ->disableOriginalConstructor()
+ ->setMethods( array( 'getResourceFromSocketClient' ) )
+ ->getMock();
+
+ $instance->expects( $this->once() )
+ ->method( 'getResourceFromSocketClient' )
+ ->will( $this->returnValue( false ) );
+
+ $instance->setOption( ONOI_HTTP_REQUEST_URL, 'http://example.com/' );
+
+ $this->assertEquals(
+ false,
+ $instance->execute()
+ );
+ }
+
+ public function locationProvider() {
+
+ $urlComponent = array (
+ 'scheme' => 'http',
+ 'host' => 'example.com',
+ 'port' => 80,
+ 'path' => ''
+ );
+
+ $provider[] = array(
+ 'http://example.com',
+ $urlComponent,
+ 'http://abc.com',
+ 'http://abc.com'
+ );
+
+ $provider[] = array(
+ 'http://example.com',
+ $urlComponent,
+ '/foo',
+ 'http://example.com/foo'
+ );
+
+ $urlComponent = array (
+ 'scheme' => 'https',
+ 'host' => 'tls://example.com',
+ 'port' => 443,
+ 'path' => ''
+ );
+
+ $provider[] = array(
+ 'https://example.com',
+ $urlComponent,
+ '/foo',
+ 'https://example.com/foo'
+ );
+
+ $urlComponent = array (
+ 'scheme' => 'https',
+ 'host' => 'tls://example.com',
+ 'port' => 4443,
+ 'path' => ''
+ );
+
+ $provider[] = array(
+ 'https://example.com:4443',
+ $urlComponent,
+ '/foo',
+ 'https://example.com/foo'
+ );
+
+ return $provider;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/MultiCurlRequestTest.php b/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/MultiCurlRequestTest.php
new file mode 100644
index 00000000..f85698b6
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/MultiCurlRequestTest.php
@@ -0,0 +1,190 @@
+<?php
+
+namespace Onoi\HttpRequest\Tests;
+
+use Onoi\HttpRequest\MultiCurlRequest;
+use Onoi\HttpRequest\CurlRequest;
+
+/**
+ * @covers \Onoi\HttpRequest\MultiCurlRequest
+ * @group onoi-http-request
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class MultiCurlRequestTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $instance = new MultiCurlRequest( curl_multi_init() );
+
+ $this->assertInstanceOf(
+ '\Onoi\HttpRequest\HttpRequest',
+ $instance
+ );
+
+ $this->assertInstanceOf(
+ '\Onoi\HttpRequest\MultiCurlRequest',
+ $instance
+ );
+ }
+
+ public function testWrongResourceTypeThrowsException() {
+ $this->setExpectedException( 'InvalidArgumentException' );
+ new MultiCurlRequest( curl_init() );
+ }
+
+ public function testPingForEmptyHttpRequest() {
+
+ $instance = new MultiCurlRequest( curl_multi_init() );
+
+ $this->assertFalse(
+ $instance->ping()
+ );
+ }
+
+ /**
+ * @dataProvider pingConnectionStateProvider
+ */
+ public function testPingConnectionState( $connectionState ) {
+
+ $instance = new MultiCurlRequest( curl_multi_init() );
+
+ $httpRequest = $this->getMockBuilder( '\Onoi\HttpRequest\CurlRequest' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $httpRequest->expects( $this->once() )
+ ->method( 'ping' )
+ ->will( $this->returnValue( $connectionState ) );
+
+ $instance->addHttpRequest(
+ $httpRequest
+ );
+
+ $this->assertEquals(
+ $connectionState,
+ $instance->ping()
+ );
+ }
+
+ public function testSetGetOption() {
+
+ // https://github.com/facebook/hhvm/issues/5761
+ if ( !defined( 'CURLMOPT_MAXCONNECTS' ) ) {
+ $this->markTestSkipped( "Option is not supported for current PHP version" );
+ }
+
+ $instance = new MultiCurlRequest( curl_multi_init() );
+
+ $instance->setOption(
+ CURLMOPT_MAXCONNECTS,
+ 5
+ );
+
+ $this->assertSame(
+ 5,
+ $instance->getOption( CURLMOPT_MAXCONNECTS )
+ );
+ }
+
+ public function testExecuteForResponse() {
+
+ $instance = new MultiCurlRequest( curl_multi_init() );
+
+ $instance->addHttpRequest(
+ new CurlRequest( curl_init() )
+ );
+
+ $this->assertInternalType(
+ 'array',
+ $instance->execute()
+ );
+
+ $this->assertInternalType(
+ 'string',
+ $instance->getLastError()
+ );
+
+ // http://php.net/manual/en/function.curl-multi-info-read.php
+ $this->assertFalse(
+ $instance->getLastTransferInfo()
+ );
+ }
+
+ public function testDeprecatedSetCallback() {
+
+ $instance = new MultiCurlRequest( curl_multi_init() );
+
+ $instance->addHttpRequest(
+ new CurlRequest( curl_init() )
+ );
+
+ $requestResponse = null;
+
+ $instance->setCallback( function( $requestResponseCompleted ) use ( &$requestResponse ) {
+ $requestResponse = $requestResponseCompleted;
+ } );
+
+ $instance->execute();
+
+ $this->assertRequestResponse( $requestResponse );
+ }
+
+ public function testOnCompletedCallback() {
+
+ $instance = new MultiCurlRequest( curl_multi_init() );
+
+ $instance->addHttpRequest(
+ new CurlRequest( curl_init() )
+ );
+
+ $requestResponse = null;
+
+ $instance->setOption( ONOI_HTTP_REQUEST_ON_COMPLETED_CALLBACK, function( $requestResponseCompleted ) use ( &$requestResponse ) {
+ $requestResponse = $requestResponseCompleted;
+ } );
+
+ $instance->execute();
+
+ $this->assertSame(
+ 0,
+ $instance->getLastErrorCode()
+ );
+
+ $this->assertRequestResponse( $requestResponse );
+ }
+
+ private function assertRequestResponse( $requestResponse ) {
+
+ $this->assertInstanceOf(
+ '\Onoi\HttpRequest\RequestResponse',
+ $requestResponse
+ );
+
+ $expectedRequestResponseFields = array(
+ 'contents',
+ 'info'
+ );
+
+ foreach ( $expectedRequestResponseFields as $field ) {
+ $this->assertTrue( $requestResponse->has( $field ) );
+ }
+ }
+
+ public function pingConnectionStateProvider() {
+
+ $provider[] = array(
+ true
+ );
+
+ $provider[] = array(
+ false
+ );
+
+ return $provider;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/NullRequestTest.php b/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/NullRequestTest.php
new file mode 100644
index 00000000..03c3065f
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/NullRequestTest.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace Onoi\HttpRequest\Tests;
+
+use Onoi\HttpRequest\NullRequest;
+
+/**
+ * @covers \Onoi\HttpRequest\NullRequest
+ * @group onoi-http-request
+ *
+ * @license GNU GPL v2+
+ * @since 1.0
+ *
+ * @author mwjames
+ */
+class NullRequestTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $instance = new NullRequest();
+
+ $this->assertInstanceOf(
+ '\Onoi\HttpRequest\NullRequest',
+ $instance
+ );
+
+ $this->assertInstanceOf(
+ '\Onoi\HttpRequest\HttpRequest',
+ $instance
+ );
+ }
+
+ public function testNull() {
+
+ $instance = new NullRequest();
+
+ $this->assertInternalType(
+ 'boolean',
+ $instance->ping()
+ );
+
+ $this->assertInternalType(
+ 'null',
+ $instance->setOption( 'foo', 42 )
+ );
+
+ $this->assertInternalType(
+ 'null',
+ $instance->getOption( 'foo' )
+ );
+
+ $this->assertInternalType(
+ 'null',
+ $instance->getLastTransferInfo()
+ );
+
+ $this->assertInternalType(
+ 'string',
+ $instance->getLastError()
+ );
+
+ $this->assertInternalType(
+ 'integer',
+ $instance->getLastErrorCode()
+ );
+
+ $this->assertInternalType(
+ 'null',
+ $instance->execute()
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/RequestResponseTest.php b/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/RequestResponseTest.php
new file mode 100644
index 00000000..99b616ab
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/RequestResponseTest.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Onoi\HttpRequest\Tests;
+
+use Onoi\HttpRequest\RequestResponse;
+
+/**
+ * @covers \Onoi\HttpRequest\RequestResponse
+ * @group onoi-http-request
+ *
+ * @license GNU GPL v2+
+ * @since 1.1
+ *
+ * @author mwjames
+ */
+class RequestResponseTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\HttpRequest\RequestResponse',
+ new RequestResponse()
+ );
+ }
+
+ public function testSetGetValue() {
+
+ $instance = new RequestResponse();
+
+ $this->assertFalse(
+ $instance->has( 'Foo' )
+ );
+
+ $instance->set( 'Foo', 42 );
+
+ $this->assertEquals(
+ 42,
+ $instance->get( 'Foo' )
+ );
+
+ $this->assertEquals(
+ array( 'Foo' => 42 ),
+ $instance->getList()
+ );
+
+ $this->assertInternalType(
+ 'string',
+ $instance->asJsonString()
+ );
+ }
+
+ public function testUnregisteredKeyThrowsException() {
+
+ $instance = new RequestResponse();
+
+ $this->setExpectedException( 'InvalidArgumentException' );
+ $instance->get( 'Foo' );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/SocketRequestTest.php b/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/SocketRequestTest.php
new file mode 100644
index 00000000..1fe34a00
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/tests/phpunit/Unit/SocketRequestTest.php
@@ -0,0 +1,224 @@
+<?php
+
+namespace Onoi\HttpRequest\Tests;
+
+use Onoi\HttpRequest\SocketRequest;
+
+/**
+ * @covers \Onoi\HttpRequest\SocketRequest
+ * @group onoi-http-request
+ *
+ * @license GNU GPL v2+
+ * @since 1.1
+ *
+ * @author mwjames
+ */
+class SocketRequestTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $instance = new SocketRequest();
+
+ $this->assertInstanceOf(
+ '\Onoi\HttpRequest\SocketRequest',
+ $instance
+ );
+
+ $this->assertInstanceOf(
+ '\Onoi\HttpRequest\HttpRequest',
+ $instance
+ );
+ }
+
+ public function testPing() {
+
+ $instance = new SocketRequest();
+
+ $this->assertFalse(
+ $instance->ping()
+ );
+
+ $instance->setOption( ONOI_HTTP_REQUEST_URL, 'http://example.org' );
+
+ $this->assertInternalType(
+ 'boolean',
+ $instance->ping()
+ );
+ }
+
+ public function testExecute() {
+
+ $instance = new SocketRequest();
+ $instance->setOption( ONOI_HTTP_REQUEST_CONNECTION_TIMEOUT, 1 );
+ $instance->setOption( ONOI_HTTP_REQUEST_URL, 'http://localhost:8888' );
+
+ $this->assertInternalType(
+ 'boolean',
+ $instance->execute()
+ );
+ }
+
+ public function testGetLastError() {
+
+ $instance = new SocketRequest();
+ $instance->setOption( ONOI_HTTP_REQUEST_CONNECTION_TIMEOUT, 1 );
+
+ $instance->execute();
+
+ $this->assertInternalType(
+ 'string',
+ $instance->getLastError()
+ );
+ }
+
+ public function testGetLastErrorCode() {
+
+ $instance = new SocketRequest();
+
+ $this->assertInternalType(
+ 'integer',
+ $instance->getLastErrorCode()
+ );
+ }
+
+ public function testGetLastTransferInfo() {
+
+ $instance = new SocketRequest();
+
+ $this->assertInternalType(
+ 'string',
+ $instance->getLastTransferInfo()
+ );
+ }
+
+ public function testCallbackOnRequestNotCompleted() {
+
+ $instance = new SocketRequest();
+ $instance->setOption( ONOI_HTTP_REQUEST_CONNECTION_TIMEOUT, 0.1 );
+
+ $requestResponse = null;
+
+ $instance->setOption( ONOI_HTTP_REQUEST_ON_FAILED_CALLBACK, function( $requestResponseFailed ) use ( &$requestResponse ) {
+ $requestResponse = $requestResponseFailed;
+ } );
+
+ $instance->execute();
+
+ $this->assertRequestResponse( $requestResponse );
+ }
+
+ public function testCallbackOnRequestCompleted() {
+
+ $url = 'http://localhost:8080/';
+
+ $instance = new SocketRequest( $url );
+ $instance->setOption( ONOI_HTTP_REQUEST_CONNECTION_TIMEOUT, 2 );
+ $instance->setOption( ONOI_HTTP_REQUEST_METHOD, 'HEAD' );
+
+ if ( !$instance->ping() ) {
+ $this->markTestSkipped( "Skip test because {$url} was not reachable" );
+ }
+
+ $requestResponse = null;
+
+ $instance->setOption( ONOI_HTTP_REQUEST_ON_COMPLETED_CALLBACK, function( $requestResponseCompleted ) use ( &$requestResponse ) {
+ $requestResponse = $requestResponseCompleted;
+ } );
+
+ $instance->execute();
+
+ $this->assertRequestResponse( $requestResponse );
+ }
+
+ public function testTryInvalidCallbackOnRequestCompleted() {
+
+ $instance = new SocketRequest();
+
+ $instance->setOption( ONOI_HTTP_REQUEST_CONNECTION_TIMEOUT, 0.1 );
+ $instance->setOption( ONOI_HTTP_REQUEST_ON_COMPLETED_CALLBACK, 'foo' );
+
+ $this->assertFalse(
+ $instance->execute()
+ );
+ }
+
+ public function testCallbackOnRequestFailed() {
+
+ $instance = new SocketRequest();
+ $instance->setOption( ONOI_HTTP_REQUEST_CONNECTION_TIMEOUT, 0.1 );
+
+ $requestResponse = null;
+
+ $instance->setOption( ONOI_HTTP_REQUEST_ON_FAILED_CALLBACK, function( $requestResponseFailed ) use ( &$requestResponse ) {
+ $requestResponse = $requestResponseFailed;
+ } );
+
+ $instance->execute();
+
+ $this->assertRequestResponse( $requestResponse );
+ }
+
+ public function testCallbackOnRequestNotAccepted() {
+
+ $instance = new SocketRequest();
+ $instance->setOption( ONOI_HTTP_REQUEST_CONNECTION_TIMEOUT, 2 );
+
+ $instance->ping();
+
+ $requestResponse = null;
+
+ $instance->setOption( ONOI_HTTP_REQUEST_ON_FAILED_CALLBACK, function( $requestResponseFailed ) use ( &$requestResponse ) {
+ $requestResponse = $requestResponseFailed;
+ } );
+
+ $instance->execute();
+ $this->assertRequestResponse( $requestResponse );
+ }
+
+ private function assertRequestResponse( $requestResponse ) {
+
+ $this->assertInstanceOf(
+ '\Onoi\HttpRequest\RequestResponse',
+ $requestResponse
+ );
+
+ $expectedRequestResponseFields = array(
+ 'wasCompleted',
+ 'responseMessage',
+ 'host',
+ 'port',
+ 'path',
+ 'connectionFailure',
+ 'requestProcTime'
+ );
+
+ foreach ( $expectedRequestResponseFields as $field ) {
+ $this->assertTrue( $requestResponse->has( $field ), 'Failed for ' . $field );
+ }
+ }
+
+ public function testDefinedConstants() {
+
+ $constants = array(
+ 'ONOI_HTTP_REQUEST_ON_FAILED_CALLBACK',
+ 'ONOI_HTTP_REQUEST_ON_COMPLETED_CALLBACK',
+ 'ONOI_HTTP_REQUEST_SOCKET_CLIENT_FLAGS',
+ 'ONOI_HTTP_REQUEST_URL',
+ 'ONOI_HTTP_REQUEST_CONNECTION_TIMEOUT',
+ 'ONOI_HTTP_REQUEST_CONNECTION_FAILURE_REPEAT',
+ 'ONOI_HTTP_REQUEST_CONTENT',
+ 'ONOI_HTTP_REQUEST_CONTENT_TYPE',
+ 'ONOI_HTTP_REQUEST_METHOD',
+ 'ONOI_HTTP_REQUEST_PROTOCOL_VERSION',
+ 'ONOI_HTTP_REQUEST_SSL_VERIFYPEER',
+ 'ONOI_HTTP_REQUEST_FOLLOWLOCATION'
+ );
+
+ $instance = new SocketRequest();
+
+ foreach ( $constants as $constant ) {
+ $this->assertTrue( defined( $constant ) );
+ }
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/http-request/tests/travis/run-tests.sh b/www/wiki/vendor/onoi/http-request/tests/travis/run-tests.sh
new file mode 100644
index 00000000..8f032fb9
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/tests/travis/run-tests.sh
@@ -0,0 +1,15 @@
+#! /bin/bash
+set -ex
+
+BASE_PATH=$(pwd)
+
+if [ "$TYPE" == "coverage" ]
+then
+ composer install
+ composer validate --no-interaction
+ composer phpunit -- --coverage-clover $BASE_PATH/build/coverage.clover
+else
+ composer install
+ composer validate --no-interaction
+ composer phpunit
+fi
diff --git a/www/wiki/vendor/onoi/http-request/tests/travis/upload-coverage-report.sh b/www/wiki/vendor/onoi/http-request/tests/travis/upload-coverage-report.sh
new file mode 100644
index 00000000..aff95cf7
--- /dev/null
+++ b/www/wiki/vendor/onoi/http-request/tests/travis/upload-coverage-report.sh
@@ -0,0 +1,10 @@
+#! /bin/bash
+set -ex
+
+BASE_PATH=$(pwd)
+
+if [ "$TYPE" == "coverage" ]
+then
+ wget https://scrutinizer-ci.com/ocular.phar
+ php ocular.phar code-coverage:upload --format=php-clover $BASE_PATH/build/coverage.clover
+fi \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/message-reporter/LICENSE b/www/wiki/vendor/onoi/message-reporter/LICENSE
new file mode 100644
index 00000000..d6a93266
--- /dev/null
+++ b/www/wiki/vendor/onoi/message-reporter/LICENSE
@@ -0,0 +1,340 @@
+GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ {description}
+ Copyright (C) {year} {fullname}
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ {signature of Ty Coon}, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
+
diff --git a/www/wiki/vendor/onoi/message-reporter/Makefile b/www/wiki/vendor/onoi/message-reporter/Makefile
new file mode 100644
index 00000000..f1b427ca
--- /dev/null
+++ b/www/wiki/vendor/onoi/message-reporter/Makefile
@@ -0,0 +1,22 @@
+.PHONY: ci test phpunit cs stan covers
+
+DEFAULT_GOAL := ci
+
+ci: test cs
+
+test: covers phpunit
+
+cs: phpcs stan
+
+phpunit:
+ ./vendor/bin/phpunit
+
+phpcs:
+ ./vendor/bin/phpcs -p -s
+
+stan:
+ ./vendor/bin/phpstan analyse --level=1 --no-progress src/ tests/
+
+covers:
+ ./vendor/bin/covers-validator
+
diff --git a/www/wiki/vendor/onoi/message-reporter/README.md b/www/wiki/vendor/onoi/message-reporter/README.md
new file mode 100644
index 00000000..d8c5fcbc
--- /dev/null
+++ b/www/wiki/vendor/onoi/message-reporter/README.md
@@ -0,0 +1,147 @@
+# Message reporter
+
+[![Build Status](https://secure.travis-ci.org/onoi/message-reporter.svg?branch=master)](http://travis-ci.org/onoi/message-reporter)
+[![Code Coverage](https://scrutinizer-ci.com/g/onoi/message-reporter/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/onoi/message-reporter/?branch=master)
+[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/onoi/message-reporter/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/onoi/message-reporter/?branch=master)
+[![Latest Stable Version](https://poser.pugx.org/onoi/message-reporter/version.png)](https://packagist.org/packages/onoi/message-reporter)
+[![Packagist download count](https://poser.pugx.org/onoi/message-reporter/d/total.png)](https://packagist.org/packages/onoi/message-reporter)
+
+An interface to report and relay arbitrary messages to registered handlers. This was part of
+the [Semantic MediaWiki][smw] code base and is now being deployed as independent library.
+
+## Requirements
+
+PHP 5.6.99 or later
+
+## Installation
+
+The recommended installation method for this library is to add it as dependency to your [composer.json][composer].
+
+```json
+{
+ "require": {
+ "onoi/message-reporter": "~1.3"
+ }
+}
+```
+
+## Usage
+
+The message reporter specifies `MessageReporter` and `MessageReporterAware` as an interface for all interactions with a set of supporting classes:
+- `MessageReporterFactory`
+- `ObservableMessageReporter`
+- `NullMessageReporter`
+- `SpyMessageReporter`
+- `CallbackMessageReporter`
+
+```php
+use Onoi\MessageReporter\MessageReporterFactory;
+use Onoi\MessageReporter\MessageReporterAware;
+use Onoi\MessageReporter\MessageReporterAwareTrait;
+use Onoi\MessageReporter\MessageReporter;
+
+class Bar implements MessageReporterAware {
+
+ use MessageReporterAwareTrait;
+
+ public function __construct() {
+ $this->messageReporter = MessageReporterFactory::getInstance()->newNullMessageReporter();
+ }
+
+ public function doSomething() {
+ $this->messageReporter->reportMessage( 'Doing ...' );
+ }
+}
+```
+
+```php
+use Onoi\MessageReporter\MessageReporterFactory;
+use Onoi\MessageReporter\MessageReporter;
+
+class Foo implements MessageReporter {
+
+ public function reportMessage( $message ) {
+ // output
+ }
+}
+
+$foo = new Foo();
+
+$messageReporterFactory = new MessageReporterFactory();
+
+$observableMessageReporter = $messageReporterFactory->newObservableMessageReporter();
+$observableMessageReporter->registerReporterCallback( array( $foo, 'reportMessage' ) );
+
+or
+
+// If the class implements the MessageReporter
+$observableMessageReporter->registerMessageReporter( $foo );
+
+$bar = new Bar();
+$bar->setMessageReporter( $observableMessageReporter );
+```
+
+## Contribution and support
+
+If you want to contribute work to the project please subscribe to the
+developers mailing list and have a look at the [contribution guidelinee](/CONTRIBUTING.md). A list of people who have made contributions in the past can be found [here][contributors].
+
+* [File an issue](https://github.com/onoi/message-reporter/issues)
+* [Submit a pull request](https://github.com/onoi/message-reporter/pulls)
+
+## Development
+
+Start by installing the project dependencies by executing
+
+ composer update
+
+You can run the tests by executing
+
+ make test
+
+You can run the style checks by executing
+
+ make cs
+
+To run all CI checks, execute
+
+ make ci
+
+You can also invoke PHPUnit directly to pass it arguments, as follows
+
+ vendor/bin/phpunit --filter SomeClassNameOrFilter
+
+## Release notes
+
+* 1.4.1 (2019-04-10)
+- Added `.gitattributes`
+
+* 1.4.0 (2019-04-08)
+ - Added `CallbackMessageReporter`
+ - Changed minimum PHP version to 5.6.99
+
+* 1.3.0 (2017-11-05)
+ - Added `MessageReporterAwareTrait`
+
+* 1.2.0 (2016-08-02)
+ - Added `MessageReporterAware` and `SpyMessageReporter`
+
+* 1.1.0 (2016-04-13)
+ - `ObservableMessageReporter::registerReporterCallback` to register only callable handlers
+
+* 1.0.0 (2015-01-24)
+ - Initial release
+ - `MessageReporterFactory`
+ - `ObservableMessageReporter`
+ - `NullMessageReporter`
+ - `MessageReporter`
+
+## License
+
+[GNU General Public License 2.0 or later][license].
+
+[composer]: https://getcomposer.org/
+[contributors]: https://github.com/onoi/message-reporter/graphs/contributors
+[license]: https://www.gnu.org/copyleft/gpl.html
+[travis]: https://travis-ci.org/onoi/message-reporter
+[smw]: https://github.com/SemanticMediaWiki/SemanticMediaWiki/
diff --git a/www/wiki/vendor/onoi/message-reporter/src/CallbackMessageReporter.php b/www/wiki/vendor/onoi/message-reporter/src/CallbackMessageReporter.php
new file mode 100644
index 00000000..03da76bb
--- /dev/null
+++ b/www/wiki/vendor/onoi/message-reporter/src/CallbackMessageReporter.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Onoi\MessageReporter;
+
+/**
+ * @since 1.4.0
+ */
+class CallbackMessageReporter implements MessageReporter {
+
+ private $callback;
+
+ public function __construct( callable $callback ) {
+ $this->callback = $callback;
+ }
+
+ public function reportMessage( $message ) {
+ call_user_func( $this->callback, $message );
+ }
+
+} \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/message-reporter/src/MessageReporter.php b/www/wiki/vendor/onoi/message-reporter/src/MessageReporter.php
new file mode 100644
index 00000000..bc356626
--- /dev/null
+++ b/www/wiki/vendor/onoi/message-reporter/src/MessageReporter.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Onoi\MessageReporter;
+
+/**
+ * Interface for objects that can report messages
+ *
+ * @since 1.0
+ *
+ * @license GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+interface MessageReporter {
+
+ /**
+ * Report the provided message
+ *
+ * @param string $message
+ */
+ public function reportMessage( $message );
+
+}
diff --git a/www/wiki/vendor/onoi/message-reporter/src/MessageReporterAware.php b/www/wiki/vendor/onoi/message-reporter/src/MessageReporterAware.php
new file mode 100644
index 00000000..2fe73f16
--- /dev/null
+++ b/www/wiki/vendor/onoi/message-reporter/src/MessageReporterAware.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Onoi\MessageReporter;
+
+/**
+ * @since 1.2
+ *
+ * @license GNU GPL v2+
+ * @author mwjames
+ */
+interface MessageReporterAware {
+
+ /**
+ * Allows to inject a MessageReporter and make an object aware of its
+ * existence.
+ */
+ public function setMessageReporter( MessageReporter $messageReporter );
+
+}
diff --git a/www/wiki/vendor/onoi/message-reporter/src/MessageReporterAwareTrait.php b/www/wiki/vendor/onoi/message-reporter/src/MessageReporterAwareTrait.php
new file mode 100644
index 00000000..32bf9d0f
--- /dev/null
+++ b/www/wiki/vendor/onoi/message-reporter/src/MessageReporterAwareTrait.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Onoi\MessageReporter;
+
+/**
+ * @since 1.3
+ *
+ * @license GNU GPL v2+
+ * @author mwjames
+ */
+trait MessageReporterAwareTrait {
+
+ /**
+ * @var MessageReporter
+ */
+ protected $messageReporter;
+
+ public function setMessageReporter( MessageReporter $messageReporter ) {
+ $this->messageReporter = $messageReporter;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/message-reporter/src/MessageReporterFactory.php b/www/wiki/vendor/onoi/message-reporter/src/MessageReporterFactory.php
new file mode 100644
index 00000000..8363bb42
--- /dev/null
+++ b/www/wiki/vendor/onoi/message-reporter/src/MessageReporterFactory.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Onoi\MessageReporter;
+
+/**
+ * @since 1.0
+ *
+ * @license GNU GPL v2+
+ * @author mwjames
+ */
+class MessageReporterFactory {
+
+ /**
+ * @var MessageReporterFactory
+ */
+ private static $instance = null;
+
+ /**
+ * @since 1.0
+ * @return MessageReporterFactory
+ */
+ public static function getInstance() {
+
+ if ( self::$instance === null ) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * @since 1.0
+ */
+ public static function clear() {
+ self::$instance = null;
+ }
+
+ /**
+ * @since 1.0
+ * @return NullMessageReporter
+ */
+ public function newNullMessageReporter() {
+ return new NullMessageReporter();
+ }
+
+ /**
+ * @since 1.0
+ * @return ObservableMessageReporter
+ */
+ public function newObservableMessageReporter() {
+ return new ObservableMessageReporter();
+ }
+
+ /**
+ * @since 1.2
+ * @return SpyMessageReporter
+ */
+ public function newSpyMessageReporter() {
+ return new SpyMessageReporter();
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/message-reporter/src/NullMessageReporter.php b/www/wiki/vendor/onoi/message-reporter/src/NullMessageReporter.php
new file mode 100644
index 00000000..f2cbaed5
--- /dev/null
+++ b/www/wiki/vendor/onoi/message-reporter/src/NullMessageReporter.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Onoi\MessageReporter;
+
+/**
+ * @since 1.0
+ *
+ * @license GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+class NullMessageReporter implements MessageReporter {
+
+ /**
+ * @since 1.0
+ *
+ * {@inheritDoc}
+ */
+ public function reportMessage( $message ) { }
+
+}
diff --git a/www/wiki/vendor/onoi/message-reporter/src/ObservableMessageReporter.php b/www/wiki/vendor/onoi/message-reporter/src/ObservableMessageReporter.php
new file mode 100644
index 00000000..ca118a6b
--- /dev/null
+++ b/www/wiki/vendor/onoi/message-reporter/src/ObservableMessageReporter.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace Onoi\MessageReporter;
+
+/**
+ * Message reporter that reports messages by passing them along to all
+ * registered handlers.
+ *
+ * @since 1.0
+ *
+ * @license GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+class ObservableMessageReporter implements MessageReporter {
+
+ /**
+ * @since 1.0
+ *
+ * @var MessageReporter[]
+ */
+ protected $reporters = [];
+
+ /**
+ * @since 1.0
+ *
+ * @var callable[]
+ */
+ protected $callbacks = [];
+
+ /**
+ * @since 1.0
+ *
+ * @param string $message
+ *
+ * @see MessageReporter::report
+ */
+ public function reportMessage( $message ) {
+ foreach ( $this->reporters as $reporter ) {
+ $reporter->reportMessage( $message );
+ }
+
+ foreach ( $this->callbacks as $callback ) {
+ call_user_func( $callback, $message );
+ }
+ }
+
+ /**
+ * Register a new message reporter.
+ *
+ * @since 1.0
+ *
+ * @param MessageReporter $reporter
+ *
+ */
+ public function registerMessageReporter( MessageReporter $reporter ) {
+ $this->reporters[] = $reporter;
+ }
+
+ /**
+ * Register a callback as message reporter.
+ *
+ * @since 1.0
+ *
+ * @param callable $handler |null
+ *
+ */
+ public function registerReporterCallback( $handler = null ) {
+ if ( is_callable( $handler ) ) {
+ $this->callbacks[] = $handler;
+ }
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/message-reporter/src/SpyMessageReporter.php b/www/wiki/vendor/onoi/message-reporter/src/SpyMessageReporter.php
new file mode 100644
index 00000000..da650a79
--- /dev/null
+++ b/www/wiki/vendor/onoi/message-reporter/src/SpyMessageReporter.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace Onoi\MessageReporter;
+
+/**
+ * @since 1.2
+ *
+ * @license GNU GPL v2+
+ * @author mwjames
+ */
+class SpyMessageReporter implements MessageReporter {
+
+ /**
+ * @var array
+ */
+ private $messages = [];
+
+ /**
+ * @since 1.2
+ *
+ * {@inheritDoc}
+ */
+ public function reportMessage( $message ) {
+ $this->messages[] = $message;
+ }
+
+ /**
+ * @since 1.2
+ *
+ * @return array
+ */
+ public function getMessages() {
+ return $this->messages;
+ }
+
+ /**
+ * @since 1.2
+ *
+ * @return string
+ */
+ public function getMessagesAsString() {
+ return implode( ', ', $this->messages );
+ }
+
+ /**
+ * @since 1.2
+ */
+ public function clearMessages() {
+ $this->messages = [];
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/shared-resources/.gitignore b/www/wiki/vendor/onoi/shared-resources/.gitignore
new file mode 100644
index 00000000..0745b001
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/.gitignore
@@ -0,0 +1,11 @@
+*~
+*.kate-swp
+
+vendor/
+extensions/
+
+composer.phar
+composer.lock
+
+!.*
+.idea/ \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/shared-resources/LICENSE b/www/wiki/vendor/onoi/shared-resources/LICENSE
new file mode 100644
index 00000000..d6a93266
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/LICENSE
@@ -0,0 +1,340 @@
+GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ {description}
+ Copyright (C) {year} {fullname}
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ {signature of Ty Coon}, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
+
diff --git a/www/wiki/vendor/onoi/shared-resources/README.md b/www/wiki/vendor/onoi/shared-resources/README.md
new file mode 100644
index 00000000..bafd702a
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/README.md
@@ -0,0 +1,115 @@
+# shared-resources
+
+[![Latest Stable Version](https://poser.pugx.org/onoi/shared-resources/version.png)](https://packagist.org/packages/onoi/shared-resources)
+[![Packagist download count](https://poser.pugx.org/onoi/shared-resources/d/total.png)](https://packagist.org/packages/onoi/shared-resources)
+[![Dependency Status](https://www.versioneye.com/php/onoi:shared-resources/badge.png)](https://www.versioneye.com/php/onoi:shared-resources)
+
+Some resources in this package were part of the [Semantic MediaWiki][smw] code base and are
+now deployed as separate library so that [ResourceLoader][rl] modules can be used independently.
+
+## Requirements
+
+PHP 5.3 / HHVM 3.3 or later
+
+## Installation
+
+The recommended installation method for this library is to add
+the dependency to your [composer.json][composer].
+
+```json
+{
+ "require": {
+ "onoi/shared-resources": "~0.4"
+ }
+}
+```
+
+## Usage
+
+Define a dependency or load a specifc resource module.
+
+```
+$GLOBALS['wgResourceModules']['ext.something'] = array(
+ ...
+ 'dependencies' => array(
+ 'onoi.md5',
+ 'onoi.blobstore'
+ );
+);
+```
+```
+mw.loader.using( 'onoi.md5' ).done( function () {
+ // do something
+} );
+```
+
+### Resources
+
+- `onoi.md5` (1.1.0)
+- `onoi.blockUI` (2.70)
+- `onoi.rangeslider` (2.1.2)
+- `onoi.localForage` (1.4.2)
+- `onoi.async` (1.0)
+- `onoi.qtip` (3.0.3)
+- `onoi.jstorage` (0.4.12)
+- `onoi.blobstore` (0.1)
+- `onoi.clipboard` (1.5.15)
+- `onoi.dataTables` (`jquery.dataTables` 1.10.15)
+- `onoi.bootstrap.tab` (v4.0.0-alpha.6)
+
+## Contribution and support
+
+If you want to contribute work to the project please subscribe to the
+developers mailing list and have a look at the [contribution guidelinee](/CONTRIBUTING.md).
+A list of people who have made contributions in the past can be found [here][contributors].
+
+* [File an issue](https://github.com/onoi/shared-resources/issues)
+* [Submit a pull request](https://github.com/onoi/shared-resources/pulls)
+
+## Release notes
+
+- 0.4.1 (2017-04-22)
+ - Replaced `jquery.dataTables` 1.10.13 with 1.10.15
+
+- 0.4 (2017-04-15)
+ - Addedd `onoi.dataTables` using `jquery.dataTables` 1.10.13
+ - Addedd `onoi.highlight` using `jquery.highlight`
+ - Addedd `onoi.bootstrap.tab` using Bootstrap (v4.0.0-alpha.6): tab.js
+ - Replaced `clipboard.js` v1.5.15 with v1.6.1
+ - Replaced `localForage.js` 1.4.2 with 1.5.0
+
+- 0.3 (2016-12-17)
+ - Addedd `onoi.clipboard` 1.5.15
+
+- 0.2 (2016-05-25)
+ - Addedd `onoi.qtip` 3.0.3
+ - Replaced `onoi.localForage` 1.4.0 with 1.4.2
+ - Replaced `onoi.md5` 2.3.0 with 1.1.0 as some issues were encountered when loading it as resource
+
+- 0.1 (2016-04-05)
+ - Initial release
+
+## License
+
+[GNU General Public License 2.0 or later][license]. Libraries and third-party
+plug-ins are deployed with their respective published licenses.
+
+- [MD5](https://github.com/blueimp/JavaScript-MD5), MIT license
+- [jquery.blockUI](http://malsup.com/jquery/block/), Dual licensed under the MIT and GPL licenses
+- [ion.rangeSlider](https://github.com/IonDen/ion.rangeSlider), MIT license
+- [Mozilla localForage](https://github.com/mozilla/localForage/releases), Apache License 2.0
+- [jquery.async](http://mess.genezys.net/jquery/jquery.async.php) Dual licensed under the MIT and GPL licenses
+- [jStorage](https://github.com/andris9/jStorage), Unlicense
+- [jquery.qtip](http://qtip2.com/), Dual licensed under the MIT and GPL licenses
+- [jquery.dataTables](https://datatables.net/), MIT licenses
+- [jquery.highlight](http://bartaz.github.io/sandbox.js/jquery.highlight.html/), MIT licenses
+- [clipboard.js](https://github.com/zenorocha/clipboard.js), MIT License
+- [Bootstrap](https://github.com/twbs/bootstrap), MIT licenses
+- `onoi.blobstore` (0.1, GPL 2+)
+
+[composer]: https://getcomposer.org/
+[contributors]: https://github.com/onoi/shared-resources/graphs/contributors
+[license]: https://www.gnu.org/copyleft/gpl.html
+[travis]: https://travis-ci.org/onoi/shared-resources
+[smw]: https://github.com/SemanticMediaWiki/SemanticMediaWiki/
+[rl]: https://www.mediawiki.org/wiki/ResourceLoader
diff --git a/www/wiki/vendor/onoi/shared-resources/Resources.php b/www/wiki/vendor/onoi/shared-resources/Resources.php
new file mode 100644
index 00000000..a3d1307f
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/Resources.php
@@ -0,0 +1,316 @@
+<?php
+
+/**
+ * @codeCoverageIgnore
+ * @since 0.1
+ *
+ * @license GNU GPL v2+
+ * @author mwjames
+ */
+
+if ( defined( 'ONOI_SHARED_RESOURCES_VERSION' ) ) {
+ return 1;
+}
+
+define( 'ONOI_SHARED_RESOURCES_VERSION', true );
+
+if ( defined( 'MEDIAWIKI' ) ) {
+
+ // Core styles
+ $GLOBALS['wgResourceModules']['onoi.qtip.core'] = array(
+ 'localBasePath' => __DIR__ ,
+ 'remoteExtPath' => '../vendor/onoi/shared-resources',
+ 'position' => 'bottom',
+ 'styles' => array(
+ 'res/jquery.qtip/core/jquery.qtip.css'
+ ),
+ 'scripts' => array(
+ 'res/jquery.qtip/core/jquery.qtip.js'
+ ),
+ 'targets' => array(
+ 'mobile',
+ 'desktop'
+ )
+ );
+
+ // Core styles, Basic colour styles, CSS3 styles
+ // Viewport adjustment, SVG support
+ $GLOBALS['wgResourceModules']['onoi.qtip.extended'] = array(
+ 'localBasePath' => __DIR__ ,
+ 'remoteExtPath' => '../vendor/onoi/shared-resources',
+ 'position' => 'bottom',
+ 'styles' => array(
+ 'res/jquery.qtip/extended/jquery.qtip.css'
+ ),
+ 'scripts' => array(
+ 'res/jquery.qtip/extended/jquery.qtip.js'
+ ),
+ 'targets' => array(
+ 'mobile',
+ 'desktop'
+ )
+ );
+
+ $GLOBALS['wgResourceModules']['onoi.qtip'] = array(
+ 'localBasePath' => __DIR__ ,
+ 'remoteExtPath' => '../vendor/onoi/shared-resources',
+ 'position' => 'bottom',
+ 'dependencies' => array(
+ 'onoi.qtip.extended',
+ ),
+ 'targets' => array(
+ 'mobile',
+ 'desktop'
+ )
+ );
+
+ $GLOBALS['wgResourceModules']['onoi.md5'] = array(
+ 'localBasePath' => __DIR__ ,
+ 'remoteExtPath' => '../vendor/onoi/shared-resources',
+ 'position' => 'bottom',
+ 'scripts' => array(
+ 'res/md5/jquery.md5.js'
+ ),
+ 'targets' => array(
+ 'mobile',
+ 'desktop'
+ )
+ );
+
+ $GLOBALS['wgResourceModules']['onoi.blockUI'] = array(
+ 'localBasePath' => __DIR__ ,
+ 'remoteExtPath' => '../vendor/onoi/shared-resources',
+ 'position' => 'bottom',
+ 'scripts' => array(
+ 'res/jquery.blockUI/jquery.blockUI.js'
+ ),
+ 'targets' => array(
+ 'mobile',
+ 'desktop'
+ )
+ );
+
+ $GLOBALS['wgResourceModules']['onoi.rangeslider'] = array(
+ 'localBasePath' => __DIR__ ,
+ 'remoteExtPath' => '../vendor/onoi/shared-resources',
+ 'position' => 'bottom',
+ 'styles' => array(
+ 'res/jquery.rangeSlider/ion.rangeSlider.css',
+ 'res/jquery.rangeSlider/ion.rangeSlider.skinFlat.css'
+ ),
+ 'scripts' => array(
+ 'res/jquery.rangeSlider/ion.rangeSlider.js'
+ ),
+ 'targets' => array(
+ 'mobile',
+ 'desktop'
+ )
+ );
+
+ $GLOBALS['wgResourceModules']['onoi.localForage'] = array(
+ 'localBasePath' => __DIR__ ,
+ 'remoteExtPath' => '../vendor/onoi/shared-resources',
+ 'position' => 'bottom',
+ 'scripts' => array(
+ 'res/localForage/localforage.min.js'
+ ),
+ 'targets' => array(
+ 'mobile',
+ 'desktop'
+ )
+ );
+
+ $GLOBALS['wgResourceModules']['onoi.blobstore'] = array(
+ 'localBasePath' => __DIR__ ,
+ 'remoteExtPath' => '../vendor/onoi/shared-resources',
+ 'position' => 'bottom',
+ 'scripts' => array(
+ 'res/onoi.blobstore.js'
+ ),
+ 'dependencies' => array(
+ 'onoi.localForage',
+ ),
+ 'targets' => array(
+ 'mobile',
+ 'desktop'
+ )
+ );
+
+ $GLOBALS['wgResourceModules']['onoi.util'] = array(
+ 'localBasePath' => __DIR__ ,
+ 'remoteExtPath' => '../vendor/onoi/shared-resources',
+ 'position' => 'bottom',
+ 'styles' => array(
+ 'res/onoi.util.css'
+ ),
+ 'scripts' => array(
+ 'res/onoi.util.js'
+ ),
+ 'dependencies' => array(
+ 'onoi.md5',
+ ),
+ 'targets' => array(
+ 'mobile',
+ 'desktop'
+ )
+ );
+
+ $GLOBALS['wgResourceModules']['onoi.async'] = array(
+ 'localBasePath' => __DIR__ ,
+ 'remoteExtPath' => '../vendor/onoi/shared-resources',
+ 'position' => 'bottom',
+ 'scripts' => array(
+ 'res/jquery.async/jquery.async.js'
+ ),
+ 'targets' => array(
+ 'mobile',
+ 'desktop'
+ )
+ );
+
+ $GLOBALS['wgResourceModules']['onoi.jstorage'] = array(
+ 'localBasePath' => __DIR__ ,
+ 'remoteExtPath' => '../vendor/onoi/shared-resources',
+ 'position' => 'bottom',
+ 'scripts' => array(
+ 'res/jStorage/jstorage.js'
+ ),
+ 'targets' => array(
+ 'mobile',
+ 'desktop'
+ )
+ );
+
+ $GLOBALS['wgResourceModules']['onoi.clipboard'] = array(
+ 'localBasePath' => __DIR__ ,
+ 'remoteExtPath' => '../vendor/onoi/shared-resources',
+ 'position' => 'bottom',
+ 'scripts' => array(
+ 'res/clipboard/clipboard.js',
+ 'res/onoi.clipboard.js'
+ ),
+ 'targets' => array(
+ 'mobile',
+ 'desktop'
+ )
+ );
+
+ // Bootstrap tab resources
+ $GLOBALS['wgResourceModules']['onoi.bootstrap.tab.styles'] = array(
+ 'localBasePath' => __DIR__ ,
+ 'remoteExtPath' => '../vendor/onoi/shared-resources',
+ 'position' => 'top',
+ 'styles' => array(
+ 'res/bootstrap/bootstrap.4.tab.css',
+ 'res/bootstrap/bootstrap.4.mediawiki.tab.css'
+ ),
+ 'targets' => array(
+ 'mobile',
+ 'desktop'
+ )
+ );
+
+ $GLOBALS['wgResourceModules']['onoi.bootstrap.tab'] = array(
+ 'localBasePath' => __DIR__ ,
+ 'remoteExtPath' => '../vendor/onoi/shared-resources',
+ 'position' => 'top',
+ 'styles' => array(
+ 'res/bootstrap/bootstrap.4.tab.css',
+ 'res/bootstrap/bootstrap.4.mediawiki.tab.css'
+ ),
+ 'scripts' => array(
+ 'res/bootstrap/bootstrap.4.util.js',
+ 'res/bootstrap/bootstrap.4.tab.js'
+ ),
+ 'targets' => array(
+ 'mobile',
+ 'desktop'
+ )
+ );
+
+ $GLOBALS['wgResourceModules']['onoi.highlight'] = array(
+ 'localBasePath' => __DIR__ ,
+ 'remoteExtPath' => '../vendor/onoi/shared-resources',
+ 'position' => 'bottom',
+ 'scripts' => array(
+ 'res/jquery.highlight/jquery.highlight.js'
+ ),
+ 'targets' => array(
+ 'mobile',
+ 'desktop'
+ )
+ );
+
+ // DataTables resources
+ $GLOBALS['wgResourceModules']['onoi.dataTables.styles'] = array(
+ 'localBasePath' => __DIR__ ,
+ 'remoteExtPath' => '../vendor/onoi/shared-resources',
+ 'position' => 'top',
+ 'styles' => array(
+ 'res/jquery.dataTables/jquery.dataTables.css'
+ ),
+ 'targets' => array(
+ 'mobile',
+ 'desktop'
+ )
+ );
+
+ $GLOBALS['wgResourceModules']['onoi.dataTables.searchHighlight'] = array(
+ 'localBasePath' => __DIR__ ,
+ 'remoteExtPath' => '../vendor/onoi/shared-resources',
+ 'position' => 'bottom',
+ 'styles' => array(
+ 'res/jquery.dataTables/dataTables.searchHighlight.css'
+ ),
+ 'scripts' => array(
+ 'res/jquery.dataTables/dataTables.searchHighlight.js'
+ ),
+ 'dependencies' => array(
+ 'onoi.highlight',
+ ),
+ 'targets' => array(
+ 'mobile',
+ 'desktop'
+ )
+ );
+
+ $GLOBALS['wgResourceModules']['onoi.dataTables.responsive'] = array(
+ 'localBasePath' => __DIR__ ,
+ 'remoteExtPath' => '../vendor/onoi/shared-resources',
+ 'position' => 'bottom',
+ 'styles' => array(
+ 'res/jquery.dataTables/dataTables.responsive.css'
+ ),
+ 'scripts' => array(
+ 'res/jquery.dataTables/dataTables.responsive.js'
+ ),
+ 'dependencies' => array(
+ 'onoi.dataTables',
+ ),
+ 'targets' => array(
+ 'mobile',
+ 'desktop'
+ )
+ );
+
+ $GLOBALS['wgResourceModules']['onoi.dataTables'] = array(
+ 'localBasePath' => __DIR__ ,
+ 'remoteExtPath' => '../vendor/onoi/shared-resources',
+ 'position' => 'top',
+ 'styles' => array(
+ 'res/jquery.dataTables/jquery.dataTables.css'
+ ),
+ 'scripts' => array(
+ 'res/jquery.dataTables/jquery.dataTables.min.js',
+ 'res/jquery.dataTables/dataTables.search.js'
+ ),
+ 'dependencies' => array(
+ 'onoi.dataTables.searchHighlight',
+ ),
+ 'targets' => array(
+ 'mobile',
+ 'desktop'
+ )
+ );
+
+}
diff --git a/www/wiki/vendor/onoi/shared-resources/composer.json b/www/wiki/vendor/onoi/shared-resources/composer.json
new file mode 100644
index 00000000..34a1a09b
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/composer.json
@@ -0,0 +1,36 @@
+{
+ "name": "onoi/shared-resources",
+ "type": "library",
+ "description": "Shareable and common resource modules",
+ "keywords": [
+ "MediaWiki"
+ ],
+ "homepage": "https://github.com/onoi/common-resources",
+ "license": "GPL-2.0+",
+ "authors": [
+ {
+ "name": "mwjames",
+ "role": "Developer"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0",
+ "composer/installers": "1.*,>=1.0.1"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "files" : [
+ "Resources.php"
+ ]
+ },
+ "config": {
+ "process-timeout": 0
+ },
+ "scripts":{
+ "phpunit": "php ../../tests/phpunit/phpunit.php -c phpunit.xml.dist"
+ }
+}
diff --git a/www/wiki/vendor/onoi/shared-resources/res/bootstrap/bootstrap.4.mediawiki.tab.css b/www/wiki/vendor/onoi/shared-resources/res/bootstrap/bootstrap.4.mediawiki.tab.css
new file mode 100644
index 00000000..85059b5c
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/bootstrap/bootstrap.4.mediawiki.tab.css
@@ -0,0 +1,10 @@
+/**
+ * MediaWiki special
+ */
+ul.nav {
+ margin: 0em 0 0 0em;
+}
+
+ul.nav li {
+ list-style: none;
+}
diff --git a/www/wiki/vendor/onoi/shared-resources/res/bootstrap/bootstrap.4.tab.css b/www/wiki/vendor/onoi/shared-resources/res/bootstrap/bootstrap.4.tab.css
new file mode 100644
index 00000000..71dd63cd
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/bootstrap/bootstrap.4.tab.css
@@ -0,0 +1,300 @@
+/*!
+ * Bootstrap v4.0.0-alpha.6 (https://getbootstrap.com)
+ * Copyright 2011-2017 The Bootstrap Authors
+ * Copyright 2011-2017 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+.nav {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-wrap: wrap;
+ -ms-flex-wrap: wrap;
+ flex-wrap: wrap;
+ padding-left: 0;
+ margin-bottom: 0;
+ list-style: none;
+}
+
+.nav-link {
+ display: block;
+ padding: 0.5rem 1rem;
+}
+
+.nav-link:focus, .nav-link:hover {
+ text-decoration: none;
+}
+
+.nav-link.disabled {
+ color: #636c72;
+ cursor: not-allowed;
+}
+
+.nav-tabs {
+ border-bottom: 1px solid #ddd;
+}
+
+.nav-tabs .nav-item {
+ margin-bottom: -1px;
+}
+
+.nav-tabs .nav-link {
+ border: 1px solid transparent;
+ border-top-left-radius: 0.25rem;
+ border-top-right-radius: 0.25rem;
+}
+
+.nav-tabs .nav-link:focus, .nav-tabs .nav-link:hover {
+ border-color: #eceeef #eceeef #ddd;
+}
+
+.nav-tabs .nav-link.disabled {
+ color: #636c72;
+ background-color: transparent;
+ border-color: transparent;
+}
+
+.nav-tabs .nav-link.active,
+.nav-tabs .nav-item.show .nav-link {
+ color: #464a4c;
+ background-color: #fff;
+ border-color: #ddd #ddd #fff;
+}
+
+.nav-tabs .dropdown-menu {
+ margin-top: -1px;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+}
+
+.nav-pills .nav-link {
+ border-radius: 0.25rem;
+}
+
+.nav-pills .nav-link.active,
+.nav-pills .nav-item.show .nav-link {
+ color: #fff;
+ background-color: #0275d8;
+}
+
+.nav-fill .nav-item {
+ -webkit-box-flex: 1;
+ -webkit-flex: 1 1 auto;
+ -ms-flex: 1 1 auto;
+ flex: 1 1 auto;
+ text-align: center;
+}
+
+.nav-justified .nav-item {
+ -webkit-flex-basis: 0;
+ -ms-flex-preferred-size: 0;
+ flex-basis: 0;
+ -webkit-box-flex: 1;
+ -webkit-flex-grow: 1;
+ -ms-flex-positive: 1;
+ flex-grow: 1;
+ text-align: center;
+}
+
+.tab-content > .tab-pane {
+ display: none;
+}
+
+.tab-content > .active {
+ display: block;
+}
+
+.flex-row {
+ -webkit-box-orient: horizontal !important;
+ -webkit-box-direction: normal !important;
+ -webkit-flex-direction: row !important;
+ -ms-flex-direction: row !important;
+ flex-direction: row !important;
+}
+
+.flex-column {
+ -webkit-box-orient: vertical !important;
+ -webkit-box-direction: normal !important;
+ -webkit-flex-direction: column !important;
+ -ms-flex-direction: column !important;
+ flex-direction: column !important;
+}
+
+.flex-row-reverse {
+ -webkit-box-orient: horizontal !important;
+ -webkit-box-direction: reverse !important;
+ -webkit-flex-direction: row-reverse !important;
+ -ms-flex-direction: row-reverse !important;
+ flex-direction: row-reverse !important;
+}
+
+.flex-column-reverse {
+ -webkit-box-orient: vertical !important;
+ -webkit-box-direction: reverse !important;
+ -webkit-flex-direction: column-reverse !important;
+ -ms-flex-direction: column-reverse !important;
+ flex-direction: column-reverse !important;
+}
+
+.flex-wrap {
+ -webkit-flex-wrap: wrap !important;
+ -ms-flex-wrap: wrap !important;
+ flex-wrap: wrap !important;
+}
+
+.flex-nowrap {
+ -webkit-flex-wrap: nowrap !important;
+ -ms-flex-wrap: nowrap !important;
+ flex-wrap: nowrap !important;
+}
+
+.flex-wrap-reverse {
+ -webkit-flex-wrap: wrap-reverse !important;
+ -ms-flex-wrap: wrap-reverse !important;
+ flex-wrap: wrap-reverse !important;
+}
+
+.justify-content-start {
+ -webkit-box-pack: start !important;
+ -webkit-justify-content: flex-start !important;
+ -ms-flex-pack: start !important;
+ justify-content: flex-start !important;
+}
+
+.justify-content-end {
+ -webkit-box-pack: end !important;
+ -webkit-justify-content: flex-end !important;
+ -ms-flex-pack: end !important;
+ justify-content: flex-end !important;
+}
+
+.justify-content-center {
+ -webkit-box-pack: center !important;
+ -webkit-justify-content: center !important;
+ -ms-flex-pack: center !important;
+ justify-content: center !important;
+}
+
+.justify-content-between {
+ -webkit-box-pack: justify !important;
+ -webkit-justify-content: space-between !important;
+ -ms-flex-pack: justify !important;
+ justify-content: space-between !important;
+}
+
+.justify-content-around {
+ -webkit-justify-content: space-around !important;
+ -ms-flex-pack: distribute !important;
+ justify-content: space-around !important;
+}
+
+.align-items-start {
+ -webkit-box-align: start !important;
+ -webkit-align-items: flex-start !important;
+ -ms-flex-align: start !important;
+ align-items: flex-start !important;
+}
+
+.align-items-end {
+ -webkit-box-align: end !important;
+ -webkit-align-items: flex-end !important;
+ -ms-flex-align: end !important;
+ align-items: flex-end !important;
+}
+
+.align-items-center {
+ -webkit-box-align: center !important;
+ -webkit-align-items: center !important;
+ -ms-flex-align: center !important;
+ align-items: center !important;
+}
+
+.align-items-baseline {
+ -webkit-box-align: baseline !important;
+ -webkit-align-items: baseline !important;
+ -ms-flex-align: baseline !important;
+ align-items: baseline !important;
+}
+
+.align-items-stretch {
+ -webkit-box-align: stretch !important;
+ -webkit-align-items: stretch !important;
+ -ms-flex-align: stretch !important;
+ align-items: stretch !important;
+}
+
+.align-content-start {
+ -webkit-align-content: flex-start !important;
+ -ms-flex-line-pack: start !important;
+ align-content: flex-start !important;
+}
+
+.align-content-end {
+ -webkit-align-content: flex-end !important;
+ -ms-flex-line-pack: end !important;
+ align-content: flex-end !important;
+}
+
+.align-content-center {
+ -webkit-align-content: center !important;
+ -ms-flex-line-pack: center !important;
+ align-content: center !important;
+}
+
+.align-content-between {
+ -webkit-align-content: space-between !important;
+ -ms-flex-line-pack: justify !important;
+ align-content: space-between !important;
+}
+
+.align-content-around {
+ -webkit-align-content: space-around !important;
+ -ms-flex-line-pack: distribute !important;
+ align-content: space-around !important;
+}
+
+.align-content-stretch {
+ -webkit-align-content: stretch !important;
+ -ms-flex-line-pack: stretch !important;
+ align-content: stretch !important;
+}
+
+.align-self-auto {
+ -webkit-align-self: auto !important;
+ -ms-flex-item-align: auto !important;
+ -ms-grid-row-align: auto !important;
+ align-self: auto !important;
+}
+
+.align-self-start {
+ -webkit-align-self: flex-start !important;
+ -ms-flex-item-align: start !important;
+ align-self: flex-start !important;
+}
+
+.align-self-end {
+ -webkit-align-self: flex-end !important;
+ -ms-flex-item-align: end !important;
+ align-self: flex-end !important;
+}
+
+.align-self-center {
+ -webkit-align-self: center !important;
+ -ms-flex-item-align: center !important;
+ -ms-grid-row-align: center !important;
+ align-self: center !important;
+}
+
+.align-self-baseline {
+ -webkit-align-self: baseline !important;
+ -ms-flex-item-align: baseline !important;
+ align-self: baseline !important;
+}
+
+.align-self-stretch {
+ -webkit-align-self: stretch !important;
+ -ms-flex-item-align: stretch !important;
+ -ms-grid-row-align: stretch !important;
+ align-self: stretch !important;
+}
diff --git a/www/wiki/vendor/onoi/shared-resources/res/bootstrap/bootstrap.4.tab.js b/www/wiki/vendor/onoi/shared-resources/res/bootstrap/bootstrap.4.tab.js
new file mode 100644
index 00000000..9656cf9a
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/bootstrap/bootstrap.4.tab.js
@@ -0,0 +1,259 @@
+/**
+ * --------------------------------------------------------------------------
+ * Bootstrap (v4.0.0-alpha.6): tab.js
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * --------------------------------------------------------------------------
+ */
+
+var Tab = function ($) {
+
+ /**
+ * ------------------------------------------------------------------------
+ * Constants
+ * ------------------------------------------------------------------------
+ */
+
+ var NAME = 'tab';
+ var VERSION = '4.0.0-alpha.6';
+ var DATA_KEY = 'bs.tab';
+ var EVENT_KEY = '.' + DATA_KEY;
+ var DATA_API_KEY = '.data-api';
+ var JQUERY_NO_CONFLICT = $.fn[NAME];
+ var TRANSITION_DURATION = 150;
+
+ var Event = {
+ HIDE: 'hide' + EVENT_KEY,
+ HIDDEN: 'hidden' + EVENT_KEY,
+ SHOW: 'show' + EVENT_KEY,
+ SHOWN: 'shown' + EVENT_KEY,
+ CLICK_DATA_API: 'click' + EVENT_KEY + DATA_API_KEY
+ };
+
+ var ClassName = {
+ DROPDOWN_MENU: 'dropdown-menu',
+ ACTIVE: 'active',
+ DISABLED: 'disabled',
+ FADE: 'fade',
+ SHOW: 'show'
+ };
+
+ var Selector = {
+ A: 'a',
+ LI: 'li',
+ DROPDOWN: '.dropdown',
+ LIST: 'ul:not(.dropdown-menu), ol:not(.dropdown-menu), nav:not(.dropdown-menu), .list-group:not(.dropdown-menu)',
+ FADE_CHILD: '> .nav-item .fade, > .list-group-item .fade, > .fade',
+ ACTIVE: '.active',
+ ACTIVE_CHILD: '> .nav-item > .active, > .list-group-item > .active, > .active',
+ DATA_TOGGLE: '[data-toggle="tab"], [data-toggle="pill"]',
+ DROPDOWN_TOGGLE: '.dropdown-toggle',
+ DROPDOWN_ACTIVE_CHILD: '> .dropdown-menu .active'
+ };
+
+ /**
+ * ------------------------------------------------------------------------
+ * Class Definition
+ * ------------------------------------------------------------------------
+ */
+
+ var Tab = function () {
+ function Tab(element) {
+ _classCallCheck(this, Tab);
+
+ this._element = element;
+ }
+
+ // getters
+
+ // public
+
+ Tab.prototype.show = function show() {
+ var _this20 = this;
+
+ if (this._element.parentNode && this._element.parentNode.nodeType === Node.ELEMENT_NODE && $(this._element).hasClass(ClassName.ACTIVE) || $(this._element).hasClass(ClassName.DISABLED)) {
+ return;
+ }
+
+ var target = void 0;
+ var previous = void 0;
+ var listElement = $(this._element).closest(Selector.LIST)[0];
+ var selector = Util.getSelectorFromElement(this._element);
+
+ if (listElement) {
+ previous = $.makeArray($(listElement).find(Selector.ACTIVE));
+ previous = previous[previous.length - 1];
+ }
+
+ var hideEvent = $.Event(Event.HIDE, {
+ relatedTarget: this._element
+ });
+
+ var showEvent = $.Event(Event.SHOW, {
+ relatedTarget: previous
+ });
+
+ if (previous) {
+ $(previous).trigger(hideEvent);
+ }
+
+ $(this._element).trigger(showEvent);
+
+ if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) {
+ return;
+ }
+
+ if (selector) {
+ target = $(selector)[0];
+ }
+
+ this._activate(this._element, listElement);
+
+ var complete = function complete() {
+ var hiddenEvent = $.Event(Event.HIDDEN, {
+ relatedTarget: _this20._element
+ });
+
+ var shownEvent = $.Event(Event.SHOWN, {
+ relatedTarget: previous
+ });
+
+ $(previous).trigger(hiddenEvent);
+ $(_this20._element).trigger(shownEvent);
+ };
+
+ if (target) {
+ this._activate(target, target.parentNode, complete);
+ } else {
+ complete();
+ }
+ };
+
+ Tab.prototype.dispose = function dispose() {
+ $.removeClass(this._element, DATA_KEY);
+ this._element = null;
+ };
+
+ // private
+
+ Tab.prototype._activate = function _activate(element, container, callback) {
+ var _this21 = this;
+
+ var active = $(container).find(Selector.ACTIVE_CHILD)[0];
+ var isTransitioning = callback && Util.supportsTransitionEnd() && (active && $(active).hasClass(ClassName.FADE) || Boolean($(container).find(Selector.FADE_CHILD)[0]));
+
+ var complete = function complete() {
+ return _this21._transitionComplete(element, active, isTransitioning, callback);
+ };
+
+ if (active && isTransitioning) {
+ $(active).one(Util.TRANSITION_END, complete).emulateTransitionEnd(TRANSITION_DURATION);
+ } else {
+ complete();
+ }
+
+ if (active) {
+ $(active).removeClass(ClassName.SHOW);
+ }
+ };
+
+ Tab.prototype._transitionComplete = function _transitionComplete(element, active, isTransitioning, callback) {
+ if (active) {
+ $(active).removeClass(ClassName.ACTIVE);
+ if ($(active).hasClass('list-group-item')) {
+ $(active).find('a.nav-link').removeClass(ClassName.ACTIVE);
+ }
+
+ var dropdownChild = $(active.parentNode).find(Selector.DROPDOWN_ACTIVE_CHILD)[0];
+
+ if (dropdownChild) {
+ $(dropdownChild).removeClass(ClassName.ACTIVE);
+ }
+
+ active.setAttribute('aria-expanded', false);
+ }
+
+ $(element).addClass(ClassName.ACTIVE);
+ if ($(element.parentNode).hasClass('list-group-item')) {
+ $(element.parentNode).addClass(ClassName.ACTIVE);
+ }
+ element.setAttribute('aria-expanded', true);
+
+ if (isTransitioning) {
+ Util.reflow(element);
+ $(element).addClass(ClassName.SHOW);
+ } else {
+ $(element).removeClass(ClassName.FADE);
+ }
+
+ if (element.parentNode && $(element.parentNode).hasClass(ClassName.DROPDOWN_MENU)) {
+
+ var dropdownElement = $(element).closest(Selector.DROPDOWN)[0];
+ if (dropdownElement) {
+ $(dropdownElement).find(Selector.DROPDOWN_TOGGLE).addClass(ClassName.ACTIVE);
+ }
+
+ element.setAttribute('aria-expanded', true);
+ }
+
+ if (callback) {
+ callback();
+ }
+ };
+
+ // static
+
+ Tab._jQueryInterface = function _jQueryInterface(config) {
+ return this.each(function () {
+ var $this = $(this);
+ var data = $this.data(DATA_KEY);
+
+ if (!data) {
+ data = new Tab(this);
+ $this.data(DATA_KEY, data);
+ }
+
+ if (typeof config === 'string') {
+ if (data[config] === undefined) {
+ throw new Error('No method named "' + config + '"');
+ }
+ data[config]();
+ }
+ });
+ };
+
+ _createClass(Tab, null, [{
+ key: 'VERSION',
+ get: function get() {
+ return VERSION;
+ }
+ }]);
+
+ return Tab;
+ }();
+
+ /**
+ * ------------------------------------------------------------------------
+ * Data Api implementation
+ * ------------------------------------------------------------------------
+ */
+
+ $(document).on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) {
+ event.preventDefault();
+ Tab._jQueryInterface.call($(this), 'show');
+ });
+
+ /**
+ * ------------------------------------------------------------------------
+ * jQuery
+ * ------------------------------------------------------------------------
+ */
+
+ $.fn[NAME] = Tab._jQueryInterface;
+ $.fn[NAME].Constructor = Tab;
+ $.fn[NAME].noConflict = function () {
+ $.fn[NAME] = JQUERY_NO_CONFLICT;
+ return Tab._jQueryInterface;
+ };
+
+ return Tab;
+}(jQuery);
diff --git a/www/wiki/vendor/onoi/shared-resources/res/bootstrap/bootstrap.4.util.js b/www/wiki/vendor/onoi/shared-resources/res/bootstrap/bootstrap.4.util.js
new file mode 100644
index 00000000..37fa4921
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/bootstrap/bootstrap.4.util.js
@@ -0,0 +1,175 @@
+
+/*!
+ * Bootstrap v4.0.0-alpha.6 (https://getbootstrap.com)
+ * Copyright 2011-2017 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+
+if (typeof jQuery === 'undefined') {
+ throw new Error('Bootstrap\'s JavaScript requires jQuery. jQuery must be included before Bootstrap\'s JavaScript.')
+}
+
++function ($) {
+ var version = $.fn.jquery.split(' ')[0].split('.')
+ if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] >= 4)) {
+ throw new Error('Bootstrap\'s JavaScript requires at least jQuery v1.9.1 but less than v4.0.0')
+ }
+}(jQuery);
+
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+/**
+ * --------------------------------------------------------------------------
+ * Bootstrap (v4.0.0-alpha.6): util.js
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * --------------------------------------------------------------------------
+ */
+
+var Util = function ($) {
+
+ /**
+ * ------------------------------------------------------------------------
+ * Private TransitionEnd Helpers
+ * ------------------------------------------------------------------------
+ */
+
+ var transition = false;
+
+ var MAX_UID = 1000000;
+
+ var TransitionEndEvent = {
+ WebkitTransition: 'webkitTransitionEnd',
+ MozTransition: 'transitionend',
+ OTransition: 'oTransitionEnd otransitionend',
+ transition: 'transitionend'
+ };
+
+ // shoutout AngusCroll (https://goo.gl/pxwQGp)
+ function toType(obj) {
+ return {}.toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
+ }
+
+ function isElement(obj) {
+ return (obj[0] || obj).nodeType;
+ }
+
+ function getSpecialTransitionEndEvent() {
+ return {
+ bindType: transition.end,
+ delegateType: transition.end,
+ handle: function handle(event) {
+ if ($(event.target).is(this)) {
+ return event.handleObj.handler.apply(this, arguments); // eslint-disable-line prefer-rest-params
+ }
+ return undefined;
+ }
+ };
+ }
+
+ function transitionEndTest() {
+ if (window.QUnit) {
+ return false;
+ }
+
+ var el = document.createElement('bootstrap');
+
+ for (var name in TransitionEndEvent) {
+ if (el.style[name] !== undefined) {
+ return {
+ end: TransitionEndEvent[name]
+ };
+ }
+ }
+
+ return false;
+ }
+
+ function transitionEndEmulator(duration) {
+ var _this = this;
+
+ var called = false;
+
+ $(this).one(Util.TRANSITION_END, function () {
+ called = true;
+ });
+
+ setTimeout(function () {
+ if (!called) {
+ Util.triggerTransitionEnd(_this);
+ }
+ }, duration);
+
+ return this;
+ }
+
+ function setTransitionEndSupport() {
+ transition = transitionEndTest();
+
+ $.fn.emulateTransitionEnd = transitionEndEmulator;
+
+ if (Util.supportsTransitionEnd()) {
+ $.event.special[Util.TRANSITION_END] = getSpecialTransitionEndEvent();
+ }
+ }
+
+ /**
+ * --------------------------------------------------------------------------
+ * Public Util Api
+ * --------------------------------------------------------------------------
+ */
+
+ var Util = {
+
+ TRANSITION_END: 'bsTransitionEnd',
+
+ getUID: function getUID(prefix) {
+ do {
+ // eslint-disable-next-line no-bitwise
+ prefix += ~~(Math.random() * MAX_UID); // "~~" acts like a faster Math.floor() here
+ } while (document.getElementById(prefix));
+ return prefix;
+ },
+ getSelectorFromElement: function getSelectorFromElement(element) {
+ var selector = element.getAttribute('data-target');
+ if (!selector || selector === '#') {
+ selector = element.getAttribute('href') || '';
+ }
+
+ try {
+ var $selector = $(selector);
+ return $selector.length > 0 ? selector : null;
+ } catch (error) {
+ return null;
+ }
+ },
+ reflow: function reflow(element) {
+ return element.offsetHeight;
+ },
+ triggerTransitionEnd: function triggerTransitionEnd(element) {
+ $(element).trigger(transition.end);
+ },
+ supportsTransitionEnd: function supportsTransitionEnd() {
+ return Boolean(transition);
+ },
+ typeCheckConfig: function typeCheckConfig(componentName, config, configTypes) {
+ for (var property in configTypes) {
+ if (configTypes.hasOwnProperty(property)) {
+ var expectedTypes = configTypes[property];
+ var value = config[property];
+ var valueType = value && isElement(value) ? 'element' : toType(value);
+
+ if (!new RegExp(expectedTypes).test(valueType)) {
+ throw new Error(componentName.toUpperCase() + ': ' + ('Option "' + property + '" provided type "' + valueType + '" ') + ('but expected type "' + expectedTypes + '".'));
+ }
+ }
+ }
+ }
+ };
+
+ setTransitionEndSupport();
+
+ return Util;
+}(jQuery);
diff --git a/www/wiki/vendor/onoi/shared-resources/res/clipboard/clipboard.js b/www/wiki/vendor/onoi/shared-resources/res/clipboard/clipboard.js
new file mode 100644
index 00000000..75b6af35
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/clipboard/clipboard.js
@@ -0,0 +1,778 @@
+/*!
+ * clipboard.js v1.6.1
+ * https://zenorocha.github.io/clipboard.js
+ *
+ * Licensed MIT © Zeno Rocha
+ */
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Clipboard = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+var DOCUMENT_NODE_TYPE = 9;
+
+/**
+ * A polyfill for Element.matches()
+ */
+if (typeof Element !== 'undefined' && !Element.prototype.matches) {
+ var proto = Element.prototype;
+
+ proto.matches = proto.matchesSelector ||
+ proto.mozMatchesSelector ||
+ proto.msMatchesSelector ||
+ proto.oMatchesSelector ||
+ proto.webkitMatchesSelector;
+}
+
+/**
+ * Finds the closest parent that matches a selector.
+ *
+ * @param {Element} element
+ * @param {String} selector
+ * @return {Function}
+ */
+function closest (element, selector) {
+ while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {
+ if (element.matches(selector)) return element;
+ element = element.parentNode;
+ }
+}
+
+module.exports = closest;
+
+},{}],2:[function(require,module,exports){
+var closest = require('./closest');
+
+/**
+ * Delegates event to a selector.
+ *
+ * @param {Element} element
+ * @param {String} selector
+ * @param {String} type
+ * @param {Function} callback
+ * @param {Boolean} useCapture
+ * @return {Object}
+ */
+function delegate(element, selector, type, callback, useCapture) {
+ var listenerFn = listener.apply(this, arguments);
+
+ element.addEventListener(type, listenerFn, useCapture);
+
+ return {
+ destroy: function() {
+ element.removeEventListener(type, listenerFn, useCapture);
+ }
+ }
+}
+
+/**
+ * Finds closest match and invokes callback.
+ *
+ * @param {Element} element
+ * @param {String} selector
+ * @param {String} type
+ * @param {Function} callback
+ * @return {Function}
+ */
+function listener(element, selector, type, callback) {
+ return function(e) {
+ e.delegateTarget = closest(e.target, selector);
+
+ if (e.delegateTarget) {
+ callback.call(element, e);
+ }
+ }
+}
+
+module.exports = delegate;
+
+},{"./closest":1}],3:[function(require,module,exports){
+/**
+ * Check if argument is a HTML element.
+ *
+ * @param {Object} value
+ * @return {Boolean}
+ */
+exports.node = function(value) {
+ return value !== undefined
+ && value instanceof HTMLElement
+ && value.nodeType === 1;
+};
+
+/**
+ * Check if argument is a list of HTML elements.
+ *
+ * @param {Object} value
+ * @return {Boolean}
+ */
+exports.nodeList = function(value) {
+ var type = Object.prototype.toString.call(value);
+
+ return value !== undefined
+ && (type === '[object NodeList]' || type === '[object HTMLCollection]')
+ && ('length' in value)
+ && (value.length === 0 || exports.node(value[0]));
+};
+
+/**
+ * Check if argument is a string.
+ *
+ * @param {Object} value
+ * @return {Boolean}
+ */
+exports.string = function(value) {
+ return typeof value === 'string'
+ || value instanceof String;
+};
+
+/**
+ * Check if argument is a function.
+ *
+ * @param {Object} value
+ * @return {Boolean}
+ */
+exports.fn = function(value) {
+ var type = Object.prototype.toString.call(value);
+
+ return type === '[object Function]';
+};
+
+},{}],4:[function(require,module,exports){
+var is = require('./is');
+var delegate = require('delegate');
+
+/**
+ * Validates all params and calls the right
+ * listener function based on its target type.
+ *
+ * @param {String|HTMLElement|HTMLCollection|NodeList} target
+ * @param {String} type
+ * @param {Function} callback
+ * @return {Object}
+ */
+function listen(target, type, callback) {
+ if (!target && !type && !callback) {
+ throw new Error('Missing required arguments');
+ }
+
+ if (!is.string(type)) {
+ throw new TypeError('Second argument must be a String');
+ }
+
+ if (!is.fn(callback)) {
+ throw new TypeError('Third argument must be a Function');
+ }
+
+ if (is.node(target)) {
+ return listenNode(target, type, callback);
+ }
+ else if (is.nodeList(target)) {
+ return listenNodeList(target, type, callback);
+ }
+ else if (is.string(target)) {
+ return listenSelector(target, type, callback);
+ }
+ else {
+ throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');
+ }
+}
+
+/**
+ * Adds an event listener to a HTML element
+ * and returns a remove listener function.
+ *
+ * @param {HTMLElement} node
+ * @param {String} type
+ * @param {Function} callback
+ * @return {Object}
+ */
+function listenNode(node, type, callback) {
+ node.addEventListener(type, callback);
+
+ return {
+ destroy: function() {
+ node.removeEventListener(type, callback);
+ }
+ }
+}
+
+/**
+ * Add an event listener to a list of HTML elements
+ * and returns a remove listener function.
+ *
+ * @param {NodeList|HTMLCollection} nodeList
+ * @param {String} type
+ * @param {Function} callback
+ * @return {Object}
+ */
+function listenNodeList(nodeList, type, callback) {
+ Array.prototype.forEach.call(nodeList, function(node) {
+ node.addEventListener(type, callback);
+ });
+
+ return {
+ destroy: function() {
+ Array.prototype.forEach.call(nodeList, function(node) {
+ node.removeEventListener(type, callback);
+ });
+ }
+ }
+}
+
+/**
+ * Add an event listener to a selector
+ * and returns a remove listener function.
+ *
+ * @param {String} selector
+ * @param {String} type
+ * @param {Function} callback
+ * @return {Object}
+ */
+function listenSelector(selector, type, callback) {
+ return delegate(document.body, selector, type, callback);
+}
+
+module.exports = listen;
+
+},{"./is":3,"delegate":2}],5:[function(require,module,exports){
+function select(element) {
+ var selectedText;
+
+ if (element.nodeName === 'SELECT') {
+ element.focus();
+
+ selectedText = element.value;
+ }
+ else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
+ var isReadOnly = element.hasAttribute('readonly');
+
+ if (!isReadOnly) {
+ element.setAttribute('readonly', '');
+ }
+
+ element.select();
+ element.setSelectionRange(0, element.value.length);
+
+ if (!isReadOnly) {
+ element.removeAttribute('readonly');
+ }
+
+ selectedText = element.value;
+ }
+ else {
+ if (element.hasAttribute('contenteditable')) {
+ element.focus();
+ }
+
+ var selection = window.getSelection();
+ var range = document.createRange();
+
+ range.selectNodeContents(element);
+ selection.removeAllRanges();
+ selection.addRange(range);
+
+ selectedText = selection.toString();
+ }
+
+ return selectedText;
+}
+
+module.exports = select;
+
+},{}],6:[function(require,module,exports){
+function E () {
+ // Keep this empty so it's easier to inherit from
+ // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)
+}
+
+E.prototype = {
+ on: function (name, callback, ctx) {
+ var e = this.e || (this.e = {});
+
+ (e[name] || (e[name] = [])).push({
+ fn: callback,
+ ctx: ctx
+ });
+
+ return this;
+ },
+
+ once: function (name, callback, ctx) {
+ var self = this;
+ function listener () {
+ self.off(name, listener);
+ callback.apply(ctx, arguments);
+ };
+
+ listener._ = callback
+ return this.on(name, listener, ctx);
+ },
+
+ emit: function (name) {
+ var data = [].slice.call(arguments, 1);
+ var evtArr = ((this.e || (this.e = {}))[name] || []).slice();
+ var i = 0;
+ var len = evtArr.length;
+
+ for (i; i < len; i++) {
+ evtArr[i].fn.apply(evtArr[i].ctx, data);
+ }
+
+ return this;
+ },
+
+ off: function (name, callback) {
+ var e = this.e || (this.e = {});
+ var evts = e[name];
+ var liveEvents = [];
+
+ if (evts && callback) {
+ for (var i = 0, len = evts.length; i < len; i++) {
+ if (evts[i].fn !== callback && evts[i].fn._ !== callback)
+ liveEvents.push(evts[i]);
+ }
+ }
+
+ // Remove event from queue to prevent memory leak
+ // Suggested by https://github.com/lazd
+ // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910
+
+ (liveEvents.length)
+ ? e[name] = liveEvents
+ : delete e[name];
+
+ return this;
+ }
+};
+
+module.exports = E;
+
+},{}],7:[function(require,module,exports){
+(function (global, factory) {
+ if (typeof define === "function" && define.amd) {
+ define(['module', 'select'], factory);
+ } else if (typeof exports !== "undefined") {
+ factory(module, require('select'));
+ } else {
+ var mod = {
+ exports: {}
+ };
+ factory(mod, global.select);
+ global.clipboardAction = mod.exports;
+ }
+})(this, function (module, _select) {
+ 'use strict';
+
+ var _select2 = _interopRequireDefault(_select);
+
+ function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : {
+ default: obj
+ };
+ }
+
+ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
+ return typeof obj;
+ } : function (obj) {
+ return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
+ };
+
+ function _classCallCheck(instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError("Cannot call a class as a function");
+ }
+ }
+
+ var _createClass = function () {
+ function defineProperties(target, props) {
+ for (var i = 0; i < props.length; i++) {
+ var descriptor = props[i];
+ descriptor.enumerable = descriptor.enumerable || false;
+ descriptor.configurable = true;
+ if ("value" in descriptor) descriptor.writable = true;
+ Object.defineProperty(target, descriptor.key, descriptor);
+ }
+ }
+
+ return function (Constructor, protoProps, staticProps) {
+ if (protoProps) defineProperties(Constructor.prototype, protoProps);
+ if (staticProps) defineProperties(Constructor, staticProps);
+ return Constructor;
+ };
+ }();
+
+ var ClipboardAction = function () {
+ /**
+ * @param {Object} options
+ */
+ function ClipboardAction(options) {
+ _classCallCheck(this, ClipboardAction);
+
+ this.resolveOptions(options);
+ this.initSelection();
+ }
+
+ /**
+ * Defines base properties passed from constructor.
+ * @param {Object} options
+ */
+
+
+ _createClass(ClipboardAction, [{
+ key: 'resolveOptions',
+ value: function resolveOptions() {
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
+
+ this.action = options.action;
+ this.emitter = options.emitter;
+ this.target = options.target;
+ this.text = options.text;
+ this.trigger = options.trigger;
+
+ this.selectedText = '';
+ }
+ }, {
+ key: 'initSelection',
+ value: function initSelection() {
+ if (this.text) {
+ this.selectFake();
+ } else if (this.target) {
+ this.selectTarget();
+ }
+ }
+ }, {
+ key: 'selectFake',
+ value: function selectFake() {
+ var _this = this;
+
+ var isRTL = document.documentElement.getAttribute('dir') == 'rtl';
+
+ this.removeFake();
+
+ this.fakeHandlerCallback = function () {
+ return _this.removeFake();
+ };
+ this.fakeHandler = document.body.addEventListener('click', this.fakeHandlerCallback) || true;
+
+ this.fakeElem = document.createElement('textarea');
+ // Prevent zooming on iOS
+ this.fakeElem.style.fontSize = '12pt';
+ // Reset box model
+ this.fakeElem.style.border = '0';
+ this.fakeElem.style.padding = '0';
+ this.fakeElem.style.margin = '0';
+ // Move element out of screen horizontally
+ this.fakeElem.style.position = 'absolute';
+ this.fakeElem.style[isRTL ? 'right' : 'left'] = '-9999px';
+ // Move element to the same position vertically
+ var yPosition = window.pageYOffset || document.documentElement.scrollTop;
+ this.fakeElem.style.top = yPosition + 'px';
+
+ this.fakeElem.setAttribute('readonly', '');
+ this.fakeElem.value = this.text;
+
+ document.body.appendChild(this.fakeElem);
+
+ this.selectedText = (0, _select2.default)(this.fakeElem);
+ this.copyText();
+ }
+ }, {
+ key: 'removeFake',
+ value: function removeFake() {
+ if (this.fakeHandler) {
+ document.body.removeEventListener('click', this.fakeHandlerCallback);
+ this.fakeHandler = null;
+ this.fakeHandlerCallback = null;
+ }
+
+ if (this.fakeElem) {
+ document.body.removeChild(this.fakeElem);
+ this.fakeElem = null;
+ }
+ }
+ }, {
+ key: 'selectTarget',
+ value: function selectTarget() {
+ this.selectedText = (0, _select2.default)(this.target);
+ this.copyText();
+ }
+ }, {
+ key: 'copyText',
+ value: function copyText() {
+ var succeeded = void 0;
+
+ try {
+ succeeded = document.execCommand(this.action);
+ } catch (err) {
+ succeeded = false;
+ }
+
+ this.handleResult(succeeded);
+ }
+ }, {
+ key: 'handleResult',
+ value: function handleResult(succeeded) {
+ this.emitter.emit(succeeded ? 'success' : 'error', {
+ action: this.action,
+ text: this.selectedText,
+ trigger: this.trigger,
+ clearSelection: this.clearSelection.bind(this)
+ });
+ }
+ }, {
+ key: 'clearSelection',
+ value: function clearSelection() {
+ if (this.target) {
+ this.target.blur();
+ }
+
+ window.getSelection().removeAllRanges();
+ }
+ }, {
+ key: 'destroy',
+ value: function destroy() {
+ this.removeFake();
+ }
+ }, {
+ key: 'action',
+ set: function set() {
+ var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'copy';
+
+ this._action = action;
+
+ if (this._action !== 'copy' && this._action !== 'cut') {
+ throw new Error('Invalid "action" value, use either "copy" or "cut"');
+ }
+ },
+ get: function get() {
+ return this._action;
+ }
+ }, {
+ key: 'target',
+ set: function set(target) {
+ if (target !== undefined) {
+ if (target && (typeof target === 'undefined' ? 'undefined' : _typeof(target)) === 'object' && target.nodeType === 1) {
+ if (this.action === 'copy' && target.hasAttribute('disabled')) {
+ throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');
+ }
+
+ if (this.action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {
+ throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');
+ }
+
+ this._target = target;
+ } else {
+ throw new Error('Invalid "target" value, use a valid Element');
+ }
+ }
+ },
+ get: function get() {
+ return this._target;
+ }
+ }]);
+
+ return ClipboardAction;
+ }();
+
+ module.exports = ClipboardAction;
+});
+
+},{"select":5}],8:[function(require,module,exports){
+(function (global, factory) {
+ if (typeof define === "function" && define.amd) {
+ define(['module', './clipboard-action', 'tiny-emitter', 'good-listener'], factory);
+ } else if (typeof exports !== "undefined") {
+ factory(module, require('./clipboard-action'), require('tiny-emitter'), require('good-listener'));
+ } else {
+ var mod = {
+ exports: {}
+ };
+ factory(mod, global.clipboardAction, global.tinyEmitter, global.goodListener);
+ global.clipboard = mod.exports;
+ }
+})(this, function (module, _clipboardAction, _tinyEmitter, _goodListener) {
+ 'use strict';
+
+ var _clipboardAction2 = _interopRequireDefault(_clipboardAction);
+
+ var _tinyEmitter2 = _interopRequireDefault(_tinyEmitter);
+
+ var _goodListener2 = _interopRequireDefault(_goodListener);
+
+ function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : {
+ default: obj
+ };
+ }
+
+ function _classCallCheck(instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError("Cannot call a class as a function");
+ }
+ }
+
+ var _createClass = function () {
+ function defineProperties(target, props) {
+ for (var i = 0; i < props.length; i++) {
+ var descriptor = props[i];
+ descriptor.enumerable = descriptor.enumerable || false;
+ descriptor.configurable = true;
+ if ("value" in descriptor) descriptor.writable = true;
+ Object.defineProperty(target, descriptor.key, descriptor);
+ }
+ }
+
+ return function (Constructor, protoProps, staticProps) {
+ if (protoProps) defineProperties(Constructor.prototype, protoProps);
+ if (staticProps) defineProperties(Constructor, staticProps);
+ return Constructor;
+ };
+ }();
+
+ function _possibleConstructorReturn(self, call) {
+ if (!self) {
+ throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
+ }
+
+ return call && (typeof call === "object" || typeof call === "function") ? call : self;
+ }
+
+ function _inherits(subClass, superClass) {
+ if (typeof superClass !== "function" && superClass !== null) {
+ throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
+ }
+
+ subClass.prototype = Object.create(superClass && superClass.prototype, {
+ constructor: {
+ value: subClass,
+ enumerable: false,
+ writable: true,
+ configurable: true
+ }
+ });
+ if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
+ }
+
+ var Clipboard = function (_Emitter) {
+ _inherits(Clipboard, _Emitter);
+
+ /**
+ * @param {String|HTMLElement|HTMLCollection|NodeList} trigger
+ * @param {Object} options
+ */
+ function Clipboard(trigger, options) {
+ _classCallCheck(this, Clipboard);
+
+ var _this = _possibleConstructorReturn(this, (Clipboard.__proto__ || Object.getPrototypeOf(Clipboard)).call(this));
+
+ _this.resolveOptions(options);
+ _this.listenClick(trigger);
+ return _this;
+ }
+
+ /**
+ * Defines if attributes would be resolved using internal setter functions
+ * or custom functions that were passed in the constructor.
+ * @param {Object} options
+ */
+
+
+ _createClass(Clipboard, [{
+ key: 'resolveOptions',
+ value: function resolveOptions() {
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
+
+ this.action = typeof options.action === 'function' ? options.action : this.defaultAction;
+ this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;
+ this.text = typeof options.text === 'function' ? options.text : this.defaultText;
+ }
+ }, {
+ key: 'listenClick',
+ value: function listenClick(trigger) {
+ var _this2 = this;
+
+ this.listener = (0, _goodListener2.default)(trigger, 'click', function (e) {
+ return _this2.onClick(e);
+ });
+ }
+ }, {
+ key: 'onClick',
+ value: function onClick(e) {
+ var trigger = e.delegateTarget || e.currentTarget;
+
+ if (this.clipboardAction) {
+ this.clipboardAction = null;
+ }
+
+ this.clipboardAction = new _clipboardAction2.default({
+ action: this.action(trigger),
+ target: this.target(trigger),
+ text: this.text(trigger),
+ trigger: trigger,
+ emitter: this
+ });
+ }
+ }, {
+ key: 'defaultAction',
+ value: function defaultAction(trigger) {
+ return getAttributeValue('action', trigger);
+ }
+ }, {
+ key: 'defaultTarget',
+ value: function defaultTarget(trigger) {
+ var selector = getAttributeValue('target', trigger);
+
+ if (selector) {
+ return document.querySelector(selector);
+ }
+ }
+ }, {
+ key: 'defaultText',
+ value: function defaultText(trigger) {
+ return getAttributeValue('text', trigger);
+ }
+ }, {
+ key: 'destroy',
+ value: function destroy() {
+ this.listener.destroy();
+
+ if (this.clipboardAction) {
+ this.clipboardAction.destroy();
+ this.clipboardAction = null;
+ }
+ }
+ }], [{
+ key: 'isSupported',
+ value: function isSupported() {
+ var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];
+
+ var actions = typeof action === 'string' ? [action] : action;
+ var support = !!document.queryCommandSupported;
+
+ actions.forEach(function (action) {
+ support = support && !!document.queryCommandSupported(action);
+ });
+
+ return support;
+ }
+ }]);
+
+ return Clipboard;
+ }(_tinyEmitter2.default);
+
+ /**
+ * Helper function to retrieve attribute value.
+ * @param {String} suffix
+ * @param {Element} element
+ */
+ function getAttributeValue(suffix, element) {
+ var attribute = 'data-clipboard-' + suffix;
+
+ if (!element.hasAttribute(attribute)) {
+ return;
+ }
+
+ return element.getAttribute(attribute);
+ }
+
+ module.exports = Clipboard;
+});
+
+},{"./clipboard-action":7,"good-listener":4,"tiny-emitter":6}]},{},[8])(8)
+}); \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/shared-resources/res/jStorage/jstorage.js b/www/wiki/vendor/onoi/shared-resources/res/jStorage/jstorage.js
new file mode 100644
index 00000000..45e19ac6
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/jStorage/jstorage.js
@@ -0,0 +1,996 @@
+/*
+ * ----------------------------- JSTORAGE -------------------------------------
+ * Simple local storage wrapper to save data on the browser side, supporting
+ * all major browsers - IE6+, Firefox2+, Safari4+, Chrome4+ and Opera 10.5+
+ *
+ * Author: Andris Reinman, andris.reinman@gmail.com
+ * Project homepage: www.jstorage.info
+ *
+ * Licensed under Unlicense:
+ *
+ * This is free and unencumbered software released into the public domain.
+ *
+ * Anyone is free to copy, modify, publish, use, compile, sell, or
+ * distribute this software, either in source code form or as a compiled
+ * binary, for any purpose, commercial or non-commercial, and by any
+ * means.
+ *
+ * In jurisdictions that recognize copyright laws, the author or authors
+ * of this software dedicate any and all copyright interest in the
+ * software to the public domain. We make this dedication for the benefit
+ * of the public at large and to the detriment of our heirs and
+ * successors. We intend this dedication to be an overt act of
+ * relinquishment in perpetuity of all present and future rights to this
+ * software under copyright law.
+ *
+ * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * For more information, please refer to <http://unlicense.org/>
+ */
+
+/* global ActiveXObject: false */
+/* jshint browser: true */
+
+(function() {
+ 'use strict';
+
+ var
+ /* jStorage version */
+ JSTORAGE_VERSION = '0.4.12',
+
+ /* detect a dollar object or create one if not found */
+ $ = window.jQuery || window.$ || (window.$ = {}),
+
+ /* check for a JSON handling support */
+ JSON = {
+ parse: window.JSON && (window.JSON.parse || window.JSON.decode) ||
+ String.prototype.evalJSON && function(str) {
+ return String(str).evalJSON();
+ } ||
+ $.parseJSON ||
+ $.evalJSON,
+ stringify: Object.toJSON ||
+ window.JSON && (window.JSON.stringify || window.JSON.encode) ||
+ $.toJSON
+ };
+
+ // Break if no JSON support was found
+ if (typeof JSON.parse !== 'function' || typeof JSON.stringify !== 'function') {
+ throw new Error('No JSON support found, include //cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js to page');
+ }
+
+ var
+ /* This is the object, that holds the cached values */
+ _storage = {
+ __jstorage_meta: {
+ CRC32: {}
+ }
+ },
+
+ /* Actual browser storage (localStorage or globalStorage['domain']) */
+ _storage_service = {
+ jStorage: '{}'
+ },
+
+ /* DOM element for older IE versions, holds userData behavior */
+ _storage_elm = null,
+
+ /* How much space does the storage take */
+ _storage_size = 0,
+
+ /* which backend is currently used */
+ _backend = false,
+
+ /* onchange observers */
+ _observers = {},
+
+ /* timeout to wait after onchange event */
+ _observer_timeout = false,
+
+ /* last update time */
+ _observer_update = 0,
+
+ /* pubsub observers */
+ _pubsub_observers = {},
+
+ /* skip published items older than current timestamp */
+ _pubsub_last = +new Date(),
+
+ /* Next check for TTL */
+ _ttl_timeout,
+
+ /**
+ * XML encoding and decoding as XML nodes can't be JSON'ized
+ * XML nodes are encoded and decoded if the node is the value to be saved
+ * but not if it's as a property of another object
+ * Eg. -
+ * $.jStorage.set('key', xmlNode); // IS OK
+ * $.jStorage.set('key', {xml: xmlNode}); // NOT OK
+ */
+ _XMLService = {
+
+ /**
+ * Validates a XML node to be XML
+ * based on jQuery.isXML function
+ */
+ isXML: function(elm) {
+ var documentElement = (elm ? elm.ownerDocument || elm : 0).documentElement;
+ return documentElement ? documentElement.nodeName !== 'HTML' : false;
+ },
+
+ /**
+ * Encodes a XML node to string
+ * based on http://www.mercurytide.co.uk/news/article/issues-when-working-ajax/
+ */
+ encode: function(xmlNode) {
+ if (!this.isXML(xmlNode)) {
+ return false;
+ }
+ try { // Mozilla, Webkit, Opera
+ return new XMLSerializer().serializeToString(xmlNode);
+ } catch (E1) {
+ try { // IE
+ return xmlNode.xml;
+ } catch (E2) {}
+ }
+ return false;
+ },
+
+ /**
+ * Decodes a XML node from string
+ * loosely based on http://outwestmedia.com/jquery-plugins/xmldom/
+ */
+ decode: function(xmlString) {
+ var dom_parser = ('DOMParser' in window && (new DOMParser()).parseFromString) ||
+ (window.ActiveXObject && function(_xmlString) {
+ var xml_doc = new ActiveXObject('Microsoft.XMLDOM');
+ xml_doc.async = 'false';
+ xml_doc.loadXML(_xmlString);
+ return xml_doc;
+ }),
+ resultXML;
+ if (!dom_parser) {
+ return false;
+ }
+ resultXML = dom_parser.call('DOMParser' in window && (new DOMParser()) || window, xmlString, 'text/xml');
+ return this.isXML(resultXML) ? resultXML : false;
+ }
+ };
+
+
+ ////////////////////////// PRIVATE METHODS ////////////////////////
+
+ /**
+ * Initialization function. Detects if the browser supports DOM Storage
+ * or userData behavior and behaves accordingly.
+ */
+ function _init() {
+ /* Check if browser supports localStorage */
+ var localStorageReallyWorks = false;
+ if ('localStorage' in window) {
+ try {
+ window.localStorage.setItem('_tmptest', 'tmpval');
+ localStorageReallyWorks = true;
+ window.localStorage.removeItem('_tmptest');
+ } catch (BogusQuotaExceededErrorOnIos5) {
+ // Thanks be to iOS5 Private Browsing mode which throws
+ // QUOTA_EXCEEDED_ERRROR DOM Exception 22.
+ }
+ }
+
+ if (localStorageReallyWorks) {
+ try {
+ if (window.localStorage) {
+ _storage_service = window.localStorage;
+ _backend = 'localStorage';
+ _observer_update = _storage_service.jStorage_update;
+ }
+ } catch (E3) { /* Firefox fails when touching localStorage and cookies are disabled */ }
+ }
+ /* Check if browser supports globalStorage */
+ else if ('globalStorage' in window) {
+ try {
+ if (window.globalStorage) {
+ if (window.location.hostname == 'localhost') {
+ _storage_service = window.globalStorage['localhost.localdomain'];
+ } else {
+ _storage_service = window.globalStorage[window.location.hostname];
+ }
+ _backend = 'globalStorage';
+ _observer_update = _storage_service.jStorage_update;
+ }
+ } catch (E4) { /* Firefox fails when touching localStorage and cookies are disabled */ }
+ }
+ /* Check if browser supports userData behavior */
+ else {
+ _storage_elm = document.createElement('link');
+ if (_storage_elm.addBehavior) {
+
+ /* Use a DOM element to act as userData storage */
+ _storage_elm.style.behavior = 'url(#default#userData)';
+
+ /* userData element needs to be inserted into the DOM! */
+ document.getElementsByTagName('head')[0].appendChild(_storage_elm);
+
+ try {
+ _storage_elm.load('jStorage');
+ } catch (E) {
+ // try to reset cache
+ _storage_elm.setAttribute('jStorage', '{}');
+ _storage_elm.save('jStorage');
+ _storage_elm.load('jStorage');
+ }
+
+ var data = '{}';
+ try {
+ data = _storage_elm.getAttribute('jStorage');
+ } catch (E5) {}
+
+ try {
+ _observer_update = _storage_elm.getAttribute('jStorage_update');
+ } catch (E6) {}
+
+ _storage_service.jStorage = data;
+ _backend = 'userDataBehavior';
+ } else {
+ _storage_elm = null;
+ return;
+ }
+ }
+
+ // Load data from storage
+ _load_storage();
+
+ // remove dead keys
+ _handleTTL();
+
+ // start listening for changes
+ _setupObserver();
+
+ // initialize publish-subscribe service
+ _handlePubSub();
+
+ // handle cached navigation
+ if ('addEventListener' in window) {
+ window.addEventListener('pageshow', function(event) {
+ if (event.persisted) {
+ _storageObserver();
+ }
+ }, false);
+ }
+ }
+
+ /**
+ * Reload data from storage when needed
+ */
+ function _reloadData() {
+ var data = '{}';
+
+ if (_backend == 'userDataBehavior') {
+ _storage_elm.load('jStorage');
+
+ try {
+ data = _storage_elm.getAttribute('jStorage');
+ } catch (E5) {}
+
+ try {
+ _observer_update = _storage_elm.getAttribute('jStorage_update');
+ } catch (E6) {}
+
+ _storage_service.jStorage = data;
+ }
+
+ _load_storage();
+
+ // remove dead keys
+ _handleTTL();
+
+ _handlePubSub();
+ }
+
+ /**
+ * Sets up a storage change observer
+ */
+ function _setupObserver() {
+ if (_backend == 'localStorage' || _backend == 'globalStorage') {
+ if ('addEventListener' in window) {
+ window.addEventListener('storage', _storageObserver, false);
+ } else {
+ document.attachEvent('onstorage', _storageObserver);
+ }
+ } else if (_backend == 'userDataBehavior') {
+ setInterval(_storageObserver, 1000);
+ }
+ }
+
+ /**
+ * Fired on any kind of data change, needs to check if anything has
+ * really been changed
+ */
+ function _storageObserver() {
+ var updateTime;
+ // cumulate change notifications with timeout
+ clearTimeout(_observer_timeout);
+ _observer_timeout = setTimeout(function() {
+
+ if (_backend == 'localStorage' || _backend == 'globalStorage') {
+ updateTime = _storage_service.jStorage_update;
+ } else if (_backend == 'userDataBehavior') {
+ _storage_elm.load('jStorage');
+ try {
+ updateTime = _storage_elm.getAttribute('jStorage_update');
+ } catch (E5) {}
+ }
+
+ if (updateTime && updateTime != _observer_update) {
+ _observer_update = updateTime;
+ _checkUpdatedKeys();
+ }
+
+ }, 25);
+ }
+
+ /**
+ * Reloads the data and checks if any keys are changed
+ */
+ function _checkUpdatedKeys() {
+ var oldCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32)),
+ newCrc32List;
+
+ _reloadData();
+ newCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32));
+
+ var key,
+ updated = [],
+ removed = [];
+
+ for (key in oldCrc32List) {
+ if (oldCrc32List.hasOwnProperty(key)) {
+ if (!newCrc32List[key]) {
+ removed.push(key);
+ continue;
+ }
+ if (oldCrc32List[key] != newCrc32List[key] && String(oldCrc32List[key]).substr(0, 2) == '2.') {
+ updated.push(key);
+ }
+ }
+ }
+
+ for (key in newCrc32List) {
+ if (newCrc32List.hasOwnProperty(key)) {
+ if (!oldCrc32List[key]) {
+ updated.push(key);
+ }
+ }
+ }
+
+ _fireObservers(updated, 'updated');
+ _fireObservers(removed, 'deleted');
+ }
+
+ /**
+ * Fires observers for updated keys
+ *
+ * @param {Array|String} keys Array of key names or a key
+ * @param {String} action What happened with the value (updated, deleted, flushed)
+ */
+ function _fireObservers(keys, action) {
+ keys = [].concat(keys || []);
+
+ var i, j, len, jlen;
+
+ if (action == 'flushed') {
+ keys = [];
+ for (var key in _observers) {
+ if (_observers.hasOwnProperty(key)) {
+ keys.push(key);
+ }
+ }
+ action = 'deleted';
+ }
+ for (i = 0, len = keys.length; i < len; i++) {
+ if (_observers[keys[i]]) {
+ for (j = 0, jlen = _observers[keys[i]].length; j < jlen; j++) {
+ _observers[keys[i]][j](keys[i], action);
+ }
+ }
+ if (_observers['*']) {
+ for (j = 0, jlen = _observers['*'].length; j < jlen; j++) {
+ _observers['*'][j](keys[i], action);
+ }
+ }
+ }
+ }
+
+ /**
+ * Publishes key change to listeners
+ */
+ function _publishChange() {
+ var updateTime = (+new Date()).toString();
+
+ if (_backend == 'localStorage' || _backend == 'globalStorage') {
+ try {
+ _storage_service.jStorage_update = updateTime;
+ } catch (E8) {
+ // safari private mode has been enabled after the jStorage initialization
+ _backend = false;
+ }
+ } else if (_backend == 'userDataBehavior') {
+ _storage_elm.setAttribute('jStorage_update', updateTime);
+ _storage_elm.save('jStorage');
+ }
+
+ _storageObserver();
+ }
+
+ /**
+ * Loads the data from the storage based on the supported mechanism
+ */
+ function _load_storage() {
+ /* if jStorage string is retrieved, then decode it */
+ if (_storage_service.jStorage) {
+ try {
+ _storage = JSON.parse(String(_storage_service.jStorage));
+ } catch (E6) {
+ _storage_service.jStorage = '{}';
+ }
+ } else {
+ _storage_service.jStorage = '{}';
+ }
+ _storage_size = _storage_service.jStorage ? String(_storage_service.jStorage).length : 0;
+
+ if (!_storage.__jstorage_meta) {
+ _storage.__jstorage_meta = {};
+ }
+ if (!_storage.__jstorage_meta.CRC32) {
+ _storage.__jstorage_meta.CRC32 = {};
+ }
+ }
+
+ /**
+ * This functions provides the 'save' mechanism to store the jStorage object
+ */
+ function _save() {
+ _dropOldEvents(); // remove expired events
+ try {
+ _storage_service.jStorage = JSON.stringify(_storage);
+ // If userData is used as the storage engine, additional
+ if (_storage_elm) {
+ _storage_elm.setAttribute('jStorage', _storage_service.jStorage);
+ _storage_elm.save('jStorage');
+ }
+ _storage_size = _storage_service.jStorage ? String(_storage_service.jStorage).length : 0;
+ } catch (E7) { /* probably cache is full, nothing is saved this way*/ }
+ }
+
+ /**
+ * Function checks if a key is set and is string or numberic
+ *
+ * @param {String} key Key name
+ */
+ function _checkKey(key) {
+ if (typeof key != 'string' && typeof key != 'number') {
+ throw new TypeError('Key name must be string or numeric');
+ }
+ if (key == '__jstorage_meta') {
+ throw new TypeError('Reserved key name');
+ }
+ return true;
+ }
+
+ /**
+ * Removes expired keys
+ */
+ function _handleTTL() {
+ var curtime, i, TTL, CRC32, nextExpire = Infinity,
+ changed = false,
+ deleted = [];
+
+ clearTimeout(_ttl_timeout);
+
+ if (!_storage.__jstorage_meta || typeof _storage.__jstorage_meta.TTL != 'object') {
+ // nothing to do here
+ return;
+ }
+
+ curtime = +new Date();
+ TTL = _storage.__jstorage_meta.TTL;
+
+ CRC32 = _storage.__jstorage_meta.CRC32;
+ for (i in TTL) {
+ if (TTL.hasOwnProperty(i)) {
+ if (TTL[i] <= curtime) {
+ delete TTL[i];
+ delete CRC32[i];
+ delete _storage[i];
+ changed = true;
+ deleted.push(i);
+ } else if (TTL[i] < nextExpire) {
+ nextExpire = TTL[i];
+ }
+ }
+ }
+
+ // set next check
+ if (nextExpire != Infinity) {
+ _ttl_timeout = setTimeout(_handleTTL, Math.min(nextExpire - curtime, 0x7FFFFFFF));
+ }
+
+ // save changes
+ if (changed) {
+ _save();
+ _publishChange();
+ _fireObservers(deleted, 'deleted');
+ }
+ }
+
+ /**
+ * Checks if there's any events on hold to be fired to listeners
+ */
+ function _handlePubSub() {
+ var i, len;
+ if (!_storage.__jstorage_meta.PubSub) {
+ return;
+ }
+ var pubelm,
+ _pubsubCurrent = _pubsub_last,
+ needFired = [];
+
+ for (i = len = _storage.__jstorage_meta.PubSub.length - 1; i >= 0; i--) {
+ pubelm = _storage.__jstorage_meta.PubSub[i];
+ if (pubelm[0] > _pubsub_last) {
+ _pubsubCurrent = pubelm[0];
+ needFired.unshift(pubelm);
+ }
+ }
+
+ for (i = needFired.length - 1; i >= 0; i--) {
+ _fireSubscribers(needFired[i][1], needFired[i][2]);
+ }
+
+ _pubsub_last = _pubsubCurrent;
+ }
+
+ /**
+ * Fires all subscriber listeners for a pubsub channel
+ *
+ * @param {String} channel Channel name
+ * @param {Mixed} payload Payload data to deliver
+ */
+ function _fireSubscribers(channel, payload) {
+ if (_pubsub_observers[channel]) {
+ for (var i = 0, len = _pubsub_observers[channel].length; i < len; i++) {
+ // send immutable data that can't be modified by listeners
+ try {
+ _pubsub_observers[channel][i](channel, JSON.parse(JSON.stringify(payload)));
+ } catch (E) {}
+ }
+ }
+ }
+
+ /**
+ * Remove old events from the publish stream (at least 2sec old)
+ */
+ function _dropOldEvents() {
+ if (!_storage.__jstorage_meta.PubSub) {
+ return;
+ }
+
+ var retire = +new Date() - 2000;
+
+ for (var i = 0, len = _storage.__jstorage_meta.PubSub.length; i < len; i++) {
+ if (_storage.__jstorage_meta.PubSub[i][0] <= retire) {
+ // deleteCount is needed for IE6
+ _storage.__jstorage_meta.PubSub.splice(i, _storage.__jstorage_meta.PubSub.length - i);
+ break;
+ }
+ }
+
+ if (!_storage.__jstorage_meta.PubSub.length) {
+ delete _storage.__jstorage_meta.PubSub;
+ }
+
+ }
+
+ /**
+ * Publish payload to a channel
+ *
+ * @param {String} channel Channel name
+ * @param {Mixed} payload Payload to send to the subscribers
+ */
+ function _publish(channel, payload) {
+ if (!_storage.__jstorage_meta) {
+ _storage.__jstorage_meta = {};
+ }
+ if (!_storage.__jstorage_meta.PubSub) {
+ _storage.__jstorage_meta.PubSub = [];
+ }
+
+ _storage.__jstorage_meta.PubSub.unshift([+new Date(), channel, payload]);
+
+ _save();
+ _publishChange();
+ }
+
+
+ /**
+ * JS Implementation of MurmurHash2
+ *
+ * SOURCE: https://github.com/garycourt/murmurhash-js (MIT licensed)
+ *
+ * @author <a href='mailto:gary.court@gmail.com'>Gary Court</a>
+ * @see http://github.com/garycourt/murmurhash-js
+ * @author <a href='mailto:aappleby@gmail.com'>Austin Appleby</a>
+ * @see http://sites.google.com/site/murmurhash/
+ *
+ * @param {string} str ASCII only
+ * @param {number} seed Positive integer only
+ * @return {number} 32-bit positive integer hash
+ */
+
+ function murmurhash2_32_gc(str, seed) {
+ var
+ l = str.length,
+ h = seed ^ l,
+ i = 0,
+ k;
+
+ while (l >= 4) {
+ k =
+ ((str.charCodeAt(i) & 0xff)) |
+ ((str.charCodeAt(++i) & 0xff) << 8) |
+ ((str.charCodeAt(++i) & 0xff) << 16) |
+ ((str.charCodeAt(++i) & 0xff) << 24);
+
+ k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
+ k ^= k >>> 24;
+ k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
+
+ h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k;
+
+ l -= 4;
+ ++i;
+ }
+
+ switch (l) {
+ case 3:
+ h ^= (str.charCodeAt(i + 2) & 0xff) << 16;
+ /* falls through */
+ case 2:
+ h ^= (str.charCodeAt(i + 1) & 0xff) << 8;
+ /* falls through */
+ case 1:
+ h ^= (str.charCodeAt(i) & 0xff);
+ h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
+ }
+
+ h ^= h >>> 13;
+ h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
+ h ^= h >>> 15;
+
+ return h >>> 0;
+ }
+
+ ////////////////////////// PUBLIC INTERFACE /////////////////////////
+
+ $.jStorage = {
+ /* Version number */
+ version: JSTORAGE_VERSION,
+
+ /**
+ * Sets a key's value.
+ *
+ * @param {String} key Key to set. If this value is not set or not
+ * a string an exception is raised.
+ * @param {Mixed} value Value to set. This can be any value that is JSON
+ * compatible (Numbers, Strings, Objects etc.).
+ * @param {Object} [options] - possible options to use
+ * @param {Number} [options.TTL] - optional TTL value, in milliseconds
+ * @return {Mixed} the used value
+ */
+ set: function(key, value, options) {
+ _checkKey(key);
+
+ options = options || {};
+
+ // undefined values are deleted automatically
+ if (typeof value == 'undefined') {
+ this.deleteKey(key);
+ return value;
+ }
+
+ if (_XMLService.isXML(value)) {
+ value = {
+ _is_xml: true,
+ xml: _XMLService.encode(value)
+ };
+ } else if (typeof value == 'function') {
+ return undefined; // functions can't be saved!
+ } else if (value && typeof value == 'object') {
+ // clone the object before saving to _storage tree
+ value = JSON.parse(JSON.stringify(value));
+ }
+
+ _storage[key] = value;
+
+ _storage.__jstorage_meta.CRC32[key] = '2.' + murmurhash2_32_gc(JSON.stringify(value), 0x9747b28c);
+
+ this.setTTL(key, options.TTL || 0); // also handles saving and _publishChange
+
+ _fireObservers(key, 'updated');
+ return value;
+ },
+
+ /**
+ * Looks up a key in cache
+ *
+ * @param {String} key - Key to look up.
+ * @param {mixed} def - Default value to return, if key didn't exist.
+ * @return {Mixed} the key value, default value or null
+ */
+ get: function(key, def) {
+ _checkKey(key);
+ if (key in _storage) {
+ if (_storage[key] && typeof _storage[key] == 'object' && _storage[key]._is_xml) {
+ return _XMLService.decode(_storage[key].xml);
+ } else {
+ return _storage[key];
+ }
+ }
+ return typeof(def) == 'undefined' ? null : def;
+ },
+
+ /**
+ * Deletes a key from cache.
+ *
+ * @param {String} key - Key to delete.
+ * @return {Boolean} true if key existed or false if it didn't
+ */
+ deleteKey: function(key) {
+ _checkKey(key);
+ if (key in _storage) {
+ delete _storage[key];
+ // remove from TTL list
+ if (typeof _storage.__jstorage_meta.TTL == 'object' &&
+ key in _storage.__jstorage_meta.TTL) {
+ delete _storage.__jstorage_meta.TTL[key];
+ }
+
+ delete _storage.__jstorage_meta.CRC32[key];
+
+ _save();
+ _publishChange();
+ _fireObservers(key, 'deleted');
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Sets a TTL for a key, or remove it if ttl value is 0 or below
+ *
+ * @param {String} key - key to set the TTL for
+ * @param {Number} ttl - TTL timeout in milliseconds
+ * @return {Boolean} true if key existed or false if it didn't
+ */
+ setTTL: function(key, ttl) {
+ var curtime = +new Date();
+ _checkKey(key);
+ ttl = Number(ttl) || 0;
+ if (key in _storage) {
+
+ if (!_storage.__jstorage_meta.TTL) {
+ _storage.__jstorage_meta.TTL = {};
+ }
+
+ // Set TTL value for the key
+ if (ttl > 0) {
+ _storage.__jstorage_meta.TTL[key] = curtime + ttl;
+ } else {
+ delete _storage.__jstorage_meta.TTL[key];
+ }
+
+ _save();
+
+ _handleTTL();
+
+ _publishChange();
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Gets remaining TTL (in milliseconds) for a key or 0 when no TTL has been set
+ *
+ * @param {String} key Key to check
+ * @return {Number} Remaining TTL in milliseconds
+ */
+ getTTL: function(key) {
+ var curtime = +new Date(),
+ ttl;
+ _checkKey(key);
+ if (key in _storage && _storage.__jstorage_meta.TTL && _storage.__jstorage_meta.TTL[key]) {
+ ttl = _storage.__jstorage_meta.TTL[key] - curtime;
+ return ttl || 0;
+ }
+ return 0;
+ },
+
+ /**
+ * Deletes everything in cache.
+ *
+ * @return {Boolean} Always true
+ */
+ flush: function() {
+ _storage = {
+ __jstorage_meta: {
+ CRC32: {}
+ }
+ };
+ _save();
+ _publishChange();
+ _fireObservers(null, 'flushed');
+ return true;
+ },
+
+ /**
+ * Returns a read-only copy of _storage
+ *
+ * @return {Object} Read-only copy of _storage
+ */
+ storageObj: function() {
+ function F() {}
+ F.prototype = _storage;
+ return new F();
+ },
+
+ /**
+ * Returns an index of all used keys as an array
+ * ['key1', 'key2',..'keyN']
+ *
+ * @return {Array} Used keys
+ */
+ index: function() {
+ var index = [],
+ i;
+ for (i in _storage) {
+ if (_storage.hasOwnProperty(i) && i != '__jstorage_meta') {
+ index.push(i);
+ }
+ }
+ return index;
+ },
+
+ /**
+ * How much space in bytes does the storage take?
+ *
+ * @return {Number} Storage size in chars (not the same as in bytes,
+ * since some chars may take several bytes)
+ */
+ storageSize: function() {
+ return _storage_size;
+ },
+
+ /**
+ * Which backend is currently in use?
+ *
+ * @return {String} Backend name
+ */
+ currentBackend: function() {
+ return _backend;
+ },
+
+ /**
+ * Test if storage is available
+ *
+ * @return {Boolean} True if storage can be used
+ */
+ storageAvailable: function() {
+ return !!_backend;
+ },
+
+ /**
+ * Register change listeners
+ *
+ * @param {String} key Key name
+ * @param {Function} callback Function to run when the key changes
+ */
+ listenKeyChange: function(key, callback) {
+ _checkKey(key);
+ if (!_observers[key]) {
+ _observers[key] = [];
+ }
+ _observers[key].push(callback);
+ },
+
+ /**
+ * Remove change listeners
+ *
+ * @param {String} key Key name to unregister listeners against
+ * @param {Function} [callback] If set, unregister the callback, if not - unregister all
+ */
+ stopListening: function(key, callback) {
+ _checkKey(key);
+
+ if (!_observers[key]) {
+ return;
+ }
+
+ if (!callback) {
+ delete _observers[key];
+ return;
+ }
+
+ for (var i = _observers[key].length - 1; i >= 0; i--) {
+ if (_observers[key][i] == callback) {
+ _observers[key].splice(i, 1);
+ }
+ }
+ },
+
+ /**
+ * Subscribe to a Publish/Subscribe event stream
+ *
+ * @param {String} channel Channel name
+ * @param {Function} callback Function to run when the something is published to the channel
+ */
+ subscribe: function(channel, callback) {
+ channel = (channel || '').toString();
+ if (!channel) {
+ throw new TypeError('Channel not defined');
+ }
+ if (!_pubsub_observers[channel]) {
+ _pubsub_observers[channel] = [];
+ }
+ _pubsub_observers[channel].push(callback);
+ },
+
+ /**
+ * Publish data to an event stream
+ *
+ * @param {String} channel Channel name
+ * @param {Mixed} payload Payload to deliver
+ */
+ publish: function(channel, payload) {
+ channel = (channel || '').toString();
+ if (!channel) {
+ throw new TypeError('Channel not defined');
+ }
+
+ _publish(channel, payload);
+ },
+
+ /**
+ * Reloads the data from browser storage
+ */
+ reInit: function() {
+ _reloadData();
+ },
+
+ /**
+ * Removes reference from global objects and saves it as jStorage
+ *
+ * @param {Boolean} option if needed to save object as simple 'jStorage' in windows context
+ */
+ noConflict: function(saveInGlobal) {
+ delete window.$.jStorage;
+
+ if (saveInGlobal) {
+ window.jStorage = this;
+ }
+
+ return this;
+ }
+ };
+
+ // Initialize jStorage
+ _init();
+
+})();
diff --git a/www/wiki/vendor/onoi/shared-resources/res/jquery.async/jquery.async.js b/www/wiki/vendor/onoi/shared-resources/res/jquery.async/jquery.async.js
new file mode 100644
index 00000000..56d1f88b
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/jquery.async/jquery.async.js
@@ -0,0 +1,69 @@
+/*
+ * jQuery Asynchronous Plugin 1.0
+ *
+ * Copyright (c) 2008 Vincent Robert (genezys.net)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ */
+(function($){
+
+// opts.delay : (default 10) delay between async call in ms
+// opts.bulk : (default 500) delay during which the loop can continue synchronously without yielding the CPU
+// opts.test : (default true) function to test in the while test part
+// opts.loop : (default empty) function to call in the while loop part
+// opts.end : (default empty) function to call at the end of the while loop
+$.whileAsync = function(opts) {
+ var delay = Math.abs(opts.delay) || 10,
+ bulk = isNaN(opts.bulk) ? 500 : Math.abs(opts.bulk),
+ test = opts.test || function(){ return true; },
+ loop = opts.loop || function(){},
+ end = opts.end || function(){};
+
+ (function(){
+
+ var t = false,
+ begin = new Date();
+
+ while( t = test() ) {
+ loop();
+ if( bulk === 0 || (new Date() - begin) > bulk ) {
+ break;
+ }
+ }
+ if( t ) {
+ setTimeout(arguments.callee, delay);
+ }
+ else {
+ end();
+ }
+
+ })();
+};
+
+// opts.delay : (default 10) delay between async call in ms
+// opts.bulk : (default 500) delay during which the loop can continue synchronously without yielding the CPU
+// opts.loop : (default empty) function to call in the each loop part, signature: function(index, value) this = value
+// opts.end : (default empty) function to call at the end of the each loop
+$.eachAsync = function(array, opts) {
+ var i = 0,
+ l = array.length,
+ loop = opts.loop || function(){};
+
+ $.whileAsync(
+ $.extend(opts, {
+ test: function() { return i < l; },
+ loop: function() {
+ var val = array[i];
+ return loop.call(val, i++, val);
+ }
+ })
+ );
+};
+
+$.fn.eachAsync = function(opts) {
+ $.eachAsync(this, opts);
+ return this;
+}
+
+})(jQuery); \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/shared-resources/res/jquery.blockUI/jquery.blockUI.js b/www/wiki/vendor/onoi/shared-resources/res/jquery.blockUI/jquery.blockUI.js
new file mode 100644
index 00000000..eddfac3b
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/jquery.blockUI/jquery.blockUI.js
@@ -0,0 +1,620 @@
+/*!
+ * jQuery blockUI plugin
+ * Version 2.70.0-2014.11.23
+ * Requires jQuery v1.7 or later
+ *
+ * Examples at: http://malsup.com/jquery/block/
+ * Copyright (c) 2007-2013 M. Alsup
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * Thanks to Amir-Hossein Sobhi for some excellent contributions!
+ */
+
+;(function() {
+/*jshint eqeqeq:false curly:false latedef:false */
+"use strict";
+
+ function setup($) {
+ $.fn._fadeIn = $.fn.fadeIn;
+
+ var noOp = $.noop || function() {};
+
+ // this bit is to ensure we don't call setExpression when we shouldn't (with extra muscle to handle
+ // confusing userAgent strings on Vista)
+ var msie = /MSIE/.test(navigator.userAgent);
+ var ie6 = /MSIE 6.0/.test(navigator.userAgent) && ! /MSIE 8.0/.test(navigator.userAgent);
+ var mode = document.documentMode || 0;
+ var setExpr = $.isFunction( document.createElement('div').style.setExpression );
+
+ // global $ methods for blocking/unblocking the entire page
+ $.blockUI = function(opts) { install(window, opts); };
+ $.unblockUI = function(opts) { remove(window, opts); };
+
+ // convenience method for quick growl-like notifications (http://www.google.com/search?q=growl)
+ $.growlUI = function(title, message, timeout, onClose) {
+ var $m = $('<div class="growlUI"></div>');
+ if (title) $m.append('<h1>'+title+'</h1>');
+ if (message) $m.append('<h2>'+message+'</h2>');
+ if (timeout === undefined) timeout = 3000;
+
+ // Added by konapun: Set timeout to 30 seconds if this growl is moused over, like normal toast notifications
+ var callBlock = function(opts) {
+ opts = opts || {};
+
+ $.blockUI({
+ message: $m,
+ fadeIn : typeof opts.fadeIn !== 'undefined' ? opts.fadeIn : 700,
+ fadeOut: typeof opts.fadeOut !== 'undefined' ? opts.fadeOut : 1000,
+ timeout: typeof opts.timeout !== 'undefined' ? opts.timeout : timeout,
+ centerY: false,
+ showOverlay: false,
+ onUnblock: onClose,
+ css: $.blockUI.defaults.growlCSS
+ });
+ };
+
+ callBlock();
+ var nonmousedOpacity = $m.css('opacity');
+ $m.mouseover(function() {
+ callBlock({
+ fadeIn: 0,
+ timeout: 30000
+ });
+
+ var displayBlock = $('.blockMsg');
+ displayBlock.stop(); // cancel fadeout if it has started
+ displayBlock.fadeTo(300, 1); // make it easier to read the message by removing transparency
+ }).mouseout(function() {
+ $('.blockMsg').fadeOut(1000);
+ });
+ // End konapun additions
+ };
+
+ // plugin method for blocking element content
+ $.fn.block = function(opts) {
+ if ( this[0] === window ) {
+ $.blockUI( opts );
+ return this;
+ }
+ var fullOpts = $.extend({}, $.blockUI.defaults, opts || {});
+ this.each(function() {
+ var $el = $(this);
+ if (fullOpts.ignoreIfBlocked && $el.data('blockUI.isBlocked'))
+ return;
+ $el.unblock({ fadeOut: 0 });
+ });
+
+ return this.each(function() {
+ if ($.css(this,'position') == 'static') {
+ this.style.position = 'relative';
+ $(this).data('blockUI.static', true);
+ }
+ this.style.zoom = 1; // force 'hasLayout' in ie
+ install(this, opts);
+ });
+ };
+
+ // plugin method for unblocking element content
+ $.fn.unblock = function(opts) {
+ if ( this[0] === window ) {
+ $.unblockUI( opts );
+ return this;
+ }
+ return this.each(function() {
+ remove(this, opts);
+ });
+ };
+
+ $.blockUI.version = 2.70; // 2nd generation blocking at no extra cost!
+
+ // override these in your code to change the default behavior and style
+ $.blockUI.defaults = {
+ // message displayed when blocking (use null for no message)
+ message: '<h1>Please wait...</h1>',
+
+ title: null, // title string; only used when theme == true
+ draggable: true, // only used when theme == true (requires jquery-ui.js to be loaded)
+
+ theme: false, // set to true to use with jQuery UI themes
+
+ // styles for the message when blocking; if you wish to disable
+ // these and use an external stylesheet then do this in your code:
+ // $.blockUI.defaults.css = {};
+ css: {
+ padding: 0,
+ margin: 0,
+ width: '30%',
+ top: '40%',
+ left: '35%',
+ textAlign: 'center',
+ color: '#000',
+ border: '3px solid #aaa',
+ backgroundColor:'#fff',
+ cursor: 'wait'
+ },
+
+ // minimal style set used when themes are used
+ themedCSS: {
+ width: '30%',
+ top: '40%',
+ left: '35%'
+ },
+
+ // styles for the overlay
+ overlayCSS: {
+ backgroundColor: '#000',
+ opacity: 0.6,
+ cursor: 'wait'
+ },
+
+ // style to replace wait cursor before unblocking to correct issue
+ // of lingering wait cursor
+ cursorReset: 'default',
+
+ // styles applied when using $.growlUI
+ growlCSS: {
+ width: '350px',
+ top: '10px',
+ left: '',
+ right: '10px',
+ border: 'none',
+ padding: '5px',
+ opacity: 0.6,
+ cursor: 'default',
+ color: '#fff',
+ backgroundColor: '#000',
+ '-webkit-border-radius':'10px',
+ '-moz-border-radius': '10px',
+ 'border-radius': '10px'
+ },
+
+ // IE issues: 'about:blank' fails on HTTPS and javascript:false is s-l-o-w
+ // (hat tip to Jorge H. N. de Vasconcelos)
+ /*jshint scripturl:true */
+ iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank',
+
+ // force usage of iframe in non-IE browsers (handy for blocking applets)
+ forceIframe: false,
+
+ // z-index for the blocking overlay
+ baseZ: 1000,
+
+ // set these to true to have the message automatically centered
+ centerX: true, // <-- only effects element blocking (page block controlled via css above)
+ centerY: true,
+
+ // allow body element to be stetched in ie6; this makes blocking look better
+ // on "short" pages. disable if you wish to prevent changes to the body height
+ allowBodyStretch: true,
+
+ // enable if you want key and mouse events to be disabled for content that is blocked
+ bindEvents: true,
+
+ // be default blockUI will supress tab navigation from leaving blocking content
+ // (if bindEvents is true)
+ constrainTabKey: true,
+
+ // fadeIn time in millis; set to 0 to disable fadeIn on block
+ fadeIn: 200,
+
+ // fadeOut time in millis; set to 0 to disable fadeOut on unblock
+ fadeOut: 400,
+
+ // time in millis to wait before auto-unblocking; set to 0 to disable auto-unblock
+ timeout: 0,
+
+ // disable if you don't want to show the overlay
+ showOverlay: true,
+
+ // if true, focus will be placed in the first available input field when
+ // page blocking
+ focusInput: true,
+
+ // elements that can receive focus
+ focusableElements: ':input:enabled:visible',
+
+ // suppresses the use of overlay styles on FF/Linux (due to performance issues with opacity)
+ // no longer needed in 2012
+ // applyPlatformOpacityRules: true,
+
+ // callback method invoked when fadeIn has completed and blocking message is visible
+ onBlock: null,
+
+ // callback method invoked when unblocking has completed; the callback is
+ // passed the element that has been unblocked (which is the window object for page
+ // blocks) and the options that were passed to the unblock call:
+ // onUnblock(element, options)
+ onUnblock: null,
+
+ // callback method invoked when the overlay area is clicked.
+ // setting this will turn the cursor to a pointer, otherwise cursor defined in overlayCss will be used.
+ onOverlayClick: null,
+
+ // don't ask; if you really must know: http://groups.google.com/group/jquery-en/browse_thread/thread/36640a8730503595/2f6a79a77a78e493#2f6a79a77a78e493
+ quirksmodeOffsetHack: 4,
+
+ // class name of the message block
+ blockMsgClass: 'blockMsg',
+
+ // if it is already blocked, then ignore it (don't unblock and reblock)
+ ignoreIfBlocked: false
+ };
+
+ // private data and functions follow...
+
+ var pageBlock = null;
+ var pageBlockEls = [];
+
+ function install(el, opts) {
+ var css, themedCSS;
+ var full = (el == window);
+ var msg = (opts && opts.message !== undefined ? opts.message : undefined);
+ opts = $.extend({}, $.blockUI.defaults, opts || {});
+
+ if (opts.ignoreIfBlocked && $(el).data('blockUI.isBlocked'))
+ return;
+
+ opts.overlayCSS = $.extend({}, $.blockUI.defaults.overlayCSS, opts.overlayCSS || {});
+ css = $.extend({}, $.blockUI.defaults.css, opts.css || {});
+ if (opts.onOverlayClick)
+ opts.overlayCSS.cursor = 'pointer';
+
+ themedCSS = $.extend({}, $.blockUI.defaults.themedCSS, opts.themedCSS || {});
+ msg = msg === undefined ? opts.message : msg;
+
+ // remove the current block (if there is one)
+ if (full && pageBlock)
+ remove(window, {fadeOut:0});
+
+ // if an existing element is being used as the blocking content then we capture
+ // its current place in the DOM (and current display style) so we can restore
+ // it when we unblock
+ if (msg && typeof msg != 'string' && (msg.parentNode || msg.jquery)) {
+ var node = msg.jquery ? msg[0] : msg;
+ var data = {};
+ $(el).data('blockUI.history', data);
+ data.el = node;
+ data.parent = node.parentNode;
+ data.display = node.style.display;
+ data.position = node.style.position;
+ if (data.parent)
+ data.parent.removeChild(node);
+ }
+
+ $(el).data('blockUI.onUnblock', opts.onUnblock);
+ var z = opts.baseZ;
+
+ // blockUI uses 3 layers for blocking, for simplicity they are all used on every platform;
+ // layer1 is the iframe layer which is used to supress bleed through of underlying content
+ // layer2 is the overlay layer which has opacity and a wait cursor (by default)
+ // layer3 is the message content that is displayed while blocking
+ var lyr1, lyr2, lyr3, s;
+ if (msie || opts.forceIframe)
+ lyr1 = $('<iframe class="blockUI" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;position:absolute;width:100%;height:100%;top:0;left:0" src="'+opts.iframeSrc+'"></iframe>');
+ else
+ lyr1 = $('<div class="blockUI" style="display:none"></div>');
+
+ if (opts.theme)
+ lyr2 = $('<div class="blockUI blockOverlay ui-widget-overlay" style="z-index:'+ (z++) +';display:none"></div>');
+ else
+ lyr2 = $('<div class="blockUI blockOverlay" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;width:100%;height:100%;top:0;left:0"></div>');
+
+ if (opts.theme && full) {
+ s = '<div class="blockUI ' + opts.blockMsgClass + ' blockPage ui-dialog ui-widget ui-corner-all" style="z-index:'+(z+10)+';display:none;position:fixed">';
+ if ( opts.title ) {
+ s += '<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(opts.title || '&nbsp;')+'</div>';
+ }
+ s += '<div class="ui-widget-content ui-dialog-content"></div>';
+ s += '</div>';
+ }
+ else if (opts.theme) {
+ s = '<div class="blockUI ' + opts.blockMsgClass + ' blockElement ui-dialog ui-widget ui-corner-all" style="z-index:'+(z+10)+';display:none;position:absolute">';
+ if ( opts.title ) {
+ s += '<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(opts.title || '&nbsp;')+'</div>';
+ }
+ s += '<div class="ui-widget-content ui-dialog-content"></div>';
+ s += '</div>';
+ }
+ else if (full) {
+ s = '<div class="blockUI ' + opts.blockMsgClass + ' blockPage" style="z-index:'+(z+10)+';display:none;position:fixed"></div>';
+ }
+ else {
+ s = '<div class="blockUI ' + opts.blockMsgClass + ' blockElement" style="z-index:'+(z+10)+';display:none;position:absolute"></div>';
+ }
+ lyr3 = $(s);
+
+ // if we have a message, style it
+ if (msg) {
+ if (opts.theme) {
+ lyr3.css(themedCSS);
+ lyr3.addClass('ui-widget-content');
+ }
+ else
+ lyr3.css(css);
+ }
+
+ // style the overlay
+ if (!opts.theme /*&& (!opts.applyPlatformOpacityRules)*/)
+ lyr2.css(opts.overlayCSS);
+ lyr2.css('position', full ? 'fixed' : 'absolute');
+
+ // make iframe layer transparent in IE
+ if (msie || opts.forceIframe)
+ lyr1.css('opacity',0.0);
+
+ //$([lyr1[0],lyr2[0],lyr3[0]]).appendTo(full ? 'body' : el);
+ var layers = [lyr1,lyr2,lyr3], $par = full ? $('body') : $(el);
+ $.each(layers, function() {
+ this.appendTo($par);
+ });
+
+ if (opts.theme && opts.draggable && $.fn.draggable) {
+ lyr3.draggable({
+ handle: '.ui-dialog-titlebar',
+ cancel: 'li'
+ });
+ }
+
+ // ie7 must use absolute positioning in quirks mode and to account for activex issues (when scrolling)
+ var expr = setExpr && (!$.support.boxModel || $('object,embed', full ? null : el).length > 0);
+ if (ie6 || expr) {
+ // give body 100% height
+ if (full && opts.allowBodyStretch && $.support.boxModel)
+ $('html,body').css('height','100%');
+
+ // fix ie6 issue when blocked element has a border width
+ if ((ie6 || !$.support.boxModel) && !full) {
+ var t = sz(el,'borderTopWidth'), l = sz(el,'borderLeftWidth');
+ var fixT = t ? '(0 - '+t+')' : 0;
+ var fixL = l ? '(0 - '+l+')' : 0;
+ }
+
+ // simulate fixed position
+ $.each(layers, function(i,o) {
+ var s = o[0].style;
+ s.position = 'absolute';
+ if (i < 2) {
+ if (full)
+ s.setExpression('height','Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.support.boxModel?0:'+opts.quirksmodeOffsetHack+') + "px"');
+ else
+ s.setExpression('height','this.parentNode.offsetHeight + "px"');
+ if (full)
+ s.setExpression('width','jQuery.support.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"');
+ else
+ s.setExpression('width','this.parentNode.offsetWidth + "px"');
+ if (fixL) s.setExpression('left', fixL);
+ if (fixT) s.setExpression('top', fixT);
+ }
+ else if (opts.centerY) {
+ if (full) s.setExpression('top','(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"');
+ s.marginTop = 0;
+ }
+ else if (!opts.centerY && full) {
+ var top = (opts.css && opts.css.top) ? parseInt(opts.css.top, 10) : 0;
+ var expression = '((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"';
+ s.setExpression('top',expression);
+ }
+ });
+ }
+
+ // show the message
+ if (msg) {
+ if (opts.theme)
+ lyr3.find('.ui-widget-content').append(msg);
+ else
+ lyr3.append(msg);
+ if (msg.jquery || msg.nodeType)
+ $(msg).show();
+ }
+
+ if ((msie || opts.forceIframe) && opts.showOverlay)
+ lyr1.show(); // opacity is zero
+ if (opts.fadeIn) {
+ var cb = opts.onBlock ? opts.onBlock : noOp;
+ var cb1 = (opts.showOverlay && !msg) ? cb : noOp;
+ var cb2 = msg ? cb : noOp;
+ if (opts.showOverlay)
+ lyr2._fadeIn(opts.fadeIn, cb1);
+ if (msg)
+ lyr3._fadeIn(opts.fadeIn, cb2);
+ }
+ else {
+ if (opts.showOverlay)
+ lyr2.show();
+ if (msg)
+ lyr3.show();
+ if (opts.onBlock)
+ opts.onBlock.bind(lyr3)();
+ }
+
+ // bind key and mouse events
+ bind(1, el, opts);
+
+ if (full) {
+ pageBlock = lyr3[0];
+ pageBlockEls = $(opts.focusableElements,pageBlock);
+ if (opts.focusInput)
+ setTimeout(focus, 20);
+ }
+ else
+ center(lyr3[0], opts.centerX, opts.centerY);
+
+ if (opts.timeout) {
+ // auto-unblock
+ var to = setTimeout(function() {
+ if (full)
+ $.unblockUI(opts);
+ else
+ $(el).unblock(opts);
+ }, opts.timeout);
+ $(el).data('blockUI.timeout', to);
+ }
+ }
+
+ // remove the block
+ function remove(el, opts) {
+ var count;
+ var full = (el == window);
+ var $el = $(el);
+ var data = $el.data('blockUI.history');
+ var to = $el.data('blockUI.timeout');
+ if (to) {
+ clearTimeout(to);
+ $el.removeData('blockUI.timeout');
+ }
+ opts = $.extend({}, $.blockUI.defaults, opts || {});
+ bind(0, el, opts); // unbind events
+
+ if (opts.onUnblock === null) {
+ opts.onUnblock = $el.data('blockUI.onUnblock');
+ $el.removeData('blockUI.onUnblock');
+ }
+
+ var els;
+ if (full) // crazy selector to handle odd field errors in ie6/7
+ els = $('body').children().filter('.blockUI').add('body > .blockUI');
+ else
+ els = $el.find('>.blockUI');
+
+ // fix cursor issue
+ if ( opts.cursorReset ) {
+ if ( els.length > 1 )
+ els[1].style.cursor = opts.cursorReset;
+ if ( els.length > 2 )
+ els[2].style.cursor = opts.cursorReset;
+ }
+
+ if (full)
+ pageBlock = pageBlockEls = null;
+
+ if (opts.fadeOut) {
+ count = els.length;
+ els.stop().fadeOut(opts.fadeOut, function() {
+ if ( --count === 0)
+ reset(els,data,opts,el);
+ });
+ }
+ else
+ reset(els, data, opts, el);
+ }
+
+ // move blocking element back into the DOM where it started
+ function reset(els,data,opts,el) {
+ var $el = $(el);
+ if ( $el.data('blockUI.isBlocked') )
+ return;
+
+ els.each(function(i,o) {
+ // remove via DOM calls so we don't lose event handlers
+ if (this.parentNode)
+ this.parentNode.removeChild(this);
+ });
+
+ if (data && data.el) {
+ data.el.style.display = data.display;
+ data.el.style.position = data.position;
+ data.el.style.cursor = 'default'; // #59
+ if (data.parent)
+ data.parent.appendChild(data.el);
+ $el.removeData('blockUI.history');
+ }
+
+ if ($el.data('blockUI.static')) {
+ $el.css('position', 'static'); // #22
+ }
+
+ if (typeof opts.onUnblock == 'function')
+ opts.onUnblock(el,opts);
+
+ // fix issue in Safari 6 where block artifacts remain until reflow
+ var body = $(document.body), w = body.width(), cssW = body[0].style.width;
+ body.width(w-1).width(w);
+ body[0].style.width = cssW;
+ }
+
+ // bind/unbind the handler
+ function bind(b, el, opts) {
+ var full = el == window, $el = $(el);
+
+ // don't bother unbinding if there is nothing to unbind
+ if (!b && (full && !pageBlock || !full && !$el.data('blockUI.isBlocked')))
+ return;
+
+ $el.data('blockUI.isBlocked', b);
+
+ // don't bind events when overlay is not in use or if bindEvents is false
+ if (!full || !opts.bindEvents || (b && !opts.showOverlay))
+ return;
+
+ // bind anchors and inputs for mouse and key events
+ var events = 'mousedown mouseup keydown keypress keyup touchstart touchend touchmove';
+ if (b)
+ $(document).bind(events, opts, handler);
+ else
+ $(document).unbind(events, handler);
+
+ // former impl...
+ // var $e = $('a,:input');
+ // b ? $e.bind(events, opts, handler) : $e.unbind(events, handler);
+ }
+
+ // event handler to suppress keyboard/mouse events when blocking
+ function handler(e) {
+ // allow tab navigation (conditionally)
+ if (e.type === 'keydown' && e.keyCode && e.keyCode == 9) {
+ if (pageBlock && e.data.constrainTabKey) {
+ var els = pageBlockEls;
+ var fwd = !e.shiftKey && e.target === els[els.length-1];
+ var back = e.shiftKey && e.target === els[0];
+ if (fwd || back) {
+ setTimeout(function(){focus(back);},10);
+ return false;
+ }
+ }
+ }
+ var opts = e.data;
+ var target = $(e.target);
+ if (target.hasClass('blockOverlay') && opts.onOverlayClick)
+ opts.onOverlayClick(e);
+
+ // allow events within the message content
+ if (target.parents('div.' + opts.blockMsgClass).length > 0)
+ return true;
+
+ // allow events for content that is not being blocked
+ return target.parents().children().filter('div.blockUI').length === 0;
+ }
+
+ function focus(back) {
+ if (!pageBlockEls)
+ return;
+ var e = pageBlockEls[back===true ? pageBlockEls.length-1 : 0];
+ if (e)
+ e.focus();
+ }
+
+ function center(el, x, y) {
+ var p = el.parentNode, s = el.style;
+ var l = ((p.offsetWidth - el.offsetWidth)/2) - sz(p,'borderLeftWidth');
+ var t = ((p.offsetHeight - el.offsetHeight)/2) - sz(p,'borderTopWidth');
+ if (x) s.left = l > 0 ? (l+'px') : '0';
+ if (y) s.top = t > 0 ? (t+'px') : '0';
+ }
+
+ function sz(el, p) {
+ return parseInt($.css(el,p),10)||0;
+ }
+
+ }
+
+
+ /*global define:true */
+ if (typeof define === 'function' && define.amd && define.amd.jQuery) {
+ define(['jquery'], setup);
+ } else {
+ setup(jQuery);
+ }
+
+})(); \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/dataTables.responsive.css b/www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/dataTables.responsive.css
new file mode 100644
index 00000000..72353d2a
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/dataTables.responsive.css
@@ -0,0 +1,178 @@
+table.dataTable.dtr-inline.collapsed > tbody > tr > td.child,
+table.dataTable.dtr-inline.collapsed > tbody > tr > th.child,
+table.dataTable.dtr-inline.collapsed > tbody > tr > td.dataTables_empty {
+ cursor: default !important;
+}
+table.dataTable.dtr-inline.collapsed > tbody > tr > td.child:before,
+table.dataTable.dtr-inline.collapsed > tbody > tr > th.child:before,
+table.dataTable.dtr-inline.collapsed > tbody > tr > td.dataTables_empty:before {
+ display: none !important;
+}
+table.dataTable.dtr-inline.collapsed > tbody > tr > td:first-child,
+table.dataTable.dtr-inline.collapsed > tbody > tr > th:first-child {
+ position: relative;
+ padding-left: 30px;
+ cursor: pointer;
+}
+table.dataTable.dtr-inline.collapsed > tbody > tr > td:first-child:before,
+table.dataTable.dtr-inline.collapsed > tbody > tr > th:first-child:before {
+ top: 9px;
+ left: 4px;
+ height: 14px;
+ width: 14px;
+ display: block;
+ position: absolute;
+ color: white;
+ border: 2px solid white;
+ border-radius: 14px;
+ box-shadow: 0 0 3px #444;
+ box-sizing: content-box;
+ text-align: center;
+ font-family: 'Courier New', Courier, monospace;
+ line-height: 14px;
+ content: '+';
+ background-color: #31b131;
+}
+table.dataTable.dtr-inline.collapsed > tbody > tr.parent > td:first-child:before,
+table.dataTable.dtr-inline.collapsed > tbody > tr.parent > th:first-child:before {
+ content: '-';
+ background-color: #d33333;
+}
+table.dataTable.dtr-inline.collapsed > tbody > tr.child td:before {
+ display: none;
+}
+table.dataTable.dtr-inline.collapsed.compact > tbody > tr > td:first-child,
+table.dataTable.dtr-inline.collapsed.compact > tbody > tr > th:first-child {
+ padding-left: 27px;
+}
+table.dataTable.dtr-inline.collapsed.compact > tbody > tr > td:first-child:before,
+table.dataTable.dtr-inline.collapsed.compact > tbody > tr > th:first-child:before {
+ top: 5px;
+ left: 4px;
+ height: 14px;
+ width: 14px;
+ border-radius: 14px;
+ line-height: 14px;
+ text-indent: 3px;
+}
+table.dataTable.dtr-column > tbody > tr > td.control,
+table.dataTable.dtr-column > tbody > tr > th.control {
+ position: relative;
+ cursor: pointer;
+}
+table.dataTable.dtr-column > tbody > tr > td.control:before,
+table.dataTable.dtr-column > tbody > tr > th.control:before {
+ top: 50%;
+ left: 50%;
+ height: 16px;
+ width: 16px;
+ margin-top: -10px;
+ margin-left: -10px;
+ display: block;
+ position: absolute;
+ color: white;
+ border: 2px solid white;
+ border-radius: 14px;
+ box-shadow: 0 0 3px #444;
+ box-sizing: content-box;
+ text-align: center;
+ font-family: 'Courier New', Courier, monospace;
+ line-height: 14px;
+ content: '+';
+ background-color: #31b131;
+}
+table.dataTable.dtr-column > tbody > tr.parent td.control:before,
+table.dataTable.dtr-column > tbody > tr.parent th.control:before {
+ content: '-';
+ background-color: #d33333;
+}
+table.dataTable > tbody > tr.child {
+ padding: 0.5em 1em;
+}
+table.dataTable > tbody > tr.child:hover {
+ background: transparent !important;
+}
+table.dataTable > tbody > tr.child ul.dtr-details {
+ display: inline-block;
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+}
+table.dataTable > tbody > tr.child ul.dtr-details li {
+ border-bottom: 1px solid #efefef;
+ padding: 0.5em 0;
+}
+table.dataTable > tbody > tr.child ul.dtr-details li:first-child {
+ padding-top: 0;
+}
+table.dataTable > tbody > tr.child ul.dtr-details li:last-child {
+ border-bottom: none;
+}
+table.dataTable > tbody > tr.child span.dtr-title {
+ display: inline-block;
+ min-width: 75px;
+ font-weight: bold;
+}
+
+div.dtr-modal {
+ position: fixed;
+ box-sizing: border-box;
+ top: 0;
+ left: 0;
+ height: 100%;
+ width: 100%;
+ z-index: 100;
+ padding: 10em 1em;
+}
+div.dtr-modal div.dtr-modal-display {
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ width: 50%;
+ height: 50%;
+ overflow: auto;
+ margin: auto;
+ z-index: 102;
+ overflow: auto;
+ background-color: #f5f5f7;
+ border: 1px solid black;
+ border-radius: 0.5em;
+ box-shadow: 0 12px 30px rgba(0, 0, 0, 0.6);
+}
+div.dtr-modal div.dtr-modal-content {
+ position: relative;
+ padding: 1em;
+}
+div.dtr-modal div.dtr-modal-close {
+ position: absolute;
+ top: 6px;
+ right: 6px;
+ width: 22px;
+ height: 22px;
+ border: 1px solid #eaeaea;
+ background-color: #f9f9f9;
+ text-align: center;
+ border-radius: 3px;
+ cursor: pointer;
+ z-index: 12;
+}
+div.dtr-modal div.dtr-modal-close:hover {
+ background-color: #eaeaea;
+}
+div.dtr-modal div.dtr-modal-background {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 101;
+ background: rgba(0, 0, 0, 0.6);
+}
+
+@media screen and (max-width: 767px) {
+ div.dtr-modal div.dtr-modal-display {
+ width: 95%;
+ }
+}
diff --git a/www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/dataTables.responsive.js b/www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/dataTables.responsive.js
new file mode 100644
index 00000000..a1bd5b66
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/dataTables.responsive.js
@@ -0,0 +1,1255 @@
+/*! Responsive 2.1.1
+ * 2014-2016 SpryMedia Ltd - datatables.net/license
+ */
+
+/**
+ * @summary Responsive
+ * @description Responsive tables plug-in for DataTables
+ * @version 2.1.1
+ * @file dataTables.responsive.js
+ * @author SpryMedia Ltd (www.sprymedia.co.uk)
+ * @contact www.sprymedia.co.uk/contact
+ * @copyright Copyright 2014-2016 SpryMedia Ltd.
+ *
+ * This source file is free software, available under the following license:
+ * MIT license - http://datatables.net/license/mit
+ *
+ * This source file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
+ *
+ * For details please refer to: http://www.datatables.net
+ */
+(function( factory ){
+ if ( typeof define === 'function' && define.amd ) {
+ // AMD
+ define( ['jquery', 'datatables.net'], function ( $ ) {
+ return factory( $, window, document );
+ } );
+ }
+ else if ( typeof exports === 'object' ) {
+ // CommonJS
+ module.exports = function (root, $) {
+ if ( ! root ) {
+ root = window;
+ }
+
+ if ( ! $ || ! $.fn.dataTable ) {
+ $ = require('datatables.net')(root, $).$;
+ }
+
+ return factory( $, root, root.document );
+ };
+ }
+ else {
+ // Browser
+ factory( jQuery, window, document );
+ }
+}(function( $, window, document, undefined ) {
+'use strict';
+var DataTable = $.fn.dataTable;
+
+
+/**
+ * Responsive is a plug-in for the DataTables library that makes use of
+ * DataTables' ability to change the visibility of columns, changing the
+ * visibility of columns so the displayed columns fit into the table container.
+ * The end result is that complex tables will be dynamically adjusted to fit
+ * into the viewport, be it on a desktop, tablet or mobile browser.
+ *
+ * Responsive for DataTables has two modes of operation, which can used
+ * individually or combined:
+ *
+ * * Class name based control - columns assigned class names that match the
+ * breakpoint logic can be shown / hidden as required for each breakpoint.
+ * * Automatic control - columns are automatically hidden when there is no
+ * room left to display them. Columns removed from the right.
+ *
+ * In additional to column visibility control, Responsive also has built into
+ * options to use DataTables' child row display to show / hide the information
+ * from the table that has been hidden. There are also two modes of operation
+ * for this child row display:
+ *
+ * * Inline - when the control element that the user can use to show / hide
+ * child rows is displayed inside the first column of the table.
+ * * Column - where a whole column is dedicated to be the show / hide control.
+ *
+ * Initialisation of Responsive is performed by:
+ *
+ * * Adding the class `responsive` or `dt-responsive` to the table. In this case
+ * Responsive will automatically be initialised with the default configuration
+ * options when the DataTable is created.
+ * * Using the `responsive` option in the DataTables configuration options. This
+ * can also be used to specify the configuration options, or simply set to
+ * `true` to use the defaults.
+ *
+ * @class
+ * @param {object} settings DataTables settings object for the host table
+ * @param {object} [opts] Configuration options
+ * @requires jQuery 1.7+
+ * @requires DataTables 1.10.3+
+ *
+ * @example
+ * $('#example').DataTable( {
+ * responsive: true
+ * } );
+ * } );
+ */
+var Responsive = function ( settings, opts ) {
+ // Sanity check that we are using DataTables 1.10 or newer
+ if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.3' ) ) {
+ throw 'DataTables Responsive requires DataTables 1.10.3 or newer';
+ }
+
+ this.s = {
+ dt: new DataTable.Api( settings ),
+ columns: [],
+ current: []
+ };
+
+ // Check if responsive has already been initialised on this table
+ if ( this.s.dt.settings()[0].responsive ) {
+ return;
+ }
+
+ // details is an object, but for simplicity the user can give it as a string
+ // or a boolean
+ if ( opts && typeof opts.details === 'string' ) {
+ opts.details = { type: opts.details };
+ }
+ else if ( opts && opts.details === false ) {
+ opts.details = { type: false };
+ }
+ else if ( opts && opts.details === true ) {
+ opts.details = { type: 'inline' };
+ }
+
+ this.c = $.extend( true, {}, Responsive.defaults, DataTable.defaults.responsive, opts );
+ settings.responsive = this;
+ this._constructor();
+};
+
+$.extend( Responsive.prototype, {
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Constructor
+ */
+
+ /**
+ * Initialise the Responsive instance
+ *
+ * @private
+ */
+ _constructor: function ()
+ {
+ var that = this;
+ var dt = this.s.dt;
+ var dtPrivateSettings = dt.settings()[0];
+ var oldWindowWidth = $(window).width();
+
+ dt.settings()[0]._responsive = this;
+
+ // Use DataTables' throttle function to avoid processor thrashing on
+ // resize
+ $(window).on( 'resize.dtr orientationchange.dtr', DataTable.util.throttle( function () {
+ // iOS has a bug whereby resize can fire when only scrolling
+ // See: http://stackoverflow.com/questions/8898412
+ var width = $(window).width();
+
+ if ( width !== oldWindowWidth ) {
+ that._resize();
+ oldWindowWidth = width;
+ }
+ } ) );
+
+ // DataTables doesn't currently trigger an event when a row is added, so
+ // we need to hook into its private API to enforce the hidden rows when
+ // new data is added
+ dtPrivateSettings.oApi._fnCallbackReg( dtPrivateSettings, 'aoRowCreatedCallback', function (tr, data, idx) {
+ if ( $.inArray( false, that.s.current ) !== -1 ) {
+ $('>td, >th', tr).each( function ( i ) {
+ var idx = dt.column.index( 'toData', i );
+
+ if ( that.s.current[idx] === false ) {
+ $(this).css('display', 'none');
+ }
+ } );
+ }
+ } );
+
+ // Destroy event handler
+ dt.on( 'destroy.dtr', function () {
+ dt.off( '.dtr' );
+ $( dt.table().body() ).off( '.dtr' );
+ $(window).off( 'resize.dtr orientationchange.dtr' );
+
+ // Restore the columns that we've hidden
+ $.each( that.s.current, function ( i, val ) {
+ if ( val === false ) {
+ that._setColumnVis( i, true );
+ }
+ } );
+ } );
+
+ // Reorder the breakpoints array here in case they have been added out
+ // of order
+ this.c.breakpoints.sort( function (a, b) {
+ return a.width < b.width ? 1 :
+ a.width > b.width ? -1 : 0;
+ } );
+
+ this._classLogic();
+ this._resizeAuto();
+
+ // Details handler
+ var details = this.c.details;
+
+ if ( details.type !== false ) {
+ that._detailsInit();
+
+ // DataTables will trigger this event on every column it shows and
+ // hides individually
+ dt.on( 'column-visibility.dtr', function (e, ctx, col, vis) {
+ that._classLogic();
+ that._resizeAuto();
+ that._resize();
+ } );
+
+ // Redraw the details box on each draw which will happen if the data
+ // has changed. This is used until DataTables implements a native
+ // `updated` event for rows
+ dt.on( 'draw.dtr', function () {
+ that._redrawChildren();
+ } );
+
+ $(dt.table().node()).addClass( 'dtr-'+details.type );
+ }
+
+ dt.on( 'column-reorder.dtr', function (e, settings, details) {
+ that._classLogic();
+ that._resizeAuto();
+ that._resize();
+ } );
+
+ // Change in column sizes means we need to calc
+ dt.on( 'column-sizing.dtr', function () {
+ that._resizeAuto();
+ that._resize();
+ });
+
+ // On Ajax reload we want to reopen any child rows which are displayed
+ // by responsive
+ dt.on( 'preXhr.dtr', function () {
+ var rowIds = [];
+ dt.rows().every( function () {
+ if ( this.child.isShown() ) {
+ rowIds.push( this.id(true) );
+ }
+ } );
+
+ dt.one( 'draw.dtr', function () {
+ dt.rows( rowIds ).every( function () {
+ that._detailsDisplay( this, false );
+ } );
+ } );
+ });
+
+ dt.on( 'init.dtr', function (e, settings, details) {
+ that._resizeAuto();
+ that._resize();
+
+ // If columns were hidden, then DataTables needs to adjust the
+ // column sizing
+ if ( $.inArray( false, that.s.current ) ) {
+ dt.columns.adjust();
+ }
+ } );
+
+ // First pass - draw the table for the current viewport size
+ this._resize();
+ },
+
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Private methods
+ */
+
+ /**
+ * Calculate the visibility for the columns in a table for a given
+ * breakpoint. The result is pre-determined based on the class logic if
+ * class names are used to control all columns, but the width of the table
+ * is also used if there are columns which are to be automatically shown
+ * and hidden.
+ *
+ * @param {string} breakpoint Breakpoint name to use for the calculation
+ * @return {array} Array of boolean values initiating the visibility of each
+ * column.
+ * @private
+ */
+ _columnsVisiblity: function ( breakpoint )
+ {
+ var dt = this.s.dt;
+ var columns = this.s.columns;
+ var i, ien;
+
+ // Create an array that defines the column ordering based first on the
+ // column's priority, and secondly the column index. This allows the
+ // columns to be removed from the right if the priority matches
+ var order = columns
+ .map( function ( col, idx ) {
+ return {
+ columnIdx: idx,
+ priority: col.priority
+ };
+ } )
+ .sort( function ( a, b ) {
+ if ( a.priority !== b.priority ) {
+ return a.priority - b.priority;
+ }
+ return a.columnIdx - b.columnIdx;
+ } );
+
+ // Class logic - determine which columns are in this breakpoint based
+ // on the classes. If no class control (i.e. `auto`) then `-` is used
+ // to indicate this to the rest of the function
+ var display = $.map( columns, function ( col ) {
+ return col.auto && col.minWidth === null ?
+ false :
+ col.auto === true ?
+ '-' :
+ $.inArray( breakpoint, col.includeIn ) !== -1;
+ } );
+
+ // Auto column control - first pass: how much width is taken by the
+ // ones that must be included from the non-auto columns
+ var requiredWidth = 0;
+ for ( i=0, ien=display.length ; i<ien ; i++ ) {
+ if ( display[i] === true ) {
+ requiredWidth += columns[i].minWidth;
+ }
+ }
+
+ // Second pass, use up any remaining width for other columns. For
+ // scrolling tables we need to subtract the width of the scrollbar. It
+ // may not be requires which makes this sub-optimal, but it would
+ // require another full redraw to make complete use of those extra few
+ // pixels
+ var scrolling = dt.settings()[0].oScroll;
+ var bar = scrolling.sY || scrolling.sX ? scrolling.iBarWidth : 0;
+ var widthAvailable = dt.table().container().offsetWidth - bar;
+ var usedWidth = widthAvailable - requiredWidth;
+
+ // Control column needs to always be included. This makes it sub-
+ // optimal in terms of using the available with, but to stop layout
+ // thrashing or overflow. Also we need to account for the control column
+ // width first so we know how much width is available for the other
+ // columns, since the control column might not be the first one shown
+ for ( i=0, ien=display.length ; i<ien ; i++ ) {
+ if ( columns[i].control ) {
+ usedWidth -= columns[i].minWidth;
+ }
+ }
+
+ // Allow columns to be shown (counting by priority and then right to
+ // left) until we run out of room
+ var empty = false;
+ for ( i=0, ien=order.length ; i<ien ; i++ ) {
+ var colIdx = order[i].columnIdx;
+
+ if ( display[colIdx] === '-' && ! columns[colIdx].control && columns[colIdx].minWidth ) {
+ // Once we've found a column that won't fit we don't let any
+ // others display either, or columns might disappear in the
+ // middle of the table
+ if ( empty || usedWidth - columns[colIdx].minWidth < 0 ) {
+ empty = true;
+ display[colIdx] = false;
+ }
+ else {
+ display[colIdx] = true;
+ }
+
+ usedWidth -= columns[colIdx].minWidth;
+ }
+ }
+
+ // Determine if the 'control' column should be shown (if there is one).
+ // This is the case when there is a hidden column (that is not the
+ // control column). The two loops look inefficient here, but they are
+ // trivial and will fly through. We need to know the outcome from the
+ // first , before the action in the second can be taken
+ var showControl = false;
+
+ for ( i=0, ien=columns.length ; i<ien ; i++ ) {
+ if ( ! columns[i].control && ! columns[i].never && ! display[i] ) {
+ showControl = true;
+ break;
+ }
+ }
+
+ for ( i=0, ien=columns.length ; i<ien ; i++ ) {
+ if ( columns[i].control ) {
+ display[i] = showControl;
+ }
+ }
+
+ // Finally we need to make sure that there is at least one column that
+ // is visible
+ if ( $.inArray( true, display ) === -1 ) {
+ display[0] = true;
+ }
+
+ return display;
+ },
+
+
+ /**
+ * Create the internal `columns` array with information about the columns
+ * for the table. This includes determining which breakpoints the column
+ * will appear in, based upon class names in the column, which makes up the
+ * vast majority of this method.
+ *
+ * @private
+ */
+ _classLogic: function ()
+ {
+ var that = this;
+ var calc = {};
+ var breakpoints = this.c.breakpoints;
+ var dt = this.s.dt;
+ var columns = dt.columns().eq(0).map( function (i) {
+ var column = this.column(i);
+ var className = column.header().className;
+ var priority = dt.settings()[0].aoColumns[i].responsivePriority;
+
+ if ( priority === undefined ) {
+ var dataPriority = $(column.header()).data('priority');
+
+ priority = dataPriority !== undefined ?
+ dataPriority * 1 :
+ 10000;
+ }
+
+ return {
+ className: className,
+ includeIn: [],
+ auto: false,
+ control: false,
+ never: className.match(/\bnever\b/) ? true : false,
+ priority: priority
+ };
+ } );
+
+ // Simply add a breakpoint to `includeIn` array, ensuring that there are
+ // no duplicates
+ var add = function ( colIdx, name ) {
+ var includeIn = columns[ colIdx ].includeIn;
+
+ if ( $.inArray( name, includeIn ) === -1 ) {
+ includeIn.push( name );
+ }
+ };
+
+ var column = function ( colIdx, name, operator, matched ) {
+ var size, i, ien;
+
+ if ( ! operator ) {
+ columns[ colIdx ].includeIn.push( name );
+ }
+ else if ( operator === 'max-' ) {
+ // Add this breakpoint and all smaller
+ size = that._find( name ).width;
+
+ for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) {
+ if ( breakpoints[i].width <= size ) {
+ add( colIdx, breakpoints[i].name );
+ }
+ }
+ }
+ else if ( operator === 'min-' ) {
+ // Add this breakpoint and all larger
+ size = that._find( name ).width;
+
+ for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) {
+ if ( breakpoints[i].width >= size ) {
+ add( colIdx, breakpoints[i].name );
+ }
+ }
+ }
+ else if ( operator === 'not-' ) {
+ // Add all but this breakpoint
+ for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) {
+ if ( breakpoints[i].name.indexOf( matched ) === -1 ) {
+ add( colIdx, breakpoints[i].name );
+ }
+ }
+ }
+ };
+
+ // Loop over each column and determine if it has a responsive control
+ // class
+ columns.each( function ( col, i ) {
+ var classNames = col.className.split(' ');
+ var hasClass = false;
+
+ // Split the class name up so multiple rules can be applied if needed
+ for ( var k=0, ken=classNames.length ; k<ken ; k++ ) {
+ var className = $.trim( classNames[k] );
+
+ if ( className === 'all' ) {
+ // Include in all
+ hasClass = true;
+ col.includeIn = $.map( breakpoints, function (a) {
+ return a.name;
+ } );
+ return;
+ }
+ else if ( className === 'none' || col.never ) {
+ // Include in none (default) and no auto
+ hasClass = true;
+ return;
+ }
+ else if ( className === 'control' ) {
+ // Special column that is only visible, when one of the other
+ // columns is hidden. This is used for the details control
+ hasClass = true;
+ col.control = true;
+ return;
+ }
+
+ $.each( breakpoints, function ( j, breakpoint ) {
+ // Does this column have a class that matches this breakpoint?
+ var brokenPoint = breakpoint.name.split('-');
+ var re = new RegExp( '(min\\-|max\\-|not\\-)?('+brokenPoint[0]+')(\\-[_a-zA-Z0-9])?' );
+ var match = className.match( re );
+
+ if ( match ) {
+ hasClass = true;
+
+ if ( match[2] === brokenPoint[0] && match[3] === '-'+brokenPoint[1] ) {
+ // Class name matches breakpoint name fully
+ column( i, breakpoint.name, match[1], match[2]+match[3] );
+ }
+ else if ( match[2] === brokenPoint[0] && ! match[3] ) {
+ // Class name matched primary breakpoint name with no qualifier
+ column( i, breakpoint.name, match[1], match[2] );
+ }
+ }
+ } );
+ }
+
+ // If there was no control class, then automatic sizing is used
+ if ( ! hasClass ) {
+ col.auto = true;
+ }
+ } );
+
+ this.s.columns = columns;
+ },
+
+
+ /**
+ * Show the details for the child row
+ *
+ * @param {DataTables.Api} row API instance for the row
+ * @param {boolean} update Update flag
+ * @private
+ */
+ _detailsDisplay: function ( row, update )
+ {
+ var that = this;
+ var dt = this.s.dt;
+ var details = this.c.details;
+
+ if ( details && details.type !== false ) {
+ var res = details.display( row, update, function () {
+ return details.renderer(
+ dt, row[0], that._detailsObj(row[0])
+ );
+ } );
+
+ if ( res === true || res === false ) {
+ $(dt.table().node()).triggerHandler( 'responsive-display.dt', [dt, row, res, update] );
+ }
+ }
+ },
+
+
+ /**
+ * Initialisation for the details handler
+ *
+ * @private
+ */
+ _detailsInit: function ()
+ {
+ var that = this;
+ var dt = this.s.dt;
+ var details = this.c.details;
+
+ // The inline type always uses the first child as the target
+ if ( details.type === 'inline' ) {
+ details.target = 'td:first-child, th:first-child';
+ }
+
+ // Keyboard accessibility
+ dt.on( 'draw.dtr', function () {
+ that._tabIndexes();
+ } );
+ that._tabIndexes(); // Initial draw has already happened
+
+ $( dt.table().body() ).on( 'keyup.dtr', 'td, th', function (e) {
+ if ( e.keyCode === 13 && $(this).data('dtr-keyboard') ) {
+ $(this).click();
+ }
+ } );
+
+ // type.target can be a string jQuery selector or a column index
+ var target = details.target;
+ var selector = typeof target === 'string' ? target : 'td, th';
+
+ // Click handler to show / hide the details rows when they are available
+ $( dt.table().body() )
+ .on( 'click.dtr mousedown.dtr mouseup.dtr', selector, function (e) {
+ // If the table is not collapsed (i.e. there is no hidden columns)
+ // then take no action
+ if ( ! $(dt.table().node()).hasClass('collapsed' ) ) {
+ return;
+ }
+
+ // Check that the row is actually a DataTable's controlled node
+ if ( $.inArray( $(this).closest('tr').get(0), dt.rows().nodes().toArray() ) === -1 ) {
+ return;
+ }
+
+ // For column index, we determine if we should act or not in the
+ // handler - otherwise it is already okay
+ if ( typeof target === 'number' ) {
+ var targetIdx = target < 0 ?
+ dt.columns().eq(0).length + target :
+ target;
+
+ if ( dt.cell( this ).index().column !== targetIdx ) {
+ return;
+ }
+ }
+
+ // $().closest() includes itself in its check
+ var row = dt.row( $(this).closest('tr') );
+
+ // Check event type to do an action
+ if ( e.type === 'click' ) {
+ // The renderer is given as a function so the caller can execute it
+ // only when they need (i.e. if hiding there is no point is running
+ // the renderer)
+ that._detailsDisplay( row, false );
+ }
+ else if ( e.type === 'mousedown' ) {
+ // For mouse users, prevent the focus ring from showing
+ $(this).css('outline', 'none');
+ }
+ else if ( e.type === 'mouseup' ) {
+ // And then re-allow at the end of the click
+ $(this).blur().css('outline', '');
+ }
+ } );
+ },
+
+
+ /**
+ * Get the details to pass to a renderer for a row
+ * @param {int} rowIdx Row index
+ * @private
+ */
+ _detailsObj: function ( rowIdx )
+ {
+ var that = this;
+ var dt = this.s.dt;
+
+ return $.map( this.s.columns, function( col, i ) {
+ // Never and control columns should not be passed to the renderer
+ if ( col.never || col.control ) {
+ return;
+ }
+
+ return {
+ title: dt.settings()[0].aoColumns[ i ].sTitle,
+ data: dt.cell( rowIdx, i ).render( that.c.orthogonal ),
+ hidden: dt.column( i ).visible() && !that.s.current[ i ],
+ columnIndex: i,
+ rowIndex: rowIdx
+ };
+ } );
+ },
+
+
+ /**
+ * Find a breakpoint object from a name
+ *
+ * @param {string} name Breakpoint name to find
+ * @return {object} Breakpoint description object
+ * @private
+ */
+ _find: function ( name )
+ {
+ var breakpoints = this.c.breakpoints;
+
+ for ( var i=0, ien=breakpoints.length ; i<ien ; i++ ) {
+ if ( breakpoints[i].name === name ) {
+ return breakpoints[i];
+ }
+ }
+ },
+
+
+ /**
+ * Re-create the contents of the child rows as the display has changed in
+ * some way.
+ *
+ * @private
+ */
+ _redrawChildren: function ()
+ {
+ var that = this;
+ var dt = this.s.dt;
+
+ dt.rows( {page: 'current'} ).iterator( 'row', function ( settings, idx ) {
+ var row = dt.row( idx );
+
+ that._detailsDisplay( dt.row( idx ), true );
+ } );
+ },
+
+
+ /**
+ * Alter the table display for a resized viewport. This involves first
+ * determining what breakpoint the window currently is in, getting the
+ * column visibilities to apply and then setting them.
+ *
+ * @private
+ */
+ _resize: function ()
+ {
+ var that = this;
+ var dt = this.s.dt;
+ var width = $(window).width();
+ var breakpoints = this.c.breakpoints;
+ var breakpoint = breakpoints[0].name;
+ var columns = this.s.columns;
+ var i, ien;
+ var oldVis = this.s.current.slice();
+
+ // Determine what breakpoint we are currently at
+ for ( i=breakpoints.length-1 ; i>=0 ; i-- ) {
+ if ( width <= breakpoints[i].width ) {
+ breakpoint = breakpoints[i].name;
+ break;
+ }
+ }
+
+ // Show the columns for that break point
+ var columnsVis = this._columnsVisiblity( breakpoint );
+ this.s.current = columnsVis;
+
+ // Set the class before the column visibility is changed so event
+ // listeners know what the state is. Need to determine if there are
+ // any columns that are not visible but can be shown
+ var collapsedClass = false;
+ for ( i=0, ien=columns.length ; i<ien ; i++ ) {
+ if ( columnsVis[i] === false && ! columns[i].never && ! columns[i].control ) {
+ collapsedClass = true;
+ break;
+ }
+ }
+
+ $( dt.table().node() ).toggleClass( 'collapsed', collapsedClass );
+
+ var changed = false;
+
+ dt.columns().eq(0).each( function ( colIdx, i ) {
+ if ( columnsVis[i] !== oldVis[i] ) {
+ changed = true;
+ that._setColumnVis( colIdx, columnsVis[i] );
+ }
+ } );
+
+ if ( changed ) {
+ this._redrawChildren();
+
+ // Inform listeners of the change
+ $(dt.table().node()).trigger( 'responsive-resize.dt', [dt, this.s.current] );
+ }
+ },
+
+
+ /**
+ * Determine the width of each column in the table so the auto column hiding
+ * has that information to work with. This method is never going to be 100%
+ * perfect since column widths can change slightly per page, but without
+ * seriously compromising performance this is quite effective.
+ *
+ * @private
+ */
+ _resizeAuto: function ()
+ {
+ var dt = this.s.dt;
+ var columns = this.s.columns;
+
+ // Are we allowed to do auto sizing?
+ if ( ! this.c.auto ) {
+ return;
+ }
+
+ // Are there any columns that actually need auto-sizing, or do they all
+ // have classes defined
+ if ( $.inArray( true, $.map( columns, function (c) { return c.auto; } ) ) === -1 ) {
+ return;
+ }
+
+ // Clone the table with the current data in it
+ var tableWidth = dt.table().node().offsetWidth;
+ var columnWidths = dt.columns;
+ var clonedTable = dt.table().node().cloneNode( false );
+ var clonedHeader = $( dt.table().header().cloneNode( false ) ).appendTo( clonedTable );
+ var clonedBody = $( dt.table().body() ).clone( false, false ).empty().appendTo( clonedTable ); // use jQuery because of IE8
+
+ // Header
+ var headerCells = dt.columns()
+ .header()
+ .filter( function (idx) {
+ return dt.column(idx).visible();
+ } )
+ .to$()
+ .clone( false )
+ .css( 'display', 'table-cell' );
+
+ // Body rows - we don't need to take account of DataTables' column
+ // visibility since we implement our own here (hence the `display` set)
+ $(clonedBody)
+ .append( $(dt.rows( { page: 'current' } ).nodes()).clone( false ) )
+ .find( 'th, td' ).css( 'display', '' );
+
+ // Footer
+ var footer = dt.table().footer();
+ if ( footer ) {
+ var clonedFooter = $( footer.cloneNode( false ) ).appendTo( clonedTable );
+ var footerCells = dt.columns()
+ .footer()
+ .filter( function (idx) {
+ return dt.column(idx).visible();
+ } )
+ .to$()
+ .clone( false )
+ .css( 'display', 'table-cell' );
+
+ $('<tr/>')
+ .append( footerCells )
+ .appendTo( clonedFooter );
+ }
+
+ $('<tr/>')
+ .append( headerCells )
+ .appendTo( clonedHeader );
+
+ // In the inline case extra padding is applied to the first column to
+ // give space for the show / hide icon. We need to use this in the
+ // calculation
+ if ( this.c.details.type === 'inline' ) {
+ $(clonedTable).addClass( 'dtr-inline collapsed' );
+ }
+
+ // It is unsafe to insert elements with the same name into the DOM
+ // multiple times. For example, cloning and inserting a checked radio
+ // clears the chcecked state of the original radio.
+ $( clonedTable ).find( '[name]' ).removeAttr( 'name' );
+
+ var inserted = $('<div/>')
+ .css( {
+ width: 1,
+ height: 1,
+ overflow: 'hidden'
+ } )
+ .append( clonedTable );
+
+ inserted.insertBefore( dt.table().node() );
+
+ // The cloned header now contains the smallest that each column can be
+ headerCells.each( function (i) {
+ var idx = dt.column.index( 'fromVisible', i );
+ columns[ idx ].minWidth = this.offsetWidth || 0;
+ } );
+
+ inserted.remove();
+ },
+
+ /**
+ * Set a column's visibility.
+ *
+ * We don't use DataTables' column visibility controls in order to ensure
+ * that column visibility can Responsive can no-exist. Since only IE8+ is
+ * supported (and all evergreen browsers of course) the control of the
+ * display attribute works well.
+ *
+ * @param {integer} col Column index
+ * @param {boolean} showHide Show or hide (true or false)
+ * @private
+ */
+ _setColumnVis: function ( col, showHide )
+ {
+ var dt = this.s.dt;
+ var display = showHide ? '' : 'none'; // empty string will remove the attr
+
+ $( dt.column( col ).header() ).css( 'display', display );
+ $( dt.column( col ).footer() ).css( 'display', display );
+ dt.column( col ).nodes().to$().css( 'display', display );
+ },
+
+
+ /**
+ * Update the cell tab indexes for keyboard accessibility. This is called on
+ * every table draw - that is potentially inefficient, but also the least
+ * complex option given that column visibility can change on the fly. Its a
+ * shame user-focus was removed from CSS 3 UI, as it would have solved this
+ * issue with a single CSS statement.
+ *
+ * @private
+ */
+ _tabIndexes: function ()
+ {
+ var dt = this.s.dt;
+ var cells = dt.cells( { page: 'current' } ).nodes().to$();
+ var ctx = dt.settings()[0];
+ var target = this.c.details.target;
+
+ cells.filter( '[data-dtr-keyboard]' ).removeData( '[data-dtr-keyboard]' );
+
+ var selector = typeof target === 'number' ?
+ ':eq('+target+')' :
+ target;
+
+ // This is a bit of a hack - we need to limit the selected nodes to just
+ // those of this table
+ if ( selector === 'td:first-child, th:first-child' ) {
+ selector = '>td:first-child, >th:first-child';
+ }
+
+ $( selector, dt.rows( { page: 'current' } ).nodes() )
+ .attr( 'tabIndex', ctx.iTabIndex )
+ .data( 'dtr-keyboard', 1 );
+ }
+} );
+
+
+/**
+ * List of default breakpoints. Each item in the array is an object with two
+ * properties:
+ *
+ * * `name` - the breakpoint name.
+ * * `width` - the breakpoint width
+ *
+ * @name Responsive.breakpoints
+ * @static
+ */
+Responsive.breakpoints = [
+ { name: 'desktop', width: Infinity },
+ { name: 'tablet-l', width: 1024 },
+ { name: 'tablet-p', width: 768 },
+ { name: 'mobile-l', width: 480 },
+ { name: 'mobile-p', width: 320 }
+];
+
+
+/**
+ * Display methods - functions which define how the hidden data should be shown
+ * in the table.
+ *
+ * @namespace
+ * @name Responsive.defaults
+ * @static
+ */
+Responsive.display = {
+ childRow: function ( row, update, render ) {
+ if ( update ) {
+ if ( $(row.node()).hasClass('parent') ) {
+ row.child( render(), 'child' ).show();
+
+ return true;
+ }
+ }
+ else {
+ if ( ! row.child.isShown() ) {
+ row.child( render(), 'child' ).show();
+ $( row.node() ).addClass( 'parent' );
+
+ return true;
+ }
+ else {
+ row.child( false );
+ $( row.node() ).removeClass( 'parent' );
+
+ return false;
+ }
+ }
+ },
+
+ childRowImmediate: function ( row, update, render ) {
+ if ( (! update && row.child.isShown()) || ! row.responsive.hasHidden() ) {
+ // User interaction and the row is show, or nothing to show
+ row.child( false );
+ $( row.node() ).removeClass( 'parent' );
+
+ return false;
+ }
+ else {
+ // Display
+ row.child( render(), 'child' ).show();
+ $( row.node() ).addClass( 'parent' );
+
+ return true;
+ }
+ },
+
+ // This is a wrapper so the modal options for Bootstrap and jQuery UI can
+ // have options passed into them. This specific one doesn't need to be a
+ // function but it is for consistency in the `modal` name
+ modal: function ( options ) {
+ return function ( row, update, render ) {
+ if ( ! update ) {
+ // Show a modal
+ var close = function () {
+ modal.remove(); // will tidy events for us
+ $(document).off( 'keypress.dtr' );
+ };
+
+ var modal = $('<div class="dtr-modal"/>')
+ .append( $('<div class="dtr-modal-display"/>')
+ .append( $('<div class="dtr-modal-content"/>')
+ .append( render() )
+ )
+ .append( $('<div class="dtr-modal-close">&times;</div>' )
+ .click( function () {
+ close();
+ } )
+ )
+ )
+ .append( $('<div class="dtr-modal-background"/>')
+ .click( function () {
+ close();
+ } )
+ )
+ .appendTo( 'body' );
+
+ $(document).on( 'keyup.dtr', function (e) {
+ if ( e.keyCode === 27 ) {
+ e.stopPropagation();
+
+ close();
+ }
+ } );
+ }
+ else {
+ $('div.dtr-modal-content')
+ .empty()
+ .append( render() );
+ }
+
+ if ( options && options.header ) {
+ $('div.dtr-modal-content').prepend(
+ '<h2>'+options.header( row )+'</h2>'
+ );
+ }
+ };
+ }
+};
+
+
+/**
+ * Display methods - functions which define how the hidden data should be shown
+ * in the table.
+ *
+ * @namespace
+ * @name Responsive.defaults
+ * @static
+ */
+Responsive.renderer = {
+ listHidden: function () {
+ return function ( api, rowIdx, columns ) {
+ var data = $.map( columns, function ( col ) {
+ return col.hidden ?
+ '<li data-dtr-index="'+col.columnIndex+'" data-dt-row="'+col.rowIndex+'" data-dt-column="'+col.columnIndex+'">'+
+ '<span class="dtr-title">'+
+ col.title+
+ '</span> '+
+ '<span class="dtr-data">'+
+ col.data+
+ '</span>'+
+ '</li>' :
+ '';
+ } ).join('');
+
+ return data ?
+ $('<ul data-dtr-index="'+rowIdx+'" class="dtr-details"/>').append( data ) :
+ false;
+ }
+ },
+
+ tableAll: function ( options ) {
+ options = $.extend( {
+ tableClass: ''
+ }, options );
+
+ return function ( api, rowIdx, columns ) {
+ var data = $.map( columns, function ( col ) {
+ return '<tr data-dt-row="'+col.rowIndex+'" data-dt-column="'+col.columnIndex+'">'+
+ '<td>'+col.title+':'+'</td> '+
+ '<td>'+col.data+'</td>'+
+ '</tr>';
+ } ).join('');
+
+ return $('<table class="'+options.tableClass+' dtr-details" width="100%"/>').append( data );
+ }
+ }
+};
+
+/**
+ * Responsive default settings for initialisation
+ *
+ * @namespace
+ * @name Responsive.defaults
+ * @static
+ */
+Responsive.defaults = {
+ /**
+ * List of breakpoints for the instance. Note that this means that each
+ * instance can have its own breakpoints. Additionally, the breakpoints
+ * cannot be changed once an instance has been creased.
+ *
+ * @type {Array}
+ * @default Takes the value of `Responsive.breakpoints`
+ */
+ breakpoints: Responsive.breakpoints,
+
+ /**
+ * Enable / disable auto hiding calculations. It can help to increase
+ * performance slightly if you disable this option, but all columns would
+ * need to have breakpoint classes assigned to them
+ *
+ * @type {Boolean}
+ * @default `true`
+ */
+ auto: true,
+
+ /**
+ * Details control. If given as a string value, the `type` property of the
+ * default object is set to that value, and the defaults used for the rest
+ * of the object - this is for ease of implementation.
+ *
+ * The object consists of the following properties:
+ *
+ * * `display` - A function that is used to show and hide the hidden details
+ * * `renderer` - function that is called for display of the child row data.
+ * The default function will show the data from the hidden columns
+ * * `target` - Used as the selector for what objects to attach the child
+ * open / close to
+ * * `type` - `false` to disable the details display, `inline` or `column`
+ * for the two control types
+ *
+ * @type {Object|string}
+ */
+ details: {
+ display: Responsive.display.childRow,
+
+ renderer: Responsive.renderer.listHidden(),
+
+ target: 0,
+
+ type: 'inline'
+ },
+
+ /**
+ * Orthogonal data request option. This is used to define the data type
+ * requested when Responsive gets the data to show in the child row.
+ *
+ * @type {String}
+ */
+ orthogonal: 'display'
+};
+
+
+/*
+ * API
+ */
+var Api = $.fn.dataTable.Api;
+
+// Doesn't do anything - work around for a bug in DT... Not documented
+Api.register( 'responsive()', function () {
+ return this;
+} );
+
+Api.register( 'responsive.index()', function ( li ) {
+ li = $(li);
+
+ return {
+ column: li.data('dtr-index'),
+ row: li.parent().data('dtr-index')
+ };
+} );
+
+Api.register( 'responsive.rebuild()', function () {
+ return this.iterator( 'table', function ( ctx ) {
+ if ( ctx._responsive ) {
+ ctx._responsive._classLogic();
+ }
+ } );
+} );
+
+Api.register( 'responsive.recalc()', function () {
+ return this.iterator( 'table', function ( ctx ) {
+ if ( ctx._responsive ) {
+ ctx._responsive._resizeAuto();
+ ctx._responsive._resize();
+ }
+ } );
+} );
+
+Api.register( 'responsive.hasHidden()', function () {
+ var ctx = this.context[0];
+
+ return ctx._responsive ?
+ $.inArray( false, ctx._responsive.s.current ) !== -1 :
+ false;
+} );
+
+
+/**
+ * Version information
+ *
+ * @name Responsive.version
+ * @static
+ */
+Responsive.version = '2.1.1';
+
+
+$.fn.dataTable.Responsive = Responsive;
+$.fn.DataTable.Responsive = Responsive;
+
+// Attach a listener to the document which listens for DataTables initialisation
+// events so we can automatically initialise
+$(document).on( 'preInit.dt.dtr', function (e, settings, json) {
+ if ( e.namespace !== 'dt' ) {
+ return;
+ }
+
+ if ( $(settings.nTable).hasClass( 'responsive' ) ||
+ $(settings.nTable).hasClass( 'dt-responsive' ) ||
+ settings.oInit.responsive ||
+ DataTable.defaults.responsive
+ ) {
+ var init = settings.oInit.responsive;
+
+ if ( init !== false ) {
+ new Responsive( settings, $.isPlainObject( init ) ? init : {} );
+ }
+ }
+} );
+
+
+return Responsive;
+}));
diff --git a/www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/dataTables.search.js b/www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/dataTables.search.js
new file mode 100644
index 00000000..1c71ecf5
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/dataTables.search.js
@@ -0,0 +1,77 @@
+/**
+ * @license GNU GPL v2+
+ * @since 3.0
+ *
+ * @author mwjames
+ */
+
+/*global jQuery */
+( function ( $ ) {
+
+ 'use strict';
+
+ /**
+ * @since 3.0
+ *
+ * @param {String} text
+ *
+ * @return {String}
+ */
+ function replace( text ) {
+ return text
+ .replace(/[áÁàÀâÂäÄãÃåÅæÆâ]/g, 'a')
+ .replace(/[çÇ]/g, 'c')
+ .replace(/[éÉèÈêÊëËê]/g, 'e')
+ .replace(/[íÍìÌîÎïÏîĩĨĬĭ]/g, 'i')
+ .replace(/[ñÑ]/g, 'n')
+ .replace(/[óÓòÒôÔöÖœŒ]/g, 'o')
+ .replace(/[ß]/g, 's')
+ .replace(/[úÚùÙûÛüÜ]/g, 'u')
+ .replace(/[ýÝŷŶŸÿ]/g, 'n')
+ .replace( /έ/g, 'ε' )
+ .replace( /[ύϋΰ]/g, 'υ' )
+ .replace( /ό/g, 'ο' )
+ .replace( /ώ/g, 'ω' )
+ .replace( /ά/g, 'α' )
+ .replace( /[ίϊΐ]/g, 'ι' )
+ .replace( /ή/g, 'η' )
+ .replace( /\n/g, ' ' )
+ .replace( /á/g, 'a' )
+ .replace( /é/g, 'e' )
+ .replace( /í/g, 'i' )
+ .replace( /ó/g, 'o' )
+ .replace( /ú/g, 'u' )
+ .replace( /ê/g, 'e' )
+ .replace( /î/g, 'i' )
+ .replace( /ô/g, 'o' )
+ .replace( /è/g, 'e' )
+ .replace( /ï/g, 'i' )
+ .replace( /ü/g, 'u' )
+ .replace( /ã/g, 'a' )
+ .replace( /õ/g, 'o' )
+ .replace( /ç/g, 'c' )
+ .replace( /ì/g, 'i' );
+ }
+
+ /**
+ * @see https://github.com/DataTables/Plugins/blob/master/filtering/type-based/accent-neutralise.js
+ *
+ * @param {[type]} data [description]
+ * @return {[type]} [description]
+ */
+ $.fn.dataTable.ext.type.search.string = function ( data ) {
+ return ! data ? '' : typeof data === 'string' ? replace( data ) : data;
+ };
+
+ /**
+ * @see https://datatables.net/plug-ins/filtering/type-based/html
+ * @see https://datatables.net//forums/discussion/comment/98704/#Comment_98704
+ */
+ var _div = document.createElement('div');
+
+ $.fn.dataTable.ext.type.search.html = function( data ) {
+ _div.innerHTML = data;
+ return _div.textContent ? replace( _div.textContent ) : replace( _div.innerText );
+ };
+
+}( jQuery ) );
diff --git a/www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/dataTables.searchHighlight.css b/www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/dataTables.searchHighlight.css
new file mode 100644
index 00000000..d037aba2
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/dataTables.searchHighlight.css
@@ -0,0 +1,11 @@
+
+
+table.dataTable span.highlight {
+ background-color: #FFFF88;
+ border-radius: 0.28571429rem;
+}
+
+table.dataTable span.column_highlight {
+ background-color: #ffcc99;
+ border-radius: 0.28571429rem;
+} \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/dataTables.searchHighlight.js b/www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/dataTables.searchHighlight.js
new file mode 100644
index 00000000..f8b8763a
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/dataTables.searchHighlight.js
@@ -0,0 +1,87 @@
+/*! SearchHighlight for DataTables v1.0.1
+ * 2014 SpryMedia Ltd - datatables.net/license
+ */
+
+/**
+ * @summary SearchHighlight
+ * @description Search term highlighter for DataTables
+ * @version 1.0.1
+ * @file dataTables.searchHighlight.js
+ * @author SpryMedia Ltd (www.sprymedia.co.uk)
+ * @contact www.sprymedia.co.uk/contact
+ * @copyright Copyright 2014 SpryMedia Ltd.
+ *
+ * License MIT - http://datatables.net/license/mit
+ *
+ * This feature plug-in for DataTables will highlight search terms in the
+ * DataTable as they are entered into the main search input element, or via the
+ * `search()` API method.
+ *
+ * It depends upon the jQuery Highlight plug-in by Bartek Szopka:
+ * http://bartaz.github.io/sandbox.js/jquery.highlight.js
+ *
+ * Search highlighting in DataTables can be enabled by:
+ *
+ * * Adding the class `searchHighlight` to the HTML table
+ * * Setting the `searchHighlight` parameter in the DataTables initialisation to
+ * be true
+ * * Setting the `searchHighlight` parameter to be true in the DataTables
+ * defaults (thus causing all tables to have this feature) - i.e.
+ * `$.fn.dataTable.defaults.searchHighlight = true`.
+ *
+ * For more detailed information please see:
+ * http://datatables.net/blog/2014-10-22
+ */
+
+(function(window, document, $){
+
+
+function highlight( body, table )
+{
+ // Removing the old highlighting first
+ body.unhighlight();
+
+ // Don't highlight the "not found" row, so we get the rows using the api
+ if ( table.rows( { filter: 'applied' } ).data().length ) {
+ table.columns().every( function () {
+ var column = this;
+ column.nodes().flatten().to$().unhighlight({ className: 'column_highlight' });
+ column.nodes().flatten().to$().highlight( $.trim( column.search() ).split(/\s+/), { className: 'column_highlight' } );
+ } );
+ body.highlight( $.trim( table.search() ).split(/\s+/) );
+ }
+}
+
+
+// Listen for DataTables initialisations
+$(document).on( 'init.dt.dth', function (e, settings, json) {
+ if ( e.namespace !== 'dt' ) {
+ return;
+ }
+
+ var table = new $.fn.dataTable.Api( settings );
+ var body = $( table.table().body() );
+
+ if (
+ $( table.table().node() ).hasClass( 'searchHighlight' ) || // table has class
+ settings.oInit.searchHighlight || // option specified
+ $.fn.dataTable.defaults.searchHighlight // default set
+ ) {
+ table
+ .on( 'draw.dt.dth column-visibility.dt.dth column-reorder.dt.dth', function () {
+ highlight( body, table );
+ } )
+ .on( 'destroy', function () {
+ // Remove event handler
+ table.off( 'draw.dt.dth column-visibility.dt.dth column-reorder.dt.dth' );
+ } );
+
+ // initial highlight for state saved conditions and initial states
+ if ( table.search() ) {
+ highlight( body, table );
+ }
+ }
+} );
+
+
+})(window, document, jQuery); \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/jquery.dataTables.css b/www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/jquery.dataTables.css
new file mode 100644
index 00000000..5f9c82eb
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/jquery.dataTables.css
@@ -0,0 +1,459 @@
+/**
+ * Removed some !important attributes, replaced images with base64 complement
+ */
+/*
+ * Table styles
+ */
+table.dataTable {
+ width: 100%;
+ margin: 0 auto;
+ clear: both;
+ border-collapse: separate;
+ border-spacing: 0;
+ /*
+ * Header and footer styles
+ */
+ /*
+ * Body styles
+ */
+}
+table.dataTable thead th,
+table.dataTable tfoot th {
+ font-weight: bold;
+}
+table.dataTable thead th,
+table.dataTable thead td {
+ padding: 10px 18px;
+ border-bottom: 1px solid #111;
+}
+table.dataTable thead th:active,
+table.dataTable thead td:active {
+ outline: none;
+}
+table.dataTable tfoot th,
+table.dataTable tfoot td {
+ padding: 10px 18px 6px 18px;
+ border-top: 1px solid #111;
+}
+table.dataTable thead .sorting,
+table.dataTable thead .sorting_asc,
+table.dataTable thead .sorting_desc,
+table.dataTable thead .sorting_asc_disabled,
+table.dataTable thead .sorting_desc_disabled {
+ cursor: pointer;
+ *cursor: hand;
+}
+table.dataTable thead .sorting,
+table.dataTable thead .sorting_asc,
+table.dataTable thead .sorting_desc,
+table.dataTable thead .sorting_asc_disabled,
+table.dataTable thead .sorting_desc_disabled {
+ background-repeat: no-repeat;
+ background-position: center right;
+}
+
+table.dataTable thead .sorting {
+ /* @embed */ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAAkElEQVQoz7XQMQ5AQBCF4dWQSJxC5wwax1Cq1e7BAdxD5SL+Tq/QCM1oNiJidwox0355mXnG/DrEtIQ6azioNZQxI0ykPhTQIwhCR+BmBYtlK7kLJYwWCcJA9M4qdrZrd8pPjZWPtOqdRQy320YSV17OatFC4euts6z39GYMKRPCTKY9UnPQ6P+GtMRfGtPnBCiqhAeJPmkqAAAAAElFTkSuQmCC);
+}
+table.dataTable thead .sorting_asc {
+ /* @embed */ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZ0lEQVQ4y2NgGLKgquEuFxBPAGI2ahhWCsS/gDibUoO0gPgxEP8H4ttArEyuQYxAPBdqEAxPBImTY5gjEL9DM+wTENuQahAvEO9DMwiGdwAxOymGJQLxTyD+jgWDxCMZRsEoGAVoAADeemwtPcZI2wAAAABJRU5ErkJggg==);
+}
+table.dataTable thead .sorting_desc {
+ /* @embed */ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZUlEQVQ4y2NgGAWjYBSggaqGu5FA/BOIv2PBIPFEUgxjB+IdQPwfC94HxLykus4GiD+hGfQOiB3J8SojEE9EM2wuSJzcsFMG4ttQgx4DsRalkZENxL+AuJQaMcsGxBOAmGvopk8AVz1sLZgg0bsAAAAASUVORK5CYII=);
+}
+table.dataTable thead .sorting_asc_disabled {
+ /* @embed */ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAAW0lEQVQoz2NgoCm4w3Vnwh02wspK7/y6k01Ikdadx3f+37l9RxmfIsY7c4GKQHDiHUbcyhzvvIMq+3THBpci3jv7oIpAcMcdduzKEu/8vPMdDn/eiWQYBYMKAAC3ykIEuYQJUgAAAABJRU5ErkJggg==);
+}
+table.dataTable thead .sorting_desc_disabled {
+ /* @embed */ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAAWUlEQVQoz2NgGAWDCtyJvPPzznc4/HknEbsy9js77vyHw313eHGZZ3PnE1TRuzuOuK1lvDMRqmzuHUZ87lO+cxuo6PEdLUIeyb7z604pYf+y3Zlwh4u2YQoAc7ZCBHH4jigAAAAASUVORK5CYII=);
+}
+table.dataTable tbody tr {
+ background-color: #ffffff;
+}
+table.dataTable tbody tr.selected {
+ background-color: #B0BED9;
+}
+table.dataTable tbody th,
+table.dataTable tbody td {
+ padding: 8px 10px;
+}
+table.dataTable.row-border tbody th, table.dataTable.row-border tbody td, table.dataTable.display tbody th, table.dataTable.display tbody td {
+ border-top: 1px solid #ddd;
+}
+table.dataTable.row-border tbody tr:first-child th,
+table.dataTable.row-border tbody tr:first-child td, table.dataTable.display tbody tr:first-child th,
+table.dataTable.display tbody tr:first-child td {
+ border-top: none;
+}
+table.dataTable.cell-border tbody th, table.dataTable.cell-border tbody td {
+ border-top: 1px solid #ddd;
+ border-right: 1px solid #ddd;
+}
+table.dataTable.cell-border tbody tr th:first-child,
+table.dataTable.cell-border tbody tr td:first-child {
+ border-left: 1px solid #ddd;
+}
+table.dataTable.cell-border tbody tr:first-child th,
+table.dataTable.cell-border tbody tr:first-child td {
+ border-top: none;
+}
+table.dataTable.stripe tbody tr.odd, table.dataTable.display tbody tr.odd {
+ background-color: #f9f9f9;
+}
+table.dataTable.stripe tbody tr.odd.selected, table.dataTable.display tbody tr.odd.selected {
+ background-color: #acbad4;
+}
+table.dataTable.hover tbody tr:hover, table.dataTable.display tbody tr:hover {
+ background-color: #f6f6f6;
+}
+table.dataTable.hover tbody tr:hover.selected, table.dataTable.display tbody tr:hover.selected {
+ background-color: #aab7d1;
+}
+table.dataTable.order-column tbody tr > .sorting_1,
+table.dataTable.order-column tbody tr > .sorting_2,
+table.dataTable.order-column tbody tr > .sorting_3, table.dataTable.display tbody tr > .sorting_1,
+table.dataTable.display tbody tr > .sorting_2,
+table.dataTable.display tbody tr > .sorting_3 {
+ background-color: #fafafa;
+}
+table.dataTable.order-column tbody tr.selected > .sorting_1,
+table.dataTable.order-column tbody tr.selected > .sorting_2,
+table.dataTable.order-column tbody tr.selected > .sorting_3, table.dataTable.display tbody tr.selected > .sorting_1,
+table.dataTable.display tbody tr.selected > .sorting_2,
+table.dataTable.display tbody tr.selected > .sorting_3 {
+ background-color: #acbad5;
+}
+table.dataTable.display tbody tr.odd > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd > .sorting_1 {
+ background-color: #f1f1f1;
+}
+table.dataTable.display tbody tr.odd > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd > .sorting_2 {
+ background-color: #f3f3f3;
+}
+table.dataTable.display tbody tr.odd > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd > .sorting_3 {
+ background-color: whitesmoke;
+}
+table.dataTable.display tbody tr.odd.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_1 {
+ background-color: #a6b4cd;
+}
+table.dataTable.display tbody tr.odd.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_2 {
+ background-color: #a8b5cf;
+}
+table.dataTable.display tbody tr.odd.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_3 {
+ background-color: #a9b7d1;
+}
+table.dataTable.display tbody tr.even > .sorting_1, table.dataTable.order-column.stripe tbody tr.even > .sorting_1 {
+ background-color: #fafafa;
+}
+table.dataTable.display tbody tr.even > .sorting_2, table.dataTable.order-column.stripe tbody tr.even > .sorting_2 {
+ background-color: #fcfcfc;
+}
+table.dataTable.display tbody tr.even > .sorting_3, table.dataTable.order-column.stripe tbody tr.even > .sorting_3 {
+ background-color: #fefefe;
+}
+table.dataTable.display tbody tr.even.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_1 {
+ background-color: #acbad5;
+}
+table.dataTable.display tbody tr.even.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_2 {
+ background-color: #aebcd6;
+}
+table.dataTable.display tbody tr.even.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_3 {
+ background-color: #afbdd8;
+}
+table.dataTable.display tbody tr:hover > .sorting_1, table.dataTable.order-column.hover tbody tr:hover > .sorting_1 {
+ background-color: #eaeaea;
+}
+table.dataTable.display tbody tr:hover > .sorting_2, table.dataTable.order-column.hover tbody tr:hover > .sorting_2 {
+ background-color: #ececec;
+}
+table.dataTable.display tbody tr:hover > .sorting_3, table.dataTable.order-column.hover tbody tr:hover > .sorting_3 {
+ background-color: #efefef;
+}
+table.dataTable.display tbody tr:hover.selected > .sorting_1, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_1 {
+ background-color: #a2aec7;
+}
+table.dataTable.display tbody tr:hover.selected > .sorting_2, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_2 {
+ background-color: #a3b0c9;
+}
+table.dataTable.display tbody tr:hover.selected > .sorting_3, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_3 {
+ background-color: #a5b2cb;
+}
+table.dataTable.no-footer {
+ border-bottom: 1px solid #111;
+}
+table.dataTable.nowrap th, table.dataTable.nowrap td {
+ white-space: nowrap;
+}
+table.dataTable.compact thead th,
+table.dataTable.compact thead td {
+ padding: 4px 17px 4px 4px;
+}
+table.dataTable.compact tfoot th,
+table.dataTable.compact tfoot td {
+ padding: 4px;
+}
+table.dataTable.compact tbody th,
+table.dataTable.compact tbody td {
+ padding: 4px;
+}
+table.dataTable th.dt-left,
+table.dataTable td.dt-left {
+ text-align: left;
+}
+table.dataTable th.dt-center,
+table.dataTable td.dt-center,
+table.dataTable td.dataTables_empty {
+ text-align: center;
+}
+table.dataTable th.dt-right,
+table.dataTable td.dt-right {
+ text-align: right;
+}
+table.dataTable th.dt-justify,
+table.dataTable td.dt-justify {
+ text-align: justify;
+}
+table.dataTable th.dt-nowrap,
+table.dataTable td.dt-nowrap {
+ white-space: nowrap;
+}
+table.dataTable thead th.dt-head-left,
+table.dataTable thead td.dt-head-left,
+table.dataTable tfoot th.dt-head-left,
+table.dataTable tfoot td.dt-head-left {
+ text-align: left;
+}
+table.dataTable thead th.dt-head-center,
+table.dataTable thead td.dt-head-center,
+table.dataTable tfoot th.dt-head-center,
+table.dataTable tfoot td.dt-head-center {
+ text-align: center;
+}
+table.dataTable thead th.dt-head-right,
+table.dataTable thead td.dt-head-right,
+table.dataTable tfoot th.dt-head-right,
+table.dataTable tfoot td.dt-head-right {
+ text-align: right;
+}
+table.dataTable thead th.dt-head-justify,
+table.dataTable thead td.dt-head-justify,
+table.dataTable tfoot th.dt-head-justify,
+table.dataTable tfoot td.dt-head-justify {
+ text-align: justify;
+}
+table.dataTable thead th.dt-head-nowrap,
+table.dataTable thead td.dt-head-nowrap,
+table.dataTable tfoot th.dt-head-nowrap,
+table.dataTable tfoot td.dt-head-nowrap {
+ white-space: nowrap;
+}
+table.dataTable tbody th.dt-body-left,
+table.dataTable tbody td.dt-body-left {
+ text-align: left;
+}
+table.dataTable tbody th.dt-body-center,
+table.dataTable tbody td.dt-body-center {
+ text-align: center;
+}
+table.dataTable tbody th.dt-body-right,
+table.dataTable tbody td.dt-body-right {
+ text-align: right;
+}
+table.dataTable tbody th.dt-body-justify,
+table.dataTable tbody td.dt-body-justify {
+ text-align: justify;
+}
+table.dataTable tbody th.dt-body-nowrap,
+table.dataTable tbody td.dt-body-nowrap {
+ white-space: nowrap;
+}
+
+table.dataTable,
+table.dataTable th,
+table.dataTable td {
+ -webkit-box-sizing: content-box;
+ box-sizing: content-box;
+}
+
+/*
+ * Control feature layout
+ */
+.dataTables_wrapper {
+ position: relative;
+ clear: both;
+ *zoom: 1;
+ zoom: 1;
+}
+.dataTables_wrapper .dataTables_length {
+ float: left;
+}
+.dataTables_wrapper .dataTables_filter {
+ float: right;
+ text-align: right;
+}
+.dataTables_wrapper .dataTables_filter input {
+ margin-left: 0.5em;
+}
+.dataTables_wrapper .dataTables_info {
+ clear: both;
+ float: left;
+ padding-top: 0.755em;
+}
+.dataTables_wrapper .dataTables_paginate {
+ float: right;
+ text-align: right;
+ padding-top: 0.25em;
+}
+.dataTables_wrapper .dataTables_paginate .paginate_button {
+ box-sizing: border-box;
+ display: inline-block;
+ min-width: 1.5em;
+ padding: 0.5em 1em;
+ margin-left: 2px;
+ text-align: center;
+ text-decoration: none !important;
+ cursor: pointer;
+ *cursor: hand;
+ color: #333;
+ border: 1px solid transparent;
+ border-radius: 2px;
+}
+.dataTables_wrapper .dataTables_paginate .paginate_button.current, .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover {
+ color: #333;
+ border: 1px solid #979797;
+ background-color: white;
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(100%, #dcdcdc));
+ /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, white 0%, #dcdcdc 100%);
+ /* Chrome10+,Safari5.1+ */
+ background: -moz-linear-gradient(top, white 0%, #dcdcdc 100%);
+ /* FF3.6+ */
+ background: -ms-linear-gradient(top, white 0%, #dcdcdc 100%);
+ /* IE10+ */
+ background: -o-linear-gradient(top, white 0%, #dcdcdc 100%);
+ /* Opera 11.10+ */
+ background: linear-gradient(to bottom, white 0%, #dcdcdc 100%);
+ /* W3C */
+}
+.dataTables_wrapper .dataTables_paginate .paginate_button.disabled, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active {
+ cursor: default;
+ color: #666;
+ border: 1px solid transparent;
+ background: transparent;
+ box-shadow: none;
+}
+.dataTables_wrapper .dataTables_paginate .paginate_button:hover {
+ color: white;
+ border: 1px solid #111;
+ background-color: #585858;
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));
+ /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #585858 0%, #111 100%);
+ /* Chrome10+,Safari5.1+ */
+ background: -moz-linear-gradient(top, #585858 0%, #111 100%);
+ /* FF3.6+ */
+ background: -ms-linear-gradient(top, #585858 0%, #111 100%);
+ /* IE10+ */
+ background: -o-linear-gradient(top, #585858 0%, #111 100%);
+ /* Opera 11.10+ */
+ background: linear-gradient(to bottom, #585858 0%, #111 100%);
+ /* W3C */
+}
+.dataTables_wrapper .dataTables_paginate .paginate_button:active {
+ outline: none;
+ background-color: #2b2b2b;
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));
+ /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
+ /* Chrome10+,Safari5.1+ */
+ background: -moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
+ /* FF3.6+ */
+ background: -ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
+ /* IE10+ */
+ background: -o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
+ /* Opera 11.10+ */
+ background: linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);
+ /* W3C */
+ box-shadow: inset 0 0 3px #111;
+}
+.dataTables_wrapper .dataTables_paginate .ellipsis {
+ padding: 0 1em;
+}
+.dataTables_wrapper .dataTables_processing {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 100%;
+ height: 40px;
+ margin-left: -50%;
+ margin-top: -25px;
+ padding-top: 20px;
+ text-align: center;
+ font-size: 1.2em;
+ background-color: white;
+ background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(25%, rgba(255, 255, 255, 0.9)), color-stop(75%, rgba(255, 255, 255, 0.9)), color-stop(100%, rgba(255, 255, 255, 0)));
+ background: -webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
+ background: -moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
+ background: -ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
+ background: -o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
+ background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
+}
+.dataTables_wrapper .dataTables_length,
+.dataTables_wrapper .dataTables_filter,
+.dataTables_wrapper .dataTables_info,
+.dataTables_wrapper .dataTables_processing,
+.dataTables_wrapper .dataTables_paginate {
+ color: #333;
+}
+.dataTables_wrapper .dataTables_scroll {
+ clear: both;
+}
+.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody {
+ *margin-top: -1px;
+ -webkit-overflow-scrolling: touch;
+}
+.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td {
+ vertical-align: middle;
+}
+.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th > div.dataTables_sizing,
+.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td > div.dataTables_sizing, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th > div.dataTables_sizing,
+.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td > div.dataTables_sizing {
+ height: 0;
+ overflow: hidden;
+ margin: 0 !important;
+ padding: 0 !important;
+}
+.dataTables_wrapper.no-footer .dataTables_scrollBody {
+ border-bottom: 1px solid #111;
+}
+.dataTables_wrapper.no-footer div.dataTables_scrollHead > table,
+.dataTables_wrapper.no-footer div.dataTables_scrollBody > table {
+ border-bottom: none;
+}
+.dataTables_wrapper:after {
+ visibility: hidden;
+ display: block;
+ content: "";
+ clear: both;
+ height: 0;
+}
+
+@media screen and (max-width: 767px) {
+ .dataTables_wrapper .dataTables_info,
+ .dataTables_wrapper .dataTables_paginate {
+ float: none;
+ text-align: center;
+ }
+ .dataTables_wrapper .dataTables_paginate {
+ margin-top: 0.5em;
+ }
+}
+@media screen and (max-width: 640px) {
+ .dataTables_wrapper .dataTables_length,
+ .dataTables_wrapper .dataTables_filter {
+ float: none;
+ text-align: center;
+ }
+ .dataTables_wrapper .dataTables_filter {
+ margin-top: 0.5em;
+ }
+}
diff --git a/www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/jquery.dataTables.js b/www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/jquery.dataTables.js
new file mode 100644
index 00000000..e749018a
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/jquery.dataTables.js
@@ -0,0 +1,15345 @@
+/*! DataTables 1.10.15
+ * ©2008-2017 SpryMedia Ltd - datatables.net/license
+ */
+
+/**
+ * @summary DataTables
+ * @description Paginate, search and order HTML tables
+ * @version 1.10.15
+ * @file jquery.dataTables.js
+ * @author SpryMedia Ltd
+ * @contact www.datatables.net
+ * @copyright Copyright 2008-2017 SpryMedia Ltd.
+ *
+ * This source file is free software, available under the following license:
+ * MIT license - http://datatables.net/license
+ *
+ * This source file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
+ *
+ * For details please refer to: http://www.datatables.net
+ */
+
+/*jslint evil: true, undef: true, browser: true */
+/*globals $,require,jQuery,define,_selector_run,_selector_opts,_selector_first,_selector_row_indexes,_ext,_Api,_api_register,_api_registerPlural,_re_new_lines,_re_html,_re_formatted_numeric,_re_escape_regex,_empty,_intVal,_numToDecimal,_isNumber,_isHtml,_htmlNumeric,_pluck,_pluck_order,_range,_stripHtml,_unique,_fnBuildAjax,_fnAjaxUpdate,_fnAjaxParameters,_fnAjaxUpdateDraw,_fnAjaxDataSrc,_fnAddColumn,_fnColumnOptions,_fnAdjustColumnSizing,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnVisbleColumns,_fnGetColumns,_fnColumnTypes,_fnApplyColumnDefs,_fnHungarianMap,_fnCamelToHungarian,_fnLanguageCompat,_fnBrowserDetect,_fnAddData,_fnAddTr,_fnNodeToDataIndex,_fnNodeToColumnIndex,_fnGetCellData,_fnSetCellData,_fnSplitObjNotation,_fnGetObjectDataFn,_fnSetObjectDataFn,_fnGetDataMaster,_fnClearTable,_fnDeleteIndex,_fnInvalidate,_fnGetRowElements,_fnCreateTr,_fnBuildHead,_fnDrawHead,_fnDraw,_fnReDraw,_fnAddOptionsHtml,_fnDetectHeader,_fnGetUniqueThs,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnFilterCreateSearch,_fnEscapeRegex,_fnFilterData,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnInfoMacros,_fnInitialise,_fnInitComplete,_fnLengthChange,_fnFeatureHtmlLength,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnFeatureHtmlTable,_fnScrollDraw,_fnApplyToChildren,_fnCalculateColumnWidths,_fnThrottle,_fnConvertToWidth,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnSortFlatten,_fnSort,_fnSortAria,_fnSortListener,_fnSortAttachListener,_fnSortingClasses,_fnSortData,_fnSaveState,_fnLoadState,_fnSettingsFromNode,_fnLog,_fnMap,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnLengthOverflow,_fnRenderer,_fnDataSource,_fnRowAttributes*/
+
+(function( factory ) {
+ "use strict";
+
+ if ( typeof define === 'function' && define.amd ) {
+ // AMD
+ define( ['jquery'], function ( $ ) {
+ return factory( $, window, document );
+ } );
+ }
+ else if ( typeof exports === 'object' ) {
+ // CommonJS
+ module.exports = function (root, $) {
+ if ( ! root ) {
+ // CommonJS environments without a window global must pass a
+ // root. This will give an error otherwise
+ root = window;
+ }
+
+ if ( ! $ ) {
+ $ = typeof window !== 'undefined' ? // jQuery's factory checks for a global window
+ require('jquery') :
+ require('jquery')( root );
+ }
+
+ return factory( $, root, root.document );
+ };
+ }
+ else {
+ // Browser
+ factory( jQuery, window, document );
+ }
+}
+(function( $, window, document, undefined ) {
+ "use strict";
+
+ /**
+ * DataTables is a plug-in for the jQuery Javascript library. It is a highly
+ * flexible tool, based upon the foundations of progressive enhancement,
+ * which will add advanced interaction controls to any HTML table. For a
+ * full list of features please refer to
+ * [DataTables.net](href="http://datatables.net).
+ *
+ * Note that the `DataTable` object is not a global variable but is aliased
+ * to `jQuery.fn.DataTable` and `jQuery.fn.dataTable` through which it may
+ * be accessed.
+ *
+ * @class
+ * @param {object} [init={}] Configuration object for DataTables. Options
+ * are defined by {@link DataTable.defaults}
+ * @requires jQuery 1.7+
+ *
+ * @example
+ * // Basic initialisation
+ * $(document).ready( function {
+ * $('#example').dataTable();
+ * } );
+ *
+ * @example
+ * // Initialisation with configuration options - in this case, disable
+ * // pagination and sorting.
+ * $(document).ready( function {
+ * $('#example').dataTable( {
+ * "paginate": false,
+ * "sort": false
+ * } );
+ * } );
+ */
+ var DataTable = function ( options )
+ {
+ /**
+ * Perform a jQuery selector action on the table's TR elements (from the tbody) and
+ * return the resulting jQuery object.
+ * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on
+ * @param {object} [oOpts] Optional parameters for modifying the rows to be included
+ * @param {string} [oOpts.filter=none] Select TR elements that meet the current filter
+ * criterion ("applied") or all TR elements (i.e. no filter).
+ * @param {string} [oOpts.order=current] Order of the TR elements in the processed array.
+ * Can be either 'current', whereby the current sorting of the table is used, or
+ * 'original' whereby the original order the data was read into the table is used.
+ * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page
+ * ("current") or not ("all"). If 'current' is given, then order is assumed to be
+ * 'current' and filter is 'applied', regardless of what they might be given as.
+ * @returns {object} jQuery object, filtered by the given selector.
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ *
+ * // Highlight every second row
+ * oTable.$('tr:odd').css('backgroundColor', 'blue');
+ * } );
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ *
+ * // Filter to rows with 'Webkit' in them, add a background colour and then
+ * // remove the filter, thus highlighting the 'Webkit' rows only.
+ * oTable.fnFilter('Webkit');
+ * oTable.$('tr', {"search": "applied"}).css('backgroundColor', 'blue');
+ * oTable.fnFilter('');
+ * } );
+ */
+ this.$ = function ( sSelector, oOpts )
+ {
+ return this.api(true).$( sSelector, oOpts );
+ };
+
+
+ /**
+ * Almost identical to $ in operation, but in this case returns the data for the matched
+ * rows - as such, the jQuery selector used should match TR row nodes or TD/TH cell nodes
+ * rather than any descendants, so the data can be obtained for the row/cell. If matching
+ * rows are found, the data returned is the original data array/object that was used to
+ * create the row (or a generated array if from a DOM source).
+ *
+ * This method is often useful in-combination with $ where both functions are given the
+ * same parameters and the array indexes will match identically.
+ * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on
+ * @param {object} [oOpts] Optional parameters for modifying the rows to be included
+ * @param {string} [oOpts.filter=none] Select elements that meet the current filter
+ * criterion ("applied") or all elements (i.e. no filter).
+ * @param {string} [oOpts.order=current] Order of the data in the processed array.
+ * Can be either 'current', whereby the current sorting of the table is used, or
+ * 'original' whereby the original order the data was read into the table is used.
+ * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page
+ * ("current") or not ("all"). If 'current' is given, then order is assumed to be
+ * 'current' and filter is 'applied', regardless of what they might be given as.
+ * @returns {array} Data for the matched elements. If any elements, as a result of the
+ * selector, were not TR, TD or TH elements in the DataTable, they will have a null
+ * entry in the array.
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ *
+ * // Get the data from the first row in the table
+ * var data = oTable._('tr:first');
+ *
+ * // Do something useful with the data
+ * alert( "First cell is: "+data[0] );
+ * } );
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ *
+ * // Filter to 'Webkit' and get all data for
+ * oTable.fnFilter('Webkit');
+ * var data = oTable._('tr', {"search": "applied"});
+ *
+ * // Do something with the data
+ * alert( data.length+" rows matched the search" );
+ * } );
+ */
+ this._ = function ( sSelector, oOpts )
+ {
+ return this.api(true).rows( sSelector, oOpts ).data();
+ };
+
+
+ /**
+ * Create a DataTables Api instance, with the currently selected tables for
+ * the Api's context.
+ * @param {boolean} [traditional=false] Set the API instance's context to be
+ * only the table referred to by the `DataTable.ext.iApiIndex` option, as was
+ * used in the API presented by DataTables 1.9- (i.e. the traditional mode),
+ * or if all tables captured in the jQuery object should be used.
+ * @return {DataTables.Api}
+ */
+ this.api = function ( traditional )
+ {
+ return traditional ?
+ new _Api(
+ _fnSettingsFromNode( this[ _ext.iApiIndex ] )
+ ) :
+ new _Api( this );
+ };
+
+
+ /**
+ * Add a single new row or multiple rows of data to the table. Please note
+ * that this is suitable for client-side processing only - if you are using
+ * server-side processing (i.e. "bServerSide": true), then to add data, you
+ * must add it to the data source, i.e. the server-side, through an Ajax call.
+ * @param {array|object} data The data to be added to the table. This can be:
+ * <ul>
+ * <li>1D array of data - add a single row with the data provided</li>
+ * <li>2D array of arrays - add multiple rows in a single call</li>
+ * <li>object - data object when using <i>mData</i></li>
+ * <li>array of objects - multiple data objects when using <i>mData</i></li>
+ * </ul>
+ * @param {bool} [redraw=true] redraw the table or not
+ * @returns {array} An array of integers, representing the list of indexes in
+ * <i>aoData</i> ({@link DataTable.models.oSettings}) that have been added to
+ * the table.
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * // Global var for counter
+ * var giCount = 2;
+ *
+ * $(document).ready(function() {
+ * $('#example').dataTable();
+ * } );
+ *
+ * function fnClickAddRow() {
+ * $('#example').dataTable().fnAddData( [
+ * giCount+".1",
+ * giCount+".2",
+ * giCount+".3",
+ * giCount+".4" ]
+ * );
+ *
+ * giCount++;
+ * }
+ */
+ this.fnAddData = function( data, redraw )
+ {
+ var api = this.api( true );
+
+ /* Check if we want to add multiple rows or not */
+ var rows = $.isArray(data) && ( $.isArray(data[0]) || $.isPlainObject(data[0]) ) ?
+ api.rows.add( data ) :
+ api.row.add( data );
+
+ if ( redraw === undefined || redraw ) {
+ api.draw();
+ }
+
+ return rows.flatten().toArray();
+ };
+
+
+ /**
+ * This function will make DataTables recalculate the column sizes, based on the data
+ * contained in the table and the sizes applied to the columns (in the DOM, CSS or
+ * through the sWidth parameter). This can be useful when the width of the table's
+ * parent element changes (for example a window resize).
+ * @param {boolean} [bRedraw=true] Redraw the table or not, you will typically want to
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable( {
+ * "sScrollY": "200px",
+ * "bPaginate": false
+ * } );
+ *
+ * $(window).on('resize', function () {
+ * oTable.fnAdjustColumnSizing();
+ * } );
+ * } );
+ */
+ this.fnAdjustColumnSizing = function ( bRedraw )
+ {
+ var api = this.api( true ).columns.adjust();
+ var settings = api.settings()[0];
+ var scroll = settings.oScroll;
+
+ if ( bRedraw === undefined || bRedraw ) {
+ api.draw( false );
+ }
+ else if ( scroll.sX !== "" || scroll.sY !== "" ) {
+ /* If not redrawing, but scrolling, we want to apply the new column sizes anyway */
+ _fnScrollDraw( settings );
+ }
+ };
+
+
+ /**
+ * Quickly and simply clear a table
+ * @param {bool} [bRedraw=true] redraw the table or not
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ *
+ * // Immediately 'nuke' the current rows (perhaps waiting for an Ajax callback...)
+ * oTable.fnClearTable();
+ * } );
+ */
+ this.fnClearTable = function( bRedraw )
+ {
+ var api = this.api( true ).clear();
+
+ if ( bRedraw === undefined || bRedraw ) {
+ api.draw();
+ }
+ };
+
+
+ /**
+ * The exact opposite of 'opening' a row, this function will close any rows which
+ * are currently 'open'.
+ * @param {node} nTr the table row to 'close'
+ * @returns {int} 0 on success, or 1 if failed (can't find the row)
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable;
+ *
+ * // 'open' an information row when a row is clicked on
+ * $('#example tbody tr').click( function () {
+ * if ( oTable.fnIsOpen(this) ) {
+ * oTable.fnClose( this );
+ * } else {
+ * oTable.fnOpen( this, "Temporary row opened", "info_row" );
+ * }
+ * } );
+ *
+ * oTable = $('#example').dataTable();
+ * } );
+ */
+ this.fnClose = function( nTr )
+ {
+ this.api( true ).row( nTr ).child.hide();
+ };
+
+
+ /**
+ * Remove a row for the table
+ * @param {mixed} target The index of the row from aoData to be deleted, or
+ * the TR element you want to delete
+ * @param {function|null} [callBack] Callback function
+ * @param {bool} [redraw=true] Redraw the table or not
+ * @returns {array} The row that was deleted
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ *
+ * // Immediately remove the first row
+ * oTable.fnDeleteRow( 0 );
+ * } );
+ */
+ this.fnDeleteRow = function( target, callback, redraw )
+ {
+ var api = this.api( true );
+ var rows = api.rows( target );
+ var settings = rows.settings()[0];
+ var data = settings.aoData[ rows[0][0] ];
+
+ rows.remove();
+
+ if ( callback ) {
+ callback.call( this, settings, data );
+ }
+
+ if ( redraw === undefined || redraw ) {
+ api.draw();
+ }
+
+ return data;
+ };
+
+
+ /**
+ * Restore the table to it's original state in the DOM by removing all of DataTables
+ * enhancements, alterations to the DOM structure of the table and event listeners.
+ * @param {boolean} [remove=false] Completely remove the table from the DOM
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * // This example is fairly pointless in reality, but shows how fnDestroy can be used
+ * var oTable = $('#example').dataTable();
+ * oTable.fnDestroy();
+ * } );
+ */
+ this.fnDestroy = function ( remove )
+ {
+ this.api( true ).destroy( remove );
+ };
+
+
+ /**
+ * Redraw the table
+ * @param {bool} [complete=true] Re-filter and resort (if enabled) the table before the draw.
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ *
+ * // Re-draw the table - you wouldn't want to do it here, but it's an example :-)
+ * oTable.fnDraw();
+ * } );
+ */
+ this.fnDraw = function( complete )
+ {
+ // Note that this isn't an exact match to the old call to _fnDraw - it takes
+ // into account the new data, but can hold position.
+ this.api( true ).draw( complete );
+ };
+
+
+ /**
+ * Filter the input based on data
+ * @param {string} sInput String to filter the table on
+ * @param {int|null} [iColumn] Column to limit filtering to
+ * @param {bool} [bRegex=false] Treat as regular expression or not
+ * @param {bool} [bSmart=true] Perform smart filtering or not
+ * @param {bool} [bShowGlobal=true] Show the input global filter in it's input box(es)
+ * @param {bool} [bCaseInsensitive=true] Do case-insensitive matching (true) or not (false)
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ *
+ * // Sometime later - filter...
+ * oTable.fnFilter( 'test string' );
+ * } );
+ */
+ this.fnFilter = function( sInput, iColumn, bRegex, bSmart, bShowGlobal, bCaseInsensitive )
+ {
+ var api = this.api( true );
+
+ if ( iColumn === null || iColumn === undefined ) {
+ api.search( sInput, bRegex, bSmart, bCaseInsensitive );
+ }
+ else {
+ api.column( iColumn ).search( sInput, bRegex, bSmart, bCaseInsensitive );
+ }
+
+ api.draw();
+ };
+
+
+ /**
+ * Get the data for the whole table, an individual row or an individual cell based on the
+ * provided parameters.
+ * @param {int|node} [src] A TR row node, TD/TH cell node or an integer. If given as
+ * a TR node then the data source for the whole row will be returned. If given as a
+ * TD/TH cell node then iCol will be automatically calculated and the data for the
+ * cell returned. If given as an integer, then this is treated as the aoData internal
+ * data index for the row (see fnGetPosition) and the data for that row used.
+ * @param {int} [col] Optional column index that you want the data of.
+ * @returns {array|object|string} If mRow is undefined, then the data for all rows is
+ * returned. If mRow is defined, just data for that row, and is iCol is
+ * defined, only data for the designated cell is returned.
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * // Row data
+ * $(document).ready(function() {
+ * oTable = $('#example').dataTable();
+ *
+ * oTable.$('tr').click( function () {
+ * var data = oTable.fnGetData( this );
+ * // ... do something with the array / object of data for the row
+ * } );
+ * } );
+ *
+ * @example
+ * // Individual cell data
+ * $(document).ready(function() {
+ * oTable = $('#example').dataTable();
+ *
+ * oTable.$('td').click( function () {
+ * var sData = oTable.fnGetData( this );
+ * alert( 'The cell clicked on had the value of '+sData );
+ * } );
+ * } );
+ */
+ this.fnGetData = function( src, col )
+ {
+ var api = this.api( true );
+
+ if ( src !== undefined ) {
+ var type = src.nodeName ? src.nodeName.toLowerCase() : '';
+
+ return col !== undefined || type == 'td' || type == 'th' ?
+ api.cell( src, col ).data() :
+ api.row( src ).data() || null;
+ }
+
+ return api.data().toArray();
+ };
+
+
+ /**
+ * Get an array of the TR nodes that are used in the table's body. Note that you will
+ * typically want to use the '$' API method in preference to this as it is more
+ * flexible.
+ * @param {int} [iRow] Optional row index for the TR element you want
+ * @returns {array|node} If iRow is undefined, returns an array of all TR elements
+ * in the table's body, or iRow is defined, just the TR element requested.
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ *
+ * // Get the nodes from the table
+ * var nNodes = oTable.fnGetNodes( );
+ * } );
+ */
+ this.fnGetNodes = function( iRow )
+ {
+ var api = this.api( true );
+
+ return iRow !== undefined ?
+ api.row( iRow ).node() :
+ api.rows().nodes().flatten().toArray();
+ };
+
+
+ /**
+ * Get the array indexes of a particular cell from it's DOM element
+ * and column index including hidden columns
+ * @param {node} node this can either be a TR, TD or TH in the table's body
+ * @returns {int} If nNode is given as a TR, then a single index is returned, or
+ * if given as a cell, an array of [row index, column index (visible),
+ * column index (all)] is given.
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * $('#example tbody td').click( function () {
+ * // Get the position of the current data from the node
+ * var aPos = oTable.fnGetPosition( this );
+ *
+ * // Get the data array for this row
+ * var aData = oTable.fnGetData( aPos[0] );
+ *
+ * // Update the data array and return the value
+ * aData[ aPos[1] ] = 'clicked';
+ * this.innerHTML = 'clicked';
+ * } );
+ *
+ * // Init DataTables
+ * oTable = $('#example').dataTable();
+ * } );
+ */
+ this.fnGetPosition = function( node )
+ {
+ var api = this.api( true );
+ var nodeName = node.nodeName.toUpperCase();
+
+ if ( nodeName == 'TR' ) {
+ return api.row( node ).index();
+ }
+ else if ( nodeName == 'TD' || nodeName == 'TH' ) {
+ var cell = api.cell( node ).index();
+
+ return [
+ cell.row,
+ cell.columnVisible,
+ cell.column
+ ];
+ }
+ return null;
+ };
+
+
+ /**
+ * Check to see if a row is 'open' or not.
+ * @param {node} nTr the table row to check
+ * @returns {boolean} true if the row is currently open, false otherwise
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable;
+ *
+ * // 'open' an information row when a row is clicked on
+ * $('#example tbody tr').click( function () {
+ * if ( oTable.fnIsOpen(this) ) {
+ * oTable.fnClose( this );
+ * } else {
+ * oTable.fnOpen( this, "Temporary row opened", "info_row" );
+ * }
+ * } );
+ *
+ * oTable = $('#example').dataTable();
+ * } );
+ */
+ this.fnIsOpen = function( nTr )
+ {
+ return this.api( true ).row( nTr ).child.isShown();
+ };
+
+
+ /**
+ * This function will place a new row directly after a row which is currently
+ * on display on the page, with the HTML contents that is passed into the
+ * function. This can be used, for example, to ask for confirmation that a
+ * particular record should be deleted.
+ * @param {node} nTr The table row to 'open'
+ * @param {string|node|jQuery} mHtml The HTML to put into the row
+ * @param {string} sClass Class to give the new TD cell
+ * @returns {node} The row opened. Note that if the table row passed in as the
+ * first parameter, is not found in the table, this method will silently
+ * return.
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable;
+ *
+ * // 'open' an information row when a row is clicked on
+ * $('#example tbody tr').click( function () {
+ * if ( oTable.fnIsOpen(this) ) {
+ * oTable.fnClose( this );
+ * } else {
+ * oTable.fnOpen( this, "Temporary row opened", "info_row" );
+ * }
+ * } );
+ *
+ * oTable = $('#example').dataTable();
+ * } );
+ */
+ this.fnOpen = function( nTr, mHtml, sClass )
+ {
+ return this.api( true )
+ .row( nTr )
+ .child( mHtml, sClass )
+ .show()
+ .child()[0];
+ };
+
+
+ /**
+ * Change the pagination - provides the internal logic for pagination in a simple API
+ * function. With this function you can have a DataTables table go to the next,
+ * previous, first or last pages.
+ * @param {string|int} mAction Paging action to take: "first", "previous", "next" or "last"
+ * or page number to jump to (integer), note that page 0 is the first page.
+ * @param {bool} [bRedraw=true] Redraw the table or not
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ * oTable.fnPageChange( 'next' );
+ * } );
+ */
+ this.fnPageChange = function ( mAction, bRedraw )
+ {
+ var api = this.api( true ).page( mAction );
+
+ if ( bRedraw === undefined || bRedraw ) {
+ api.draw(false);
+ }
+ };
+
+
+ /**
+ * Show a particular column
+ * @param {int} iCol The column whose display should be changed
+ * @param {bool} bShow Show (true) or hide (false) the column
+ * @param {bool} [bRedraw=true] Redraw the table or not
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ *
+ * // Hide the second column after initialisation
+ * oTable.fnSetColumnVis( 1, false );
+ * } );
+ */
+ this.fnSetColumnVis = function ( iCol, bShow, bRedraw )
+ {
+ var api = this.api( true ).column( iCol ).visible( bShow );
+
+ if ( bRedraw === undefined || bRedraw ) {
+ api.columns.adjust().draw();
+ }
+ };
+
+
+ /**
+ * Get the settings for a particular table for external manipulation
+ * @returns {object} DataTables settings object. See
+ * {@link DataTable.models.oSettings}
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ * var oSettings = oTable.fnSettings();
+ *
+ * // Show an example parameter from the settings
+ * alert( oSettings._iDisplayStart );
+ * } );
+ */
+ this.fnSettings = function()
+ {
+ return _fnSettingsFromNode( this[_ext.iApiIndex] );
+ };
+
+
+ /**
+ * Sort the table by a particular column
+ * @param {int} iCol the data index to sort on. Note that this will not match the
+ * 'display index' if you have hidden data entries
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ *
+ * // Sort immediately with columns 0 and 1
+ * oTable.fnSort( [ [0,'asc'], [1,'asc'] ] );
+ * } );
+ */
+ this.fnSort = function( aaSort )
+ {
+ this.api( true ).order( aaSort ).draw();
+ };
+
+
+ /**
+ * Attach a sort listener to an element for a given column
+ * @param {node} nNode the element to attach the sort listener to
+ * @param {int} iColumn the column that a click on this node will sort on
+ * @param {function} [fnCallback] callback function when sort is run
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ *
+ * // Sort on column 1, when 'sorter' is clicked on
+ * oTable.fnSortListener( document.getElementById('sorter'), 1 );
+ * } );
+ */
+ this.fnSortListener = function( nNode, iColumn, fnCallback )
+ {
+ this.api( true ).order.listener( nNode, iColumn, fnCallback );
+ };
+
+
+ /**
+ * Update a table cell or row - this method will accept either a single value to
+ * update the cell with, an array of values with one element for each column or
+ * an object in the same format as the original data source. The function is
+ * self-referencing in order to make the multi column updates easier.
+ * @param {object|array|string} mData Data to update the cell/row with
+ * @param {node|int} mRow TR element you want to update or the aoData index
+ * @param {int} [iColumn] The column to update, give as null or undefined to
+ * update a whole row.
+ * @param {bool} [bRedraw=true] Redraw the table or not
+ * @param {bool} [bAction=true] Perform pre-draw actions or not
+ * @returns {int} 0 on success, 1 on error
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ * oTable.fnUpdate( 'Example update', 0, 0 ); // Single cell
+ * oTable.fnUpdate( ['a', 'b', 'c', 'd', 'e'], $('tbody tr')[0] ); // Row
+ * } );
+ */
+ this.fnUpdate = function( mData, mRow, iColumn, bRedraw, bAction )
+ {
+ var api = this.api( true );
+
+ if ( iColumn === undefined || iColumn === null ) {
+ api.row( mRow ).data( mData );
+ }
+ else {
+ api.cell( mRow, iColumn ).data( mData );
+ }
+
+ if ( bAction === undefined || bAction ) {
+ api.columns.adjust();
+ }
+
+ if ( bRedraw === undefined || bRedraw ) {
+ api.draw();
+ }
+ return 0;
+ };
+
+
+ /**
+ * Provide a common method for plug-ins to check the version of DataTables being used, in order
+ * to ensure compatibility.
+ * @param {string} sVersion Version string to check for, in the format "X.Y.Z". Note that the
+ * formats "X" and "X.Y" are also acceptable.
+ * @returns {boolean} true if this version of DataTables is greater or equal to the required
+ * version, or false if this version of DataTales is not suitable
+ * @method
+ * @dtopt API
+ * @deprecated Since v1.10
+ *
+ * @example
+ * $(document).ready(function() {
+ * var oTable = $('#example').dataTable();
+ * alert( oTable.fnVersionCheck( '1.9.0' ) );
+ * } );
+ */
+ this.fnVersionCheck = _ext.fnVersionCheck;
+
+
+ var _that = this;
+ var emptyInit = options === undefined;
+ var len = this.length;
+
+ if ( emptyInit ) {
+ options = {};
+ }
+
+ this.oApi = this.internal = _ext.internal;
+
+ // Extend with old style plug-in API methods
+ for ( var fn in DataTable.ext.internal ) {
+ if ( fn ) {
+ this[fn] = _fnExternApiFunc(fn);
+ }
+ }
+
+ this.each(function() {
+ // For each initialisation we want to give it a clean initialisation
+ // object that can be bashed around
+ var o = {};
+ var oInit = len > 1 ? // optimisation for single table case
+ _fnExtend( o, options, true ) :
+ options;
+
+ /*global oInit,_that,emptyInit*/
+ var i=0, iLen, j, jLen, k, kLen;
+ var sId = this.getAttribute( 'id' );
+ var bInitHandedOff = false;
+ var defaults = DataTable.defaults;
+ var $this = $(this);
+
+
+ /* Sanity check */
+ if ( this.nodeName.toLowerCase() != 'table' )
+ {
+ _fnLog( null, 0, 'Non-table node initialisation ('+this.nodeName+')', 2 );
+ return;
+ }
+
+ /* Backwards compatibility for the defaults */
+ _fnCompatOpts( defaults );
+ _fnCompatCols( defaults.column );
+
+ /* Convert the camel-case defaults to Hungarian */
+ _fnCamelToHungarian( defaults, defaults, true );
+ _fnCamelToHungarian( defaults.column, defaults.column, true );
+
+ /* Setting up the initialisation object */
+ _fnCamelToHungarian( defaults, $.extend( oInit, $this.data() ) );
+
+
+
+ /* Check to see if we are re-initialising a table */
+ var allSettings = DataTable.settings;
+ for ( i=0, iLen=allSettings.length ; i<iLen ; i++ )
+ {
+ var s = allSettings[i];
+
+ /* Base check on table node */
+ if ( s.nTable == this || s.nTHead.parentNode == this || (s.nTFoot && s.nTFoot.parentNode == this) )
+ {
+ var bRetrieve = oInit.bRetrieve !== undefined ? oInit.bRetrieve : defaults.bRetrieve;
+ var bDestroy = oInit.bDestroy !== undefined ? oInit.bDestroy : defaults.bDestroy;
+
+ if ( emptyInit || bRetrieve )
+ {
+ return s.oInstance;
+ }
+ else if ( bDestroy )
+ {
+ s.oInstance.fnDestroy();
+ break;
+ }
+ else
+ {
+ _fnLog( s, 0, 'Cannot reinitialise DataTable', 3 );
+ return;
+ }
+ }
+
+ /* If the element we are initialising has the same ID as a table which was previously
+ * initialised, but the table nodes don't match (from before) then we destroy the old
+ * instance by simply deleting it. This is under the assumption that the table has been
+ * destroyed by other methods. Anyone using non-id selectors will need to do this manually
+ */
+ if ( s.sTableId == this.id )
+ {
+ allSettings.splice( i, 1 );
+ break;
+ }
+ }
+
+ /* Ensure the table has an ID - required for accessibility */
+ if ( sId === null || sId === "" )
+ {
+ sId = "DataTables_Table_"+(DataTable.ext._unique++);
+ this.id = sId;
+ }
+
+ /* Create the settings object for this table and set some of the default parameters */
+ var oSettings = $.extend( true, {}, DataTable.models.oSettings, {
+ "sDestroyWidth": $this[0].style.width,
+ "sInstance": sId,
+ "sTableId": sId
+ } );
+ oSettings.nTable = this;
+ oSettings.oApi = _that.internal;
+ oSettings.oInit = oInit;
+
+ allSettings.push( oSettings );
+
+ // Need to add the instance after the instance after the settings object has been added
+ // to the settings array, so we can self reference the table instance if more than one
+ oSettings.oInstance = (_that.length===1) ? _that : $this.dataTable();
+
+ // Backwards compatibility, before we apply all the defaults
+ _fnCompatOpts( oInit );
+
+ if ( oInit.oLanguage )
+ {
+ _fnLanguageCompat( oInit.oLanguage );
+ }
+
+ // If the length menu is given, but the init display length is not, use the length menu
+ if ( oInit.aLengthMenu && ! oInit.iDisplayLength )
+ {
+ oInit.iDisplayLength = $.isArray( oInit.aLengthMenu[0] ) ?
+ oInit.aLengthMenu[0][0] : oInit.aLengthMenu[0];
+ }
+
+ // Apply the defaults and init options to make a single init object will all
+ // options defined from defaults and instance options.
+ oInit = _fnExtend( $.extend( true, {}, defaults ), oInit );
+
+
+ // Map the initialisation options onto the settings object
+ _fnMap( oSettings.oFeatures, oInit, [
+ "bPaginate",
+ "bLengthChange",
+ "bFilter",
+ "bSort",
+ "bSortMulti",
+ "bInfo",
+ "bProcessing",
+ "bAutoWidth",
+ "bSortClasses",
+ "bServerSide",
+ "bDeferRender"
+ ] );
+ _fnMap( oSettings, oInit, [
+ "asStripeClasses",
+ "ajax",
+ "fnServerData",
+ "fnFormatNumber",
+ "sServerMethod",
+ "aaSorting",
+ "aaSortingFixed",
+ "aLengthMenu",
+ "sPaginationType",
+ "sAjaxSource",
+ "sAjaxDataProp",
+ "iStateDuration",
+ "sDom",
+ "bSortCellsTop",
+ "iTabIndex",
+ "fnStateLoadCallback",
+ "fnStateSaveCallback",
+ "renderer",
+ "searchDelay",
+ "rowId",
+ [ "iCookieDuration", "iStateDuration" ], // backwards compat
+ [ "oSearch", "oPreviousSearch" ],
+ [ "aoSearchCols", "aoPreSearchCols" ],
+ [ "iDisplayLength", "_iDisplayLength" ],
+ [ "bJQueryUI", "bJUI" ]
+ ] );
+ _fnMap( oSettings.oScroll, oInit, [
+ [ "sScrollX", "sX" ],
+ [ "sScrollXInner", "sXInner" ],
+ [ "sScrollY", "sY" ],
+ [ "bScrollCollapse", "bCollapse" ]
+ ] );
+ _fnMap( oSettings.oLanguage, oInit, "fnInfoCallback" );
+
+ /* Callback functions which are array driven */
+ _fnCallbackReg( oSettings, 'aoDrawCallback', oInit.fnDrawCallback, 'user' );
+ _fnCallbackReg( oSettings, 'aoServerParams', oInit.fnServerParams, 'user' );
+ _fnCallbackReg( oSettings, 'aoStateSaveParams', oInit.fnStateSaveParams, 'user' );
+ _fnCallbackReg( oSettings, 'aoStateLoadParams', oInit.fnStateLoadParams, 'user' );
+ _fnCallbackReg( oSettings, 'aoStateLoaded', oInit.fnStateLoaded, 'user' );
+ _fnCallbackReg( oSettings, 'aoRowCallback', oInit.fnRowCallback, 'user' );
+ _fnCallbackReg( oSettings, 'aoRowCreatedCallback', oInit.fnCreatedRow, 'user' );
+ _fnCallbackReg( oSettings, 'aoHeaderCallback', oInit.fnHeaderCallback, 'user' );
+ _fnCallbackReg( oSettings, 'aoFooterCallback', oInit.fnFooterCallback, 'user' );
+ _fnCallbackReg( oSettings, 'aoInitComplete', oInit.fnInitComplete, 'user' );
+ _fnCallbackReg( oSettings, 'aoPreDrawCallback', oInit.fnPreDrawCallback, 'user' );
+
+ oSettings.rowIdFn = _fnGetObjectDataFn( oInit.rowId );
+
+ /* Browser support detection */
+ _fnBrowserDetect( oSettings );
+
+ var oClasses = oSettings.oClasses;
+
+ // @todo Remove in 1.11
+ if ( oInit.bJQueryUI )
+ {
+ /* Use the JUI classes object for display. You could clone the oStdClasses object if
+ * you want to have multiple tables with multiple independent classes
+ */
+ $.extend( oClasses, DataTable.ext.oJUIClasses, oInit.oClasses );
+
+ if ( oInit.sDom === defaults.sDom && defaults.sDom === "lfrtip" )
+ {
+ /* Set the DOM to use a layout suitable for jQuery UI's theming */
+ oSettings.sDom = '<"H"lfr>t<"F"ip>';
+ }
+
+ if ( ! oSettings.renderer ) {
+ oSettings.renderer = 'jqueryui';
+ }
+ else if ( $.isPlainObject( oSettings.renderer ) && ! oSettings.renderer.header ) {
+ oSettings.renderer.header = 'jqueryui';
+ }
+ }
+ else
+ {
+ $.extend( oClasses, DataTable.ext.classes, oInit.oClasses );
+ }
+ $this.addClass( oClasses.sTable );
+
+
+ if ( oSettings.iInitDisplayStart === undefined )
+ {
+ /* Display start point, taking into account the save saving */
+ oSettings.iInitDisplayStart = oInit.iDisplayStart;
+ oSettings._iDisplayStart = oInit.iDisplayStart;
+ }
+
+ if ( oInit.iDeferLoading !== null )
+ {
+ oSettings.bDeferLoading = true;
+ var tmp = $.isArray( oInit.iDeferLoading );
+ oSettings._iRecordsDisplay = tmp ? oInit.iDeferLoading[0] : oInit.iDeferLoading;
+ oSettings._iRecordsTotal = tmp ? oInit.iDeferLoading[1] : oInit.iDeferLoading;
+ }
+
+ /* Language definitions */
+ var oLanguage = oSettings.oLanguage;
+ $.extend( true, oLanguage, oInit.oLanguage );
+
+ if ( oLanguage.sUrl )
+ {
+ /* Get the language definitions from a file - because this Ajax call makes the language
+ * get async to the remainder of this function we use bInitHandedOff to indicate that
+ * _fnInitialise will be fired by the returned Ajax handler, rather than the constructor
+ */
+ $.ajax( {
+ dataType: 'json',
+ url: oLanguage.sUrl,
+ success: function ( json ) {
+ _fnLanguageCompat( json );
+ _fnCamelToHungarian( defaults.oLanguage, json );
+ $.extend( true, oLanguage, json );
+ _fnInitialise( oSettings );
+ },
+ error: function () {
+ // Error occurred loading language file, continue on as best we can
+ _fnInitialise( oSettings );
+ }
+ } );
+ bInitHandedOff = true;
+ }
+
+ /*
+ * Stripes
+ */
+ if ( oInit.asStripeClasses === null )
+ {
+ oSettings.asStripeClasses =[
+ oClasses.sStripeOdd,
+ oClasses.sStripeEven
+ ];
+ }
+
+ /* Remove row stripe classes if they are already on the table row */
+ var stripeClasses = oSettings.asStripeClasses;
+ var rowOne = $this.children('tbody').find('tr').eq(0);
+ if ( $.inArray( true, $.map( stripeClasses, function(el, i) {
+ return rowOne.hasClass(el);
+ } ) ) !== -1 ) {
+ $('tbody tr', this).removeClass( stripeClasses.join(' ') );
+ oSettings.asDestroyStripes = stripeClasses.slice();
+ }
+
+ /*
+ * Columns
+ * See if we should load columns automatically or use defined ones
+ */
+ var anThs = [];
+ var aoColumnsInit;
+ var nThead = this.getElementsByTagName('thead');
+ if ( nThead.length !== 0 )
+ {
+ _fnDetectHeader( oSettings.aoHeader, nThead[0] );
+ anThs = _fnGetUniqueThs( oSettings );
+ }
+
+ /* If not given a column array, generate one with nulls */
+ if ( oInit.aoColumns === null )
+ {
+ aoColumnsInit = [];
+ for ( i=0, iLen=anThs.length ; i<iLen ; i++ )
+ {
+ aoColumnsInit.push( null );
+ }
+ }
+ else
+ {
+ aoColumnsInit = oInit.aoColumns;
+ }
+
+ /* Add the columns */
+ for ( i=0, iLen=aoColumnsInit.length ; i<iLen ; i++ )
+ {
+ _fnAddColumn( oSettings, anThs ? anThs[i] : null );
+ }
+
+ /* Apply the column definitions */
+ _fnApplyColumnDefs( oSettings, oInit.aoColumnDefs, aoColumnsInit, function (iCol, oDef) {
+ _fnColumnOptions( oSettings, iCol, oDef );
+ } );
+
+ /* HTML5 attribute detection - build an mData object automatically if the
+ * attributes are found
+ */
+ if ( rowOne.length ) {
+ var a = function ( cell, name ) {
+ return cell.getAttribute( 'data-'+name ) !== null ? name : null;
+ };
+
+ $( rowOne[0] ).children('th, td').each( function (i, cell) {
+ var col = oSettings.aoColumns[i];
+
+ if ( col.mData === i ) {
+ var sort = a( cell, 'sort' ) || a( cell, 'order' );
+ var filter = a( cell, 'filter' ) || a( cell, 'search' );
+
+ if ( sort !== null || filter !== null ) {
+ col.mData = {
+ _: i+'.display',
+ sort: sort !== null ? i+'.@data-'+sort : undefined,
+ type: sort !== null ? i+'.@data-'+sort : undefined,
+ filter: filter !== null ? i+'.@data-'+filter : undefined
+ };
+
+ _fnColumnOptions( oSettings, i );
+ }
+ }
+ } );
+ }
+
+ var features = oSettings.oFeatures;
+ var loadedInit = function () {
+ /*
+ * Sorting
+ * @todo For modularisation (1.11) this needs to do into a sort start up handler
+ */
+
+ // If aaSorting is not defined, then we use the first indicator in asSorting
+ // in case that has been altered, so the default sort reflects that option
+ if ( oInit.aaSorting === undefined ) {
+ var sorting = oSettings.aaSorting;
+ for ( i=0, iLen=sorting.length ; i<iLen ; i++ ) {
+ sorting[i][1] = oSettings.aoColumns[ i ].asSorting[0];
+ }
+ }
+
+ /* Do a first pass on the sorting classes (allows any size changes to be taken into
+ * account, and also will apply sorting disabled classes if disabled
+ */
+ _fnSortingClasses( oSettings );
+
+ if ( features.bSort ) {
+ _fnCallbackReg( oSettings, 'aoDrawCallback', function () {
+ if ( oSettings.bSorted ) {
+ var aSort = _fnSortFlatten( oSettings );
+ var sortedColumns = {};
+
+ $.each( aSort, function (i, val) {
+ sortedColumns[ val.src ] = val.dir;
+ } );
+
+ _fnCallbackFire( oSettings, null, 'order', [oSettings, aSort, sortedColumns] );
+ _fnSortAria( oSettings );
+ }
+ } );
+ }
+
+ _fnCallbackReg( oSettings, 'aoDrawCallback', function () {
+ if ( oSettings.bSorted || _fnDataSource( oSettings ) === 'ssp' || features.bDeferRender ) {
+ _fnSortingClasses( oSettings );
+ }
+ }, 'sc' );
+
+
+ /*
+ * Final init
+ * Cache the header, body and footer as required, creating them if needed
+ */
+
+ // Work around for Webkit bug 83867 - store the caption-side before removing from doc
+ var captions = $this.children('caption').each( function () {
+ this._captionSide = $(this).css('caption-side');
+ } );
+
+ var thead = $this.children('thead');
+ if ( thead.length === 0 ) {
+ thead = $('<thead/>').appendTo($this);
+ }
+ oSettings.nTHead = thead[0];
+
+ var tbody = $this.children('tbody');
+ if ( tbody.length === 0 ) {
+ tbody = $('<tbody/>').appendTo($this);
+ }
+ oSettings.nTBody = tbody[0];
+
+ var tfoot = $this.children('tfoot');
+ if ( tfoot.length === 0 && captions.length > 0 && (oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "") ) {
+ // If we are a scrolling table, and no footer has been given, then we need to create
+ // a tfoot element for the caption element to be appended to
+ tfoot = $('<tfoot/>').appendTo($this);
+ }
+
+ if ( tfoot.length === 0 || tfoot.children().length === 0 ) {
+ $this.addClass( oClasses.sNoFooter );
+ }
+ else if ( tfoot.length > 0 ) {
+ oSettings.nTFoot = tfoot[0];
+ _fnDetectHeader( oSettings.aoFooter, oSettings.nTFoot );
+ }
+
+ /* Check if there is data passing into the constructor */
+ if ( oInit.aaData ) {
+ for ( i=0 ; i<oInit.aaData.length ; i++ ) {
+ _fnAddData( oSettings, oInit.aaData[ i ] );
+ }
+ }
+ else if ( oSettings.bDeferLoading || _fnDataSource( oSettings ) == 'dom' ) {
+ /* Grab the data from the page - only do this when deferred loading or no Ajax
+ * source since there is no point in reading the DOM data if we are then going
+ * to replace it with Ajax data
+ */
+ _fnAddTr( oSettings, $(oSettings.nTBody).children('tr') );
+ }
+
+ /* Copy the data index array */
+ oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
+
+ /* Initialisation complete - table can be drawn */
+ oSettings.bInitialised = true;
+
+ /* Check if we need to initialise the table (it might not have been handed off to the
+ * language processor)
+ */
+ if ( bInitHandedOff === false ) {
+ _fnInitialise( oSettings );
+ }
+ };
+
+ /* Must be done after everything which can be overridden by the state saving! */
+ if ( oInit.bStateSave )
+ {
+ features.bStateSave = true;
+ _fnCallbackReg( oSettings, 'aoDrawCallback', _fnSaveState, 'state_save' );
+ _fnLoadState( oSettings, oInit, loadedInit );
+ }
+ else {
+ loadedInit();
+ }
+
+ } );
+ _that = null;
+ return this;
+ };
+
+
+ /*
+ * It is useful to have variables which are scoped locally so only the
+ * DataTables functions can access them and they don't leak into global space.
+ * At the same time these functions are often useful over multiple files in the
+ * core and API, so we list, or at least document, all variables which are used
+ * by DataTables as private variables here. This also ensures that there is no
+ * clashing of variable names and that they can easily referenced for reuse.
+ */
+
+
+ // Defined else where
+ // _selector_run
+ // _selector_opts
+ // _selector_first
+ // _selector_row_indexes
+
+ var _ext; // DataTable.ext
+ var _Api; // DataTable.Api
+ var _api_register; // DataTable.Api.register
+ var _api_registerPlural; // DataTable.Api.registerPlural
+
+ var _re_dic = {};
+ var _re_new_lines = /[\r\n]/g;
+ var _re_html = /<.*?>/g;
+
+ // This is not strict ISO8601 - Date.parse() is quite lax, although
+ // implementations differ between browsers.
+ var _re_date = /^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/;
+
+ // Escape regular expression special characters
+ var _re_escape_regex = new RegExp( '(\\' + [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ].join('|\\') + ')', 'g' );
+
+ // http://en.wikipedia.org/wiki/Foreign_exchange_market
+ // - \u20BD - Russian ruble.
+ // - \u20a9 - South Korean Won
+ // - \u20BA - Turkish Lira
+ // - \u20B9 - Indian Rupee
+ // - R - Brazil (R$) and South Africa
+ // - fr - Swiss Franc
+ // - kr - Swedish krona, Norwegian krone and Danish krone
+ // - \u2009 is thin space and \u202F is narrow no-break space, both used in many
+ // standards as thousands separators.
+ var _re_formatted_numeric = /[',$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfk]/gi;
+
+
+ var _empty = function ( d ) {
+ return !d || d === true || d === '-' ? true : false;
+ };
+
+
+ var _intVal = function ( s ) {
+ var integer = parseInt( s, 10 );
+ return !isNaN(integer) && isFinite(s) ? integer : null;
+ };
+
+ // Convert from a formatted number with characters other than `.` as the
+ // decimal place, to a Javascript number
+ var _numToDecimal = function ( num, decimalPoint ) {
+ // Cache created regular expressions for speed as this function is called often
+ if ( ! _re_dic[ decimalPoint ] ) {
+ _re_dic[ decimalPoint ] = new RegExp( _fnEscapeRegex( decimalPoint ), 'g' );
+ }
+ return typeof num === 'string' && decimalPoint !== '.' ?
+ num.replace( /\./g, '' ).replace( _re_dic[ decimalPoint ], '.' ) :
+ num;
+ };
+
+
+ var _isNumber = function ( d, decimalPoint, formatted ) {
+ var strType = typeof d === 'string';
+
+ // If empty return immediately so there must be a number if it is a
+ // formatted string (this stops the string "k", or "kr", etc being detected
+ // as a formatted number for currency
+ if ( _empty( d ) ) {
+ return true;
+ }
+
+ if ( decimalPoint && strType ) {
+ d = _numToDecimal( d, decimalPoint );
+ }
+
+ if ( formatted && strType ) {
+ d = d.replace( _re_formatted_numeric, '' );
+ }
+
+ return !isNaN( parseFloat(d) ) && isFinite( d );
+ };
+
+
+ // A string without HTML in it can be considered to be HTML still
+ var _isHtml = function ( d ) {
+ return _empty( d ) || typeof d === 'string';
+ };
+
+
+ var _htmlNumeric = function ( d, decimalPoint, formatted ) {
+ if ( _empty( d ) ) {
+ return true;
+ }
+
+ var html = _isHtml( d );
+ return ! html ?
+ null :
+ _isNumber( _stripHtml( d ), decimalPoint, formatted ) ?
+ true :
+ null;
+ };
+
+
+ var _pluck = function ( a, prop, prop2 ) {
+ var out = [];
+ var i=0, ien=a.length;
+
+ // Could have the test in the loop for slightly smaller code, but speed
+ // is essential here
+ if ( prop2 !== undefined ) {
+ for ( ; i<ien ; i++ ) {
+ if ( a[i] && a[i][ prop ] ) {
+ out.push( a[i][ prop ][ prop2 ] );
+ }
+ }
+ }
+ else {
+ for ( ; i<ien ; i++ ) {
+ if ( a[i] ) {
+ out.push( a[i][ prop ] );
+ }
+ }
+ }
+
+ return out;
+ };
+
+
+ // Basically the same as _pluck, but rather than looping over `a` we use `order`
+ // as the indexes to pick from `a`
+ var _pluck_order = function ( a, order, prop, prop2 )
+ {
+ var out = [];
+ var i=0, ien=order.length;
+
+ // Could have the test in the loop for slightly smaller code, but speed
+ // is essential here
+ if ( prop2 !== undefined ) {
+ for ( ; i<ien ; i++ ) {
+ if ( a[ order[i] ][ prop ] ) {
+ out.push( a[ order[i] ][ prop ][ prop2 ] );
+ }
+ }
+ }
+ else {
+ for ( ; i<ien ; i++ ) {
+ out.push( a[ order[i] ][ prop ] );
+ }
+ }
+
+ return out;
+ };
+
+
+ var _range = function ( len, start )
+ {
+ var out = [];
+ var end;
+
+ if ( start === undefined ) {
+ start = 0;
+ end = len;
+ }
+ else {
+ end = start;
+ start = len;
+ }
+
+ for ( var i=start ; i<end ; i++ ) {
+ out.push( i );
+ }
+
+ return out;
+ };
+
+
+ var _removeEmpty = function ( a )
+ {
+ var out = [];
+
+ for ( var i=0, ien=a.length ; i<ien ; i++ ) {
+ if ( a[i] ) { // careful - will remove all falsy values!
+ out.push( a[i] );
+ }
+ }
+
+ return out;
+ };
+
+
+ var _stripHtml = function ( d ) {
+ return d.replace( _re_html, '' );
+ };
+
+
+ /**
+ * Determine if all values in the array are unique. This means we can short
+ * cut the _unique method at the cost of a single loop. A sorted array is used
+ * to easily check the values.
+ *
+ * @param {array} src Source array
+ * @return {boolean} true if all unique, false otherwise
+ * @ignore
+ */
+ var _areAllUnique = function ( src ) {
+ if ( src.length < 2 ) {
+ return true;
+ }
+
+ var sorted = src.slice().sort();
+ var last = sorted[0];
+
+ for ( var i=1, ien=sorted.length ; i<ien ; i++ ) {
+ if ( sorted[i] === last ) {
+ return false;
+ }
+
+ last = sorted[i];
+ }
+
+ return true;
+ };
+
+
+ /**
+ * Find the unique elements in a source array.
+ *
+ * @param {array} src Source array
+ * @return {array} Array of unique items
+ * @ignore
+ */
+ var _unique = function ( src )
+ {
+ if ( _areAllUnique( src ) ) {
+ return src.slice();
+ }
+
+ // A faster unique method is to use object keys to identify used values,
+ // but this doesn't work with arrays or objects, which we must also
+ // consider. See jsperf.com/compare-array-unique-versions/4 for more
+ // information.
+ var
+ out = [],
+ val,
+ i, ien=src.length,
+ j, k=0;
+
+ again: for ( i=0 ; i<ien ; i++ ) {
+ val = src[i];
+
+ for ( j=0 ; j<k ; j++ ) {
+ if ( out[j] === val ) {
+ continue again;
+ }
+ }
+
+ out.push( val );
+ k++;
+ }
+
+ return out;
+ };
+
+
+ /**
+ * DataTables utility methods
+ *
+ * This namespace provides helper methods that DataTables uses internally to
+ * create a DataTable, but which are not exclusively used only for DataTables.
+ * These methods can be used by extension authors to save the duplication of
+ * code.
+ *
+ * @namespace
+ */
+ DataTable.util = {
+ /**
+ * Throttle the calls to a function. Arguments and context are maintained
+ * for the throttled function.
+ *
+ * @param {function} fn Function to be called
+ * @param {integer} freq Call frequency in mS
+ * @return {function} Wrapped function
+ */
+ throttle: function ( fn, freq ) {
+ var
+ frequency = freq !== undefined ? freq : 200,
+ last,
+ timer;
+
+ return function () {
+ var
+ that = this,
+ now = +new Date(),
+ args = arguments;
+
+ if ( last && now < last + frequency ) {
+ clearTimeout( timer );
+
+ timer = setTimeout( function () {
+ last = undefined;
+ fn.apply( that, args );
+ }, frequency );
+ }
+ else {
+ last = now;
+ fn.apply( that, args );
+ }
+ };
+ },
+
+
+ /**
+ * Escape a string such that it can be used in a regular expression
+ *
+ * @param {string} val string to escape
+ * @returns {string} escaped string
+ */
+ escapeRegex: function ( val ) {
+ return val.replace( _re_escape_regex, '\\$1' );
+ }
+ };
+
+
+
+ /**
+ * Create a mapping object that allows camel case parameters to be looked up
+ * for their Hungarian counterparts. The mapping is stored in a private
+ * parameter called `_hungarianMap` which can be accessed on the source object.
+ * @param {object} o
+ * @memberof DataTable#oApi
+ */
+ function _fnHungarianMap ( o )
+ {
+ var
+ hungarian = 'a aa ai ao as b fn i m o s ',
+ match,
+ newKey,
+ map = {};
+
+ $.each( o, function (key, val) {
+ match = key.match(/^([^A-Z]+?)([A-Z])/);
+
+ if ( match && hungarian.indexOf(match[1]+' ') !== -1 )
+ {
+ newKey = key.replace( match[0], match[2].toLowerCase() );
+ map[ newKey ] = key;
+
+ if ( match[1] === 'o' )
+ {
+ _fnHungarianMap( o[key] );
+ }
+ }
+ } );
+
+ o._hungarianMap = map;
+ }
+
+
+ /**
+ * Convert from camel case parameters to Hungarian, based on a Hungarian map
+ * created by _fnHungarianMap.
+ * @param {object} src The model object which holds all parameters that can be
+ * mapped.
+ * @param {object} user The object to convert from camel case to Hungarian.
+ * @param {boolean} force When set to `true`, properties which already have a
+ * Hungarian value in the `user` object will be overwritten. Otherwise they
+ * won't be.
+ * @memberof DataTable#oApi
+ */
+ function _fnCamelToHungarian ( src, user, force )
+ {
+ if ( ! src._hungarianMap ) {
+ _fnHungarianMap( src );
+ }
+
+ var hungarianKey;
+
+ $.each( user, function (key, val) {
+ hungarianKey = src._hungarianMap[ key ];
+
+ if ( hungarianKey !== undefined && (force || user[hungarianKey] === undefined) )
+ {
+ // For objects, we need to buzz down into the object to copy parameters
+ if ( hungarianKey.charAt(0) === 'o' )
+ {
+ // Copy the camelCase options over to the hungarian
+ if ( ! user[ hungarianKey ] ) {
+ user[ hungarianKey ] = {};
+ }
+ $.extend( true, user[hungarianKey], user[key] );
+
+ _fnCamelToHungarian( src[hungarianKey], user[hungarianKey], force );
+ }
+ else {
+ user[hungarianKey] = user[ key ];
+ }
+ }
+ } );
+ }
+
+
+ /**
+ * Language compatibility - when certain options are given, and others aren't, we
+ * need to duplicate the values over, in order to provide backwards compatibility
+ * with older language files.
+ * @param {object} oSettings dataTables settings object
+ * @memberof DataTable#oApi
+ */
+ function _fnLanguageCompat( lang )
+ {
+ var defaults = DataTable.defaults.oLanguage;
+ var zeroRecords = lang.sZeroRecords;
+
+ /* Backwards compatibility - if there is no sEmptyTable given, then use the same as
+ * sZeroRecords - assuming that is given.
+ */
+ if ( ! lang.sEmptyTable && zeroRecords &&
+ defaults.sEmptyTable === "No data available in table" )
+ {
+ _fnMap( lang, lang, 'sZeroRecords', 'sEmptyTable' );
+ }
+
+ /* Likewise with loading records */
+ if ( ! lang.sLoadingRecords && zeroRecords &&
+ defaults.sLoadingRecords === "Loading..." )
+ {
+ _fnMap( lang, lang, 'sZeroRecords', 'sLoadingRecords' );
+ }
+
+ // Old parameter name of the thousands separator mapped onto the new
+ if ( lang.sInfoThousands ) {
+ lang.sThousands = lang.sInfoThousands;
+ }
+
+ var decimal = lang.sDecimal;
+ if ( decimal ) {
+ _addNumericSort( decimal );
+ }
+ }
+
+
+ /**
+ * Map one parameter onto another
+ * @param {object} o Object to map
+ * @param {*} knew The new parameter name
+ * @param {*} old The old parameter name
+ */
+ var _fnCompatMap = function ( o, knew, old ) {
+ if ( o[ knew ] !== undefined ) {
+ o[ old ] = o[ knew ];
+ }
+ };
+
+
+ /**
+ * Provide backwards compatibility for the main DT options. Note that the new
+ * options are mapped onto the old parameters, so this is an external interface
+ * change only.
+ * @param {object} init Object to map
+ */
+ function _fnCompatOpts ( init )
+ {
+ _fnCompatMap( init, 'ordering', 'bSort' );
+ _fnCompatMap( init, 'orderMulti', 'bSortMulti' );
+ _fnCompatMap( init, 'orderClasses', 'bSortClasses' );
+ _fnCompatMap( init, 'orderCellsTop', 'bSortCellsTop' );
+ _fnCompatMap( init, 'order', 'aaSorting' );
+ _fnCompatMap( init, 'orderFixed', 'aaSortingFixed' );
+ _fnCompatMap( init, 'paging', 'bPaginate' );
+ _fnCompatMap( init, 'pagingType', 'sPaginationType' );
+ _fnCompatMap( init, 'pageLength', 'iDisplayLength' );
+ _fnCompatMap( init, 'searching', 'bFilter' );
+
+ // Boolean initialisation of x-scrolling
+ if ( typeof init.sScrollX === 'boolean' ) {
+ init.sScrollX = init.sScrollX ? '100%' : '';
+ }
+ if ( typeof init.scrollX === 'boolean' ) {
+ init.scrollX = init.scrollX ? '100%' : '';
+ }
+
+ // Column search objects are in an array, so it needs to be converted
+ // element by element
+ var searchCols = init.aoSearchCols;
+
+ if ( searchCols ) {
+ for ( var i=0, ien=searchCols.length ; i<ien ; i++ ) {
+ if ( searchCols[i] ) {
+ _fnCamelToHungarian( DataTable.models.oSearch, searchCols[i] );
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Provide backwards compatibility for column options. Note that the new options
+ * are mapped onto the old parameters, so this is an external interface change
+ * only.
+ * @param {object} init Object to map
+ */
+ function _fnCompatCols ( init )
+ {
+ _fnCompatMap( init, 'orderable', 'bSortable' );
+ _fnCompatMap( init, 'orderData', 'aDataSort' );
+ _fnCompatMap( init, 'orderSequence', 'asSorting' );
+ _fnCompatMap( init, 'orderDataType', 'sortDataType' );
+
+ // orderData can be given as an integer
+ var dataSort = init.aDataSort;
+ if ( typeof dataSort === 'number' && ! $.isArray( dataSort ) ) {
+ init.aDataSort = [ dataSort ];
+ }
+ }
+
+
+ /**
+ * Browser feature detection for capabilities, quirks
+ * @param {object} settings dataTables settings object
+ * @memberof DataTable#oApi
+ */
+ function _fnBrowserDetect( settings )
+ {
+ // We don't need to do this every time DataTables is constructed, the values
+ // calculated are specific to the browser and OS configuration which we
+ // don't expect to change between initialisations
+ if ( ! DataTable.__browser ) {
+ var browser = {};
+ DataTable.__browser = browser;
+
+ // Scrolling feature / quirks detection
+ var n = $('<div/>')
+ .css( {
+ position: 'fixed',
+ top: 0,
+ left: $(window).scrollLeft()*-1, // allow for scrolling
+ height: 1,
+ width: 1,
+ overflow: 'hidden'
+ } )
+ .append(
+ $('<div/>')
+ .css( {
+ position: 'absolute',
+ top: 1,
+ left: 1,
+ width: 100,
+ overflow: 'scroll'
+ } )
+ .append(
+ $('<div/>')
+ .css( {
+ width: '100%',
+ height: 10
+ } )
+ )
+ )
+ .appendTo( 'body' );
+
+ var outer = n.children();
+ var inner = outer.children();
+
+ // Numbers below, in order, are:
+ // inner.offsetWidth, inner.clientWidth, outer.offsetWidth, outer.clientWidth
+ //
+ // IE6 XP: 100 100 100 83
+ // IE7 Vista: 100 100 100 83
+ // IE 8+ Windows: 83 83 100 83
+ // Evergreen Windows: 83 83 100 83
+ // Evergreen Mac with scrollbars: 85 85 100 85
+ // Evergreen Mac without scrollbars: 100 100 100 100
+
+ // Get scrollbar width
+ browser.barWidth = outer[0].offsetWidth - outer[0].clientWidth;
+
+ // IE6/7 will oversize a width 100% element inside a scrolling element, to
+ // include the width of the scrollbar, while other browsers ensure the inner
+ // element is contained without forcing scrolling
+ browser.bScrollOversize = inner[0].offsetWidth === 100 && outer[0].clientWidth !== 100;
+
+ // In rtl text layout, some browsers (most, but not all) will place the
+ // scrollbar on the left, rather than the right.
+ browser.bScrollbarLeft = Math.round( inner.offset().left ) !== 1;
+
+ // IE8- don't provide height and width for getBoundingClientRect
+ browser.bBounding = n[0].getBoundingClientRect().width ? true : false;
+
+ n.remove();
+ }
+
+ $.extend( settings.oBrowser, DataTable.__browser );
+ settings.oScroll.iBarWidth = DataTable.__browser.barWidth;
+ }
+
+
+ /**
+ * Array.prototype reduce[Right] method, used for browsers which don't support
+ * JS 1.6. Done this way to reduce code size, since we iterate either way
+ * @param {object} settings dataTables settings object
+ * @memberof DataTable#oApi
+ */
+ function _fnReduce ( that, fn, init, start, end, inc )
+ {
+ var
+ i = start,
+ value,
+ isSet = false;
+
+ if ( init !== undefined ) {
+ value = init;
+ isSet = true;
+ }
+
+ while ( i !== end ) {
+ if ( ! that.hasOwnProperty(i) ) {
+ continue;
+ }
+
+ value = isSet ?
+ fn( value, that[i], i, that ) :
+ that[i];
+
+ isSet = true;
+ i += inc;
+ }
+
+ return value;
+ }
+
+ /**
+ * Add a column to the list used for the table with default values
+ * @param {object} oSettings dataTables settings object
+ * @param {node} nTh The th element for this column
+ * @memberof DataTable#oApi
+ */
+ function _fnAddColumn( oSettings, nTh )
+ {
+ // Add column to aoColumns array
+ var oDefaults = DataTable.defaults.column;
+ var iCol = oSettings.aoColumns.length;
+ var oCol = $.extend( {}, DataTable.models.oColumn, oDefaults, {
+ "nTh": nTh ? nTh : document.createElement('th'),
+ "sTitle": oDefaults.sTitle ? oDefaults.sTitle : nTh ? nTh.innerHTML : '',
+ "aDataSort": oDefaults.aDataSort ? oDefaults.aDataSort : [iCol],
+ "mData": oDefaults.mData ? oDefaults.mData : iCol,
+ idx: iCol
+ } );
+ oSettings.aoColumns.push( oCol );
+
+ // Add search object for column specific search. Note that the `searchCols[ iCol ]`
+ // passed into extend can be undefined. This allows the user to give a default
+ // with only some of the parameters defined, and also not give a default
+ var searchCols = oSettings.aoPreSearchCols;
+ searchCols[ iCol ] = $.extend( {}, DataTable.models.oSearch, searchCols[ iCol ] );
+
+ // Use the default column options function to initialise classes etc
+ _fnColumnOptions( oSettings, iCol, $(nTh).data() );
+ }
+
+
+ /**
+ * Apply options for a column
+ * @param {object} oSettings dataTables settings object
+ * @param {int} iCol column index to consider
+ * @param {object} oOptions object with sType, bVisible and bSearchable etc
+ * @memberof DataTable#oApi
+ */
+ function _fnColumnOptions( oSettings, iCol, oOptions )
+ {
+ var oCol = oSettings.aoColumns[ iCol ];
+ var oClasses = oSettings.oClasses;
+ var th = $(oCol.nTh);
+
+ // Try to get width information from the DOM. We can't get it from CSS
+ // as we'd need to parse the CSS stylesheet. `width` option can override
+ if ( ! oCol.sWidthOrig ) {
+ // Width attribute
+ oCol.sWidthOrig = th.attr('width') || null;
+
+ // Style attribute
+ var t = (th.attr('style') || '').match(/width:\s*(\d+[pxem%]+)/);
+ if ( t ) {
+ oCol.sWidthOrig = t[1];
+ }
+ }
+
+ /* User specified column options */
+ if ( oOptions !== undefined && oOptions !== null )
+ {
+ // Backwards compatibility
+ _fnCompatCols( oOptions );
+
+ // Map camel case parameters to their Hungarian counterparts
+ _fnCamelToHungarian( DataTable.defaults.column, oOptions );
+
+ /* Backwards compatibility for mDataProp */
+ if ( oOptions.mDataProp !== undefined && !oOptions.mData )
+ {
+ oOptions.mData = oOptions.mDataProp;
+ }
+
+ if ( oOptions.sType )
+ {
+ oCol._sManualType = oOptions.sType;
+ }
+
+ // `class` is a reserved word in Javascript, so we need to provide
+ // the ability to use a valid name for the camel case input
+ if ( oOptions.className && ! oOptions.sClass )
+ {
+ oOptions.sClass = oOptions.className;
+ }
+
+ $.extend( oCol, oOptions );
+ _fnMap( oCol, oOptions, "sWidth", "sWidthOrig" );
+
+ /* iDataSort to be applied (backwards compatibility), but aDataSort will take
+ * priority if defined
+ */
+ if ( oOptions.iDataSort !== undefined )
+ {
+ oCol.aDataSort = [ oOptions.iDataSort ];
+ }
+ _fnMap( oCol, oOptions, "aDataSort" );
+ }
+
+ /* Cache the data get and set functions for speed */
+ var mDataSrc = oCol.mData;
+ var mData = _fnGetObjectDataFn( mDataSrc );
+ var mRender = oCol.mRender ? _fnGetObjectDataFn( oCol.mRender ) : null;
+
+ var attrTest = function( src ) {
+ return typeof src === 'string' && src.indexOf('@') !== -1;
+ };
+ oCol._bAttrSrc = $.isPlainObject( mDataSrc ) && (
+ attrTest(mDataSrc.sort) || attrTest(mDataSrc.type) || attrTest(mDataSrc.filter)
+ );
+ oCol._setter = null;
+
+ oCol.fnGetData = function (rowData, type, meta) {
+ var innerData = mData( rowData, type, undefined, meta );
+
+ return mRender && type ?
+ mRender( innerData, type, rowData, meta ) :
+ innerData;
+ };
+ oCol.fnSetData = function ( rowData, val, meta ) {
+ return _fnSetObjectDataFn( mDataSrc )( rowData, val, meta );
+ };
+
+ // Indicate if DataTables should read DOM data as an object or array
+ // Used in _fnGetRowElements
+ if ( typeof mDataSrc !== 'number' ) {
+ oSettings._rowReadObject = true;
+ }
+
+ /* Feature sorting overrides column specific when off */
+ if ( !oSettings.oFeatures.bSort )
+ {
+ oCol.bSortable = false;
+ th.addClass( oClasses.sSortableNone ); // Have to add class here as order event isn't called
+ }
+
+ /* Check that the class assignment is correct for sorting */
+ var bAsc = $.inArray('asc', oCol.asSorting) !== -1;
+ var bDesc = $.inArray('desc', oCol.asSorting) !== -1;
+ if ( !oCol.bSortable || (!bAsc && !bDesc) )
+ {
+ oCol.sSortingClass = oClasses.sSortableNone;
+ oCol.sSortingClassJUI = "";
+ }
+ else if ( bAsc && !bDesc )
+ {
+ oCol.sSortingClass = oClasses.sSortableAsc;
+ oCol.sSortingClassJUI = oClasses.sSortJUIAscAllowed;
+ }
+ else if ( !bAsc && bDesc )
+ {
+ oCol.sSortingClass = oClasses.sSortableDesc;
+ oCol.sSortingClassJUI = oClasses.sSortJUIDescAllowed;
+ }
+ else
+ {
+ oCol.sSortingClass = oClasses.sSortable;
+ oCol.sSortingClassJUI = oClasses.sSortJUI;
+ }
+ }
+
+
+ /**
+ * Adjust the table column widths for new data. Note: you would probably want to
+ * do a redraw after calling this function!
+ * @param {object} settings dataTables settings object
+ * @memberof DataTable#oApi
+ */
+ function _fnAdjustColumnSizing ( settings )
+ {
+ /* Not interested in doing column width calculation if auto-width is disabled */
+ if ( settings.oFeatures.bAutoWidth !== false )
+ {
+ var columns = settings.aoColumns;
+
+ _fnCalculateColumnWidths( settings );
+ for ( var i=0 , iLen=columns.length ; i<iLen ; i++ )
+ {
+ columns[i].nTh.style.width = columns[i].sWidth;
+ }
+ }
+
+ var scroll = settings.oScroll;
+ if ( scroll.sY !== '' || scroll.sX !== '')
+ {
+ _fnScrollDraw( settings );
+ }
+
+ _fnCallbackFire( settings, null, 'column-sizing', [settings] );
+ }
+
+
+ /**
+ * Covert the index of a visible column to the index in the data array (take account
+ * of hidden columns)
+ * @param {object} oSettings dataTables settings object
+ * @param {int} iMatch Visible column index to lookup
+ * @returns {int} i the data index
+ * @memberof DataTable#oApi
+ */
+ function _fnVisibleToColumnIndex( oSettings, iMatch )
+ {
+ var aiVis = _fnGetColumns( oSettings, 'bVisible' );
+
+ return typeof aiVis[iMatch] === 'number' ?
+ aiVis[iMatch] :
+ null;
+ }
+
+
+ /**
+ * Covert the index of an index in the data array and convert it to the visible
+ * column index (take account of hidden columns)
+ * @param {int} iMatch Column index to lookup
+ * @param {object} oSettings dataTables settings object
+ * @returns {int} i the data index
+ * @memberof DataTable#oApi
+ */
+ function _fnColumnIndexToVisible( oSettings, iMatch )
+ {
+ var aiVis = _fnGetColumns( oSettings, 'bVisible' );
+ var iPos = $.inArray( iMatch, aiVis );
+
+ return iPos !== -1 ? iPos : null;
+ }
+
+
+ /**
+ * Get the number of visible columns
+ * @param {object} oSettings dataTables settings object
+ * @returns {int} i the number of visible columns
+ * @memberof DataTable#oApi
+ */
+ function _fnVisbleColumns( oSettings )
+ {
+ var vis = 0;
+
+ // No reduce in IE8, use a loop for now
+ $.each( oSettings.aoColumns, function ( i, col ) {
+ if ( col.bVisible && $(col.nTh).css('display') !== 'none' ) {
+ vis++;
+ }
+ } );
+
+ return vis;
+ }
+
+
+ /**
+ * Get an array of column indexes that match a given property
+ * @param {object} oSettings dataTables settings object
+ * @param {string} sParam Parameter in aoColumns to look for - typically
+ * bVisible or bSearchable
+ * @returns {array} Array of indexes with matched properties
+ * @memberof DataTable#oApi
+ */
+ function _fnGetColumns( oSettings, sParam )
+ {
+ var a = [];
+
+ $.map( oSettings.aoColumns, function(val, i) {
+ if ( val[sParam] ) {
+ a.push( i );
+ }
+ } );
+
+ return a;
+ }
+
+
+ /**
+ * Calculate the 'type' of a column
+ * @param {object} settings dataTables settings object
+ * @memberof DataTable#oApi
+ */
+ function _fnColumnTypes ( settings )
+ {
+ var columns = settings.aoColumns;
+ var data = settings.aoData;
+ var types = DataTable.ext.type.detect;
+ var i, ien, j, jen, k, ken;
+ var col, cell, detectedType, cache;
+
+ // For each column, spin over the
+ for ( i=0, ien=columns.length ; i<ien ; i++ ) {
+ col = columns[i];
+ cache = [];
+
+ if ( ! col.sType && col._sManualType ) {
+ col.sType = col._sManualType;
+ }
+ else if ( ! col.sType ) {
+ for ( j=0, jen=types.length ; j<jen ; j++ ) {
+ for ( k=0, ken=data.length ; k<ken ; k++ ) {
+ // Use a cache array so we only need to get the type data
+ // from the formatter once (when using multiple detectors)
+ if ( cache[k] === undefined ) {
+ cache[k] = _fnGetCellData( settings, k, i, 'type' );
+ }
+
+ detectedType = types[j]( cache[k], settings );
+
+ // If null, then this type can't apply to this column, so
+ // rather than testing all cells, break out. There is an
+ // exception for the last type which is `html`. We need to
+ // scan all rows since it is possible to mix string and HTML
+ // types
+ if ( ! detectedType && j !== types.length-1 ) {
+ break;
+ }
+
+ // Only a single match is needed for html type since it is
+ // bottom of the pile and very similar to string
+ if ( detectedType === 'html' ) {
+ break;
+ }
+ }
+
+ // Type is valid for all data points in the column - use this
+ // type
+ if ( detectedType ) {
+ col.sType = detectedType;
+ break;
+ }
+ }
+
+ // Fall back - if no type was detected, always use string
+ if ( ! col.sType ) {
+ col.sType = 'string';
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Take the column definitions and static columns arrays and calculate how
+ * they relate to column indexes. The callback function will then apply the
+ * definition found for a column to a suitable configuration object.
+ * @param {object} oSettings dataTables settings object
+ * @param {array} aoColDefs The aoColumnDefs array that is to be applied
+ * @param {array} aoCols The aoColumns array that defines columns individually
+ * @param {function} fn Callback function - takes two parameters, the calculated
+ * column index and the definition for that column.
+ * @memberof DataTable#oApi
+ */
+ function _fnApplyColumnDefs( oSettings, aoColDefs, aoCols, fn )
+ {
+ var i, iLen, j, jLen, k, kLen, def;
+ var columns = oSettings.aoColumns;
+
+ // Column definitions with aTargets
+ if ( aoColDefs )
+ {
+ /* Loop over the definitions array - loop in reverse so first instance has priority */
+ for ( i=aoColDefs.length-1 ; i>=0 ; i-- )
+ {
+ def = aoColDefs[i];
+
+ /* Each definition can target multiple columns, as it is an array */
+ var aTargets = def.targets !== undefined ?
+ def.targets :
+ def.aTargets;
+
+ if ( ! $.isArray( aTargets ) )
+ {
+ aTargets = [ aTargets ];
+ }
+
+ for ( j=0, jLen=aTargets.length ; j<jLen ; j++ )
+ {
+ if ( typeof aTargets[j] === 'number' && aTargets[j] >= 0 )
+ {
+ /* Add columns that we don't yet know about */
+ while( columns.length <= aTargets[j] )
+ {
+ _fnAddColumn( oSettings );
+ }
+
+ /* Integer, basic index */
+ fn( aTargets[j], def );
+ }
+ else if ( typeof aTargets[j] === 'number' && aTargets[j] < 0 )
+ {
+ /* Negative integer, right to left column counting */
+ fn( columns.length+aTargets[j], def );
+ }
+ else if ( typeof aTargets[j] === 'string' )
+ {
+ /* Class name matching on TH element */
+ for ( k=0, kLen=columns.length ; k<kLen ; k++ )
+ {
+ if ( aTargets[j] == "_all" ||
+ $(columns[k].nTh).hasClass( aTargets[j] ) )
+ {
+ fn( k, def );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Statically defined columns array
+ if ( aoCols )
+ {
+ for ( i=0, iLen=aoCols.length ; i<iLen ; i++ )
+ {
+ fn( i, aoCols[i] );
+ }
+ }
+ }
+
+ /**
+ * Add a data array to the table, creating DOM node etc. This is the parallel to
+ * _fnGatherData, but for adding rows from a Javascript source, rather than a
+ * DOM source.
+ * @param {object} oSettings dataTables settings object
+ * @param {array} aData data array to be added
+ * @param {node} [nTr] TR element to add to the table - optional. If not given,
+ * DataTables will create a row automatically
+ * @param {array} [anTds] Array of TD|TH elements for the row - must be given
+ * if nTr is.
+ * @returns {int} >=0 if successful (index of new aoData entry), -1 if failed
+ * @memberof DataTable#oApi
+ */
+ function _fnAddData ( oSettings, aDataIn, nTr, anTds )
+ {
+ /* Create the object for storing information about this new row */
+ var iRow = oSettings.aoData.length;
+ var oData = $.extend( true, {}, DataTable.models.oRow, {
+ src: nTr ? 'dom' : 'data',
+ idx: iRow
+ } );
+
+ oData._aData = aDataIn;
+ oSettings.aoData.push( oData );
+
+ /* Create the cells */
+ var nTd, sThisType;
+ var columns = oSettings.aoColumns;
+
+ // Invalidate the column types as the new data needs to be revalidated
+ for ( var i=0, iLen=columns.length ; i<iLen ; i++ )
+ {
+ columns[i].sType = null;
+ }
+
+ /* Add to the display array */
+ oSettings.aiDisplayMaster.push( iRow );
+
+ var id = oSettings.rowIdFn( aDataIn );
+ if ( id !== undefined ) {
+ oSettings.aIds[ id ] = oData;
+ }
+
+ /* Create the DOM information, or register it if already present */
+ if ( nTr || ! oSettings.oFeatures.bDeferRender )
+ {
+ _fnCreateTr( oSettings, iRow, nTr, anTds );
+ }
+
+ return iRow;
+ }
+
+
+ /**
+ * Add one or more TR elements to the table. Generally we'd expect to
+ * use this for reading data from a DOM sourced table, but it could be
+ * used for an TR element. Note that if a TR is given, it is used (i.e.
+ * it is not cloned).
+ * @param {object} settings dataTables settings object
+ * @param {array|node|jQuery} trs The TR element(s) to add to the table
+ * @returns {array} Array of indexes for the added rows
+ * @memberof DataTable#oApi
+ */
+ function _fnAddTr( settings, trs )
+ {
+ var row;
+
+ // Allow an individual node to be passed in
+ if ( ! (trs instanceof $) ) {
+ trs = $(trs);
+ }
+
+ return trs.map( function (i, el) {
+ row = _fnGetRowElements( settings, el );
+ return _fnAddData( settings, row.data, el, row.cells );
+ } );
+ }
+
+
+ /**
+ * Take a TR element and convert it to an index in aoData
+ * @param {object} oSettings dataTables settings object
+ * @param {node} n the TR element to find
+ * @returns {int} index if the node is found, null if not
+ * @memberof DataTable#oApi
+ */
+ function _fnNodeToDataIndex( oSettings, n )
+ {
+ return (n._DT_RowIndex!==undefined) ? n._DT_RowIndex : null;
+ }
+
+
+ /**
+ * Take a TD element and convert it into a column data index (not the visible index)
+ * @param {object} oSettings dataTables settings object
+ * @param {int} iRow The row number the TD/TH can be found in
+ * @param {node} n The TD/TH element to find
+ * @returns {int} index if the node is found, -1 if not
+ * @memberof DataTable#oApi
+ */
+ function _fnNodeToColumnIndex( oSettings, iRow, n )
+ {
+ return $.inArray( n, oSettings.aoData[ iRow ].anCells );
+ }
+
+
+ /**
+ * Get the data for a given cell from the internal cache, taking into account data mapping
+ * @param {object} settings dataTables settings object
+ * @param {int} rowIdx aoData row id
+ * @param {int} colIdx Column index
+ * @param {string} type data get type ('display', 'type' 'filter' 'sort')
+ * @returns {*} Cell data
+ * @memberof DataTable#oApi
+ */
+ function _fnGetCellData( settings, rowIdx, colIdx, type )
+ {
+ var draw = settings.iDraw;
+ var col = settings.aoColumns[colIdx];
+ var rowData = settings.aoData[rowIdx]._aData;
+ var defaultContent = col.sDefaultContent;
+ var cellData = col.fnGetData( rowData, type, {
+ settings: settings,
+ row: rowIdx,
+ col: colIdx
+ } );
+
+ if ( cellData === undefined ) {
+ if ( settings.iDrawError != draw && defaultContent === null ) {
+ _fnLog( settings, 0, "Requested unknown parameter "+
+ (typeof col.mData=='function' ? '{function}' : "'"+col.mData+"'")+
+ " for row "+rowIdx+", column "+colIdx, 4 );
+ settings.iDrawError = draw;
+ }
+ return defaultContent;
+ }
+
+ // When the data source is null and a specific data type is requested (i.e.
+ // not the original data), we can use default column data
+ if ( (cellData === rowData || cellData === null) && defaultContent !== null && type !== undefined ) {
+ cellData = defaultContent;
+ }
+ else if ( typeof cellData === 'function' ) {
+ // If the data source is a function, then we run it and use the return,
+ // executing in the scope of the data object (for instances)
+ return cellData.call( rowData );
+ }
+
+ if ( cellData === null && type == 'display' ) {
+ return '';
+ }
+ return cellData;
+ }
+
+
+ /**
+ * Set the value for a specific cell, into the internal data cache
+ * @param {object} settings dataTables settings object
+ * @param {int} rowIdx aoData row id
+ * @param {int} colIdx Column index
+ * @param {*} val Value to set
+ * @memberof DataTable#oApi
+ */
+ function _fnSetCellData( settings, rowIdx, colIdx, val )
+ {
+ var col = settings.aoColumns[colIdx];
+ var rowData = settings.aoData[rowIdx]._aData;
+
+ col.fnSetData( rowData, val, {
+ settings: settings,
+ row: rowIdx,
+ col: colIdx
+ } );
+ }
+
+
+ // Private variable that is used to match action syntax in the data property object
+ var __reArray = /\[.*?\]$/;
+ var __reFn = /\(\)$/;
+
+ /**
+ * Split string on periods, taking into account escaped periods
+ * @param {string} str String to split
+ * @return {array} Split string
+ */
+ function _fnSplitObjNotation( str )
+ {
+ return $.map( str.match(/(\\.|[^\.])+/g) || [''], function ( s ) {
+ return s.replace(/\\\./g, '.');
+ } );
+ }
+
+
+ /**
+ * Return a function that can be used to get data from a source object, taking
+ * into account the ability to use nested objects as a source
+ * @param {string|int|function} mSource The data source for the object
+ * @returns {function} Data get function
+ * @memberof DataTable#oApi
+ */
+ function _fnGetObjectDataFn( mSource )
+ {
+ if ( $.isPlainObject( mSource ) )
+ {
+ /* Build an object of get functions, and wrap them in a single call */
+ var o = {};
+ $.each( mSource, function (key, val) {
+ if ( val ) {
+ o[key] = _fnGetObjectDataFn( val );
+ }
+ } );
+
+ return function (data, type, row, meta) {
+ var t = o[type] || o._;
+ return t !== undefined ?
+ t(data, type, row, meta) :
+ data;
+ };
+ }
+ else if ( mSource === null )
+ {
+ /* Give an empty string for rendering / sorting etc */
+ return function (data) { // type, row and meta also passed, but not used
+ return data;
+ };
+ }
+ else if ( typeof mSource === 'function' )
+ {
+ return function (data, type, row, meta) {
+ return mSource( data, type, row, meta );
+ };
+ }
+ else if ( typeof mSource === 'string' && (mSource.indexOf('.') !== -1 ||
+ mSource.indexOf('[') !== -1 || mSource.indexOf('(') !== -1) )
+ {
+ /* If there is a . in the source string then the data source is in a
+ * nested object so we loop over the data for each level to get the next
+ * level down. On each loop we test for undefined, and if found immediately
+ * return. This allows entire objects to be missing and sDefaultContent to
+ * be used if defined, rather than throwing an error
+ */
+ var fetchData = function (data, type, src) {
+ var arrayNotation, funcNotation, out, innerSrc;
+
+ if ( src !== "" )
+ {
+ var a = _fnSplitObjNotation( src );
+
+ for ( var i=0, iLen=a.length ; i<iLen ; i++ )
+ {
+ // Check if we are dealing with special notation
+ arrayNotation = a[i].match(__reArray);
+ funcNotation = a[i].match(__reFn);
+
+ if ( arrayNotation )
+ {
+ // Array notation
+ a[i] = a[i].replace(__reArray, '');
+
+ // Condition allows simply [] to be passed in
+ if ( a[i] !== "" ) {
+ data = data[ a[i] ];
+ }
+ out = [];
+
+ // Get the remainder of the nested object to get
+ a.splice( 0, i+1 );
+ innerSrc = a.join('.');
+
+ // Traverse each entry in the array getting the properties requested
+ if ( $.isArray( data ) ) {
+ for ( var j=0, jLen=data.length ; j<jLen ; j++ ) {
+ out.push( fetchData( data[j], type, innerSrc ) );
+ }
+ }
+
+ // If a string is given in between the array notation indicators, that
+ // is used to join the strings together, otherwise an array is returned
+ var join = arrayNotation[0].substring(1, arrayNotation[0].length-1);
+ data = (join==="") ? out : out.join(join);
+
+ // The inner call to fetchData has already traversed through the remainder
+ // of the source requested, so we exit from the loop
+ break;
+ }
+ else if ( funcNotation )
+ {
+ // Function call
+ a[i] = a[i].replace(__reFn, '');
+ data = data[ a[i] ]();
+ continue;
+ }
+
+ if ( data === null || data[ a[i] ] === undefined )
+ {
+ return undefined;
+ }
+ data = data[ a[i] ];
+ }
+ }
+
+ return data;
+ };
+
+ return function (data, type) { // row and meta also passed, but not used
+ return fetchData( data, type, mSource );
+ };
+ }
+ else
+ {
+ /* Array or flat object mapping */
+ return function (data, type) { // row and meta also passed, but not used
+ return data[mSource];
+ };
+ }
+ }
+
+
+ /**
+ * Return a function that can be used to set data from a source object, taking
+ * into account the ability to use nested objects as a source
+ * @param {string|int|function} mSource The data source for the object
+ * @returns {function} Data set function
+ * @memberof DataTable#oApi
+ */
+ function _fnSetObjectDataFn( mSource )
+ {
+ if ( $.isPlainObject( mSource ) )
+ {
+ /* Unlike get, only the underscore (global) option is used for for
+ * setting data since we don't know the type here. This is why an object
+ * option is not documented for `mData` (which is read/write), but it is
+ * for `mRender` which is read only.
+ */
+ return _fnSetObjectDataFn( mSource._ );
+ }
+ else if ( mSource === null )
+ {
+ /* Nothing to do when the data source is null */
+ return function () {};
+ }
+ else if ( typeof mSource === 'function' )
+ {
+ return function (data, val, meta) {
+ mSource( data, 'set', val, meta );
+ };
+ }
+ else if ( typeof mSource === 'string' && (mSource.indexOf('.') !== -1 ||
+ mSource.indexOf('[') !== -1 || mSource.indexOf('(') !== -1) )
+ {
+ /* Like the get, we need to get data from a nested object */
+ var setData = function (data, val, src) {
+ var a = _fnSplitObjNotation( src ), b;
+ var aLast = a[a.length-1];
+ var arrayNotation, funcNotation, o, innerSrc;
+
+ for ( var i=0, iLen=a.length-1 ; i<iLen ; i++ )
+ {
+ // Check if we are dealing with an array notation request
+ arrayNotation = a[i].match(__reArray);
+ funcNotation = a[i].match(__reFn);
+
+ if ( arrayNotation )
+ {
+ a[i] = a[i].replace(__reArray, '');
+ data[ a[i] ] = [];
+
+ // Get the remainder of the nested object to set so we can recurse
+ b = a.slice();
+ b.splice( 0, i+1 );
+ innerSrc = b.join('.');
+
+ // Traverse each entry in the array setting the properties requested
+ if ( $.isArray( val ) )
+ {
+ for ( var j=0, jLen=val.length ; j<jLen ; j++ )
+ {
+ o = {};
+ setData( o, val[j], innerSrc );
+ data[ a[i] ].push( o );
+ }
+ }
+ else
+ {
+ // We've been asked to save data to an array, but it
+ // isn't array data to be saved. Best that can be done
+ // is to just save the value.
+ data[ a[i] ] = val;
+ }
+
+ // The inner call to setData has already traversed through the remainder
+ // of the source and has set the data, thus we can exit here
+ return;
+ }
+ else if ( funcNotation )
+ {
+ // Function call
+ a[i] = a[i].replace(__reFn, '');
+ data = data[ a[i] ]( val );
+ }
+
+ // If the nested object doesn't currently exist - since we are
+ // trying to set the value - create it
+ if ( data[ a[i] ] === null || data[ a[i] ] === undefined )
+ {
+ data[ a[i] ] = {};
+ }
+ data = data[ a[i] ];
+ }
+
+ // Last item in the input - i.e, the actual set
+ if ( aLast.match(__reFn ) )
+ {
+ // Function call
+ data = data[ aLast.replace(__reFn, '') ]( val );
+ }
+ else
+ {
+ // If array notation is used, we just want to strip it and use the property name
+ // and assign the value. If it isn't used, then we get the result we want anyway
+ data[ aLast.replace(__reArray, '') ] = val;
+ }
+ };
+
+ return function (data, val) { // meta is also passed in, but not used
+ return setData( data, val, mSource );
+ };
+ }
+ else
+ {
+ /* Array or flat object mapping */
+ return function (data, val) { // meta is also passed in, but not used
+ data[mSource] = val;
+ };
+ }
+ }
+
+
+ /**
+ * Return an array with the full table data
+ * @param {object} oSettings dataTables settings object
+ * @returns array {array} aData Master data array
+ * @memberof DataTable#oApi
+ */
+ function _fnGetDataMaster ( settings )
+ {
+ return _pluck( settings.aoData, '_aData' );
+ }
+
+
+ /**
+ * Nuke the table
+ * @param {object} oSettings dataTables settings object
+ * @memberof DataTable#oApi
+ */
+ function _fnClearTable( settings )
+ {
+ settings.aoData.length = 0;
+ settings.aiDisplayMaster.length = 0;
+ settings.aiDisplay.length = 0;
+ settings.aIds = {};
+ }
+
+
+ /**
+ * Take an array of integers (index array) and remove a target integer (value - not
+ * the key!)
+ * @param {array} a Index array to target
+ * @param {int} iTarget value to find
+ * @memberof DataTable#oApi
+ */
+ function _fnDeleteIndex( a, iTarget, splice )
+ {
+ var iTargetIndex = -1;
+
+ for ( var i=0, iLen=a.length ; i<iLen ; i++ )
+ {
+ if ( a[i] == iTarget )
+ {
+ iTargetIndex = i;
+ }
+ else if ( a[i] > iTarget )
+ {
+ a[i]--;
+ }
+ }
+
+ if ( iTargetIndex != -1 && splice === undefined )
+ {
+ a.splice( iTargetIndex, 1 );
+ }
+ }
+
+
+ /**
+ * Mark cached data as invalid such that a re-read of the data will occur when
+ * the cached data is next requested. Also update from the data source object.
+ *
+ * @param {object} settings DataTables settings object
+ * @param {int} rowIdx Row index to invalidate
+ * @param {string} [src] Source to invalidate from: undefined, 'auto', 'dom'
+ * or 'data'
+ * @param {int} [colIdx] Column index to invalidate. If undefined the whole
+ * row will be invalidated
+ * @memberof DataTable#oApi
+ *
+ * @todo For the modularisation of v1.11 this will need to become a callback, so
+ * the sort and filter methods can subscribe to it. That will required
+ * initialisation options for sorting, which is why it is not already baked in
+ */
+ function _fnInvalidate( settings, rowIdx, src, colIdx )
+ {
+ var row = settings.aoData[ rowIdx ];
+ var i, ien;
+ var cellWrite = function ( cell, col ) {
+ // This is very frustrating, but in IE if you just write directly
+ // to innerHTML, and elements that are overwritten are GC'ed,
+ // even if there is a reference to them elsewhere
+ while ( cell.childNodes.length ) {
+ cell.removeChild( cell.firstChild );
+ }
+
+ cell.innerHTML = _fnGetCellData( settings, rowIdx, col, 'display' );
+ };
+
+ // Are we reading last data from DOM or the data object?
+ if ( src === 'dom' || ((! src || src === 'auto') && row.src === 'dom') ) {
+ // Read the data from the DOM
+ row._aData = _fnGetRowElements(
+ settings, row, colIdx, colIdx === undefined ? undefined : row._aData
+ )
+ .data;
+ }
+ else {
+ // Reading from data object, update the DOM
+ var cells = row.anCells;
+
+ if ( cells ) {
+ if ( colIdx !== undefined ) {
+ cellWrite( cells[colIdx], colIdx );
+ }
+ else {
+ for ( i=0, ien=cells.length ; i<ien ; i++ ) {
+ cellWrite( cells[i], i );
+ }
+ }
+ }
+ }
+
+ // For both row and cell invalidation, the cached data for sorting and
+ // filtering is nulled out
+ row._aSortData = null;
+ row._aFilterData = null;
+
+ // Invalidate the type for a specific column (if given) or all columns since
+ // the data might have changed
+ var cols = settings.aoColumns;
+ if ( colIdx !== undefined ) {
+ cols[ colIdx ].sType = null;
+ }
+ else {
+ for ( i=0, ien=cols.length ; i<ien ; i++ ) {
+ cols[i].sType = null;
+ }
+
+ // Update DataTables special `DT_*` attributes for the row
+ _fnRowAttributes( settings, row );
+ }
+ }
+
+
+ /**
+ * Build a data source object from an HTML row, reading the contents of the
+ * cells that are in the row.
+ *
+ * @param {object} settings DataTables settings object
+ * @param {node|object} TR element from which to read data or existing row
+ * object from which to re-read the data from the cells
+ * @param {int} [colIdx] Optional column index
+ * @param {array|object} [d] Data source object. If `colIdx` is given then this
+ * parameter should also be given and will be used to write the data into.
+ * Only the column in question will be written
+ * @returns {object} Object with two parameters: `data` the data read, in
+ * document order, and `cells` and array of nodes (they can be useful to the
+ * caller, so rather than needing a second traversal to get them, just return
+ * them from here).
+ * @memberof DataTable#oApi
+ */
+ function _fnGetRowElements( settings, row, colIdx, d )
+ {
+ var
+ tds = [],
+ td = row.firstChild,
+ name, col, o, i=0, contents,
+ columns = settings.aoColumns,
+ objectRead = settings._rowReadObject;
+
+ // Allow the data object to be passed in, or construct
+ d = d !== undefined ?
+ d :
+ objectRead ?
+ {} :
+ [];
+
+ var attr = function ( str, td ) {
+ if ( typeof str === 'string' ) {
+ var idx = str.indexOf('@');
+
+ if ( idx !== -1 ) {
+ var attr = str.substring( idx+1 );
+ var setter = _fnSetObjectDataFn( str );
+ setter( d, td.getAttribute( attr ) );
+ }
+ }
+ };
+
+ // Read data from a cell and store into the data object
+ var cellProcess = function ( cell ) {
+ if ( colIdx === undefined || colIdx === i ) {
+ col = columns[i];
+ contents = $.trim(cell.innerHTML);
+
+ if ( col && col._bAttrSrc ) {
+ var setter = _fnSetObjectDataFn( col.mData._ );
+ setter( d, contents );
+
+ attr( col.mData.sort, cell );
+ attr( col.mData.type, cell );
+ attr( col.mData.filter, cell );
+ }
+ else {
+ // Depending on the `data` option for the columns the data can
+ // be read to either an object or an array.
+ if ( objectRead ) {
+ if ( ! col._setter ) {
+ // Cache the setter function
+ col._setter = _fnSetObjectDataFn( col.mData );
+ }
+ col._setter( d, contents );
+ }
+ else {
+ d[i] = contents;
+ }
+ }
+ }
+
+ i++;
+ };
+
+ if ( td ) {
+ // `tr` element was passed in
+ while ( td ) {
+ name = td.nodeName.toUpperCase();
+
+ if ( name == "TD" || name == "TH" ) {
+ cellProcess( td );
+ tds.push( td );
+ }
+
+ td = td.nextSibling;
+ }
+ }
+ else {
+ // Existing row object passed in
+ tds = row.anCells;
+
+ for ( var j=0, jen=tds.length ; j<jen ; j++ ) {
+ cellProcess( tds[j] );
+ }
+ }
+
+ // Read the ID from the DOM if present
+ var rowNode = row.firstChild ? row : row.nTr;
+
+ if ( rowNode ) {
+ var id = rowNode.getAttribute( 'id' );
+
+ if ( id ) {
+ _fnSetObjectDataFn( settings.rowId )( d, id );
+ }
+ }
+
+ return {
+ data: d,
+ cells: tds
+ };
+ }
+ /**
+ * Create a new TR element (and it's TD children) for a row
+ * @param {object} oSettings dataTables settings object
+ * @param {int} iRow Row to consider
+ * @param {node} [nTrIn] TR element to add to the table - optional. If not given,
+ * DataTables will create a row automatically
+ * @param {array} [anTds] Array of TD|TH elements for the row - must be given
+ * if nTr is.
+ * @memberof DataTable#oApi
+ */
+ function _fnCreateTr ( oSettings, iRow, nTrIn, anTds )
+ {
+ var
+ row = oSettings.aoData[iRow],
+ rowData = row._aData,
+ cells = [],
+ nTr, nTd, oCol,
+ i, iLen;
+
+ if ( row.nTr === null )
+ {
+ nTr = nTrIn || document.createElement('tr');
+
+ row.nTr = nTr;
+ row.anCells = cells;
+
+ /* Use a private property on the node to allow reserve mapping from the node
+ * to the aoData array for fast look up
+ */
+ nTr._DT_RowIndex = iRow;
+
+ /* Special parameters can be given by the data source to be used on the row */
+ _fnRowAttributes( oSettings, row );
+
+ /* Process each column */
+ for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
+ {
+ oCol = oSettings.aoColumns[i];
+
+ nTd = nTrIn ? anTds[i] : document.createElement( oCol.sCellType );
+ nTd._DT_CellIndex = {
+ row: iRow,
+ column: i
+ };
+
+ cells.push( nTd );
+
+ // Need to create the HTML if new, or if a rendering function is defined
+ if ( (!nTrIn || oCol.mRender || oCol.mData !== i) &&
+ (!$.isPlainObject(oCol.mData) || oCol.mData._ !== i+'.display')
+ ) {
+ nTd.innerHTML = _fnGetCellData( oSettings, iRow, i, 'display' );
+ }
+
+ /* Add user defined class */
+ if ( oCol.sClass )
+ {
+ nTd.className += ' '+oCol.sClass;
+ }
+
+ // Visibility - add or remove as required
+ if ( oCol.bVisible && ! nTrIn )
+ {
+ nTr.appendChild( nTd );
+ }
+ else if ( ! oCol.bVisible && nTrIn )
+ {
+ nTd.parentNode.removeChild( nTd );
+ }
+
+ if ( oCol.fnCreatedCell )
+ {
+ oCol.fnCreatedCell.call( oSettings.oInstance,
+ nTd, _fnGetCellData( oSettings, iRow, i ), rowData, iRow, i
+ );
+ }
+ }
+
+ _fnCallbackFire( oSettings, 'aoRowCreatedCallback', null, [nTr, rowData, iRow] );
+ }
+
+ // Remove once webkit bug 131819 and Chromium bug 365619 have been resolved
+ // and deployed
+ row.nTr.setAttribute( 'role', 'row' );
+ }
+
+
+ /**
+ * Add attributes to a row based on the special `DT_*` parameters in a data
+ * source object.
+ * @param {object} settings DataTables settings object
+ * @param {object} DataTables row object for the row to be modified
+ * @memberof DataTable#oApi
+ */
+ function _fnRowAttributes( settings, row )
+ {
+ var tr = row.nTr;
+ var data = row._aData;
+
+ if ( tr ) {
+ var id = settings.rowIdFn( data );
+
+ if ( id ) {
+ tr.id = id;
+ }
+
+ if ( data.DT_RowClass ) {
+ // Remove any classes added by DT_RowClass before
+ var a = data.DT_RowClass.split(' ');
+ row.__rowc = row.__rowc ?
+ _unique( row.__rowc.concat( a ) ) :
+ a;
+
+ $(tr)
+ .removeClass( row.__rowc.join(' ') )
+ .addClass( data.DT_RowClass );
+ }
+
+ if ( data.DT_RowAttr ) {
+ $(tr).attr( data.DT_RowAttr );
+ }
+
+ if ( data.DT_RowData ) {
+ $(tr).data( data.DT_RowData );
+ }
+ }
+ }
+
+
+ /**
+ * Create the HTML header for the table
+ * @param {object} oSettings dataTables settings object
+ * @memberof DataTable#oApi
+ */
+ function _fnBuildHead( oSettings )
+ {
+ var i, ien, cell, row, column;
+ var thead = oSettings.nTHead;
+ var tfoot = oSettings.nTFoot;
+ var createHeader = $('th, td', thead).length === 0;
+ var classes = oSettings.oClasses;
+ var columns = oSettings.aoColumns;
+
+ if ( createHeader ) {
+ row = $('<tr/>').appendTo( thead );
+ }
+
+ for ( i=0, ien=columns.length ; i<ien ; i++ ) {
+ column = columns[i];
+ cell = $( column.nTh ).addClass( column.sClass );
+
+ if ( createHeader ) {
+ cell.appendTo( row );
+ }
+
+ // 1.11 move into sorting
+ if ( oSettings.oFeatures.bSort ) {
+ cell.addClass( column.sSortingClass );
+
+ if ( column.bSortable !== false ) {
+ cell
+ .attr( 'tabindex', oSettings.iTabIndex )
+ .attr( 'aria-controls', oSettings.sTableId );
+
+ _fnSortAttachListener( oSettings, column.nTh, i );
+ }
+ }
+
+ if ( column.sTitle != cell[0].innerHTML ) {
+ cell.html( column.sTitle );
+ }
+
+ _fnRenderer( oSettings, 'header' )(
+ oSettings, cell, column, classes
+ );
+ }
+
+ if ( createHeader ) {
+ _fnDetectHeader( oSettings.aoHeader, thead );
+ }
+
+ /* ARIA role for the rows */
+ $(thead).find('>tr').attr('role', 'row');
+
+ /* Deal with the footer - add classes if required */
+ $(thead).find('>tr>th, >tr>td').addClass( classes.sHeaderTH );
+ $(tfoot).find('>tr>th, >tr>td').addClass( classes.sFooterTH );
+
+ // Cache the footer cells. Note that we only take the cells from the first
+ // row in the footer. If there is more than one row the user wants to
+ // interact with, they need to use the table().foot() method. Note also this
+ // allows cells to be used for multiple columns using colspan
+ if ( tfoot !== null ) {
+ var cells = oSettings.aoFooter[0];
+
+ for ( i=0, ien=cells.length ; i<ien ; i++ ) {
+ column = columns[i];
+ column.nTf = cells[i].cell;
+
+ if ( column.sClass ) {
+ $(column.nTf).addClass( column.sClass );
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Draw the header (or footer) element based on the column visibility states. The
+ * methodology here is to use the layout array from _fnDetectHeader, modified for
+ * the instantaneous column visibility, to construct the new layout. The grid is
+ * traversed over cell at a time in a rows x columns grid fashion, although each
+ * cell insert can cover multiple elements in the grid - which is tracks using the
+ * aApplied array. Cell inserts in the grid will only occur where there isn't
+ * already a cell in that position.
+ * @param {object} oSettings dataTables settings object
+ * @param array {objects} aoSource Layout array from _fnDetectHeader
+ * @param {boolean} [bIncludeHidden=false] If true then include the hidden columns in the calc,
+ * @memberof DataTable#oApi
+ */
+ function _fnDrawHead( oSettings, aoSource, bIncludeHidden )
+ {
+ var i, iLen, j, jLen, k, kLen, n, nLocalTr;
+ var aoLocal = [];
+ var aApplied = [];
+ var iColumns = oSettings.aoColumns.length;
+ var iRowspan, iColspan;
+
+ if ( ! aoSource )
+ {
+ return;
+ }
+
+ if ( bIncludeHidden === undefined )
+ {
+ bIncludeHidden = false;
+ }
+
+ /* Make a copy of the master layout array, but without the visible columns in it */
+ for ( i=0, iLen=aoSource.length ; i<iLen ; i++ )
+ {
+ aoLocal[i] = aoSource[i].slice();
+ aoLocal[i].nTr = aoSource[i].nTr;
+
+ /* Remove any columns which are currently hidden */
+ for ( j=iColumns-1 ; j>=0 ; j-- )
+ {
+ if ( !oSettings.aoColumns[j].bVisible && !bIncludeHidden )
+ {
+ aoLocal[i].splice( j, 1 );
+ }
+ }
+
+ /* Prep the applied array - it needs an element for each row */
+ aApplied.push( [] );
+ }
+
+ for ( i=0, iLen=aoLocal.length ; i<iLen ; i++ )
+ {
+ nLocalTr = aoLocal[i].nTr;
+
+ /* All cells are going to be replaced, so empty out the row */
+ if ( nLocalTr )
+ {
+ while( (n = nLocalTr.firstChild) )
+ {
+ nLocalTr.removeChild( n );
+ }
+ }
+
+ for ( j=0, jLen=aoLocal[i].length ; j<jLen ; j++ )
+ {
+ iRowspan = 1;
+ iColspan = 1;
+
+ /* Check to see if there is already a cell (row/colspan) covering our target
+ * insert point. If there is, then there is nothing to do.
+ */
+ if ( aApplied[i][j] === undefined )
+ {
+ nLocalTr.appendChild( aoLocal[i][j].cell );
+ aApplied[i][j] = 1;
+
+ /* Expand the cell to cover as many rows as needed */
+ while ( aoLocal[i+iRowspan] !== undefined &&
+ aoLocal[i][j].cell == aoLocal[i+iRowspan][j].cell )
+ {
+ aApplied[i+iRowspan][j] = 1;
+ iRowspan++;
+ }
+
+ /* Expand the cell to cover as many columns as needed */
+ while ( aoLocal[i][j+iColspan] !== undefined &&
+ aoLocal[i][j].cell == aoLocal[i][j+iColspan].cell )
+ {
+ /* Must update the applied array over the rows for the columns */
+ for ( k=0 ; k<iRowspan ; k++ )
+ {
+ aApplied[i+k][j+iColspan] = 1;
+ }
+ iColspan++;
+ }
+
+ /* Do the actual expansion in the DOM */
+ $(aoLocal[i][j].cell)
+ .attr('rowspan', iRowspan)
+ .attr('colspan', iColspan);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Insert the required TR nodes into the table for display
+ * @param {object} oSettings dataTables settings object
+ * @memberof DataTable#oApi
+ */
+ function _fnDraw( oSettings )
+ {
+ /* Provide a pre-callback function which can be used to cancel the draw is false is returned */
+ var aPreDraw = _fnCallbackFire( oSettings, 'aoPreDrawCallback', 'preDraw', [oSettings] );
+ if ( $.inArray( false, aPreDraw ) !== -1 )
+ {
+ _fnProcessingDisplay( oSettings, false );
+ return;
+ }
+
+ var i, iLen, n;
+ var anRows = [];
+ var iRowCount = 0;
+ var asStripeClasses = oSettings.asStripeClasses;
+ var iStripes = asStripeClasses.length;
+ var iOpenRows = oSettings.aoOpenRows.length;
+ var oLang = oSettings.oLanguage;
+ var iInitDisplayStart = oSettings.iInitDisplayStart;
+ var bServerSide = _fnDataSource( oSettings ) == 'ssp';
+ var aiDisplay = oSettings.aiDisplay;
+
+ oSettings.bDrawing = true;
+
+ /* Check and see if we have an initial draw position from state saving */
+ if ( iInitDisplayStart !== undefined && iInitDisplayStart !== -1 )
+ {
+ oSettings._iDisplayStart = bServerSide ?
+ iInitDisplayStart :
+ iInitDisplayStart >= oSettings.fnRecordsDisplay() ?
+ 0 :
+ iInitDisplayStart;
+
+ oSettings.iInitDisplayStart = -1;
+ }
+
+ var iDisplayStart = oSettings._iDisplayStart;
+ var iDisplayEnd = oSettings.fnDisplayEnd();
+
+ /* Server-side processing draw intercept */
+ if ( oSettings.bDeferLoading )
+ {
+ oSettings.bDeferLoading = false;
+ oSettings.iDraw++;
+ _fnProcessingDisplay( oSettings, false );
+ }
+ else if ( !bServerSide )
+ {
+ oSettings.iDraw++;
+ }
+ else if ( !oSettings.bDestroying && !_fnAjaxUpdate( oSettings ) )
+ {
+ return;
+ }
+
+ if ( aiDisplay.length !== 0 )
+ {
+ var iStart = bServerSide ? 0 : iDisplayStart;
+ var iEnd = bServerSide ? oSettings.aoData.length : iDisplayEnd;
+
+ for ( var j=iStart ; j<iEnd ; j++ )
+ {
+ var iDataIndex = aiDisplay[j];
+ var aoData = oSettings.aoData[ iDataIndex ];
+ if ( aoData.nTr === null )
+ {
+ _fnCreateTr( oSettings, iDataIndex );
+ }
+
+ var nRow = aoData.nTr;
+
+ /* Remove the old striping classes and then add the new one */
+ if ( iStripes !== 0 )
+ {
+ var sStripe = asStripeClasses[ iRowCount % iStripes ];
+ if ( aoData._sRowStripe != sStripe )
+ {
+ $(nRow).removeClass( aoData._sRowStripe ).addClass( sStripe );
+ aoData._sRowStripe = sStripe;
+ }
+ }
+
+ // Row callback functions - might want to manipulate the row
+ // iRowCount and j are not currently documented. Are they at all
+ // useful?
+ _fnCallbackFire( oSettings, 'aoRowCallback', null,
+ [nRow, aoData._aData, iRowCount, j] );
+
+ anRows.push( nRow );
+ iRowCount++;
+ }
+ }
+ else
+ {
+ /* Table is empty - create a row with an empty message in it */
+ var sZero = oLang.sZeroRecords;
+ if ( oSettings.iDraw == 1 && _fnDataSource( oSettings ) == 'ajax' )
+ {
+ sZero = oLang.sLoadingRecords;
+ }
+ else if ( oLang.sEmptyTable && oSettings.fnRecordsTotal() === 0 )
+ {
+ sZero = oLang.sEmptyTable;
+ }
+
+ anRows[ 0 ] = $( '<tr/>', { 'class': iStripes ? asStripeClasses[0] : '' } )
+ .append( $('<td />', {
+ 'valign': 'top',
+ 'colSpan': _fnVisbleColumns( oSettings ),
+ 'class': oSettings.oClasses.sRowEmpty
+ } ).html( sZero ) )[0];
+ }
+
+ /* Header and footer callbacks */
+ _fnCallbackFire( oSettings, 'aoHeaderCallback', 'header', [ $(oSettings.nTHead).children('tr')[0],
+ _fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] );
+
+ _fnCallbackFire( oSettings, 'aoFooterCallback', 'footer', [ $(oSettings.nTFoot).children('tr')[0],
+ _fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] );
+
+ var body = $(oSettings.nTBody);
+
+ body.children().detach();
+ body.append( $(anRows) );
+
+ /* Call all required callback functions for the end of a draw */
+ _fnCallbackFire( oSettings, 'aoDrawCallback', 'draw', [oSettings] );
+
+ /* Draw is complete, sorting and filtering must be as well */
+ oSettings.bSorted = false;
+ oSettings.bFiltered = false;
+ oSettings.bDrawing = false;
+ }
+
+
+ /**
+ * Redraw the table - taking account of the various features which are enabled
+ * @param {object} oSettings dataTables settings object
+ * @param {boolean} [holdPosition] Keep the current paging position. By default
+ * the paging is reset to the first page
+ * @memberof DataTable#oApi
+ */
+ function _fnReDraw( settings, holdPosition )
+ {
+ var
+ features = settings.oFeatures,
+ sort = features.bSort,
+ filter = features.bFilter;
+
+ if ( sort ) {
+ _fnSort( settings );
+ }
+
+ if ( filter ) {
+ _fnFilterComplete( settings, settings.oPreviousSearch );
+ }
+ else {
+ // No filtering, so we want to just use the display master
+ settings.aiDisplay = settings.aiDisplayMaster.slice();
+ }
+
+ if ( holdPosition !== true ) {
+ settings._iDisplayStart = 0;
+ }
+
+ // Let any modules know about the draw hold position state (used by
+ // scrolling internally)
+ settings._drawHold = holdPosition;
+
+ _fnDraw( settings );
+
+ settings._drawHold = false;
+ }
+
+
+ /**
+ * Add the options to the page HTML for the table
+ * @param {object} oSettings dataTables settings object
+ * @memberof DataTable#oApi
+ */
+ function _fnAddOptionsHtml ( oSettings )
+ {
+ var classes = oSettings.oClasses;
+ var table = $(oSettings.nTable);
+ var holding = $('<div/>').insertBefore( table ); // Holding element for speed
+ var features = oSettings.oFeatures;
+
+ // All DataTables are wrapped in a div
+ var insert = $('<div/>', {
+ id: oSettings.sTableId+'_wrapper',
+ 'class': classes.sWrapper + (oSettings.nTFoot ? '' : ' '+classes.sNoFooter)
+ } );
+
+ oSettings.nHolding = holding[0];
+ oSettings.nTableWrapper = insert[0];
+ oSettings.nTableReinsertBefore = oSettings.nTable.nextSibling;
+
+ /* Loop over the user set positioning and place the elements as needed */
+ var aDom = oSettings.sDom.split('');
+ var featureNode, cOption, nNewNode, cNext, sAttr, j;
+ for ( var i=0 ; i<aDom.length ; i++ )
+ {
+ featureNode = null;
+ cOption = aDom[i];
+
+ if ( cOption == '<' )
+ {
+ /* New container div */
+ nNewNode = $('<div/>')[0];
+
+ /* Check to see if we should append an id and/or a class name to the container */
+ cNext = aDom[i+1];
+ if ( cNext == "'" || cNext == '"' )
+ {
+ sAttr = "";
+ j = 2;
+ while ( aDom[i+j] != cNext )
+ {
+ sAttr += aDom[i+j];
+ j++;
+ }
+
+ /* Replace jQuery UI constants @todo depreciated */
+ if ( sAttr == "H" )
+ {
+ sAttr = classes.sJUIHeader;
+ }
+ else if ( sAttr == "F" )
+ {
+ sAttr = classes.sJUIFooter;
+ }
+
+ /* The attribute can be in the format of "#id.class", "#id" or "class" This logic
+ * breaks the string into parts and applies them as needed
+ */
+ if ( sAttr.indexOf('.') != -1 )
+ {
+ var aSplit = sAttr.split('.');
+ nNewNode.id = aSplit[0].substr(1, aSplit[0].length-1);
+ nNewNode.className = aSplit[1];
+ }
+ else if ( sAttr.charAt(0) == "#" )
+ {
+ nNewNode.id = sAttr.substr(1, sAttr.length-1);
+ }
+ else
+ {
+ nNewNode.className = sAttr;
+ }
+
+ i += j; /* Move along the position array */
+ }
+
+ insert.append( nNewNode );
+ insert = $(nNewNode);
+ }
+ else if ( cOption == '>' )
+ {
+ /* End container div */
+ insert = insert.parent();
+ }
+ // @todo Move options into their own plugins?
+ else if ( cOption == 'l' && features.bPaginate && features.bLengthChange )
+ {
+ /* Length */
+ featureNode = _fnFeatureHtmlLength( oSettings );
+ }
+ else if ( cOption == 'f' && features.bFilter )
+ {
+ /* Filter */
+ featureNode = _fnFeatureHtmlFilter( oSettings );
+ }
+ else if ( cOption == 'r' && features.bProcessing )
+ {
+ /* pRocessing */
+ featureNode = _fnFeatureHtmlProcessing( oSettings );
+ }
+ else if ( cOption == 't' )
+ {
+ /* Table */
+ featureNode = _fnFeatureHtmlTable( oSettings );
+ }
+ else if ( cOption == 'i' && features.bInfo )
+ {
+ /* Info */
+ featureNode = _fnFeatureHtmlInfo( oSettings );
+ }
+ else if ( cOption == 'p' && features.bPaginate )
+ {
+ /* Pagination */
+ featureNode = _fnFeatureHtmlPaginate( oSettings );
+ }
+ else if ( DataTable.ext.feature.length !== 0 )
+ {
+ /* Plug-in features */
+ var aoFeatures = DataTable.ext.feature;
+ for ( var k=0, kLen=aoFeatures.length ; k<kLen ; k++ )
+ {
+ if ( cOption == aoFeatures[k].cFeature )
+ {
+ featureNode = aoFeatures[k].fnInit( oSettings );
+ break;
+ }
+ }
+ }
+
+ /* Add to the 2D features array */
+ if ( featureNode )
+ {
+ var aanFeatures = oSettings.aanFeatures;
+
+ if ( ! aanFeatures[cOption] )
+ {
+ aanFeatures[cOption] = [];
+ }
+
+ aanFeatures[cOption].push( featureNode );
+ insert.append( featureNode );
+ }
+ }
+
+ /* Built our DOM structure - replace the holding div with what we want */
+ holding.replaceWith( insert );
+ oSettings.nHolding = null;
+ }
+
+
+ /**
+ * Use the DOM source to create up an array of header cells. The idea here is to
+ * create a layout grid (array) of rows x columns, which contains a reference
+ * to the cell that that point in the grid (regardless of col/rowspan), such that
+ * any column / row could be removed and the new grid constructed
+ * @param array {object} aLayout Array to store the calculated layout in
+ * @param {node} nThead The header/footer element for the table
+ * @memberof DataTable#oApi
+ */
+ function _fnDetectHeader ( aLayout, nThead )
+ {
+ var nTrs = $(nThead).children('tr');
+ var nTr, nCell;
+ var i, k, l, iLen, jLen, iColShifted, iColumn, iColspan, iRowspan;
+ var bUnique;
+ var fnShiftCol = function ( a, i, j ) {
+ var k = a[i];
+ while ( k[j] ) {
+ j++;
+ }
+ return j;
+ };
+
+ aLayout.splice( 0, aLayout.length );
+
+ /* We know how many rows there are in the layout - so prep it */
+ for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
+ {
+ aLayout.push( [] );
+ }
+
+ /* Calculate a layout array */
+ for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
+ {
+ nTr = nTrs[i];
+ iColumn = 0;
+
+ /* For every cell in the row... */
+ nCell = nTr.firstChild;
+ while ( nCell ) {
+ if ( nCell.nodeName.toUpperCase() == "TD" ||
+ nCell.nodeName.toUpperCase() == "TH" )
+ {
+ /* Get the col and rowspan attributes from the DOM and sanitise them */
+ iColspan = nCell.getAttribute('colspan') * 1;
+ iRowspan = nCell.getAttribute('rowspan') * 1;
+ iColspan = (!iColspan || iColspan===0 || iColspan===1) ? 1 : iColspan;
+ iRowspan = (!iRowspan || iRowspan===0 || iRowspan===1) ? 1 : iRowspan;
+
+ /* There might be colspan cells already in this row, so shift our target
+ * accordingly
+ */
+ iColShifted = fnShiftCol( aLayout, i, iColumn );
+
+ /* Cache calculation for unique columns */
+ bUnique = iColspan === 1 ? true : false;
+
+ /* If there is col / rowspan, copy the information into the layout grid */
+ for ( l=0 ; l<iColspan ; l++ )
+ {
+ for ( k=0 ; k<iRowspan ; k++ )
+ {
+ aLayout[i+k][iColShifted+l] = {
+ "cell": nCell,
+ "unique": bUnique
+ };
+ aLayout[i+k].nTr = nTr;
+ }
+ }
+ }
+ nCell = nCell.nextSibling;
+ }
+ }
+ }
+
+
+ /**
+ * Get an array of unique th elements, one for each column
+ * @param {object} oSettings dataTables settings object
+ * @param {node} nHeader automatically detect the layout from this node - optional
+ * @param {array} aLayout thead/tfoot layout from _fnDetectHeader - optional
+ * @returns array {node} aReturn list of unique th's
+ * @memberof DataTable#oApi
+ */
+ function _fnGetUniqueThs ( oSettings, nHeader, aLayout )
+ {
+ var aReturn = [];
+ if ( !aLayout )
+ {
+ aLayout = oSettings.aoHeader;
+ if ( nHeader )
+ {
+ aLayout = [];
+ _fnDetectHeader( aLayout, nHeader );
+ }
+ }
+
+ for ( var i=0, iLen=aLayout.length ; i<iLen ; i++ )
+ {
+ for ( var j=0, jLen=aLayout[i].length ; j<jLen ; j++ )
+ {
+ if ( aLayout[i][j].unique &&
+ (!aReturn[j] || !oSettings.bSortCellsTop) )
+ {
+ aReturn[j] = aLayout[i][j].cell;
+ }
+ }
+ }
+
+ return aReturn;
+ }
+
+ /**
+ * Create an Ajax call based on the table's settings, taking into account that
+ * parameters can have multiple forms, and backwards compatibility.
+ *
+ * @param {object} oSettings dataTables settings object
+ * @param {array} data Data to send to the server, required by
+ * DataTables - may be augmented by developer callbacks
+ * @param {function} fn Callback function to run when data is obtained
+ */
+ function _fnBuildAjax( oSettings, data, fn )
+ {
+ // Compatibility with 1.9-, allow fnServerData and event to manipulate
+ _fnCallbackFire( oSettings, 'aoServerParams', 'serverParams', [data] );
+
+ // Convert to object based for 1.10+ if using the old array scheme which can
+ // come from server-side processing or serverParams
+ if ( data && $.isArray(data) ) {
+ var tmp = {};
+ var rbracket = /(.*?)\[\]$/;
+
+ $.each( data, function (key, val) {
+ var match = val.name.match(rbracket);
+
+ if ( match ) {
+ // Support for arrays
+ var name = match[0];
+
+ if ( ! tmp[ name ] ) {
+ tmp[ name ] = [];
+ }
+ tmp[ name ].push( val.value );
+ }
+ else {
+ tmp[val.name] = val.value;
+ }
+ } );
+ data = tmp;
+ }
+
+ var ajaxData;
+ var ajax = oSettings.ajax;
+ var instance = oSettings.oInstance;
+ var callback = function ( json ) {
+ _fnCallbackFire( oSettings, null, 'xhr', [oSettings, json, oSettings.jqXHR] );
+ fn( json );
+ };
+
+ if ( $.isPlainObject( ajax ) && ajax.data )
+ {
+ ajaxData = ajax.data;
+
+ var newData = $.isFunction( ajaxData ) ?
+ ajaxData( data, oSettings ) : // fn can manipulate data or return
+ ajaxData; // an object object or array to merge
+
+ // If the function returned something, use that alone
+ data = $.isFunction( ajaxData ) && newData ?
+ newData :
+ $.extend( true, data, newData );
+
+ // Remove the data property as we've resolved it already and don't want
+ // jQuery to do it again (it is restored at the end of the function)
+ delete ajax.data;
+ }
+
+ var baseAjax = {
+ "data": data,
+ "success": function (json) {
+ var error = json.error || json.sError;
+ if ( error ) {
+ _fnLog( oSettings, 0, error );
+ }
+
+ oSettings.json = json;
+ callback( json );
+ },
+ "dataType": "json",
+ "cache": false,
+ "type": oSettings.sServerMethod,
+ "error": function (xhr, error, thrown) {
+ var ret = _fnCallbackFire( oSettings, null, 'xhr', [oSettings, null, oSettings.jqXHR] );
+
+ if ( $.inArray( true, ret ) === -1 ) {
+ if ( error == "parsererror" ) {
+ _fnLog( oSettings, 0, 'Invalid JSON response', 1 );
+ }
+ else if ( xhr.readyState === 4 ) {
+ _fnLog( oSettings, 0, 'Ajax error', 7 );
+ }
+ }
+
+ _fnProcessingDisplay( oSettings, false );
+ }
+ };
+
+ // Store the data submitted for the API
+ oSettings.oAjaxData = data;
+
+ // Allow plug-ins and external processes to modify the data
+ _fnCallbackFire( oSettings, null, 'preXhr', [oSettings, data] );
+
+ if ( oSettings.fnServerData )
+ {
+ // DataTables 1.9- compatibility
+ oSettings.fnServerData.call( instance,
+ oSettings.sAjaxSource,
+ $.map( data, function (val, key) { // Need to convert back to 1.9 trad format
+ return { name: key, value: val };
+ } ),
+ callback,
+ oSettings
+ );
+ }
+ else if ( oSettings.sAjaxSource || typeof ajax === 'string' )
+ {
+ // DataTables 1.9- compatibility
+ oSettings.jqXHR = $.ajax( $.extend( baseAjax, {
+ url: ajax || oSettings.sAjaxSource
+ } ) );
+ }
+ else if ( $.isFunction( ajax ) )
+ {
+ // Is a function - let the caller define what needs to be done
+ oSettings.jqXHR = ajax.call( instance, data, callback, oSettings );
+ }
+ else
+ {
+ // Object to extend the base settings
+ oSettings.jqXHR = $.ajax( $.extend( baseAjax, ajax ) );
+
+ // Restore for next time around
+ ajax.data = ajaxData;
+ }
+ }
+
+
+ /**
+ * Update the table using an Ajax call
+ * @param {object} settings dataTables settings object
+ * @returns {boolean} Block the table drawing or not
+ * @memberof DataTable#oApi
+ */
+ function _fnAjaxUpdate( settings )
+ {
+ if ( settings.bAjaxDataGet ) {
+ settings.iDraw++;
+ _fnProcessingDisplay( settings, true );
+
+ _fnBuildAjax(
+ settings,
+ _fnAjaxParameters( settings ),
+ function(json) {
+ _fnAjaxUpdateDraw( settings, json );
+ }
+ );
+
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * Build up the parameters in an object needed for a server-side processing
+ * request. Note that this is basically done twice, is different ways - a modern
+ * method which is used by default in DataTables 1.10 which uses objects and
+ * arrays, or the 1.9- method with is name / value pairs. 1.9 method is used if
+ * the sAjaxSource option is used in the initialisation, or the legacyAjax
+ * option is set.
+ * @param {object} oSettings dataTables settings object
+ * @returns {bool} block the table drawing or not
+ * @memberof DataTable#oApi
+ */
+ function _fnAjaxParameters( settings )
+ {
+ var
+ columns = settings.aoColumns,
+ columnCount = columns.length,
+ features = settings.oFeatures,
+ preSearch = settings.oPreviousSearch,
+ preColSearch = settings.aoPreSearchCols,
+ i, data = [], dataProp, column, columnSearch,
+ sort = _fnSortFlatten( settings ),
+ displayStart = settings._iDisplayStart,
+ displayLength = features.bPaginate !== false ?
+ settings._iDisplayLength :
+ -1;
+
+ var param = function ( name, value ) {
+ data.push( { 'name': name, 'value': value } );
+ };
+
+ // DataTables 1.9- compatible method
+ param( 'sEcho', settings.iDraw );
+ param( 'iColumns', columnCount );
+ param( 'sColumns', _pluck( columns, 'sName' ).join(',') );
+ param( 'iDisplayStart', displayStart );
+ param( 'iDisplayLength', displayLength );
+
+ // DataTables 1.10+ method
+ var d = {
+ draw: settings.iDraw,
+ columns: [],
+ order: [],
+ start: displayStart,
+ length: displayLength,
+ search: {
+ value: preSearch.sSearch,
+ regex: preSearch.bRegex
+ }
+ };
+
+ for ( i=0 ; i<columnCount ; i++ ) {
+ column = columns[i];
+ columnSearch = preColSearch[i];
+ dataProp = typeof column.mData=="function" ? 'function' : column.mData ;
+
+ d.columns.push( {
+ data: dataProp,
+ name: column.sName,
+ searchable: column.bSearchable,
+ orderable: column.bSortable,
+ search: {
+ value: columnSearch.sSearch,
+ regex: columnSearch.bRegex
+ }
+ } );
+
+ param( "mDataProp_"+i, dataProp );
+
+ if ( features.bFilter ) {
+ param( 'sSearch_'+i, columnSearch.sSearch );
+ param( 'bRegex_'+i, columnSearch.bRegex );
+ param( 'bSearchable_'+i, column.bSearchable );
+ }
+
+ if ( features.bSort ) {
+ param( 'bSortable_'+i, column.bSortable );
+ }
+ }
+
+ if ( features.bFilter ) {
+ param( 'sSearch', preSearch.sSearch );
+ param( 'bRegex', preSearch.bRegex );
+ }
+
+ if ( features.bSort ) {
+ $.each( sort, function ( i, val ) {
+ d.order.push( { column: val.col, dir: val.dir } );
+
+ param( 'iSortCol_'+i, val.col );
+ param( 'sSortDir_'+i, val.dir );
+ } );
+
+ param( 'iSortingCols', sort.length );
+ }
+
+ // If the legacy.ajax parameter is null, then we automatically decide which
+ // form to use, based on sAjaxSource
+ var legacy = DataTable.ext.legacy.ajax;
+ if ( legacy === null ) {
+ return settings.sAjaxSource ? data : d;
+ }
+
+ // Otherwise, if legacy has been specified then we use that to decide on the
+ // form
+ return legacy ? data : d;
+ }
+
+
+ /**
+ * Data the data from the server (nuking the old) and redraw the table
+ * @param {object} oSettings dataTables settings object
+ * @param {object} json json data return from the server.
+ * @param {string} json.sEcho Tracking flag for DataTables to match requests
+ * @param {int} json.iTotalRecords Number of records in the data set, not accounting for filtering
+ * @param {int} json.iTotalDisplayRecords Number of records in the data set, accounting for filtering
+ * @param {array} json.aaData The data to display on this page
+ * @param {string} [json.sColumns] Column ordering (sName, comma separated)
+ * @memberof DataTable#oApi
+ */
+ function _fnAjaxUpdateDraw ( settings, json )
+ {
+ // v1.10 uses camelCase variables, while 1.9 uses Hungarian notation.
+ // Support both
+ var compat = function ( old, modern ) {
+ return json[old] !== undefined ? json[old] : json[modern];
+ };
+
+ var data = _fnAjaxDataSrc( settings, json );
+ var draw = compat( 'sEcho', 'draw' );
+ var recordsTotal = compat( 'iTotalRecords', 'recordsTotal' );
+ var recordsFiltered = compat( 'iTotalDisplayRecords', 'recordsFiltered' );
+
+ if ( draw ) {
+ // Protect against out of sequence returns
+ if ( draw*1 < settings.iDraw ) {
+ return;
+ }
+ settings.iDraw = draw * 1;
+ }
+
+ _fnClearTable( settings );
+ settings._iRecordsTotal = parseInt(recordsTotal, 10);
+ settings._iRecordsDisplay = parseInt(recordsFiltered, 10);
+
+ for ( var i=0, ien=data.length ; i<ien ; i++ ) {
+ _fnAddData( settings, data[i] );
+ }
+ settings.aiDisplay = settings.aiDisplayMaster.slice();
+
+ settings.bAjaxDataGet = false;
+ _fnDraw( settings );
+
+ if ( ! settings._bInitComplete ) {
+ _fnInitComplete( settings, json );
+ }
+
+ settings.bAjaxDataGet = true;
+ _fnProcessingDisplay( settings, false );
+ }
+
+
+ /**
+ * Get the data from the JSON data source to use for drawing a table. Using
+ * `_fnGetObjectDataFn` allows the data to be sourced from a property of the
+ * source object, or from a processing function.
+ * @param {object} oSettings dataTables settings object
+ * @param {object} json Data source object / array from the server
+ * @return {array} Array of data to use
+ */
+ function _fnAjaxDataSrc ( oSettings, json )
+ {
+ var dataSrc = $.isPlainObject( oSettings.ajax ) && oSettings.ajax.dataSrc !== undefined ?
+ oSettings.ajax.dataSrc :
+ oSettings.sAjaxDataProp; // Compatibility with 1.9-.
+
+ // Compatibility with 1.9-. In order to read from aaData, check if the
+ // default has been changed, if not, check for aaData
+ if ( dataSrc === 'data' ) {
+ return json.aaData || json[dataSrc];
+ }
+
+ return dataSrc !== "" ?
+ _fnGetObjectDataFn( dataSrc )( json ) :
+ json;
+ }
+
+ /**
+ * Generate the node required for filtering text
+ * @returns {node} Filter control element
+ * @param {object} oSettings dataTables settings object
+ * @memberof DataTable#oApi
+ */
+ function _fnFeatureHtmlFilter ( settings )
+ {
+ var classes = settings.oClasses;
+ var tableId = settings.sTableId;
+ var language = settings.oLanguage;
+ var previousSearch = settings.oPreviousSearch;
+ var features = settings.aanFeatures;
+ var input = '<input type="search" class="'+classes.sFilterInput+'"/>';
+
+ var str = language.sSearch;
+ str = str.match(/_INPUT_/) ?
+ str.replace('_INPUT_', input) :
+ str+input;
+
+ var filter = $('<div/>', {
+ 'id': ! features.f ? tableId+'_filter' : null,
+ 'class': classes.sFilter
+ } )
+ .append( $('<label/>' ).append( str ) );
+
+ var searchFn = function() {
+ /* Update all other filter input elements for the new display */
+ var n = features.f;
+ var val = !this.value ? "" : this.value; // mental IE8 fix :-(
+
+ /* Now do the filter */
+ if ( val != previousSearch.sSearch ) {
+ _fnFilterComplete( settings, {
+ "sSearch": val,
+ "bRegex": previousSearch.bRegex,
+ "bSmart": previousSearch.bSmart ,
+ "bCaseInsensitive": previousSearch.bCaseInsensitive
+ } );
+
+ // Need to redraw, without resorting
+ settings._iDisplayStart = 0;
+ _fnDraw( settings );
+ }
+ };
+
+ var searchDelay = settings.searchDelay !== null ?
+ settings.searchDelay :
+ _fnDataSource( settings ) === 'ssp' ?
+ 400 :
+ 0;
+
+ var jqFilter = $('input', filter)
+ .val( previousSearch.sSearch )
+ .attr( 'placeholder', language.sSearchPlaceholder )
+ .on(
+ 'keyup.DT search.DT input.DT paste.DT cut.DT',
+ searchDelay ?
+ _fnThrottle( searchFn, searchDelay ) :
+ searchFn
+ )
+ .on( 'keypress.DT', function(e) {
+ /* Prevent form submission */
+ if ( e.keyCode == 13 ) {
+ return false;
+ }
+ } )
+ .attr('aria-controls', tableId);
+
+ // Update the input elements whenever the table is filtered
+ $(settings.nTable).on( 'search.dt.DT', function ( ev, s ) {
+ if ( settings === s ) {
+ // IE9 throws an 'unknown error' if document.activeElement is used
+ // inside an iframe or frame...
+ try {
+ if ( jqFilter[0] !== document.activeElement ) {
+ jqFilter.val( previousSearch.sSearch );
+ }
+ }
+ catch ( e ) {}
+ }
+ } );
+
+ return filter[0];
+ }
+
+
+ /**
+ * Filter the table using both the global filter and column based filtering
+ * @param {object} oSettings dataTables settings object
+ * @param {object} oSearch search information
+ * @param {int} [iForce] force a research of the master array (1) or not (undefined or 0)
+ * @memberof DataTable#oApi
+ */
+ function _fnFilterComplete ( oSettings, oInput, iForce )
+ {
+ var oPrevSearch = oSettings.oPreviousSearch;
+ var aoPrevSearch = oSettings.aoPreSearchCols;
+ var fnSaveFilter = function ( oFilter ) {
+ /* Save the filtering values */
+ oPrevSearch.sSearch = oFilter.sSearch;
+ oPrevSearch.bRegex = oFilter.bRegex;
+ oPrevSearch.bSmart = oFilter.bSmart;
+ oPrevSearch.bCaseInsensitive = oFilter.bCaseInsensitive;
+ };
+ var fnRegex = function ( o ) {
+ // Backwards compatibility with the bEscapeRegex option
+ return o.bEscapeRegex !== undefined ? !o.bEscapeRegex : o.bRegex;
+ };
+
+ // Resolve any column types that are unknown due to addition or invalidation
+ // @todo As per sort - can this be moved into an event handler?
+ _fnColumnTypes( oSettings );
+
+ /* In server-side processing all filtering is done by the server, so no point hanging around here */
+ if ( _fnDataSource( oSettings ) != 'ssp' )
+ {
+ /* Global filter */
+ _fnFilter( oSettings, oInput.sSearch, iForce, fnRegex(oInput), oInput.bSmart, oInput.bCaseInsensitive );
+ fnSaveFilter( oInput );
+
+ /* Now do the individual column filter */
+ for ( var i=0 ; i<aoPrevSearch.length ; i++ )
+ {
+ _fnFilterColumn( oSettings, aoPrevSearch[i].sSearch, i, fnRegex(aoPrevSearch[i]),
+ aoPrevSearch[i].bSmart, aoPrevSearch[i].bCaseInsensitive );
+ }
+
+ /* Custom filtering */
+ _fnFilterCustom( oSettings );
+ }
+ else
+ {
+ fnSaveFilter( oInput );
+ }
+
+ /* Tell the draw function we have been filtering */
+ oSettings.bFiltered = true;
+ _fnCallbackFire( oSettings, null, 'search', [oSettings] );
+ }
+
+
+ /**
+ * Apply custom filtering functions
+ * @param {object} oSettings dataTables settings object
+ * @memberof DataTable#oApi
+ */
+ function _fnFilterCustom( settings )
+ {
+ var filters = DataTable.ext.search;
+ var displayRows = settings.aiDisplay;
+ var row, rowIdx;
+
+ for ( var i=0, ien=filters.length ; i<ien ; i++ ) {
+ var rows = [];
+
+ // Loop over each row and see if it should be included
+ for ( var j=0, jen=displayRows.length ; j<jen ; j++ ) {
+ rowIdx = displayRows[ j ];
+ row = settings.aoData[ rowIdx ];
+
+ if ( filters[i]( settings, row._aFilterData, rowIdx, row._aData, j ) ) {
+ rows.push( rowIdx );
+ }
+ }
+
+ // So the array reference doesn't break set the results into the
+ // existing array
+ displayRows.length = 0;
+ $.merge( displayRows, rows );
+ }
+ }
+
+
+ /**
+ * Filter the table on a per-column basis
+ * @param {object} oSettings dataTables settings object
+ * @param {string} sInput string to filter on
+ * @param {int} iColumn column to filter
+ * @param {bool} bRegex treat search string as a regular expression or not
+ * @param {bool} bSmart use smart filtering or not
+ * @param {bool} bCaseInsensitive Do case insenstive matching or not
+ * @memberof DataTable#oApi
+ */
+ function _fnFilterColumn ( settings, searchStr, colIdx, regex, smart, caseInsensitive )
+ {
+ if ( searchStr === '' ) {
+ return;
+ }
+
+ var data;
+ var out = [];
+ var display = settings.aiDisplay;
+ var rpSearch = _fnFilterCreateSearch( searchStr, regex, smart, caseInsensitive );
+
+ for ( var i=0 ; i<display.length ; i++ ) {
+ data = settings.aoData[ display[i] ]._aFilterData[ colIdx ];
+
+ if ( rpSearch.test( data ) ) {
+ out.push( display[i] );
+ }
+ }
+
+ settings.aiDisplay = out;
+ }
+
+
+ /**
+ * Filter the data table based on user input and draw the table
+ * @param {object} settings dataTables settings object
+ * @param {string} input string to filter on
+ * @param {int} force optional - force a research of the master array (1) or not (undefined or 0)
+ * @param {bool} regex treat as a regular expression or not
+ * @param {bool} smart perform smart filtering or not
+ * @param {bool} caseInsensitive Do case insenstive matching or not
+ * @memberof DataTable#oApi
+ */
+ function _fnFilter( settings, input, force, regex, smart, caseInsensitive )
+ {
+ var rpSearch = _fnFilterCreateSearch( input, regex, smart, caseInsensitive );
+ var prevSearch = settings.oPreviousSearch.sSearch;
+ var displayMaster = settings.aiDisplayMaster;
+ var display, invalidated, i;
+ var filtered = [];
+
+ // Need to take account of custom filtering functions - always filter
+ if ( DataTable.ext.search.length !== 0 ) {
+ force = true;
+ }
+
+ // Check if any of the rows were invalidated
+ invalidated = _fnFilterData( settings );
+
+ // If the input is blank - we just want the full data set
+ if ( input.length <= 0 ) {
+ settings.aiDisplay = displayMaster.slice();
+ }
+ else {
+ // New search - start from the master array
+ if ( invalidated ||
+ force ||
+ prevSearch.length > input.length ||
+ input.indexOf(prevSearch) !== 0 ||
+ settings.bSorted // On resort, the display master needs to be
+ // re-filtered since indexes will have changed
+ ) {
+ settings.aiDisplay = displayMaster.slice();
+ }
+
+ // Search the display array
+ display = settings.aiDisplay;
+
+ for ( i=0 ; i<display.length ; i++ ) {
+ if ( rpSearch.test( settings.aoData[ display[i] ]._sFilterRow ) ) {
+ filtered.push( display[i] );
+ }
+ }
+
+ settings.aiDisplay = filtered;
+ }
+ }
+
+
+ /**
+ * Build a regular expression object suitable for searching a table
+ * @param {string} sSearch string to search for
+ * @param {bool} bRegex treat as a regular expression or not
+ * @param {bool} bSmart perform smart filtering or not
+ * @param {bool} bCaseInsensitive Do case insensitive matching or not
+ * @returns {RegExp} constructed object
+ * @memberof DataTable#oApi
+ */
+ function _fnFilterCreateSearch( search, regex, smart, caseInsensitive )
+ {
+ search = regex ?
+ search :
+ _fnEscapeRegex( search );
+
+ if ( smart ) {
+ /* For smart filtering we want to allow the search to work regardless of
+ * word order. We also want double quoted text to be preserved, so word
+ * order is important - a la google. So this is what we want to
+ * generate:
+ *
+ * ^(?=.*?\bone\b)(?=.*?\btwo three\b)(?=.*?\bfour\b).*$
+ */
+ var a = $.map( search.match( /"[^"]+"|[^ ]+/g ) || [''], function ( word ) {
+ if ( word.charAt(0) === '"' ) {
+ var m = word.match( /^"(.*)"$/ );
+ word = m ? m[1] : word;
+ }
+
+ return word.replace('"', '');
+ } );
+
+ search = '^(?=.*?'+a.join( ')(?=.*?' )+').*$';
+ }
+
+ return new RegExp( search, caseInsensitive ? 'i' : '' );
+ }
+
+
+ /**
+ * Escape a string such that it can be used in a regular expression
+ * @param {string} sVal string to escape
+ * @returns {string} escaped string
+ * @memberof DataTable#oApi
+ */
+ var _fnEscapeRegex = DataTable.util.escapeRegex;
+
+ var __filter_div = $('<div>')[0];
+ var __filter_div_textContent = __filter_div.textContent !== undefined;
+
+ // Update the filtering data for each row if needed (by invalidation or first run)
+ function _fnFilterData ( settings )
+ {
+ var columns = settings.aoColumns;
+ var column;
+ var i, j, ien, jen, filterData, cellData, row;
+ var fomatters = DataTable.ext.type.search;
+ var wasInvalidated = false;
+
+ for ( i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
+ row = settings.aoData[i];
+
+ if ( ! row._aFilterData ) {
+ filterData = [];
+
+ for ( j=0, jen=columns.length ; j<jen ; j++ ) {
+ column = columns[j];
+
+ if ( column.bSearchable ) {
+ cellData = _fnGetCellData( settings, i, j, 'filter' );
+
+ if ( fomatters[ column.sType ] ) {
+ cellData = fomatters[ column.sType ]( cellData );
+ }
+
+ // Search in DataTables 1.10 is string based. In 1.11 this
+ // should be altered to also allow strict type checking.
+ if ( cellData === null ) {
+ cellData = '';
+ }
+
+ if ( typeof cellData !== 'string' && cellData.toString ) {
+ cellData = cellData.toString();
+ }
+ }
+ else {
+ cellData = '';
+ }
+
+ // If it looks like there is an HTML entity in the string,
+ // attempt to decode it so sorting works as expected. Note that
+ // we could use a single line of jQuery to do this, but the DOM
+ // method used here is much faster http://jsperf.com/html-decode
+ if ( cellData.indexOf && cellData.indexOf('&') !== -1 ) {
+ __filter_div.innerHTML = cellData;
+ cellData = __filter_div_textContent ?
+ __filter_div.textContent :
+ __filter_div.innerText;
+ }
+
+ if ( cellData.replace ) {
+ cellData = cellData.replace(/[\r\n]/g, '');
+ }
+
+ filterData.push( cellData );
+ }
+
+ row._aFilterData = filterData;
+ row._sFilterRow = filterData.join(' ');
+ wasInvalidated = true;
+ }
+ }
+
+ return wasInvalidated;
+ }
+
+
+ /**
+ * Convert from the internal Hungarian notation to camelCase for external
+ * interaction
+ * @param {object} obj Object to convert
+ * @returns {object} Inverted object
+ * @memberof DataTable#oApi
+ */
+ function _fnSearchToCamel ( obj )
+ {
+ return {
+ search: obj.sSearch,
+ smart: obj.bSmart,
+ regex: obj.bRegex,
+ caseInsensitive: obj.bCaseInsensitive
+ };
+ }
+
+
+
+ /**
+ * Convert from camelCase notation to the internal Hungarian. We could use the
+ * Hungarian convert function here, but this is cleaner
+ * @param {object} obj Object to convert
+ * @returns {object} Inverted object
+ * @memberof DataTable#oApi
+ */
+ function _fnSearchToHung ( obj )
+ {
+ return {
+ sSearch: obj.search,
+ bSmart: obj.smart,
+ bRegex: obj.regex,
+ bCaseInsensitive: obj.caseInsensitive
+ };
+ }
+
+ /**
+ * Generate the node required for the info display
+ * @param {object} oSettings dataTables settings object
+ * @returns {node} Information element
+ * @memberof DataTable#oApi
+ */
+ function _fnFeatureHtmlInfo ( settings )
+ {
+ var
+ tid = settings.sTableId,
+ nodes = settings.aanFeatures.i,
+ n = $('<div/>', {
+ 'class': settings.oClasses.sInfo,
+ 'id': ! nodes ? tid+'_info' : null
+ } );
+
+ if ( ! nodes ) {
+ // Update display on each draw
+ settings.aoDrawCallback.push( {
+ "fn": _fnUpdateInfo,
+ "sName": "information"
+ } );
+
+ n
+ .attr( 'role', 'status' )
+ .attr( 'aria-live', 'polite' );
+
+ // Table is described by our info div
+ $(settings.nTable).attr( 'aria-describedby', tid+'_info' );
+ }
+
+ return n[0];
+ }
+
+
+ /**
+ * Update the information elements in the display
+ * @param {object} settings dataTables settings object
+ * @memberof DataTable#oApi
+ */
+ function _fnUpdateInfo ( settings )
+ {
+ /* Show information about the table */
+ var nodes = settings.aanFeatures.i;
+ if ( nodes.length === 0 ) {
+ return;
+ }
+
+ var
+ lang = settings.oLanguage,
+ start = settings._iDisplayStart+1,
+ end = settings.fnDisplayEnd(),
+ max = settings.fnRecordsTotal(),
+ total = settings.fnRecordsDisplay(),
+ out = total ?
+ lang.sInfo :
+ lang.sInfoEmpty;
+
+ if ( total !== max ) {
+ /* Record set after filtering */
+ out += ' ' + lang.sInfoFiltered;
+ }
+
+ // Convert the macros
+ out += lang.sInfoPostFix;
+ out = _fnInfoMacros( settings, out );
+
+ var callback = lang.fnInfoCallback;
+ if ( callback !== null ) {
+ out = callback.call( settings.oInstance,
+ settings, start, end, max, total, out
+ );
+ }
+
+ $(nodes).html( out );
+ }
+
+
+ function _fnInfoMacros ( settings, str )
+ {
+ // When infinite scrolling, we are always starting at 1. _iDisplayStart is used only
+ // internally
+ var
+ formatter = settings.fnFormatNumber,
+ start = settings._iDisplayStart+1,
+ len = settings._iDisplayLength,
+ vis = settings.fnRecordsDisplay(),
+ all = len === -1;
+
+ return str.
+ replace(/_START_/g, formatter.call( settings, start ) ).
+ replace(/_END_/g, formatter.call( settings, settings.fnDisplayEnd() ) ).
+ replace(/_MAX_/g, formatter.call( settings, settings.fnRecordsTotal() ) ).
+ replace(/_TOTAL_/g, formatter.call( settings, vis ) ).
+ replace(/_PAGE_/g, formatter.call( settings, all ? 1 : Math.ceil( start / len ) ) ).
+ replace(/_PAGES_/g, formatter.call( settings, all ? 1 : Math.ceil( vis / len ) ) );
+ }
+
+
+
+ /**
+ * Draw the table for the first time, adding all required features
+ * @param {object} settings dataTables settings object
+ * @memberof DataTable#oApi
+ */
+ function _fnInitialise ( settings )
+ {
+ var i, iLen, iAjaxStart=settings.iInitDisplayStart;
+ var columns = settings.aoColumns, column;
+ var features = settings.oFeatures;
+ var deferLoading = settings.bDeferLoading; // value modified by the draw
+
+ /* Ensure that the table data is fully initialised */
+ if ( ! settings.bInitialised ) {
+ setTimeout( function(){ _fnInitialise( settings ); }, 200 );
+ return;
+ }
+
+ /* Show the display HTML options */
+ _fnAddOptionsHtml( settings );
+
+ /* Build and draw the header / footer for the table */
+ _fnBuildHead( settings );
+ _fnDrawHead( settings, settings.aoHeader );
+ _fnDrawHead( settings, settings.aoFooter );
+
+ /* Okay to show that something is going on now */
+ _fnProcessingDisplay( settings, true );
+
+ /* Calculate sizes for columns */
+ if ( features.bAutoWidth ) {
+ _fnCalculateColumnWidths( settings );
+ }
+
+ for ( i=0, iLen=columns.length ; i<iLen ; i++ ) {
+ column = columns[i];
+
+ if ( column.sWidth ) {
+ column.nTh.style.width = _fnStringToCss( column.sWidth );
+ }
+ }
+
+ _fnCallbackFire( settings, null, 'preInit', [settings] );
+
+ // If there is default sorting required - let's do it. The sort function
+ // will do the drawing for us. Otherwise we draw the table regardless of the
+ // Ajax source - this allows the table to look initialised for Ajax sourcing
+ // data (show 'loading' message possibly)
+ _fnReDraw( settings );
+
+ // Server-side processing init complete is done by _fnAjaxUpdateDraw
+ var dataSrc = _fnDataSource( settings );
+ if ( dataSrc != 'ssp' || deferLoading ) {
+ // if there is an ajax source load the data
+ if ( dataSrc == 'ajax' ) {
+ _fnBuildAjax( settings, [], function(json) {
+ var aData = _fnAjaxDataSrc( settings, json );
+
+ // Got the data - add it to the table
+ for ( i=0 ; i<aData.length ; i++ ) {
+ _fnAddData( settings, aData[i] );
+ }
+
+ // Reset the init display for cookie saving. We've already done
+ // a filter, and therefore cleared it before. So we need to make
+ // it appear 'fresh'
+ settings.iInitDisplayStart = iAjaxStart;
+
+ _fnReDraw( settings );
+
+ _fnProcessingDisplay( settings, false );
+ _fnInitComplete( settings, json );
+ }, settings );
+ }
+ else {
+ _fnProcessingDisplay( settings, false );
+ _fnInitComplete( settings );
+ }
+ }
+ }
+
+
+ /**
+ * Draw the table for the first time, adding all required features
+ * @param {object} oSettings dataTables settings object
+ * @param {object} [json] JSON from the server that completed the table, if using Ajax source
+ * with client-side processing (optional)
+ * @memberof DataTable#oApi
+ */
+ function _fnInitComplete ( settings, json )
+ {
+ settings._bInitComplete = true;
+
+ // When data was added after the initialisation (data or Ajax) we need to
+ // calculate the column sizing
+ if ( json || settings.oInit.aaData ) {
+ _fnAdjustColumnSizing( settings );
+ }
+
+ _fnCallbackFire( settings, null, 'plugin-init', [settings, json] );
+ _fnCallbackFire( settings, 'aoInitComplete', 'init', [settings, json] );
+ }
+
+
+ function _fnLengthChange ( settings, val )
+ {
+ var len = parseInt( val, 10 );
+ settings._iDisplayLength = len;
+
+ _fnLengthOverflow( settings );
+
+ // Fire length change event
+ _fnCallbackFire( settings, null, 'length', [settings, len] );
+ }
+
+
+ /**
+ * Generate the node required for user display length changing
+ * @param {object} settings dataTables settings object
+ * @returns {node} Display length feature node
+ * @memberof DataTable#oApi
+ */
+ function _fnFeatureHtmlLength ( settings )
+ {
+ var
+ classes = settings.oClasses,
+ tableId = settings.sTableId,
+ menu = settings.aLengthMenu,
+ d2 = $.isArray( menu[0] ),
+ lengths = d2 ? menu[0] : menu,
+ language = d2 ? menu[1] : menu;
+
+ var select = $('<select/>', {
+ 'name': tableId+'_length',
+ 'aria-controls': tableId,
+ 'class': classes.sLengthSelect
+ } );
+
+ for ( var i=0, ien=lengths.length ; i<ien ; i++ ) {
+ select[0][ i ] = new Option( language[i], lengths[i] );
+ }
+
+ var div = $('<div><label/></div>').addClass( classes.sLength );
+ if ( ! settings.aanFeatures.l ) {
+ div[0].id = tableId+'_length';
+ }
+
+ div.children().append(
+ settings.oLanguage.sLengthMenu.replace( '_MENU_', select[0].outerHTML )
+ );
+
+ // Can't use `select` variable as user might provide their own and the
+ // reference is broken by the use of outerHTML
+ $('select', div)
+ .val( settings._iDisplayLength )
+ .on( 'change.DT', function(e) {
+ _fnLengthChange( settings, $(this).val() );
+ _fnDraw( settings );
+ } );
+
+ // Update node value whenever anything changes the table's length
+ $(settings.nTable).on( 'length.dt.DT', function (e, s, len) {
+ if ( settings === s ) {
+ $('select', div).val( len );
+ }
+ } );
+
+ return div[0];
+ }
+
+
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Note that most of the paging logic is done in
+ * DataTable.ext.pager
+ */
+
+ /**
+ * Generate the node required for default pagination
+ * @param {object} oSettings dataTables settings object
+ * @returns {node} Pagination feature node
+ * @memberof DataTable#oApi
+ */
+ function _fnFeatureHtmlPaginate ( settings )
+ {
+ var
+ type = settings.sPaginationType,
+ plugin = DataTable.ext.pager[ type ],
+ modern = typeof plugin === 'function',
+ redraw = function( settings ) {
+ _fnDraw( settings );
+ },
+ node = $('<div/>').addClass( settings.oClasses.sPaging + type )[0],
+ features = settings.aanFeatures;
+
+ if ( ! modern ) {
+ plugin.fnInit( settings, node, redraw );
+ }
+
+ /* Add a draw callback for the pagination on first instance, to update the paging display */
+ if ( ! features.p )
+ {
+ node.id = settings.sTableId+'_paginate';
+
+ settings.aoDrawCallback.push( {
+ "fn": function( settings ) {
+ if ( modern ) {
+ var
+ start = settings._iDisplayStart,
+ len = settings._iDisplayLength,
+ visRecords = settings.fnRecordsDisplay(),
+ all = len === -1,
+ page = all ? 0 : Math.ceil( start / len ),
+ pages = all ? 1 : Math.ceil( visRecords / len ),
+ buttons = plugin(page, pages),
+ i, ien;
+
+ for ( i=0, ien=features.p.length ; i<ien ; i++ ) {
+ _fnRenderer( settings, 'pageButton' )(
+ settings, features.p[i], i, buttons, page, pages
+ );
+ }
+ }
+ else {
+ plugin.fnUpdate( settings, redraw );
+ }
+ },
+ "sName": "pagination"
+ } );
+ }
+
+ return node;
+ }
+
+
+ /**
+ * Alter the display settings to change the page
+ * @param {object} settings DataTables settings object
+ * @param {string|int} action Paging action to take: "first", "previous",
+ * "next" or "last" or page number to jump to (integer)
+ * @param [bool] redraw Automatically draw the update or not
+ * @returns {bool} true page has changed, false - no change
+ * @memberof DataTable#oApi
+ */
+ function _fnPageChange ( settings, action, redraw )
+ {
+ var
+ start = settings._iDisplayStart,
+ len = settings._iDisplayLength,
+ records = settings.fnRecordsDisplay();
+
+ if ( records === 0 || len === -1 )
+ {
+ start = 0;
+ }
+ else if ( typeof action === "number" )
+ {
+ start = action * len;
+
+ if ( start > records )
+ {
+ start = 0;
+ }
+ }
+ else if ( action == "first" )
+ {
+ start = 0;
+ }
+ else if ( action == "previous" )
+ {
+ start = len >= 0 ?
+ start - len :
+ 0;
+
+ if ( start < 0 )
+ {
+ start = 0;
+ }
+ }
+ else if ( action == "next" )
+ {
+ if ( start + len < records )
+ {
+ start += len;
+ }
+ }
+ else if ( action == "last" )
+ {
+ start = Math.floor( (records-1) / len) * len;
+ }
+ else
+ {
+ _fnLog( settings, 0, "Unknown paging action: "+action, 5 );
+ }
+
+ var changed = settings._iDisplayStart !== start;
+ settings._iDisplayStart = start;
+
+ if ( changed ) {
+ _fnCallbackFire( settings, null, 'page', [settings] );
+
+ if ( redraw ) {
+ _fnDraw( settings );
+ }
+ }
+
+ return changed;
+ }
+
+
+
+ /**
+ * Generate the node required for the processing node
+ * @param {object} settings dataTables settings object
+ * @returns {node} Processing element
+ * @memberof DataTable#oApi
+ */
+ function _fnFeatureHtmlProcessing ( settings )
+ {
+ return $('<div/>', {
+ 'id': ! settings.aanFeatures.r ? settings.sTableId+'_processing' : null,
+ 'class': settings.oClasses.sProcessing
+ } )
+ .html( settings.oLanguage.sProcessing )
+ .insertBefore( settings.nTable )[0];
+ }
+
+
+ /**
+ * Display or hide the processing indicator
+ * @param {object} settings dataTables settings object
+ * @param {bool} show Show the processing indicator (true) or not (false)
+ * @memberof DataTable#oApi
+ */
+ function _fnProcessingDisplay ( settings, show )
+ {
+ if ( settings.oFeatures.bProcessing ) {
+ $(settings.aanFeatures.r).css( 'display', show ? 'block' : 'none' );
+ }
+
+ _fnCallbackFire( settings, null, 'processing', [settings, show] );
+ }
+
+ /**
+ * Add any control elements for the table - specifically scrolling
+ * @param {object} settings dataTables settings object
+ * @returns {node} Node to add to the DOM
+ * @memberof DataTable#oApi
+ */
+ function _fnFeatureHtmlTable ( settings )
+ {
+ var table = $(settings.nTable);
+
+ // Add the ARIA grid role to the table
+ table.attr( 'role', 'grid' );
+
+ // Scrolling from here on in
+ var scroll = settings.oScroll;
+
+ if ( scroll.sX === '' && scroll.sY === '' ) {
+ return settings.nTable;
+ }
+
+ var scrollX = scroll.sX;
+ var scrollY = scroll.sY;
+ var classes = settings.oClasses;
+ var caption = table.children('caption');
+ var captionSide = caption.length ? caption[0]._captionSide : null;
+ var headerClone = $( table[0].cloneNode(false) );
+ var footerClone = $( table[0].cloneNode(false) );
+ var footer = table.children('tfoot');
+ var _div = '<div/>';
+ var size = function ( s ) {
+ return !s ? null : _fnStringToCss( s );
+ };
+
+ if ( ! footer.length ) {
+ footer = null;
+ }
+
+ /*
+ * The HTML structure that we want to generate in this function is:
+ * div - scroller
+ * div - scroll head
+ * div - scroll head inner
+ * table - scroll head table
+ * thead - thead
+ * div - scroll body
+ * table - table (master table)
+ * thead - thead clone for sizing
+ * tbody - tbody
+ * div - scroll foot
+ * div - scroll foot inner
+ * table - scroll foot table
+ * tfoot - tfoot
+ */
+ var scroller = $( _div, { 'class': classes.sScrollWrapper } )
+ .append(
+ $(_div, { 'class': classes.sScrollHead } )
+ .css( {
+ overflow: 'hidden',
+ position: 'relative',
+ border: 0,
+ width: scrollX ? size(scrollX) : '100%'
+ } )
+ .append(
+ $(_div, { 'class': classes.sScrollHeadInner } )
+ .css( {
+ 'box-sizing': 'content-box',
+ width: scroll.sXInner || '100%'
+ } )
+ .append(
+ headerClone
+ .removeAttr('id')
+ .css( 'margin-left', 0 )
+ .append( captionSide === 'top' ? caption : null )
+ .append(
+ table.children('thead')
+ )
+ )
+ )
+ )
+ .append(
+ $(_div, { 'class': classes.sScrollBody } )
+ .css( {
+ position: 'relative',
+ overflow: 'auto',
+ width: size( scrollX )
+ } )
+ .append( table )
+ );
+
+ if ( footer ) {
+ scroller.append(
+ $(_div, { 'class': classes.sScrollFoot } )
+ .css( {
+ overflow: 'hidden',
+ border: 0,
+ width: scrollX ? size(scrollX) : '100%'
+ } )
+ .append(
+ $(_div, { 'class': classes.sScrollFootInner } )
+ .append(
+ footerClone
+ .removeAttr('id')
+ .css( 'margin-left', 0 )
+ .append( captionSide === 'bottom' ? caption : null )
+ .append(
+ table.children('tfoot')
+ )
+ )
+ )
+ );
+ }
+
+ var children = scroller.children();
+ var scrollHead = children[0];
+ var scrollBody = children[1];
+ var scrollFoot = footer ? children[2] : null;
+
+ // When the body is scrolled, then we also want to scroll the headers
+ if ( scrollX ) {
+ $(scrollBody).on( 'scroll.DT', function (e) {
+ var scrollLeft = this.scrollLeft;
+
+ scrollHead.scrollLeft = scrollLeft;
+
+ if ( footer ) {
+ scrollFoot.scrollLeft = scrollLeft;
+ }
+ } );
+ }
+
+ $(scrollBody).css(
+ scrollY && scroll.bCollapse ? 'max-height' : 'height',
+ scrollY
+ );
+
+ settings.nScrollHead = scrollHead;
+ settings.nScrollBody = scrollBody;
+ settings.nScrollFoot = scrollFoot;
+
+ // On redraw - align columns
+ settings.aoDrawCallback.push( {
+ "fn": _fnScrollDraw,
+ "sName": "scrolling"
+ } );
+
+ return scroller[0];
+ }
+
+
+
+ /**
+ * Update the header, footer and body tables for resizing - i.e. column
+ * alignment.
+ *
+ * Welcome to the most horrible function DataTables. The process that this
+ * function follows is basically:
+ * 1. Re-create the table inside the scrolling div
+ * 2. Take live measurements from the DOM
+ * 3. Apply the measurements to align the columns
+ * 4. Clean up
+ *
+ * @param {object} settings dataTables settings object
+ * @memberof DataTable#oApi
+ */
+ function _fnScrollDraw ( settings )
+ {
+ // Given that this is such a monster function, a lot of variables are use
+ // to try and keep the minimised size as small as possible
+ var
+ scroll = settings.oScroll,
+ scrollX = scroll.sX,
+ scrollXInner = scroll.sXInner,
+ scrollY = scroll.sY,
+ barWidth = scroll.iBarWidth,
+ divHeader = $(settings.nScrollHead),
+ divHeaderStyle = divHeader[0].style,
+ divHeaderInner = divHeader.children('div'),
+ divHeaderInnerStyle = divHeaderInner[0].style,
+ divHeaderTable = divHeaderInner.children('table'),
+ divBodyEl = settings.nScrollBody,
+ divBody = $(divBodyEl),
+ divBodyStyle = divBodyEl.style,
+ divFooter = $(settings.nScrollFoot),
+ divFooterInner = divFooter.children('div'),
+ divFooterTable = divFooterInner.children('table'),
+ header = $(settings.nTHead),
+ table = $(settings.nTable),
+ tableEl = table[0],
+ tableStyle = tableEl.style,
+ footer = settings.nTFoot ? $(settings.nTFoot) : null,
+ browser = settings.oBrowser,
+ ie67 = browser.bScrollOversize,
+ dtHeaderCells = _pluck( settings.aoColumns, 'nTh' ),
+ headerTrgEls, footerTrgEls,
+ headerSrcEls, footerSrcEls,
+ headerCopy, footerCopy,
+ headerWidths=[], footerWidths=[],
+ headerContent=[], footerContent=[],
+ idx, correction, sanityWidth,
+ zeroOut = function(nSizer) {
+ var style = nSizer.style;
+ style.paddingTop = "0";
+ style.paddingBottom = "0";
+ style.borderTopWidth = "0";
+ style.borderBottomWidth = "0";
+ style.height = 0;
+ };
+
+ // If the scrollbar visibility has changed from the last draw, we need to
+ // adjust the column sizes as the table width will have changed to account
+ // for the scrollbar
+ var scrollBarVis = divBodyEl.scrollHeight > divBodyEl.clientHeight;
+
+ if ( settings.scrollBarVis !== scrollBarVis && settings.scrollBarVis !== undefined ) {
+ settings.scrollBarVis = scrollBarVis;
+ _fnAdjustColumnSizing( settings );
+ return; // adjust column sizing will call this function again
+ }
+ else {
+ settings.scrollBarVis = scrollBarVis;
+ }
+
+ /*
+ * 1. Re-create the table inside the scrolling div
+ */
+
+ // Remove the old minimised thead and tfoot elements in the inner table
+ table.children('thead, tfoot').remove();
+
+ if ( footer ) {
+ footerCopy = footer.clone().prependTo( table );
+ footerTrgEls = footer.find('tr'); // the original tfoot is in its own table and must be sized
+ footerSrcEls = footerCopy.find('tr');
+ }
+
+ // Clone the current header and footer elements and then place it into the inner table
+ headerCopy = header.clone().prependTo( table );
+ headerTrgEls = header.find('tr'); // original header is in its own table
+ headerSrcEls = headerCopy.find('tr');
+ headerCopy.find('th, td').removeAttr('tabindex');
+
+
+ /*
+ * 2. Take live measurements from the DOM - do not alter the DOM itself!
+ */
+
+ // Remove old sizing and apply the calculated column widths
+ // Get the unique column headers in the newly created (cloned) header. We want to apply the
+ // calculated sizes to this header
+ if ( ! scrollX )
+ {
+ divBodyStyle.width = '100%';
+ divHeader[0].style.width = '100%';
+ }
+
+ $.each( _fnGetUniqueThs( settings, headerCopy ), function ( i, el ) {
+ idx = _fnVisibleToColumnIndex( settings, i );
+ el.style.width = settings.aoColumns[idx].sWidth;
+ } );
+
+ if ( footer ) {
+ _fnApplyToChildren( function(n) {
+ n.style.width = "";
+ }, footerSrcEls );
+ }
+
+ // Size the table as a whole
+ sanityWidth = table.outerWidth();
+ if ( scrollX === "" ) {
+ // No x scrolling
+ tableStyle.width = "100%";
+
+ // IE7 will make the width of the table when 100% include the scrollbar
+ // - which is shouldn't. When there is a scrollbar we need to take this
+ // into account.
+ if ( ie67 && (table.find('tbody').height() > divBodyEl.offsetHeight ||
+ divBody.css('overflow-y') == "scroll")
+ ) {
+ tableStyle.width = _fnStringToCss( table.outerWidth() - barWidth);
+ }
+
+ // Recalculate the sanity width
+ sanityWidth = table.outerWidth();
+ }
+ else if ( scrollXInner !== "" ) {
+ // legacy x scroll inner has been given - use it
+ tableStyle.width = _fnStringToCss(scrollXInner);
+
+ // Recalculate the sanity width
+ sanityWidth = table.outerWidth();
+ }
+
+ // Hidden header should have zero height, so remove padding and borders. Then
+ // set the width based on the real headers
+
+ // Apply all styles in one pass
+ _fnApplyToChildren( zeroOut, headerSrcEls );
+
+ // Read all widths in next pass
+ _fnApplyToChildren( function(nSizer) {
+ headerContent.push( nSizer.innerHTML );
+ headerWidths.push( _fnStringToCss( $(nSizer).css('width') ) );
+ }, headerSrcEls );
+
+ // Apply all widths in final pass
+ _fnApplyToChildren( function(nToSize, i) {
+ // Only apply widths to the DataTables detected header cells - this
+ // prevents complex headers from having contradictory sizes applied
+ if ( $.inArray( nToSize, dtHeaderCells ) !== -1 ) {
+ nToSize.style.width = headerWidths[i];
+ }
+ }, headerTrgEls );
+
+ $(headerSrcEls).height(0);
+
+ /* Same again with the footer if we have one */
+ if ( footer )
+ {
+ _fnApplyToChildren( zeroOut, footerSrcEls );
+
+ _fnApplyToChildren( function(nSizer) {
+ footerContent.push( nSizer.innerHTML );
+ footerWidths.push( _fnStringToCss( $(nSizer).css('width') ) );
+ }, footerSrcEls );
+
+ _fnApplyToChildren( function(nToSize, i) {
+ nToSize.style.width = footerWidths[i];
+ }, footerTrgEls );
+
+ $(footerSrcEls).height(0);
+ }
+
+
+ /*
+ * 3. Apply the measurements
+ */
+
+ // "Hide" the header and footer that we used for the sizing. We need to keep
+ // the content of the cell so that the width applied to the header and body
+ // both match, but we want to hide it completely. We want to also fix their
+ // width to what they currently are
+ _fnApplyToChildren( function(nSizer, i) {
+ nSizer.innerHTML = '<div class="dataTables_sizing" style="height:0;overflow:hidden;">'+headerContent[i]+'</div>';
+ nSizer.style.width = headerWidths[i];
+ }, headerSrcEls );
+
+ if ( footer )
+ {
+ _fnApplyToChildren( function(nSizer, i) {
+ nSizer.innerHTML = '<div class="dataTables_sizing" style="height:0;overflow:hidden;">'+footerContent[i]+'</div>';
+ nSizer.style.width = footerWidths[i];
+ }, footerSrcEls );
+ }
+
+ // Sanity check that the table is of a sensible width. If not then we are going to get
+ // misalignment - try to prevent this by not allowing the table to shrink below its min width
+ if ( table.outerWidth() < sanityWidth )
+ {
+ // The min width depends upon if we have a vertical scrollbar visible or not */
+ correction = ((divBodyEl.scrollHeight > divBodyEl.offsetHeight ||
+ divBody.css('overflow-y') == "scroll")) ?
+ sanityWidth+barWidth :
+ sanityWidth;
+
+ // IE6/7 are a law unto themselves...
+ if ( ie67 && (divBodyEl.scrollHeight >
+ divBodyEl.offsetHeight || divBody.css('overflow-y') == "scroll")
+ ) {
+ tableStyle.width = _fnStringToCss( correction-barWidth );
+ }
+
+ // And give the user a warning that we've stopped the table getting too small
+ if ( scrollX === "" || scrollXInner !== "" ) {
+ _fnLog( settings, 1, 'Possible column misalignment', 6 );
+ }
+ }
+ else
+ {
+ correction = '100%';
+ }
+
+ // Apply to the container elements
+ divBodyStyle.width = _fnStringToCss( correction );
+ divHeaderStyle.width = _fnStringToCss( correction );
+
+ if ( footer ) {
+ settings.nScrollFoot.style.width = _fnStringToCss( correction );
+ }
+
+
+ /*
+ * 4. Clean up
+ */
+ if ( ! scrollY ) {
+ /* IE7< puts a vertical scrollbar in place (when it shouldn't be) due to subtracting
+ * the scrollbar height from the visible display, rather than adding it on. We need to
+ * set the height in order to sort this. Don't want to do it in any other browsers.
+ */
+ if ( ie67 ) {
+ divBodyStyle.height = _fnStringToCss( tableEl.offsetHeight+barWidth );
+ }
+ }
+
+ /* Finally set the width's of the header and footer tables */
+ var iOuterWidth = table.outerWidth();
+ divHeaderTable[0].style.width = _fnStringToCss( iOuterWidth );
+ divHeaderInnerStyle.width = _fnStringToCss( iOuterWidth );
+
+ // Figure out if there are scrollbar present - if so then we need a the header and footer to
+ // provide a bit more space to allow "overflow" scrolling (i.e. past the scrollbar)
+ var bScrolling = table.height() > divBodyEl.clientHeight || divBody.css('overflow-y') == "scroll";
+ var padding = 'padding' + (browser.bScrollbarLeft ? 'Left' : 'Right' );
+ divHeaderInnerStyle[ padding ] = bScrolling ? barWidth+"px" : "0px";
+
+ if ( footer ) {
+ divFooterTable[0].style.width = _fnStringToCss( iOuterWidth );
+ divFooterInner[0].style.width = _fnStringToCss( iOuterWidth );
+ divFooterInner[0].style[padding] = bScrolling ? barWidth+"px" : "0px";
+ }
+
+ // Correct DOM ordering for colgroup - comes before the thead
+ table.children('colgroup').insertBefore( table.children('thead') );
+
+ /* Adjust the position of the header in case we loose the y-scrollbar */
+ divBody.scroll();
+
+ // If sorting or filtering has occurred, jump the scrolling back to the top
+ // only if we aren't holding the position
+ if ( (settings.bSorted || settings.bFiltered) && ! settings._drawHold ) {
+ divBodyEl.scrollTop = 0;
+ }
+ }
+
+
+
+ /**
+ * Apply a given function to the display child nodes of an element array (typically
+ * TD children of TR rows
+ * @param {function} fn Method to apply to the objects
+ * @param array {nodes} an1 List of elements to look through for display children
+ * @param array {nodes} an2 Another list (identical structure to the first) - optional
+ * @memberof DataTable#oApi
+ */
+ function _fnApplyToChildren( fn, an1, an2 )
+ {
+ var index=0, i=0, iLen=an1.length;
+ var nNode1, nNode2;
+
+ while ( i < iLen ) {
+ nNode1 = an1[i].firstChild;
+ nNode2 = an2 ? an2[i].firstChild : null;
+
+ while ( nNode1 ) {
+ if ( nNode1.nodeType === 1 ) {
+ if ( an2 ) {
+ fn( nNode1, nNode2, index );
+ }
+ else {
+ fn( nNode1, index );
+ }
+
+ index++;
+ }
+
+ nNode1 = nNode1.nextSibling;
+ nNode2 = an2 ? nNode2.nextSibling : null;
+ }
+
+ i++;
+ }
+ }
+
+
+
+ var __re_html_remove = /<.*?>/g;
+
+
+ /**
+ * Calculate the width of columns for the table
+ * @param {object} oSettings dataTables settings object
+ * @memberof DataTable#oApi
+ */
+ function _fnCalculateColumnWidths ( oSettings )
+ {
+ var
+ table = oSettings.nTable,
+ columns = oSettings.aoColumns,
+ scroll = oSettings.oScroll,
+ scrollY = scroll.sY,
+ scrollX = scroll.sX,
+ scrollXInner = scroll.sXInner,
+ columnCount = columns.length,
+ visibleColumns = _fnGetColumns( oSettings, 'bVisible' ),
+ headerCells = $('th', oSettings.nTHead),
+ tableWidthAttr = table.getAttribute('width'), // from DOM element
+ tableContainer = table.parentNode,
+ userInputs = false,
+ i, column, columnIdx, width, outerWidth,
+ browser = oSettings.oBrowser,
+ ie67 = browser.bScrollOversize;
+
+ var styleWidth = table.style.width;
+ if ( styleWidth && styleWidth.indexOf('%') !== -1 ) {
+ tableWidthAttr = styleWidth;
+ }
+
+ /* Convert any user input sizes into pixel sizes */
+ for ( i=0 ; i<visibleColumns.length ; i++ ) {
+ column = columns[ visibleColumns[i] ];
+
+ if ( column.sWidth !== null ) {
+ column.sWidth = _fnConvertToWidth( column.sWidthOrig, tableContainer );
+
+ userInputs = true;
+ }
+ }
+
+ /* If the number of columns in the DOM equals the number that we have to
+ * process in DataTables, then we can use the offsets that are created by
+ * the web- browser. No custom sizes can be set in order for this to happen,
+ * nor scrolling used
+ */
+ if ( ie67 || ! userInputs && ! scrollX && ! scrollY &&
+ columnCount == _fnVisbleColumns( oSettings ) &&
+ columnCount == headerCells.length
+ ) {
+ for ( i=0 ; i<columnCount ; i++ ) {
+ var colIdx = _fnVisibleToColumnIndex( oSettings, i );
+
+ if ( colIdx !== null ) {
+ columns[ colIdx ].sWidth = _fnStringToCss( headerCells.eq(i).width() );
+ }
+ }
+ }
+ else
+ {
+ // Otherwise construct a single row, worst case, table with the widest
+ // node in the data, assign any user defined widths, then insert it into
+ // the DOM and allow the browser to do all the hard work of calculating
+ // table widths
+ var tmpTable = $(table).clone() // don't use cloneNode - IE8 will remove events on the main table
+ .css( 'visibility', 'hidden' )
+ .removeAttr( 'id' );
+
+ // Clean up the table body
+ tmpTable.find('tbody tr').remove();
+ var tr = $('<tr/>').appendTo( tmpTable.find('tbody') );
+
+ // Clone the table header and footer - we can't use the header / footer
+ // from the cloned table, since if scrolling is active, the table's
+ // real header and footer are contained in different table tags
+ tmpTable.find('thead, tfoot').remove();
+ tmpTable
+ .append( $(oSettings.nTHead).clone() )
+ .append( $(oSettings.nTFoot).clone() );
+
+ // Remove any assigned widths from the footer (from scrolling)
+ tmpTable.find('tfoot th, tfoot td').css('width', '');
+
+ // Apply custom sizing to the cloned header
+ headerCells = _fnGetUniqueThs( oSettings, tmpTable.find('thead')[0] );
+
+ for ( i=0 ; i<visibleColumns.length ; i++ ) {
+ column = columns[ visibleColumns[i] ];
+
+ headerCells[i].style.width = column.sWidthOrig !== null && column.sWidthOrig !== '' ?
+ _fnStringToCss( column.sWidthOrig ) :
+ '';
+
+ // For scrollX we need to force the column width otherwise the
+ // browser will collapse it. If this width is smaller than the
+ // width the column requires, then it will have no effect
+ if ( column.sWidthOrig && scrollX ) {
+ $( headerCells[i] ).append( $('<div/>').css( {
+ width: column.sWidthOrig,
+ margin: 0,
+ padding: 0,
+ border: 0,
+ height: 1
+ } ) );
+ }
+ }
+
+ // Find the widest cell for each column and put it into the table
+ if ( oSettings.aoData.length ) {
+ for ( i=0 ; i<visibleColumns.length ; i++ ) {
+ columnIdx = visibleColumns[i];
+ column = columns[ columnIdx ];
+
+ $( _fnGetWidestNode( oSettings, columnIdx ) )
+ .clone( false )
+ .append( column.sContentPadding )
+ .appendTo( tr );
+ }
+ }
+
+ // Tidy the temporary table - remove name attributes so there aren't
+ // duplicated in the dom (radio elements for example)
+ $('[name]', tmpTable).removeAttr('name');
+
+ // Table has been built, attach to the document so we can work with it.
+ // A holding element is used, positioned at the top of the container
+ // with minimal height, so it has no effect on if the container scrolls
+ // or not. Otherwise it might trigger scrolling when it actually isn't
+ // needed
+ var holder = $('<div/>').css( scrollX || scrollY ?
+ {
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ height: 1,
+ right: 0,
+ overflow: 'hidden'
+ } :
+ {}
+ )
+ .append( tmpTable )
+ .appendTo( tableContainer );
+
+ // When scrolling (X or Y) we want to set the width of the table as
+ // appropriate. However, when not scrolling leave the table width as it
+ // is. This results in slightly different, but I think correct behaviour
+ if ( scrollX && scrollXInner ) {
+ tmpTable.width( scrollXInner );
+ }
+ else if ( scrollX ) {
+ tmpTable.css( 'width', 'auto' );
+ tmpTable.removeAttr('width');
+
+ // If there is no width attribute or style, then allow the table to
+ // collapse
+ if ( tmpTable.width() < tableContainer.clientWidth && tableWidthAttr ) {
+ tmpTable.width( tableContainer.clientWidth );
+ }
+ }
+ else if ( scrollY ) {
+ tmpTable.width( tableContainer.clientWidth );
+ }
+ else if ( tableWidthAttr ) {
+ tmpTable.width( tableWidthAttr );
+ }
+
+ // Get the width of each column in the constructed table - we need to
+ // know the inner width (so it can be assigned to the other table's
+ // cells) and the outer width so we can calculate the full width of the
+ // table. This is safe since DataTables requires a unique cell for each
+ // column, but if ever a header can span multiple columns, this will
+ // need to be modified.
+ var total = 0;
+ for ( i=0 ; i<visibleColumns.length ; i++ ) {
+ var cell = $(headerCells[i]);
+ var border = cell.outerWidth() - cell.width();
+
+ // Use getBounding... where possible (not IE8-) because it can give
+ // sub-pixel accuracy, which we then want to round up!
+ var bounding = browser.bBounding ?
+ Math.ceil( headerCells[i].getBoundingClientRect().width ) :
+ cell.outerWidth();
+
+ // Total is tracked to remove any sub-pixel errors as the outerWidth
+ // of the table might not equal the total given here (IE!).
+ total += bounding;
+
+ // Width for each column to use
+ columns[ visibleColumns[i] ].sWidth = _fnStringToCss( bounding - border );
+ }
+
+ table.style.width = _fnStringToCss( total );
+
+ // Finished with the table - ditch it
+ holder.remove();
+ }
+
+ // If there is a width attr, we want to attach an event listener which
+ // allows the table sizing to automatically adjust when the window is
+ // resized. Use the width attr rather than CSS, since we can't know if the
+ // CSS is a relative value or absolute - DOM read is always px.
+ if ( tableWidthAttr ) {
+ table.style.width = _fnStringToCss( tableWidthAttr );
+ }
+
+ if ( (tableWidthAttr || scrollX) && ! oSettings._reszEvt ) {
+ var bindResize = function () {
+ $(window).on('resize.DT-'+oSettings.sInstance, _fnThrottle( function () {
+ _fnAdjustColumnSizing( oSettings );
+ } ) );
+ };
+
+ // IE6/7 will crash if we bind a resize event handler on page load.
+ // To be removed in 1.11 which drops IE6/7 support
+ if ( ie67 ) {
+ setTimeout( bindResize, 1000 );
+ }
+ else {
+ bindResize();
+ }
+
+ oSettings._reszEvt = true;
+ }
+ }
+
+
+ /**
+ * Throttle the calls to a function. Arguments and context are maintained for
+ * the throttled function
+ * @param {function} fn Function to be called
+ * @param {int} [freq=200] call frequency in mS
+ * @returns {function} wrapped function
+ * @memberof DataTable#oApi
+ */
+ var _fnThrottle = DataTable.util.throttle;
+
+
+ /**
+ * Convert a CSS unit width to pixels (e.g. 2em)
+ * @param {string} width width to be converted
+ * @param {node} parent parent to get the with for (required for relative widths) - optional
+ * @returns {int} width in pixels
+ * @memberof DataTable#oApi
+ */
+ function _fnConvertToWidth ( width, parent )
+ {
+ if ( ! width ) {
+ return 0;
+ }
+
+ var n = $('<div/>')
+ .css( 'width', _fnStringToCss( width ) )
+ .appendTo( parent || document.body );
+
+ var val = n[0].offsetWidth;
+ n.remove();
+
+ return val;
+ }
+
+
+ /**
+ * Get the widest node
+ * @param {object} settings dataTables settings object
+ * @param {int} colIdx column of interest
+ * @returns {node} widest table node
+ * @memberof DataTable#oApi
+ */
+ function _fnGetWidestNode( settings, colIdx )
+ {
+ var idx = _fnGetMaxLenString( settings, colIdx );
+ if ( idx < 0 ) {
+ return null;
+ }
+
+ var data = settings.aoData[ idx ];
+ return ! data.nTr ? // Might not have been created when deferred rendering
+ $('<td/>').html( _fnGetCellData( settings, idx, colIdx, 'display' ) )[0] :
+ data.anCells[ colIdx ];
+ }
+
+
+ /**
+ * Get the maximum strlen for each data column
+ * @param {object} settings dataTables settings object
+ * @param {int} colIdx column of interest
+ * @returns {string} max string length for each column
+ * @memberof DataTable#oApi
+ */
+ function _fnGetMaxLenString( settings, colIdx )
+ {
+ var s, max=-1, maxIdx = -1;
+
+ for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
+ s = _fnGetCellData( settings, i, colIdx, 'display' )+'';
+ s = s.replace( __re_html_remove, '' );
+ s = s.replace( /&nbsp;/g, ' ' );
+
+ if ( s.length > max ) {
+ max = s.length;
+ maxIdx = i;
+ }
+ }
+
+ return maxIdx;
+ }
+
+
+ /**
+ * Append a CSS unit (only if required) to a string
+ * @param {string} value to css-ify
+ * @returns {string} value with css unit
+ * @memberof DataTable#oApi
+ */
+ function _fnStringToCss( s )
+ {
+ if ( s === null ) {
+ return '0px';
+ }
+
+ if ( typeof s == 'number' ) {
+ return s < 0 ?
+ '0px' :
+ s+'px';
+ }
+
+ // Check it has a unit character already
+ return s.match(/\d$/) ?
+ s+'px' :
+ s;
+ }
+
+
+
+ function _fnSortFlatten ( settings )
+ {
+ var
+ i, iLen, k, kLen,
+ aSort = [],
+ aiOrig = [],
+ aoColumns = settings.aoColumns,
+ aDataSort, iCol, sType, srcCol,
+ fixed = settings.aaSortingFixed,
+ fixedObj = $.isPlainObject( fixed ),
+ nestedSort = [],
+ add = function ( a ) {
+ if ( a.length && ! $.isArray( a[0] ) ) {
+ // 1D array
+ nestedSort.push( a );
+ }
+ else {
+ // 2D array
+ $.merge( nestedSort, a );
+ }
+ };
+
+ // Build the sort array, with pre-fix and post-fix options if they have been
+ // specified
+ if ( $.isArray( fixed ) ) {
+ add( fixed );
+ }
+
+ if ( fixedObj && fixed.pre ) {
+ add( fixed.pre );
+ }
+
+ add( settings.aaSorting );
+
+ if (fixedObj && fixed.post ) {
+ add( fixed.post );
+ }
+
+ for ( i=0 ; i<nestedSort.length ; i++ )
+ {
+ srcCol = nestedSort[i][0];
+ aDataSort = aoColumns[ srcCol ].aDataSort;
+
+ for ( k=0, kLen=aDataSort.length ; k<kLen ; k++ )
+ {
+ iCol = aDataSort[k];
+ sType = aoColumns[ iCol ].sType || 'string';
+
+ if ( nestedSort[i]._idx === undefined ) {
+ nestedSort[i]._idx = $.inArray( nestedSort[i][1], aoColumns[iCol].asSorting );
+ }
+
+ aSort.push( {
+ src: srcCol,
+ col: iCol,
+ dir: nestedSort[i][1],
+ index: nestedSort[i]._idx,
+ type: sType,
+ formatter: DataTable.ext.type.order[ sType+"-pre" ]
+ } );
+ }
+ }
+
+ return aSort;
+ }
+
+ /**
+ * Change the order of the table
+ * @param {object} oSettings dataTables settings object
+ * @memberof DataTable#oApi
+ * @todo This really needs split up!
+ */
+ function _fnSort ( oSettings )
+ {
+ var
+ i, ien, iLen, j, jLen, k, kLen,
+ sDataType, nTh,
+ aiOrig = [],
+ oExtSort = DataTable.ext.type.order,
+ aoData = oSettings.aoData,
+ aoColumns = oSettings.aoColumns,
+ aDataSort, data, iCol, sType, oSort,
+ formatters = 0,
+ sortCol,
+ displayMaster = oSettings.aiDisplayMaster,
+ aSort;
+
+ // Resolve any column types that are unknown due to addition or invalidation
+ // @todo Can this be moved into a 'data-ready' handler which is called when
+ // data is going to be used in the table?
+ _fnColumnTypes( oSettings );
+
+ aSort = _fnSortFlatten( oSettings );
+
+ for ( i=0, ien=aSort.length ; i<ien ; i++ ) {
+ sortCol = aSort[i];
+
+ // Track if we can use the fast sort algorithm
+ if ( sortCol.formatter ) {
+ formatters++;
+ }
+
+ // Load the data needed for the sort, for each cell
+ _fnSortData( oSettings, sortCol.col );
+ }
+
+ /* No sorting required if server-side or no sorting array */
+ if ( _fnDataSource( oSettings ) != 'ssp' && aSort.length !== 0 )
+ {
+ // Create a value - key array of the current row positions such that we can use their
+ // current position during the sort, if values match, in order to perform stable sorting
+ for ( i=0, iLen=displayMaster.length ; i<iLen ; i++ ) {
+ aiOrig[ displayMaster[i] ] = i;
+ }
+
+ /* Do the sort - here we want multi-column sorting based on a given data source (column)
+ * and sorting function (from oSort) in a certain direction. It's reasonably complex to
+ * follow on it's own, but this is what we want (example two column sorting):
+ * fnLocalSorting = function(a,b){
+ * var iTest;
+ * iTest = oSort['string-asc']('data11', 'data12');
+ * if (iTest !== 0)
+ * return iTest;
+ * iTest = oSort['numeric-desc']('data21', 'data22');
+ * if (iTest !== 0)
+ * return iTest;
+ * return oSort['numeric-asc']( aiOrig[a], aiOrig[b] );
+ * }
+ * Basically we have a test for each sorting column, if the data in that column is equal,
+ * test the next column. If all columns match, then we use a numeric sort on the row
+ * positions in the original data array to provide a stable sort.
+ *
+ * Note - I know it seems excessive to have two sorting methods, but the first is around
+ * 15% faster, so the second is only maintained for backwards compatibility with sorting
+ * methods which do not have a pre-sort formatting function.
+ */
+ if ( formatters === aSort.length ) {
+ // All sort types have formatting functions
+ displayMaster.sort( function ( a, b ) {
+ var
+ x, y, k, test, sort,
+ len=aSort.length,
+ dataA = aoData[a]._aSortData,
+ dataB = aoData[b]._aSortData;
+
+ for ( k=0 ; k<len ; k++ ) {
+ sort = aSort[k];
+
+ x = dataA[ sort.col ];
+ y = dataB[ sort.col ];
+
+ test = x<y ? -1 : x>y ? 1 : 0;
+ if ( test !== 0 ) {
+ return sort.dir === 'asc' ? test : -test;
+ }
+ }
+
+ x = aiOrig[a];
+ y = aiOrig[b];
+ return x<y ? -1 : x>y ? 1 : 0;
+ } );
+ }
+ else {
+ // Depreciated - remove in 1.11 (providing a plug-in option)
+ // Not all sort types have formatting methods, so we have to call their sorting
+ // methods.
+ displayMaster.sort( function ( a, b ) {
+ var
+ x, y, k, l, test, sort, fn,
+ len=aSort.length,
+ dataA = aoData[a]._aSortData,
+ dataB = aoData[b]._aSortData;
+
+ for ( k=0 ; k<len ; k++ ) {
+ sort = aSort[k];
+
+ x = dataA[ sort.col ];
+ y = dataB[ sort.col ];
+
+ fn = oExtSort[ sort.type+"-"+sort.dir ] || oExtSort[ "string-"+sort.dir ];
+ test = fn( x, y );
+ if ( test !== 0 ) {
+ return test;
+ }
+ }
+
+ x = aiOrig[a];
+ y = aiOrig[b];
+ return x<y ? -1 : x>y ? 1 : 0;
+ } );
+ }
+ }
+
+ /* Tell the draw function that we have sorted the data */
+ oSettings.bSorted = true;
+ }
+
+
+ function _fnSortAria ( settings )
+ {
+ var label;
+ var nextSort;
+ var columns = settings.aoColumns;
+ var aSort = _fnSortFlatten( settings );
+ var oAria = settings.oLanguage.oAria;
+
+ // ARIA attributes - need to loop all columns, to update all (removing old
+ // attributes as needed)
+ for ( var i=0, iLen=columns.length ; i<iLen ; i++ )
+ {
+ var col = columns[i];
+ var asSorting = col.asSorting;
+ var sTitle = col.sTitle.replace( /<.*?>/g, "" );
+ var th = col.nTh;
+
+ // IE7 is throwing an error when setting these properties with jQuery's
+ // attr() and removeAttr() methods...
+ th.removeAttribute('aria-sort');
+
+ /* In ARIA only the first sorting column can be marked as sorting - no multi-sort option */
+ if ( col.bSortable ) {
+ if ( aSort.length > 0 && aSort[0].col == i ) {
+ th.setAttribute('aria-sort', aSort[0].dir=="asc" ? "ascending" : "descending" );
+ nextSort = asSorting[ aSort[0].index+1 ] || asSorting[0];
+ }
+ else {
+ nextSort = asSorting[0];
+ }
+
+ label = sTitle + ( nextSort === "asc" ?
+ oAria.sSortAscending :
+ oAria.sSortDescending
+ );
+ }
+ else {
+ label = sTitle;
+ }
+
+ th.setAttribute('aria-label', label);
+ }
+ }
+
+
+ /**
+ * Function to run on user sort request
+ * @param {object} settings dataTables settings object
+ * @param {node} attachTo node to attach the handler to
+ * @param {int} colIdx column sorting index
+ * @param {boolean} [append=false] Append the requested sort to the existing
+ * sort if true (i.e. multi-column sort)
+ * @param {function} [callback] callback function
+ * @memberof DataTable#oApi
+ */
+ function _fnSortListener ( settings, colIdx, append, callback )
+ {
+ var col = settings.aoColumns[ colIdx ];
+ var sorting = settings.aaSorting;
+ var asSorting = col.asSorting;
+ var nextSortIdx;
+ var next = function ( a, overflow ) {
+ var idx = a._idx;
+ if ( idx === undefined ) {
+ idx = $.inArray( a[1], asSorting );
+ }
+
+ return idx+1 < asSorting.length ?
+ idx+1 :
+ overflow ?
+ null :
+ 0;
+ };
+
+ // Convert to 2D array if needed
+ if ( typeof sorting[0] === 'number' ) {
+ sorting = settings.aaSorting = [ sorting ];
+ }
+
+ // If appending the sort then we are multi-column sorting
+ if ( append && settings.oFeatures.bSortMulti ) {
+ // Are we already doing some kind of sort on this column?
+ var sortIdx = $.inArray( colIdx, _pluck(sorting, '0') );
+
+ if ( sortIdx !== -1 ) {
+ // Yes, modify the sort
+ nextSortIdx = next( sorting[sortIdx], true );
+
+ if ( nextSortIdx === null && sorting.length === 1 ) {
+ nextSortIdx = 0; // can't remove sorting completely
+ }
+
+ if ( nextSortIdx === null ) {
+ sorting.splice( sortIdx, 1 );
+ }
+ else {
+ sorting[sortIdx][1] = asSorting[ nextSortIdx ];
+ sorting[sortIdx]._idx = nextSortIdx;
+ }
+ }
+ else {
+ // No sort on this column yet
+ sorting.push( [ colIdx, asSorting[0], 0 ] );
+ sorting[sorting.length-1]._idx = 0;
+ }
+ }
+ else if ( sorting.length && sorting[0][0] == colIdx ) {
+ // Single column - already sorting on this column, modify the sort
+ nextSortIdx = next( sorting[0] );
+
+ sorting.length = 1;
+ sorting[0][1] = asSorting[ nextSortIdx ];
+ sorting[0]._idx = nextSortIdx;
+ }
+ else {
+ // Single column - sort only on this column
+ sorting.length = 0;
+ sorting.push( [ colIdx, asSorting[0] ] );
+ sorting[0]._idx = 0;
+ }
+
+ // Run the sort by calling a full redraw
+ _fnReDraw( settings );
+
+ // callback used for async user interaction
+ if ( typeof callback == 'function' ) {
+ callback( settings );
+ }
+ }
+
+
+ /**
+ * Attach a sort handler (click) to a node
+ * @param {object} settings dataTables settings object
+ * @param {node} attachTo node to attach the handler to
+ * @param {int} colIdx column sorting index
+ * @param {function} [callback] callback function
+ * @memberof DataTable#oApi
+ */
+ function _fnSortAttachListener ( settings, attachTo, colIdx, callback )
+ {
+ var col = settings.aoColumns[ colIdx ];
+
+ _fnBindAction( attachTo, {}, function (e) {
+ /* If the column is not sortable - don't to anything */
+ if ( col.bSortable === false ) {
+ return;
+ }
+
+ // If processing is enabled use a timeout to allow the processing
+ // display to be shown - otherwise to it synchronously
+ if ( settings.oFeatures.bProcessing ) {
+ _fnProcessingDisplay( settings, true );
+
+ setTimeout( function() {
+ _fnSortListener( settings, colIdx, e.shiftKey, callback );
+
+ // In server-side processing, the draw callback will remove the
+ // processing display
+ if ( _fnDataSource( settings ) !== 'ssp' ) {
+ _fnProcessingDisplay( settings, false );
+ }
+ }, 0 );
+ }
+ else {
+ _fnSortListener( settings, colIdx, e.shiftKey, callback );
+ }
+ } );
+ }
+
+
+ /**
+ * Set the sorting classes on table's body, Note: it is safe to call this function
+ * when bSort and bSortClasses are false
+ * @param {object} oSettings dataTables settings object
+ * @memberof DataTable#oApi
+ */
+ function _fnSortingClasses( settings )
+ {
+ var oldSort = settings.aLastSort;
+ var sortClass = settings.oClasses.sSortColumn;
+ var sort = _fnSortFlatten( settings );
+ var features = settings.oFeatures;
+ var i, ien, colIdx;
+
+ if ( features.bSort && features.bSortClasses ) {
+ // Remove old sorting classes
+ for ( i=0, ien=oldSort.length ; i<ien ; i++ ) {
+ colIdx = oldSort[i].src;
+
+ // Remove column sorting
+ $( _pluck( settings.aoData, 'anCells', colIdx ) )
+ .removeClass( sortClass + (i<2 ? i+1 : 3) );
+ }
+
+ // Add new column sorting
+ for ( i=0, ien=sort.length ; i<ien ; i++ ) {
+ colIdx = sort[i].src;
+
+ $( _pluck( settings.aoData, 'anCells', colIdx ) )
+ .addClass( sortClass + (i<2 ? i+1 : 3) );
+ }
+ }
+
+ settings.aLastSort = sort;
+ }
+
+
+ // Get the data to sort a column, be it from cache, fresh (populating the
+ // cache), or from a sort formatter
+ function _fnSortData( settings, idx )
+ {
+ // Custom sorting function - provided by the sort data type
+ var column = settings.aoColumns[ idx ];
+ var customSort = DataTable.ext.order[ column.sSortDataType ];
+ var customData;
+
+ if ( customSort ) {
+ customData = customSort.call( settings.oInstance, settings, idx,
+ _fnColumnIndexToVisible( settings, idx )
+ );
+ }
+
+ // Use / populate cache
+ var row, cellData;
+ var formatter = DataTable.ext.type.order[ column.sType+"-pre" ];
+
+ for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
+ row = settings.aoData[i];
+
+ if ( ! row._aSortData ) {
+ row._aSortData = [];
+ }
+
+ if ( ! row._aSortData[idx] || customSort ) {
+ cellData = customSort ?
+ customData[i] : // If there was a custom sort function, use data from there
+ _fnGetCellData( settings, i, idx, 'sort' );
+
+ row._aSortData[ idx ] = formatter ?
+ formatter( cellData ) :
+ cellData;
+ }
+ }
+ }
+
+
+
+ /**
+ * Save the state of a table
+ * @param {object} oSettings dataTables settings object
+ * @memberof DataTable#oApi
+ */
+ function _fnSaveState ( settings )
+ {
+ if ( !settings.oFeatures.bStateSave || settings.bDestroying )
+ {
+ return;
+ }
+
+ /* Store the interesting variables */
+ var state = {
+ time: +new Date(),
+ start: settings._iDisplayStart,
+ length: settings._iDisplayLength,
+ order: $.extend( true, [], settings.aaSorting ),
+ search: _fnSearchToCamel( settings.oPreviousSearch ),
+ columns: $.map( settings.aoColumns, function ( col, i ) {
+ return {
+ visible: col.bVisible,
+ search: _fnSearchToCamel( settings.aoPreSearchCols[i] )
+ };
+ } )
+ };
+
+ _fnCallbackFire( settings, "aoStateSaveParams", 'stateSaveParams', [settings, state] );
+
+ settings.oSavedState = state;
+ settings.fnStateSaveCallback.call( settings.oInstance, settings, state );
+ }
+
+
+ /**
+ * Attempt to load a saved table state
+ * @param {object} oSettings dataTables settings object
+ * @param {object} oInit DataTables init object so we can override settings
+ * @param {function} callback Callback to execute when the state has been loaded
+ * @memberof DataTable#oApi
+ */
+ function _fnLoadState ( settings, oInit, callback )
+ {
+ var i, ien;
+ var columns = settings.aoColumns;
+ var loaded = function ( s ) {
+ if ( ! s || ! s.time ) {
+ callback();
+ return;
+ }
+
+ // Allow custom and plug-in manipulation functions to alter the saved data set and
+ // cancelling of loading by returning false
+ var abStateLoad = _fnCallbackFire( settings, 'aoStateLoadParams', 'stateLoadParams', [settings, s] );
+ if ( $.inArray( false, abStateLoad ) !== -1 ) {
+ callback();
+ return;
+ }
+
+ // Reject old data
+ var duration = settings.iStateDuration;
+ if ( duration > 0 && s.time < +new Date() - (duration*1000) ) {
+ callback();
+ return;
+ }
+
+ // Number of columns have changed - all bets are off, no restore of settings
+ if ( s.columns && columns.length !== s.columns.length ) {
+ callback();
+ return;
+ }
+
+ // Store the saved state so it might be accessed at any time
+ settings.oLoadedState = $.extend( true, {}, s );
+
+ // Restore key features - todo - for 1.11 this needs to be done by
+ // subscribed events
+ if ( s.start !== undefined ) {
+ settings._iDisplayStart = s.start;
+ settings.iInitDisplayStart = s.start;
+ }
+ if ( s.length !== undefined ) {
+ settings._iDisplayLength = s.length;
+ }
+
+ // Order
+ if ( s.order !== undefined ) {
+ settings.aaSorting = [];
+ $.each( s.order, function ( i, col ) {
+ settings.aaSorting.push( col[0] >= columns.length ?
+ [ 0, col[1] ] :
+ col
+ );
+ } );
+ }
+
+ // Search
+ if ( s.search !== undefined ) {
+ $.extend( settings.oPreviousSearch, _fnSearchToHung( s.search ) );
+ }
+
+ // Columns
+ //
+ if ( s.columns ) {
+ for ( i=0, ien=s.columns.length ; i<ien ; i++ ) {
+ var col = s.columns[i];
+
+ // Visibility
+ if ( col.visible !== undefined ) {
+ columns[i].bVisible = col.visible;
+ }
+
+ // Search
+ if ( col.search !== undefined ) {
+ $.extend( settings.aoPreSearchCols[i], _fnSearchToHung( col.search ) );
+ }
+ }
+ }
+
+ _fnCallbackFire( settings, 'aoStateLoaded', 'stateLoaded', [settings, s] );
+ callback();
+ }
+
+ if ( ! settings.oFeatures.bStateSave ) {
+ callback();
+ return;
+ }
+
+ var state = settings.fnStateLoadCallback.call( settings.oInstance, settings, loaded );
+
+ if ( state !== undefined ) {
+ loaded( state );
+ }
+ // otherwise, wait for the loaded callback to be executed
+ }
+
+
+ /**
+ * Return the settings object for a particular table
+ * @param {node} table table we are using as a dataTable
+ * @returns {object} Settings object - or null if not found
+ * @memberof DataTable#oApi
+ */
+ function _fnSettingsFromNode ( table )
+ {
+ var settings = DataTable.settings;
+ var idx = $.inArray( table, _pluck( settings, 'nTable' ) );
+
+ return idx !== -1 ?
+ settings[ idx ] :
+ null;
+ }
+
+
+ /**
+ * Log an error message
+ * @param {object} settings dataTables settings object
+ * @param {int} level log error messages, or display them to the user
+ * @param {string} msg error message
+ * @param {int} tn Technical note id to get more information about the error.
+ * @memberof DataTable#oApi
+ */
+ function _fnLog( settings, level, msg, tn )
+ {
+ msg = 'DataTables warning: '+
+ (settings ? 'table id='+settings.sTableId+' - ' : '')+msg;
+
+ if ( tn ) {
+ msg += '. For more information about this error, please see '+
+ 'http://datatables.net/tn/'+tn;
+ }
+
+ if ( ! level ) {
+ // Backwards compatibility pre 1.10
+ var ext = DataTable.ext;
+ var type = ext.sErrMode || ext.errMode;
+
+ if ( settings ) {
+ _fnCallbackFire( settings, null, 'error', [ settings, tn, msg ] );
+ }
+
+ if ( type == 'alert' ) {
+ alert( msg );
+ }
+ else if ( type == 'throw' ) {
+ throw new Error(msg);
+ }
+ else if ( typeof type == 'function' ) {
+ type( settings, tn, msg );
+ }
+ }
+ else if ( window.console && console.log ) {
+ console.log( msg );
+ }
+ }
+
+
+ /**
+ * See if a property is defined on one object, if so assign it to the other object
+ * @param {object} ret target object
+ * @param {object} src source object
+ * @param {string} name property
+ * @param {string} [mappedName] name to map too - optional, name used if not given
+ * @memberof DataTable#oApi
+ */
+ function _fnMap( ret, src, name, mappedName )
+ {
+ if ( $.isArray( name ) ) {
+ $.each( name, function (i, val) {
+ if ( $.isArray( val ) ) {
+ _fnMap( ret, src, val[0], val[1] );
+ }
+ else {
+ _fnMap( ret, src, val );
+ }
+ } );
+
+ return;
+ }
+
+ if ( mappedName === undefined ) {
+ mappedName = name;
+ }
+
+ if ( src[name] !== undefined ) {
+ ret[mappedName] = src[name];
+ }
+ }
+
+
+ /**
+ * Extend objects - very similar to jQuery.extend, but deep copy objects, and
+ * shallow copy arrays. The reason we need to do this, is that we don't want to
+ * deep copy array init values (such as aaSorting) since the dev wouldn't be
+ * able to override them, but we do want to deep copy arrays.
+ * @param {object} out Object to extend
+ * @param {object} extender Object from which the properties will be applied to
+ * out
+ * @param {boolean} breakRefs If true, then arrays will be sliced to take an
+ * independent copy with the exception of the `data` or `aaData` parameters
+ * if they are present. This is so you can pass in a collection to
+ * DataTables and have that used as your data source without breaking the
+ * references
+ * @returns {object} out Reference, just for convenience - out === the return.
+ * @memberof DataTable#oApi
+ * @todo This doesn't take account of arrays inside the deep copied objects.
+ */
+ function _fnExtend( out, extender, breakRefs )
+ {
+ var val;
+
+ for ( var prop in extender ) {
+ if ( extender.hasOwnProperty(prop) ) {
+ val = extender[prop];
+
+ if ( $.isPlainObject( val ) ) {
+ if ( ! $.isPlainObject( out[prop] ) ) {
+ out[prop] = {};
+ }
+ $.extend( true, out[prop], val );
+ }
+ else if ( breakRefs && prop !== 'data' && prop !== 'aaData' && $.isArray(val) ) {
+ out[prop] = val.slice();
+ }
+ else {
+ out[prop] = val;
+ }
+ }
+ }
+
+ return out;
+ }
+
+
+ /**
+ * Bind an event handers to allow a click or return key to activate the callback.
+ * This is good for accessibility since a return on the keyboard will have the
+ * same effect as a click, if the element has focus.
+ * @param {element} n Element to bind the action to
+ * @param {object} oData Data object to pass to the triggered function
+ * @param {function} fn Callback function for when the event is triggered
+ * @memberof DataTable#oApi
+ */
+ function _fnBindAction( n, oData, fn )
+ {
+ $(n)
+ .on( 'click.DT', oData, function (e) {
+ n.blur(); // Remove focus outline for mouse users
+ fn(e);
+ } )
+ .on( 'keypress.DT', oData, function (e){
+ if ( e.which === 13 ) {
+ e.preventDefault();
+ fn(e);
+ }
+ } )
+ .on( 'selectstart.DT', function () {
+ /* Take the brutal approach to cancelling text selection */
+ return false;
+ } );
+ }
+
+
+ /**
+ * Register a callback function. Easily allows a callback function to be added to
+ * an array store of callback functions that can then all be called together.
+ * @param {object} oSettings dataTables settings object
+ * @param {string} sStore Name of the array storage for the callbacks in oSettings
+ * @param {function} fn Function to be called back
+ * @param {string} sName Identifying name for the callback (i.e. a label)
+ * @memberof DataTable#oApi
+ */
+ function _fnCallbackReg( oSettings, sStore, fn, sName )
+ {
+ if ( fn )
+ {
+ oSettings[sStore].push( {
+ "fn": fn,
+ "sName": sName
+ } );
+ }
+ }
+
+
+ /**
+ * Fire callback functions and trigger events. Note that the loop over the
+ * callback array store is done backwards! Further note that you do not want to
+ * fire off triggers in time sensitive applications (for example cell creation)
+ * as its slow.
+ * @param {object} settings dataTables settings object
+ * @param {string} callbackArr Name of the array storage for the callbacks in
+ * oSettings
+ * @param {string} eventName Name of the jQuery custom event to trigger. If
+ * null no trigger is fired
+ * @param {array} args Array of arguments to pass to the callback function /
+ * trigger
+ * @memberof DataTable#oApi
+ */
+ function _fnCallbackFire( settings, callbackArr, eventName, args )
+ {
+ var ret = [];
+
+ if ( callbackArr ) {
+ ret = $.map( settings[callbackArr].slice().reverse(), function (val, i) {
+ return val.fn.apply( settings.oInstance, args );
+ } );
+ }
+
+ if ( eventName !== null ) {
+ var e = $.Event( eventName+'.dt' );
+
+ $(settings.nTable).trigger( e, args );
+
+ ret.push( e.result );
+ }
+
+ return ret;
+ }
+
+
+ function _fnLengthOverflow ( settings )
+ {
+ var
+ start = settings._iDisplayStart,
+ end = settings.fnDisplayEnd(),
+ len = settings._iDisplayLength;
+
+ /* If we have space to show extra rows (backing up from the end point - then do so */
+ if ( start >= end )
+ {
+ start = end - len;
+ }
+
+ // Keep the start record on the current page
+ start -= (start % len);
+
+ if ( len === -1 || start < 0 )
+ {
+ start = 0;
+ }
+
+ settings._iDisplayStart = start;
+ }
+
+
+ function _fnRenderer( settings, type )
+ {
+ var renderer = settings.renderer;
+ var host = DataTable.ext.renderer[type];
+
+ if ( $.isPlainObject( renderer ) && renderer[type] ) {
+ // Specific renderer for this type. If available use it, otherwise use
+ // the default.
+ return host[renderer[type]] || host._;
+ }
+ else if ( typeof renderer === 'string' ) {
+ // Common renderer - if there is one available for this type use it,
+ // otherwise use the default
+ return host[renderer] || host._;
+ }
+
+ // Use the default
+ return host._;
+ }
+
+
+ /**
+ * Detect the data source being used for the table. Used to simplify the code
+ * a little (ajax) and to make it compress a little smaller.
+ *
+ * @param {object} settings dataTables settings object
+ * @returns {string} Data source
+ * @memberof DataTable#oApi
+ */
+ function _fnDataSource ( settings )
+ {
+ if ( settings.oFeatures.bServerSide ) {
+ return 'ssp';
+ }
+ else if ( settings.ajax || settings.sAjaxSource ) {
+ return 'ajax';
+ }
+ return 'dom';
+ }
+
+
+
+
+ /**
+ * Computed structure of the DataTables API, defined by the options passed to
+ * `DataTable.Api.register()` when building the API.
+ *
+ * The structure is built in order to speed creation and extension of the Api
+ * objects since the extensions are effectively pre-parsed.
+ *
+ * The array is an array of objects with the following structure, where this
+ * base array represents the Api prototype base:
+ *
+ * [
+ * {
+ * name: 'data' -- string - Property name
+ * val: function () {}, -- function - Api method (or undefined if just an object
+ * methodExt: [ ... ], -- array - Array of Api object definitions to extend the method result
+ * propExt: [ ... ] -- array - Array of Api object definitions to extend the property
+ * },
+ * {
+ * name: 'row'
+ * val: {},
+ * methodExt: [ ... ],
+ * propExt: [
+ * {
+ * name: 'data'
+ * val: function () {},
+ * methodExt: [ ... ],
+ * propExt: [ ... ]
+ * },
+ * ...
+ * ]
+ * }
+ * ]
+ *
+ * @type {Array}
+ * @ignore
+ */
+ var __apiStruct = [];
+
+
+ /**
+ * `Array.prototype` reference.
+ *
+ * @type object
+ * @ignore
+ */
+ var __arrayProto = Array.prototype;
+
+
+ /**
+ * Abstraction for `context` parameter of the `Api` constructor to allow it to
+ * take several different forms for ease of use.
+ *
+ * Each of the input parameter types will be converted to a DataTables settings
+ * object where possible.
+ *
+ * @param {string|node|jQuery|object} mixed DataTable identifier. Can be one
+ * of:
+ *
+ * * `string` - jQuery selector. Any DataTables' matching the given selector
+ * with be found and used.
+ * * `node` - `TABLE` node which has already been formed into a DataTable.
+ * * `jQuery` - A jQuery object of `TABLE` nodes.
+ * * `object` - DataTables settings object
+ * * `DataTables.Api` - API instance
+ * @return {array|null} Matching DataTables settings objects. `null` or
+ * `undefined` is returned if no matching DataTable is found.
+ * @ignore
+ */
+ var _toSettings = function ( mixed )
+ {
+ var idx, jq;
+ var settings = DataTable.settings;
+ var tables = $.map( settings, function (el, i) {
+ return el.nTable;
+ } );
+
+ if ( ! mixed ) {
+ return [];
+ }
+ else if ( mixed.nTable && mixed.oApi ) {
+ // DataTables settings object
+ return [ mixed ];
+ }
+ else if ( mixed.nodeName && mixed.nodeName.toLowerCase() === 'table' ) {
+ // Table node
+ idx = $.inArray( mixed, tables );
+ return idx !== -1 ? [ settings[idx] ] : null;
+ }
+ else if ( mixed && typeof mixed.settings === 'function' ) {
+ return mixed.settings().toArray();
+ }
+ else if ( typeof mixed === 'string' ) {
+ // jQuery selector
+ jq = $(mixed);
+ }
+ else if ( mixed instanceof $ ) {
+ // jQuery object (also DataTables instance)
+ jq = mixed;
+ }
+
+ if ( jq ) {
+ return jq.map( function(i) {
+ idx = $.inArray( this, tables );
+ return idx !== -1 ? settings[idx] : null;
+ } ).toArray();
+ }
+ };
+
+
+ /**
+ * DataTables API class - used to control and interface with one or more
+ * DataTables enhanced tables.
+ *
+ * The API class is heavily based on jQuery, presenting a chainable interface
+ * that you can use to interact with tables. Each instance of the API class has
+ * a "context" - i.e. the tables that it will operate on. This could be a single
+ * table, all tables on a page or a sub-set thereof.
+ *
+ * Additionally the API is designed to allow you to easily work with the data in
+ * the tables, retrieving and manipulating it as required. This is done by
+ * presenting the API class as an array like interface. The contents of the
+ * array depend upon the actions requested by each method (for example
+ * `rows().nodes()` will return an array of nodes, while `rows().data()` will
+ * return an array of objects or arrays depending upon your table's
+ * configuration). The API object has a number of array like methods (`push`,
+ * `pop`, `reverse` etc) as well as additional helper methods (`each`, `pluck`,
+ * `unique` etc) to assist your working with the data held in a table.
+ *
+ * Most methods (those which return an Api instance) are chainable, which means
+ * the return from a method call also has all of the methods available that the
+ * top level object had. For example, these two calls are equivalent:
+ *
+ * // Not chained
+ * api.row.add( {...} );
+ * api.draw();
+ *
+ * // Chained
+ * api.row.add( {...} ).draw();
+ *
+ * @class DataTable.Api
+ * @param {array|object|string|jQuery} context DataTable identifier. This is
+ * used to define which DataTables enhanced tables this API will operate on.
+ * Can be one of:
+ *
+ * * `string` - jQuery selector. Any DataTables' matching the given selector
+ * with be found and used.
+ * * `node` - `TABLE` node which has already been formed into a DataTable.
+ * * `jQuery` - A jQuery object of `TABLE` nodes.
+ * * `object` - DataTables settings object
+ * @param {array} [data] Data to initialise the Api instance with.
+ *
+ * @example
+ * // Direct initialisation during DataTables construction
+ * var api = $('#example').DataTable();
+ *
+ * @example
+ * // Initialisation using a DataTables jQuery object
+ * var api = $('#example').dataTable().api();
+ *
+ * @example
+ * // Initialisation as a constructor
+ * var api = new $.fn.DataTable.Api( 'table.dataTable' );
+ */
+ _Api = function ( context, data )
+ {
+ if ( ! (this instanceof _Api) ) {
+ return new _Api( context, data );
+ }
+
+ var settings = [];
+ var ctxSettings = function ( o ) {
+ var a = _toSettings( o );
+ if ( a ) {
+ settings = settings.concat( a );
+ }
+ };
+
+ if ( $.isArray( context ) ) {
+ for ( var i=0, ien=context.length ; i<ien ; i++ ) {
+ ctxSettings( context[i] );
+ }
+ }
+ else {
+ ctxSettings( context );
+ }
+
+ // Remove duplicates
+ this.context = _unique( settings );
+
+ // Initial data
+ if ( data ) {
+ $.merge( this, data );
+ }
+
+ // selector
+ this.selector = {
+ rows: null,
+ cols: null,
+ opts: null
+ };
+
+ _Api.extend( this, this, __apiStruct );
+ };
+
+ DataTable.Api = _Api;
+
+ // Don't destroy the existing prototype, just extend it. Required for jQuery 2's
+ // isPlainObject.
+ $.extend( _Api.prototype, {
+ any: function ()
+ {
+ return this.count() !== 0;
+ },
+
+
+ concat: __arrayProto.concat,
+
+
+ context: [], // array of table settings objects
+
+
+ count: function ()
+ {
+ return this.flatten().length;
+ },
+
+
+ each: function ( fn )
+ {
+ for ( var i=0, ien=this.length ; i<ien; i++ ) {
+ fn.call( this, this[i], i, this );
+ }
+
+ return this;
+ },
+
+
+ eq: function ( idx )
+ {
+ var ctx = this.context;
+
+ return ctx.length > idx ?
+ new _Api( ctx[idx], this[idx] ) :
+ null;
+ },
+
+
+ filter: function ( fn )
+ {
+ var a = [];
+
+ if ( __arrayProto.filter ) {
+ a = __arrayProto.filter.call( this, fn, this );
+ }
+ else {
+ // Compatibility for browsers without EMCA-252-5 (JS 1.6)
+ for ( var i=0, ien=this.length ; i<ien ; i++ ) {
+ if ( fn.call( this, this[i], i, this ) ) {
+ a.push( this[i] );
+ }
+ }
+ }
+
+ return new _Api( this.context, a );
+ },
+
+
+ flatten: function ()
+ {
+ var a = [];
+ return new _Api( this.context, a.concat.apply( a, this.toArray() ) );
+ },
+
+
+ join: __arrayProto.join,
+
+
+ indexOf: __arrayProto.indexOf || function (obj, start)
+ {
+ for ( var i=(start || 0), ien=this.length ; i<ien ; i++ ) {
+ if ( this[i] === obj ) {
+ return i;
+ }
+ }
+ return -1;
+ },
+
+ iterator: function ( flatten, type, fn, alwaysNew ) {
+ var
+ a = [], ret,
+ i, ien, j, jen,
+ context = this.context,
+ rows, items, item,
+ selector = this.selector;
+
+ // Argument shifting
+ if ( typeof flatten === 'string' ) {
+ alwaysNew = fn;
+ fn = type;
+ type = flatten;
+ flatten = false;
+ }
+
+ for ( i=0, ien=context.length ; i<ien ; i++ ) {
+ var apiInst = new _Api( context[i] );
+
+ if ( type === 'table' ) {
+ ret = fn.call( apiInst, context[i], i );
+
+ if ( ret !== undefined ) {
+ a.push( ret );
+ }
+ }
+ else if ( type === 'columns' || type === 'rows' ) {
+ // this has same length as context - one entry for each table
+ ret = fn.call( apiInst, context[i], this[i], i );
+
+ if ( ret !== undefined ) {
+ a.push( ret );
+ }
+ }
+ else if ( type === 'column' || type === 'column-rows' || type === 'row' || type === 'cell' ) {
+ // columns and rows share the same structure.
+ // 'this' is an array of column indexes for each context
+ items = this[i];
+
+ if ( type === 'column-rows' ) {
+ rows = _selector_row_indexes( context[i], selector.opts );
+ }
+
+ for ( j=0, jen=items.length ; j<jen ; j++ ) {
+ item = items[j];
+
+ if ( type === 'cell' ) {
+ ret = fn.call( apiInst, context[i], item.row, item.column, i, j );
+ }
+ else {
+ ret = fn.call( apiInst, context[i], item, i, j, rows );
+ }
+
+ if ( ret !== undefined ) {
+ a.push( ret );
+ }
+ }
+ }
+ }
+
+ if ( a.length || alwaysNew ) {
+ var api = new _Api( context, flatten ? a.concat.apply( [], a ) : a );
+ var apiSelector = api.selector;
+ apiSelector.rows = selector.rows;
+ apiSelector.cols = selector.cols;
+ apiSelector.opts = selector.opts;
+ return api;
+ }
+ return this;
+ },
+
+
+ lastIndexOf: __arrayProto.lastIndexOf || function (obj, start)
+ {
+ // Bit cheeky...
+ return this.indexOf.apply( this.toArray.reverse(), arguments );
+ },
+
+
+ length: 0,
+
+
+ map: function ( fn )
+ {
+ var a = [];
+
+ if ( __arrayProto.map ) {
+ a = __arrayProto.map.call( this, fn, this );
+ }
+ else {
+ // Compatibility for browsers without EMCA-252-5 (JS 1.6)
+ for ( var i=0, ien=this.length ; i<ien ; i++ ) {
+ a.push( fn.call( this, this[i], i ) );
+ }
+ }
+
+ return new _Api( this.context, a );
+ },
+
+
+ pluck: function ( prop )
+ {
+ return this.map( function ( el ) {
+ return el[ prop ];
+ } );
+ },
+
+ pop: __arrayProto.pop,
+
+
+ push: __arrayProto.push,
+
+
+ // Does not return an API instance
+ reduce: __arrayProto.reduce || function ( fn, init )
+ {
+ return _fnReduce( this, fn, init, 0, this.length, 1 );
+ },
+
+
+ reduceRight: __arrayProto.reduceRight || function ( fn, init )
+ {
+ return _fnReduce( this, fn, init, this.length-1, -1, -1 );
+ },
+
+
+ reverse: __arrayProto.reverse,
+
+
+ // Object with rows, columns and opts
+ selector: null,
+
+
+ shift: __arrayProto.shift,
+
+
+ slice: function () {
+ return new _Api( this.context, this );
+ },
+
+
+ sort: __arrayProto.sort, // ? name - order?
+
+
+ splice: __arrayProto.splice,
+
+
+ toArray: function ()
+ {
+ return __arrayProto.slice.call( this );
+ },
+
+
+ to$: function ()
+ {
+ return $( this );
+ },
+
+
+ toJQuery: function ()
+ {
+ return $( this );
+ },
+
+
+ unique: function ()
+ {
+ return new _Api( this.context, _unique(this) );
+ },
+
+
+ unshift: __arrayProto.unshift
+ } );
+
+
+ _Api.extend = function ( scope, obj, ext )
+ {
+ // Only extend API instances and static properties of the API
+ if ( ! ext.length || ! obj || ( ! (obj instanceof _Api) && ! obj.__dt_wrapper ) ) {
+ return;
+ }
+
+ var
+ i, ien,
+ j, jen,
+ struct, inner,
+ methodScoping = function ( scope, fn, struc ) {
+ return function () {
+ var ret = fn.apply( scope, arguments );
+
+ // Method extension
+ _Api.extend( ret, ret, struc.methodExt );
+ return ret;
+ };
+ };
+
+ for ( i=0, ien=ext.length ; i<ien ; i++ ) {
+ struct = ext[i];
+
+ // Value
+ obj[ struct.name ] = typeof struct.val === 'function' ?
+ methodScoping( scope, struct.val, struct ) :
+ $.isPlainObject( struct.val ) ?
+ {} :
+ struct.val;
+
+ obj[ struct.name ].__dt_wrapper = true;
+
+ // Property extension
+ _Api.extend( scope, obj[ struct.name ], struct.propExt );
+ }
+ };
+
+
+ // @todo - Is there need for an augment function?
+ // _Api.augment = function ( inst, name )
+ // {
+ // // Find src object in the structure from the name
+ // var parts = name.split('.');
+
+ // _Api.extend( inst, obj );
+ // };
+
+
+ // [
+ // {
+ // name: 'data' -- string - Property name
+ // val: function () {}, -- function - Api method (or undefined if just an object
+ // methodExt: [ ... ], -- array - Array of Api object definitions to extend the method result
+ // propExt: [ ... ] -- array - Array of Api object definitions to extend the property
+ // },
+ // {
+ // name: 'row'
+ // val: {},
+ // methodExt: [ ... ],
+ // propExt: [
+ // {
+ // name: 'data'
+ // val: function () {},
+ // methodExt: [ ... ],
+ // propExt: [ ... ]
+ // },
+ // ...
+ // ]
+ // }
+ // ]
+
+ _Api.register = _api_register = function ( name, val )
+ {
+ if ( $.isArray( name ) ) {
+ for ( var j=0, jen=name.length ; j<jen ; j++ ) {
+ _Api.register( name[j], val );
+ }
+ return;
+ }
+
+ var
+ i, ien,
+ heir = name.split('.'),
+ struct = __apiStruct,
+ key, method;
+
+ var find = function ( src, name ) {
+ for ( var i=0, ien=src.length ; i<ien ; i++ ) {
+ if ( src[i].name === name ) {
+ return src[i];
+ }
+ }
+ return null;
+ };
+
+ for ( i=0, ien=heir.length ; i<ien ; i++ ) {
+ method = heir[i].indexOf('()') !== -1;
+ key = method ?
+ heir[i].replace('()', '') :
+ heir[i];
+
+ var src = find( struct, key );
+ if ( ! src ) {
+ src = {
+ name: key,
+ val: {},
+ methodExt: [],
+ propExt: []
+ };
+ struct.push( src );
+ }
+
+ if ( i === ien-1 ) {
+ src.val = val;
+ }
+ else {
+ struct = method ?
+ src.methodExt :
+ src.propExt;
+ }
+ }
+ };
+
+
+ _Api.registerPlural = _api_registerPlural = function ( pluralName, singularName, val ) {
+ _Api.register( pluralName, val );
+
+ _Api.register( singularName, function () {
+ var ret = val.apply( this, arguments );
+
+ if ( ret === this ) {
+ // Returned item is the API instance that was passed in, return it
+ return this;
+ }
+ else if ( ret instanceof _Api ) {
+ // New API instance returned, want the value from the first item
+ // in the returned array for the singular result.
+ return ret.length ?
+ $.isArray( ret[0] ) ?
+ new _Api( ret.context, ret[0] ) : // Array results are 'enhanced'
+ ret[0] :
+ undefined;
+ }
+
+ // Non-API return - just fire it back
+ return ret;
+ } );
+ };
+
+
+ /**
+ * Selector for HTML tables. Apply the given selector to the give array of
+ * DataTables settings objects.
+ *
+ * @param {string|integer} [selector] jQuery selector string or integer
+ * @param {array} Array of DataTables settings objects to be filtered
+ * @return {array}
+ * @ignore
+ */
+ var __table_selector = function ( selector, a )
+ {
+ // Integer is used to pick out a table by index
+ if ( typeof selector === 'number' ) {
+ return [ a[ selector ] ];
+ }
+
+ // Perform a jQuery selector on the table nodes
+ var nodes = $.map( a, function (el, i) {
+ return el.nTable;
+ } );
+
+ return $(nodes)
+ .filter( selector )
+ .map( function (i) {
+ // Need to translate back from the table node to the settings
+ var idx = $.inArray( this, nodes );
+ return a[ idx ];
+ } )
+ .toArray();
+ };
+
+
+
+ /**
+ * Context selector for the API's context (i.e. the tables the API instance
+ * refers to.
+ *
+ * @name DataTable.Api#tables
+ * @param {string|integer} [selector] Selector to pick which tables the iterator
+ * should operate on. If not given, all tables in the current context are
+ * used. This can be given as a jQuery selector (for example `':gt(0)'`) to
+ * select multiple tables or as an integer to select a single table.
+ * @returns {DataTable.Api} Returns a new API instance if a selector is given.
+ */
+ _api_register( 'tables()', function ( selector ) {
+ // A new instance is created if there was a selector specified
+ return selector ?
+ new _Api( __table_selector( selector, this.context ) ) :
+ this;
+ } );
+
+
+ _api_register( 'table()', function ( selector ) {
+ var tables = this.tables( selector );
+ var ctx = tables.context;
+
+ // Truncate to the first matched table
+ return ctx.length ?
+ new _Api( ctx[0] ) :
+ tables;
+ } );
+
+
+ _api_registerPlural( 'tables().nodes()', 'table().node()' , function () {
+ return this.iterator( 'table', function ( ctx ) {
+ return ctx.nTable;
+ }, 1 );
+ } );
+
+
+ _api_registerPlural( 'tables().body()', 'table().body()' , function () {
+ return this.iterator( 'table', function ( ctx ) {
+ return ctx.nTBody;
+ }, 1 );
+ } );
+
+
+ _api_registerPlural( 'tables().header()', 'table().header()' , function () {
+ return this.iterator( 'table', function ( ctx ) {
+ return ctx.nTHead;
+ }, 1 );
+ } );
+
+
+ _api_registerPlural( 'tables().footer()', 'table().footer()' , function () {
+ return this.iterator( 'table', function ( ctx ) {
+ return ctx.nTFoot;
+ }, 1 );
+ } );
+
+
+ _api_registerPlural( 'tables().containers()', 'table().container()' , function () {
+ return this.iterator( 'table', function ( ctx ) {
+ return ctx.nTableWrapper;
+ }, 1 );
+ } );
+
+
+
+ /**
+ * Redraw the tables in the current context.
+ */
+ _api_register( 'draw()', function ( paging ) {
+ return this.iterator( 'table', function ( settings ) {
+ if ( paging === 'page' ) {
+ _fnDraw( settings );
+ }
+ else {
+ if ( typeof paging === 'string' ) {
+ paging = paging === 'full-hold' ?
+ false :
+ true;
+ }
+
+ _fnReDraw( settings, paging===false );
+ }
+ } );
+ } );
+
+
+
+ /**
+ * Get the current page index.
+ *
+ * @return {integer} Current page index (zero based)
+ *//**
+ * Set the current page.
+ *
+ * Note that if you attempt to show a page which does not exist, DataTables will
+ * not throw an error, but rather reset the paging.
+ *
+ * @param {integer|string} action The paging action to take. This can be one of:
+ * * `integer` - The page index to jump to
+ * * `string` - An action to take:
+ * * `first` - Jump to first page.
+ * * `next` - Jump to the next page
+ * * `previous` - Jump to previous page
+ * * `last` - Jump to the last page.
+ * @returns {DataTables.Api} this
+ */
+ _api_register( 'page()', function ( action ) {
+ if ( action === undefined ) {
+ return this.page.info().page; // not an expensive call
+ }
+
+ // else, have an action to take on all tables
+ return this.iterator( 'table', function ( settings ) {
+ _fnPageChange( settings, action );
+ } );
+ } );
+
+
+ /**
+ * Paging information for the first table in the current context.
+ *
+ * If you require paging information for another table, use the `table()` method
+ * with a suitable selector.
+ *
+ * @return {object} Object with the following properties set:
+ * * `page` - Current page index (zero based - i.e. the first page is `0`)
+ * * `pages` - Total number of pages
+ * * `start` - Display index for the first record shown on the current page
+ * * `end` - Display index for the last record shown on the current page
+ * * `length` - Display length (number of records). Note that generally `start
+ * + length = end`, but this is not always true, for example if there are
+ * only 2 records to show on the final page, with a length of 10.
+ * * `recordsTotal` - Full data set length
+ * * `recordsDisplay` - Data set length once the current filtering criterion
+ * are applied.
+ */
+ _api_register( 'page.info()', function ( action ) {
+ if ( this.context.length === 0 ) {
+ return undefined;
+ }
+
+ var
+ settings = this.context[0],
+ start = settings._iDisplayStart,
+ len = settings.oFeatures.bPaginate ? settings._iDisplayLength : -1,
+ visRecords = settings.fnRecordsDisplay(),
+ all = len === -1;
+
+ return {
+ "page": all ? 0 : Math.floor( start / len ),
+ "pages": all ? 1 : Math.ceil( visRecords / len ),
+ "start": start,
+ "end": settings.fnDisplayEnd(),
+ "length": len,
+ "recordsTotal": settings.fnRecordsTotal(),
+ "recordsDisplay": visRecords,
+ "serverSide": _fnDataSource( settings ) === 'ssp'
+ };
+ } );
+
+
+ /**
+ * Get the current page length.
+ *
+ * @return {integer} Current page length. Note `-1` indicates that all records
+ * are to be shown.
+ *//**
+ * Set the current page length.
+ *
+ * @param {integer} Page length to set. Use `-1` to show all records.
+ * @returns {DataTables.Api} this
+ */
+ _api_register( 'page.len()', function ( len ) {
+ // Note that we can't call this function 'length()' because `length`
+ // is a Javascript property of functions which defines how many arguments
+ // the function expects.
+ if ( len === undefined ) {
+ return this.context.length !== 0 ?
+ this.context[0]._iDisplayLength :
+ undefined;
+ }
+
+ // else, set the page length
+ return this.iterator( 'table', function ( settings ) {
+ _fnLengthChange( settings, len );
+ } );
+ } );
+
+
+
+ var __reload = function ( settings, holdPosition, callback ) {
+ // Use the draw event to trigger a callback
+ if ( callback ) {
+ var api = new _Api( settings );
+
+ api.one( 'draw', function () {
+ callback( api.ajax.json() );
+ } );
+ }
+
+ if ( _fnDataSource( settings ) == 'ssp' ) {
+ _fnReDraw( settings, holdPosition );
+ }
+ else {
+ _fnProcessingDisplay( settings, true );
+
+ // Cancel an existing request
+ var xhr = settings.jqXHR;
+ if ( xhr && xhr.readyState !== 4 ) {
+ xhr.abort();
+ }
+
+ // Trigger xhr
+ _fnBuildAjax( settings, [], function( json ) {
+ _fnClearTable( settings );
+
+ var data = _fnAjaxDataSrc( settings, json );
+ for ( var i=0, ien=data.length ; i<ien ; i++ ) {
+ _fnAddData( settings, data[i] );
+ }
+
+ _fnReDraw( settings, holdPosition );
+ _fnProcessingDisplay( settings, false );
+ } );
+ }
+ };
+
+
+ /**
+ * Get the JSON response from the last Ajax request that DataTables made to the
+ * server. Note that this returns the JSON from the first table in the current
+ * context.
+ *
+ * @return {object} JSON received from the server.
+ */
+ _api_register( 'ajax.json()', function () {
+ var ctx = this.context;
+
+ if ( ctx.length > 0 ) {
+ return ctx[0].json;
+ }
+
+ // else return undefined;
+ } );
+
+
+ /**
+ * Get the data submitted in the last Ajax request
+ */
+ _api_register( 'ajax.params()', function () {
+ var ctx = this.context;
+
+ if ( ctx.length > 0 ) {
+ return ctx[0].oAjaxData;
+ }
+
+ // else return undefined;
+ } );
+
+
+ /**
+ * Reload tables from the Ajax data source. Note that this function will
+ * automatically re-draw the table when the remote data has been loaded.
+ *
+ * @param {boolean} [reset=true] Reset (default) or hold the current paging
+ * position. A full re-sort and re-filter is performed when this method is
+ * called, which is why the pagination reset is the default action.
+ * @returns {DataTables.Api} this
+ */
+ _api_register( 'ajax.reload()', function ( callback, resetPaging ) {
+ return this.iterator( 'table', function (settings) {
+ __reload( settings, resetPaging===false, callback );
+ } );
+ } );
+
+
+ /**
+ * Get the current Ajax URL. Note that this returns the URL from the first
+ * table in the current context.
+ *
+ * @return {string} Current Ajax source URL
+ *//**
+ * Set the Ajax URL. Note that this will set the URL for all tables in the
+ * current context.
+ *
+ * @param {string} url URL to set.
+ * @returns {DataTables.Api} this
+ */
+ _api_register( 'ajax.url()', function ( url ) {
+ var ctx = this.context;
+
+ if ( url === undefined ) {
+ // get
+ if ( ctx.length === 0 ) {
+ return undefined;
+ }
+ ctx = ctx[0];
+
+ return ctx.ajax ?
+ $.isPlainObject( ctx.ajax ) ?
+ ctx.ajax.url :
+ ctx.ajax :
+ ctx.sAjaxSource;
+ }
+
+ // set
+ return this.iterator( 'table', function ( settings ) {
+ if ( $.isPlainObject( settings.ajax ) ) {
+ settings.ajax.url = url;
+ }
+ else {
+ settings.ajax = url;
+ }
+ // No need to consider sAjaxSource here since DataTables gives priority
+ // to `ajax` over `sAjaxSource`. So setting `ajax` here, renders any
+ // value of `sAjaxSource` redundant.
+ } );
+ } );
+
+
+ /**
+ * Load data from the newly set Ajax URL. Note that this method is only
+ * available when `ajax.url()` is used to set a URL. Additionally, this method
+ * has the same effect as calling `ajax.reload()` but is provided for
+ * convenience when setting a new URL. Like `ajax.reload()` it will
+ * automatically redraw the table once the remote data has been loaded.
+ *
+ * @returns {DataTables.Api} this
+ */
+ _api_register( 'ajax.url().load()', function ( callback, resetPaging ) {
+ // Same as a reload, but makes sense to present it for easy access after a
+ // url change
+ return this.iterator( 'table', function ( ctx ) {
+ __reload( ctx, resetPaging===false, callback );
+ } );
+ } );
+
+
+
+
+ var _selector_run = function ( type, selector, selectFn, settings, opts )
+ {
+ var
+ out = [], res,
+ a, i, ien, j, jen,
+ selectorType = typeof selector;
+
+ // Can't just check for isArray here, as an API or jQuery instance might be
+ // given with their array like look
+ if ( ! selector || selectorType === 'string' || selectorType === 'function' || selector.length === undefined ) {
+ selector = [ selector ];
+ }
+
+ for ( i=0, ien=selector.length ; i<ien ; i++ ) {
+ // Only split on simple strings - complex expressions will be jQuery selectors
+ a = selector[i] && selector[i].split && ! selector[i].match(/[\[\(:]/) ?
+ selector[i].split(',') :
+ [ selector[i] ];
+
+ for ( j=0, jen=a.length ; j<jen ; j++ ) {
+ res = selectFn( typeof a[j] === 'string' ? $.trim(a[j]) : a[j] );
+
+ if ( res && res.length ) {
+ out = out.concat( res );
+ }
+ }
+ }
+
+ // selector extensions
+ var ext = _ext.selector[ type ];
+ if ( ext.length ) {
+ for ( i=0, ien=ext.length ; i<ien ; i++ ) {
+ out = ext[i]( settings, opts, out );
+ }
+ }
+
+ return _unique( out );
+ };
+
+
+ var _selector_opts = function ( opts )
+ {
+ if ( ! opts ) {
+ opts = {};
+ }
+
+ // Backwards compatibility for 1.9- which used the terminology filter rather
+ // than search
+ if ( opts.filter && opts.search === undefined ) {
+ opts.search = opts.filter;
+ }
+
+ return $.extend( {
+ search: 'none',
+ order: 'current',
+ page: 'all'
+ }, opts );
+ };
+
+
+ var _selector_first = function ( inst )
+ {
+ // Reduce the API instance to the first item found
+ for ( var i=0, ien=inst.length ; i<ien ; i++ ) {
+ if ( inst[i].length > 0 ) {
+ // Assign the first element to the first item in the instance
+ // and truncate the instance and context
+ inst[0] = inst[i];
+ inst[0].length = 1;
+ inst.length = 1;
+ inst.context = [ inst.context[i] ];
+
+ return inst;
+ }
+ }
+
+ // Not found - return an empty instance
+ inst.length = 0;
+ return inst;
+ };
+
+
+ var _selector_row_indexes = function ( settings, opts )
+ {
+ var
+ i, ien, tmp, a=[],
+ displayFiltered = settings.aiDisplay,
+ displayMaster = settings.aiDisplayMaster;
+
+ var
+ search = opts.search, // none, applied, removed
+ order = opts.order, // applied, current, index (original - compatibility with 1.9)
+ page = opts.page; // all, current
+
+ if ( _fnDataSource( settings ) == 'ssp' ) {
+ // In server-side processing mode, most options are irrelevant since
+ // rows not shown don't exist and the index order is the applied order
+ // Removed is a special case - for consistency just return an empty
+ // array
+ return search === 'removed' ?
+ [] :
+ _range( 0, displayMaster.length );
+ }
+ else if ( page == 'current' ) {
+ // Current page implies that order=current and fitler=applied, since it is
+ // fairly senseless otherwise, regardless of what order and search actually
+ // are
+ for ( i=settings._iDisplayStart, ien=settings.fnDisplayEnd() ; i<ien ; i++ ) {
+ a.push( displayFiltered[i] );
+ }
+ }
+ else if ( order == 'current' || order == 'applied' ) {
+ a = search == 'none' ?
+ displayMaster.slice() : // no search
+ search == 'applied' ?
+ displayFiltered.slice() : // applied search
+ $.map( displayMaster, function (el, i) { // removed search
+ return $.inArray( el, displayFiltered ) === -1 ? el : null;
+ } );
+ }
+ else if ( order == 'index' || order == 'original' ) {
+ for ( i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
+ if ( search == 'none' ) {
+ a.push( i );
+ }
+ else { // applied | removed
+ tmp = $.inArray( i, displayFiltered );
+
+ if ((tmp === -1 && search == 'removed') ||
+ (tmp >= 0 && search == 'applied') )
+ {
+ a.push( i );
+ }
+ }
+ }
+ }
+
+ return a;
+ };
+
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Rows
+ *
+ * {} - no selector - use all available rows
+ * {integer} - row aoData index
+ * {node} - TR node
+ * {string} - jQuery selector to apply to the TR elements
+ * {array} - jQuery array of nodes, or simply an array of TR nodes
+ *
+ */
+
+
+ var __row_selector = function ( settings, selector, opts )
+ {
+ var rows;
+ var run = function ( sel ) {
+ var selInt = _intVal( sel );
+ var i, ien;
+
+ // Short cut - selector is a number and no options provided (default is
+ // all records, so no need to check if the index is in there, since it
+ // must be - dev error if the index doesn't exist).
+ if ( selInt !== null && ! opts ) {
+ return [ selInt ];
+ }
+
+ if ( ! rows ) {
+ rows = _selector_row_indexes( settings, opts );
+ }
+
+ if ( selInt !== null && $.inArray( selInt, rows ) !== -1 ) {
+ // Selector - integer
+ return [ selInt ];
+ }
+ else if ( sel === null || sel === undefined || sel === '' ) {
+ // Selector - none
+ return rows;
+ }
+
+ // Selector - function
+ if ( typeof sel === 'function' ) {
+ return $.map( rows, function (idx) {
+ var row = settings.aoData[ idx ];
+ return sel( idx, row._aData, row.nTr ) ? idx : null;
+ } );
+ }
+
+ // Get nodes in the order from the `rows` array with null values removed
+ var nodes = _removeEmpty(
+ _pluck_order( settings.aoData, rows, 'nTr' )
+ );
+
+ // Selector - node
+ if ( sel.nodeName ) {
+ if ( sel._DT_RowIndex !== undefined ) {
+ return [ sel._DT_RowIndex ]; // Property added by DT for fast lookup
+ }
+ else if ( sel._DT_CellIndex ) {
+ return [ sel._DT_CellIndex.row ];
+ }
+ else {
+ var host = $(sel).closest('*[data-dt-row]');
+ return host.length ?
+ [ host.data('dt-row') ] :
+ [];
+ }
+ }
+
+ // ID selector. Want to always be able to select rows by id, regardless
+ // of if the tr element has been created or not, so can't rely upon
+ // jQuery here - hence a custom implementation. This does not match
+ // Sizzle's fast selector or HTML4 - in HTML5 the ID can be anything,
+ // but to select it using a CSS selector engine (like Sizzle or
+ // querySelect) it would need to need to be escaped for some characters.
+ // DataTables simplifies this for row selectors since you can select
+ // only a row. A # indicates an id any anything that follows is the id -
+ // unescaped.
+ if ( typeof sel === 'string' && sel.charAt(0) === '#' ) {
+ // get row index from id
+ var rowObj = settings.aIds[ sel.replace( /^#/, '' ) ];
+ if ( rowObj !== undefined ) {
+ return [ rowObj.idx ];
+ }
+
+ // need to fall through to jQuery in case there is DOM id that
+ // matches
+ }
+
+ // Selector - jQuery selector string, array of nodes or jQuery object/
+ // As jQuery's .filter() allows jQuery objects to be passed in filter,
+ // it also allows arrays, so this will cope with all three options
+ return $(nodes)
+ .filter( sel )
+ .map( function () {
+ return this._DT_RowIndex;
+ } )
+ .toArray();
+ };
+
+ return _selector_run( 'row', selector, run, settings, opts );
+ };
+
+
+ _api_register( 'rows()', function ( selector, opts ) {
+ // argument shifting
+ if ( selector === undefined ) {
+ selector = '';
+ }
+ else if ( $.isPlainObject( selector ) ) {
+ opts = selector;
+ selector = '';
+ }
+
+ opts = _selector_opts( opts );
+
+ var inst = this.iterator( 'table', function ( settings ) {
+ return __row_selector( settings, selector, opts );
+ }, 1 );
+
+ // Want argument shifting here and in __row_selector?
+ inst.selector.rows = selector;
+ inst.selector.opts = opts;
+
+ return inst;
+ } );
+
+ _api_register( 'rows().nodes()', function () {
+ return this.iterator( 'row', function ( settings, row ) {
+ return settings.aoData[ row ].nTr || undefined;
+ }, 1 );
+ } );
+
+ _api_register( 'rows().data()', function () {
+ return this.iterator( true, 'rows', function ( settings, rows ) {
+ return _pluck_order( settings.aoData, rows, '_aData' );
+ }, 1 );
+ } );
+
+ _api_registerPlural( 'rows().cache()', 'row().cache()', function ( type ) {
+ return this.iterator( 'row', function ( settings, row ) {
+ var r = settings.aoData[ row ];
+ return type === 'search' ? r._aFilterData : r._aSortData;
+ }, 1 );
+ } );
+
+ _api_registerPlural( 'rows().invalidate()', 'row().invalidate()', function ( src ) {
+ return this.iterator( 'row', function ( settings, row ) {
+ _fnInvalidate( settings, row, src );
+ } );
+ } );
+
+ _api_registerPlural( 'rows().indexes()', 'row().index()', function () {
+ return this.iterator( 'row', function ( settings, row ) {
+ return row;
+ }, 1 );
+ } );
+
+ _api_registerPlural( 'rows().ids()', 'row().id()', function ( hash ) {
+ var a = [];
+ var context = this.context;
+
+ // `iterator` will drop undefined values, but in this case we want them
+ for ( var i=0, ien=context.length ; i<ien ; i++ ) {
+ for ( var j=0, jen=this[i].length ; j<jen ; j++ ) {
+ var id = context[i].rowIdFn( context[i].aoData[ this[i][j] ]._aData );
+ a.push( (hash === true ? '#' : '' )+ id );
+ }
+ }
+
+ return new _Api( context, a );
+ } );
+
+ _api_registerPlural( 'rows().remove()', 'row().remove()', function () {
+ var that = this;
+
+ this.iterator( 'row', function ( settings, row, thatIdx ) {
+ var data = settings.aoData;
+ var rowData = data[ row ];
+ var i, ien, j, jen;
+ var loopRow, loopCells;
+
+ data.splice( row, 1 );
+
+ // Update the cached indexes
+ for ( i=0, ien=data.length ; i<ien ; i++ ) {
+ loopRow = data[i];
+ loopCells = loopRow.anCells;
+
+ // Rows
+ if ( loopRow.nTr !== null ) {
+ loopRow.nTr._DT_RowIndex = i;
+ }
+
+ // Cells
+ if ( loopCells !== null ) {
+ for ( j=0, jen=loopCells.length ; j<jen ; j++ ) {
+ loopCells[j]._DT_CellIndex.row = i;
+ }
+ }
+ }
+
+ // Delete from the display arrays
+ _fnDeleteIndex( settings.aiDisplayMaster, row );
+ _fnDeleteIndex( settings.aiDisplay, row );
+ _fnDeleteIndex( that[ thatIdx ], row, false ); // maintain local indexes
+
+ // Check for an 'overflow' they case for displaying the table
+ _fnLengthOverflow( settings );
+
+ // Remove the row's ID reference if there is one
+ var id = settings.rowIdFn( rowData._aData );
+ if ( id !== undefined ) {
+ delete settings.aIds[ id ];
+ }
+ } );
+
+ this.iterator( 'table', function ( settings ) {
+ for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
+ settings.aoData[i].idx = i;
+ }
+ } );
+
+ return this;
+ } );
+
+
+ _api_register( 'rows.add()', function ( rows ) {
+ var newRows = this.iterator( 'table', function ( settings ) {
+ var row, i, ien;
+ var out = [];
+
+ for ( i=0, ien=rows.length ; i<ien ; i++ ) {
+ row = rows[i];
+
+ if ( row.nodeName && row.nodeName.toUpperCase() === 'TR' ) {
+ out.push( _fnAddTr( settings, row )[0] );
+ }
+ else {
+ out.push( _fnAddData( settings, row ) );
+ }
+ }
+
+ return out;
+ }, 1 );
+
+ // Return an Api.rows() extended instance, so rows().nodes() etc can be used
+ var modRows = this.rows( -1 );
+ modRows.pop();
+ $.merge( modRows, newRows );
+
+ return modRows;
+ } );
+
+
+
+
+
+ /**
+ *
+ */
+ _api_register( 'row()', function ( selector, opts ) {
+ return _selector_first( this.rows( selector, opts ) );
+ } );
+
+
+ _api_register( 'row().data()', function ( data ) {
+ var ctx = this.context;
+
+ if ( data === undefined ) {
+ // Get
+ return ctx.length && this.length ?
+ ctx[0].aoData[ this[0] ]._aData :
+ undefined;
+ }
+
+ // Set
+ ctx[0].aoData[ this[0] ]._aData = data;
+
+ // Automatically invalidate
+ _fnInvalidate( ctx[0], this[0], 'data' );
+
+ return this;
+ } );
+
+
+ _api_register( 'row().node()', function () {
+ var ctx = this.context;
+
+ return ctx.length && this.length ?
+ ctx[0].aoData[ this[0] ].nTr || null :
+ null;
+ } );
+
+
+ _api_register( 'row.add()', function ( row ) {
+ // Allow a jQuery object to be passed in - only a single row is added from
+ // it though - the first element in the set
+ if ( row instanceof $ && row.length ) {
+ row = row[0];
+ }
+
+ var rows = this.iterator( 'table', function ( settings ) {
+ if ( row.nodeName && row.nodeName.toUpperCase() === 'TR' ) {
+ return _fnAddTr( settings, row )[0];
+ }
+ return _fnAddData( settings, row );
+ } );
+
+ // Return an Api.rows() extended instance, with the newly added row selected
+ return this.row( rows[0] );
+ } );
+
+
+
+ var __details_add = function ( ctx, row, data, klass )
+ {
+ // Convert to array of TR elements
+ var rows = [];
+ var addRow = function ( r, k ) {
+ // Recursion to allow for arrays of jQuery objects
+ if ( $.isArray( r ) || r instanceof $ ) {
+ for ( var i=0, ien=r.length ; i<ien ; i++ ) {
+ addRow( r[i], k );
+ }
+ return;
+ }
+
+ // If we get a TR element, then just add it directly - up to the dev
+ // to add the correct number of columns etc
+ if ( r.nodeName && r.nodeName.toLowerCase() === 'tr' ) {
+ rows.push( r );
+ }
+ else {
+ // Otherwise create a row with a wrapper
+ var created = $('<tr><td/></tr>').addClass( k );
+ $('td', created)
+ .addClass( k )
+ .html( r )
+ [0].colSpan = _fnVisbleColumns( ctx );
+
+ rows.push( created[0] );
+ }
+ };
+
+ addRow( data, klass );
+
+ if ( row._details ) {
+ row._details.detach();
+ }
+
+ row._details = $(rows);
+
+ // If the children were already shown, that state should be retained
+ if ( row._detailsShow ) {
+ row._details.insertAfter( row.nTr );
+ }
+ };
+
+
+ var __details_remove = function ( api, idx )
+ {
+ var ctx = api.context;
+
+ if ( ctx.length ) {
+ var row = ctx[0].aoData[ idx !== undefined ? idx : api[0] ];
+
+ if ( row && row._details ) {
+ row._details.remove();
+
+ row._detailsShow = undefined;
+ row._details = undefined;
+ }
+ }
+ };
+
+
+ var __details_display = function ( api, show ) {
+ var ctx = api.context;
+
+ if ( ctx.length && api.length ) {
+ var row = ctx[0].aoData[ api[0] ];
+
+ if ( row._details ) {
+ row._detailsShow = show;
+
+ if ( show ) {
+ row._details.insertAfter( row.nTr );
+ }
+ else {
+ row._details.detach();
+ }
+
+ __details_events( ctx[0] );
+ }
+ }
+ };
+
+
+ var __details_events = function ( settings )
+ {
+ var api = new _Api( settings );
+ var namespace = '.dt.DT_details';
+ var drawEvent = 'draw'+namespace;
+ var colvisEvent = 'column-visibility'+namespace;
+ var destroyEvent = 'destroy'+namespace;
+ var data = settings.aoData;
+
+ api.off( drawEvent +' '+ colvisEvent +' '+ destroyEvent );
+
+ if ( _pluck( data, '_details' ).length > 0 ) {
+ // On each draw, insert the required elements into the document
+ api.on( drawEvent, function ( e, ctx ) {
+ if ( settings !== ctx ) {
+ return;
+ }
+
+ api.rows( {page:'current'} ).eq(0).each( function (idx) {
+ // Internal data grab
+ var row = data[ idx ];
+
+ if ( row._detailsShow ) {
+ row._details.insertAfter( row.nTr );
+ }
+ } );
+ } );
+
+ // Column visibility change - update the colspan
+ api.on( colvisEvent, function ( e, ctx, idx, vis ) {
+ if ( settings !== ctx ) {
+ return;
+ }
+
+ // Update the colspan for the details rows (note, only if it already has
+ // a colspan)
+ var row, visible = _fnVisbleColumns( ctx );
+
+ for ( var i=0, ien=data.length ; i<ien ; i++ ) {
+ row = data[i];
+
+ if ( row._details ) {
+ row._details.children('td[colspan]').attr('colspan', visible );
+ }
+ }
+ } );
+
+ // Table destroyed - nuke any child rows
+ api.on( destroyEvent, function ( e, ctx ) {
+ if ( settings !== ctx ) {
+ return;
+ }
+
+ for ( var i=0, ien=data.length ; i<ien ; i++ ) {
+ if ( data[i]._details ) {
+ __details_remove( api, i );
+ }
+ }
+ } );
+ }
+ };
+
+ // Strings for the method names to help minification
+ var _emp = '';
+ var _child_obj = _emp+'row().child';
+ var _child_mth = _child_obj+'()';
+
+ // data can be:
+ // tr
+ // string
+ // jQuery or array of any of the above
+ _api_register( _child_mth, function ( data, klass ) {
+ var ctx = this.context;
+
+ if ( data === undefined ) {
+ // get
+ return ctx.length && this.length ?
+ ctx[0].aoData[ this[0] ]._details :
+ undefined;
+ }
+ else if ( data === true ) {
+ // show
+ this.child.show();
+ }
+ else if ( data === false ) {
+ // remove
+ __details_remove( this );
+ }
+ else if ( ctx.length && this.length ) {
+ // set
+ __details_add( ctx[0], ctx[0].aoData[ this[0] ], data, klass );
+ }
+
+ return this;
+ } );
+
+
+ _api_register( [
+ _child_obj+'.show()',
+ _child_mth+'.show()' // only when `child()` was called with parameters (without
+ ], function ( show ) { // it returns an object and this method is not executed)
+ __details_display( this, true );
+ return this;
+ } );
+
+
+ _api_register( [
+ _child_obj+'.hide()',
+ _child_mth+'.hide()' // only when `child()` was called with parameters (without
+ ], function () { // it returns an object and this method is not executed)
+ __details_display( this, false );
+ return this;
+ } );
+
+
+ _api_register( [
+ _child_obj+'.remove()',
+ _child_mth+'.remove()' // only when `child()` was called with parameters (without
+ ], function () { // it returns an object and this method is not executed)
+ __details_remove( this );
+ return this;
+ } );
+
+
+ _api_register( _child_obj+'.isShown()', function () {
+ var ctx = this.context;
+
+ if ( ctx.length && this.length ) {
+ // _detailsShown as false or undefined will fall through to return false
+ return ctx[0].aoData[ this[0] ]._detailsShow || false;
+ }
+ return false;
+ } );
+
+
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Columns
+ *
+ * {integer} - column index (>=0 count from left, <0 count from right)
+ * "{integer}:visIdx" - visible column index (i.e. translate to column index) (>=0 count from left, <0 count from right)
+ * "{integer}:visible" - alias for {integer}:visIdx (>=0 count from left, <0 count from right)
+ * "{string}:name" - column name
+ * "{string}" - jQuery selector on column header nodes
+ *
+ */
+
+ // can be an array of these items, comma separated list, or an array of comma
+ // separated lists
+
+ var __re_column_selector = /^([^:]+):(name|visIdx|visible)$/;
+
+
+ // r1 and r2 are redundant - but it means that the parameters match for the
+ // iterator callback in columns().data()
+ var __columnData = function ( settings, column, r1, r2, rows ) {
+ var a = [];
+ for ( var row=0, ien=rows.length ; row<ien ; row++ ) {
+ a.push( _fnGetCellData( settings, rows[row], column ) );
+ }
+ return a;
+ };
+
+
+ var __column_selector = function ( settings, selector, opts )
+ {
+ var
+ columns = settings.aoColumns,
+ names = _pluck( columns, 'sName' ),
+ nodes = _pluck( columns, 'nTh' );
+
+ var run = function ( s ) {
+ var selInt = _intVal( s );
+
+ // Selector - all
+ if ( s === '' ) {
+ return _range( columns.length );
+ }
+
+ // Selector - index
+ if ( selInt !== null ) {
+ return [ selInt >= 0 ?
+ selInt : // Count from left
+ columns.length + selInt // Count from right (+ because its a negative value)
+ ];
+ }
+
+ // Selector = function
+ if ( typeof s === 'function' ) {
+ var rows = _selector_row_indexes( settings, opts );
+
+ return $.map( columns, function (col, idx) {
+ return s(
+ idx,
+ __columnData( settings, idx, 0, 0, rows ),
+ nodes[ idx ]
+ ) ? idx : null;
+ } );
+ }
+
+ // jQuery or string selector
+ var match = typeof s === 'string' ?
+ s.match( __re_column_selector ) :
+ '';
+
+ if ( match ) {
+ switch( match[2] ) {
+ case 'visIdx':
+ case 'visible':
+ var idx = parseInt( match[1], 10 );
+ // Visible index given, convert to column index
+ if ( idx < 0 ) {
+ // Counting from the right
+ var visColumns = $.map( columns, function (col,i) {
+ return col.bVisible ? i : null;
+ } );
+ return [ visColumns[ visColumns.length + idx ] ];
+ }
+ // Counting from the left
+ return [ _fnVisibleToColumnIndex( settings, idx ) ];
+
+ case 'name':
+ // match by name. `names` is column index complete and in order
+ return $.map( names, function (name, i) {
+ return name === match[1] ? i : null;
+ } );
+
+ default:
+ return [];
+ }
+ }
+
+ // Cell in the table body
+ if ( s.nodeName && s._DT_CellIndex ) {
+ return [ s._DT_CellIndex.column ];
+ }
+
+ // jQuery selector on the TH elements for the columns
+ var jqResult = $( nodes )
+ .filter( s )
+ .map( function () {
+ return $.inArray( this, nodes ); // `nodes` is column index complete and in order
+ } )
+ .toArray();
+
+ if ( jqResult.length || ! s.nodeName ) {
+ return jqResult;
+ }
+
+ // Otherwise a node which might have a `dt-column` data attribute, or be
+ // a child or such an element
+ var host = $(s).closest('*[data-dt-column]');
+ return host.length ?
+ [ host.data('dt-column') ] :
+ [];
+ };
+
+ return _selector_run( 'column', selector, run, settings, opts );
+ };
+
+
+ var __setColumnVis = function ( settings, column, vis ) {
+ var
+ cols = settings.aoColumns,
+ col = cols[ column ],
+ data = settings.aoData,
+ row, cells, i, ien, tr;
+
+ // Get
+ if ( vis === undefined ) {
+ return col.bVisible;
+ }
+
+ // Set
+ // No change
+ if ( col.bVisible === vis ) {
+ return;
+ }
+
+ if ( vis ) {
+ // Insert column
+ // Need to decide if we should use appendChild or insertBefore
+ var insertBefore = $.inArray( true, _pluck(cols, 'bVisible'), column+1 );
+
+ for ( i=0, ien=data.length ; i<ien ; i++ ) {
+ tr = data[i].nTr;
+ cells = data[i].anCells;
+
+ if ( tr ) {
+ // insertBefore can act like appendChild if 2nd arg is null
+ tr.insertBefore( cells[ column ], cells[ insertBefore ] || null );
+ }
+ }
+ }
+ else {
+ // Remove column
+ $( _pluck( settings.aoData, 'anCells', column ) ).detach();
+ }
+
+ // Common actions
+ col.bVisible = vis;
+ _fnDrawHead( settings, settings.aoHeader );
+ _fnDrawHead( settings, settings.aoFooter );
+
+ _fnSaveState( settings );
+ };
+
+
+ _api_register( 'columns()', function ( selector, opts ) {
+ // argument shifting
+ if ( selector === undefined ) {
+ selector = '';
+ }
+ else if ( $.isPlainObject( selector ) ) {
+ opts = selector;
+ selector = '';
+ }
+
+ opts = _selector_opts( opts );
+
+ var inst = this.iterator( 'table', function ( settings ) {
+ return __column_selector( settings, selector, opts );
+ }, 1 );
+
+ // Want argument shifting here and in _row_selector?
+ inst.selector.cols = selector;
+ inst.selector.opts = opts;
+
+ return inst;
+ } );
+
+ _api_registerPlural( 'columns().header()', 'column().header()', function ( selector, opts ) {
+ return this.iterator( 'column', function ( settings, column ) {
+ return settings.aoColumns[column].nTh;
+ }, 1 );
+ } );
+
+ _api_registerPlural( 'columns().footer()', 'column().footer()', function ( selector, opts ) {
+ return this.iterator( 'column', function ( settings, column ) {
+ return settings.aoColumns[column].nTf;
+ }, 1 );
+ } );
+
+ _api_registerPlural( 'columns().data()', 'column().data()', function () {
+ return this.iterator( 'column-rows', __columnData, 1 );
+ } );
+
+ _api_registerPlural( 'columns().dataSrc()', 'column().dataSrc()', function () {
+ return this.iterator( 'column', function ( settings, column ) {
+ return settings.aoColumns[column].mData;
+ }, 1 );
+ } );
+
+ _api_registerPlural( 'columns().cache()', 'column().cache()', function ( type ) {
+ return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) {
+ return _pluck_order( settings.aoData, rows,
+ type === 'search' ? '_aFilterData' : '_aSortData', column
+ );
+ }, 1 );
+ } );
+
+ _api_registerPlural( 'columns().nodes()', 'column().nodes()', function () {
+ return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) {
+ return _pluck_order( settings.aoData, rows, 'anCells', column ) ;
+ }, 1 );
+ } );
+
+ _api_registerPlural( 'columns().visible()', 'column().visible()', function ( vis, calc ) {
+ var ret = this.iterator( 'column', function ( settings, column ) {
+ if ( vis === undefined ) {
+ return settings.aoColumns[ column ].bVisible;
+ } // else
+ __setColumnVis( settings, column, vis );
+ } );
+
+ // Group the column visibility changes
+ if ( vis !== undefined ) {
+ // Second loop once the first is done for events
+ this.iterator( 'column', function ( settings, column ) {
+ _fnCallbackFire( settings, null, 'column-visibility', [settings, column, vis, calc] );
+ } );
+
+ if ( calc === undefined || calc ) {
+ this.columns.adjust();
+ }
+ }
+
+ return ret;
+ } );
+
+ _api_registerPlural( 'columns().indexes()', 'column().index()', function ( type ) {
+ return this.iterator( 'column', function ( settings, column ) {
+ return type === 'visible' ?
+ _fnColumnIndexToVisible( settings, column ) :
+ column;
+ }, 1 );
+ } );
+
+ _api_register( 'columns.adjust()', function () {
+ return this.iterator( 'table', function ( settings ) {
+ _fnAdjustColumnSizing( settings );
+ }, 1 );
+ } );
+
+ _api_register( 'column.index()', function ( type, idx ) {
+ if ( this.context.length !== 0 ) {
+ var ctx = this.context[0];
+
+ if ( type === 'fromVisible' || type === 'toData' ) {
+ return _fnVisibleToColumnIndex( ctx, idx );
+ }
+ else if ( type === 'fromData' || type === 'toVisible' ) {
+ return _fnColumnIndexToVisible( ctx, idx );
+ }
+ }
+ } );
+
+ _api_register( 'column()', function ( selector, opts ) {
+ return _selector_first( this.columns( selector, opts ) );
+ } );
+
+
+
+ var __cell_selector = function ( settings, selector, opts )
+ {
+ var data = settings.aoData;
+ var rows = _selector_row_indexes( settings, opts );
+ var cells = _removeEmpty( _pluck_order( data, rows, 'anCells' ) );
+ var allCells = $( [].concat.apply([], cells) );
+ var row;
+ var columns = settings.aoColumns.length;
+ var a, i, ien, j, o, host;
+
+ var run = function ( s ) {
+ var fnSelector = typeof s === 'function';
+
+ if ( s === null || s === undefined || fnSelector ) {
+ // All cells and function selectors
+ a = [];
+
+ for ( i=0, ien=rows.length ; i<ien ; i++ ) {
+ row = rows[i];
+
+ for ( j=0 ; j<columns ; j++ ) {
+ o = {
+ row: row,
+ column: j
+ };
+
+ if ( fnSelector ) {
+ // Selector - function
+ host = data[ row ];
+
+ if ( s( o, _fnGetCellData(settings, row, j), host.anCells ? host.anCells[j] : null ) ) {
+ a.push( o );
+ }
+ }
+ else {
+ // Selector - all
+ a.push( o );
+ }
+ }
+ }
+
+ return a;
+ }
+
+ // Selector - index
+ if ( $.isPlainObject( s ) ) {
+ return [s];
+ }
+
+ // Selector - jQuery filtered cells
+ var jqResult = allCells
+ .filter( s )
+ .map( function (i, el) {
+ return { // use a new object, in case someone changes the values
+ row: el._DT_CellIndex.row,
+ column: el._DT_CellIndex.column
+ };
+ } )
+ .toArray();
+
+ if ( jqResult.length || ! s.nodeName ) {
+ return jqResult;
+ }
+
+ // Otherwise the selector is a node, and there is one last option - the
+ // element might be a child of an element which has dt-row and dt-column
+ // data attributes
+ host = $(s).closest('*[data-dt-row]');
+ return host.length ?
+ [ {
+ row: host.data('dt-row'),
+ column: host.data('dt-column')
+ } ] :
+ [];
+ };
+
+ return _selector_run( 'cell', selector, run, settings, opts );
+ };
+
+
+
+
+ _api_register( 'cells()', function ( rowSelector, columnSelector, opts ) {
+ // Argument shifting
+ if ( $.isPlainObject( rowSelector ) ) {
+ // Indexes
+ if ( rowSelector.row === undefined ) {
+ // Selector options in first parameter
+ opts = rowSelector;
+ rowSelector = null;
+ }
+ else {
+ // Cell index objects in first parameter
+ opts = columnSelector;
+ columnSelector = null;
+ }
+ }
+ if ( $.isPlainObject( columnSelector ) ) {
+ opts = columnSelector;
+ columnSelector = null;
+ }
+
+ // Cell selector
+ if ( columnSelector === null || columnSelector === undefined ) {
+ return this.iterator( 'table', function ( settings ) {
+ return __cell_selector( settings, rowSelector, _selector_opts( opts ) );
+ } );
+ }
+
+ // Row + column selector
+ var columns = this.columns( columnSelector, opts );
+ var rows = this.rows( rowSelector, opts );
+ var a, i, ien, j, jen;
+
+ var cells = this.iterator( 'table', function ( settings, idx ) {
+ a = [];
+
+ for ( i=0, ien=rows[idx].length ; i<ien ; i++ ) {
+ for ( j=0, jen=columns[idx].length ; j<jen ; j++ ) {
+ a.push( {
+ row: rows[idx][i],
+ column: columns[idx][j]
+ } );
+ }
+ }
+
+ return a;
+ }, 1 );
+
+ $.extend( cells.selector, {
+ cols: columnSelector,
+ rows: rowSelector,
+ opts: opts
+ } );
+
+ return cells;
+ } );
+
+
+ _api_registerPlural( 'cells().nodes()', 'cell().node()', function () {
+ return this.iterator( 'cell', function ( settings, row, column ) {
+ var data = settings.aoData[ row ];
+
+ return data && data.anCells ?
+ data.anCells[ column ] :
+ undefined;
+ }, 1 );
+ } );
+
+
+ _api_register( 'cells().data()', function () {
+ return this.iterator( 'cell', function ( settings, row, column ) {
+ return _fnGetCellData( settings, row, column );
+ }, 1 );
+ } );
+
+
+ _api_registerPlural( 'cells().cache()', 'cell().cache()', function ( type ) {
+ type = type === 'search' ? '_aFilterData' : '_aSortData';
+
+ return this.iterator( 'cell', function ( settings, row, column ) {
+ return settings.aoData[ row ][ type ][ column ];
+ }, 1 );
+ } );
+
+
+ _api_registerPlural( 'cells().render()', 'cell().render()', function ( type ) {
+ return this.iterator( 'cell', function ( settings, row, column ) {
+ return _fnGetCellData( settings, row, column, type );
+ }, 1 );
+ } );
+
+
+ _api_registerPlural( 'cells().indexes()', 'cell().index()', function () {
+ return this.iterator( 'cell', function ( settings, row, column ) {
+ return {
+ row: row,
+ column: column,
+ columnVisible: _fnColumnIndexToVisible( settings, column )
+ };
+ }, 1 );
+ } );
+
+
+ _api_registerPlural( 'cells().invalidate()', 'cell().invalidate()', function ( src ) {
+ return this.iterator( 'cell', function ( settings, row, column ) {
+ _fnInvalidate( settings, row, src, column );
+ } );
+ } );
+
+
+
+ _api_register( 'cell()', function ( rowSelector, columnSelector, opts ) {
+ return _selector_first( this.cells( rowSelector, columnSelector, opts ) );
+ } );
+
+
+ _api_register( 'cell().data()', function ( data ) {
+ var ctx = this.context;
+ var cell = this[0];
+
+ if ( data === undefined ) {
+ // Get
+ return ctx.length && cell.length ?
+ _fnGetCellData( ctx[0], cell[0].row, cell[0].column ) :
+ undefined;
+ }
+
+ // Set
+ _fnSetCellData( ctx[0], cell[0].row, cell[0].column, data );
+ _fnInvalidate( ctx[0], cell[0].row, 'data', cell[0].column );
+
+ return this;
+ } );
+
+
+
+ /**
+ * Get current ordering (sorting) that has been applied to the table.
+ *
+ * @returns {array} 2D array containing the sorting information for the first
+ * table in the current context. Each element in the parent array represents
+ * a column being sorted upon (i.e. multi-sorting with two columns would have
+ * 2 inner arrays). The inner arrays may have 2 or 3 elements. The first is
+ * the column index that the sorting condition applies to, the second is the
+ * direction of the sort (`desc` or `asc`) and, optionally, the third is the
+ * index of the sorting order from the `column.sorting` initialisation array.
+ *//**
+ * Set the ordering for the table.
+ *
+ * @param {integer} order Column index to sort upon.
+ * @param {string} direction Direction of the sort to be applied (`asc` or `desc`)
+ * @returns {DataTables.Api} this
+ *//**
+ * Set the ordering for the table.
+ *
+ * @param {array} order 1D array of sorting information to be applied.
+ * @param {array} [...] Optional additional sorting conditions
+ * @returns {DataTables.Api} this
+ *//**
+ * Set the ordering for the table.
+ *
+ * @param {array} order 2D array of sorting information to be applied.
+ * @returns {DataTables.Api} this
+ */
+ _api_register( 'order()', function ( order, dir ) {
+ var ctx = this.context;
+
+ if ( order === undefined ) {
+ // get
+ return ctx.length !== 0 ?
+ ctx[0].aaSorting :
+ undefined;
+ }
+
+ // set
+ if ( typeof order === 'number' ) {
+ // Simple column / direction passed in
+ order = [ [ order, dir ] ];
+ }
+ else if ( order.length && ! $.isArray( order[0] ) ) {
+ // Arguments passed in (list of 1D arrays)
+ order = Array.prototype.slice.call( arguments );
+ }
+ // otherwise a 2D array was passed in
+
+ return this.iterator( 'table', function ( settings ) {
+ settings.aaSorting = order.slice();
+ } );
+ } );
+
+
+ /**
+ * Attach a sort listener to an element for a given column
+ *
+ * @param {node|jQuery|string} node Identifier for the element(s) to attach the
+ * listener to. This can take the form of a single DOM node, a jQuery
+ * collection of nodes or a jQuery selector which will identify the node(s).
+ * @param {integer} column the column that a click on this node will sort on
+ * @param {function} [callback] callback function when sort is run
+ * @returns {DataTables.Api} this
+ */
+ _api_register( 'order.listener()', function ( node, column, callback ) {
+ return this.iterator( 'table', function ( settings ) {
+ _fnSortAttachListener( settings, node, column, callback );
+ } );
+ } );
+
+
+ _api_register( 'order.fixed()', function ( set ) {
+ if ( ! set ) {
+ var ctx = this.context;
+ var fixed = ctx.length ?
+ ctx[0].aaSortingFixed :
+ undefined;
+
+ return $.isArray( fixed ) ?
+ { pre: fixed } :
+ fixed;
+ }
+
+ return this.iterator( 'table', function ( settings ) {
+ settings.aaSortingFixed = $.extend( true, {}, set );
+ } );
+ } );
+
+
+ // Order by the selected column(s)
+ _api_register( [
+ 'columns().order()',
+ 'column().order()'
+ ], function ( dir ) {
+ var that = this;
+
+ return this.iterator( 'table', function ( settings, i ) {
+ var sort = [];
+
+ $.each( that[i], function (j, col) {
+ sort.push( [ col, dir ] );
+ } );
+
+ settings.aaSorting = sort;
+ } );
+ } );
+
+
+
+ _api_register( 'search()', function ( input, regex, smart, caseInsen ) {
+ var ctx = this.context;
+
+ if ( input === undefined ) {
+ // get
+ return ctx.length !== 0 ?
+ ctx[0].oPreviousSearch.sSearch :
+ undefined;
+ }
+
+ // set
+ return this.iterator( 'table', function ( settings ) {
+ if ( ! settings.oFeatures.bFilter ) {
+ return;
+ }
+
+ _fnFilterComplete( settings, $.extend( {}, settings.oPreviousSearch, {
+ "sSearch": input+"",
+ "bRegex": regex === null ? false : regex,
+ "bSmart": smart === null ? true : smart,
+ "bCaseInsensitive": caseInsen === null ? true : caseInsen
+ } ), 1 );
+ } );
+ } );
+
+
+ _api_registerPlural(
+ 'columns().search()',
+ 'column().search()',
+ function ( input, regex, smart, caseInsen ) {
+ return this.iterator( 'column', function ( settings, column ) {
+ var preSearch = settings.aoPreSearchCols;
+
+ if ( input === undefined ) {
+ // get
+ return preSearch[ column ].sSearch;
+ }
+
+ // set
+ if ( ! settings.oFeatures.bFilter ) {
+ return;
+ }
+
+ $.extend( preSearch[ column ], {
+ "sSearch": input+"",
+ "bRegex": regex === null ? false : regex,
+ "bSmart": smart === null ? true : smart,
+ "bCaseInsensitive": caseInsen === null ? true : caseInsen
+ } );
+
+ _fnFilterComplete( settings, settings.oPreviousSearch, 1 );
+ } );
+ }
+ );
+
+ /*
+ * State API methods
+ */
+
+ _api_register( 'state()', function () {
+ return this.context.length ?
+ this.context[0].oSavedState :
+ null;
+ } );
+
+
+ _api_register( 'state.clear()', function () {
+ return this.iterator( 'table', function ( settings ) {
+ // Save an empty object
+ settings.fnStateSaveCallback.call( settings.oInstance, settings, {} );
+ } );
+ } );
+
+
+ _api_register( 'state.loaded()', function () {
+ return this.context.length ?
+ this.context[0].oLoadedState :
+ null;
+ } );
+
+
+ _api_register( 'state.save()', function () {
+ return this.iterator( 'table', function ( settings ) {
+ _fnSaveState( settings );
+ } );
+ } );
+
+
+
+ /**
+ * Provide a common method for plug-ins to check the version of DataTables being
+ * used, in order to ensure compatibility.
+ *
+ * @param {string} version Version string to check for, in the format "X.Y.Z".
+ * Note that the formats "X" and "X.Y" are also acceptable.
+ * @returns {boolean} true if this version of DataTables is greater or equal to
+ * the required version, or false if this version of DataTales is not
+ * suitable
+ * @static
+ * @dtopt API-Static
+ *
+ * @example
+ * alert( $.fn.dataTable.versionCheck( '1.9.0' ) );
+ */
+ DataTable.versionCheck = DataTable.fnVersionCheck = function( version )
+ {
+ var aThis = DataTable.version.split('.');
+ var aThat = version.split('.');
+ var iThis, iThat;
+
+ for ( var i=0, iLen=aThat.length ; i<iLen ; i++ ) {
+ iThis = parseInt( aThis[i], 10 ) || 0;
+ iThat = parseInt( aThat[i], 10 ) || 0;
+
+ // Parts are the same, keep comparing
+ if (iThis === iThat) {
+ continue;
+ }
+
+ // Parts are different, return immediately
+ return iThis > iThat;
+ }
+
+ return true;
+ };
+
+
+ /**
+ * Check if a `<table>` node is a DataTable table already or not.
+ *
+ * @param {node|jquery|string} table Table node, jQuery object or jQuery
+ * selector for the table to test. Note that if more than more than one
+ * table is passed on, only the first will be checked
+ * @returns {boolean} true the table given is a DataTable, or false otherwise
+ * @static
+ * @dtopt API-Static
+ *
+ * @example
+ * if ( ! $.fn.DataTable.isDataTable( '#example' ) ) {
+ * $('#example').dataTable();
+ * }
+ */
+ DataTable.isDataTable = DataTable.fnIsDataTable = function ( table )
+ {
+ var t = $(table).get(0);
+ var is = false;
+
+ if ( table instanceof DataTable.Api ) {
+ return true;
+ }
+
+ $.each( DataTable.settings, function (i, o) {
+ var head = o.nScrollHead ? $('table', o.nScrollHead)[0] : null;
+ var foot = o.nScrollFoot ? $('table', o.nScrollFoot)[0] : null;
+
+ if ( o.nTable === t || head === t || foot === t ) {
+ is = true;
+ }
+ } );
+
+ return is;
+ };
+
+
+ /**
+ * Get all DataTable tables that have been initialised - optionally you can
+ * select to get only currently visible tables.
+ *
+ * @param {boolean} [visible=false] Flag to indicate if you want all (default)
+ * or visible tables only.
+ * @returns {array} Array of `table` nodes (not DataTable instances) which are
+ * DataTables
+ * @static
+ * @dtopt API-Static
+ *
+ * @example
+ * $.each( $.fn.dataTable.tables(true), function () {
+ * $(table).DataTable().columns.adjust();
+ * } );
+ */
+ DataTable.tables = DataTable.fnTables = function ( visible )
+ {
+ var api = false;
+
+ if ( $.isPlainObject( visible ) ) {
+ api = visible.api;
+ visible = visible.visible;
+ }
+
+ var a = $.map( DataTable.settings, function (o) {
+ if ( !visible || (visible && $(o.nTable).is(':visible')) ) {
+ return o.nTable;
+ }
+ } );
+
+ return api ?
+ new _Api( a ) :
+ a;
+ };
+
+
+ /**
+ * Convert from camel case parameters to Hungarian notation. This is made public
+ * for the extensions to provide the same ability as DataTables core to accept
+ * either the 1.9 style Hungarian notation, or the 1.10+ style camelCase
+ * parameters.
+ *
+ * @param {object} src The model object which holds all parameters that can be
+ * mapped.
+ * @param {object} user The object to convert from camel case to Hungarian.
+ * @param {boolean} force When set to `true`, properties which already have a
+ * Hungarian value in the `user` object will be overwritten. Otherwise they
+ * won't be.
+ */
+ DataTable.camelToHungarian = _fnCamelToHungarian;
+
+
+
+ /**
+ *
+ */
+ _api_register( '$()', function ( selector, opts ) {
+ var
+ rows = this.rows( opts ).nodes(), // Get all rows
+ jqRows = $(rows);
+
+ return $( [].concat(
+ jqRows.filter( selector ).toArray(),
+ jqRows.find( selector ).toArray()
+ ) );
+ } );
+
+
+ // jQuery functions to operate on the tables
+ $.each( [ 'on', 'one', 'off' ], function (i, key) {
+ _api_register( key+'()', function ( /* event, handler */ ) {
+ var args = Array.prototype.slice.call(arguments);
+
+ // Add the `dt` namespace automatically if it isn't already present
+ args[0] = $.map( args[0].split( /\s/ ), function ( e ) {
+ return ! e.match(/\.dt\b/) ?
+ e+'.dt' :
+ e;
+ } ).join( ' ' );
+
+ var inst = $( this.tables().nodes() );
+ inst[key].apply( inst, args );
+ return this;
+ } );
+ } );
+
+
+ _api_register( 'clear()', function () {
+ return this.iterator( 'table', function ( settings ) {
+ _fnClearTable( settings );
+ } );
+ } );
+
+
+ _api_register( 'settings()', function () {
+ return new _Api( this.context, this.context );
+ } );
+
+
+ _api_register( 'init()', function () {
+ var ctx = this.context;
+ return ctx.length ? ctx[0].oInit : null;
+ } );
+
+
+ _api_register( 'data()', function () {
+ return this.iterator( 'table', function ( settings ) {
+ return _pluck( settings.aoData, '_aData' );
+ } ).flatten();
+ } );
+
+
+ _api_register( 'destroy()', function ( remove ) {
+ remove = remove || false;
+
+ return this.iterator( 'table', function ( settings ) {
+ var orig = settings.nTableWrapper.parentNode;
+ var classes = settings.oClasses;
+ var table = settings.nTable;
+ var tbody = settings.nTBody;
+ var thead = settings.nTHead;
+ var tfoot = settings.nTFoot;
+ var jqTable = $(table);
+ var jqTbody = $(tbody);
+ var jqWrapper = $(settings.nTableWrapper);
+ var rows = $.map( settings.aoData, function (r) { return r.nTr; } );
+ var i, ien;
+
+ // Flag to note that the table is currently being destroyed - no action
+ // should be taken
+ settings.bDestroying = true;
+
+ // Fire off the destroy callbacks for plug-ins etc
+ _fnCallbackFire( settings, "aoDestroyCallback", "destroy", [settings] );
+
+ // If not being removed from the document, make all columns visible
+ if ( ! remove ) {
+ new _Api( settings ).columns().visible( true );
+ }
+
+ // Blitz all `DT` namespaced events (these are internal events, the
+ // lowercase, `dt` events are user subscribed and they are responsible
+ // for removing them
+ jqWrapper.off('.DT').find(':not(tbody *)').off('.DT');
+ $(window).off('.DT-'+settings.sInstance);
+
+ // When scrolling we had to break the table up - restore it
+ if ( table != thead.parentNode ) {
+ jqTable.children('thead').detach();
+ jqTable.append( thead );
+ }
+
+ if ( tfoot && table != tfoot.parentNode ) {
+ jqTable.children('tfoot').detach();
+ jqTable.append( tfoot );
+ }
+
+ settings.aaSorting = [];
+ settings.aaSortingFixed = [];
+ _fnSortingClasses( settings );
+
+ $( rows ).removeClass( settings.asStripeClasses.join(' ') );
+
+ $('th, td', thead).removeClass( classes.sSortable+' '+
+ classes.sSortableAsc+' '+classes.sSortableDesc+' '+classes.sSortableNone
+ );
+
+ if ( settings.bJUI ) {
+ $('th span.'+classes.sSortIcon+ ', td span.'+classes.sSortIcon, thead).detach();
+ $('th, td', thead).each( function () {
+ var wrapper = $('div.'+classes.sSortJUIWrapper, this);
+ $(this).append( wrapper.contents() );
+ wrapper.detach();
+ } );
+ }
+
+ // Add the TR elements back into the table in their original order
+ jqTbody.children().detach();
+ jqTbody.append( rows );
+
+ // Remove the DataTables generated nodes, events and classes
+ var removedMethod = remove ? 'remove' : 'detach';
+ jqTable[ removedMethod ]();
+ jqWrapper[ removedMethod ]();
+
+ // If we need to reattach the table to the document
+ if ( ! remove && orig ) {
+ // insertBefore acts like appendChild if !arg[1]
+ orig.insertBefore( table, settings.nTableReinsertBefore );
+
+ // Restore the width of the original table - was read from the style property,
+ // so we can restore directly to that
+ jqTable
+ .css( 'width', settings.sDestroyWidth )
+ .removeClass( classes.sTable );
+
+ // If the were originally stripe classes - then we add them back here.
+ // Note this is not fool proof (for example if not all rows had stripe
+ // classes - but it's a good effort without getting carried away
+ ien = settings.asDestroyStripes.length;
+
+ if ( ien ) {
+ jqTbody.children().each( function (i) {
+ $(this).addClass( settings.asDestroyStripes[i % ien] );
+ } );
+ }
+ }
+
+ /* Remove the settings object from the settings array */
+ var idx = $.inArray( settings, DataTable.settings );
+ if ( idx !== -1 ) {
+ DataTable.settings.splice( idx, 1 );
+ }
+ } );
+ } );
+
+
+ // Add the `every()` method for rows, columns and cells in a compact form
+ $.each( [ 'column', 'row', 'cell' ], function ( i, type ) {
+ _api_register( type+'s().every()', function ( fn ) {
+ var opts = this.selector.opts;
+ var api = this;
+
+ return this.iterator( type, function ( settings, arg1, arg2, arg3, arg4 ) {
+ // Rows and columns:
+ // arg1 - index
+ // arg2 - table counter
+ // arg3 - loop counter
+ // arg4 - undefined
+ // Cells:
+ // arg1 - row index
+ // arg2 - column index
+ // arg3 - table counter
+ // arg4 - loop counter
+ fn.call(
+ api[ type ](
+ arg1,
+ type==='cell' ? arg2 : opts,
+ type==='cell' ? opts : undefined
+ ),
+ arg1, arg2, arg3, arg4
+ );
+ } );
+ } );
+ } );
+
+
+ // i18n method for extensions to be able to use the language object from the
+ // DataTable
+ _api_register( 'i18n()', function ( token, def, plural ) {
+ var ctx = this.context[0];
+ var resolved = _fnGetObjectDataFn( token )( ctx.oLanguage );
+
+ if ( resolved === undefined ) {
+ resolved = def;
+ }
+
+ if ( plural !== undefined && $.isPlainObject( resolved ) ) {
+ resolved = resolved[ plural ] !== undefined ?
+ resolved[ plural ] :
+ resolved._;
+ }
+
+ return resolved.replace( '%d', plural ); // nb: plural might be undefined,
+ } );
+
+ /**
+ * Version string for plug-ins to check compatibility. Allowed format is
+ * `a.b.c-d` where: a:int, b:int, c:int, d:string(dev|beta|alpha). `d` is used
+ * only for non-release builds. See http://semver.org/ for more information.
+ * @member
+ * @type string
+ * @default Version number
+ */
+ DataTable.version = "1.10.15";
+
+ /**
+ * Private data store, containing all of the settings objects that are
+ * created for the tables on a given page.
+ *
+ * Note that the `DataTable.settings` object is aliased to
+ * `jQuery.fn.dataTableExt` through which it may be accessed and
+ * manipulated, or `jQuery.fn.dataTable.settings`.
+ * @member
+ * @type array
+ * @default []
+ * @private
+ */
+ DataTable.settings = [];
+
+ /**
+ * Object models container, for the various models that DataTables has
+ * available to it. These models define the objects that are used to hold
+ * the active state and configuration of the table.
+ * @namespace
+ */
+ DataTable.models = {};
+
+
+
+ /**
+ * Template object for the way in which DataTables holds information about
+ * search information for the global filter and individual column filters.
+ * @namespace
+ */
+ DataTable.models.oSearch = {
+ /**
+ * Flag to indicate if the filtering should be case insensitive or not
+ * @type boolean
+ * @default true
+ */
+ "bCaseInsensitive": true,
+
+ /**
+ * Applied search term
+ * @type string
+ * @default <i>Empty string</i>
+ */
+ "sSearch": "",
+
+ /**
+ * Flag to indicate if the search term should be interpreted as a
+ * regular expression (true) or not (false) and therefore and special
+ * regex characters escaped.
+ * @type boolean
+ * @default false
+ */
+ "bRegex": false,
+
+ /**
+ * Flag to indicate if DataTables is to use its smart filtering or not.
+ * @type boolean
+ * @default true
+ */
+ "bSmart": true
+ };
+
+
+
+
+ /**
+ * Template object for the way in which DataTables holds information about
+ * each individual row. This is the object format used for the settings
+ * aoData array.
+ * @namespace
+ */
+ DataTable.models.oRow = {
+ /**
+ * TR element for the row
+ * @type node
+ * @default null
+ */
+ "nTr": null,
+
+ /**
+ * Array of TD elements for each row. This is null until the row has been
+ * created.
+ * @type array nodes
+ * @default []
+ */
+ "anCells": null,
+
+ /**
+ * Data object from the original data source for the row. This is either
+ * an array if using the traditional form of DataTables, or an object if
+ * using mData options. The exact type will depend on the passed in
+ * data from the data source, or will be an array if using DOM a data
+ * source.
+ * @type array|object
+ * @default []
+ */
+ "_aData": [],
+
+ /**
+ * Sorting data cache - this array is ostensibly the same length as the
+ * number of columns (although each index is generated only as it is
+ * needed), and holds the data that is used for sorting each column in the
+ * row. We do this cache generation at the start of the sort in order that
+ * the formatting of the sort data need be done only once for each cell
+ * per sort. This array should not be read from or written to by anything
+ * other than the master sorting methods.
+ * @type array
+ * @default null
+ * @private
+ */
+ "_aSortData": null,
+
+ /**
+ * Per cell filtering data cache. As per the sort data cache, used to
+ * increase the performance of the filtering in DataTables
+ * @type array
+ * @default null
+ * @private
+ */
+ "_aFilterData": null,
+
+ /**
+ * Filtering data cache. This is the same as the cell filtering cache, but
+ * in this case a string rather than an array. This is easily computed with
+ * a join on `_aFilterData`, but is provided as a cache so the join isn't
+ * needed on every search (memory traded for performance)
+ * @type array
+ * @default null
+ * @private
+ */
+ "_sFilterRow": null,
+
+ /**
+ * Cache of the class name that DataTables has applied to the row, so we
+ * can quickly look at this variable rather than needing to do a DOM check
+ * on className for the nTr property.
+ * @type string
+ * @default <i>Empty string</i>
+ * @private
+ */
+ "_sRowStripe": "",
+
+ /**
+ * Denote if the original data source was from the DOM, or the data source
+ * object. This is used for invalidating data, so DataTables can
+ * automatically read data from the original source, unless uninstructed
+ * otherwise.
+ * @type string
+ * @default null
+ * @private
+ */
+ "src": null,
+
+ /**
+ * Index in the aoData array. This saves an indexOf lookup when we have the
+ * object, but want to know the index
+ * @type integer
+ * @default -1
+ * @private
+ */
+ "idx": -1
+ };
+
+
+ /**
+ * Template object for the column information object in DataTables. This object
+ * is held in the settings aoColumns array and contains all the information that
+ * DataTables needs about each individual column.
+ *
+ * Note that this object is related to {@link DataTable.defaults.column}
+ * but this one is the internal data store for DataTables's cache of columns.
+ * It should NOT be manipulated outside of DataTables. Any configuration should
+ * be done through the initialisation options.
+ * @namespace
+ */
+ DataTable.models.oColumn = {
+ /**
+ * Column index. This could be worked out on-the-fly with $.inArray, but it
+ * is faster to just hold it as a variable
+ * @type integer
+ * @default null
+ */
+ "idx": null,
+
+ /**
+ * A list of the columns that sorting should occur on when this column
+ * is sorted. That this property is an array allows multi-column sorting
+ * to be defined for a column (for example first name / last name columns
+ * would benefit from this). The values are integers pointing to the
+ * columns to be sorted on (typically it will be a single integer pointing
+ * at itself, but that doesn't need to be the case).
+ * @type array
+ */
+ "aDataSort": null,
+
+ /**
+ * Define the sorting directions that are applied to the column, in sequence
+ * as the column is repeatedly sorted upon - i.e. the first value is used
+ * as the sorting direction when the column if first sorted (clicked on).
+ * Sort it again (click again) and it will move on to the next index.
+ * Repeat until loop.
+ * @type array
+ */
+ "asSorting": null,
+
+ /**
+ * Flag to indicate if the column is searchable, and thus should be included
+ * in the filtering or not.
+ * @type boolean
+ */
+ "bSearchable": null,
+
+ /**
+ * Flag to indicate if the column is sortable or not.
+ * @type boolean
+ */
+ "bSortable": null,
+
+ /**
+ * Flag to indicate if the column is currently visible in the table or not
+ * @type boolean
+ */
+ "bVisible": null,
+
+ /**
+ * Store for manual type assignment using the `column.type` option. This
+ * is held in store so we can manipulate the column's `sType` property.
+ * @type string
+ * @default null
+ * @private
+ */
+ "_sManualType": null,
+
+ /**
+ * Flag to indicate if HTML5 data attributes should be used as the data
+ * source for filtering or sorting. True is either are.
+ * @type boolean
+ * @default false
+ * @private
+ */
+ "_bAttrSrc": false,
+
+ /**
+ * Developer definable function that is called whenever a cell is created (Ajax source,
+ * etc) or processed for input (DOM source). This can be used as a compliment to mRender
+ * allowing you to modify the DOM element (add background colour for example) when the
+ * element is available.
+ * @type function
+ * @param {element} nTd The TD node that has been created
+ * @param {*} sData The Data for the cell
+ * @param {array|object} oData The data for the whole row
+ * @param {int} iRow The row index for the aoData data store
+ * @default null
+ */
+ "fnCreatedCell": null,
+
+ /**
+ * Function to get data from a cell in a column. You should <b>never</b>
+ * access data directly through _aData internally in DataTables - always use
+ * the method attached to this property. It allows mData to function as
+ * required. This function is automatically assigned by the column
+ * initialisation method
+ * @type function
+ * @param {array|object} oData The data array/object for the array
+ * (i.e. aoData[]._aData)
+ * @param {string} sSpecific The specific data type you want to get -
+ * 'display', 'type' 'filter' 'sort'
+ * @returns {*} The data for the cell from the given row's data
+ * @default null
+ */
+ "fnGetData": null,
+
+ /**
+ * Function to set data for a cell in the column. You should <b>never</b>
+ * set the data directly to _aData internally in DataTables - always use
+ * this method. It allows mData to function as required. This function
+ * is automatically assigned by the column initialisation method
+ * @type function
+ * @param {array|object} oData The data array/object for the array
+ * (i.e. aoData[]._aData)
+ * @param {*} sValue Value to set
+ * @default null
+ */
+ "fnSetData": null,
+
+ /**
+ * Property to read the value for the cells in the column from the data
+ * source array / object. If null, then the default content is used, if a
+ * function is given then the return from the function is used.
+ * @type function|int|string|null
+ * @default null
+ */
+ "mData": null,
+
+ /**
+ * Partner property to mData which is used (only when defined) to get
+ * the data - i.e. it is basically the same as mData, but without the
+ * 'set' option, and also the data fed to it is the result from mData.
+ * This is the rendering method to match the data method of mData.
+ * @type function|int|string|null
+ * @default null
+ */
+ "mRender": null,
+
+ /**
+ * Unique header TH/TD element for this column - this is what the sorting
+ * listener is attached to (if sorting is enabled.)
+ * @type node
+ * @default null
+ */
+ "nTh": null,
+
+ /**
+ * Unique footer TH/TD element for this column (if there is one). Not used
+ * in DataTables as such, but can be used for plug-ins to reference the
+ * footer for each column.
+ * @type node
+ * @default null
+ */
+ "nTf": null,
+
+ /**
+ * The class to apply to all TD elements in the table's TBODY for the column
+ * @type string
+ * @default null
+ */
+ "sClass": null,
+
+ /**
+ * When DataTables calculates the column widths to assign to each column,
+ * it finds the longest string in each column and then constructs a
+ * temporary table and reads the widths from that. The problem with this
+ * is that "mmm" is much wider then "iiii", but the latter is a longer
+ * string - thus the calculation can go wrong (doing it properly and putting
+ * it into an DOM object and measuring that is horribly(!) slow). Thus as
+ * a "work around" we provide this option. It will append its value to the
+ * text that is found to be the longest string for the column - i.e. padding.
+ * @type string
+ */
+ "sContentPadding": null,
+
+ /**
+ * Allows a default value to be given for a column's data, and will be used
+ * whenever a null data source is encountered (this can be because mData
+ * is set to null, or because the data source itself is null).
+ * @type string
+ * @default null
+ */
+ "sDefaultContent": null,
+
+ /**
+ * Name for the column, allowing reference to the column by name as well as
+ * by index (needs a lookup to work by name).
+ * @type string
+ */
+ "sName": null,
+
+ /**
+ * Custom sorting data type - defines which of the available plug-ins in
+ * afnSortData the custom sorting will use - if any is defined.
+ * @type string
+ * @default std
+ */
+ "sSortDataType": 'std',
+
+ /**
+ * Class to be applied to the header element when sorting on this column
+ * @type string
+ * @default null
+ */
+ "sSortingClass": null,
+
+ /**
+ * Class to be applied to the header element when sorting on this column -
+ * when jQuery UI theming is used.
+ * @type string
+ * @default null
+ */
+ "sSortingClassJUI": null,
+
+ /**
+ * Title of the column - what is seen in the TH element (nTh).
+ * @type string
+ */
+ "sTitle": null,
+
+ /**
+ * Column sorting and filtering type
+ * @type string
+ * @default null
+ */
+ "sType": null,
+
+ /**
+ * Width of the column
+ * @type string
+ * @default null
+ */
+ "sWidth": null,
+
+ /**
+ * Width of the column when it was first "encountered"
+ * @type string
+ * @default null
+ */
+ "sWidthOrig": null
+ };
+
+
+ /*
+ * Developer note: The properties of the object below are given in Hungarian
+ * notation, that was used as the interface for DataTables prior to v1.10, however
+ * from v1.10 onwards the primary interface is camel case. In order to avoid
+ * breaking backwards compatibility utterly with this change, the Hungarian
+ * version is still, internally the primary interface, but is is not documented
+ * - hence the @name tags in each doc comment. This allows a Javascript function
+ * to create a map from Hungarian notation to camel case (going the other direction
+ * would require each property to be listed, which would at around 3K to the size
+ * of DataTables, while this method is about a 0.5K hit.
+ *
+ * Ultimately this does pave the way for Hungarian notation to be dropped
+ * completely, but that is a massive amount of work and will break current
+ * installs (therefore is on-hold until v2).
+ */
+
+ /**
+ * Initialisation options that can be given to DataTables at initialisation
+ * time.
+ * @namespace
+ */
+ DataTable.defaults = {
+ /**
+ * An array of data to use for the table, passed in at initialisation which
+ * will be used in preference to any data which is already in the DOM. This is
+ * particularly useful for constructing tables purely in Javascript, for
+ * example with a custom Ajax call.
+ * @type array
+ * @default null
+ *
+ * @dtopt Option
+ * @name DataTable.defaults.data
+ *
+ * @example
+ * // Using a 2D array data source
+ * $(document).ready( function () {
+ * $('#example').dataTable( {
+ * "data": [
+ * ['Trident', 'Internet Explorer 4.0', 'Win 95+', 4, 'X'],
+ * ['Trident', 'Internet Explorer 5.0', 'Win 95+', 5, 'C'],
+ * ],
+ * "columns": [
+ * { "title": "Engine" },
+ * { "title": "Browser" },
+ * { "title": "Platform" },
+ * { "title": "Version" },
+ * { "title": "Grade" }
+ * ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Using an array of objects as a data source (`data`)
+ * $(document).ready( function () {
+ * $('#example').dataTable( {
+ * "data": [
+ * {
+ * "engine": "Trident",
+ * "browser": "Internet Explorer 4.0",
+ * "platform": "Win 95+",
+ * "version": 4,
+ * "grade": "X"
+ * },
+ * {
+ * "engine": "Trident",
+ * "browser": "Internet Explorer 5.0",
+ * "platform": "Win 95+",
+ * "version": 5,
+ * "grade": "C"
+ * }
+ * ],
+ * "columns": [
+ * { "title": "Engine", "data": "engine" },
+ * { "title": "Browser", "data": "browser" },
+ * { "title": "Platform", "data": "platform" },
+ * { "title": "Version", "data": "version" },
+ * { "title": "Grade", "data": "grade" }
+ * ]
+ * } );
+ * } );
+ */
+ "aaData": null,
+
+
+ /**
+ * If ordering is enabled, then DataTables will perform a first pass sort on
+ * initialisation. You can define which column(s) the sort is performed
+ * upon, and the sorting direction, with this variable. The `sorting` array
+ * should contain an array for each column to be sorted initially containing
+ * the column's index and a direction string ('asc' or 'desc').
+ * @type array
+ * @default [[0,'asc']]
+ *
+ * @dtopt Option
+ * @name DataTable.defaults.order
+ *
+ * @example
+ * // Sort by 3rd column first, and then 4th column
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "order": [[2,'asc'], [3,'desc']]
+ * } );
+ * } );
+ *
+ * // No initial sorting
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "order": []
+ * } );
+ * } );
+ */
+ "aaSorting": [[0,'asc']],
+
+
+ /**
+ * This parameter is basically identical to the `sorting` parameter, but
+ * cannot be overridden by user interaction with the table. What this means
+ * is that you could have a column (visible or hidden) which the sorting
+ * will always be forced on first - any sorting after that (from the user)
+ * will then be performed as required. This can be useful for grouping rows
+ * together.
+ * @type array
+ * @default null
+ *
+ * @dtopt Option
+ * @name DataTable.defaults.orderFixed
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "orderFixed": [[0,'asc']]
+ * } );
+ * } )
+ */
+ "aaSortingFixed": [],
+
+
+ /**
+ * DataTables can be instructed to load data to display in the table from a
+ * Ajax source. This option defines how that Ajax call is made and where to.
+ *
+ * The `ajax` property has three different modes of operation, depending on
+ * how it is defined. These are:
+ *
+ * * `string` - Set the URL from where the data should be loaded from.
+ * * `object` - Define properties for `jQuery.ajax`.
+ * * `function` - Custom data get function
+ *
+ * `string`
+ * --------
+ *
+ * As a string, the `ajax` property simply defines the URL from which
+ * DataTables will load data.
+ *
+ * `object`
+ * --------
+ *
+ * As an object, the parameters in the object are passed to
+ * [jQuery.ajax](http://api.jquery.com/jQuery.ajax/) allowing fine control
+ * of the Ajax request. DataTables has a number of default parameters which
+ * you can override using this option. Please refer to the jQuery
+ * documentation for a full description of the options available, although
+ * the following parameters provide additional options in DataTables or
+ * require special consideration:
+ *
+ * * `data` - As with jQuery, `data` can be provided as an object, but it
+ * can also be used as a function to manipulate the data DataTables sends
+ * to the server. The function takes a single parameter, an object of
+ * parameters with the values that DataTables has readied for sending. An
+ * object may be returned which will be merged into the DataTables
+ * defaults, or you can add the items to the object that was passed in and
+ * not return anything from the function. This supersedes `fnServerParams`
+ * from DataTables 1.9-.
+ *
+ * * `dataSrc` - By default DataTables will look for the property `data` (or
+ * `aaData` for compatibility with DataTables 1.9-) when obtaining data
+ * from an Ajax source or for server-side processing - this parameter
+ * allows that property to be changed. You can use Javascript dotted
+ * object notation to get a data source for multiple levels of nesting, or
+ * it my be used as a function. As a function it takes a single parameter,
+ * the JSON returned from the server, which can be manipulated as
+ * required, with the returned value being that used by DataTables as the
+ * data source for the table. This supersedes `sAjaxDataProp` from
+ * DataTables 1.9-.
+ *
+ * * `success` - Should not be overridden it is used internally in
+ * DataTables. To manipulate / transform the data returned by the server
+ * use `ajax.dataSrc`, or use `ajax` as a function (see below).
+ *
+ * `function`
+ * ----------
+ *
+ * As a function, making the Ajax call is left up to yourself allowing
+ * complete control of the Ajax request. Indeed, if desired, a method other
+ * than Ajax could be used to obtain the required data, such as Web storage
+ * or an AIR database.
+ *
+ * The function is given four parameters and no return is required. The
+ * parameters are:
+ *
+ * 1. _object_ - Data to send to the server
+ * 2. _function_ - Callback function that must be executed when the required
+ * data has been obtained. That data should be passed into the callback
+ * as the only parameter
+ * 3. _object_ - DataTables settings object for the table
+ *
+ * Note that this supersedes `fnServerData` from DataTables 1.9-.
+ *
+ * @type string|object|function
+ * @default null
+ *
+ * @dtopt Option
+ * @name DataTable.defaults.ajax
+ * @since 1.10.0
+ *
+ * @example
+ * // Get JSON data from a file via Ajax.
+ * // Note DataTables expects data in the form `{ data: [ ...data... ] }` by default).
+ * $('#example').dataTable( {
+ * "ajax": "data.json"
+ * } );
+ *
+ * @example
+ * // Get JSON data from a file via Ajax, using `dataSrc` to change
+ * // `data` to `tableData` (i.e. `{ tableData: [ ...data... ] }`)
+ * $('#example').dataTable( {
+ * "ajax": {
+ * "url": "data.json",
+ * "dataSrc": "tableData"
+ * }
+ * } );
+ *
+ * @example
+ * // Get JSON data from a file via Ajax, using `dataSrc` to read data
+ * // from a plain array rather than an array in an object
+ * $('#example').dataTable( {
+ * "ajax": {
+ * "url": "data.json",
+ * "dataSrc": ""
+ * }
+ * } );
+ *
+ * @example
+ * // Manipulate the data returned from the server - add a link to data
+ * // (note this can, should, be done using `render` for the column - this
+ * // is just a simple example of how the data can be manipulated).
+ * $('#example').dataTable( {
+ * "ajax": {
+ * "url": "data.json",
+ * "dataSrc": function ( json ) {
+ * for ( var i=0, ien=json.length ; i<ien ; i++ ) {
+ * json[i][0] = '<a href="/message/'+json[i][0]+'>View message</a>';
+ * }
+ * return json;
+ * }
+ * }
+ * } );
+ *
+ * @example
+ * // Add data to the request
+ * $('#example').dataTable( {
+ * "ajax": {
+ * "url": "data.json",
+ * "data": function ( d ) {
+ * return {
+ * "extra_search": $('#extra').val()
+ * };
+ * }
+ * }
+ * } );
+ *
+ * @example
+ * // Send request as POST
+ * $('#example').dataTable( {
+ * "ajax": {
+ * "url": "data.json",
+ * "type": "POST"
+ * }
+ * } );
+ *
+ * @example
+ * // Get the data from localStorage (could interface with a form for
+ * // adding, editing and removing rows).
+ * $('#example').dataTable( {
+ * "ajax": function (data, callback, settings) {
+ * callback(
+ * JSON.parse( localStorage.getItem('dataTablesData') )
+ * );
+ * }
+ * } );
+ */
+ "ajax": null,
+
+
+ /**
+ * This parameter allows you to readily specify the entries in the length drop
+ * down menu that DataTables shows when pagination is enabled. It can be
+ * either a 1D array of options which will be used for both the displayed
+ * option and the value, or a 2D array which will use the array in the first
+ * position as the value, and the array in the second position as the
+ * displayed options (useful for language strings such as 'All').
+ *
+ * Note that the `pageLength` property will be automatically set to the
+ * first value given in this array, unless `pageLength` is also provided.
+ * @type array
+ * @default [ 10, 25, 50, 100 ]
+ *
+ * @dtopt Option
+ * @name DataTable.defaults.lengthMenu
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "lengthMenu": [[10, 25, 50, -1], [10, 25, 50, "All"]]
+ * } );
+ * } );
+ */
+ "aLengthMenu": [ 10, 25, 50, 100 ],
+
+
+ /**
+ * The `columns` option in the initialisation parameter allows you to define
+ * details about the way individual columns behave. For a full list of
+ * column options that can be set, please see
+ * {@link DataTable.defaults.column}. Note that if you use `columns` to
+ * define your columns, you must have an entry in the array for every single
+ * column that you have in your table (these can be null if you don't which
+ * to specify any options).
+ * @member
+ *
+ * @name DataTable.defaults.column
+ */
+ "aoColumns": null,
+
+ /**
+ * Very similar to `columns`, `columnDefs` allows you to target a specific
+ * column, multiple columns, or all columns, using the `targets` property of
+ * each object in the array. This allows great flexibility when creating
+ * tables, as the `columnDefs` arrays can be of any length, targeting the
+ * columns you specifically want. `columnDefs` may use any of the column
+ * options available: {@link DataTable.defaults.column}, but it _must_
+ * have `targets` defined in each object in the array. Values in the `targets`
+ * array may be:
+ * <ul>
+ * <li>a string - class name will be matched on the TH for the column</li>
+ * <li>0 or a positive integer - column index counting from the left</li>
+ * <li>a negative integer - column index counting from the right</li>
+ * <li>the string "_all" - all columns (i.e. assign a default)</li>
+ * </ul>
+ * @member
+ *
+ * @name DataTable.defaults.columnDefs
+ */
+ "aoColumnDefs": null,
+
+
+ /**
+ * Basically the same as `search`, this parameter defines the individual column
+ * filtering state at initialisation time. The array must be of the same size
+ * as the number of columns, and each element be an object with the parameters
+ * `search` and `escapeRegex` (the latter is optional). 'null' is also
+ * accepted and the default will be used.
+ * @type array
+ * @default []
+ *
+ * @dtopt Option
+ * @name DataTable.defaults.searchCols
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "searchCols": [
+ * null,
+ * { "search": "My filter" },
+ * null,
+ * { "search": "^[0-9]", "escapeRegex": false }
+ * ]
+ * } );
+ * } )
+ */
+ "aoSearchCols": [],
+
+
+ /**
+ * An array of CSS classes that should be applied to displayed rows. This
+ * array may be of any length, and DataTables will apply each class
+ * sequentially, looping when required.
+ * @type array
+ * @default null <i>Will take the values determined by the `oClasses.stripe*`
+ * options</i>
+ *
+ * @dtopt Option
+ * @name DataTable.defaults.stripeClasses
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "stripeClasses": [ 'strip1', 'strip2', 'strip3' ]
+ * } );
+ * } )
+ */
+ "asStripeClasses": null,
+
+
+ /**
+ * Enable or disable automatic column width calculation. This can be disabled
+ * as an optimisation (it takes some time to calculate the widths) if the
+ * tables widths are passed in using `columns`.
+ * @type boolean
+ * @default true
+ *
+ * @dtopt Features
+ * @name DataTable.defaults.autoWidth
+ *
+ * @example
+ * $(document).ready( function () {
+ * $('#example').dataTable( {
+ * "autoWidth": false
+ * } );
+ * } );
+ */
+ "bAutoWidth": true,
+
+
+ /**
+ * Deferred rendering can provide DataTables with a huge speed boost when you
+ * are using an Ajax or JS data source for the table. This option, when set to
+ * true, will cause DataTables to defer the creation of the table elements for
+ * each row until they are needed for a draw - saving a significant amount of
+ * time.
+ * @type boolean
+ * @default false
+ *
+ * @dtopt Features
+ * @name DataTable.defaults.deferRender
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "ajax": "sources/arrays.txt",
+ * "deferRender": true
+ * } );
+ * } );
+ */
+ "bDeferRender": false,
+
+
+ /**
+ * Replace a DataTable which matches the given selector and replace it with
+ * one which has the properties of the new initialisation object passed. If no
+ * table matches the selector, then the new DataTable will be constructed as
+ * per normal.
+ * @type boolean
+ * @default false
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.destroy
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "srollY": "200px",
+ * "paginate": false
+ * } );
+ *
+ * // Some time later....
+ * $('#example').dataTable( {
+ * "filter": false,
+ * "destroy": true
+ * } );
+ * } );
+ */
+ "bDestroy": false,
+
+
+ /**
+ * Enable or disable filtering of data. Filtering in DataTables is "smart" in
+ * that it allows the end user to input multiple words (space separated) and
+ * will match a row containing those words, even if not in the order that was
+ * specified (this allow matching across multiple columns). Note that if you
+ * wish to use filtering in DataTables this must remain 'true' - to remove the
+ * default filtering input box and retain filtering abilities, please use
+ * {@link DataTable.defaults.dom}.
+ * @type boolean
+ * @default true
+ *
+ * @dtopt Features
+ * @name DataTable.defaults.searching
+ *
+ * @example
+ * $(document).ready( function () {
+ * $('#example').dataTable( {
+ * "searching": false
+ * } );
+ * } );
+ */
+ "bFilter": true,
+
+
+ /**
+ * Enable or disable the table information display. This shows information
+ * about the data that is currently visible on the page, including information
+ * about filtered data if that action is being performed.
+ * @type boolean
+ * @default true
+ *
+ * @dtopt Features
+ * @name DataTable.defaults.info
+ *
+ * @example
+ * $(document).ready( function () {
+ * $('#example').dataTable( {
+ * "info": false
+ * } );
+ * } );
+ */
+ "bInfo": true,
+
+
+ /**
+ * Enable jQuery UI ThemeRoller support (required as ThemeRoller requires some
+ * slightly different and additional mark-up from what DataTables has
+ * traditionally used).
+ * @type boolean
+ * @default false
+ *
+ * @dtopt Features
+ * @name DataTable.defaults.jQueryUI
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "jQueryUI": true
+ * } );
+ * } );
+ */
+ "bJQueryUI": false,
+
+
+ /**
+ * Allows the end user to select the size of a formatted page from a select
+ * menu (sizes are 10, 25, 50 and 100). Requires pagination (`paginate`).
+ * @type boolean
+ * @default true
+ *
+ * @dtopt Features
+ * @name DataTable.defaults.lengthChange
+ *
+ * @example
+ * $(document).ready( function () {
+ * $('#example').dataTable( {
+ * "lengthChange": false
+ * } );
+ * } );
+ */
+ "bLengthChange": true,
+
+
+ /**
+ * Enable or disable pagination.
+ * @type boolean
+ * @default true
+ *
+ * @dtopt Features
+ * @name DataTable.defaults.paging
+ *
+ * @example
+ * $(document).ready( function () {
+ * $('#example').dataTable( {
+ * "paging": false
+ * } );
+ * } );
+ */
+ "bPaginate": true,
+
+
+ /**
+ * Enable or disable the display of a 'processing' indicator when the table is
+ * being processed (e.g. a sort). This is particularly useful for tables with
+ * large amounts of data where it can take a noticeable amount of time to sort
+ * the entries.
+ * @type boolean
+ * @default false
+ *
+ * @dtopt Features
+ * @name DataTable.defaults.processing
+ *
+ * @example
+ * $(document).ready( function () {
+ * $('#example').dataTable( {
+ * "processing": true
+ * } );
+ * } );
+ */
+ "bProcessing": false,
+
+
+ /**
+ * Retrieve the DataTables object for the given selector. Note that if the
+ * table has already been initialised, this parameter will cause DataTables
+ * to simply return the object that has already been set up - it will not take
+ * account of any changes you might have made to the initialisation object
+ * passed to DataTables (setting this parameter to true is an acknowledgement
+ * that you understand this). `destroy` can be used to reinitialise a table if
+ * you need.
+ * @type boolean
+ * @default false
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.retrieve
+ *
+ * @example
+ * $(document).ready( function() {
+ * initTable();
+ * tableActions();
+ * } );
+ *
+ * function initTable ()
+ * {
+ * return $('#example').dataTable( {
+ * "scrollY": "200px",
+ * "paginate": false,
+ * "retrieve": true
+ * } );
+ * }
+ *
+ * function tableActions ()
+ * {
+ * var table = initTable();
+ * // perform API operations with oTable
+ * }
+ */
+ "bRetrieve": false,
+
+
+ /**
+ * When vertical (y) scrolling is enabled, DataTables will force the height of
+ * the table's viewport to the given height at all times (useful for layout).
+ * However, this can look odd when filtering data down to a small data set,
+ * and the footer is left "floating" further down. This parameter (when
+ * enabled) will cause DataTables to collapse the table's viewport down when
+ * the result set will fit within the given Y height.
+ * @type boolean
+ * @default false
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.scrollCollapse
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "scrollY": "200",
+ * "scrollCollapse": true
+ * } );
+ * } );
+ */
+ "bScrollCollapse": false,
+
+
+ /**
+ * Configure DataTables to use server-side processing. Note that the
+ * `ajax` parameter must also be given in order to give DataTables a
+ * source to obtain the required data for each draw.
+ * @type boolean
+ * @default false
+ *
+ * @dtopt Features
+ * @dtopt Server-side
+ * @name DataTable.defaults.serverSide
+ *
+ * @example
+ * $(document).ready( function () {
+ * $('#example').dataTable( {
+ * "serverSide": true,
+ * "ajax": "xhr.php"
+ * } );
+ * } );
+ */
+ "bServerSide": false,
+
+
+ /**
+ * Enable or disable sorting of columns. Sorting of individual columns can be
+ * disabled by the `sortable` option for each column.
+ * @type boolean
+ * @default true
+ *
+ * @dtopt Features
+ * @name DataTable.defaults.ordering
+ *
+ * @example
+ * $(document).ready( function () {
+ * $('#example').dataTable( {
+ * "ordering": false
+ * } );
+ * } );
+ */
+ "bSort": true,
+
+
+ /**
+ * Enable or display DataTables' ability to sort multiple columns at the
+ * same time (activated by shift-click by the user).
+ * @type boolean
+ * @default true
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.orderMulti
+ *
+ * @example
+ * // Disable multiple column sorting ability
+ * $(document).ready( function () {
+ * $('#example').dataTable( {
+ * "orderMulti": false
+ * } );
+ * } );
+ */
+ "bSortMulti": true,
+
+
+ /**
+ * Allows control over whether DataTables should use the top (true) unique
+ * cell that is found for a single column, or the bottom (false - default).
+ * This is useful when using complex headers.
+ * @type boolean
+ * @default false
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.orderCellsTop
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "orderCellsTop": true
+ * } );
+ * } );
+ */
+ "bSortCellsTop": false,
+
+
+ /**
+ * Enable or disable the addition of the classes `sorting\_1`, `sorting\_2` and
+ * `sorting\_3` to the columns which are currently being sorted on. This is
+ * presented as a feature switch as it can increase processing time (while
+ * classes are removed and added) so for large data sets you might want to
+ * turn this off.
+ * @type boolean
+ * @default true
+ *
+ * @dtopt Features
+ * @name DataTable.defaults.orderClasses
+ *
+ * @example
+ * $(document).ready( function () {
+ * $('#example').dataTable( {
+ * "orderClasses": false
+ * } );
+ * } );
+ */
+ "bSortClasses": true,
+
+
+ /**
+ * Enable or disable state saving. When enabled HTML5 `localStorage` will be
+ * used to save table display information such as pagination information,
+ * display length, filtering and sorting. As such when the end user reloads
+ * the page the display display will match what thy had previously set up.
+ *
+ * Due to the use of `localStorage` the default state saving is not supported
+ * in IE6 or 7. If state saving is required in those browsers, use
+ * `stateSaveCallback` to provide a storage solution such as cookies.
+ * @type boolean
+ * @default false
+ *
+ * @dtopt Features
+ * @name DataTable.defaults.stateSave
+ *
+ * @example
+ * $(document).ready( function () {
+ * $('#example').dataTable( {
+ * "stateSave": true
+ * } );
+ * } );
+ */
+ "bStateSave": false,
+
+
+ /**
+ * This function is called when a TR element is created (and all TD child
+ * elements have been inserted), or registered if using a DOM source, allowing
+ * manipulation of the TR element (adding classes etc).
+ * @type function
+ * @param {node} row "TR" element for the current row
+ * @param {array} data Raw data array for this row
+ * @param {int} dataIndex The index of this row in the internal aoData array
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.createdRow
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "createdRow": function( row, data, dataIndex ) {
+ * // Bold the grade for all 'A' grade browsers
+ * if ( data[4] == "A" )
+ * {
+ * $('td:eq(4)', row).html( '<b>A</b>' );
+ * }
+ * }
+ * } );
+ * } );
+ */
+ "fnCreatedRow": null,
+
+
+ /**
+ * This function is called on every 'draw' event, and allows you to
+ * dynamically modify any aspect you want about the created DOM.
+ * @type function
+ * @param {object} settings DataTables settings object
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.drawCallback
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "drawCallback": function( settings ) {
+ * alert( 'DataTables has redrawn the table' );
+ * }
+ * } );
+ * } );
+ */
+ "fnDrawCallback": null,
+
+
+ /**
+ * Identical to fnHeaderCallback() but for the table footer this function
+ * allows you to modify the table footer on every 'draw' event.
+ * @type function
+ * @param {node} foot "TR" element for the footer
+ * @param {array} data Full table data (as derived from the original HTML)
+ * @param {int} start Index for the current display starting point in the
+ * display array
+ * @param {int} end Index for the current display ending point in the
+ * display array
+ * @param {array int} display Index array to translate the visual position
+ * to the full data array
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.footerCallback
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "footerCallback": function( tfoot, data, start, end, display ) {
+ * tfoot.getElementsByTagName('th')[0].innerHTML = "Starting index is "+start;
+ * }
+ * } );
+ * } )
+ */
+ "fnFooterCallback": null,
+
+
+ /**
+ * When rendering large numbers in the information element for the table
+ * (i.e. "Showing 1 to 10 of 57 entries") DataTables will render large numbers
+ * to have a comma separator for the 'thousands' units (e.g. 1 million is
+ * rendered as "1,000,000") to help readability for the end user. This
+ * function will override the default method DataTables uses.
+ * @type function
+ * @member
+ * @param {int} toFormat number to be formatted
+ * @returns {string} formatted string for DataTables to show the number
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.formatNumber
+ *
+ * @example
+ * // Format a number using a single quote for the separator (note that
+ * // this can also be done with the language.thousands option)
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "formatNumber": function ( toFormat ) {
+ * return toFormat.toString().replace(
+ * /\B(?=(\d{3})+(?!\d))/g, "'"
+ * );
+ * };
+ * } );
+ * } );
+ */
+ "fnFormatNumber": function ( toFormat ) {
+ return toFormat.toString().replace(
+ /\B(?=(\d{3})+(?!\d))/g,
+ this.oLanguage.sThousands
+ );
+ },
+
+
+ /**
+ * This function is called on every 'draw' event, and allows you to
+ * dynamically modify the header row. This can be used to calculate and
+ * display useful information about the table.
+ * @type function
+ * @param {node} head "TR" element for the header
+ * @param {array} data Full table data (as derived from the original HTML)
+ * @param {int} start Index for the current display starting point in the
+ * display array
+ * @param {int} end Index for the current display ending point in the
+ * display array
+ * @param {array int} display Index array to translate the visual position
+ * to the full data array
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.headerCallback
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "fheaderCallback": function( head, data, start, end, display ) {
+ * head.getElementsByTagName('th')[0].innerHTML = "Displaying "+(end-start)+" records";
+ * }
+ * } );
+ * } )
+ */
+ "fnHeaderCallback": null,
+
+
+ /**
+ * The information element can be used to convey information about the current
+ * state of the table. Although the internationalisation options presented by
+ * DataTables are quite capable of dealing with most customisations, there may
+ * be times where you wish to customise the string further. This callback
+ * allows you to do exactly that.
+ * @type function
+ * @param {object} oSettings DataTables settings object
+ * @param {int} start Starting position in data for the draw
+ * @param {int} end End position in data for the draw
+ * @param {int} max Total number of rows in the table (regardless of
+ * filtering)
+ * @param {int} total Total number of rows in the data set, after filtering
+ * @param {string} pre The string that DataTables has formatted using it's
+ * own rules
+ * @returns {string} The string to be displayed in the information element.
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.infoCallback
+ *
+ * @example
+ * $('#example').dataTable( {
+ * "infoCallback": function( settings, start, end, max, total, pre ) {
+ * return start +" to "+ end;
+ * }
+ * } );
+ */
+ "fnInfoCallback": null,
+
+
+ /**
+ * Called when the table has been initialised. Normally DataTables will
+ * initialise sequentially and there will be no need for this function,
+ * however, this does not hold true when using external language information
+ * since that is obtained using an async XHR call.
+ * @type function
+ * @param {object} settings DataTables settings object
+ * @param {object} json The JSON object request from the server - only
+ * present if client-side Ajax sourced data is used
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.initComplete
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "initComplete": function(settings, json) {
+ * alert( 'DataTables has finished its initialisation.' );
+ * }
+ * } );
+ * } )
+ */
+ "fnInitComplete": null,
+
+
+ /**
+ * Called at the very start of each table draw and can be used to cancel the
+ * draw by returning false, any other return (including undefined) results in
+ * the full draw occurring).
+ * @type function
+ * @param {object} settings DataTables settings object
+ * @returns {boolean} False will cancel the draw, anything else (including no
+ * return) will allow it to complete.
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.preDrawCallback
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "preDrawCallback": function( settings ) {
+ * if ( $('#test').val() == 1 ) {
+ * return false;
+ * }
+ * }
+ * } );
+ * } );
+ */
+ "fnPreDrawCallback": null,
+
+
+ /**
+ * This function allows you to 'post process' each row after it have been
+ * generated for each table draw, but before it is rendered on screen. This
+ * function might be used for setting the row class name etc.
+ * @type function
+ * @param {node} row "TR" element for the current row
+ * @param {array} data Raw data array for this row
+ * @param {int} displayIndex The display index for the current table draw
+ * @param {int} displayIndexFull The index of the data in the full list of
+ * rows (after filtering)
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.rowCallback
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "rowCallback": function( row, data, displayIndex, displayIndexFull ) {
+ * // Bold the grade for all 'A' grade browsers
+ * if ( data[4] == "A" ) {
+ * $('td:eq(4)', row).html( '<b>A</b>' );
+ * }
+ * }
+ * } );
+ * } );
+ */
+ "fnRowCallback": null,
+
+
+ /**
+ * __Deprecated__ The functionality provided by this parameter has now been
+ * superseded by that provided through `ajax`, which should be used instead.
+ *
+ * This parameter allows you to override the default function which obtains
+ * the data from the server so something more suitable for your application.
+ * For example you could use POST data, or pull information from a Gears or
+ * AIR database.
+ * @type function
+ * @member
+ * @param {string} source HTTP source to obtain the data from (`ajax`)
+ * @param {array} data A key/value pair object containing the data to send
+ * to the server
+ * @param {function} callback to be called on completion of the data get
+ * process that will draw the data on the page.
+ * @param {object} settings DataTables settings object
+ *
+ * @dtopt Callbacks
+ * @dtopt Server-side
+ * @name DataTable.defaults.serverData
+ *
+ * @deprecated 1.10. Please use `ajax` for this functionality now.
+ */
+ "fnServerData": null,
+
+
+ /**
+ * __Deprecated__ The functionality provided by this parameter has now been
+ * superseded by that provided through `ajax`, which should be used instead.
+ *
+ * It is often useful to send extra data to the server when making an Ajax
+ * request - for example custom filtering information, and this callback
+ * function makes it trivial to send extra information to the server. The
+ * passed in parameter is the data set that has been constructed by
+ * DataTables, and you can add to this or modify it as you require.
+ * @type function
+ * @param {array} data Data array (array of objects which are name/value
+ * pairs) that has been constructed by DataTables and will be sent to the
+ * server. In the case of Ajax sourced data with server-side processing
+ * this will be an empty array, for server-side processing there will be a
+ * significant number of parameters!
+ * @returns {undefined} Ensure that you modify the data array passed in,
+ * as this is passed by reference.
+ *
+ * @dtopt Callbacks
+ * @dtopt Server-side
+ * @name DataTable.defaults.serverParams
+ *
+ * @deprecated 1.10. Please use `ajax` for this functionality now.
+ */
+ "fnServerParams": null,
+
+
+ /**
+ * Load the table state. With this function you can define from where, and how, the
+ * state of a table is loaded. By default DataTables will load from `localStorage`
+ * but you might wish to use a server-side database or cookies.
+ * @type function
+ * @member
+ * @param {object} settings DataTables settings object
+ * @param {object} callback Callback that can be executed when done. It
+ * should be passed the loaded state object.
+ * @return {object} The DataTables state object to be loaded
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.stateLoadCallback
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "stateSave": true,
+ * "stateLoadCallback": function (settings, callback) {
+ * $.ajax( {
+ * "url": "/state_load",
+ * "dataType": "json",
+ * "success": function (json) {
+ * callback( json );
+ * }
+ * } );
+ * }
+ * } );
+ * } );
+ */
+ "fnStateLoadCallback": function ( settings ) {
+ try {
+ return JSON.parse(
+ (settings.iStateDuration === -1 ? sessionStorage : localStorage).getItem(
+ 'DataTables_'+settings.sInstance+'_'+location.pathname
+ )
+ );
+ } catch (e) {}
+ },
+
+
+ /**
+ * Callback which allows modification of the saved state prior to loading that state.
+ * This callback is called when the table is loading state from the stored data, but
+ * prior to the settings object being modified by the saved state. Note that for
+ * plug-in authors, you should use the `stateLoadParams` event to load parameters for
+ * a plug-in.
+ * @type function
+ * @param {object} settings DataTables settings object
+ * @param {object} data The state object that is to be loaded
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.stateLoadParams
+ *
+ * @example
+ * // Remove a saved filter, so filtering is never loaded
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "stateSave": true,
+ * "stateLoadParams": function (settings, data) {
+ * data.oSearch.sSearch = "";
+ * }
+ * } );
+ * } );
+ *
+ * @example
+ * // Disallow state loading by returning false
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "stateSave": true,
+ * "stateLoadParams": function (settings, data) {
+ * return false;
+ * }
+ * } );
+ * } );
+ */
+ "fnStateLoadParams": null,
+
+
+ /**
+ * Callback that is called when the state has been loaded from the state saving method
+ * and the DataTables settings object has been modified as a result of the loaded state.
+ * @type function
+ * @param {object} settings DataTables settings object
+ * @param {object} data The state object that was loaded
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.stateLoaded
+ *
+ * @example
+ * // Show an alert with the filtering value that was saved
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "stateSave": true,
+ * "stateLoaded": function (settings, data) {
+ * alert( 'Saved filter was: '+data.oSearch.sSearch );
+ * }
+ * } );
+ * } );
+ */
+ "fnStateLoaded": null,
+
+
+ /**
+ * Save the table state. This function allows you to define where and how the state
+ * information for the table is stored By default DataTables will use `localStorage`
+ * but you might wish to use a server-side database or cookies.
+ * @type function
+ * @member
+ * @param {object} settings DataTables settings object
+ * @param {object} data The state object to be saved
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.stateSaveCallback
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "stateSave": true,
+ * "stateSaveCallback": function (settings, data) {
+ * // Send an Ajax request to the server with the state object
+ * $.ajax( {
+ * "url": "/state_save",
+ * "data": data,
+ * "dataType": "json",
+ * "method": "POST"
+ * "success": function () {}
+ * } );
+ * }
+ * } );
+ * } );
+ */
+ "fnStateSaveCallback": function ( settings, data ) {
+ try {
+ (settings.iStateDuration === -1 ? sessionStorage : localStorage).setItem(
+ 'DataTables_'+settings.sInstance+'_'+location.pathname,
+ JSON.stringify( data )
+ );
+ } catch (e) {}
+ },
+
+
+ /**
+ * Callback which allows modification of the state to be saved. Called when the table
+ * has changed state a new state save is required. This method allows modification of
+ * the state saving object prior to actually doing the save, including addition or
+ * other state properties or modification. Note that for plug-in authors, you should
+ * use the `stateSaveParams` event to save parameters for a plug-in.
+ * @type function
+ * @param {object} settings DataTables settings object
+ * @param {object} data The state object to be saved
+ *
+ * @dtopt Callbacks
+ * @name DataTable.defaults.stateSaveParams
+ *
+ * @example
+ * // Remove a saved filter, so filtering is never saved
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "stateSave": true,
+ * "stateSaveParams": function (settings, data) {
+ * data.oSearch.sSearch = "";
+ * }
+ * } );
+ * } );
+ */
+ "fnStateSaveParams": null,
+
+
+ /**
+ * Duration for which the saved state information is considered valid. After this period
+ * has elapsed the state will be returned to the default.
+ * Value is given in seconds.
+ * @type int
+ * @default 7200 <i>(2 hours)</i>
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.stateDuration
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "stateDuration": 60*60*24; // 1 day
+ * } );
+ * } )
+ */
+ "iStateDuration": 7200,
+
+
+ /**
+ * When enabled DataTables will not make a request to the server for the first
+ * page draw - rather it will use the data already on the page (no sorting etc
+ * will be applied to it), thus saving on an XHR at load time. `deferLoading`
+ * is used to indicate that deferred loading is required, but it is also used
+ * to tell DataTables how many records there are in the full table (allowing
+ * the information element and pagination to be displayed correctly). In the case
+ * where a filtering is applied to the table on initial load, this can be
+ * indicated by giving the parameter as an array, where the first element is
+ * the number of records available after filtering and the second element is the
+ * number of records without filtering (allowing the table information element
+ * to be shown correctly).
+ * @type int | array
+ * @default null
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.deferLoading
+ *
+ * @example
+ * // 57 records available in the table, no filtering applied
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "serverSide": true,
+ * "ajax": "scripts/server_processing.php",
+ * "deferLoading": 57
+ * } );
+ * } );
+ *
+ * @example
+ * // 57 records after filtering, 100 without filtering (an initial filter applied)
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "serverSide": true,
+ * "ajax": "scripts/server_processing.php",
+ * "deferLoading": [ 57, 100 ],
+ * "search": {
+ * "search": "my_filter"
+ * }
+ * } );
+ * } );
+ */
+ "iDeferLoading": null,
+
+
+ /**
+ * Number of rows to display on a single page when using pagination. If
+ * feature enabled (`lengthChange`) then the end user will be able to override
+ * this to a custom setting using a pop-up menu.
+ * @type int
+ * @default 10
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.pageLength
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "pageLength": 50
+ * } );
+ * } )
+ */
+ "iDisplayLength": 10,
+
+
+ /**
+ * Define the starting point for data display when using DataTables with
+ * pagination. Note that this parameter is the number of records, rather than
+ * the page number, so if you have 10 records per page and want to start on
+ * the third page, it should be "20".
+ * @type int
+ * @default 0
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.displayStart
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "displayStart": 20
+ * } );
+ * } )
+ */
+ "iDisplayStart": 0,
+
+
+ /**
+ * By default DataTables allows keyboard navigation of the table (sorting, paging,
+ * and filtering) by adding a `tabindex` attribute to the required elements. This
+ * allows you to tab through the controls and press the enter key to activate them.
+ * The tabindex is default 0, meaning that the tab follows the flow of the document.
+ * You can overrule this using this parameter if you wish. Use a value of -1 to
+ * disable built-in keyboard navigation.
+ * @type int
+ * @default 0
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.tabIndex
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "tabIndex": 1
+ * } );
+ * } );
+ */
+ "iTabIndex": 0,
+
+
+ /**
+ * Classes that DataTables assigns to the various components and features
+ * that it adds to the HTML table. This allows classes to be configured
+ * during initialisation in addition to through the static
+ * {@link DataTable.ext.oStdClasses} object).
+ * @namespace
+ * @name DataTable.defaults.classes
+ */
+ "oClasses": {},
+
+
+ /**
+ * All strings that DataTables uses in the user interface that it creates
+ * are defined in this object, allowing you to modified them individually or
+ * completely replace them all as required.
+ * @namespace
+ * @name DataTable.defaults.language
+ */
+ "oLanguage": {
+ /**
+ * Strings that are used for WAI-ARIA labels and controls only (these are not
+ * actually visible on the page, but will be read by screenreaders, and thus
+ * must be internationalised as well).
+ * @namespace
+ * @name DataTable.defaults.language.aria
+ */
+ "oAria": {
+ /**
+ * ARIA label that is added to the table headers when the column may be
+ * sorted ascending by activing the column (click or return when focused).
+ * Note that the column header is prefixed to this string.
+ * @type string
+ * @default : activate to sort column ascending
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.aria.sortAscending
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "aria": {
+ * "sortAscending": " - click/return to sort ascending"
+ * }
+ * }
+ * } );
+ * } );
+ */
+ "sSortAscending": ": activate to sort column ascending",
+
+ /**
+ * ARIA label that is added to the table headers when the column may be
+ * sorted descending by activing the column (click or return when focused).
+ * Note that the column header is prefixed to this string.
+ * @type string
+ * @default : activate to sort column ascending
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.aria.sortDescending
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "aria": {
+ * "sortDescending": " - click/return to sort descending"
+ * }
+ * }
+ * } );
+ * } );
+ */
+ "sSortDescending": ": activate to sort column descending"
+ },
+
+ /**
+ * Pagination string used by DataTables for the built-in pagination
+ * control types.
+ * @namespace
+ * @name DataTable.defaults.language.paginate
+ */
+ "oPaginate": {
+ /**
+ * Text to use when using the 'full_numbers' type of pagination for the
+ * button to take the user to the first page.
+ * @type string
+ * @default First
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.paginate.first
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "paginate": {
+ * "first": "First page"
+ * }
+ * }
+ * } );
+ * } );
+ */
+ "sFirst": "First",
+
+
+ /**
+ * Text to use when using the 'full_numbers' type of pagination for the
+ * button to take the user to the last page.
+ * @type string
+ * @default Last
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.paginate.last
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "paginate": {
+ * "last": "Last page"
+ * }
+ * }
+ * } );
+ * } );
+ */
+ "sLast": "Last",
+
+
+ /**
+ * Text to use for the 'next' pagination button (to take the user to the
+ * next page).
+ * @type string
+ * @default Next
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.paginate.next
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "paginate": {
+ * "next": "Next page"
+ * }
+ * }
+ * } );
+ * } );
+ */
+ "sNext": "Next",
+
+
+ /**
+ * Text to use for the 'previous' pagination button (to take the user to
+ * the previous page).
+ * @type string
+ * @default Previous
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.paginate.previous
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "paginate": {
+ * "previous": "Previous page"
+ * }
+ * }
+ * } );
+ * } );
+ */
+ "sPrevious": "Previous"
+ },
+
+ /**
+ * This string is shown in preference to `zeroRecords` when the table is
+ * empty of data (regardless of filtering). Note that this is an optional
+ * parameter - if it is not given, the value of `zeroRecords` will be used
+ * instead (either the default or given value).
+ * @type string
+ * @default No data available in table
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.emptyTable
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "emptyTable": "No data available in table"
+ * }
+ * } );
+ * } );
+ */
+ "sEmptyTable": "No data available in table",
+
+
+ /**
+ * This string gives information to the end user about the information
+ * that is current on display on the page. The following tokens can be
+ * used in the string and will be dynamically replaced as the table
+ * display updates. This tokens can be placed anywhere in the string, or
+ * removed as needed by the language requires:
+ *
+ * * `\_START\_` - Display index of the first record on the current page
+ * * `\_END\_` - Display index of the last record on the current page
+ * * `\_TOTAL\_` - Number of records in the table after filtering
+ * * `\_MAX\_` - Number of records in the table without filtering
+ * * `\_PAGE\_` - Current page number
+ * * `\_PAGES\_` - Total number of pages of data in the table
+ *
+ * @type string
+ * @default Showing _START_ to _END_ of _TOTAL_ entries
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.info
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "info": "Showing page _PAGE_ of _PAGES_"
+ * }
+ * } );
+ * } );
+ */
+ "sInfo": "Showing _START_ to _END_ of _TOTAL_ entries",
+
+
+ /**
+ * Display information string for when the table is empty. Typically the
+ * format of this string should match `info`.
+ * @type string
+ * @default Showing 0 to 0 of 0 entries
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.infoEmpty
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "infoEmpty": "No entries to show"
+ * }
+ * } );
+ * } );
+ */
+ "sInfoEmpty": "Showing 0 to 0 of 0 entries",
+
+
+ /**
+ * When a user filters the information in a table, this string is appended
+ * to the information (`info`) to give an idea of how strong the filtering
+ * is. The variable _MAX_ is dynamically updated.
+ * @type string
+ * @default (filtered from _MAX_ total entries)
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.infoFiltered
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "infoFiltered": " - filtering from _MAX_ records"
+ * }
+ * } );
+ * } );
+ */
+ "sInfoFiltered": "(filtered from _MAX_ total entries)",
+
+
+ /**
+ * If can be useful to append extra information to the info string at times,
+ * and this variable does exactly that. This information will be appended to
+ * the `info` (`infoEmpty` and `infoFiltered` in whatever combination they are
+ * being used) at all times.
+ * @type string
+ * @default <i>Empty string</i>
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.infoPostFix
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "infoPostFix": "All records shown are derived from real information."
+ * }
+ * } );
+ * } );
+ */
+ "sInfoPostFix": "",
+
+
+ /**
+ * This decimal place operator is a little different from the other
+ * language options since DataTables doesn't output floating point
+ * numbers, so it won't ever use this for display of a number. Rather,
+ * what this parameter does is modify the sort methods of the table so
+ * that numbers which are in a format which has a character other than
+ * a period (`.`) as a decimal place will be sorted numerically.
+ *
+ * Note that numbers with different decimal places cannot be shown in
+ * the same table and still be sortable, the table must be consistent.
+ * However, multiple different tables on the page can use different
+ * decimal place characters.
+ * @type string
+ * @default
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.decimal
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "decimal": ","
+ * "thousands": "."
+ * }
+ * } );
+ * } );
+ */
+ "sDecimal": "",
+
+
+ /**
+ * DataTables has a build in number formatter (`formatNumber`) which is
+ * used to format large numbers that are used in the table information.
+ * By default a comma is used, but this can be trivially changed to any
+ * character you wish with this parameter.
+ * @type string
+ * @default ,
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.thousands
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "thousands": "'"
+ * }
+ * } );
+ * } );
+ */
+ "sThousands": ",",
+
+
+ /**
+ * Detail the action that will be taken when the drop down menu for the
+ * pagination length option is changed. The '_MENU_' variable is replaced
+ * with a default select list of 10, 25, 50 and 100, and can be replaced
+ * with a custom select box if required.
+ * @type string
+ * @default Show _MENU_ entries
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.lengthMenu
+ *
+ * @example
+ * // Language change only
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "lengthMenu": "Display _MENU_ records"
+ * }
+ * } );
+ * } );
+ *
+ * @example
+ * // Language and options change
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "lengthMenu": 'Display <select>'+
+ * '<option value="10">10</option>'+
+ * '<option value="20">20</option>'+
+ * '<option value="30">30</option>'+
+ * '<option value="40">40</option>'+
+ * '<option value="50">50</option>'+
+ * '<option value="-1">All</option>'+
+ * '</select> records'
+ * }
+ * } );
+ * } );
+ */
+ "sLengthMenu": "Show _MENU_ entries",
+
+
+ /**
+ * When using Ajax sourced data and during the first draw when DataTables is
+ * gathering the data, this message is shown in an empty row in the table to
+ * indicate to the end user the the data is being loaded. Note that this
+ * parameter is not used when loading data by server-side processing, just
+ * Ajax sourced data with client-side processing.
+ * @type string
+ * @default Loading...
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.loadingRecords
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "loadingRecords": "Please wait - loading..."
+ * }
+ * } );
+ * } );
+ */
+ "sLoadingRecords": "Loading...",
+
+
+ /**
+ * Text which is displayed when the table is processing a user action
+ * (usually a sort command or similar).
+ * @type string
+ * @default Processing...
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.processing
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "processing": "DataTables is currently busy"
+ * }
+ * } );
+ * } );
+ */
+ "sProcessing": "Processing...",
+
+
+ /**
+ * Details the actions that will be taken when the user types into the
+ * filtering input text box. The variable "_INPUT_", if used in the string,
+ * is replaced with the HTML text box for the filtering input allowing
+ * control over where it appears in the string. If "_INPUT_" is not given
+ * then the input box is appended to the string automatically.
+ * @type string
+ * @default Search:
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.search
+ *
+ * @example
+ * // Input text box will be appended at the end automatically
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "search": "Filter records:"
+ * }
+ * } );
+ * } );
+ *
+ * @example
+ * // Specify where the filter should appear
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "search": "Apply filter _INPUT_ to table"
+ * }
+ * } );
+ * } );
+ */
+ "sSearch": "Search:",
+
+
+ /**
+ * Assign a `placeholder` attribute to the search `input` element
+ * @type string
+ * @default
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.searchPlaceholder
+ */
+ "sSearchPlaceholder": "",
+
+
+ /**
+ * All of the language information can be stored in a file on the
+ * server-side, which DataTables will look up if this parameter is passed.
+ * It must store the URL of the language file, which is in a JSON format,
+ * and the object has the same properties as the oLanguage object in the
+ * initialiser object (i.e. the above parameters). Please refer to one of
+ * the example language files to see how this works in action.
+ * @type string
+ * @default <i>Empty string - i.e. disabled</i>
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.url
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "url": "http://www.sprymedia.co.uk/dataTables/lang.txt"
+ * }
+ * } );
+ * } );
+ */
+ "sUrl": "",
+
+
+ /**
+ * Text shown inside the table records when the is no information to be
+ * displayed after filtering. `emptyTable` is shown when there is simply no
+ * information in the table at all (regardless of filtering).
+ * @type string
+ * @default No matching records found
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.zeroRecords
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "language": {
+ * "zeroRecords": "No records to display"
+ * }
+ * } );
+ * } );
+ */
+ "sZeroRecords": "No matching records found"
+ },
+
+
+ /**
+ * This parameter allows you to have define the global filtering state at
+ * initialisation time. As an object the `search` parameter must be
+ * defined, but all other parameters are optional. When `regex` is true,
+ * the search string will be treated as a regular expression, when false
+ * (default) it will be treated as a straight string. When `smart`
+ * DataTables will use it's smart filtering methods (to word match at
+ * any point in the data), when false this will not be done.
+ * @namespace
+ * @extends DataTable.models.oSearch
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.search
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "search": {"search": "Initial search"}
+ * } );
+ * } )
+ */
+ "oSearch": $.extend( {}, DataTable.models.oSearch ),
+
+
+ /**
+ * __Deprecated__ The functionality provided by this parameter has now been
+ * superseded by that provided through `ajax`, which should be used instead.
+ *
+ * By default DataTables will look for the property `data` (or `aaData` for
+ * compatibility with DataTables 1.9-) when obtaining data from an Ajax
+ * source or for server-side processing - this parameter allows that
+ * property to be changed. You can use Javascript dotted object notation to
+ * get a data source for multiple levels of nesting.
+ * @type string
+ * @default data
+ *
+ * @dtopt Options
+ * @dtopt Server-side
+ * @name DataTable.defaults.ajaxDataProp
+ *
+ * @deprecated 1.10. Please use `ajax` for this functionality now.
+ */
+ "sAjaxDataProp": "data",
+
+
+ /**
+ * __Deprecated__ The functionality provided by this parameter has now been
+ * superseded by that provided through `ajax`, which should be used instead.
+ *
+ * You can instruct DataTables to load data from an external
+ * source using this parameter (use aData if you want to pass data in you
+ * already have). Simply provide a url a JSON object can be obtained from.
+ * @type string
+ * @default null
+ *
+ * @dtopt Options
+ * @dtopt Server-side
+ * @name DataTable.defaults.ajaxSource
+ *
+ * @deprecated 1.10. Please use `ajax` for this functionality now.
+ */
+ "sAjaxSource": null,
+
+
+ /**
+ * This initialisation variable allows you to specify exactly where in the
+ * DOM you want DataTables to inject the various controls it adds to the page
+ * (for example you might want the pagination controls at the top of the
+ * table). DIV elements (with or without a custom class) can also be added to
+ * aid styling. The follow syntax is used:
+ * <ul>
+ * <li>The following options are allowed:
+ * <ul>
+ * <li>'l' - Length changing</li>
+ * <li>'f' - Filtering input</li>
+ * <li>'t' - The table!</li>
+ * <li>'i' - Information</li>
+ * <li>'p' - Pagination</li>
+ * <li>'r' - pRocessing</li>
+ * </ul>
+ * </li>
+ * <li>The following constants are allowed:
+ * <ul>
+ * <li>'H' - jQueryUI theme "header" classes ('fg-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix')</li>
+ * <li>'F' - jQueryUI theme "footer" classes ('fg-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix')</li>
+ * </ul>
+ * </li>
+ * <li>The following syntax is expected:
+ * <ul>
+ * <li>'&lt;' and '&gt;' - div elements</li>
+ * <li>'&lt;"class" and '&gt;' - div with a class</li>
+ * <li>'&lt;"#id" and '&gt;' - div with an ID</li>
+ * </ul>
+ * </li>
+ * <li>Examples:
+ * <ul>
+ * <li>'&lt;"wrapper"flipt&gt;'</li>
+ * <li>'&lt;lf&lt;t&gt;ip&gt;'</li>
+ * </ul>
+ * </li>
+ * </ul>
+ * @type string
+ * @default lfrtip <i>(when `jQueryUI` is false)</i> <b>or</b>
+ * <"H"lfr>t<"F"ip> <i>(when `jQueryUI` is true)</i>
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.dom
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "dom": '&lt;"top"i&gt;rt&lt;"bottom"flp&gt;&lt;"clear"&gt;'
+ * } );
+ * } );
+ */
+ "sDom": "lfrtip",
+
+
+ /**
+ * Search delay option. This will throttle full table searches that use the
+ * DataTables provided search input element (it does not effect calls to
+ * `dt-api search()`, providing a delay before the search is made.
+ * @type integer
+ * @default 0
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.searchDelay
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "searchDelay": 200
+ * } );
+ * } )
+ */
+ "searchDelay": null,
+
+
+ /**
+ * DataTables features six different built-in options for the buttons to
+ * display for pagination control:
+ *
+ * * `numbers` - Page number buttons only
+ * * `simple` - 'Previous' and 'Next' buttons only
+ * * 'simple_numbers` - 'Previous' and 'Next' buttons, plus page numbers
+ * * `full` - 'First', 'Previous', 'Next' and 'Last' buttons
+ * * `full_numbers` - 'First', 'Previous', 'Next' and 'Last' buttons, plus page numbers
+ * * `first_last_numbers` - 'First' and 'Last' buttons, plus page numbers
+ *
+ * Further methods can be added using {@link DataTable.ext.oPagination}.
+ * @type string
+ * @default simple_numbers
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.pagingType
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "pagingType": "full_numbers"
+ * } );
+ * } )
+ */
+ "sPaginationType": "simple_numbers",
+
+
+ /**
+ * Enable horizontal scrolling. When a table is too wide to fit into a
+ * certain layout, or you have a large number of columns in the table, you
+ * can enable x-scrolling to show the table in a viewport, which can be
+ * scrolled. This property can be `true` which will allow the table to
+ * scroll horizontally when needed, or any CSS unit, or a number (in which
+ * case it will be treated as a pixel measurement). Setting as simply `true`
+ * is recommended.
+ * @type boolean|string
+ * @default <i>blank string - i.e. disabled</i>
+ *
+ * @dtopt Features
+ * @name DataTable.defaults.scrollX
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "scrollX": true,
+ * "scrollCollapse": true
+ * } );
+ * } );
+ */
+ "sScrollX": "",
+
+
+ /**
+ * This property can be used to force a DataTable to use more width than it
+ * might otherwise do when x-scrolling is enabled. For example if you have a
+ * table which requires to be well spaced, this parameter is useful for
+ * "over-sizing" the table, and thus forcing scrolling. This property can by
+ * any CSS unit, or a number (in which case it will be treated as a pixel
+ * measurement).
+ * @type string
+ * @default <i>blank string - i.e. disabled</i>
+ *
+ * @dtopt Options
+ * @name DataTable.defaults.scrollXInner
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "scrollX": "100%",
+ * "scrollXInner": "110%"
+ * } );
+ * } );
+ */
+ "sScrollXInner": "",
+
+
+ /**
+ * Enable vertical scrolling. Vertical scrolling will constrain the DataTable
+ * to the given height, and enable scrolling for any data which overflows the
+ * current viewport. This can be used as an alternative to paging to display
+ * a lot of data in a small area (although paging and scrolling can both be
+ * enabled at the same time). This property can be any CSS unit, or a number
+ * (in which case it will be treated as a pixel measurement).
+ * @type string
+ * @default <i>blank string - i.e. disabled</i>
+ *
+ * @dtopt Features
+ * @name DataTable.defaults.scrollY
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "scrollY": "200px",
+ * "paginate": false
+ * } );
+ * } );
+ */
+ "sScrollY": "",
+
+
+ /**
+ * __Deprecated__ The functionality provided by this parameter has now been
+ * superseded by that provided through `ajax`, which should be used instead.
+ *
+ * Set the HTTP method that is used to make the Ajax call for server-side
+ * processing or Ajax sourced data.
+ * @type string
+ * @default GET
+ *
+ * @dtopt Options
+ * @dtopt Server-side
+ * @name DataTable.defaults.serverMethod
+ *
+ * @deprecated 1.10. Please use `ajax` for this functionality now.
+ */
+ "sServerMethod": "GET",
+
+
+ /**
+ * DataTables makes use of renderers when displaying HTML elements for
+ * a table. These renderers can be added or modified by plug-ins to
+ * generate suitable mark-up for a site. For example the Bootstrap
+ * integration plug-in for DataTables uses a paging button renderer to
+ * display pagination buttons in the mark-up required by Bootstrap.
+ *
+ * For further information about the renderers available see
+ * DataTable.ext.renderer
+ * @type string|object
+ * @default null
+ *
+ * @name DataTable.defaults.renderer
+ *
+ */
+ "renderer": null,
+
+
+ /**
+ * Set the data property name that DataTables should use to get a row's id
+ * to set as the `id` property in the node.
+ * @type string
+ * @default DT_RowId
+ *
+ * @name DataTable.defaults.rowId
+ */
+ "rowId": "DT_RowId"
+ };
+
+ _fnHungarianMap( DataTable.defaults );
+
+
+
+ /*
+ * Developer note - See note in model.defaults.js about the use of Hungarian
+ * notation and camel case.
+ */
+
+ /**
+ * Column options that can be given to DataTables at initialisation time.
+ * @namespace
+ */
+ DataTable.defaults.column = {
+ /**
+ * Define which column(s) an order will occur on for this column. This
+ * allows a column's ordering to take multiple columns into account when
+ * doing a sort or use the data from a different column. For example first
+ * name / last name columns make sense to do a multi-column sort over the
+ * two columns.
+ * @type array|int
+ * @default null <i>Takes the value of the column index automatically</i>
+ *
+ * @name DataTable.defaults.column.orderData
+ * @dtopt Columns
+ *
+ * @example
+ * // Using `columnDefs`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [
+ * { "orderData": [ 0, 1 ], "targets": [ 0 ] },
+ * { "orderData": [ 1, 0 ], "targets": [ 1 ] },
+ * { "orderData": 2, "targets": [ 2 ] }
+ * ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Using `columns`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columns": [
+ * { "orderData": [ 0, 1 ] },
+ * { "orderData": [ 1, 0 ] },
+ * { "orderData": 2 },
+ * null,
+ * null
+ * ]
+ * } );
+ * } );
+ */
+ "aDataSort": null,
+ "iDataSort": -1,
+
+
+ /**
+ * You can control the default ordering direction, and even alter the
+ * behaviour of the sort handler (i.e. only allow ascending ordering etc)
+ * using this parameter.
+ * @type array
+ * @default [ 'asc', 'desc' ]
+ *
+ * @name DataTable.defaults.column.orderSequence
+ * @dtopt Columns
+ *
+ * @example
+ * // Using `columnDefs`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [
+ * { "orderSequence": [ "asc" ], "targets": [ 1 ] },
+ * { "orderSequence": [ "desc", "asc", "asc" ], "targets": [ 2 ] },
+ * { "orderSequence": [ "desc" ], "targets": [ 3 ] }
+ * ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Using `columns`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columns": [
+ * null,
+ * { "orderSequence": [ "asc" ] },
+ * { "orderSequence": [ "desc", "asc", "asc" ] },
+ * { "orderSequence": [ "desc" ] },
+ * null
+ * ]
+ * } );
+ * } );
+ */
+ "asSorting": [ 'asc', 'desc' ],
+
+
+ /**
+ * Enable or disable filtering on the data in this column.
+ * @type boolean
+ * @default true
+ *
+ * @name DataTable.defaults.column.searchable
+ * @dtopt Columns
+ *
+ * @example
+ * // Using `columnDefs`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [
+ * { "searchable": false, "targets": [ 0 ] }
+ * ] } );
+ * } );
+ *
+ * @example
+ * // Using `columns`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columns": [
+ * { "searchable": false },
+ * null,
+ * null,
+ * null,
+ * null
+ * ] } );
+ * } );
+ */
+ "bSearchable": true,
+
+
+ /**
+ * Enable or disable ordering on this column.
+ * @type boolean
+ * @default true
+ *
+ * @name DataTable.defaults.column.orderable
+ * @dtopt Columns
+ *
+ * @example
+ * // Using `columnDefs`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [
+ * { "orderable": false, "targets": [ 0 ] }
+ * ] } );
+ * } );
+ *
+ * @example
+ * // Using `columns`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columns": [
+ * { "orderable": false },
+ * null,
+ * null,
+ * null,
+ * null
+ * ] } );
+ * } );
+ */
+ "bSortable": true,
+
+
+ /**
+ * Enable or disable the display of this column.
+ * @type boolean
+ * @default true
+ *
+ * @name DataTable.defaults.column.visible
+ * @dtopt Columns
+ *
+ * @example
+ * // Using `columnDefs`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [
+ * { "visible": false, "targets": [ 0 ] }
+ * ] } );
+ * } );
+ *
+ * @example
+ * // Using `columns`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columns": [
+ * { "visible": false },
+ * null,
+ * null,
+ * null,
+ * null
+ * ] } );
+ * } );
+ */
+ "bVisible": true,
+
+
+ /**
+ * Developer definable function that is called whenever a cell is created (Ajax source,
+ * etc) or processed for input (DOM source). This can be used as a compliment to mRender
+ * allowing you to modify the DOM element (add background colour for example) when the
+ * element is available.
+ * @type function
+ * @param {element} td The TD node that has been created
+ * @param {*} cellData The Data for the cell
+ * @param {array|object} rowData The data for the whole row
+ * @param {int} row The row index for the aoData data store
+ * @param {int} col The column index for aoColumns
+ *
+ * @name DataTable.defaults.column.createdCell
+ * @dtopt Columns
+ *
+ * @example
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [ {
+ * "targets": [3],
+ * "createdCell": function (td, cellData, rowData, row, col) {
+ * if ( cellData == "1.7" ) {
+ * $(td).css('color', 'blue')
+ * }
+ * }
+ * } ]
+ * });
+ * } );
+ */
+ "fnCreatedCell": null,
+
+
+ /**
+ * This parameter has been replaced by `data` in DataTables to ensure naming
+ * consistency. `dataProp` can still be used, as there is backwards
+ * compatibility in DataTables for this option, but it is strongly
+ * recommended that you use `data` in preference to `dataProp`.
+ * @name DataTable.defaults.column.dataProp
+ */
+
+
+ /**
+ * This property can be used to read data from any data source property,
+ * including deeply nested objects / properties. `data` can be given in a
+ * number of different ways which effect its behaviour:
+ *
+ * * `integer` - treated as an array index for the data source. This is the
+ * default that DataTables uses (incrementally increased for each column).
+ * * `string` - read an object property from the data source. There are
+ * three 'special' options that can be used in the string to alter how
+ * DataTables reads the data from the source object:
+ * * `.` - Dotted Javascript notation. Just as you use a `.` in
+ * Javascript to read from nested objects, so to can the options
+ * specified in `data`. For example: `browser.version` or
+ * `browser.name`. If your object parameter name contains a period, use
+ * `\\` to escape it - i.e. `first\\.name`.
+ * * `[]` - Array notation. DataTables can automatically combine data
+ * from and array source, joining the data with the characters provided
+ * between the two brackets. For example: `name[, ]` would provide a
+ * comma-space separated list from the source array. If no characters
+ * are provided between the brackets, the original array source is
+ * returned.
+ * * `()` - Function notation. Adding `()` to the end of a parameter will
+ * execute a function of the name given. For example: `browser()` for a
+ * simple function on the data source, `browser.version()` for a
+ * function in a nested property or even `browser().version` to get an
+ * object property if the function called returns an object. Note that
+ * function notation is recommended for use in `render` rather than
+ * `data` as it is much simpler to use as a renderer.
+ * * `null` - use the original data source for the row rather than plucking
+ * data directly from it. This action has effects on two other
+ * initialisation options:
+ * * `defaultContent` - When null is given as the `data` option and
+ * `defaultContent` is specified for the column, the value defined by
+ * `defaultContent` will be used for the cell.
+ * * `render` - When null is used for the `data` option and the `render`
+ * option is specified for the column, the whole data source for the
+ * row is used for the renderer.
+ * * `function` - the function given will be executed whenever DataTables
+ * needs to set or get the data for a cell in the column. The function
+ * takes three parameters:
+ * * Parameters:
+ * * `{array|object}` The data source for the row
+ * * `{string}` The type call data requested - this will be 'set' when
+ * setting data or 'filter', 'display', 'type', 'sort' or undefined
+ * when gathering data. Note that when `undefined` is given for the
+ * type DataTables expects to get the raw data for the object back<
+ * * `{*}` Data to set when the second parameter is 'set'.
+ * * Return:
+ * * The return value from the function is not required when 'set' is
+ * the type of call, but otherwise the return is what will be used
+ * for the data requested.
+ *
+ * Note that `data` is a getter and setter option. If you just require
+ * formatting of data for output, you will likely want to use `render` which
+ * is simply a getter and thus simpler to use.
+ *
+ * Note that prior to DataTables 1.9.2 `data` was called `mDataProp`. The
+ * name change reflects the flexibility of this property and is consistent
+ * with the naming of mRender. If 'mDataProp' is given, then it will still
+ * be used by DataTables, as it automatically maps the old name to the new
+ * if required.
+ *
+ * @type string|int|function|null
+ * @default null <i>Use automatically calculated column index</i>
+ *
+ * @name DataTable.defaults.column.data
+ * @dtopt Columns
+ *
+ * @example
+ * // Read table data from objects
+ * // JSON structure for each row:
+ * // {
+ * // "engine": {value},
+ * // "browser": {value},
+ * // "platform": {value},
+ * // "version": {value},
+ * // "grade": {value}
+ * // }
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "ajaxSource": "sources/objects.txt",
+ * "columns": [
+ * { "data": "engine" },
+ * { "data": "browser" },
+ * { "data": "platform" },
+ * { "data": "version" },
+ * { "data": "grade" }
+ * ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Read information from deeply nested objects
+ * // JSON structure for each row:
+ * // {
+ * // "engine": {value},
+ * // "browser": {value},
+ * // "platform": {
+ * // "inner": {value}
+ * // },
+ * // "details": [
+ * // {value}, {value}
+ * // ]
+ * // }
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "ajaxSource": "sources/deep.txt",
+ * "columns": [
+ * { "data": "engine" },
+ * { "data": "browser" },
+ * { "data": "platform.inner" },
+ * { "data": "platform.details.0" },
+ * { "data": "platform.details.1" }
+ * ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Using `data` as a function to provide different information for
+ * // sorting, filtering and display. In this case, currency (price)
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [ {
+ * "targets": [ 0 ],
+ * "data": function ( source, type, val ) {
+ * if (type === 'set') {
+ * source.price = val;
+ * // Store the computed dislay and filter values for efficiency
+ * source.price_display = val=="" ? "" : "$"+numberFormat(val);
+ * source.price_filter = val=="" ? "" : "$"+numberFormat(val)+" "+val;
+ * return;
+ * }
+ * else if (type === 'display') {
+ * return source.price_display;
+ * }
+ * else if (type === 'filter') {
+ * return source.price_filter;
+ * }
+ * // 'sort', 'type' and undefined all just use the integer
+ * return source.price;
+ * }
+ * } ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Using default content
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [ {
+ * "targets": [ 0 ],
+ * "data": null,
+ * "defaultContent": "Click to edit"
+ * } ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Using array notation - outputting a list from an array
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [ {
+ * "targets": [ 0 ],
+ * "data": "name[, ]"
+ * } ]
+ * } );
+ * } );
+ *
+ */
+ "mData": null,
+
+
+ /**
+ * This property is the rendering partner to `data` and it is suggested that
+ * when you want to manipulate data for display (including filtering,
+ * sorting etc) without altering the underlying data for the table, use this
+ * property. `render` can be considered to be the the read only companion to
+ * `data` which is read / write (then as such more complex). Like `data`
+ * this option can be given in a number of different ways to effect its
+ * behaviour:
+ *
+ * * `integer` - treated as an array index for the data source. This is the
+ * default that DataTables uses (incrementally increased for each column).
+ * * `string` - read an object property from the data source. There are
+ * three 'special' options that can be used in the string to alter how
+ * DataTables reads the data from the source object:
+ * * `.` - Dotted Javascript notation. Just as you use a `.` in
+ * Javascript to read from nested objects, so to can the options
+ * specified in `data`. For example: `browser.version` or
+ * `browser.name`. If your object parameter name contains a period, use
+ * `\\` to escape it - i.e. `first\\.name`.
+ * * `[]` - Array notation. DataTables can automatically combine data
+ * from and array source, joining the data with the characters provided
+ * between the two brackets. For example: `name[, ]` would provide a
+ * comma-space separated list from the source array. If no characters
+ * are provided between the brackets, the original array source is
+ * returned.
+ * * `()` - Function notation. Adding `()` to the end of a parameter will
+ * execute a function of the name given. For example: `browser()` for a
+ * simple function on the data source, `browser.version()` for a
+ * function in a nested property or even `browser().version` to get an
+ * object property if the function called returns an object.
+ * * `object` - use different data for the different data types requested by
+ * DataTables ('filter', 'display', 'type' or 'sort'). The property names
+ * of the object is the data type the property refers to and the value can
+ * defined using an integer, string or function using the same rules as
+ * `render` normally does. Note that an `_` option _must_ be specified.
+ * This is the default value to use if you haven't specified a value for
+ * the data type requested by DataTables.
+ * * `function` - the function given will be executed whenever DataTables
+ * needs to set or get the data for a cell in the column. The function
+ * takes three parameters:
+ * * Parameters:
+ * * {array|object} The data source for the row (based on `data`)
+ * * {string} The type call data requested - this will be 'filter',
+ * 'display', 'type' or 'sort'.
+ * * {array|object} The full data source for the row (not based on
+ * `data`)
+ * * Return:
+ * * The return value from the function is what will be used for the
+ * data requested.
+ *
+ * @type string|int|function|object|null
+ * @default null Use the data source value.
+ *
+ * @name DataTable.defaults.column.render
+ * @dtopt Columns
+ *
+ * @example
+ * // Create a comma separated list from an array of objects
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "ajaxSource": "sources/deep.txt",
+ * "columns": [
+ * { "data": "engine" },
+ * { "data": "browser" },
+ * {
+ * "data": "platform",
+ * "render": "[, ].name"
+ * }
+ * ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Execute a function to obtain data
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [ {
+ * "targets": [ 0 ],
+ * "data": null, // Use the full data source object for the renderer's source
+ * "render": "browserName()"
+ * } ]
+ * } );
+ * } );
+ *
+ * @example
+ * // As an object, extracting different data for the different types
+ * // This would be used with a data source such as:
+ * // { "phone": 5552368, "phone_filter": "5552368 555-2368", "phone_display": "555-2368" }
+ * // Here the `phone` integer is used for sorting and type detection, while `phone_filter`
+ * // (which has both forms) is used for filtering for if a user inputs either format, while
+ * // the formatted phone number is the one that is shown in the table.
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [ {
+ * "targets": [ 0 ],
+ * "data": null, // Use the full data source object for the renderer's source
+ * "render": {
+ * "_": "phone",
+ * "filter": "phone_filter",
+ * "display": "phone_display"
+ * }
+ * } ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Use as a function to create a link from the data source
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [ {
+ * "targets": [ 0 ],
+ * "data": "download_link",
+ * "render": function ( data, type, full ) {
+ * return '<a href="'+data+'">Download</a>';
+ * }
+ * } ]
+ * } );
+ * } );
+ */
+ "mRender": null,
+
+
+ /**
+ * Change the cell type created for the column - either TD cells or TH cells. This
+ * can be useful as TH cells have semantic meaning in the table body, allowing them
+ * to act as a header for a row (you may wish to add scope='row' to the TH elements).
+ * @type string
+ * @default td
+ *
+ * @name DataTable.defaults.column.cellType
+ * @dtopt Columns
+ *
+ * @example
+ * // Make the first column use TH cells
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [ {
+ * "targets": [ 0 ],
+ * "cellType": "th"
+ * } ]
+ * } );
+ * } );
+ */
+ "sCellType": "td",
+
+
+ /**
+ * Class to give to each cell in this column.
+ * @type string
+ * @default <i>Empty string</i>
+ *
+ * @name DataTable.defaults.column.class
+ * @dtopt Columns
+ *
+ * @example
+ * // Using `columnDefs`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [
+ * { "class": "my_class", "targets": [ 0 ] }
+ * ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Using `columns`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columns": [
+ * { "class": "my_class" },
+ * null,
+ * null,
+ * null,
+ * null
+ * ]
+ * } );
+ * } );
+ */
+ "sClass": "",
+
+ /**
+ * When DataTables calculates the column widths to assign to each column,
+ * it finds the longest string in each column and then constructs a
+ * temporary table and reads the widths from that. The problem with this
+ * is that "mmm" is much wider then "iiii", but the latter is a longer
+ * string - thus the calculation can go wrong (doing it properly and putting
+ * it into an DOM object and measuring that is horribly(!) slow). Thus as
+ * a "work around" we provide this option. It will append its value to the
+ * text that is found to be the longest string for the column - i.e. padding.
+ * Generally you shouldn't need this!
+ * @type string
+ * @default <i>Empty string<i>
+ *
+ * @name DataTable.defaults.column.contentPadding
+ * @dtopt Columns
+ *
+ * @example
+ * // Using `columns`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columns": [
+ * null,
+ * null,
+ * null,
+ * {
+ * "contentPadding": "mmm"
+ * }
+ * ]
+ * } );
+ * } );
+ */
+ "sContentPadding": "",
+
+
+ /**
+ * Allows a default value to be given for a column's data, and will be used
+ * whenever a null data source is encountered (this can be because `data`
+ * is set to null, or because the data source itself is null).
+ * @type string
+ * @default null
+ *
+ * @name DataTable.defaults.column.defaultContent
+ * @dtopt Columns
+ *
+ * @example
+ * // Using `columnDefs`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [
+ * {
+ * "data": null,
+ * "defaultContent": "Edit",
+ * "targets": [ -1 ]
+ * }
+ * ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Using `columns`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columns": [
+ * null,
+ * null,
+ * null,
+ * {
+ * "data": null,
+ * "defaultContent": "Edit"
+ * }
+ * ]
+ * } );
+ * } );
+ */
+ "sDefaultContent": null,
+
+
+ /**
+ * This parameter is only used in DataTables' server-side processing. It can
+ * be exceptionally useful to know what columns are being displayed on the
+ * client side, and to map these to database fields. When defined, the names
+ * also allow DataTables to reorder information from the server if it comes
+ * back in an unexpected order (i.e. if you switch your columns around on the
+ * client-side, your server-side code does not also need updating).
+ * @type string
+ * @default <i>Empty string</i>
+ *
+ * @name DataTable.defaults.column.name
+ * @dtopt Columns
+ *
+ * @example
+ * // Using `columnDefs`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [
+ * { "name": "engine", "targets": [ 0 ] },
+ * { "name": "browser", "targets": [ 1 ] },
+ * { "name": "platform", "targets": [ 2 ] },
+ * { "name": "version", "targets": [ 3 ] },
+ * { "name": "grade", "targets": [ 4 ] }
+ * ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Using `columns`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columns": [
+ * { "name": "engine" },
+ * { "name": "browser" },
+ * { "name": "platform" },
+ * { "name": "version" },
+ * { "name": "grade" }
+ * ]
+ * } );
+ * } );
+ */
+ "sName": "",
+
+
+ /**
+ * Defines a data source type for the ordering which can be used to read
+ * real-time information from the table (updating the internally cached
+ * version) prior to ordering. This allows ordering to occur on user
+ * editable elements such as form inputs.
+ * @type string
+ * @default std
+ *
+ * @name DataTable.defaults.column.orderDataType
+ * @dtopt Columns
+ *
+ * @example
+ * // Using `columnDefs`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [
+ * { "orderDataType": "dom-text", "targets": [ 2, 3 ] },
+ * { "type": "numeric", "targets": [ 3 ] },
+ * { "orderDataType": "dom-select", "targets": [ 4 ] },
+ * { "orderDataType": "dom-checkbox", "targets": [ 5 ] }
+ * ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Using `columns`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columns": [
+ * null,
+ * null,
+ * { "orderDataType": "dom-text" },
+ * { "orderDataType": "dom-text", "type": "numeric" },
+ * { "orderDataType": "dom-select" },
+ * { "orderDataType": "dom-checkbox" }
+ * ]
+ * } );
+ * } );
+ */
+ "sSortDataType": "std",
+
+
+ /**
+ * The title of this column.
+ * @type string
+ * @default null <i>Derived from the 'TH' value for this column in the
+ * original HTML table.</i>
+ *
+ * @name DataTable.defaults.column.title
+ * @dtopt Columns
+ *
+ * @example
+ * // Using `columnDefs`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [
+ * { "title": "My column title", "targets": [ 0 ] }
+ * ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Using `columns`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columns": [
+ * { "title": "My column title" },
+ * null,
+ * null,
+ * null,
+ * null
+ * ]
+ * } );
+ * } );
+ */
+ "sTitle": null,
+
+
+ /**
+ * The type allows you to specify how the data for this column will be
+ * ordered. Four types (string, numeric, date and html (which will strip
+ * HTML tags before ordering)) are currently available. Note that only date
+ * formats understood by Javascript's Date() object will be accepted as type
+ * date. For example: "Mar 26, 2008 5:03 PM". May take the values: 'string',
+ * 'numeric', 'date' or 'html' (by default). Further types can be adding
+ * through plug-ins.
+ * @type string
+ * @default null <i>Auto-detected from raw data</i>
+ *
+ * @name DataTable.defaults.column.type
+ * @dtopt Columns
+ *
+ * @example
+ * // Using `columnDefs`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [
+ * { "type": "html", "targets": [ 0 ] }
+ * ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Using `columns`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columns": [
+ * { "type": "html" },
+ * null,
+ * null,
+ * null,
+ * null
+ * ]
+ * } );
+ * } );
+ */
+ "sType": null,
+
+
+ /**
+ * Defining the width of the column, this parameter may take any CSS value
+ * (3em, 20px etc). DataTables applies 'smart' widths to columns which have not
+ * been given a specific width through this interface ensuring that the table
+ * remains readable.
+ * @type string
+ * @default null <i>Automatic</i>
+ *
+ * @name DataTable.defaults.column.width
+ * @dtopt Columns
+ *
+ * @example
+ * // Using `columnDefs`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columnDefs": [
+ * { "width": "20%", "targets": [ 0 ] }
+ * ]
+ * } );
+ * } );
+ *
+ * @example
+ * // Using `columns`
+ * $(document).ready( function() {
+ * $('#example').dataTable( {
+ * "columns": [
+ * { "width": "20%" },
+ * null,
+ * null,
+ * null,
+ * null
+ * ]
+ * } );
+ * } );
+ */
+ "sWidth": null
+ };
+
+ _fnHungarianMap( DataTable.defaults.column );
+
+
+
+ /**
+ * DataTables settings object - this holds all the information needed for a
+ * given table, including configuration, data and current application of the
+ * table options. DataTables does not have a single instance for each DataTable
+ * with the settings attached to that instance, but rather instances of the
+ * DataTable "class" are created on-the-fly as needed (typically by a
+ * $().dataTable() call) and the settings object is then applied to that
+ * instance.
+ *
+ * Note that this object is related to {@link DataTable.defaults} but this
+ * one is the internal data store for DataTables's cache of columns. It should
+ * NOT be manipulated outside of DataTables. Any configuration should be done
+ * through the initialisation options.
+ * @namespace
+ * @todo Really should attach the settings object to individual instances so we
+ * don't need to create new instances on each $().dataTable() call (if the
+ * table already exists). It would also save passing oSettings around and
+ * into every single function. However, this is a very significant
+ * architecture change for DataTables and will almost certainly break
+ * backwards compatibility with older installations. This is something that
+ * will be done in 2.0.
+ */
+ DataTable.models.oSettings = {
+ /**
+ * Primary features of DataTables and their enablement state.
+ * @namespace
+ */
+ "oFeatures": {
+
+ /**
+ * Flag to say if DataTables should automatically try to calculate the
+ * optimum table and columns widths (true) or not (false).
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bAutoWidth": null,
+
+ /**
+ * Delay the creation of TR and TD elements until they are actually
+ * needed by a driven page draw. This can give a significant speed
+ * increase for Ajax source and Javascript source data, but makes no
+ * difference at all fro DOM and server-side processing tables.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bDeferRender": null,
+
+ /**
+ * Enable filtering on the table or not. Note that if this is disabled
+ * then there is no filtering at all on the table, including fnFilter.
+ * To just remove the filtering input use sDom and remove the 'f' option.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bFilter": null,
+
+ /**
+ * Table information element (the 'Showing x of y records' div) enable
+ * flag.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bInfo": null,
+
+ /**
+ * Present a user control allowing the end user to change the page size
+ * when pagination is enabled.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bLengthChange": null,
+
+ /**
+ * Pagination enabled or not. Note that if this is disabled then length
+ * changing must also be disabled.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bPaginate": null,
+
+ /**
+ * Processing indicator enable flag whenever DataTables is enacting a
+ * user request - typically an Ajax request for server-side processing.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bProcessing": null,
+
+ /**
+ * Server-side processing enabled flag - when enabled DataTables will
+ * get all data from the server for every draw - there is no filtering,
+ * sorting or paging done on the client-side.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bServerSide": null,
+
+ /**
+ * Sorting enablement flag.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bSort": null,
+
+ /**
+ * Multi-column sorting
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bSortMulti": null,
+
+ /**
+ * Apply a class to the columns which are being sorted to provide a
+ * visual highlight or not. This can slow things down when enabled since
+ * there is a lot of DOM interaction.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bSortClasses": null,
+
+ /**
+ * State saving enablement flag.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bStateSave": null
+ },
+
+
+ /**
+ * Scrolling settings for a table.
+ * @namespace
+ */
+ "oScroll": {
+ /**
+ * When the table is shorter in height than sScrollY, collapse the
+ * table container down to the height of the table (when true).
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bCollapse": null,
+
+ /**
+ * Width of the scrollbar for the web-browser's platform. Calculated
+ * during table initialisation.
+ * @type int
+ * @default 0
+ */
+ "iBarWidth": 0,
+
+ /**
+ * Viewport width for horizontal scrolling. Horizontal scrolling is
+ * disabled if an empty string.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type string
+ */
+ "sX": null,
+
+ /**
+ * Width to expand the table to when using x-scrolling. Typically you
+ * should not need to use this.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type string
+ * @deprecated
+ */
+ "sXInner": null,
+
+ /**
+ * Viewport height for vertical scrolling. Vertical scrolling is disabled
+ * if an empty string.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type string
+ */
+ "sY": null
+ },
+
+ /**
+ * Language information for the table.
+ * @namespace
+ * @extends DataTable.defaults.oLanguage
+ */
+ "oLanguage": {
+ /**
+ * Information callback function. See
+ * {@link DataTable.defaults.fnInfoCallback}
+ * @type function
+ * @default null
+ */
+ "fnInfoCallback": null
+ },
+
+ /**
+ * Browser support parameters
+ * @namespace
+ */
+ "oBrowser": {
+ /**
+ * Indicate if the browser incorrectly calculates width:100% inside a
+ * scrolling element (IE6/7)
+ * @type boolean
+ * @default false
+ */
+ "bScrollOversize": false,
+
+ /**
+ * Determine if the vertical scrollbar is on the right or left of the
+ * scrolling container - needed for rtl language layout, although not
+ * all browsers move the scrollbar (Safari).
+ * @type boolean
+ * @default false
+ */
+ "bScrollbarLeft": false,
+
+ /**
+ * Flag for if `getBoundingClientRect` is fully supported or not
+ * @type boolean
+ * @default false
+ */
+ "bBounding": false,
+
+ /**
+ * Browser scrollbar width
+ * @type integer
+ * @default 0
+ */
+ "barWidth": 0
+ },
+
+
+ "ajax": null,
+
+
+ /**
+ * Array referencing the nodes which are used for the features. The
+ * parameters of this object match what is allowed by sDom - i.e.
+ * <ul>
+ * <li>'l' - Length changing</li>
+ * <li>'f' - Filtering input</li>
+ * <li>'t' - The table!</li>
+ * <li>'i' - Information</li>
+ * <li>'p' - Pagination</li>
+ * <li>'r' - pRocessing</li>
+ * </ul>
+ * @type array
+ * @default []
+ */
+ "aanFeatures": [],
+
+ /**
+ * Store data information - see {@link DataTable.models.oRow} for detailed
+ * information.
+ * @type array
+ * @default []
+ */
+ "aoData": [],
+
+ /**
+ * Array of indexes which are in the current display (after filtering etc)
+ * @type array
+ * @default []
+ */
+ "aiDisplay": [],
+
+ /**
+ * Array of indexes for display - no filtering
+ * @type array
+ * @default []
+ */
+ "aiDisplayMaster": [],
+
+ /**
+ * Map of row ids to data indexes
+ * @type object
+ * @default {}
+ */
+ "aIds": {},
+
+ /**
+ * Store information about each column that is in use
+ * @type array
+ * @default []
+ */
+ "aoColumns": [],
+
+ /**
+ * Store information about the table's header
+ * @type array
+ * @default []
+ */
+ "aoHeader": [],
+
+ /**
+ * Store information about the table's footer
+ * @type array
+ * @default []
+ */
+ "aoFooter": [],
+
+ /**
+ * Store the applied global search information in case we want to force a
+ * research or compare the old search to a new one.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @namespace
+ * @extends DataTable.models.oSearch
+ */
+ "oPreviousSearch": {},
+
+ /**
+ * Store the applied search for each column - see
+ * {@link DataTable.models.oSearch} for the format that is used for the
+ * filtering information for each column.
+ * @type array
+ * @default []
+ */
+ "aoPreSearchCols": [],
+
+ /**
+ * Sorting that is applied to the table. Note that the inner arrays are
+ * used in the following manner:
+ * <ul>
+ * <li>Index 0 - column number</li>
+ * <li>Index 1 - current sorting direction</li>
+ * </ul>
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type array
+ * @todo These inner arrays should really be objects
+ */
+ "aaSorting": null,
+
+ /**
+ * Sorting that is always applied to the table (i.e. prefixed in front of
+ * aaSorting).
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type array
+ * @default []
+ */
+ "aaSortingFixed": [],
+
+ /**
+ * Classes to use for the striping of a table.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type array
+ * @default []
+ */
+ "asStripeClasses": null,
+
+ /**
+ * If restoring a table - we should restore its striping classes as well
+ * @type array
+ * @default []
+ */
+ "asDestroyStripes": [],
+
+ /**
+ * If restoring a table - we should restore its width
+ * @type int
+ * @default 0
+ */
+ "sDestroyWidth": 0,
+
+ /**
+ * Callback functions array for every time a row is inserted (i.e. on a draw).
+ * @type array
+ * @default []
+ */
+ "aoRowCallback": [],
+
+ /**
+ * Callback functions for the header on each draw.
+ * @type array
+ * @default []
+ */
+ "aoHeaderCallback": [],
+
+ /**
+ * Callback function for the footer on each draw.
+ * @type array
+ * @default []
+ */
+ "aoFooterCallback": [],
+
+ /**
+ * Array of callback functions for draw callback functions
+ * @type array
+ * @default []
+ */
+ "aoDrawCallback": [],
+
+ /**
+ * Array of callback functions for row created function
+ * @type array
+ * @default []
+ */
+ "aoRowCreatedCallback": [],
+
+ /**
+ * Callback functions for just before the table is redrawn. A return of
+ * false will be used to cancel the draw.
+ * @type array
+ * @default []
+ */
+ "aoPreDrawCallback": [],
+
+ /**
+ * Callback functions for when the table has been initialised.
+ * @type array
+ * @default []
+ */
+ "aoInitComplete": [],
+
+
+ /**
+ * Callbacks for modifying the settings to be stored for state saving, prior to
+ * saving state.
+ * @type array
+ * @default []
+ */
+ "aoStateSaveParams": [],
+
+ /**
+ * Callbacks for modifying the settings that have been stored for state saving
+ * prior to using the stored values to restore the state.
+ * @type array
+ * @default []
+ */
+ "aoStateLoadParams": [],
+
+ /**
+ * Callbacks for operating on the settings object once the saved state has been
+ * loaded
+ * @type array
+ * @default []
+ */
+ "aoStateLoaded": [],
+
+ /**
+ * Cache the table ID for quick access
+ * @type string
+ * @default <i>Empty string</i>
+ */
+ "sTableId": "",
+
+ /**
+ * The TABLE node for the main table
+ * @type node
+ * @default null
+ */
+ "nTable": null,
+
+ /**
+ * Permanent ref to the thead element
+ * @type node
+ * @default null
+ */
+ "nTHead": null,
+
+ /**
+ * Permanent ref to the tfoot element - if it exists
+ * @type node
+ * @default null
+ */
+ "nTFoot": null,
+
+ /**
+ * Permanent ref to the tbody element
+ * @type node
+ * @default null
+ */
+ "nTBody": null,
+
+ /**
+ * Cache the wrapper node (contains all DataTables controlled elements)
+ * @type node
+ * @default null
+ */
+ "nTableWrapper": null,
+
+ /**
+ * Indicate if when using server-side processing the loading of data
+ * should be deferred until the second draw.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ * @default false
+ */
+ "bDeferLoading": false,
+
+ /**
+ * Indicate if all required information has been read in
+ * @type boolean
+ * @default false
+ */
+ "bInitialised": false,
+
+ /**
+ * Information about open rows. Each object in the array has the parameters
+ * 'nTr' and 'nParent'
+ * @type array
+ * @default []
+ */
+ "aoOpenRows": [],
+
+ /**
+ * Dictate the positioning of DataTables' control elements - see
+ * {@link DataTable.model.oInit.sDom}.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type string
+ * @default null
+ */
+ "sDom": null,
+
+ /**
+ * Search delay (in mS)
+ * @type integer
+ * @default null
+ */
+ "searchDelay": null,
+
+ /**
+ * Which type of pagination should be used.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type string
+ * @default two_button
+ */
+ "sPaginationType": "two_button",
+
+ /**
+ * The state duration (for `stateSave`) in seconds.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type int
+ * @default 0
+ */
+ "iStateDuration": 0,
+
+ /**
+ * Array of callback functions for state saving. Each array element is an
+ * object with the following parameters:
+ * <ul>
+ * <li>function:fn - function to call. Takes two parameters, oSettings
+ * and the JSON string to save that has been thus far created. Returns
+ * a JSON string to be inserted into a json object
+ * (i.e. '"param": [ 0, 1, 2]')</li>
+ * <li>string:sName - name of callback</li>
+ * </ul>
+ * @type array
+ * @default []
+ */
+ "aoStateSave": [],
+
+ /**
+ * Array of callback functions for state loading. Each array element is an
+ * object with the following parameters:
+ * <ul>
+ * <li>function:fn - function to call. Takes two parameters, oSettings
+ * and the object stored. May return false to cancel state loading</li>
+ * <li>string:sName - name of callback</li>
+ * </ul>
+ * @type array
+ * @default []
+ */
+ "aoStateLoad": [],
+
+ /**
+ * State that was saved. Useful for back reference
+ * @type object
+ * @default null
+ */
+ "oSavedState": null,
+
+ /**
+ * State that was loaded. Useful for back reference
+ * @type object
+ * @default null
+ */
+ "oLoadedState": null,
+
+ /**
+ * Source url for AJAX data for the table.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type string
+ * @default null
+ */
+ "sAjaxSource": null,
+
+ /**
+ * Property from a given object from which to read the table data from. This
+ * can be an empty string (when not server-side processing), in which case
+ * it is assumed an an array is given directly.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type string
+ */
+ "sAjaxDataProp": null,
+
+ /**
+ * Note if draw should be blocked while getting data
+ * @type boolean
+ * @default true
+ */
+ "bAjaxDataGet": true,
+
+ /**
+ * The last jQuery XHR object that was used for server-side data gathering.
+ * This can be used for working with the XHR information in one of the
+ * callbacks
+ * @type object
+ * @default null
+ */
+ "jqXHR": null,
+
+ /**
+ * JSON returned from the server in the last Ajax request
+ * @type object
+ * @default undefined
+ */
+ "json": undefined,
+
+ /**
+ * Data submitted as part of the last Ajax request
+ * @type object
+ * @default undefined
+ */
+ "oAjaxData": undefined,
+
+ /**
+ * Function to get the server-side data.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type function
+ */
+ "fnServerData": null,
+
+ /**
+ * Functions which are called prior to sending an Ajax request so extra
+ * parameters can easily be sent to the server
+ * @type array
+ * @default []
+ */
+ "aoServerParams": [],
+
+ /**
+ * Send the XHR HTTP method - GET or POST (could be PUT or DELETE if
+ * required).
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type string
+ */
+ "sServerMethod": null,
+
+ /**
+ * Format numbers for display.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type function
+ */
+ "fnFormatNumber": null,
+
+ /**
+ * List of options that can be used for the user selectable length menu.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type array
+ * @default []
+ */
+ "aLengthMenu": null,
+
+ /**
+ * Counter for the draws that the table does. Also used as a tracker for
+ * server-side processing
+ * @type int
+ * @default 0
+ */
+ "iDraw": 0,
+
+ /**
+ * Indicate if a redraw is being done - useful for Ajax
+ * @type boolean
+ * @default false
+ */
+ "bDrawing": false,
+
+ /**
+ * Draw index (iDraw) of the last error when parsing the returned data
+ * @type int
+ * @default -1
+ */
+ "iDrawError": -1,
+
+ /**
+ * Paging display length
+ * @type int
+ * @default 10
+ */
+ "_iDisplayLength": 10,
+
+ /**
+ * Paging start point - aiDisplay index
+ * @type int
+ * @default 0
+ */
+ "_iDisplayStart": 0,
+
+ /**
+ * Server-side processing - number of records in the result set
+ * (i.e. before filtering), Use fnRecordsTotal rather than
+ * this property to get the value of the number of records, regardless of
+ * the server-side processing setting.
+ * @type int
+ * @default 0
+ * @private
+ */
+ "_iRecordsTotal": 0,
+
+ /**
+ * Server-side processing - number of records in the current display set
+ * (i.e. after filtering). Use fnRecordsDisplay rather than
+ * this property to get the value of the number of records, regardless of
+ * the server-side processing setting.
+ * @type boolean
+ * @default 0
+ * @private
+ */
+ "_iRecordsDisplay": 0,
+
+ /**
+ * Flag to indicate if jQuery UI marking and classes should be used.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bJUI": null,
+
+ /**
+ * The classes to use for the table
+ * @type object
+ * @default {}
+ */
+ "oClasses": {},
+
+ /**
+ * Flag attached to the settings object so you can check in the draw
+ * callback if filtering has been done in the draw. Deprecated in favour of
+ * events.
+ * @type boolean
+ * @default false
+ * @deprecated
+ */
+ "bFiltered": false,
+
+ /**
+ * Flag attached to the settings object so you can check in the draw
+ * callback if sorting has been done in the draw. Deprecated in favour of
+ * events.
+ * @type boolean
+ * @default false
+ * @deprecated
+ */
+ "bSorted": false,
+
+ /**
+ * Indicate that if multiple rows are in the header and there is more than
+ * one unique cell per column, if the top one (true) or bottom one (false)
+ * should be used for sorting / title by DataTables.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ */
+ "bSortCellsTop": null,
+
+ /**
+ * Initialisation object that is used for the table
+ * @type object
+ * @default null
+ */
+ "oInit": null,
+
+ /**
+ * Destroy callback functions - for plug-ins to attach themselves to the
+ * destroy so they can clean up markup and events.
+ * @type array
+ * @default []
+ */
+ "aoDestroyCallback": [],
+
+
+ /**
+ * Get the number of records in the current record set, before filtering
+ * @type function
+ */
+ "fnRecordsTotal": function ()
+ {
+ return _fnDataSource( this ) == 'ssp' ?
+ this._iRecordsTotal * 1 :
+ this.aiDisplayMaster.length;
+ },
+
+ /**
+ * Get the number of records in the current record set, after filtering
+ * @type function
+ */
+ "fnRecordsDisplay": function ()
+ {
+ return _fnDataSource( this ) == 'ssp' ?
+ this._iRecordsDisplay * 1 :
+ this.aiDisplay.length;
+ },
+
+ /**
+ * Get the display end point - aiDisplay index
+ * @type function
+ */
+ "fnDisplayEnd": function ()
+ {
+ var
+ len = this._iDisplayLength,
+ start = this._iDisplayStart,
+ calc = start + len,
+ records = this.aiDisplay.length,
+ features = this.oFeatures,
+ paginate = features.bPaginate;
+
+ if ( features.bServerSide ) {
+ return paginate === false || len === -1 ?
+ start + records :
+ Math.min( start+len, this._iRecordsDisplay );
+ }
+ else {
+ return ! paginate || calc>records || len===-1 ?
+ records :
+ calc;
+ }
+ },
+
+ /**
+ * The DataTables object for this table
+ * @type object
+ * @default null
+ */
+ "oInstance": null,
+
+ /**
+ * Unique identifier for each instance of the DataTables object. If there
+ * is an ID on the table node, then it takes that value, otherwise an
+ * incrementing internal counter is used.
+ * @type string
+ * @default null
+ */
+ "sInstance": null,
+
+ /**
+ * tabindex attribute value that is added to DataTables control elements, allowing
+ * keyboard navigation of the table and its controls.
+ */
+ "iTabIndex": 0,
+
+ /**
+ * DIV container for the footer scrolling table if scrolling
+ */
+ "nScrollHead": null,
+
+ /**
+ * DIV container for the footer scrolling table if scrolling
+ */
+ "nScrollFoot": null,
+
+ /**
+ * Last applied sort
+ * @type array
+ * @default []
+ */
+ "aLastSort": [],
+
+ /**
+ * Stored plug-in instances
+ * @type object
+ * @default {}
+ */
+ "oPlugins": {},
+
+ /**
+ * Function used to get a row's id from the row's data
+ * @type function
+ * @default null
+ */
+ "rowIdFn": null,
+
+ /**
+ * Data location where to store a row's id
+ * @type string
+ * @default null
+ */
+ "rowId": null
+ };
+
+ /**
+ * Extension object for DataTables that is used to provide all extension
+ * options.
+ *
+ * Note that the `DataTable.ext` object is available through
+ * `jQuery.fn.dataTable.ext` where it may be accessed and manipulated. It is
+ * also aliased to `jQuery.fn.dataTableExt` for historic reasons.
+ * @namespace
+ * @extends DataTable.models.ext
+ */
+
+
+ /**
+ * DataTables extensions
+ *
+ * This namespace acts as a collection area for plug-ins that can be used to
+ * extend DataTables capabilities. Indeed many of the build in methods
+ * use this method to provide their own capabilities (sorting methods for
+ * example).
+ *
+ * Note that this namespace is aliased to `jQuery.fn.dataTableExt` for legacy
+ * reasons
+ *
+ * @namespace
+ */
+ DataTable.ext = _ext = {
+ /**
+ * Buttons. For use with the Buttons extension for DataTables. This is
+ * defined here so other extensions can define buttons regardless of load
+ * order. It is _not_ used by DataTables core.
+ *
+ * @type object
+ * @default {}
+ */
+ buttons: {},
+
+
+ /**
+ * Element class names
+ *
+ * @type object
+ * @default {}
+ */
+ classes: {},
+
+
+ /**
+ * DataTables build type (expanded by the download builder)
+ *
+ * @type string
+ */
+ builder: "-source-",
+
+
+ /**
+ * Error reporting.
+ *
+ * How should DataTables report an error. Can take the value 'alert',
+ * 'throw', 'none' or a function.
+ *
+ * @type string|function
+ * @default alert
+ */
+ errMode: "alert",
+
+
+ /**
+ * Feature plug-ins.
+ *
+ * This is an array of objects which describe the feature plug-ins that are
+ * available to DataTables. These feature plug-ins are then available for
+ * use through the `dom` initialisation option.
+ *
+ * Each feature plug-in is described by an object which must have the
+ * following properties:
+ *
+ * * `fnInit` - function that is used to initialise the plug-in,
+ * * `cFeature` - a character so the feature can be enabled by the `dom`
+ * instillation option. This is case sensitive.
+ *
+ * The `fnInit` function has the following input parameters:
+ *
+ * 1. `{object}` DataTables settings object: see
+ * {@link DataTable.models.oSettings}
+ *
+ * And the following return is expected:
+ *
+ * * {node|null} The element which contains your feature. Note that the
+ * return may also be void if your plug-in does not require to inject any
+ * DOM elements into DataTables control (`dom`) - for example this might
+ * be useful when developing a plug-in which allows table control via
+ * keyboard entry
+ *
+ * @type array
+ *
+ * @example
+ * $.fn.dataTable.ext.features.push( {
+ * "fnInit": function( oSettings ) {
+ * return new TableTools( { "oDTSettings": oSettings } );
+ * },
+ * "cFeature": "T"
+ * } );
+ */
+ feature: [],
+
+
+ /**
+ * Row searching.
+ *
+ * This method of searching is complimentary to the default type based
+ * searching, and a lot more comprehensive as it allows you complete control
+ * over the searching logic. Each element in this array is a function
+ * (parameters described below) that is called for every row in the table,
+ * and your logic decides if it should be included in the searching data set
+ * or not.
+ *
+ * Searching functions have the following input parameters:
+ *
+ * 1. `{object}` DataTables settings object: see
+ * {@link DataTable.models.oSettings}
+ * 2. `{array|object}` Data for the row to be processed (same as the
+ * original format that was passed in as the data source, or an array
+ * from a DOM data source
+ * 3. `{int}` Row index ({@link DataTable.models.oSettings.aoData}), which
+ * can be useful to retrieve the `TR` element if you need DOM interaction.
+ *
+ * And the following return is expected:
+ *
+ * * {boolean} Include the row in the searched result set (true) or not
+ * (false)
+ *
+ * Note that as with the main search ability in DataTables, technically this
+ * is "filtering", since it is subtractive. However, for consistency in
+ * naming we call it searching here.
+ *
+ * @type array
+ * @default []
+ *
+ * @example
+ * // The following example shows custom search being applied to the
+ * // fourth column (i.e. the data[3] index) based on two input values
+ * // from the end-user, matching the data in a certain range.
+ * $.fn.dataTable.ext.search.push(
+ * function( settings, data, dataIndex ) {
+ * var min = document.getElementById('min').value * 1;
+ * var max = document.getElementById('max').value * 1;
+ * var version = data[3] == "-" ? 0 : data[3]*1;
+ *
+ * if ( min == "" && max == "" ) {
+ * return true;
+ * }
+ * else if ( min == "" && version < max ) {
+ * return true;
+ * }
+ * else if ( min < version && "" == max ) {
+ * return true;
+ * }
+ * else if ( min < version && version < max ) {
+ * return true;
+ * }
+ * return false;
+ * }
+ * );
+ */
+ search: [],
+
+
+ /**
+ * Selector extensions
+ *
+ * The `selector` option can be used to extend the options available for the
+ * selector modifier options (`selector-modifier` object data type) that
+ * each of the three built in selector types offer (row, column and cell +
+ * their plural counterparts). For example the Select extension uses this
+ * mechanism to provide an option to select only rows, columns and cells
+ * that have been marked as selected by the end user (`{selected: true}`),
+ * which can be used in conjunction with the existing built in selector
+ * options.
+ *
+ * Each property is an array to which functions can be pushed. The functions
+ * take three attributes:
+ *
+ * * Settings object for the host table
+ * * Options object (`selector-modifier` object type)
+ * * Array of selected item indexes
+ *
+ * The return is an array of the resulting item indexes after the custom
+ * selector has been applied.
+ *
+ * @type object
+ */
+ selector: {
+ cell: [],
+ column: [],
+ row: []
+ },
+
+
+ /**
+ * Internal functions, exposed for used in plug-ins.
+ *
+ * Please note that you should not need to use the internal methods for
+ * anything other than a plug-in (and even then, try to avoid if possible).
+ * The internal function may change between releases.
+ *
+ * @type object
+ * @default {}
+ */
+ internal: {},
+
+
+ /**
+ * Legacy configuration options. Enable and disable legacy options that
+ * are available in DataTables.
+ *
+ * @type object
+ */
+ legacy: {
+ /**
+ * Enable / disable DataTables 1.9 compatible server-side processing
+ * requests
+ *
+ * @type boolean
+ * @default null
+ */
+ ajax: null
+ },
+
+
+ /**
+ * Pagination plug-in methods.
+ *
+ * Each entry in this object is a function and defines which buttons should
+ * be shown by the pagination rendering method that is used for the table:
+ * {@link DataTable.ext.renderer.pageButton}. The renderer addresses how the
+ * buttons are displayed in the document, while the functions here tell it
+ * what buttons to display. This is done by returning an array of button
+ * descriptions (what each button will do).
+ *
+ * Pagination types (the four built in options and any additional plug-in
+ * options defined here) can be used through the `paginationType`
+ * initialisation parameter.
+ *
+ * The functions defined take two parameters:
+ *
+ * 1. `{int} page` The current page index
+ * 2. `{int} pages` The number of pages in the table
+ *
+ * Each function is expected to return an array where each element of the
+ * array can be one of:
+ *
+ * * `first` - Jump to first page when activated
+ * * `last` - Jump to last page when activated
+ * * `previous` - Show previous page when activated
+ * * `next` - Show next page when activated
+ * * `{int}` - Show page of the index given
+ * * `{array}` - A nested array containing the above elements to add a
+ * containing 'DIV' element (might be useful for styling).
+ *
+ * Note that DataTables v1.9- used this object slightly differently whereby
+ * an object with two functions would be defined for each plug-in. That
+ * ability is still supported by DataTables 1.10+ to provide backwards
+ * compatibility, but this option of use is now decremented and no longer
+ * documented in DataTables 1.10+.
+ *
+ * @type object
+ * @default {}
+ *
+ * @example
+ * // Show previous, next and current page buttons only
+ * $.fn.dataTableExt.oPagination.current = function ( page, pages ) {
+ * return [ 'previous', page, 'next' ];
+ * };
+ */
+ pager: {},
+
+
+ renderer: {
+ pageButton: {},
+ header: {}
+ },
+
+
+ /**
+ * Ordering plug-ins - custom data source
+ *
+ * The extension options for ordering of data available here is complimentary
+ * to the default type based ordering that DataTables typically uses. It
+ * allows much greater control over the the data that is being used to
+ * order a column, but is necessarily therefore more complex.
+ *
+ * This type of ordering is useful if you want to do ordering based on data
+ * live from the DOM (for example the contents of an 'input' element) rather
+ * than just the static string that DataTables knows of.
+ *
+ * The way these plug-ins work is that you create an array of the values you
+ * wish to be ordering for the column in question and then return that
+ * array. The data in the array much be in the index order of the rows in
+ * the table (not the currently ordering order!). Which order data gathering
+ * function is run here depends on the `dt-init columns.orderDataType`
+ * parameter that is used for the column (if any).
+ *
+ * The functions defined take two parameters:
+ *
+ * 1. `{object}` DataTables settings object: see
+ * {@link DataTable.models.oSettings}
+ * 2. `{int}` Target column index
+ *
+ * Each function is expected to return an array:
+ *
+ * * `{array}` Data for the column to be ordering upon
+ *
+ * @type array
+ *
+ * @example
+ * // Ordering using `input` node values
+ * $.fn.dataTable.ext.order['dom-text'] = function ( settings, col )
+ * {
+ * return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) {
+ * return $('input', td).val();
+ * } );
+ * }
+ */
+ order: {},
+
+
+ /**
+ * Type based plug-ins.
+ *
+ * Each column in DataTables has a type assigned to it, either by automatic
+ * detection or by direct assignment using the `type` option for the column.
+ * The type of a column will effect how it is ordering and search (plug-ins
+ * can also make use of the column type if required).
+ *
+ * @namespace
+ */
+ type: {
+ /**
+ * Type detection functions.
+ *
+ * The functions defined in this object are used to automatically detect
+ * a column's type, making initialisation of DataTables super easy, even
+ * when complex data is in the table.
+ *
+ * The functions defined take two parameters:
+ *
+ * 1. `{*}` Data from the column cell to be analysed
+ * 2. `{settings}` DataTables settings object. This can be used to
+ * perform context specific type detection - for example detection
+ * based on language settings such as using a comma for a decimal
+ * place. Generally speaking the options from the settings will not
+ * be required
+ *
+ * Each function is expected to return:
+ *
+ * * `{string|null}` Data type detected, or null if unknown (and thus
+ * pass it on to the other type detection functions.
+ *
+ * @type array
+ *
+ * @example
+ * // Currency type detection plug-in:
+ * $.fn.dataTable.ext.type.detect.push(
+ * function ( data, settings ) {
+ * // Check the numeric part
+ * if ( ! $.isNumeric( data.substring(1) ) ) {
+ * return null;
+ * }
+ *
+ * // Check prefixed by currency
+ * if ( data.charAt(0) == '$' || data.charAt(0) == '&pound;' ) {
+ * return 'currency';
+ * }
+ * return null;
+ * }
+ * );
+ */
+ detect: [],
+
+
+ /**
+ * Type based search formatting.
+ *
+ * The type based searching functions can be used to pre-format the
+ * data to be search on. For example, it can be used to strip HTML
+ * tags or to de-format telephone numbers for numeric only searching.
+ *
+ * Note that is a search is not defined for a column of a given type,
+ * no search formatting will be performed.
+ *
+ * Pre-processing of searching data plug-ins - When you assign the sType
+ * for a column (or have it automatically detected for you by DataTables
+ * or a type detection plug-in), you will typically be using this for
+ * custom sorting, but it can also be used to provide custom searching
+ * by allowing you to pre-processing the data and returning the data in
+ * the format that should be searched upon. This is done by adding
+ * functions this object with a parameter name which matches the sType
+ * for that target column. This is the corollary of <i>afnSortData</i>
+ * for searching data.
+ *
+ * The functions defined take a single parameter:
+ *
+ * 1. `{*}` Data from the column cell to be prepared for searching
+ *
+ * Each function is expected to return:
+ *
+ * * `{string|null}` Formatted string that will be used for the searching.
+ *
+ * @type object
+ * @default {}
+ *
+ * @example
+ * $.fn.dataTable.ext.type.search['title-numeric'] = function ( d ) {
+ * return d.replace(/\n/g," ").replace( /<.*?>/g, "" );
+ * }
+ */
+ search: {},
+
+
+ /**
+ * Type based ordering.
+ *
+ * The column type tells DataTables what ordering to apply to the table
+ * when a column is sorted upon. The order for each type that is defined,
+ * is defined by the functions available in this object.
+ *
+ * Each ordering option can be described by three properties added to
+ * this object:
+ *
+ * * `{type}-pre` - Pre-formatting function
+ * * `{type}-asc` - Ascending order function
+ * * `{type}-desc` - Descending order function
+ *
+ * All three can be used together, only `{type}-pre` or only
+ * `{type}-asc` and `{type}-desc` together. It is generally recommended
+ * that only `{type}-pre` is used, as this provides the optimal
+ * implementation in terms of speed, although the others are provided
+ * for compatibility with existing Javascript sort functions.
+ *
+ * `{type}-pre`: Functions defined take a single parameter:
+ *
+ * 1. `{*}` Data from the column cell to be prepared for ordering
+ *
+ * And return:
+ *
+ * * `{*}` Data to be sorted upon
+ *
+ * `{type}-asc` and `{type}-desc`: Functions are typical Javascript sort
+ * functions, taking two parameters:
+ *
+ * 1. `{*}` Data to compare to the second parameter
+ * 2. `{*}` Data to compare to the first parameter
+ *
+ * And returning:
+ *
+ * * `{*}` Ordering match: <0 if first parameter should be sorted lower
+ * than the second parameter, ===0 if the two parameters are equal and
+ * >0 if the first parameter should be sorted height than the second
+ * parameter.
+ *
+ * @type object
+ * @default {}
+ *
+ * @example
+ * // Numeric ordering of formatted numbers with a pre-formatter
+ * $.extend( $.fn.dataTable.ext.type.order, {
+ * "string-pre": function(x) {
+ * a = (a === "-" || a === "") ? 0 : a.replace( /[^\d\-\.]/g, "" );
+ * return parseFloat( a );
+ * }
+ * } );
+ *
+ * @example
+ * // Case-sensitive string ordering, with no pre-formatting method
+ * $.extend( $.fn.dataTable.ext.order, {
+ * "string-case-asc": function(x,y) {
+ * return ((x < y) ? -1 : ((x > y) ? 1 : 0));
+ * },
+ * "string-case-desc": function(x,y) {
+ * return ((x < y) ? 1 : ((x > y) ? -1 : 0));
+ * }
+ * } );
+ */
+ order: {}
+ },
+
+ /**
+ * Unique DataTables instance counter
+ *
+ * @type int
+ * @private
+ */
+ _unique: 0,
+
+
+ //
+ // Depreciated
+ // The following properties are retained for backwards compatiblity only.
+ // The should not be used in new projects and will be removed in a future
+ // version
+ //
+
+ /**
+ * Version check function.
+ * @type function
+ * @depreciated Since 1.10
+ */
+ fnVersionCheck: DataTable.fnVersionCheck,
+
+
+ /**
+ * Index for what 'this' index API functions should use
+ * @type int
+ * @deprecated Since v1.10
+ */
+ iApiIndex: 0,
+
+
+ /**
+ * jQuery UI class container
+ * @type object
+ * @deprecated Since v1.10
+ */
+ oJUIClasses: {},
+
+
+ /**
+ * Software version
+ * @type string
+ * @deprecated Since v1.10
+ */
+ sVersion: DataTable.version
+ };
+
+
+ //
+ // Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts
+ //
+ $.extend( _ext, {
+ afnFiltering: _ext.search,
+ aTypes: _ext.type.detect,
+ ofnSearch: _ext.type.search,
+ oSort: _ext.type.order,
+ afnSortData: _ext.order,
+ aoFeatures: _ext.feature,
+ oApi: _ext.internal,
+ oStdClasses: _ext.classes,
+ oPagination: _ext.pager
+ } );
+
+
+ $.extend( DataTable.ext.classes, {
+ "sTable": "dataTable",
+ "sNoFooter": "no-footer",
+
+ /* Paging buttons */
+ "sPageButton": "paginate_button",
+ "sPageButtonActive": "current",
+ "sPageButtonDisabled": "disabled",
+
+ /* Striping classes */
+ "sStripeOdd": "odd",
+ "sStripeEven": "even",
+
+ /* Empty row */
+ "sRowEmpty": "dataTables_empty",
+
+ /* Features */
+ "sWrapper": "dataTables_wrapper",
+ "sFilter": "dataTables_filter",
+ "sInfo": "dataTables_info",
+ "sPaging": "dataTables_paginate paging_", /* Note that the type is postfixed */
+ "sLength": "dataTables_length",
+ "sProcessing": "dataTables_processing",
+
+ /* Sorting */
+ "sSortAsc": "sorting_asc",
+ "sSortDesc": "sorting_desc",
+ "sSortable": "sorting", /* Sortable in both directions */
+ "sSortableAsc": "sorting_asc_disabled",
+ "sSortableDesc": "sorting_desc_disabled",
+ "sSortableNone": "sorting_disabled",
+ "sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */
+
+ /* Filtering */
+ "sFilterInput": "",
+
+ /* Page length */
+ "sLengthSelect": "",
+
+ /* Scrolling */
+ "sScrollWrapper": "dataTables_scroll",
+ "sScrollHead": "dataTables_scrollHead",
+ "sScrollHeadInner": "dataTables_scrollHeadInner",
+ "sScrollBody": "dataTables_scrollBody",
+ "sScrollFoot": "dataTables_scrollFoot",
+ "sScrollFootInner": "dataTables_scrollFootInner",
+
+ /* Misc */
+ "sHeaderTH": "",
+ "sFooterTH": "",
+
+ // Deprecated
+ "sSortJUIAsc": "",
+ "sSortJUIDesc": "",
+ "sSortJUI": "",
+ "sSortJUIAscAllowed": "",
+ "sSortJUIDescAllowed": "",
+ "sSortJUIWrapper": "",
+ "sSortIcon": "",
+ "sJUIHeader": "",
+ "sJUIFooter": ""
+ } );
+
+
+ (function() {
+
+ // Reused strings for better compression. Closure compiler appears to have a
+ // weird edge case where it is trying to expand strings rather than use the
+ // variable version. This results in about 200 bytes being added, for very
+ // little preference benefit since it this run on script load only.
+ var _empty = '';
+ _empty = '';
+
+ var _stateDefault = _empty + 'ui-state-default';
+ var _sortIcon = _empty + 'css_right ui-icon ui-icon-';
+ var _headerFooter = _empty + 'fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix';
+
+ $.extend( DataTable.ext.oJUIClasses, DataTable.ext.classes, {
+ /* Full numbers paging buttons */
+ "sPageButton": "fg-button ui-button "+_stateDefault,
+ "sPageButtonActive": "ui-state-disabled",
+ "sPageButtonDisabled": "ui-state-disabled",
+
+ /* Features */
+ "sPaging": "dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi "+
+ "ui-buttonset-multi paging_", /* Note that the type is postfixed */
+
+ /* Sorting */
+ "sSortAsc": _stateDefault+" sorting_asc",
+ "sSortDesc": _stateDefault+" sorting_desc",
+ "sSortable": _stateDefault+" sorting",
+ "sSortableAsc": _stateDefault+" sorting_asc_disabled",
+ "sSortableDesc": _stateDefault+" sorting_desc_disabled",
+ "sSortableNone": _stateDefault+" sorting_disabled",
+ "sSortJUIAsc": _sortIcon+"triangle-1-n",
+ "sSortJUIDesc": _sortIcon+"triangle-1-s",
+ "sSortJUI": _sortIcon+"carat-2-n-s",
+ "sSortJUIAscAllowed": _sortIcon+"carat-1-n",
+ "sSortJUIDescAllowed": _sortIcon+"carat-1-s",
+ "sSortJUIWrapper": "DataTables_sort_wrapper",
+ "sSortIcon": "DataTables_sort_icon",
+
+ /* Scrolling */
+ "sScrollHead": "dataTables_scrollHead "+_stateDefault,
+ "sScrollFoot": "dataTables_scrollFoot "+_stateDefault,
+
+ /* Misc */
+ "sHeaderTH": _stateDefault,
+ "sFooterTH": _stateDefault,
+ "sJUIHeader": _headerFooter+" ui-corner-tl ui-corner-tr",
+ "sJUIFooter": _headerFooter+" ui-corner-bl ui-corner-br"
+ } );
+
+ }());
+
+
+
+ var extPagination = DataTable.ext.pager;
+
+ function _numbers ( page, pages ) {
+ var
+ numbers = [],
+ buttons = extPagination.numbers_length,
+ half = Math.floor( buttons / 2 ),
+ i = 1;
+
+ if ( pages <= buttons ) {
+ numbers = _range( 0, pages );
+ }
+ else if ( page <= half ) {
+ numbers = _range( 0, buttons-2 );
+ numbers.push( 'ellipsis' );
+ numbers.push( pages-1 );
+ }
+ else if ( page >= pages - 1 - half ) {
+ numbers = _range( pages-(buttons-2), pages );
+ numbers.splice( 0, 0, 'ellipsis' ); // no unshift in ie6
+ numbers.splice( 0, 0, 0 );
+ }
+ else {
+ numbers = _range( page-half+2, page+half-1 );
+ numbers.push( 'ellipsis' );
+ numbers.push( pages-1 );
+ numbers.splice( 0, 0, 'ellipsis' );
+ numbers.splice( 0, 0, 0 );
+ }
+
+ numbers.DT_el = 'span';
+ return numbers;
+ }
+
+
+ $.extend( extPagination, {
+ simple: function ( page, pages ) {
+ return [ 'previous', 'next' ];
+ },
+
+ full: function ( page, pages ) {
+ return [ 'first', 'previous', 'next', 'last' ];
+ },
+
+ numbers: function ( page, pages ) {
+ return [ _numbers(page, pages) ];
+ },
+
+ simple_numbers: function ( page, pages ) {
+ return [ 'previous', _numbers(page, pages), 'next' ];
+ },
+
+ full_numbers: function ( page, pages ) {
+ return [ 'first', 'previous', _numbers(page, pages), 'next', 'last' ];
+ },
+
+ first_last_numbers: function (page, pages) {
+ return ['first', _numbers(page, pages), 'last'];
+ },
+
+ // For testing and plug-ins to use
+ _numbers: _numbers,
+
+ // Number of number buttons (including ellipsis) to show. _Must be odd!_
+ numbers_length: 7
+ } );
+
+
+ $.extend( true, DataTable.ext.renderer, {
+ pageButton: {
+ _: function ( settings, host, idx, buttons, page, pages ) {
+ var classes = settings.oClasses;
+ var lang = settings.oLanguage.oPaginate;
+ var aria = settings.oLanguage.oAria.paginate || {};
+ var btnDisplay, btnClass, counter=0;
+
+ var attach = function( container, buttons ) {
+ var i, ien, node, button;
+ var clickHandler = function ( e ) {
+ _fnPageChange( settings, e.data.action, true );
+ };
+
+ for ( i=0, ien=buttons.length ; i<ien ; i++ ) {
+ button = buttons[i];
+
+ if ( $.isArray( button ) ) {
+ var inner = $( '<'+(button.DT_el || 'div')+'/>' )
+ .appendTo( container );
+ attach( inner, button );
+ }
+ else {
+ btnDisplay = null;
+ btnClass = '';
+
+ switch ( button ) {
+ case 'ellipsis':
+ container.append('<span class="ellipsis">&#x2026;</span>');
+ break;
+
+ case 'first':
+ btnDisplay = lang.sFirst;
+ btnClass = button + (page > 0 ?
+ '' : ' '+classes.sPageButtonDisabled);
+ break;
+
+ case 'previous':
+ btnDisplay = lang.sPrevious;
+ btnClass = button + (page > 0 ?
+ '' : ' '+classes.sPageButtonDisabled);
+ break;
+
+ case 'next':
+ btnDisplay = lang.sNext;
+ btnClass = button + (page < pages-1 ?
+ '' : ' '+classes.sPageButtonDisabled);
+ break;
+
+ case 'last':
+ btnDisplay = lang.sLast;
+ btnClass = button + (page < pages-1 ?
+ '' : ' '+classes.sPageButtonDisabled);
+ break;
+
+ default:
+ btnDisplay = button + 1;
+ btnClass = page === button ?
+ classes.sPageButtonActive : '';
+ break;
+ }
+
+ if ( btnDisplay !== null ) {
+ node = $('<a>', {
+ 'class': classes.sPageButton+' '+btnClass,
+ 'aria-controls': settings.sTableId,
+ 'aria-label': aria[ button ],
+ 'data-dt-idx': counter,
+ 'tabindex': settings.iTabIndex,
+ 'id': idx === 0 && typeof button === 'string' ?
+ settings.sTableId +'_'+ button :
+ null
+ } )
+ .html( btnDisplay )
+ .appendTo( container );
+
+ _fnBindAction(
+ node, {action: button}, clickHandler
+ );
+
+ counter++;
+ }
+ }
+ }
+ };
+
+ // IE9 throws an 'unknown error' if document.activeElement is used
+ // inside an iframe or frame. Try / catch the error. Not good for
+ // accessibility, but neither are frames.
+ var activeEl;
+
+ try {
+ // Because this approach is destroying and recreating the paging
+ // elements, focus is lost on the select button which is bad for
+ // accessibility. So we want to restore focus once the draw has
+ // completed
+ activeEl = $(host).find(document.activeElement).data('dt-idx');
+ }
+ catch (e) {}
+
+ attach( $(host).empty(), buttons );
+
+ if ( activeEl !== undefined ) {
+ $(host).find( '[data-dt-idx='+activeEl+']' ).focus();
+ }
+ }
+ }
+ } );
+
+
+
+ // Built in type detection. See model.ext.aTypes for information about
+ // what is required from this methods.
+ $.extend( DataTable.ext.type.detect, [
+ // Plain numbers - first since V8 detects some plain numbers as dates
+ // e.g. Date.parse('55') (but not all, e.g. Date.parse('22')...).
+ function ( d, settings )
+ {
+ var decimal = settings.oLanguage.sDecimal;
+ return _isNumber( d, decimal ) ? 'num'+decimal : null;
+ },
+
+ // Dates (only those recognised by the browser's Date.parse)
+ function ( d, settings )
+ {
+ // V8 tries _very_ hard to make a string passed into `Date.parse()`
+ // valid, so we need to use a regex to restrict date formats. Use a
+ // plug-in for anything other than ISO8601 style strings
+ if ( d && !(d instanceof Date) && ! _re_date.test(d) ) {
+ return null;
+ }
+ var parsed = Date.parse(d);
+ return (parsed !== null && !isNaN(parsed)) || _empty(d) ? 'date' : null;
+ },
+
+ // Formatted numbers
+ function ( d, settings )
+ {
+ var decimal = settings.oLanguage.sDecimal;
+ return _isNumber( d, decimal, true ) ? 'num-fmt'+decimal : null;
+ },
+
+ // HTML numeric
+ function ( d, settings )
+ {
+ var decimal = settings.oLanguage.sDecimal;
+ return _htmlNumeric( d, decimal ) ? 'html-num'+decimal : null;
+ },
+
+ // HTML numeric, formatted
+ function ( d, settings )
+ {
+ var decimal = settings.oLanguage.sDecimal;
+ return _htmlNumeric( d, decimal, true ) ? 'html-num-fmt'+decimal : null;
+ },
+
+ // HTML (this is strict checking - there must be html)
+ function ( d, settings )
+ {
+ return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1) ?
+ 'html' : null;
+ }
+ ] );
+
+
+
+ // Filter formatting functions. See model.ext.ofnSearch for information about
+ // what is required from these methods.
+ //
+ // Note that additional search methods are added for the html numbers and
+ // html formatted numbers by `_addNumericSort()` when we know what the decimal
+ // place is
+
+
+ $.extend( DataTable.ext.type.search, {
+ html: function ( data ) {
+ return _empty(data) ?
+ data :
+ typeof data === 'string' ?
+ data
+ .replace( _re_new_lines, " " )
+ .replace( _re_html, "" ) :
+ '';
+ },
+
+ string: function ( data ) {
+ return _empty(data) ?
+ data :
+ typeof data === 'string' ?
+ data.replace( _re_new_lines, " " ) :
+ data;
+ }
+ } );
+
+
+
+ var __numericReplace = function ( d, decimalPlace, re1, re2 ) {
+ if ( d !== 0 && (!d || d === '-') ) {
+ return -Infinity;
+ }
+
+ // If a decimal place other than `.` is used, it needs to be given to the
+ // function so we can detect it and replace with a `.` which is the only
+ // decimal place Javascript recognises - it is not locale aware.
+ if ( decimalPlace ) {
+ d = _numToDecimal( d, decimalPlace );
+ }
+
+ if ( d.replace ) {
+ if ( re1 ) {
+ d = d.replace( re1, '' );
+ }
+
+ if ( re2 ) {
+ d = d.replace( re2, '' );
+ }
+ }
+
+ return d * 1;
+ };
+
+
+ // Add the numeric 'deformatting' functions for sorting and search. This is done
+ // in a function to provide an easy ability for the language options to add
+ // additional methods if a non-period decimal place is used.
+ function _addNumericSort ( decimalPlace ) {
+ $.each(
+ {
+ // Plain numbers
+ "num": function ( d ) {
+ return __numericReplace( d, decimalPlace );
+ },
+
+ // Formatted numbers
+ "num-fmt": function ( d ) {
+ return __numericReplace( d, decimalPlace, _re_formatted_numeric );
+ },
+
+ // HTML numeric
+ "html-num": function ( d ) {
+ return __numericReplace( d, decimalPlace, _re_html );
+ },
+
+ // HTML numeric, formatted
+ "html-num-fmt": function ( d ) {
+ return __numericReplace( d, decimalPlace, _re_html, _re_formatted_numeric );
+ }
+ },
+ function ( key, fn ) {
+ // Add the ordering method
+ _ext.type.order[ key+decimalPlace+'-pre' ] = fn;
+
+ // For HTML types add a search formatter that will strip the HTML
+ if ( key.match(/^html\-/) ) {
+ _ext.type.search[ key+decimalPlace ] = _ext.type.search.html;
+ }
+ }
+ );
+ }
+
+
+ // Default sort methods
+ $.extend( _ext.type.order, {
+ // Dates
+ "date-pre": function ( d ) {
+ return Date.parse( d ) || -Infinity;
+ },
+
+ // html
+ "html-pre": function ( a ) {
+ return _empty(a) ?
+ '' :
+ a.replace ?
+ a.replace( /<.*?>/g, "" ).toLowerCase() :
+ a+'';
+ },
+
+ // string
+ "string-pre": function ( a ) {
+ // This is a little complex, but faster than always calling toString,
+ // http://jsperf.com/tostring-v-check
+ return _empty(a) ?
+ '' :
+ typeof a === 'string' ?
+ a.toLowerCase() :
+ ! a.toString ?
+ '' :
+ a.toString();
+ },
+
+ // string-asc and -desc are retained only for compatibility with the old
+ // sort methods
+ "string-asc": function ( x, y ) {
+ return ((x < y) ? -1 : ((x > y) ? 1 : 0));
+ },
+
+ "string-desc": function ( x, y ) {
+ return ((x < y) ? 1 : ((x > y) ? -1 : 0));
+ }
+ } );
+
+
+ // Numeric sorting types - order doesn't matter here
+ _addNumericSort( '' );
+
+
+ $.extend( true, DataTable.ext.renderer, {
+ header: {
+ _: function ( settings, cell, column, classes ) {
+ // No additional mark-up required
+ // Attach a sort listener to update on sort - note that using the
+ // `DT` namespace will allow the event to be removed automatically
+ // on destroy, while the `dt` namespaced event is the one we are
+ // listening for
+ $(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) {
+ if ( settings !== ctx ) { // need to check this this is the host
+ return; // table, not a nested one
+ }
+
+ var colIdx = column.idx;
+
+ cell
+ .removeClass(
+ column.sSortingClass +' '+
+ classes.sSortAsc +' '+
+ classes.sSortDesc
+ )
+ .addClass( columns[ colIdx ] == 'asc' ?
+ classes.sSortAsc : columns[ colIdx ] == 'desc' ?
+ classes.sSortDesc :
+ column.sSortingClass
+ );
+ } );
+ },
+
+ jqueryui: function ( settings, cell, column, classes ) {
+ $('<div/>')
+ .addClass( classes.sSortJUIWrapper )
+ .append( cell.contents() )
+ .append( $('<span/>')
+ .addClass( classes.sSortIcon+' '+column.sSortingClassJUI )
+ )
+ .appendTo( cell );
+
+ // Attach a sort listener to update on sort
+ $(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) {
+ if ( settings !== ctx ) {
+ return;
+ }
+
+ var colIdx = column.idx;
+
+ cell
+ .removeClass( classes.sSortAsc +" "+classes.sSortDesc )
+ .addClass( columns[ colIdx ] == 'asc' ?
+ classes.sSortAsc : columns[ colIdx ] == 'desc' ?
+ classes.sSortDesc :
+ column.sSortingClass
+ );
+
+ cell
+ .find( 'span.'+classes.sSortIcon )
+ .removeClass(
+ classes.sSortJUIAsc +" "+
+ classes.sSortJUIDesc +" "+
+ classes.sSortJUI +" "+
+ classes.sSortJUIAscAllowed +" "+
+ classes.sSortJUIDescAllowed
+ )
+ .addClass( columns[ colIdx ] == 'asc' ?
+ classes.sSortJUIAsc : columns[ colIdx ] == 'desc' ?
+ classes.sSortJUIDesc :
+ column.sSortingClassJUI
+ );
+ } );
+ }
+ }
+ } );
+
+ /*
+ * Public helper functions. These aren't used internally by DataTables, or
+ * called by any of the options passed into DataTables, but they can be used
+ * externally by developers working with DataTables. They are helper functions
+ * to make working with DataTables a little bit easier.
+ */
+
+ var __htmlEscapeEntities = function ( d ) {
+ return typeof d === 'string' ?
+ d.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;') :
+ d;
+ };
+
+ /**
+ * Helpers for `columns.render`.
+ *
+ * The options defined here can be used with the `columns.render` initialisation
+ * option to provide a display renderer. The following functions are defined:
+ *
+ * * `number` - Will format numeric data (defined by `columns.data`) for
+ * display, retaining the original unformatted data for sorting and filtering.
+ * It takes 5 parameters:
+ * * `string` - Thousands grouping separator
+ * * `string` - Decimal point indicator
+ * * `integer` - Number of decimal points to show
+ * * `string` (optional) - Prefix.
+ * * `string` (optional) - Postfix (/suffix).
+ * * `text` - Escape HTML to help prevent XSS attacks. It has no optional
+ * parameters.
+ *
+ * @example
+ * // Column definition using the number renderer
+ * {
+ * data: "salary",
+ * render: $.fn.dataTable.render.number( '\'', '.', 0, '$' )
+ * }
+ *
+ * @namespace
+ */
+ DataTable.render = {
+ number: function ( thousands, decimal, precision, prefix, postfix ) {
+ return {
+ display: function ( d ) {
+ if ( typeof d !== 'number' && typeof d !== 'string' ) {
+ return d;
+ }
+
+ var negative = d < 0 ? '-' : '';
+ var flo = parseFloat( d );
+
+ // If NaN then there isn't much formatting that we can do - just
+ // return immediately, escaping any HTML (this was supposed to
+ // be a number after all)
+ if ( isNaN( flo ) ) {
+ return __htmlEscapeEntities( d );
+ }
+
+ flo = flo.toFixed( precision );
+ d = Math.abs( flo );
+
+ var intPart = parseInt( d, 10 );
+ var floatPart = precision ?
+ decimal+(d - intPart).toFixed( precision ).substring( 2 ):
+ '';
+
+ return negative + (prefix||'') +
+ intPart.toString().replace(
+ /\B(?=(\d{3})+(?!\d))/g, thousands
+ ) +
+ floatPart +
+ (postfix||'');
+ }
+ };
+ },
+
+ text: function () {
+ return {
+ display: __htmlEscapeEntities
+ };
+ }
+ };
+
+
+ /*
+ * This is really a good bit rubbish this method of exposing the internal methods
+ * publicly... - To be fixed in 2.0 using methods on the prototype
+ */
+
+
+ /**
+ * Create a wrapper function for exporting an internal functions to an external API.
+ * @param {string} fn API function name
+ * @returns {function} wrapped function
+ * @memberof DataTable#internal
+ */
+ function _fnExternApiFunc (fn)
+ {
+ return function() {
+ var args = [_fnSettingsFromNode( this[DataTable.ext.iApiIndex] )].concat(
+ Array.prototype.slice.call(arguments)
+ );
+ return DataTable.ext.internal[fn].apply( this, args );
+ };
+ }
+
+
+ /**
+ * Reference to internal functions for use by plug-in developers. Note that
+ * these methods are references to internal functions and are considered to be
+ * private. If you use these methods, be aware that they are liable to change
+ * between versions.
+ * @namespace
+ */
+ $.extend( DataTable.ext.internal, {
+ _fnExternApiFunc: _fnExternApiFunc,
+ _fnBuildAjax: _fnBuildAjax,
+ _fnAjaxUpdate: _fnAjaxUpdate,
+ _fnAjaxParameters: _fnAjaxParameters,
+ _fnAjaxUpdateDraw: _fnAjaxUpdateDraw,
+ _fnAjaxDataSrc: _fnAjaxDataSrc,
+ _fnAddColumn: _fnAddColumn,
+ _fnColumnOptions: _fnColumnOptions,
+ _fnAdjustColumnSizing: _fnAdjustColumnSizing,
+ _fnVisibleToColumnIndex: _fnVisibleToColumnIndex,
+ _fnColumnIndexToVisible: _fnColumnIndexToVisible,
+ _fnVisbleColumns: _fnVisbleColumns,
+ _fnGetColumns: _fnGetColumns,
+ _fnColumnTypes: _fnColumnTypes,
+ _fnApplyColumnDefs: _fnApplyColumnDefs,
+ _fnHungarianMap: _fnHungarianMap,
+ _fnCamelToHungarian: _fnCamelToHungarian,
+ _fnLanguageCompat: _fnLanguageCompat,
+ _fnBrowserDetect: _fnBrowserDetect,
+ _fnAddData: _fnAddData,
+ _fnAddTr: _fnAddTr,
+ _fnNodeToDataIndex: _fnNodeToDataIndex,
+ _fnNodeToColumnIndex: _fnNodeToColumnIndex,
+ _fnGetCellData: _fnGetCellData,
+ _fnSetCellData: _fnSetCellData,
+ _fnSplitObjNotation: _fnSplitObjNotation,
+ _fnGetObjectDataFn: _fnGetObjectDataFn,
+ _fnSetObjectDataFn: _fnSetObjectDataFn,
+ _fnGetDataMaster: _fnGetDataMaster,
+ _fnClearTable: _fnClearTable,
+ _fnDeleteIndex: _fnDeleteIndex,
+ _fnInvalidate: _fnInvalidate,
+ _fnGetRowElements: _fnGetRowElements,
+ _fnCreateTr: _fnCreateTr,
+ _fnBuildHead: _fnBuildHead,
+ _fnDrawHead: _fnDrawHead,
+ _fnDraw: _fnDraw,
+ _fnReDraw: _fnReDraw,
+ _fnAddOptionsHtml: _fnAddOptionsHtml,
+ _fnDetectHeader: _fnDetectHeader,
+ _fnGetUniqueThs: _fnGetUniqueThs,
+ _fnFeatureHtmlFilter: _fnFeatureHtmlFilter,
+ _fnFilterComplete: _fnFilterComplete,
+ _fnFilterCustom: _fnFilterCustom,
+ _fnFilterColumn: _fnFilterColumn,
+ _fnFilter: _fnFilter,
+ _fnFilterCreateSearch: _fnFilterCreateSearch,
+ _fnEscapeRegex: _fnEscapeRegex,
+ _fnFilterData: _fnFilterData,
+ _fnFeatureHtmlInfo: _fnFeatureHtmlInfo,
+ _fnUpdateInfo: _fnUpdateInfo,
+ _fnInfoMacros: _fnInfoMacros,
+ _fnInitialise: _fnInitialise,
+ _fnInitComplete: _fnInitComplete,
+ _fnLengthChange: _fnLengthChange,
+ _fnFeatureHtmlLength: _fnFeatureHtmlLength,
+ _fnFeatureHtmlPaginate: _fnFeatureHtmlPaginate,
+ _fnPageChange: _fnPageChange,
+ _fnFeatureHtmlProcessing: _fnFeatureHtmlProcessing,
+ _fnProcessingDisplay: _fnProcessingDisplay,
+ _fnFeatureHtmlTable: _fnFeatureHtmlTable,
+ _fnScrollDraw: _fnScrollDraw,
+ _fnApplyToChildren: _fnApplyToChildren,
+ _fnCalculateColumnWidths: _fnCalculateColumnWidths,
+ _fnThrottle: _fnThrottle,
+ _fnConvertToWidth: _fnConvertToWidth,
+ _fnGetWidestNode: _fnGetWidestNode,
+ _fnGetMaxLenString: _fnGetMaxLenString,
+ _fnStringToCss: _fnStringToCss,
+ _fnSortFlatten: _fnSortFlatten,
+ _fnSort: _fnSort,
+ _fnSortAria: _fnSortAria,
+ _fnSortListener: _fnSortListener,
+ _fnSortAttachListener: _fnSortAttachListener,
+ _fnSortingClasses: _fnSortingClasses,
+ _fnSortData: _fnSortData,
+ _fnSaveState: _fnSaveState,
+ _fnLoadState: _fnLoadState,
+ _fnSettingsFromNode: _fnSettingsFromNode,
+ _fnLog: _fnLog,
+ _fnMap: _fnMap,
+ _fnBindAction: _fnBindAction,
+ _fnCallbackReg: _fnCallbackReg,
+ _fnCallbackFire: _fnCallbackFire,
+ _fnLengthOverflow: _fnLengthOverflow,
+ _fnRenderer: _fnRenderer,
+ _fnDataSource: _fnDataSource,
+ _fnRowAttributes: _fnRowAttributes,
+ _fnCalculateEnd: function () {} // Used by a lot of plug-ins, but redundant
+ // in 1.10, so this dead-end function is
+ // added to prevent errors
+ } );
+
+
+ // jQuery access
+ $.fn.dataTable = DataTable;
+
+ // Provide access to the host jQuery object (circular reference)
+ DataTable.$ = $;
+
+ // Legacy aliases
+ $.fn.dataTableSettings = DataTable.settings;
+ $.fn.dataTableExt = DataTable.ext;
+
+ // With a capital `D` we return a DataTables API instance rather than a
+ // jQuery object
+ $.fn.DataTable = function ( opts ) {
+ return $(this).dataTable( opts ).api();
+ };
+
+ // All properties that are available to $.fn.dataTable should also be
+ // available on $.fn.DataTable
+ $.each( DataTable, function ( prop, val ) {
+ $.fn.DataTable[ prop ] = val;
+ } );
+
+
+ // Information about events fired by DataTables - for documentation.
+ /**
+ * Draw event, fired whenever the table is redrawn on the page, at the same
+ * point as fnDrawCallback. This may be useful for binding events or
+ * performing calculations when the table is altered at all.
+ * @name DataTable#draw.dt
+ * @event
+ * @param {event} e jQuery event object
+ * @param {object} o DataTables settings object {@link DataTable.models.oSettings}
+ */
+
+ /**
+ * Search event, fired when the searching applied to the table (using the
+ * built-in global search, or column filters) is altered.
+ * @name DataTable#search.dt
+ * @event
+ * @param {event} e jQuery event object
+ * @param {object} o DataTables settings object {@link DataTable.models.oSettings}
+ */
+
+ /**
+ * Page change event, fired when the paging of the table is altered.
+ * @name DataTable#page.dt
+ * @event
+ * @param {event} e jQuery event object
+ * @param {object} o DataTables settings object {@link DataTable.models.oSettings}
+ */
+
+ /**
+ * Order event, fired when the ordering applied to the table is altered.
+ * @name DataTable#order.dt
+ * @event
+ * @param {event} e jQuery event object
+ * @param {object} o DataTables settings object {@link DataTable.models.oSettings}
+ */
+
+ /**
+ * DataTables initialisation complete event, fired when the table is fully
+ * drawn, including Ajax data loaded, if Ajax data is required.
+ * @name DataTable#init.dt
+ * @event
+ * @param {event} e jQuery event object
+ * @param {object} oSettings DataTables settings object
+ * @param {object} json The JSON object request from the server - only
+ * present if client-side Ajax sourced data is used</li></ol>
+ */
+
+ /**
+ * State save event, fired when the table has changed state a new state save
+ * is required. This event allows modification of the state saving object
+ * prior to actually doing the save, including addition or other state
+ * properties (for plug-ins) or modification of a DataTables core property.
+ * @name DataTable#stateSaveParams.dt
+ * @event
+ * @param {event} e jQuery event object
+ * @param {object} oSettings DataTables settings object
+ * @param {object} json The state information to be saved
+ */
+
+ /**
+ * State load event, fired when the table is loading state from the stored
+ * data, but prior to the settings object being modified by the saved state
+ * - allowing modification of the saved state is required or loading of
+ * state for a plug-in.
+ * @name DataTable#stateLoadParams.dt
+ * @event
+ * @param {event} e jQuery event object
+ * @param {object} oSettings DataTables settings object
+ * @param {object} json The saved state information
+ */
+
+ /**
+ * State loaded event, fired when state has been loaded from stored data and
+ * the settings object has been modified by the loaded data.
+ * @name DataTable#stateLoaded.dt
+ * @event
+ * @param {event} e jQuery event object
+ * @param {object} oSettings DataTables settings object
+ * @param {object} json The saved state information
+ */
+
+ /**
+ * Processing event, fired when DataTables is doing some kind of processing
+ * (be it, order, searcg or anything else). It can be used to indicate to
+ * the end user that there is something happening, or that something has
+ * finished.
+ * @name DataTable#processing.dt
+ * @event
+ * @param {event} e jQuery event object
+ * @param {object} oSettings DataTables settings object
+ * @param {boolean} bShow Flag for if DataTables is doing processing or not
+ */
+
+ /**
+ * Ajax (XHR) event, fired whenever an Ajax request is completed from a
+ * request to made to the server for new data. This event is called before
+ * DataTables processed the returned data, so it can also be used to pre-
+ * process the data returned from the server, if needed.
+ *
+ * Note that this trigger is called in `fnServerData`, if you override
+ * `fnServerData` and which to use this event, you need to trigger it in you
+ * success function.
+ * @name DataTable#xhr.dt
+ * @event
+ * @param {event} e jQuery event object
+ * @param {object} o DataTables settings object {@link DataTable.models.oSettings}
+ * @param {object} json JSON returned from the server
+ *
+ * @example
+ * // Use a custom property returned from the server in another DOM element
+ * $('#table').dataTable().on('xhr.dt', function (e, settings, json) {
+ * $('#status').html( json.status );
+ * } );
+ *
+ * @example
+ * // Pre-process the data returned from the server
+ * $('#table').dataTable().on('xhr.dt', function (e, settings, json) {
+ * for ( var i=0, ien=json.aaData.length ; i<ien ; i++ ) {
+ * json.aaData[i].sum = json.aaData[i].one + json.aaData[i].two;
+ * }
+ * // Note no return - manipulate the data directly in the JSON object.
+ * } );
+ */
+
+ /**
+ * Destroy event, fired when the DataTable is destroyed by calling fnDestroy
+ * or passing the bDestroy:true parameter in the initialisation object. This
+ * can be used to remove bound events, added DOM nodes, etc.
+ * @name DataTable#destroy.dt
+ * @event
+ * @param {event} e jQuery event object
+ * @param {object} o DataTables settings object {@link DataTable.models.oSettings}
+ */
+
+ /**
+ * Page length change event, fired when number of records to show on each
+ * page (the length) is changed.
+ * @name DataTable#length.dt
+ * @event
+ * @param {event} e jQuery event object
+ * @param {object} o DataTables settings object {@link DataTable.models.oSettings}
+ * @param {integer} len New length
+ */
+
+ /**
+ * Column sizing has changed.
+ * @name DataTable#column-sizing.dt
+ * @event
+ * @param {event} e jQuery event object
+ * @param {object} o DataTables settings object {@link DataTable.models.oSettings}
+ */
+
+ /**
+ * Column visibility has changed.
+ * @name DataTable#column-visibility.dt
+ * @event
+ * @param {event} e jQuery event object
+ * @param {object} o DataTables settings object {@link DataTable.models.oSettings}
+ * @param {int} column Column index
+ * @param {bool} vis `false` if column now hidden, or `true` if visible
+ */
+
+ return $.fn.dataTable;
+}));
diff --git a/www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/jquery.dataTables.min.js b/www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/jquery.dataTables.min.js
new file mode 100644
index 00000000..dc969eef
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/jquery.dataTables/jquery.dataTables.min.js
@@ -0,0 +1,167 @@
+/*!
+ DataTables 1.10.15
+ ©2008-2017 SpryMedia Ltd - datatables.net/license
+*/
+(function(h){"function"===typeof define&&define.amd?define(["jquery"],function(E){return h(E,window,document)}):"object"===typeof exports?module.exports=function(E,H){E||(E=window);H||(H="undefined"!==typeof window?require("jquery"):require("jquery")(E));return h(H,E,E.document)}:h(jQuery,window,document)})(function(h,E,H,k){function Y(a){var b,c,d={};h.each(a,function(e){if((b=e.match(/^([^A-Z]+?)([A-Z])/))&&-1!=="a aa ai ao as b fn i m o s ".indexOf(b[1]+" "))c=e.replace(b[0],b[2].toLowerCase()),
+d[c]=e,"o"===b[1]&&Y(a[e])});a._hungarianMap=d}function J(a,b,c){a._hungarianMap||Y(a);var d;h.each(b,function(e){d=a._hungarianMap[e];if(d!==k&&(c||b[d]===k))"o"===d.charAt(0)?(b[d]||(b[d]={}),h.extend(!0,b[d],b[e]),J(a[d],b[d],c)):b[d]=b[e]})}function Fa(a){var b=m.defaults.oLanguage,c=a.sZeroRecords;!a.sEmptyTable&&(c&&"No data available in table"===b.sEmptyTable)&&F(a,a,"sZeroRecords","sEmptyTable");!a.sLoadingRecords&&(c&&"Loading..."===b.sLoadingRecords)&&F(a,a,"sZeroRecords","sLoadingRecords");
+a.sInfoThousands&&(a.sThousands=a.sInfoThousands);(a=a.sDecimal)&&fb(a)}function gb(a){A(a,"ordering","bSort");A(a,"orderMulti","bSortMulti");A(a,"orderClasses","bSortClasses");A(a,"orderCellsTop","bSortCellsTop");A(a,"order","aaSorting");A(a,"orderFixed","aaSortingFixed");A(a,"paging","bPaginate");A(a,"pagingType","sPaginationType");A(a,"pageLength","iDisplayLength");A(a,"searching","bFilter");"boolean"===typeof a.sScrollX&&(a.sScrollX=a.sScrollX?"100%":"");"boolean"===typeof a.scrollX&&(a.scrollX=
+a.scrollX?"100%":"");if(a=a.aoSearchCols)for(var b=0,c=a.length;b<c;b++)a[b]&&J(m.models.oSearch,a[b])}function hb(a){A(a,"orderable","bSortable");A(a,"orderData","aDataSort");A(a,"orderSequence","asSorting");A(a,"orderDataType","sortDataType");var b=a.aDataSort;"number"===typeof b&&!h.isArray(b)&&(a.aDataSort=[b])}function ib(a){if(!m.__browser){var b={};m.__browser=b;var c=h("<div/>").css({position:"fixed",top:0,left:-1*h(E).scrollLeft(),height:1,width:1,overflow:"hidden"}).append(h("<div/>").css({position:"absolute",
+top:1,left:1,width:100,overflow:"scroll"}).append(h("<div/>").css({width:"100%",height:10}))).appendTo("body"),d=c.children(),e=d.children();b.barWidth=d[0].offsetWidth-d[0].clientWidth;b.bScrollOversize=100===e[0].offsetWidth&&100!==d[0].clientWidth;b.bScrollbarLeft=1!==Math.round(e.offset().left);b.bBounding=c[0].getBoundingClientRect().width?!0:!1;c.remove()}h.extend(a.oBrowser,m.__browser);a.oScroll.iBarWidth=m.__browser.barWidth}function jb(a,b,c,d,e,f){var g,j=!1;c!==k&&(g=c,j=!0);for(;d!==
+e;)a.hasOwnProperty(d)&&(g=j?b(g,a[d],d,a):a[d],j=!0,d+=f);return g}function Ga(a,b){var c=m.defaults.column,d=a.aoColumns.length,c=h.extend({},m.models.oColumn,c,{nTh:b?b:H.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.mData:d,idx:d});a.aoColumns.push(c);c=a.aoPreSearchCols;c[d]=h.extend({},m.models.oSearch,c[d]);la(a,d,h(b).data())}function la(a,b,c){var b=a.aoColumns[b],d=a.oClasses,e=h(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=
+e.attr("width")||null;var f=(e.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);f&&(b.sWidthOrig=f[1])}c!==k&&null!==c&&(hb(c),J(m.defaults.column,c),c.mDataProp!==k&&!c.mData&&(c.mData=c.mDataProp),c.sType&&(b._sManualType=c.sType),c.className&&!c.sClass&&(c.sClass=c.className),h.extend(b,c),F(b,c,"sWidth","sWidthOrig"),c.iDataSort!==k&&(b.aDataSort=[c.iDataSort]),F(b,c,"aDataSort"));var g=b.mData,j=R(g),i=b.mRender?R(b.mRender):null,c=function(a){return"string"===typeof a&&-1!==a.indexOf("@")};
+b._bAttrSrc=h.isPlainObject(g)&&(c(g.sort)||c(g.type)||c(g.filter));b._setter=null;b.fnGetData=function(a,b,c){var d=j(a,b,k,c);return i&&b?i(d,b,a,c):d};b.fnSetData=function(a,b,c){return S(g)(a,b,c)};"number"!==typeof g&&(a._rowReadObject=!0);a.oFeatures.bSort||(b.bSortable=!1,e.addClass(d.sSortableNone));a=-1!==h.inArray("asc",b.asSorting);c=-1!==h.inArray("desc",b.asSorting);!b.bSortable||!a&&!c?(b.sSortingClass=d.sSortableNone,b.sSortingClassJUI=""):a&&!c?(b.sSortingClass=d.sSortableAsc,b.sSortingClassJUI=
+d.sSortJUIAscAllowed):!a&&c?(b.sSortingClass=d.sSortableDesc,b.sSortingClassJUI=d.sSortJUIDescAllowed):(b.sSortingClass=d.sSortable,b.sSortingClassJUI=d.sSortJUI)}function Z(a){if(!1!==a.oFeatures.bAutoWidth){var b=a.aoColumns;Ha(a);for(var c=0,d=b.length;c<d;c++)b[c].nTh.style.width=b[c].sWidth}b=a.oScroll;(""!==b.sY||""!==b.sX)&&ma(a);s(a,null,"column-sizing",[a])}function $(a,b){var c=na(a,"bVisible");return"number"===typeof c[b]?c[b]:null}function aa(a,b){var c=na(a,"bVisible"),c=h.inArray(b,
+c);return-1!==c?c:null}function ba(a){var b=0;h.each(a.aoColumns,function(a,d){d.bVisible&&"none"!==h(d.nTh).css("display")&&b++});return b}function na(a,b){var c=[];h.map(a.aoColumns,function(a,e){a[b]&&c.push(e)});return c}function Ia(a){var b=a.aoColumns,c=a.aoData,d=m.ext.type.detect,e,f,g,j,i,h,l,q,r;e=0;for(f=b.length;e<f;e++)if(l=b[e],r=[],!l.sType&&l._sManualType)l.sType=l._sManualType;else if(!l.sType){g=0;for(j=d.length;g<j;g++){i=0;for(h=c.length;i<h;i++){r[i]===k&&(r[i]=B(a,i,e,"type"));
+q=d[g](r[i],a);if(!q&&g!==d.length-1)break;if("html"===q)break}if(q){l.sType=q;break}}l.sType||(l.sType="string")}}function kb(a,b,c,d){var e,f,g,j,i,n,l=a.aoColumns;if(b)for(e=b.length-1;0<=e;e--){n=b[e];var q=n.targets!==k?n.targets:n.aTargets;h.isArray(q)||(q=[q]);f=0;for(g=q.length;f<g;f++)if("number"===typeof q[f]&&0<=q[f]){for(;l.length<=q[f];)Ga(a);d(q[f],n)}else if("number"===typeof q[f]&&0>q[f])d(l.length+q[f],n);else if("string"===typeof q[f]){j=0;for(i=l.length;j<i;j++)("_all"==q[f]||h(l[j].nTh).hasClass(q[f]))&&
+d(j,n)}}if(c){e=0;for(a=c.length;e<a;e++)d(e,c[e])}}function N(a,b,c,d){var e=a.aoData.length,f=h.extend(!0,{},m.models.oRow,{src:c?"dom":"data",idx:e});f._aData=b;a.aoData.push(f);for(var g=a.aoColumns,j=0,i=g.length;j<i;j++)g[j].sType=null;a.aiDisplayMaster.push(e);b=a.rowIdFn(b);b!==k&&(a.aIds[b]=f);(c||!a.oFeatures.bDeferRender)&&Ja(a,e,c,d);return e}function oa(a,b){var c;b instanceof h||(b=h(b));return b.map(function(b,e){c=Ka(a,e);return N(a,c.data,e,c.cells)})}function B(a,b,c,d){var e=a.iDraw,
+f=a.aoColumns[c],g=a.aoData[b]._aData,j=f.sDefaultContent,i=f.fnGetData(g,d,{settings:a,row:b,col:c});if(i===k)return a.iDrawError!=e&&null===j&&(K(a,0,"Requested unknown parameter "+("function"==typeof f.mData?"{function}":"'"+f.mData+"'")+" for row "+b+", column "+c,4),a.iDrawError=e),j;if((i===g||null===i)&&null!==j&&d!==k)i=j;else if("function"===typeof i)return i.call(g);return null===i&&"display"==d?"":i}function lb(a,b,c,d){a.aoColumns[c].fnSetData(a.aoData[b]._aData,d,{settings:a,row:b,col:c})}
+function La(a){return h.map(a.match(/(\\.|[^\.])+/g)||[""],function(a){return a.replace(/\\\./g,".")})}function R(a){if(h.isPlainObject(a)){var b={};h.each(a,function(a,c){c&&(b[a]=R(c))});return function(a,c,f,g){var j=b[c]||b._;return j!==k?j(a,c,f,g):a}}if(null===a)return function(a){return a};if("function"===typeof a)return function(b,c,f,g){return a(b,c,f,g)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var c=function(a,b,f){var g,j;if(""!==f){j=La(f);
+for(var i=0,n=j.length;i<n;i++){f=j[i].match(ca);g=j[i].match(V);if(f){j[i]=j[i].replace(ca,"");""!==j[i]&&(a=a[j[i]]);g=[];j.splice(0,i+1);j=j.join(".");if(h.isArray(a)){i=0;for(n=a.length;i<n;i++)g.push(c(a[i],b,j))}a=f[0].substring(1,f[0].length-1);a=""===a?g:g.join(a);break}else if(g){j[i]=j[i].replace(V,"");a=a[j[i]]();continue}if(null===a||a[j[i]]===k)return k;a=a[j[i]]}}return a};return function(b,e){return c(b,e,a)}}return function(b){return b[a]}}function S(a){if(h.isPlainObject(a))return S(a._);
+if(null===a)return function(){};if("function"===typeof a)return function(b,d,e){a(b,"set",d,e)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var b=function(a,d,e){var e=La(e),f;f=e[e.length-1];for(var g,j,i=0,n=e.length-1;i<n;i++){g=e[i].match(ca);j=e[i].match(V);if(g){e[i]=e[i].replace(ca,"");a[e[i]]=[];f=e.slice();f.splice(0,i+1);g=f.join(".");if(h.isArray(d)){j=0;for(n=d.length;j<n;j++)f={},b(f,d[j],g),a[e[i]].push(f)}else a[e[i]]=d;return}j&&(e[i]=e[i].replace(V,
+""),a=a[e[i]](d));if(null===a[e[i]]||a[e[i]]===k)a[e[i]]={};a=a[e[i]]}if(f.match(V))a[f.replace(V,"")](d);else a[f.replace(ca,"")]=d};return function(c,d){return b(c,d,a)}}return function(b,d){b[a]=d}}function Ma(a){return D(a.aoData,"_aData")}function pa(a){a.aoData.length=0;a.aiDisplayMaster.length=0;a.aiDisplay.length=0;a.aIds={}}function qa(a,b,c){for(var d=-1,e=0,f=a.length;e<f;e++)a[e]==b?d=e:a[e]>b&&a[e]--; -1!=d&&c===k&&a.splice(d,1)}function da(a,b,c,d){var e=a.aoData[b],f,g=function(c,d){for(;c.childNodes.length;)c.removeChild(c.firstChild);
+c.innerHTML=B(a,b,d,"display")};if("dom"===c||(!c||"auto"===c)&&"dom"===e.src)e._aData=Ka(a,e,d,d===k?k:e._aData).data;else{var j=e.anCells;if(j)if(d!==k)g(j[d],d);else{c=0;for(f=j.length;c<f;c++)g(j[c],c)}}e._aSortData=null;e._aFilterData=null;g=a.aoColumns;if(d!==k)g[d].sType=null;else{c=0;for(f=g.length;c<f;c++)g[c].sType=null;Na(a,e)}}function Ka(a,b,c,d){var e=[],f=b.firstChild,g,j,i=0,n,l=a.aoColumns,q=a._rowReadObject,d=d!==k?d:q?{}:[],r=function(a,b){if("string"===typeof a){var c=a.indexOf("@");
+-1!==c&&(c=a.substring(c+1),S(a)(d,b.getAttribute(c)))}},m=function(a){if(c===k||c===i)j=l[i],n=h.trim(a.innerHTML),j&&j._bAttrSrc?(S(j.mData._)(d,n),r(j.mData.sort,a),r(j.mData.type,a),r(j.mData.filter,a)):q?(j._setter||(j._setter=S(j.mData)),j._setter(d,n)):d[i]=n;i++};if(f)for(;f;){g=f.nodeName.toUpperCase();if("TD"==g||"TH"==g)m(f),e.push(f);f=f.nextSibling}else{e=b.anCells;f=0;for(g=e.length;f<g;f++)m(e[f])}if(b=b.firstChild?b:b.nTr)(b=b.getAttribute("id"))&&S(a.rowId)(d,b);return{data:d,cells:e}}
+function Ja(a,b,c,d){var e=a.aoData[b],f=e._aData,g=[],j,i,n,l,q;if(null===e.nTr){j=c||H.createElement("tr");e.nTr=j;e.anCells=g;j._DT_RowIndex=b;Na(a,e);l=0;for(q=a.aoColumns.length;l<q;l++){n=a.aoColumns[l];i=c?d[l]:H.createElement(n.sCellType);i._DT_CellIndex={row:b,column:l};g.push(i);if((!c||n.mRender||n.mData!==l)&&(!h.isPlainObject(n.mData)||n.mData._!==l+".display"))i.innerHTML=B(a,b,l,"display");n.sClass&&(i.className+=" "+n.sClass);n.bVisible&&!c?j.appendChild(i):!n.bVisible&&c&&i.parentNode.removeChild(i);
+n.fnCreatedCell&&n.fnCreatedCell.call(a.oInstance,i,B(a,b,l),f,b,l)}s(a,"aoRowCreatedCallback",null,[j,f,b])}e.nTr.setAttribute("role","row")}function Na(a,b){var c=b.nTr,d=b._aData;if(c){var e=a.rowIdFn(d);e&&(c.id=e);d.DT_RowClass&&(e=d.DT_RowClass.split(" "),b.__rowc=b.__rowc?sa(b.__rowc.concat(e)):e,h(c).removeClass(b.__rowc.join(" ")).addClass(d.DT_RowClass));d.DT_RowAttr&&h(c).attr(d.DT_RowAttr);d.DT_RowData&&h(c).data(d.DT_RowData)}}function mb(a){var b,c,d,e,f,g=a.nTHead,j=a.nTFoot,i=0===
+h("th, td",g).length,n=a.oClasses,l=a.aoColumns;i&&(e=h("<tr/>").appendTo(g));b=0;for(c=l.length;b<c;b++)f=l[b],d=h(f.nTh).addClass(f.sClass),i&&d.appendTo(e),a.oFeatures.bSort&&(d.addClass(f.sSortingClass),!1!==f.bSortable&&(d.attr("tabindex",a.iTabIndex).attr("aria-controls",a.sTableId),Oa(a,f.nTh,b))),f.sTitle!=d[0].innerHTML&&d.html(f.sTitle),Pa(a,"header")(a,d,f,n);i&&ea(a.aoHeader,g);h(g).find(">tr").attr("role","row");h(g).find(">tr>th, >tr>td").addClass(n.sHeaderTH);h(j).find(">tr>th, >tr>td").addClass(n.sFooterTH);
+if(null!==j){a=a.aoFooter[0];b=0;for(c=a.length;b<c;b++)f=l[b],f.nTf=a[b].cell,f.sClass&&h(f.nTf).addClass(f.sClass)}}function fa(a,b,c){var d,e,f,g=[],j=[],i=a.aoColumns.length,n;if(b){c===k&&(c=!1);d=0;for(e=b.length;d<e;d++){g[d]=b[d].slice();g[d].nTr=b[d].nTr;for(f=i-1;0<=f;f--)!a.aoColumns[f].bVisible&&!c&&g[d].splice(f,1);j.push([])}d=0;for(e=g.length;d<e;d++){if(a=g[d].nTr)for(;f=a.firstChild;)a.removeChild(f);f=0;for(b=g[d].length;f<b;f++)if(n=i=1,j[d][f]===k){a.appendChild(g[d][f].cell);
+for(j[d][f]=1;g[d+i]!==k&&g[d][f].cell==g[d+i][f].cell;)j[d+i][f]=1,i++;for(;g[d][f+n]!==k&&g[d][f].cell==g[d][f+n].cell;){for(c=0;c<i;c++)j[d+c][f+n]=1;n++}h(g[d][f].cell).attr("rowspan",i).attr("colspan",n)}}}}function O(a){var b=s(a,"aoPreDrawCallback","preDraw",[a]);if(-1!==h.inArray(!1,b))C(a,!1);else{var b=[],c=0,d=a.asStripeClasses,e=d.length,f=a.oLanguage,g=a.iInitDisplayStart,j="ssp"==y(a),i=a.aiDisplay;a.bDrawing=!0;g!==k&&-1!==g&&(a._iDisplayStart=j?g:g>=a.fnRecordsDisplay()?0:g,a.iInitDisplayStart=
+-1);var g=a._iDisplayStart,n=a.fnDisplayEnd();if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,C(a,!1);else if(j){if(!a.bDestroying&&!nb(a))return}else a.iDraw++;if(0!==i.length){f=j?a.aoData.length:n;for(j=j?0:g;j<f;j++){var l=i[j],q=a.aoData[l];null===q.nTr&&Ja(a,l);l=q.nTr;if(0!==e){var r=d[c%e];q._sRowStripe!=r&&(h(l).removeClass(q._sRowStripe).addClass(r),q._sRowStripe=r)}s(a,"aoRowCallback",null,[l,q._aData,c,j]);b.push(l);c++}}else c=f.sZeroRecords,1==a.iDraw&&"ajax"==y(a)?c=f.sLoadingRecords:
+f.sEmptyTable&&0===a.fnRecordsTotal()&&(c=f.sEmptyTable),b[0]=h("<tr/>",{"class":e?d[0]:""}).append(h("<td />",{valign:"top",colSpan:ba(a),"class":a.oClasses.sRowEmpty}).html(c))[0];s(a,"aoHeaderCallback","header",[h(a.nTHead).children("tr")[0],Ma(a),g,n,i]);s(a,"aoFooterCallback","footer",[h(a.nTFoot).children("tr")[0],Ma(a),g,n,i]);d=h(a.nTBody);d.children().detach();d.append(h(b));s(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1}}function T(a,b){var c=a.oFeatures,d=c.bFilter;
+c.bSort&&ob(a);d?ga(a,a.oPreviousSearch):a.aiDisplay=a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;O(a);a._drawHold=!1}function pb(a){var b=a.oClasses,c=h(a.nTable),c=h("<div/>").insertBefore(c),d=a.oFeatures,e=h("<div/>",{id:a.sTableId+"_wrapper","class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=e[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var f=a.sDom.split(""),g,j,i,n,l,q,k=0;k<f.length;k++){g=null;j=f[k];if("<"==j){i=h("<div/>")[0];
+n=f[k+1];if("'"==n||'"'==n){l="";for(q=2;f[k+q]!=n;)l+=f[k+q],q++;"H"==l?l=b.sJUIHeader:"F"==l&&(l=b.sJUIFooter);-1!=l.indexOf(".")?(n=l.split("."),i.id=n[0].substr(1,n[0].length-1),i.className=n[1]):"#"==l.charAt(0)?i.id=l.substr(1,l.length-1):i.className=l;k+=q}e.append(i);e=h(i)}else if(">"==j)e=e.parent();else if("l"==j&&d.bPaginate&&d.bLengthChange)g=qb(a);else if("f"==j&&d.bFilter)g=rb(a);else if("r"==j&&d.bProcessing)g=sb(a);else if("t"==j)g=tb(a);else if("i"==j&&d.bInfo)g=ub(a);else if("p"==
+j&&d.bPaginate)g=vb(a);else if(0!==m.ext.feature.length){i=m.ext.feature;q=0;for(n=i.length;q<n;q++)if(j==i[q].cFeature){g=i[q].fnInit(a);break}}g&&(i=a.aanFeatures,i[j]||(i[j]=[]),i[j].push(g),e.append(g))}c.replaceWith(e);a.nHolding=null}function ea(a,b){var c=h(b).children("tr"),d,e,f,g,j,i,n,l,q,k;a.splice(0,a.length);f=0;for(i=c.length;f<i;f++)a.push([]);f=0;for(i=c.length;f<i;f++){d=c[f];for(e=d.firstChild;e;){if("TD"==e.nodeName.toUpperCase()||"TH"==e.nodeName.toUpperCase()){l=1*e.getAttribute("colspan");
+q=1*e.getAttribute("rowspan");l=!l||0===l||1===l?1:l;q=!q||0===q||1===q?1:q;g=0;for(j=a[f];j[g];)g++;n=g;k=1===l?!0:!1;for(j=0;j<l;j++)for(g=0;g<q;g++)a[f+g][n+j]={cell:e,unique:k},a[f+g].nTr=d}e=e.nextSibling}}}function ta(a,b,c){var d=[];c||(c=a.aoHeader,b&&(c=[],ea(c,b)));for(var b=0,e=c.length;b<e;b++)for(var f=0,g=c[b].length;f<g;f++)if(c[b][f].unique&&(!d[f]||!a.bSortCellsTop))d[f]=c[b][f].cell;return d}function ua(a,b,c){s(a,"aoServerParams","serverParams",[b]);if(b&&h.isArray(b)){var d={},
+e=/(.*?)\[\]$/;h.each(b,function(a,b){var c=b.name.match(e);c?(c=c[0],d[c]||(d[c]=[]),d[c].push(b.value)):d[b.name]=b.value});b=d}var f,g=a.ajax,j=a.oInstance,i=function(b){s(a,null,"xhr",[a,b,a.jqXHR]);c(b)};if(h.isPlainObject(g)&&g.data){f=g.data;var n=h.isFunction(f)?f(b,a):f,b=h.isFunction(f)&&n?n:h.extend(!0,b,n);delete g.data}n={data:b,success:function(b){var c=b.error||b.sError;c&&K(a,0,c);a.json=b;i(b)},dataType:"json",cache:!1,type:a.sServerMethod,error:function(b,c){var d=s(a,null,"xhr",
+[a,null,a.jqXHR]);-1===h.inArray(!0,d)&&("parsererror"==c?K(a,0,"Invalid JSON response",1):4===b.readyState&&K(a,0,"Ajax error",7));C(a,!1)}};a.oAjaxData=b;s(a,null,"preXhr",[a,b]);a.fnServerData?a.fnServerData.call(j,a.sAjaxSource,h.map(b,function(a,b){return{name:b,value:a}}),i,a):a.sAjaxSource||"string"===typeof g?a.jqXHR=h.ajax(h.extend(n,{url:g||a.sAjaxSource})):h.isFunction(g)?a.jqXHR=g.call(j,b,i,a):(a.jqXHR=h.ajax(h.extend(n,g)),g.data=f)}function nb(a){return a.bAjaxDataGet?(a.iDraw++,C(a,
+!0),ua(a,wb(a),function(b){xb(a,b)}),!1):!0}function wb(a){var b=a.aoColumns,c=b.length,d=a.oFeatures,e=a.oPreviousSearch,f=a.aoPreSearchCols,g,j=[],i,n,l,k=W(a);g=a._iDisplayStart;i=!1!==d.bPaginate?a._iDisplayLength:-1;var r=function(a,b){j.push({name:a,value:b})};r("sEcho",a.iDraw);r("iColumns",c);r("sColumns",D(b,"sName").join(","));r("iDisplayStart",g);r("iDisplayLength",i);var ra={draw:a.iDraw,columns:[],order:[],start:g,length:i,search:{value:e.sSearch,regex:e.bRegex}};for(g=0;g<c;g++)n=b[g],
+l=f[g],i="function"==typeof n.mData?"function":n.mData,ra.columns.push({data:i,name:n.sName,searchable:n.bSearchable,orderable:n.bSortable,search:{value:l.sSearch,regex:l.bRegex}}),r("mDataProp_"+g,i),d.bFilter&&(r("sSearch_"+g,l.sSearch),r("bRegex_"+g,l.bRegex),r("bSearchable_"+g,n.bSearchable)),d.bSort&&r("bSortable_"+g,n.bSortable);d.bFilter&&(r("sSearch",e.sSearch),r("bRegex",e.bRegex));d.bSort&&(h.each(k,function(a,b){ra.order.push({column:b.col,dir:b.dir});r("iSortCol_"+a,b.col);r("sSortDir_"+
+a,b.dir)}),r("iSortingCols",k.length));b=m.ext.legacy.ajax;return null===b?a.sAjaxSource?j:ra:b?j:ra}function xb(a,b){var c=va(a,b),d=b.sEcho!==k?b.sEcho:b.draw,e=b.iTotalRecords!==k?b.iTotalRecords:b.recordsTotal,f=b.iTotalDisplayRecords!==k?b.iTotalDisplayRecords:b.recordsFiltered;if(d){if(1*d<a.iDraw)return;a.iDraw=1*d}pa(a);a._iRecordsTotal=parseInt(e,10);a._iRecordsDisplay=parseInt(f,10);d=0;for(e=c.length;d<e;d++)N(a,c[d]);a.aiDisplay=a.aiDisplayMaster.slice();a.bAjaxDataGet=!1;O(a);a._bInitComplete||
+wa(a,b);a.bAjaxDataGet=!0;C(a,!1)}function va(a,b){var c=h.isPlainObject(a.ajax)&&a.ajax.dataSrc!==k?a.ajax.dataSrc:a.sAjaxDataProp;return"data"===c?b.aaData||b[c]:""!==c?R(c)(b):b}function rb(a){var b=a.oClasses,c=a.sTableId,d=a.oLanguage,e=a.oPreviousSearch,f=a.aanFeatures,g='<input type="search" class="'+b.sFilterInput+'"/>',j=d.sSearch,j=j.match(/_INPUT_/)?j.replace("_INPUT_",g):j+g,b=h("<div/>",{id:!f.f?c+"_filter":null,"class":b.sFilter}).append(h("<label/>").append(j)),f=function(){var b=!this.value?
+"":this.value;b!=e.sSearch&&(ga(a,{sSearch:b,bRegex:e.bRegex,bSmart:e.bSmart,bCaseInsensitive:e.bCaseInsensitive}),a._iDisplayStart=0,O(a))},g=null!==a.searchDelay?a.searchDelay:"ssp"===y(a)?400:0,i=h("input",b).val(e.sSearch).attr("placeholder",d.sSearchPlaceholder).on("keyup.DT search.DT input.DT paste.DT cut.DT",g?Qa(f,g):f).on("keypress.DT",function(a){if(13==a.keyCode)return!1}).attr("aria-controls",c);h(a.nTable).on("search.dt.DT",function(b,c){if(a===c)try{i[0]!==H.activeElement&&i.val(e.sSearch)}catch(d){}});
+return b[0]}function ga(a,b,c){var d=a.oPreviousSearch,e=a.aoPreSearchCols,f=function(a){d.sSearch=a.sSearch;d.bRegex=a.bRegex;d.bSmart=a.bSmart;d.bCaseInsensitive=a.bCaseInsensitive};Ia(a);if("ssp"!=y(a)){yb(a,b.sSearch,c,b.bEscapeRegex!==k?!b.bEscapeRegex:b.bRegex,b.bSmart,b.bCaseInsensitive);f(b);for(b=0;b<e.length;b++)zb(a,e[b].sSearch,b,e[b].bEscapeRegex!==k?!e[b].bEscapeRegex:e[b].bRegex,e[b].bSmart,e[b].bCaseInsensitive);Ab(a)}else f(b);a.bFiltered=!0;s(a,null,"search",[a])}function Ab(a){for(var b=
+m.ext.search,c=a.aiDisplay,d,e,f=0,g=b.length;f<g;f++){for(var j=[],i=0,n=c.length;i<n;i++)e=c[i],d=a.aoData[e],b[f](a,d._aFilterData,e,d._aData,i)&&j.push(e);c.length=0;h.merge(c,j)}}function zb(a,b,c,d,e,f){if(""!==b){for(var g=[],j=a.aiDisplay,d=Ra(b,d,e,f),e=0;e<j.length;e++)b=a.aoData[j[e]]._aFilterData[c],d.test(b)&&g.push(j[e]);a.aiDisplay=g}}function yb(a,b,c,d,e,f){var d=Ra(b,d,e,f),f=a.oPreviousSearch.sSearch,g=a.aiDisplayMaster,j,e=[];0!==m.ext.search.length&&(c=!0);j=Bb(a);if(0>=b.length)a.aiDisplay=
+g.slice();else{if(j||c||f.length>b.length||0!==b.indexOf(f)||a.bSorted)a.aiDisplay=g.slice();b=a.aiDisplay;for(c=0;c<b.length;c++)d.test(a.aoData[b[c]]._sFilterRow)&&e.push(b[c]);a.aiDisplay=e}}function Ra(a,b,c,d){a=b?a:Sa(a);c&&(a="^(?=.*?"+h.map(a.match(/"[^"]+"|[^ ]+/g)||[""],function(a){if('"'===a.charAt(0))var b=a.match(/^"(.*)"$/),a=b?b[1]:a;return a.replace('"',"")}).join(")(?=.*?")+").*$");return RegExp(a,d?"i":"")}function Bb(a){var b=a.aoColumns,c,d,e,f,g,j,i,h,l=m.ext.type.search;c=!1;
+d=0;for(f=a.aoData.length;d<f;d++)if(h=a.aoData[d],!h._aFilterData){j=[];e=0;for(g=b.length;e<g;e++)c=b[e],c.bSearchable?(i=B(a,d,e,"filter"),l[c.sType]&&(i=l[c.sType](i)),null===i&&(i=""),"string"!==typeof i&&i.toString&&(i=i.toString())):i="",i.indexOf&&-1!==i.indexOf("&")&&(xa.innerHTML=i,i=$b?xa.textContent:xa.innerText),i.replace&&(i=i.replace(/[\r\n]/g,"")),j.push(i);h._aFilterData=j;h._sFilterRow=j.join(" ");c=!0}return c}function Cb(a){return{search:a.sSearch,smart:a.bSmart,regex:a.bRegex,
+caseInsensitive:a.bCaseInsensitive}}function Db(a){return{sSearch:a.search,bSmart:a.smart,bRegex:a.regex,bCaseInsensitive:a.caseInsensitive}}function ub(a){var b=a.sTableId,c=a.aanFeatures.i,d=h("<div/>",{"class":a.oClasses.sInfo,id:!c?b+"_info":null});c||(a.aoDrawCallback.push({fn:Eb,sName:"information"}),d.attr("role","status").attr("aria-live","polite"),h(a.nTable).attr("aria-describedby",b+"_info"));return d[0]}function Eb(a){var b=a.aanFeatures.i;if(0!==b.length){var c=a.oLanguage,d=a._iDisplayStart+
+1,e=a.fnDisplayEnd(),f=a.fnRecordsTotal(),g=a.fnRecordsDisplay(),j=g?c.sInfo:c.sInfoEmpty;g!==f&&(j+=" "+c.sInfoFiltered);j+=c.sInfoPostFix;j=Fb(a,j);c=c.fnInfoCallback;null!==c&&(j=c.call(a.oInstance,a,d,e,f,g,j));h(b).html(j)}}function Fb(a,b){var c=a.fnFormatNumber,d=a._iDisplayStart+1,e=a._iDisplayLength,f=a.fnRecordsDisplay(),g=-1===e;return b.replace(/_START_/g,c.call(a,d)).replace(/_END_/g,c.call(a,a.fnDisplayEnd())).replace(/_MAX_/g,c.call(a,a.fnRecordsTotal())).replace(/_TOTAL_/g,c.call(a,
+f)).replace(/_PAGE_/g,c.call(a,g?1:Math.ceil(d/e))).replace(/_PAGES_/g,c.call(a,g?1:Math.ceil(f/e)))}function ha(a){var b,c,d=a.iInitDisplayStart,e=a.aoColumns,f;c=a.oFeatures;var g=a.bDeferLoading;if(a.bInitialised){pb(a);mb(a);fa(a,a.aoHeader);fa(a,a.aoFooter);C(a,!0);c.bAutoWidth&&Ha(a);b=0;for(c=e.length;b<c;b++)f=e[b],f.sWidth&&(f.nTh.style.width=v(f.sWidth));s(a,null,"preInit",[a]);T(a);e=y(a);if("ssp"!=e||g)"ajax"==e?ua(a,[],function(c){var f=va(a,c);for(b=0;b<f.length;b++)N(a,f[b]);a.iInitDisplayStart=
+d;T(a);C(a,!1);wa(a,c)},a):(C(a,!1),wa(a))}else setTimeout(function(){ha(a)},200)}function wa(a,b){a._bInitComplete=!0;(b||a.oInit.aaData)&&Z(a);s(a,null,"plugin-init",[a,b]);s(a,"aoInitComplete","init",[a,b])}function Ta(a,b){var c=parseInt(b,10);a._iDisplayLength=c;Ua(a);s(a,null,"length",[a,c])}function qb(a){for(var b=a.oClasses,c=a.sTableId,d=a.aLengthMenu,e=h.isArray(d[0]),f=e?d[0]:d,d=e?d[1]:d,e=h("<select/>",{name:c+"_length","aria-controls":c,"class":b.sLengthSelect}),g=0,j=f.length;g<j;g++)e[0][g]=
+new Option(d[g],f[g]);var i=h("<div><label/></div>").addClass(b.sLength);a.aanFeatures.l||(i[0].id=c+"_length");i.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",e[0].outerHTML));h("select",i).val(a._iDisplayLength).on("change.DT",function(){Ta(a,h(this).val());O(a)});h(a.nTable).on("length.dt.DT",function(b,c,d){a===c&&h("select",i).val(d)});return i[0]}function vb(a){var b=a.sPaginationType,c=m.ext.pager[b],d="function"===typeof c,e=function(a){O(a)},b=h("<div/>").addClass(a.oClasses.sPaging+
+b)[0],f=a.aanFeatures;d||c.fnInit(a,b,e);f.p||(b.id=a.sTableId+"_paginate",a.aoDrawCallback.push({fn:function(a){if(d){var b=a._iDisplayStart,i=a._iDisplayLength,h=a.fnRecordsDisplay(),l=-1===i,b=l?0:Math.ceil(b/i),i=l?1:Math.ceil(h/i),h=c(b,i),k,l=0;for(k=f.p.length;l<k;l++)Pa(a,"pageButton")(a,f.p[l],l,h,b,i)}else c.fnUpdate(a,e)},sName:"pagination"}));return b}function Va(a,b,c){var d=a._iDisplayStart,e=a._iDisplayLength,f=a.fnRecordsDisplay();0===f||-1===e?d=0:"number"===typeof b?(d=b*e,d>f&&
+(d=0)):"first"==b?d=0:"previous"==b?(d=0<=e?d-e:0,0>d&&(d=0)):"next"==b?d+e<f&&(d+=e):"last"==b?d=Math.floor((f-1)/e)*e:K(a,0,"Unknown paging action: "+b,5);b=a._iDisplayStart!==d;a._iDisplayStart=d;b&&(s(a,null,"page",[a]),c&&O(a));return b}function sb(a){return h("<div/>",{id:!a.aanFeatures.r?a.sTableId+"_processing":null,"class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).insertBefore(a.nTable)[0]}function C(a,b){a.oFeatures.bProcessing&&h(a.aanFeatures.r).css("display",b?"block":"none");
+s(a,null,"processing",[a,b])}function tb(a){var b=h(a.nTable);b.attr("role","grid");var c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var d=c.sX,e=c.sY,f=a.oClasses,g=b.children("caption"),j=g.length?g[0]._captionSide:null,i=h(b[0].cloneNode(!1)),n=h(b[0].cloneNode(!1)),l=b.children("tfoot");l.length||(l=null);i=h("<div/>",{"class":f.sScrollWrapper}).append(h("<div/>",{"class":f.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,width:d?!d?null:v(d):"100%"}).append(h("<div/>",
+{"class":f.sScrollHeadInner}).css({"box-sizing":"content-box",width:c.sXInner||"100%"}).append(i.removeAttr("id").css("margin-left",0).append("top"===j?g:null).append(b.children("thead"))))).append(h("<div/>",{"class":f.sScrollBody}).css({position:"relative",overflow:"auto",width:!d?null:v(d)}).append(b));l&&i.append(h("<div/>",{"class":f.sScrollFoot}).css({overflow:"hidden",border:0,width:d?!d?null:v(d):"100%"}).append(h("<div/>",{"class":f.sScrollFootInner}).append(n.removeAttr("id").css("margin-left",
+0).append("bottom"===j?g:null).append(b.children("tfoot")))));var b=i.children(),k=b[0],f=b[1],r=l?b[2]:null;if(d)h(f).on("scroll.DT",function(){var a=this.scrollLeft;k.scrollLeft=a;l&&(r.scrollLeft=a)});h(f).css(e&&c.bCollapse?"max-height":"height",e);a.nScrollHead=k;a.nScrollBody=f;a.nScrollFoot=r;a.aoDrawCallback.push({fn:ma,sName:"scrolling"});return i[0]}function ma(a){var b=a.oScroll,c=b.sX,d=b.sXInner,e=b.sY,b=b.iBarWidth,f=h(a.nScrollHead),g=f[0].style,j=f.children("div"),i=j[0].style,n=j.children("table"),
+j=a.nScrollBody,l=h(j),q=j.style,r=h(a.nScrollFoot).children("div"),m=r.children("table"),p=h(a.nTHead),o=h(a.nTable),t=o[0],s=t.style,u=a.nTFoot?h(a.nTFoot):null,x=a.oBrowser,U=x.bScrollOversize,ac=D(a.aoColumns,"nTh"),P,L,Q,w,Wa=[],y=[],z=[],A=[],B,C=function(a){a=a.style;a.paddingTop="0";a.paddingBottom="0";a.borderTopWidth="0";a.borderBottomWidth="0";a.height=0};L=j.scrollHeight>j.clientHeight;if(a.scrollBarVis!==L&&a.scrollBarVis!==k)a.scrollBarVis=L,Z(a);else{a.scrollBarVis=L;o.children("thead, tfoot").remove();
+u&&(Q=u.clone().prependTo(o),P=u.find("tr"),Q=Q.find("tr"));w=p.clone().prependTo(o);p=p.find("tr");L=w.find("tr");w.find("th, td").removeAttr("tabindex");c||(q.width="100%",f[0].style.width="100%");h.each(ta(a,w),function(b,c){B=$(a,b);c.style.width=a.aoColumns[B].sWidth});u&&I(function(a){a.style.width=""},Q);f=o.outerWidth();if(""===c){s.width="100%";if(U&&(o.find("tbody").height()>j.offsetHeight||"scroll"==l.css("overflow-y")))s.width=v(o.outerWidth()-b);f=o.outerWidth()}else""!==d&&(s.width=
+v(d),f=o.outerWidth());I(C,L);I(function(a){z.push(a.innerHTML);Wa.push(v(h(a).css("width")))},L);I(function(a,b){if(h.inArray(a,ac)!==-1)a.style.width=Wa[b]},p);h(L).height(0);u&&(I(C,Q),I(function(a){A.push(a.innerHTML);y.push(v(h(a).css("width")))},Q),I(function(a,b){a.style.width=y[b]},P),h(Q).height(0));I(function(a,b){a.innerHTML='<div class="dataTables_sizing" style="height:0;overflow:hidden;">'+z[b]+"</div>";a.style.width=Wa[b]},L);u&&I(function(a,b){a.innerHTML='<div class="dataTables_sizing" style="height:0;overflow:hidden;">'+
+A[b]+"</div>";a.style.width=y[b]},Q);if(o.outerWidth()<f){P=j.scrollHeight>j.offsetHeight||"scroll"==l.css("overflow-y")?f+b:f;if(U&&(j.scrollHeight>j.offsetHeight||"scroll"==l.css("overflow-y")))s.width=v(P-b);(""===c||""!==d)&&K(a,1,"Possible column misalignment",6)}else P="100%";q.width=v(P);g.width=v(P);u&&(a.nScrollFoot.style.width=v(P));!e&&U&&(q.height=v(t.offsetHeight+b));c=o.outerWidth();n[0].style.width=v(c);i.width=v(c);d=o.height()>j.clientHeight||"scroll"==l.css("overflow-y");e="padding"+
+(x.bScrollbarLeft?"Left":"Right");i[e]=d?b+"px":"0px";u&&(m[0].style.width=v(c),r[0].style.width=v(c),r[0].style[e]=d?b+"px":"0px");o.children("colgroup").insertBefore(o.children("thead"));l.scroll();if((a.bSorted||a.bFiltered)&&!a._drawHold)j.scrollTop=0}}function I(a,b,c){for(var d=0,e=0,f=b.length,g,j;e<f;){g=b[e].firstChild;for(j=c?c[e].firstChild:null;g;)1===g.nodeType&&(c?a(g,j,d):a(g,d),d++),g=g.nextSibling,j=c?j.nextSibling:null;e++}}function Ha(a){var b=a.nTable,c=a.aoColumns,d=a.oScroll,
+e=d.sY,f=d.sX,g=d.sXInner,j=c.length,i=na(a,"bVisible"),n=h("th",a.nTHead),l=b.getAttribute("width"),k=b.parentNode,r=!1,m,p,o=a.oBrowser,d=o.bScrollOversize;(m=b.style.width)&&-1!==m.indexOf("%")&&(l=m);for(m=0;m<i.length;m++)p=c[i[m]],null!==p.sWidth&&(p.sWidth=Gb(p.sWidthOrig,k),r=!0);if(d||!r&&!f&&!e&&j==ba(a)&&j==n.length)for(m=0;m<j;m++)i=$(a,m),null!==i&&(c[i].sWidth=v(n.eq(m).width()));else{j=h(b).clone().css("visibility","hidden").removeAttr("id");j.find("tbody tr").remove();var t=h("<tr/>").appendTo(j.find("tbody"));
+j.find("thead, tfoot").remove();j.append(h(a.nTHead).clone()).append(h(a.nTFoot).clone());j.find("tfoot th, tfoot td").css("width","");n=ta(a,j.find("thead")[0]);for(m=0;m<i.length;m++)p=c[i[m]],n[m].style.width=null!==p.sWidthOrig&&""!==p.sWidthOrig?v(p.sWidthOrig):"",p.sWidthOrig&&f&&h(n[m]).append(h("<div/>").css({width:p.sWidthOrig,margin:0,padding:0,border:0,height:1}));if(a.aoData.length)for(m=0;m<i.length;m++)r=i[m],p=c[r],h(Hb(a,r)).clone(!1).append(p.sContentPadding).appendTo(t);h("[name]",
+j).removeAttr("name");p=h("<div/>").css(f||e?{position:"absolute",top:0,left:0,height:1,right:0,overflow:"hidden"}:{}).append(j).appendTo(k);f&&g?j.width(g):f?(j.css("width","auto"),j.removeAttr("width"),j.width()<k.clientWidth&&l&&j.width(k.clientWidth)):e?j.width(k.clientWidth):l&&j.width(l);for(m=e=0;m<i.length;m++)k=h(n[m]),g=k.outerWidth()-k.width(),k=o.bBounding?Math.ceil(n[m].getBoundingClientRect().width):k.outerWidth(),e+=k,c[i[m]].sWidth=v(k-g);b.style.width=v(e);p.remove()}l&&(b.style.width=
+v(l));if((l||f)&&!a._reszEvt)b=function(){h(E).on("resize.DT-"+a.sInstance,Qa(function(){Z(a)}))},d?setTimeout(b,1E3):b(),a._reszEvt=!0}function Gb(a,b){if(!a)return 0;var c=h("<div/>").css("width",v(a)).appendTo(b||H.body),d=c[0].offsetWidth;c.remove();return d}function Hb(a,b){var c=Ib(a,b);if(0>c)return null;var d=a.aoData[c];return!d.nTr?h("<td/>").html(B(a,c,b,"display"))[0]:d.anCells[b]}function Ib(a,b){for(var c,d=-1,e=-1,f=0,g=a.aoData.length;f<g;f++)c=B(a,f,b,"display")+"",c=c.replace(bc,
+""),c=c.replace(/&nbsp;/g," "),c.length>d&&(d=c.length,e=f);return e}function v(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function W(a){var b,c,d=[],e=a.aoColumns,f,g,j,i;b=a.aaSortingFixed;c=h.isPlainObject(b);var n=[];f=function(a){a.length&&!h.isArray(a[0])?n.push(a):h.merge(n,a)};h.isArray(b)&&f(b);c&&b.pre&&f(b.pre);f(a.aaSorting);c&&b.post&&f(b.post);for(a=0;a<n.length;a++){i=n[a][0];f=e[i].aDataSort;b=0;for(c=f.length;b<c;b++)g=f[b],j=e[g].sType||
+"string",n[a]._idx===k&&(n[a]._idx=h.inArray(n[a][1],e[g].asSorting)),d.push({src:i,col:g,dir:n[a][1],index:n[a]._idx,type:j,formatter:m.ext.type.order[j+"-pre"]})}return d}function ob(a){var b,c,d=[],e=m.ext.type.order,f=a.aoData,g=0,j,i=a.aiDisplayMaster,h;Ia(a);h=W(a);b=0;for(c=h.length;b<c;b++)j=h[b],j.formatter&&g++,Jb(a,j.col);if("ssp"!=y(a)&&0!==h.length){b=0;for(c=i.length;b<c;b++)d[i[b]]=b;g===h.length?i.sort(function(a,b){var c,e,g,j,i=h.length,k=f[a]._aSortData,m=f[b]._aSortData;for(g=
+0;g<i;g++)if(j=h[g],c=k[j.col],e=m[j.col],c=c<e?-1:c>e?1:0,0!==c)return"asc"===j.dir?c:-c;c=d[a];e=d[b];return c<e?-1:c>e?1:0}):i.sort(function(a,b){var c,g,j,i,k=h.length,m=f[a]._aSortData,p=f[b]._aSortData;for(j=0;j<k;j++)if(i=h[j],c=m[i.col],g=p[i.col],i=e[i.type+"-"+i.dir]||e["string-"+i.dir],c=i(c,g),0!==c)return c;c=d[a];g=d[b];return c<g?-1:c>g?1:0})}a.bSorted=!0}function Kb(a){for(var b,c,d=a.aoColumns,e=W(a),a=a.oLanguage.oAria,f=0,g=d.length;f<g;f++){c=d[f];var j=c.asSorting;b=c.sTitle.replace(/<.*?>/g,
+"");var i=c.nTh;i.removeAttribute("aria-sort");c.bSortable&&(0<e.length&&e[0].col==f?(i.setAttribute("aria-sort","asc"==e[0].dir?"ascending":"descending"),c=j[e[0].index+1]||j[0]):c=j[0],b+="asc"===c?a.sSortAscending:a.sSortDescending);i.setAttribute("aria-label",b)}}function Xa(a,b,c,d){var e=a.aaSorting,f=a.aoColumns[b].asSorting,g=function(a,b){var c=a._idx;c===k&&(c=h.inArray(a[1],f));return c+1<f.length?c+1:b?null:0};"number"===typeof e[0]&&(e=a.aaSorting=[e]);c&&a.oFeatures.bSortMulti?(c=h.inArray(b,
+D(e,"0")),-1!==c?(b=g(e[c],!0),null===b&&1===e.length&&(b=0),null===b?e.splice(c,1):(e[c][1]=f[b],e[c]._idx=b)):(e.push([b,f[0],0]),e[e.length-1]._idx=0)):e.length&&e[0][0]==b?(b=g(e[0]),e.length=1,e[0][1]=f[b],e[0]._idx=b):(e.length=0,e.push([b,f[0]]),e[0]._idx=0);T(a);"function"==typeof d&&d(a)}function Oa(a,b,c,d){var e=a.aoColumns[c];Ya(b,{},function(b){!1!==e.bSortable&&(a.oFeatures.bProcessing?(C(a,!0),setTimeout(function(){Xa(a,c,b.shiftKey,d);"ssp"!==y(a)&&C(a,!1)},0)):Xa(a,c,b.shiftKey,d))})}
+function ya(a){var b=a.aLastSort,c=a.oClasses.sSortColumn,d=W(a),e=a.oFeatures,f,g;if(e.bSort&&e.bSortClasses){e=0;for(f=b.length;e<f;e++)g=b[e].src,h(D(a.aoData,"anCells",g)).removeClass(c+(2>e?e+1:3));e=0;for(f=d.length;e<f;e++)g=d[e].src,h(D(a.aoData,"anCells",g)).addClass(c+(2>e?e+1:3))}a.aLastSort=d}function Jb(a,b){var c=a.aoColumns[b],d=m.ext.order[c.sSortDataType],e;d&&(e=d.call(a.oInstance,a,b,aa(a,b)));for(var f,g=m.ext.type.order[c.sType+"-pre"],j=0,i=a.aoData.length;j<i;j++)if(c=a.aoData[j],
+c._aSortData||(c._aSortData=[]),!c._aSortData[b]||d)f=d?e[j]:B(a,j,b,"sort"),c._aSortData[b]=g?g(f):f}function za(a){if(a.oFeatures.bStateSave&&!a.bDestroying){var b={time:+new Date,start:a._iDisplayStart,length:a._iDisplayLength,order:h.extend(!0,[],a.aaSorting),search:Cb(a.oPreviousSearch),columns:h.map(a.aoColumns,function(b,d){return{visible:b.bVisible,search:Cb(a.aoPreSearchCols[d])}})};s(a,"aoStateSaveParams","stateSaveParams",[a,b]);a.oSavedState=b;a.fnStateSaveCallback.call(a.oInstance,a,
+b)}}function Lb(a,b,c){var d,e,f=a.aoColumns,b=function(b){if(b&&b.time){var g=s(a,"aoStateLoadParams","stateLoadParams",[a,b]);if(-1===h.inArray(!1,g)&&(g=a.iStateDuration,!(0<g&&b.time<+new Date-1E3*g)&&!(b.columns&&f.length!==b.columns.length))){a.oLoadedState=h.extend(!0,{},b);b.start!==k&&(a._iDisplayStart=b.start,a.iInitDisplayStart=b.start);b.length!==k&&(a._iDisplayLength=b.length);b.order!==k&&(a.aaSorting=[],h.each(b.order,function(b,c){a.aaSorting.push(c[0]>=f.length?[0,c[1]]:c)}));b.search!==
+k&&h.extend(a.oPreviousSearch,Db(b.search));if(b.columns){d=0;for(e=b.columns.length;d<e;d++)g=b.columns[d],g.visible!==k&&(f[d].bVisible=g.visible),g.search!==k&&h.extend(a.aoPreSearchCols[d],Db(g.search))}s(a,"aoStateLoaded","stateLoaded",[a,b])}}c()};if(a.oFeatures.bStateSave){var g=a.fnStateLoadCallback.call(a.oInstance,a,b);g!==k&&b(g)}else c()}function Aa(a){var b=m.settings,a=h.inArray(a,D(b,"nTable"));return-1!==a?b[a]:null}function K(a,b,c,d){c="DataTables warning: "+(a?"table id="+a.sTableId+
+" - ":"")+c;d&&(c+=". For more information about this error, please see http://datatables.net/tn/"+d);if(b)E.console&&console.log&&console.log(c);else if(b=m.ext,b=b.sErrMode||b.errMode,a&&s(a,null,"error",[a,d,c]),"alert"==b)alert(c);else{if("throw"==b)throw Error(c);"function"==typeof b&&b(a,d,c)}}function F(a,b,c,d){h.isArray(c)?h.each(c,function(c,d){h.isArray(d)?F(a,b,d[0],d[1]):F(a,b,d)}):(d===k&&(d=c),b[c]!==k&&(a[d]=b[c]))}function Mb(a,b,c){var d,e;for(e in b)b.hasOwnProperty(e)&&(d=b[e],
+h.isPlainObject(d)?(h.isPlainObject(a[e])||(a[e]={}),h.extend(!0,a[e],d)):a[e]=c&&"data"!==e&&"aaData"!==e&&h.isArray(d)?d.slice():d);return a}function Ya(a,b,c){h(a).on("click.DT",b,function(b){a.blur();c(b)}).on("keypress.DT",b,function(a){13===a.which&&(a.preventDefault(),c(a))}).on("selectstart.DT",function(){return!1})}function z(a,b,c,d){c&&a[b].push({fn:c,sName:d})}function s(a,b,c,d){var e=[];b&&(e=h.map(a[b].slice().reverse(),function(b){return b.fn.apply(a.oInstance,d)}));null!==c&&(b=h.Event(c+
+".dt"),h(a.nTable).trigger(b,d),e.push(b.result));return e}function Ua(a){var b=a._iDisplayStart,c=a.fnDisplayEnd(),d=a._iDisplayLength;b>=c&&(b=c-d);b-=b%d;if(-1===d||0>b)b=0;a._iDisplayStart=b}function Pa(a,b){var c=a.renderer,d=m.ext.renderer[b];return h.isPlainObject(c)&&c[b]?d[c[b]]||d._:"string"===typeof c?d[c]||d._:d._}function y(a){return a.oFeatures.bServerSide?"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function ia(a,b){var c=[],c=Nb.numbers_length,d=Math.floor(c/2);b<=c?c=X(0,b):a<=d?(c=X(0,
+c-2),c.push("ellipsis"),c.push(b-1)):(a>=b-1-d?c=X(b-(c-2),b):(c=X(a-d+2,a+d-1),c.push("ellipsis"),c.push(b-1)),c.splice(0,0,"ellipsis"),c.splice(0,0,0));c.DT_el="span";return c}function fb(a){h.each({num:function(b){return Ba(b,a)},"num-fmt":function(b){return Ba(b,a,Za)},"html-num":function(b){return Ba(b,a,Ca)},"html-num-fmt":function(b){return Ba(b,a,Ca,Za)}},function(b,c){x.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(x.type.search[b+a]=x.type.search.html)})}function Ob(a){return function(){var b=
+[Aa(this[m.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return m.ext.internal[a].apply(this,b)}}var m=function(a){this.$=function(a,b){return this.api(!0).$(a,b)};this._=function(a,b){return this.api(!0).rows(a,b).data()};this.api=function(a){return a?new t(Aa(this[x.iApiIndex])):new t(this)};this.fnAddData=function(a,b){var c=this.api(!0),d=h.isArray(a)&&(h.isArray(a[0])||h.isPlainObject(a[0]))?c.rows.add(a):c.row.add(a);(b===k||b)&&c.draw();return d.flatten().toArray()};this.fnAdjustColumnSizing=
+function(a){var b=this.api(!0).columns.adjust(),c=b.settings()[0],d=c.oScroll;a===k||a?b.draw(!1):(""!==d.sX||""!==d.sY)&&ma(c)};this.fnClearTable=function(a){var b=this.api(!0).clear();(a===k||a)&&b.draw()};this.fnClose=function(a){this.api(!0).row(a).child.hide()};this.fnDeleteRow=function(a,b,c){var d=this.api(!0),a=d.rows(a),e=a.settings()[0],h=e.aoData[a[0][0]];a.remove();b&&b.call(this,e,h);(c===k||c)&&d.draw();return h};this.fnDestroy=function(a){this.api(!0).destroy(a)};this.fnDraw=function(a){this.api(!0).draw(a)};
+this.fnFilter=function(a,b,c,d,e,h){e=this.api(!0);null===b||b===k?e.search(a,c,d,h):e.column(b).search(a,c,d,h);e.draw()};this.fnGetData=function(a,b){var c=this.api(!0);if(a!==k){var d=a.nodeName?a.nodeName.toLowerCase():"";return b!==k||"td"==d||"th"==d?c.cell(a,b).data():c.row(a).data()||null}return c.data().toArray()};this.fnGetNodes=function(a){var b=this.api(!0);return a!==k?b.row(a).node():b.rows().nodes().flatten().toArray()};this.fnGetPosition=function(a){var b=this.api(!0),c=a.nodeName.toUpperCase();
+return"TR"==c?b.row(a).index():"TD"==c||"TH"==c?(a=b.cell(a).index(),[a.row,a.columnVisible,a.column]):null};this.fnIsOpen=function(a){return this.api(!0).row(a).child.isShown()};this.fnOpen=function(a,b,c){return this.api(!0).row(a).child(b,c).show().child()[0]};this.fnPageChange=function(a,b){var c=this.api(!0).page(a);(b===k||b)&&c.draw(!1)};this.fnSetColumnVis=function(a,b,c){a=this.api(!0).column(a).visible(b);(c===k||c)&&a.columns.adjust().draw()};this.fnSettings=function(){return Aa(this[x.iApiIndex])};
+this.fnSort=function(a){this.api(!0).order(a).draw()};this.fnSortListener=function(a,b,c){this.api(!0).order.listener(a,b,c)};this.fnUpdate=function(a,b,c,d,e){var h=this.api(!0);c===k||null===c?h.row(b).data(a):h.cell(b,c).data(a);(e===k||e)&&h.columns.adjust();(d===k||d)&&h.draw();return 0};this.fnVersionCheck=x.fnVersionCheck;var b=this,c=a===k,d=this.length;c&&(a={});this.oApi=this.internal=x.internal;for(var e in m.ext.internal)e&&(this[e]=Ob(e));this.each(function(){var e={},g=1<d?Mb(e,a,!0):
+a,j=0,i,e=this.getAttribute("id"),n=!1,l=m.defaults,q=h(this);if("table"!=this.nodeName.toLowerCase())K(null,0,"Non-table node initialisation ("+this.nodeName+")",2);else{gb(l);hb(l.column);J(l,l,!0);J(l.column,l.column,!0);J(l,h.extend(g,q.data()));var r=m.settings,j=0;for(i=r.length;j<i;j++){var p=r[j];if(p.nTable==this||p.nTHead.parentNode==this||p.nTFoot&&p.nTFoot.parentNode==this){var t=g.bRetrieve!==k?g.bRetrieve:l.bRetrieve;if(c||t)return p.oInstance;if(g.bDestroy!==k?g.bDestroy:l.bDestroy){p.oInstance.fnDestroy();
+break}else{K(p,0,"Cannot reinitialise DataTable",3);return}}if(p.sTableId==this.id){r.splice(j,1);break}}if(null===e||""===e)this.id=e="DataTables_Table_"+m.ext._unique++;var o=h.extend(!0,{},m.models.oSettings,{sDestroyWidth:q[0].style.width,sInstance:e,sTableId:e});o.nTable=this;o.oApi=b.internal;o.oInit=g;r.push(o);o.oInstance=1===b.length?b:q.dataTable();gb(g);g.oLanguage&&Fa(g.oLanguage);g.aLengthMenu&&!g.iDisplayLength&&(g.iDisplayLength=h.isArray(g.aLengthMenu[0])?g.aLengthMenu[0][0]:g.aLengthMenu[0]);
+g=Mb(h.extend(!0,{},l),g);F(o.oFeatures,g,"bPaginate bLengthChange bFilter bSort bSortMulti bInfo bProcessing bAutoWidth bSortClasses bServerSide bDeferRender".split(" "));F(o,g,["asStripeClasses","ajax","fnServerData","fnFormatNumber","sServerMethod","aaSorting","aaSortingFixed","aLengthMenu","sPaginationType","sAjaxSource","sAjaxDataProp","iStateDuration","sDom","bSortCellsTop","iTabIndex","fnStateLoadCallback","fnStateSaveCallback","renderer","searchDelay","rowId",["iCookieDuration","iStateDuration"],
+["oSearch","oPreviousSearch"],["aoSearchCols","aoPreSearchCols"],["iDisplayLength","_iDisplayLength"],["bJQueryUI","bJUI"]]);F(o.oScroll,g,[["sScrollX","sX"],["sScrollXInner","sXInner"],["sScrollY","sY"],["bScrollCollapse","bCollapse"]]);F(o.oLanguage,g,"fnInfoCallback");z(o,"aoDrawCallback",g.fnDrawCallback,"user");z(o,"aoServerParams",g.fnServerParams,"user");z(o,"aoStateSaveParams",g.fnStateSaveParams,"user");z(o,"aoStateLoadParams",g.fnStateLoadParams,"user");z(o,"aoStateLoaded",g.fnStateLoaded,
+"user");z(o,"aoRowCallback",g.fnRowCallback,"user");z(o,"aoRowCreatedCallback",g.fnCreatedRow,"user");z(o,"aoHeaderCallback",g.fnHeaderCallback,"user");z(o,"aoFooterCallback",g.fnFooterCallback,"user");z(o,"aoInitComplete",g.fnInitComplete,"user");z(o,"aoPreDrawCallback",g.fnPreDrawCallback,"user");o.rowIdFn=R(g.rowId);ib(o);var u=o.oClasses;g.bJQueryUI?(h.extend(u,m.ext.oJUIClasses,g.oClasses),g.sDom===l.sDom&&"lfrtip"===l.sDom&&(o.sDom='<"H"lfr>t<"F"ip>'),o.renderer)?h.isPlainObject(o.renderer)&&
+!o.renderer.header&&(o.renderer.header="jqueryui"):o.renderer="jqueryui":h.extend(u,m.ext.classes,g.oClasses);q.addClass(u.sTable);o.iInitDisplayStart===k&&(o.iInitDisplayStart=g.iDisplayStart,o._iDisplayStart=g.iDisplayStart);null!==g.iDeferLoading&&(o.bDeferLoading=!0,e=h.isArray(g.iDeferLoading),o._iRecordsDisplay=e?g.iDeferLoading[0]:g.iDeferLoading,o._iRecordsTotal=e?g.iDeferLoading[1]:g.iDeferLoading);var v=o.oLanguage;h.extend(!0,v,g.oLanguage);v.sUrl&&(h.ajax({dataType:"json",url:v.sUrl,success:function(a){Fa(a);
+J(l.oLanguage,a);h.extend(true,v,a);ha(o)},error:function(){ha(o)}}),n=!0);null===g.asStripeClasses&&(o.asStripeClasses=[u.sStripeOdd,u.sStripeEven]);var e=o.asStripeClasses,x=q.children("tbody").find("tr").eq(0);-1!==h.inArray(!0,h.map(e,function(a){return x.hasClass(a)}))&&(h("tbody tr",this).removeClass(e.join(" ")),o.asDestroyStripes=e.slice());e=[];r=this.getElementsByTagName("thead");0!==r.length&&(ea(o.aoHeader,r[0]),e=ta(o));if(null===g.aoColumns){r=[];j=0;for(i=e.length;j<i;j++)r.push(null)}else r=
+g.aoColumns;j=0;for(i=r.length;j<i;j++)Ga(o,e?e[j]:null);kb(o,g.aoColumnDefs,r,function(a,b){la(o,a,b)});if(x.length){var w=function(a,b){return a.getAttribute("data-"+b)!==null?b:null};h(x[0]).children("th, td").each(function(a,b){var c=o.aoColumns[a];if(c.mData===a){var d=w(b,"sort")||w(b,"order"),e=w(b,"filter")||w(b,"search");if(d!==null||e!==null){c.mData={_:a+".display",sort:d!==null?a+".@data-"+d:k,type:d!==null?a+".@data-"+d:k,filter:e!==null?a+".@data-"+e:k};la(o,a)}}})}var U=o.oFeatures,
+e=function(){if(g.aaSorting===k){var a=o.aaSorting;j=0;for(i=a.length;j<i;j++)a[j][1]=o.aoColumns[j].asSorting[0]}ya(o);U.bSort&&z(o,"aoDrawCallback",function(){if(o.bSorted){var a=W(o),b={};h.each(a,function(a,c){b[c.src]=c.dir});s(o,null,"order",[o,a,b]);Kb(o)}});z(o,"aoDrawCallback",function(){(o.bSorted||y(o)==="ssp"||U.bDeferRender)&&ya(o)},"sc");var a=q.children("caption").each(function(){this._captionSide=h(this).css("caption-side")}),b=q.children("thead");b.length===0&&(b=h("<thead/>").appendTo(q));
+o.nTHead=b[0];b=q.children("tbody");b.length===0&&(b=h("<tbody/>").appendTo(q));o.nTBody=b[0];b=q.children("tfoot");if(b.length===0&&a.length>0&&(o.oScroll.sX!==""||o.oScroll.sY!==""))b=h("<tfoot/>").appendTo(q);if(b.length===0||b.children().length===0)q.addClass(u.sNoFooter);else if(b.length>0){o.nTFoot=b[0];ea(o.aoFooter,o.nTFoot)}if(g.aaData)for(j=0;j<g.aaData.length;j++)N(o,g.aaData[j]);else(o.bDeferLoading||y(o)=="dom")&&oa(o,h(o.nTBody).children("tr"));o.aiDisplay=o.aiDisplayMaster.slice();
+o.bInitialised=true;n===false&&ha(o)};g.bStateSave?(U.bStateSave=!0,z(o,"aoDrawCallback",za,"state_save"),Lb(o,g,e)):e()}});b=null;return this},x,t,p,u,$a={},Pb=/[\r\n]/g,Ca=/<.*?>/g,cc=/^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/,dc=RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^|\\-)","g"),Za=/[',$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfk]/gi,M=function(a){return!a||!0===a||"-"===a?!0:!1},Qb=function(a){var b=parseInt(a,10);return!isNaN(b)&&
+isFinite(a)?b:null},Rb=function(a,b){$a[b]||($a[b]=RegExp(Sa(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g,"").replace($a[b],"."):a},ab=function(a,b,c){var d="string"===typeof a;if(M(a))return!0;b&&d&&(a=Rb(a,b));c&&d&&(a=a.replace(Za,""));return!isNaN(parseFloat(a))&&isFinite(a)},Sb=function(a,b,c){return M(a)?!0:!(M(a)||"string"===typeof a)?null:ab(a.replace(Ca,""),b,c)?!0:null},D=function(a,b,c){var d=[],e=0,f=a.length;if(c!==k)for(;e<f;e++)a[e]&&a[e][b]&&d.push(a[e][b][c]);else for(;e<
+f;e++)a[e]&&d.push(a[e][b]);return d},ja=function(a,b,c,d){var e=[],f=0,g=b.length;if(d!==k)for(;f<g;f++)a[b[f]][c]&&e.push(a[b[f]][c][d]);else for(;f<g;f++)e.push(a[b[f]][c]);return e},X=function(a,b){var c=[],d;b===k?(b=0,d=a):(d=b,b=a);for(var e=b;e<d;e++)c.push(e);return c},Tb=function(a){for(var b=[],c=0,d=a.length;c<d;c++)a[c]&&b.push(a[c]);return b},sa=function(a){var b;a:{if(!(2>a.length)){b=a.slice().sort();for(var c=b[0],d=1,e=b.length;d<e;d++){if(b[d]===c){b=!1;break a}c=b[d]}}b=!0}if(b)return a.slice();
+b=[];var e=a.length,f,g=0,d=0;a:for(;d<e;d++){c=a[d];for(f=0;f<g;f++)if(b[f]===c)continue a;b.push(c);g++}return b};m.util={throttle:function(a,b){var c=b!==k?b:200,d,e;return function(){var b=this,g=+new Date,h=arguments;d&&g<d+c?(clearTimeout(e),e=setTimeout(function(){d=k;a.apply(b,h)},c)):(d=g,a.apply(b,h))}},escapeRegex:function(a){return a.replace(dc,"\\$1")}};var A=function(a,b,c){a[b]!==k&&(a[c]=a[b])},ca=/\[.*?\]$/,V=/\(\)$/,Sa=m.util.escapeRegex,xa=h("<div>")[0],$b=xa.textContent!==k,bc=
+/<.*?>/g,Qa=m.util.throttle,Ub=[],w=Array.prototype,ec=function(a){var b,c,d=m.settings,e=h.map(d,function(a){return a.nTable});if(a){if(a.nTable&&a.oApi)return[a];if(a.nodeName&&"table"===a.nodeName.toLowerCase())return b=h.inArray(a,e),-1!==b?[d[b]]:null;if(a&&"function"===typeof a.settings)return a.settings().toArray();"string"===typeof a?c=h(a):a instanceof h&&(c=a)}else return[];if(c)return c.map(function(){b=h.inArray(this,e);return-1!==b?d[b]:null}).toArray()};t=function(a,b){if(!(this instanceof
+t))return new t(a,b);var c=[],d=function(a){(a=ec(a))&&(c=c.concat(a))};if(h.isArray(a))for(var e=0,f=a.length;e<f;e++)d(a[e]);else d(a);this.context=sa(c);b&&h.merge(this,b);this.selector={rows:null,cols:null,opts:null};t.extend(this,this,Ub)};m.Api=t;h.extend(t.prototype,{any:function(){return 0!==this.count()},concat:w.concat,context:[],count:function(){return this.flatten().length},each:function(a){for(var b=0,c=this.length;b<c;b++)a.call(this,this[b],b,this);return this},eq:function(a){var b=
+this.context;return b.length>a?new t(b[a],this[a]):null},filter:function(a){var b=[];if(w.filter)b=w.filter.call(this,a,this);else for(var c=0,d=this.length;c<d;c++)a.call(this,this[c],c,this)&&b.push(this[c]);return new t(this.context,b)},flatten:function(){var a=[];return new t(this.context,a.concat.apply(a,this.toArray()))},join:w.join,indexOf:w.indexOf||function(a,b){for(var c=b||0,d=this.length;c<d;c++)if(this[c]===a)return c;return-1},iterator:function(a,b,c,d){var e=[],f,g,h,i,n,l=this.context,
+m,p,u=this.selector;"string"===typeof a&&(d=c,c=b,b=a,a=!1);g=0;for(h=l.length;g<h;g++){var s=new t(l[g]);if("table"===b)f=c.call(s,l[g],g),f!==k&&e.push(f);else if("columns"===b||"rows"===b)f=c.call(s,l[g],this[g],g),f!==k&&e.push(f);else if("column"===b||"column-rows"===b||"row"===b||"cell"===b){p=this[g];"column-rows"===b&&(m=Da(l[g],u.opts));i=0;for(n=p.length;i<n;i++)f=p[i],f="cell"===b?c.call(s,l[g],f.row,f.column,g,i):c.call(s,l[g],f,g,i,m),f!==k&&e.push(f)}}return e.length||d?(a=new t(l,a?
+e.concat.apply([],e):e),b=a.selector,b.rows=u.rows,b.cols=u.cols,b.opts=u.opts,a):this},lastIndexOf:w.lastIndexOf||function(a,b){return this.indexOf.apply(this.toArray.reverse(),arguments)},length:0,map:function(a){var b=[];if(w.map)b=w.map.call(this,a,this);else for(var c=0,d=this.length;c<d;c++)b.push(a.call(this,this[c],c));return new t(this.context,b)},pluck:function(a){return this.map(function(b){return b[a]})},pop:w.pop,push:w.push,reduce:w.reduce||function(a,b){return jb(this,a,b,0,this.length,
+1)},reduceRight:w.reduceRight||function(a,b){return jb(this,a,b,this.length-1,-1,-1)},reverse:w.reverse,selector:null,shift:w.shift,slice:function(){return new t(this.context,this)},sort:w.sort,splice:w.splice,toArray:function(){return w.slice.call(this)},to$:function(){return h(this)},toJQuery:function(){return h(this)},unique:function(){return new t(this.context,sa(this))},unshift:w.unshift});t.extend=function(a,b,c){if(c.length&&b&&(b instanceof t||b.__dt_wrapper)){var d,e,f,g=function(a,b,c){return function(){var d=
+b.apply(a,arguments);t.extend(d,d,c.methodExt);return d}};d=0;for(e=c.length;d<e;d++)f=c[d],b[f.name]="function"===typeof f.val?g(a,f.val,f):h.isPlainObject(f.val)?{}:f.val,b[f.name].__dt_wrapper=!0,t.extend(a,b[f.name],f.propExt)}};t.register=p=function(a,b){if(h.isArray(a))for(var c=0,d=a.length;c<d;c++)t.register(a[c],b);else for(var e=a.split("."),f=Ub,g,j,c=0,d=e.length;c<d;c++){g=(j=-1!==e[c].indexOf("()"))?e[c].replace("()",""):e[c];var i;a:{i=0;for(var n=f.length;i<n;i++)if(f[i].name===g){i=
+f[i];break a}i=null}i||(i={name:g,val:{},methodExt:[],propExt:[]},f.push(i));c===d-1?i.val=b:f=j?i.methodExt:i.propExt}};t.registerPlural=u=function(a,b,c){t.register(a,c);t.register(b,function(){var a=c.apply(this,arguments);return a===this?this:a instanceof t?a.length?h.isArray(a[0])?new t(a.context,a[0]):a[0]:k:a})};p("tables()",function(a){var b;if(a){b=t;var c=this.context;if("number"===typeof a)a=[c[a]];else var d=h.map(c,function(a){return a.nTable}),a=h(d).filter(a).map(function(){var a=h.inArray(this,
+d);return c[a]}).toArray();b=new b(a)}else b=this;return b});p("table()",function(a){var a=this.tables(a),b=a.context;return b.length?new t(b[0]):a});u("tables().nodes()","table().node()",function(){return this.iterator("table",function(a){return a.nTable},1)});u("tables().body()","table().body()",function(){return this.iterator("table",function(a){return a.nTBody},1)});u("tables().header()","table().header()",function(){return this.iterator("table",function(a){return a.nTHead},1)});u("tables().footer()",
+"table().footer()",function(){return this.iterator("table",function(a){return a.nTFoot},1)});u("tables().containers()","table().container()",function(){return this.iterator("table",function(a){return a.nTableWrapper},1)});p("draw()",function(a){return this.iterator("table",function(b){"page"===a?O(b):("string"===typeof a&&(a="full-hold"===a?!1:!0),T(b,!1===a))})});p("page()",function(a){return a===k?this.page.info().page:this.iterator("table",function(b){Va(b,a)})});p("page.info()",function(){if(0===
+this.context.length)return k;var a=this.context[0],b=a._iDisplayStart,c=a.oFeatures.bPaginate?a._iDisplayLength:-1,d=a.fnRecordsDisplay(),e=-1===c;return{page:e?0:Math.floor(b/c),pages:e?1:Math.ceil(d/c),start:b,end:a.fnDisplayEnd(),length:c,recordsTotal:a.fnRecordsTotal(),recordsDisplay:d,serverSide:"ssp"===y(a)}});p("page.len()",function(a){return a===k?0!==this.context.length?this.context[0]._iDisplayLength:k:this.iterator("table",function(b){Ta(b,a)})});var Vb=function(a,b,c){if(c){var d=new t(a);
+d.one("draw",function(){c(d.ajax.json())})}if("ssp"==y(a))T(a,b);else{C(a,!0);var e=a.jqXHR;e&&4!==e.readyState&&e.abort();ua(a,[],function(c){pa(a);for(var c=va(a,c),d=0,e=c.length;d<e;d++)N(a,c[d]);T(a,b);C(a,!1)})}};p("ajax.json()",function(){var a=this.context;if(0<a.length)return a[0].json});p("ajax.params()",function(){var a=this.context;if(0<a.length)return a[0].oAjaxData});p("ajax.reload()",function(a,b){return this.iterator("table",function(c){Vb(c,!1===b,a)})});p("ajax.url()",function(a){var b=
+this.context;if(a===k){if(0===b.length)return k;b=b[0];return b.ajax?h.isPlainObject(b.ajax)?b.ajax.url:b.ajax:b.sAjaxSource}return this.iterator("table",function(b){h.isPlainObject(b.ajax)?b.ajax.url=a:b.ajax=a})});p("ajax.url().load()",function(a,b){return this.iterator("table",function(c){Vb(c,!1===b,a)})});var bb=function(a,b,c,d,e){var f=[],g,j,i,n,l,m;i=typeof b;if(!b||"string"===i||"function"===i||b.length===k)b=[b];i=0;for(n=b.length;i<n;i++){j=b[i]&&b[i].split&&!b[i].match(/[\[\(:]/)?b[i].split(","):
+[b[i]];l=0;for(m=j.length;l<m;l++)(g=c("string"===typeof j[l]?h.trim(j[l]):j[l]))&&g.length&&(f=f.concat(g))}a=x.selector[a];if(a.length){i=0;for(n=a.length;i<n;i++)f=a[i](d,e,f)}return sa(f)},cb=function(a){a||(a={});a.filter&&a.search===k&&(a.search=a.filter);return h.extend({search:"none",order:"current",page:"all"},a)},db=function(a){for(var b=0,c=a.length;b<c;b++)if(0<a[b].length)return a[0]=a[b],a[0].length=1,a.length=1,a.context=[a.context[b]],a;a.length=0;return a},Da=function(a,b){var c,
+d,e,f=[],g=a.aiDisplay;c=a.aiDisplayMaster;var j=b.search;d=b.order;e=b.page;if("ssp"==y(a))return"removed"===j?[]:X(0,c.length);if("current"==e){c=a._iDisplayStart;for(d=a.fnDisplayEnd();c<d;c++)f.push(g[c])}else if("current"==d||"applied"==d)f="none"==j?c.slice():"applied"==j?g.slice():h.map(c,function(a){return-1===h.inArray(a,g)?a:null});else if("index"==d||"original"==d){c=0;for(d=a.aoData.length;c<d;c++)"none"==j?f.push(c):(e=h.inArray(c,g),(-1===e&&"removed"==j||0<=e&&"applied"==j)&&f.push(c))}return f};
+p("rows()",function(a,b){a===k?a="":h.isPlainObject(a)&&(b=a,a="");var b=cb(b),c=this.iterator("table",function(c){var e=b,f;return bb("row",a,function(a){var b=Qb(a);if(b!==null&&!e)return[b];f||(f=Da(c,e));if(b!==null&&h.inArray(b,f)!==-1)return[b];if(a===null||a===k||a==="")return f;if(typeof a==="function")return h.map(f,function(b){var e=c.aoData[b];return a(b,e._aData,e.nTr)?b:null});b=Tb(ja(c.aoData,f,"nTr"));if(a.nodeName){if(a._DT_RowIndex!==k)return[a._DT_RowIndex];if(a._DT_CellIndex)return[a._DT_CellIndex.row];
+b=h(a).closest("*[data-dt-row]");return b.length?[b.data("dt-row")]:[]}if(typeof a==="string"&&a.charAt(0)==="#"){var i=c.aIds[a.replace(/^#/,"")];if(i!==k)return[i.idx]}return h(b).filter(a).map(function(){return this._DT_RowIndex}).toArray()},c,e)},1);c.selector.rows=a;c.selector.opts=b;return c});p("rows().nodes()",function(){return this.iterator("row",function(a,b){return a.aoData[b].nTr||k},1)});p("rows().data()",function(){return this.iterator(!0,"rows",function(a,b){return ja(a.aoData,b,"_aData")},
+1)});u("rows().cache()","row().cache()",function(a){return this.iterator("row",function(b,c){var d=b.aoData[c];return"search"===a?d._aFilterData:d._aSortData},1)});u("rows().invalidate()","row().invalidate()",function(a){return this.iterator("row",function(b,c){da(b,c,a)})});u("rows().indexes()","row().index()",function(){return this.iterator("row",function(a,b){return b},1)});u("rows().ids()","row().id()",function(a){for(var b=[],c=this.context,d=0,e=c.length;d<e;d++)for(var f=0,g=this[d].length;f<
+g;f++){var h=c[d].rowIdFn(c[d].aoData[this[d][f]]._aData);b.push((!0===a?"#":"")+h)}return new t(c,b)});u("rows().remove()","row().remove()",function(){var a=this;this.iterator("row",function(b,c,d){var e=b.aoData,f=e[c],g,h,i,n,l;e.splice(c,1);g=0;for(h=e.length;g<h;g++)if(i=e[g],l=i.anCells,null!==i.nTr&&(i.nTr._DT_RowIndex=g),null!==l){i=0;for(n=l.length;i<n;i++)l[i]._DT_CellIndex.row=g}qa(b.aiDisplayMaster,c);qa(b.aiDisplay,c);qa(a[d],c,!1);Ua(b);c=b.rowIdFn(f._aData);c!==k&&delete b.aIds[c]});
+this.iterator("table",function(a){for(var c=0,d=a.aoData.length;c<d;c++)a.aoData[c].idx=c});return this});p("rows.add()",function(a){var b=this.iterator("table",function(b){var c,f,g,h=[];f=0;for(g=a.length;f<g;f++)c=a[f],c.nodeName&&"TR"===c.nodeName.toUpperCase()?h.push(oa(b,c)[0]):h.push(N(b,c));return h},1),c=this.rows(-1);c.pop();h.merge(c,b);return c});p("row()",function(a,b){return db(this.rows(a,b))});p("row().data()",function(a){var b=this.context;if(a===k)return b.length&&this.length?b[0].aoData[this[0]]._aData:
+k;b[0].aoData[this[0]]._aData=a;da(b[0],this[0],"data");return this});p("row().node()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]].nTr||null:null});p("row.add()",function(a){a instanceof h&&a.length&&(a=a[0]);var b=this.iterator("table",function(b){return a.nodeName&&"TR"===a.nodeName.toUpperCase()?oa(b,a)[0]:N(b,a)});return this.row(b[0])});var eb=function(a,b){var c=a.context;if(c.length&&(c=c[0].aoData[b!==k?b:a[0]])&&c._details)c._details.remove(),c._detailsShow=
+k,c._details=k},Wb=function(a,b){var c=a.context;if(c.length&&a.length){var d=c[0].aoData[a[0]];if(d._details){(d._detailsShow=b)?d._details.insertAfter(d.nTr):d._details.detach();var e=c[0],f=new t(e),g=e.aoData;f.off("draw.dt.DT_details column-visibility.dt.DT_details destroy.dt.DT_details");0<D(g,"_details").length&&(f.on("draw.dt.DT_details",function(a,b){e===b&&f.rows({page:"current"}).eq(0).each(function(a){a=g[a];a._detailsShow&&a._details.insertAfter(a.nTr)})}),f.on("column-visibility.dt.DT_details",
+function(a,b){if(e===b)for(var c,d=ba(b),f=0,h=g.length;f<h;f++)c=g[f],c._details&&c._details.children("td[colspan]").attr("colspan",d)}),f.on("destroy.dt.DT_details",function(a,b){if(e===b)for(var c=0,d=g.length;c<d;c++)g[c]._details&&eb(f,c)}))}}};p("row().child()",function(a,b){var c=this.context;if(a===k)return c.length&&this.length?c[0].aoData[this[0]]._details:k;if(!0===a)this.child.show();else if(!1===a)eb(this);else if(c.length&&this.length){var d=c[0],c=c[0].aoData[this[0]],e=[],f=function(a,
+b){if(h.isArray(a)||a instanceof h)for(var c=0,k=a.length;c<k;c++)f(a[c],b);else a.nodeName&&"tr"===a.nodeName.toLowerCase()?e.push(a):(c=h("<tr><td/></tr>").addClass(b),h("td",c).addClass(b).html(a)[0].colSpan=ba(d),e.push(c[0]))};f(a,b);c._details&&c._details.detach();c._details=h(e);c._detailsShow&&c._details.insertAfter(c.nTr)}return this});p(["row().child.show()","row().child().show()"],function(){Wb(this,!0);return this});p(["row().child.hide()","row().child().hide()"],function(){Wb(this,!1);
+return this});p(["row().child.remove()","row().child().remove()"],function(){eb(this);return this});p("row().child.isShown()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]]._detailsShow||!1:!1});var fc=/^([^:]+):(name|visIdx|visible)$/,Xb=function(a,b,c,d,e){for(var c=[],d=0,f=e.length;d<f;d++)c.push(B(a,e[d],b));return c};p("columns()",function(a,b){a===k?a="":h.isPlainObject(a)&&(b=a,a="");var b=cb(b),c=this.iterator("table",function(c){var e=a,f=b,g=c.aoColumns,
+j=D(g,"sName"),i=D(g,"nTh");return bb("column",e,function(a){var b=Qb(a);if(a==="")return X(g.length);if(b!==null)return[b>=0?b:g.length+b];if(typeof a==="function"){var e=Da(c,f);return h.map(g,function(b,f){return a(f,Xb(c,f,0,0,e),i[f])?f:null})}var k=typeof a==="string"?a.match(fc):"";if(k)switch(k[2]){case "visIdx":case "visible":b=parseInt(k[1],10);if(b<0){var m=h.map(g,function(a,b){return a.bVisible?b:null});return[m[m.length+b]]}return[$(c,b)];case "name":return h.map(j,function(a,b){return a===
+k[1]?b:null});default:return[]}if(a.nodeName&&a._DT_CellIndex)return[a._DT_CellIndex.column];b=h(i).filter(a).map(function(){return h.inArray(this,i)}).toArray();if(b.length||!a.nodeName)return b;b=h(a).closest("*[data-dt-column]");return b.length?[b.data("dt-column")]:[]},c,f)},1);c.selector.cols=a;c.selector.opts=b;return c});u("columns().header()","column().header()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTh},1)});u("columns().footer()","column().footer()",
+function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTf},1)});u("columns().data()","column().data()",function(){return this.iterator("column-rows",Xb,1)});u("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].mData},1)});u("columns().cache()","column().cache()",function(a){return this.iterator("column-rows",function(b,c,d,e,f){return ja(b.aoData,f,"search"===a?"_aFilterData":"_aSortData",c)},1)});u("columns().nodes()",
+"column().nodes()",function(){return this.iterator("column-rows",function(a,b,c,d,e){return ja(a.aoData,e,"anCells",b)},1)});u("columns().visible()","column().visible()",function(a,b){var c=this.iterator("column",function(b,c){if(a===k)return b.aoColumns[c].bVisible;var f=b.aoColumns,g=f[c],j=b.aoData,i,n,l;if(a!==k&&g.bVisible!==a){if(a){var m=h.inArray(!0,D(f,"bVisible"),c+1);i=0;for(n=j.length;i<n;i++)l=j[i].nTr,f=j[i].anCells,l&&l.insertBefore(f[c],f[m]||null)}else h(D(b.aoData,"anCells",c)).detach();
+g.bVisible=a;fa(b,b.aoHeader);fa(b,b.aoFooter);za(b)}});a!==k&&(this.iterator("column",function(c,e){s(c,null,"column-visibility",[c,e,a,b])}),(b===k||b)&&this.columns.adjust());return c});u("columns().indexes()","column().index()",function(a){return this.iterator("column",function(b,c){return"visible"===a?aa(b,c):c},1)});p("columns.adjust()",function(){return this.iterator("table",function(a){Z(a)},1)});p("column.index()",function(a,b){if(0!==this.context.length){var c=this.context[0];if("fromVisible"===
+a||"toData"===a)return $(c,b);if("fromData"===a||"toVisible"===a)return aa(c,b)}});p("column()",function(a,b){return db(this.columns(a,b))});p("cells()",function(a,b,c){h.isPlainObject(a)&&(a.row===k?(c=a,a=null):(c=b,b=null));h.isPlainObject(b)&&(c=b,b=null);if(null===b||b===k)return this.iterator("table",function(b){var d=a,e=cb(c),f=b.aoData,g=Da(b,e),j=Tb(ja(f,g,"anCells")),i=h([].concat.apply([],j)),l,n=b.aoColumns.length,m,p,u,t,s,v;return bb("cell",d,function(a){var c=typeof a==="function";
+if(a===null||a===k||c){m=[];p=0;for(u=g.length;p<u;p++){l=g[p];for(t=0;t<n;t++){s={row:l,column:t};if(c){v=f[l];a(s,B(b,l,t),v.anCells?v.anCells[t]:null)&&m.push(s)}else m.push(s)}}return m}if(h.isPlainObject(a))return[a];c=i.filter(a).map(function(a,b){return{row:b._DT_CellIndex.row,column:b._DT_CellIndex.column}}).toArray();if(c.length||!a.nodeName)return c;v=h(a).closest("*[data-dt-row]");return v.length?[{row:v.data("dt-row"),column:v.data("dt-column")}]:[]},b,e)});var d=this.columns(b,c),e=this.rows(a,
+c),f,g,j,i,n,l=this.iterator("table",function(a,b){f=[];g=0;for(j=e[b].length;g<j;g++){i=0;for(n=d[b].length;i<n;i++)f.push({row:e[b][g],column:d[b][i]})}return f},1);h.extend(l.selector,{cols:b,rows:a,opts:c});return l});u("cells().nodes()","cell().node()",function(){return this.iterator("cell",function(a,b,c){return(a=a.aoData[b])&&a.anCells?a.anCells[c]:k},1)});p("cells().data()",function(){return this.iterator("cell",function(a,b,c){return B(a,b,c)},1)});u("cells().cache()","cell().cache()",function(a){a=
+"search"===a?"_aFilterData":"_aSortData";return this.iterator("cell",function(b,c,d){return b.aoData[c][a][d]},1)});u("cells().render()","cell().render()",function(a){return this.iterator("cell",function(b,c,d){return B(b,c,d,a)},1)});u("cells().indexes()","cell().index()",function(){return this.iterator("cell",function(a,b,c){return{row:b,column:c,columnVisible:aa(a,c)}},1)});u("cells().invalidate()","cell().invalidate()",function(a){return this.iterator("cell",function(b,c,d){da(b,c,a,d)})});p("cell()",
+function(a,b,c){return db(this.cells(a,b,c))});p("cell().data()",function(a){var b=this.context,c=this[0];if(a===k)return b.length&&c.length?B(b[0],c[0].row,c[0].column):k;lb(b[0],c[0].row,c[0].column,a);da(b[0],c[0].row,"data",c[0].column);return this});p("order()",function(a,b){var c=this.context;if(a===k)return 0!==c.length?c[0].aaSorting:k;"number"===typeof a?a=[[a,b]]:a.length&&!h.isArray(a[0])&&(a=Array.prototype.slice.call(arguments));return this.iterator("table",function(b){b.aaSorting=a.slice()})});
+p("order.listener()",function(a,b,c){return this.iterator("table",function(d){Oa(d,a,b,c)})});p("order.fixed()",function(a){if(!a){var b=this.context,b=b.length?b[0].aaSortingFixed:k;return h.isArray(b)?{pre:b}:b}return this.iterator("table",function(b){b.aaSortingFixed=h.extend(!0,{},a)})});p(["columns().order()","column().order()"],function(a){var b=this;return this.iterator("table",function(c,d){var e=[];h.each(b[d],function(b,c){e.push([c,a])});c.aaSorting=e})});p("search()",function(a,b,c,d){var e=
+this.context;return a===k?0!==e.length?e[0].oPreviousSearch.sSearch:k:this.iterator("table",function(e){e.oFeatures.bFilter&&ga(e,h.extend({},e.oPreviousSearch,{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===c?!0:c,bCaseInsensitive:null===d?!0:d}),1)})});u("columns().search()","column().search()",function(a,b,c,d){return this.iterator("column",function(e,f){var g=e.aoPreSearchCols;if(a===k)return g[f].sSearch;e.oFeatures.bFilter&&(h.extend(g[f],{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===c?
+!0:c,bCaseInsensitive:null===d?!0:d}),ga(e,e.oPreviousSearch,1))})});p("state()",function(){return this.context.length?this.context[0].oSavedState:null});p("state.clear()",function(){return this.iterator("table",function(a){a.fnStateSaveCallback.call(a.oInstance,a,{})})});p("state.loaded()",function(){return this.context.length?this.context[0].oLoadedState:null});p("state.save()",function(){return this.iterator("table",function(a){za(a)})});m.versionCheck=m.fnVersionCheck=function(a){for(var b=m.version.split("."),
+a=a.split("."),c,d,e=0,f=a.length;e<f;e++)if(c=parseInt(b[e],10)||0,d=parseInt(a[e],10)||0,c!==d)return c>d;return!0};m.isDataTable=m.fnIsDataTable=function(a){var b=h(a).get(0),c=!1;if(a instanceof m.Api)return!0;h.each(m.settings,function(a,e){var f=e.nScrollHead?h("table",e.nScrollHead)[0]:null,g=e.nScrollFoot?h("table",e.nScrollFoot)[0]:null;if(e.nTable===b||f===b||g===b)c=!0});return c};m.tables=m.fnTables=function(a){var b=!1;h.isPlainObject(a)&&(b=a.api,a=a.visible);var c=h.map(m.settings,
+function(b){if(!a||a&&h(b.nTable).is(":visible"))return b.nTable});return b?new t(c):c};m.camelToHungarian=J;p("$()",function(a,b){var c=this.rows(b).nodes(),c=h(c);return h([].concat(c.filter(a).toArray(),c.find(a).toArray()))});h.each(["on","one","off"],function(a,b){p(b+"()",function(){var a=Array.prototype.slice.call(arguments);a[0]=h.map(a[0].split(/\s/),function(a){return!a.match(/\.dt\b/)?a+".dt":a}).join(" ");var d=h(this.tables().nodes());d[b].apply(d,a);return this})});p("clear()",function(){return this.iterator("table",
+function(a){pa(a)})});p("settings()",function(){return new t(this.context,this.context)});p("init()",function(){var a=this.context;return a.length?a[0].oInit:null});p("data()",function(){return this.iterator("table",function(a){return D(a.aoData,"_aData")}).flatten()});p("destroy()",function(a){a=a||!1;return this.iterator("table",function(b){var c=b.nTableWrapper.parentNode,d=b.oClasses,e=b.nTable,f=b.nTBody,g=b.nTHead,j=b.nTFoot,i=h(e),f=h(f),k=h(b.nTableWrapper),l=h.map(b.aoData,function(a){return a.nTr}),
+p;b.bDestroying=!0;s(b,"aoDestroyCallback","destroy",[b]);a||(new t(b)).columns().visible(!0);k.off(".DT").find(":not(tbody *)").off(".DT");h(E).off(".DT-"+b.sInstance);e!=g.parentNode&&(i.children("thead").detach(),i.append(g));j&&e!=j.parentNode&&(i.children("tfoot").detach(),i.append(j));b.aaSorting=[];b.aaSortingFixed=[];ya(b);h(l).removeClass(b.asStripeClasses.join(" "));h("th, td",g).removeClass(d.sSortable+" "+d.sSortableAsc+" "+d.sSortableDesc+" "+d.sSortableNone);b.bJUI&&(h("th span."+d.sSortIcon+
+", td span."+d.sSortIcon,g).detach(),h("th, td",g).each(function(){var a=h("div."+d.sSortJUIWrapper,this);h(this).append(a.contents());a.detach()}));f.children().detach();f.append(l);g=a?"remove":"detach";i[g]();k[g]();!a&&c&&(c.insertBefore(e,b.nTableReinsertBefore),i.css("width",b.sDestroyWidth).removeClass(d.sTable),(p=b.asDestroyStripes.length)&&f.children().each(function(a){h(this).addClass(b.asDestroyStripes[a%p])}));c=h.inArray(b,m.settings);-1!==c&&m.settings.splice(c,1)})});h.each(["column",
+"row","cell"],function(a,b){p(b+"s().every()",function(a){var d=this.selector.opts,e=this;return this.iterator(b,function(f,g,h,i,m){a.call(e[b](g,"cell"===b?h:d,"cell"===b?d:k),g,h,i,m)})})});p("i18n()",function(a,b,c){var d=this.context[0],a=R(a)(d.oLanguage);a===k&&(a=b);c!==k&&h.isPlainObject(a)&&(a=a[c]!==k?a[c]:a._);return a.replace("%d",c)});m.version="1.10.15";m.settings=[];m.models={};m.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0};m.models.oRow={nTr:null,anCells:null,
+_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null,idx:-1};m.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null,sWidth:null,sWidthOrig:null};m.defaults=
+{aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bJQueryUI:!1,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,
+this.oLanguage.sThousands)},fnHeaderCallback:null,fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){}},fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1===a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+a.sInstance+
+"_"+location.pathname,JSON.stringify(b))}catch(c){}},fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",
+sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sDecimal:"",sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:h.extend({},m.models.oSearch),sAjaxDataProp:"data",sAjaxSource:null,sDom:"lfrtip",searchDelay:null,sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",renderer:null,rowId:"DT_RowId"};
+Y(m.defaults);m.defaults.column={aDataSort:null,iDataSort:-1,asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};Y(m.defaults.column);m.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortMulti:null,
+bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null,iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1,bBounding:!1,barWidth:0},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aIds:{},aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],
+aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,bAjaxDataGet:!0,jqXHR:null,json:k,oAjaxData:k,fnServerData:null,
+aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,bJUI:null,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==y(this)?1*this._iRecordsTotal:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==y(this)?1*this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var a=
+this._iDisplayLength,b=this._iDisplayStart,c=b+a,d=this.aiDisplay.length,e=this.oFeatures,f=e.bPaginate;return e.bServerSide?!1===f||-1===a?b+d:Math.min(b+a,this._iRecordsDisplay):!f||c>d||-1===a?d:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{},rowIdFn:null,rowId:null};m.ext=x={buttons:{},classes:{},builder:"-source-",errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},
+header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:m.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:m.version};h.extend(x,{afnFiltering:x.search,aTypes:x.type.detect,ofnSearch:x.type.search,oSort:x.type.order,afnSortData:x.order,aoFeatures:x.feature,oApi:x.internal,oStdClasses:x.classes,oPagination:x.pager});h.extend(m.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",
+sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",sSortableDesc:"sorting_desc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",
+sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var Ea="",Ea="",G=Ea+"ui-state-default",ka=Ea+"css_right ui-icon ui-icon-",Yb=Ea+"fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix";h.extend(m.ext.oJUIClasses,
+m.ext.classes,{sPageButton:"fg-button ui-button "+G,sPageButtonActive:"ui-state-disabled",sPageButtonDisabled:"ui-state-disabled",sPaging:"dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi ui-buttonset-multi paging_",sSortAsc:G+" sorting_asc",sSortDesc:G+" sorting_desc",sSortable:G+" sorting",sSortableAsc:G+" sorting_asc_disabled",sSortableDesc:G+" sorting_desc_disabled",sSortableNone:G+" sorting_disabled",sSortJUIAsc:ka+"triangle-1-n",sSortJUIDesc:ka+"triangle-1-s",sSortJUI:ka+"carat-2-n-s",
+sSortJUIAscAllowed:ka+"carat-1-n",sSortJUIDescAllowed:ka+"carat-1-s",sSortJUIWrapper:"DataTables_sort_wrapper",sSortIcon:"DataTables_sort_icon",sScrollHead:"dataTables_scrollHead "+G,sScrollFoot:"dataTables_scrollFoot "+G,sHeaderTH:G,sFooterTH:G,sJUIHeader:Yb+" ui-corner-tl ui-corner-tr",sJUIFooter:Yb+" ui-corner-bl ui-corner-br"});var Nb=m.ext.pager;h.extend(Nb,{simple:function(){return["previous","next"]},full:function(){return["first","previous","next","last"]},numbers:function(a,b){return[ia(a,
+b)]},simple_numbers:function(a,b){return["previous",ia(a,b),"next"]},full_numbers:function(a,b){return["first","previous",ia(a,b),"next","last"]},first_last_numbers:function(a,b){return["first",ia(a,b),"last"]},_numbers:ia,numbers_length:7});h.extend(!0,m.ext.renderer,{pageButton:{_:function(a,b,c,d,e,f){var g=a.oClasses,j=a.oLanguage.oPaginate,i=a.oLanguage.oAria.paginate||{},m,l,p=0,r=function(b,d){var k,t,u,s,v=function(b){Va(a,b.data.action,true)};k=0;for(t=d.length;k<t;k++){s=d[k];if(h.isArray(s)){u=
+h("<"+(s.DT_el||"div")+"/>").appendTo(b);r(u,s)}else{m=null;l="";switch(s){case "ellipsis":b.append('<span class="ellipsis">&#x2026;</span>');break;case "first":m=j.sFirst;l=s+(e>0?"":" "+g.sPageButtonDisabled);break;case "previous":m=j.sPrevious;l=s+(e>0?"":" "+g.sPageButtonDisabled);break;case "next":m=j.sNext;l=s+(e<f-1?"":" "+g.sPageButtonDisabled);break;case "last":m=j.sLast;l=s+(e<f-1?"":" "+g.sPageButtonDisabled);break;default:m=s+1;l=e===s?g.sPageButtonActive:""}if(m!==null){u=h("<a>",{"class":g.sPageButton+
+" "+l,"aria-controls":a.sTableId,"aria-label":i[s],"data-dt-idx":p,tabindex:a.iTabIndex,id:c===0&&typeof s==="string"?a.sTableId+"_"+s:null}).html(m).appendTo(b);Ya(u,{action:s},v);p++}}}},t;try{t=h(b).find(H.activeElement).data("dt-idx")}catch(u){}r(h(b).empty(),d);t!==k&&h(b).find("[data-dt-idx="+t+"]").focus()}}});h.extend(m.ext.type.detect,[function(a,b){var c=b.oLanguage.sDecimal;return ab(a,c)?"num"+c:null},function(a){if(a&&!(a instanceof Date)&&!cc.test(a))return null;var b=Date.parse(a);
+return null!==b&&!isNaN(b)||M(a)?"date":null},function(a,b){var c=b.oLanguage.sDecimal;return ab(a,c,!0)?"num-fmt"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Sb(a,c)?"html-num"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Sb(a,c,!0)?"html-num-fmt"+c:null},function(a){return M(a)||"string"===typeof a&&-1!==a.indexOf("<")?"html":null}]);h.extend(m.ext.type.search,{html:function(a){return M(a)?a:"string"===typeof a?a.replace(Pb," ").replace(Ca,""):""},string:function(a){return M(a)?
+a:"string"===typeof a?a.replace(Pb," "):a}});var Ba=function(a,b,c,d){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=Rb(a,b));a.replace&&(c&&(a=a.replace(c,"")),d&&(a=a.replace(d,"")));return 1*a};h.extend(x.type.order,{"date-pre":function(a){return Date.parse(a)||-Infinity},"html-pre":function(a){return M(a)?"":a.replace?a.replace(/<.*?>/g,"").toLowerCase():a+""},"string-pre":function(a){return M(a)?"":"string"===typeof a?a.toLowerCase():!a.toString?"":a.toString()},"string-asc":function(a,b){return a<
+b?-1:a>b?1:0},"string-desc":function(a,b){return a<b?1:a>b?-1:0}});fb("");h.extend(!0,m.ext.renderer,{header:{_:function(a,b,c,d){h(a.nTable).on("order.dt.DT",function(e,f,g,h){if(a===f){e=c.idx;b.removeClass(c.sSortingClass+" "+d.sSortAsc+" "+d.sSortDesc).addClass(h[e]=="asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass)}})},jqueryui:function(a,b,c,d){h("<div/>").addClass(d.sSortJUIWrapper).append(b.contents()).append(h("<span/>").addClass(d.sSortIcon+" "+c.sSortingClassJUI)).appendTo(b);
+h(a.nTable).on("order.dt.DT",function(e,f,g,h){if(a===f){e=c.idx;b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass(h[e]=="asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass);b.find("span."+d.sSortIcon).removeClass(d.sSortJUIAsc+" "+d.sSortJUIDesc+" "+d.sSortJUI+" "+d.sSortJUIAscAllowed+" "+d.sSortJUIDescAllowed).addClass(h[e]=="asc"?d.sSortJUIAsc:h[e]=="desc"?d.sSortJUIDesc:c.sSortingClassJUI)}})}}});var Zb=function(a){return"string"===typeof a?a.replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,
+"&quot;"):a};m.render={number:function(a,b,c,d,e){return{display:function(f){if("number"!==typeof f&&"string"!==typeof f)return f;var g=0>f?"-":"",h=parseFloat(f);if(isNaN(h))return Zb(f);h=h.toFixed(c);f=Math.abs(h);h=parseInt(f,10);f=c?b+(f-h).toFixed(c).substring(2):"";return g+(d||"")+h.toString().replace(/\B(?=(\d{3})+(?!\d))/g,a)+f+(e||"")}}},text:function(){return{display:Zb}}};h.extend(m.ext.internal,{_fnExternApiFunc:Ob,_fnBuildAjax:ua,_fnAjaxUpdate:nb,_fnAjaxParameters:wb,_fnAjaxUpdateDraw:xb,
+_fnAjaxDataSrc:va,_fnAddColumn:Ga,_fnColumnOptions:la,_fnAdjustColumnSizing:Z,_fnVisibleToColumnIndex:$,_fnColumnIndexToVisible:aa,_fnVisbleColumns:ba,_fnGetColumns:na,_fnColumnTypes:Ia,_fnApplyColumnDefs:kb,_fnHungarianMap:Y,_fnCamelToHungarian:J,_fnLanguageCompat:Fa,_fnBrowserDetect:ib,_fnAddData:N,_fnAddTr:oa,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==k?b._DT_RowIndex:null},_fnNodeToColumnIndex:function(a,b,c){return h.inArray(c,a.aoData[b].anCells)},_fnGetCellData:B,_fnSetCellData:lb,
+_fnSplitObjNotation:La,_fnGetObjectDataFn:R,_fnSetObjectDataFn:S,_fnGetDataMaster:Ma,_fnClearTable:pa,_fnDeleteIndex:qa,_fnInvalidate:da,_fnGetRowElements:Ka,_fnCreateTr:Ja,_fnBuildHead:mb,_fnDrawHead:fa,_fnDraw:O,_fnReDraw:T,_fnAddOptionsHtml:pb,_fnDetectHeader:ea,_fnGetUniqueThs:ta,_fnFeatureHtmlFilter:rb,_fnFilterComplete:ga,_fnFilterCustom:Ab,_fnFilterColumn:zb,_fnFilter:yb,_fnFilterCreateSearch:Ra,_fnEscapeRegex:Sa,_fnFilterData:Bb,_fnFeatureHtmlInfo:ub,_fnUpdateInfo:Eb,_fnInfoMacros:Fb,_fnInitialise:ha,
+_fnInitComplete:wa,_fnLengthChange:Ta,_fnFeatureHtmlLength:qb,_fnFeatureHtmlPaginate:vb,_fnPageChange:Va,_fnFeatureHtmlProcessing:sb,_fnProcessingDisplay:C,_fnFeatureHtmlTable:tb,_fnScrollDraw:ma,_fnApplyToChildren:I,_fnCalculateColumnWidths:Ha,_fnThrottle:Qa,_fnConvertToWidth:Gb,_fnGetWidestNode:Hb,_fnGetMaxLenString:Ib,_fnStringToCss:v,_fnSortFlatten:W,_fnSort:ob,_fnSortAria:Kb,_fnSortListener:Xa,_fnSortAttachListener:Oa,_fnSortingClasses:ya,_fnSortData:Jb,_fnSaveState:za,_fnLoadState:Lb,_fnSettingsFromNode:Aa,
+_fnLog:K,_fnMap:F,_fnBindAction:Ya,_fnCallbackReg:z,_fnCallbackFire:s,_fnLengthOverflow:Ua,_fnRenderer:Pa,_fnDataSource:y,_fnRowAttributes:Na,_fnCalculateEnd:function(){}});h.fn.dataTable=m;m.$=h;h.fn.dataTableSettings=m.settings;h.fn.dataTableExt=m.ext;h.fn.DataTable=function(a){return h(this).dataTable(a).api()};h.each(m,function(a,b){h.fn.DataTable[a]=b});return h.fn.dataTable});
diff --git a/www/wiki/vendor/onoi/shared-resources/res/jquery.highlight/jquery.highlight.js b/www/wiki/vendor/onoi/shared-resources/res/jquery.highlight/jquery.highlight.js
new file mode 100644
index 00000000..08e7d4f9
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/jquery.highlight/jquery.highlight.js
@@ -0,0 +1,151 @@
+/*
+ * jQuery Highlight plugin
+ *
+ * Based on highlight v3 by Johann Burkard
+ * http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html
+ *
+ * Code a little bit refactored and cleaned (in my humble opinion).
+ * Most important changes:
+ * - has an option to highlight only entire words (wordsOnly - false by default),
+ * - has an option to be case sensitive (caseSensitive - false by default)
+ * - highlight element tag and class names can be specified in options
+ *
+ * Usage:
+ * // wrap every occurrance of text 'lorem' in content
+ * // with <span class='highlight'> (default options)
+ * $('#content').highlight('lorem');
+ *
+ * // search for and highlight more terms at once
+ * // so you can save some time on traversing DOM
+ * $('#content').highlight(['lorem', 'ipsum']);
+ * $('#content').highlight('lorem ipsum');
+ *
+ * // search only for entire word 'lorem'
+ * $('#content').highlight('lorem', { wordsOnly: true });
+ *
+ * // don't ignore case during search of term 'lorem'
+ * $('#content').highlight('lorem', { caseSensitive: true });
+ *
+ * // wrap every occurrance of term 'ipsum' in content
+ * // with <em class='important'>
+ * $('#content').highlight('ipsum', { element: 'em', className: 'important' });
+ *
+ * // remove default highlight
+ * $('#content').unhighlight();
+ *
+ * // remove custom highlight
+ * $('#content').unhighlight({ element: 'em', className: 'important' });
+ *
+ *
+ * Copyright (c) 2009 Bartek Szopka
+ *
+ * Licensed under MIT license.
+ *
+ */
+
+/**
+ * Simple match and replace to normalize diacritics and accents
+ *
+ * @param {String} text
+ *
+ * @return {String}
+ */
+var normalizeAccents = function ( text ) {
+ return text
+ .replace(/[áÁàÀâÂäÄãÃåÅæÆâ]/g, 'a')
+ .replace(/[çÇ]/g, 'c')
+ .replace(/[éÉèÈêÊëËê]/g, 'e')
+ .replace(/[íÍìÌîÎïÏîĩĨĬĭ]/g, 'i')
+ .replace(/[ñÑ]/g, 'n')
+ .replace(/[óÓòÒôÔöÖœŒ]/g, 'o')
+ .replace(/[ß]/g, 's')
+ .replace(/[úÚùÙûÛüÜ]/g, 'u')
+ .replace(/[ýÝŷŶŸÿ]/g, 'n')
+ .replace( /έ/g, 'ε' )
+ .replace( /[ύϋΰ]/g, 'υ' )
+ .replace( /ό/g, 'ο' )
+ .replace( /ώ/g, 'ω' )
+ .replace( /ά/g, 'α' )
+ .replace( /[ίϊΐ]/g, 'ι' )
+ .replace( /ή/g, 'η' )
+ .replace( /\n/g, ' ' )
+ .replace( /á/g, 'a' )
+ .replace( /é/g, 'e' )
+ .replace( /í/g, 'i' )
+ .replace( /ó/g, 'o' )
+ .replace( /ú/g, 'u' )
+ .replace( /ê/g, 'e' )
+ .replace( /î/g, 'i' )
+ .replace( /ô/g, 'o' )
+ .replace( /è/g, 'e' )
+ .replace( /ï/g, 'i' )
+ .replace( /ü/g, 'u' )
+ .replace( /ã/g, 'a' )
+ .replace( /õ/g, 'o' )
+ .replace( /ç/g, 'c' )
+ .replace( /ì/g, 'i' );
+}
+
+jQuery.extend({
+ highlight: function (node, re, nodeName, className, accentInsensitive ) {
+ if (node.nodeType === 3) {
+ var data = accentInsensitive ? normalizeAccents( node.data ) : node.data;
+ var match = data.match(re);
+ if (match) {
+ var highlight = document.createElement(nodeName || 'span');
+ highlight.className = className || 'highlight';
+ var wordNode = node.splitText(match.index);
+ wordNode.splitText(match[0].length);
+ var wordClone = wordNode.cloneNode(true);
+ highlight.appendChild(wordClone);
+ wordNode.parentNode.replaceChild(highlight, wordNode);
+ return 1; //skip added node in parent
+ }
+ } else if ((node.nodeType === 1 && node.childNodes) && // only element nodes that have children
+ !/(script|style)/i.test(node.tagName) && // ignore script and style nodes
+ !(node.tagName === nodeName.toUpperCase() && node.className === className)) { // skip if already highlighted
+ for (var i = 0; i < node.childNodes.length; i++) {
+ i += jQuery.highlight(node.childNodes[i], re, nodeName, className, accentInsensitive);
+ }
+ }
+ return 0;
+ }
+});
+
+jQuery.fn.unhighlight = function (options) {
+ var settings = { className: 'highlight', element: 'span' };
+ jQuery.extend(settings, options);
+
+ return this.find(settings.element + "." + settings.className).each(function () {
+ var parent = this.parentNode;
+ parent.replaceChild(this.firstChild, this);
+ parent.normalize();
+ }).end();
+};
+
+jQuery.fn.highlight = function (words, options) {
+ var settings = { className: 'highlight', element: 'span', caseSensitive: false, wordsOnly: false, accentInsensitive: true };
+ jQuery.extend(settings, options);
+
+ if (words.constructor === String) {
+ words = [words];
+ }
+ words = jQuery.grep(words, function(word, i){
+ return word != '';
+ });
+ words = jQuery.map(words, function(word, i) {
+ return word.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
+ });
+ if (words.length == 0) { return this; };
+
+ var flag = settings.caseSensitive ? "" : "i";
+ var pattern = "(" + words.join("|") + ")";
+ if (settings.wordsOnly) {
+ pattern = "\\b" + pattern + "\\b";
+ }
+ var re = new RegExp(pattern, flag);
+
+ return this.each(function () {
+ jQuery.highlight(this, re, settings.element, settings.className, settings.accentInsensitive );
+ });
+};
diff --git a/www/wiki/vendor/onoi/shared-resources/res/jquery.qtip/core/jquery.qtip.css b/www/wiki/vendor/onoi/shared-resources/res/jquery.qtip/core/jquery.qtip.css
new file mode 100644
index 00000000..fbc25c23
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/jquery.qtip/core/jquery.qtip.css
@@ -0,0 +1,124 @@
+/*
+ * qTip2 - Pretty powerful tooltips - v3.0.3
+ * http://qtip2.com
+ *
+ * Copyright (c) 2016
+ * Released under the MIT licenses
+ * http://jquery.org/license
+ *
+ * Date: Wed May 11 2016 06:50 EDT-0400
+ * Plugins: None
+ * Styles: core
+ */
+.qtip{
+ position: absolute;
+ left: -28000px;
+ top: -28000px;
+ display: none;
+
+ max-width: 280px;
+ min-width: 50px;
+
+ font-size: 10.5px;
+ line-height: 12px;
+
+ direction: ltr;
+
+ box-shadow: none;
+ padding: 0;
+}
+
+ .qtip-content{
+ position: relative;
+ padding: 5px 9px;
+ overflow: hidden;
+
+ text-align: left;
+ word-wrap: break-word;
+ }
+
+ .qtip-titlebar{
+ position: relative;
+ padding: 5px 35px 5px 10px;
+ overflow: hidden;
+
+ border-width: 0 0 1px;
+ font-weight: bold;
+ }
+
+ .qtip-titlebar + .qtip-content{ border-top-width: 0 !important; }
+
+ /* Default close button class */
+ .qtip-close{
+ position: absolute;
+ right: -9px; top: -9px;
+ z-index: 11; /* Overlap .qtip-tip */
+
+ cursor: pointer;
+ outline: medium none;
+
+ border: 1px solid transparent;
+ }
+
+ .qtip-titlebar .qtip-close{
+ right: 4px; top: 50%;
+ margin-top: -9px;
+ }
+
+ * html .qtip-titlebar .qtip-close{ top: 16px; } /* IE fix */
+
+ .qtip-titlebar .ui-icon,
+ .qtip-icon .ui-icon{
+ display: block;
+ text-indent: -1000em;
+ direction: ltr;
+ }
+
+ .qtip-icon, .qtip-icon .ui-icon{
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+ text-decoration: none;
+ }
+
+ .qtip-icon .ui-icon{
+ width: 18px;
+ height: 14px;
+
+ line-height: 14px;
+ text-align: center;
+ text-indent: 0;
+ font: normal bold 10px/13px Tahoma,sans-serif;
+
+ color: inherit;
+ background: transparent none no-repeat -100em -100em;
+ }
+
+/* Applied to 'focused' tooltips e.g. most recently displayed/interacted with */
+.qtip-focus{}
+
+/* Applied on hover of tooltips i.e. added/removed on mouseenter/mouseleave respectively */
+.qtip-hover{}
+
+/* Default tooltip style */
+.qtip-default{
+ border: 1px solid #F1D031;
+
+ background-color: #FFFFA3;
+ color: #555;
+}
+
+ .qtip-default .qtip-titlebar{
+ background-color: #FFEF93;
+ }
+
+ .qtip-default .qtip-icon{
+ border-color: #CCC;
+ background: #F1F1F1;
+ color: #777;
+ }
+
+ .qtip-default .qtip-titlebar .qtip-close{
+ border-color: #AAA;
+ color: #111;
+ }
diff --git a/www/wiki/vendor/onoi/shared-resources/res/jquery.qtip/core/jquery.qtip.js b/www/wiki/vendor/onoi/shared-resources/res/jquery.qtip/core/jquery.qtip.js
new file mode 100644
index 00000000..909c066b
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/jquery.qtip/core/jquery.qtip.js
@@ -0,0 +1,2016 @@
+/*
+ * qTip2 - Pretty powerful tooltips - v3.0.3
+ * http://qtip2.com
+ *
+ * Copyright (c) 2016
+ * Released under the MIT licenses
+ * http://jquery.org/license
+ *
+ * Date: Wed May 11 2016 06:50 EDT-0400
+ * Plugins: None
+ * Styles: core
+ */
+/*global window: false, jQuery: false, console: false, define: false */
+
+/* Cache window, document, undefined */
+(function( window, document, undefined ) {
+
+// Uses AMD or browser globals to create a jQuery plugin.
+(function( factory ) {
+ "use strict";
+ if(typeof define === 'function' && define.amd) {
+ define(['jquery'], factory);
+ }
+ else if(jQuery && !jQuery.fn.qtip) {
+ factory(jQuery);
+ }
+}
+(function($) {
+ "use strict"; // Enable ECMAScript "strict" operation for this function. See more: http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
+;// Munge the primitives - Paul Irish tip
+var TRUE = true,
+FALSE = false,
+NULL = null,
+
+// Common variables
+X = 'x', Y = 'y',
+WIDTH = 'width',
+HEIGHT = 'height',
+
+// Positioning sides
+TOP = 'top',
+LEFT = 'left',
+BOTTOM = 'bottom',
+RIGHT = 'right',
+CENTER = 'center',
+
+// Position adjustment types
+FLIP = 'flip',
+FLIPINVERT = 'flipinvert',
+SHIFT = 'shift',
+
+// Shortcut vars
+QTIP, PROTOTYPE, CORNER, CHECKS,
+PLUGINS = {},
+NAMESPACE = 'qtip',
+ATTR_HAS = 'data-hasqtip',
+ATTR_ID = 'data-qtip-id',
+WIDGET = ['ui-widget', 'ui-tooltip'],
+SELECTOR = '.'+NAMESPACE,
+INACTIVE_EVENTS = 'click dblclick mousedown mouseup mousemove mouseleave mouseenter'.split(' '),
+
+CLASS_FIXED = NAMESPACE+'-fixed',
+CLASS_DEFAULT = NAMESPACE + '-default',
+CLASS_FOCUS = NAMESPACE + '-focus',
+CLASS_HOVER = NAMESPACE + '-hover',
+CLASS_DISABLED = NAMESPACE+'-disabled',
+
+replaceSuffix = '_replacedByqTip',
+oldtitle = 'oldtitle',
+trackingBound,
+
+// Browser detection
+BROWSER = {
+ /*
+ * IE version detection
+ *
+ * Adapted from: http://ajaxian.com/archives/attack-of-the-ie-conditional-comment
+ * Credit to James Padolsey for the original implemntation!
+ */
+ ie: (function() {
+ /* eslint-disable no-empty */
+ var v, i;
+ for (
+ v = 4, i = document.createElement('div');
+ (i.innerHTML = '<!--[if gt IE ' + v + ']><i></i><![endif]-->') && i.getElementsByTagName('i')[0];
+ v+=1
+ ) {}
+ return v > 4 ? v : NaN;
+ /* eslint-enable no-empty */
+ })(),
+
+ /*
+ * iOS version detection
+ */
+ iOS: parseFloat(
+ ('' + (/CPU.*OS ([0-9_]{1,5})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0,''])[1])
+ .replace('undefined', '3_2').replace('_', '.').replace('_', '')
+ ) || FALSE
+};
+;function QTip(target, options, id, attr) {
+ // Elements and ID
+ this.id = id;
+ this.target = target;
+ this.tooltip = NULL;
+ this.elements = { target: target };
+
+ // Internal constructs
+ this._id = NAMESPACE + '-' + id;
+ this.timers = { img: {} };
+ this.options = options;
+ this.plugins = {};
+
+ // Cache object
+ this.cache = {
+ event: {},
+ target: $(),
+ disabled: FALSE,
+ attr: attr,
+ onTooltip: FALSE,
+ lastClass: ''
+ };
+
+ // Set the initial flags
+ this.rendered = this.destroyed = this.disabled = this.waiting =
+ this.hiddenDuringWait = this.positioning = this.triggering = FALSE;
+}
+PROTOTYPE = QTip.prototype;
+
+PROTOTYPE._when = function(deferreds) {
+ return $.when.apply($, deferreds);
+};
+
+PROTOTYPE.render = function(show) {
+ if(this.rendered || this.destroyed) { return this; } // If tooltip has already been rendered, exit
+
+ var self = this,
+ options = this.options,
+ cache = this.cache,
+ elements = this.elements,
+ text = options.content.text,
+ title = options.content.title,
+ button = options.content.button,
+ posOptions = options.position,
+ deferreds = [];
+
+ // Add ARIA attributes to target
+ $.attr(this.target[0], 'aria-describedby', this._id);
+
+ // Create public position object that tracks current position corners
+ cache.posClass = this._createPosClass(
+ (this.position = { my: posOptions.my, at: posOptions.at }).my
+ );
+
+ // Create tooltip element
+ this.tooltip = elements.tooltip = $('<div/>', {
+ 'id': this._id,
+ 'class': [ NAMESPACE, CLASS_DEFAULT, options.style.classes, cache.posClass ].join(' '),
+ 'width': options.style.width || '',
+ 'height': options.style.height || '',
+ 'tracking': posOptions.target === 'mouse' && posOptions.adjust.mouse,
+
+ /* ARIA specific attributes */
+ 'role': 'alert',
+ 'aria-live': 'polite',
+ 'aria-atomic': FALSE,
+ 'aria-describedby': this._id + '-content',
+ 'aria-hidden': TRUE
+ })
+ .toggleClass(CLASS_DISABLED, this.disabled)
+ .attr(ATTR_ID, this.id)
+ .data(NAMESPACE, this)
+ .appendTo(posOptions.container)
+ .append(
+ // Create content element
+ elements.content = $('<div />', {
+ 'class': NAMESPACE + '-content',
+ 'id': this._id + '-content',
+ 'aria-atomic': TRUE
+ })
+ );
+
+ // Set rendered flag and prevent redundant reposition calls for now
+ this.rendered = -1;
+ this.positioning = TRUE;
+
+ // Create title...
+ if(title) {
+ this._createTitle();
+
+ // Update title only if its not a callback (called in toggle if so)
+ if(!$.isFunction(title)) {
+ deferreds.push( this._updateTitle(title, FALSE) );
+ }
+ }
+
+ // Create button
+ if(button) { this._createButton(); }
+
+ // Set proper rendered flag and update content if not a callback function (called in toggle)
+ if(!$.isFunction(text)) {
+ deferreds.push( this._updateContent(text, FALSE) );
+ }
+ this.rendered = TRUE;
+
+ // Setup widget classes
+ this._setWidget();
+
+ // Initialize 'render' plugins
+ $.each(PLUGINS, function(name) {
+ var instance;
+ if(this.initialize === 'render' && (instance = this(self))) {
+ self.plugins[name] = instance;
+ }
+ });
+
+ // Unassign initial events and assign proper events
+ this._unassignEvents();
+ this._assignEvents();
+
+ // When deferreds have completed
+ this._when(deferreds).then(function() {
+ // tooltiprender event
+ self._trigger('render');
+
+ // Reset flags
+ self.positioning = FALSE;
+
+ // Show tooltip if not hidden during wait period
+ if(!self.hiddenDuringWait && (options.show.ready || show)) {
+ self.toggle(TRUE, cache.event, FALSE);
+ }
+ self.hiddenDuringWait = FALSE;
+ });
+
+ // Expose API
+ QTIP.api[this.id] = this;
+
+ return this;
+};
+
+PROTOTYPE.destroy = function(immediate) {
+ // Set flag the signify destroy is taking place to plugins
+ // and ensure it only gets destroyed once!
+ if(this.destroyed) { return this.target; }
+
+ function process() {
+ if(this.destroyed) { return; }
+ this.destroyed = TRUE;
+
+ var target = this.target,
+ title = target.attr(oldtitle),
+ timer;
+
+ // Destroy tooltip if rendered
+ if(this.rendered) {
+ this.tooltip.stop(1,0).find('*').remove().end().remove();
+ }
+
+ // Destroy all plugins
+ $.each(this.plugins, function() {
+ this.destroy && this.destroy();
+ });
+
+ // Clear timers
+ for (timer in this.timers) {
+ if (this.timers.hasOwnProperty(timer)) {
+ clearTimeout(this.timers[timer]);
+ }
+ }
+
+ // Remove api object and ARIA attributes
+ target.removeData(NAMESPACE)
+ .removeAttr(ATTR_ID)
+ .removeAttr(ATTR_HAS)
+ .removeAttr('aria-describedby');
+
+ // Reset old title attribute if removed
+ if(this.options.suppress && title) {
+ target.attr('title', title).removeAttr(oldtitle);
+ }
+
+ // Remove qTip events associated with this API
+ this._unassignEvents();
+
+ // Remove ID from used id objects, and delete object references
+ // for better garbage collection and leak protection
+ this.options = this.elements = this.cache = this.timers =
+ this.plugins = this.mouse = NULL;
+
+ // Delete epoxsed API object
+ delete QTIP.api[this.id];
+ }
+
+ // If an immediate destroy is needed
+ if((immediate !== TRUE || this.triggering === 'hide') && this.rendered) {
+ this.tooltip.one('tooltiphidden', $.proxy(process, this));
+ !this.triggering && this.hide();
+ }
+
+ // If we're not in the process of hiding... process
+ else { process.call(this); }
+
+ return this.target;
+};
+;function invalidOpt(a) {
+ return a === NULL || $.type(a) !== 'object';
+}
+
+function invalidContent(c) {
+ return !($.isFunction(c) ||
+ c && c.attr ||
+ c.length ||
+ $.type(c) === 'object' && (c.jquery || c.then));
+}
+
+// Option object sanitizer
+function sanitizeOptions(opts) {
+ var content, text, ajax, once;
+
+ if(invalidOpt(opts)) { return FALSE; }
+
+ if(invalidOpt(opts.metadata)) {
+ opts.metadata = { type: opts.metadata };
+ }
+
+ if('content' in opts) {
+ content = opts.content;
+
+ if(invalidOpt(content) || content.jquery || content.done) {
+ text = invalidContent(content) ? FALSE : content;
+ content = opts.content = {
+ text: text
+ };
+ }
+ else { text = content.text; }
+
+ // DEPRECATED - Old content.ajax plugin functionality
+ // Converts it into the proper Deferred syntax
+ if('ajax' in content) {
+ ajax = content.ajax;
+ once = ajax && ajax.once !== FALSE;
+ delete content.ajax;
+
+ content.text = function(event, api) {
+ var loading = text || $(this).attr(api.options.content.attr) || 'Loading...',
+
+ deferred = $.ajax(
+ $.extend({}, ajax, { context: api })
+ )
+ .then(ajax.success, NULL, ajax.error)
+ .then(function(newContent) {
+ if(newContent && once) { api.set('content.text', newContent); }
+ return newContent;
+ },
+ function(xhr, status, error) {
+ if(api.destroyed || xhr.status === 0) { return; }
+ api.set('content.text', status + ': ' + error);
+ });
+
+ return !once ? (api.set('content.text', loading), deferred) : loading;
+ };
+ }
+
+ if('title' in content) {
+ if($.isPlainObject(content.title)) {
+ content.button = content.title.button;
+ content.title = content.title.text;
+ }
+
+ if(invalidContent(content.title || FALSE)) {
+ content.title = FALSE;
+ }
+ }
+ }
+
+ if('position' in opts && invalidOpt(opts.position)) {
+ opts.position = { my: opts.position, at: opts.position };
+ }
+
+ if('show' in opts && invalidOpt(opts.show)) {
+ opts.show = opts.show.jquery ? { target: opts.show } :
+ opts.show === TRUE ? { ready: TRUE } : { event: opts.show };
+ }
+
+ if('hide' in opts && invalidOpt(opts.hide)) {
+ opts.hide = opts.hide.jquery ? { target: opts.hide } : { event: opts.hide };
+ }
+
+ if('style' in opts && invalidOpt(opts.style)) {
+ opts.style = { classes: opts.style };
+ }
+
+ // Sanitize plugin options
+ $.each(PLUGINS, function() {
+ this.sanitize && this.sanitize(opts);
+ });
+
+ return opts;
+}
+
+// Setup builtin .set() option checks
+CHECKS = PROTOTYPE.checks = {
+ builtin: {
+ // Core checks
+ '^id$': function(obj, o, v, prev) {
+ var id = v === TRUE ? QTIP.nextid : v,
+ newId = NAMESPACE + '-' + id;
+
+ if(id !== FALSE && id.length > 0 && !$('#'+newId).length) {
+ this._id = newId;
+
+ if(this.rendered) {
+ this.tooltip[0].id = this._id;
+ this.elements.content[0].id = this._id + '-content';
+ this.elements.title[0].id = this._id + '-title';
+ }
+ }
+ else { obj[o] = prev; }
+ },
+ '^prerender': function(obj, o, v) {
+ v && !this.rendered && this.render(this.options.show.ready);
+ },
+
+ // Content checks
+ '^content.text$': function(obj, o, v) {
+ this._updateContent(v);
+ },
+ '^content.attr$': function(obj, o, v, prev) {
+ if(this.options.content.text === this.target.attr(prev)) {
+ this._updateContent( this.target.attr(v) );
+ }
+ },
+ '^content.title$': function(obj, o, v) {
+ // Remove title if content is null
+ if(!v) { return this._removeTitle(); }
+
+ // If title isn't already created, create it now and update
+ v && !this.elements.title && this._createTitle();
+ this._updateTitle(v);
+ },
+ '^content.button$': function(obj, o, v) {
+ this._updateButton(v);
+ },
+ '^content.title.(text|button)$': function(obj, o, v) {
+ this.set('content.'+o, v); // Backwards title.text/button compat
+ },
+
+ // Position checks
+ '^position.(my|at)$': function(obj, o, v){
+ if('string' === typeof v) {
+ this.position[o] = obj[o] = new CORNER(v, o === 'at');
+ }
+ },
+ '^position.container$': function(obj, o, v){
+ this.rendered && this.tooltip.appendTo(v);
+ },
+
+ // Show checks
+ '^show.ready$': function(obj, o, v) {
+ v && (!this.rendered && this.render(TRUE) || this.toggle(TRUE));
+ },
+
+ // Style checks
+ '^style.classes$': function(obj, o, v, p) {
+ this.rendered && this.tooltip.removeClass(p).addClass(v);
+ },
+ '^style.(width|height)': function(obj, o, v) {
+ this.rendered && this.tooltip.css(o, v);
+ },
+ '^style.widget|content.title': function() {
+ this.rendered && this._setWidget();
+ },
+ '^style.def': function(obj, o, v) {
+ this.rendered && this.tooltip.toggleClass(CLASS_DEFAULT, !!v);
+ },
+
+ // Events check
+ '^events.(render|show|move|hide|focus|blur)$': function(obj, o, v) {
+ this.rendered && this.tooltip[($.isFunction(v) ? '' : 'un') + 'bind']('tooltip'+o, v);
+ },
+
+ // Properties which require event reassignment
+ '^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)': function() {
+ if(!this.rendered) { return; }
+
+ // Set tracking flag
+ var posOptions = this.options.position;
+ this.tooltip.attr('tracking', posOptions.target === 'mouse' && posOptions.adjust.mouse);
+
+ // Reassign events
+ this._unassignEvents();
+ this._assignEvents();
+ }
+ }
+};
+
+// Dot notation converter
+function convertNotation(options, notation) {
+ var i = 0, obj, option = options,
+
+ // Split notation into array
+ levels = notation.split('.');
+
+ // Loop through
+ while(option = option[ levels[i++] ]) {
+ if(i < levels.length) { obj = option; }
+ }
+
+ return [obj || options, levels.pop()];
+}
+
+PROTOTYPE.get = function(notation) {
+ if(this.destroyed) { return this; }
+
+ var o = convertNotation(this.options, notation.toLowerCase()),
+ result = o[0][ o[1] ];
+
+ return result.precedance ? result.string() : result;
+};
+
+function setCallback(notation, args) {
+ var category, rule, match;
+
+ for(category in this.checks) {
+ if (!this.checks.hasOwnProperty(category)) { continue; }
+
+ for(rule in this.checks[category]) {
+ if (!this.checks[category].hasOwnProperty(rule)) { continue; }
+
+ if(match = (new RegExp(rule, 'i')).exec(notation)) {
+ args.push(match);
+
+ if(category === 'builtin' || this.plugins[category]) {
+ this.checks[category][rule].apply(
+ this.plugins[category] || this, args
+ );
+ }
+ }
+ }
+ }
+}
+
+var rmove = /^position\.(my|at|adjust|target|container|viewport)|style|content|show\.ready/i,
+ rrender = /^prerender|show\.ready/i;
+
+PROTOTYPE.set = function(option, value) {
+ if(this.destroyed) { return this; }
+
+ var rendered = this.rendered,
+ reposition = FALSE,
+ options = this.options,
+ name;
+
+ // Convert singular option/value pair into object form
+ if('string' === typeof option) {
+ name = option; option = {}; option[name] = value;
+ }
+ else { option = $.extend({}, option); }
+
+ // Set all of the defined options to their new values
+ $.each(option, function(notation, val) {
+ if(rendered && rrender.test(notation)) {
+ delete option[notation]; return;
+ }
+
+ // Set new obj value
+ var obj = convertNotation(options, notation.toLowerCase()), previous;
+ previous = obj[0][ obj[1] ];
+ obj[0][ obj[1] ] = val && val.nodeType ? $(val) : val;
+
+ // Also check if we need to reposition
+ reposition = rmove.test(notation) || reposition;
+
+ // Set the new params for the callback
+ option[notation] = [obj[0], obj[1], val, previous];
+ });
+
+ // Re-sanitize options
+ sanitizeOptions(options);
+
+ /*
+ * Execute any valid callbacks for the set options
+ * Also set positioning flag so we don't get loads of redundant repositioning calls.
+ */
+ this.positioning = TRUE;
+ $.each(option, $.proxy(setCallback, this));
+ this.positioning = FALSE;
+
+ // Update position if needed
+ if(this.rendered && this.tooltip[0].offsetWidth > 0 && reposition) {
+ this.reposition( options.position.target === 'mouse' ? NULL : this.cache.event );
+ }
+
+ return this;
+};
+;PROTOTYPE._update = function(content, element) {
+ var self = this,
+ cache = this.cache;
+
+ // Make sure tooltip is rendered and content is defined. If not return
+ if(!this.rendered || !content) { return FALSE; }
+
+ // Use function to parse content
+ if($.isFunction(content)) {
+ content = content.call(this.elements.target, cache.event, this) || '';
+ }
+
+ // Handle deferred content
+ if($.isFunction(content.then)) {
+ cache.waiting = TRUE;
+ return content.then(function(c) {
+ cache.waiting = FALSE;
+ return self._update(c, element);
+ }, NULL, function(e) {
+ return self._update(e, element);
+ });
+ }
+
+ // If content is null... return false
+ if(content === FALSE || !content && content !== '') { return FALSE; }
+
+ // Append new content if its a DOM array and show it if hidden
+ if(content.jquery && content.length > 0) {
+ element.empty().append(
+ content.css({ display: 'block', visibility: 'visible' })
+ );
+ }
+
+ // Content is a regular string, insert the new content
+ else { element.html(content); }
+
+ // Wait for content to be loaded, and reposition
+ return this._waitForContent(element).then(function(images) {
+ if(self.rendered && self.tooltip[0].offsetWidth > 0) {
+ self.reposition(cache.event, !images.length);
+ }
+ });
+};
+
+PROTOTYPE._waitForContent = function(element) {
+ var cache = this.cache;
+
+ // Set flag
+ cache.waiting = TRUE;
+
+ // If imagesLoaded is included, ensure images have loaded and return promise
+ return ( $.fn.imagesLoaded ? element.imagesLoaded() : new $.Deferred().resolve([]) )
+ .done(function() { cache.waiting = FALSE; })
+ .promise();
+};
+
+PROTOTYPE._updateContent = function(content, reposition) {
+ this._update(content, this.elements.content, reposition);
+};
+
+PROTOTYPE._updateTitle = function(content, reposition) {
+ if(this._update(content, this.elements.title, reposition) === FALSE) {
+ this._removeTitle(FALSE);
+ }
+};
+
+PROTOTYPE._createTitle = function()
+{
+ var elements = this.elements,
+ id = this._id+'-title';
+
+ // Destroy previous title element, if present
+ if(elements.titlebar) { this._removeTitle(); }
+
+ // Create title bar and title elements
+ elements.titlebar = $('<div />', {
+ 'class': NAMESPACE + '-titlebar ' + (this.options.style.widget ? createWidgetClass('header') : '')
+ })
+ .append(
+ elements.title = $('<div />', {
+ 'id': id,
+ 'class': NAMESPACE + '-title',
+ 'aria-atomic': TRUE
+ })
+ )
+ .insertBefore(elements.content)
+
+ // Button-specific events
+ .delegate('.qtip-close', 'mousedown keydown mouseup keyup mouseout', function(event) {
+ $(this).toggleClass('ui-state-active ui-state-focus', event.type.substr(-4) === 'down');
+ })
+ .delegate('.qtip-close', 'mouseover mouseout', function(event){
+ $(this).toggleClass('ui-state-hover', event.type === 'mouseover');
+ });
+
+ // Create button if enabled
+ if(this.options.content.button) { this._createButton(); }
+};
+
+PROTOTYPE._removeTitle = function(reposition)
+{
+ var elements = this.elements;
+
+ if(elements.title) {
+ elements.titlebar.remove();
+ elements.titlebar = elements.title = elements.button = NULL;
+
+ // Reposition if enabled
+ if(reposition !== FALSE) { this.reposition(); }
+ }
+};
+;PROTOTYPE._createPosClass = function(my) {
+ return NAMESPACE + '-pos-' + (my || this.options.position.my).abbrev();
+};
+
+PROTOTYPE.reposition = function(event, effect) {
+ if(!this.rendered || this.positioning || this.destroyed) { return this; }
+
+ // Set positioning flag
+ this.positioning = TRUE;
+
+ var cache = this.cache,
+ tooltip = this.tooltip,
+ posOptions = this.options.position,
+ target = posOptions.target,
+ my = posOptions.my,
+ at = posOptions.at,
+ viewport = posOptions.viewport,
+ container = posOptions.container,
+ adjust = posOptions.adjust,
+ method = adjust.method.split(' '),
+ tooltipWidth = tooltip.outerWidth(FALSE),
+ tooltipHeight = tooltip.outerHeight(FALSE),
+ targetWidth = 0,
+ targetHeight = 0,
+ type = tooltip.css('position'),
+ position = { left: 0, top: 0 },
+ visible = tooltip[0].offsetWidth > 0,
+ isScroll = event && event.type === 'scroll',
+ win = $(window),
+ doc = container[0].ownerDocument,
+ mouse = this.mouse,
+ pluginCalculations, offset, adjusted, newClass;
+
+ // Check if absolute position was passed
+ if($.isArray(target) && target.length === 2) {
+ // Force left top and set position
+ at = { x: LEFT, y: TOP };
+ position = { left: target[0], top: target[1] };
+ }
+
+ // Check if mouse was the target
+ else if(target === 'mouse') {
+ // Force left top to allow flipping
+ at = { x: LEFT, y: TOP };
+
+ // Use the mouse origin that caused the show event, if distance hiding is enabled
+ if((!adjust.mouse || this.options.hide.distance) && cache.origin && cache.origin.pageX) {
+ event = cache.origin;
+ }
+
+ // Use cached event for resize/scroll events
+ else if(!event || event && (event.type === 'resize' || event.type === 'scroll')) {
+ event = cache.event;
+ }
+
+ // Otherwise, use the cached mouse coordinates if available
+ else if(mouse && mouse.pageX) {
+ event = mouse;
+ }
+
+ // Calculate body and container offset and take them into account below
+ if(type !== 'static') { position = container.offset(); }
+ if(doc.body.offsetWidth !== (window.innerWidth || doc.documentElement.clientWidth)) {
+ offset = $(document.body).offset();
+ }
+
+ // Use event coordinates for position
+ position = {
+ left: event.pageX - position.left + (offset && offset.left || 0),
+ top: event.pageY - position.top + (offset && offset.top || 0)
+ };
+
+ // Scroll events are a pain, some browsers
+ if(adjust.mouse && isScroll && mouse) {
+ position.left -= (mouse.scrollX || 0) - win.scrollLeft();
+ position.top -= (mouse.scrollY || 0) - win.scrollTop();
+ }
+ }
+
+ // Target wasn't mouse or absolute...
+ else {
+ // Check if event targetting is being used
+ if(target === 'event') {
+ if(event && event.target && event.type !== 'scroll' && event.type !== 'resize') {
+ cache.target = $(event.target);
+ }
+ else if(!event.target) {
+ cache.target = this.elements.target;
+ }
+ }
+ else if(target !== 'event'){
+ cache.target = $(target.jquery ? target : this.elements.target);
+ }
+ target = cache.target;
+
+ // Parse the target into a jQuery object and make sure there's an element present
+ target = $(target).eq(0);
+ if(target.length === 0) { return this; }
+
+ // Check if window or document is the target
+ else if(target[0] === document || target[0] === window) {
+ targetWidth = BROWSER.iOS ? window.innerWidth : target.width();
+ targetHeight = BROWSER.iOS ? window.innerHeight : target.height();
+
+ if(target[0] === window) {
+ position = {
+ top: (viewport || target).scrollTop(),
+ left: (viewport || target).scrollLeft()
+ };
+ }
+ }
+
+ // Check if the target is an <AREA> element
+ else if(PLUGINS.imagemap && target.is('area')) {
+ pluginCalculations = PLUGINS.imagemap(this, target, at, PLUGINS.viewport ? method : FALSE);
+ }
+
+ // Check if the target is an SVG element
+ else if(PLUGINS.svg && target && target[0].ownerSVGElement) {
+ pluginCalculations = PLUGINS.svg(this, target, at, PLUGINS.viewport ? method : FALSE);
+ }
+
+ // Otherwise use regular jQuery methods
+ else {
+ targetWidth = target.outerWidth(FALSE);
+ targetHeight = target.outerHeight(FALSE);
+ position = target.offset();
+ }
+
+ // Parse returned plugin values into proper variables
+ if(pluginCalculations) {
+ targetWidth = pluginCalculations.width;
+ targetHeight = pluginCalculations.height;
+ offset = pluginCalculations.offset;
+ position = pluginCalculations.position;
+ }
+
+ // Adjust position to take into account offset parents
+ position = this.reposition.offset(target, position, container);
+
+ // Adjust for position.fixed tooltips (and also iOS scroll bug in v3.2-4.0 & v4.3-4.3.2)
+ if(BROWSER.iOS > 3.1 && BROWSER.iOS < 4.1 ||
+ BROWSER.iOS >= 4.3 && BROWSER.iOS < 4.33 ||
+ !BROWSER.iOS && type === 'fixed'
+ ){
+ position.left -= win.scrollLeft();
+ position.top -= win.scrollTop();
+ }
+
+ // Adjust position relative to target
+ if(!pluginCalculations || pluginCalculations && pluginCalculations.adjustable !== FALSE) {
+ position.left += at.x === RIGHT ? targetWidth : at.x === CENTER ? targetWidth / 2 : 0;
+ position.top += at.y === BOTTOM ? targetHeight : at.y === CENTER ? targetHeight / 2 : 0;
+ }
+ }
+
+ // Adjust position relative to tooltip
+ position.left += adjust.x + (my.x === RIGHT ? -tooltipWidth : my.x === CENTER ? -tooltipWidth / 2 : 0);
+ position.top += adjust.y + (my.y === BOTTOM ? -tooltipHeight : my.y === CENTER ? -tooltipHeight / 2 : 0);
+
+ // Use viewport adjustment plugin if enabled
+ if(PLUGINS.viewport) {
+ adjusted = position.adjusted = PLUGINS.viewport(
+ this, position, posOptions, targetWidth, targetHeight, tooltipWidth, tooltipHeight
+ );
+
+ // Apply offsets supplied by positioning plugin (if used)
+ if(offset && adjusted.left) { position.left += offset.left; }
+ if(offset && adjusted.top) { position.top += offset.top; }
+
+ // Apply any new 'my' position
+ if(adjusted.my) { this.position.my = adjusted.my; }
+ }
+
+ // Viewport adjustment is disabled, set values to zero
+ else { position.adjusted = { left: 0, top: 0 }; }
+
+ // Set tooltip position class if it's changed
+ if(cache.posClass !== (newClass = this._createPosClass(this.position.my))) {
+ cache.posClass = newClass;
+ tooltip.removeClass(cache.posClass).addClass(newClass);
+ }
+
+ // tooltipmove event
+ if(!this._trigger('move', [position, viewport.elem || viewport], event)) { return this; }
+ delete position.adjusted;
+
+ // If effect is disabled, target it mouse, no animation is defined or positioning gives NaN out, set CSS directly
+ if(effect === FALSE || !visible || isNaN(position.left) || isNaN(position.top) || target === 'mouse' || !$.isFunction(posOptions.effect)) {
+ tooltip.css(position);
+ }
+
+ // Use custom function if provided
+ else if($.isFunction(posOptions.effect)) {
+ posOptions.effect.call(tooltip, this, $.extend({}, position));
+ tooltip.queue(function(next) {
+ // Reset attributes to avoid cross-browser rendering bugs
+ $(this).css({ opacity: '', height: '' });
+ if(BROWSER.ie) { this.style.removeAttribute('filter'); }
+
+ next();
+ });
+ }
+
+ // Set positioning flag
+ this.positioning = FALSE;
+
+ return this;
+};
+
+// Custom (more correct for qTip!) offset calculator
+PROTOTYPE.reposition.offset = function(elem, pos, container) {
+ if(!container[0]) { return pos; }
+
+ var ownerDocument = $(elem[0].ownerDocument),
+ quirks = !!BROWSER.ie && document.compatMode !== 'CSS1Compat',
+ parent = container[0],
+ scrolled, position, parentOffset, overflow;
+
+ function scroll(e, i) {
+ pos.left += i * e.scrollLeft();
+ pos.top += i * e.scrollTop();
+ }
+
+ // Compensate for non-static containers offset
+ do {
+ if((position = $.css(parent, 'position')) !== 'static') {
+ if(position === 'fixed') {
+ parentOffset = parent.getBoundingClientRect();
+ scroll(ownerDocument, -1);
+ }
+ else {
+ parentOffset = $(parent).position();
+ parentOffset.left += parseFloat($.css(parent, 'borderLeftWidth')) || 0;
+ parentOffset.top += parseFloat($.css(parent, 'borderTopWidth')) || 0;
+ }
+
+ pos.left -= parentOffset.left + (parseFloat($.css(parent, 'marginLeft')) || 0);
+ pos.top -= parentOffset.top + (parseFloat($.css(parent, 'marginTop')) || 0);
+
+ // If this is the first parent element with an overflow of "scroll" or "auto", store it
+ if(!scrolled && (overflow = $.css(parent, 'overflow')) !== 'hidden' && overflow !== 'visible') { scrolled = $(parent); }
+ }
+ }
+ while(parent = parent.offsetParent);
+
+ // Compensate for containers scroll if it also has an offsetParent (or in IE quirks mode)
+ if(scrolled && (scrolled[0] !== ownerDocument[0] || quirks)) {
+ scroll(scrolled, 1);
+ }
+
+ return pos;
+};
+
+// Corner class
+var C = (CORNER = PROTOTYPE.reposition.Corner = function(corner, forceY) {
+ corner = ('' + corner).replace(/([A-Z])/, ' $1').replace(/middle/gi, CENTER).toLowerCase();
+ this.x = (corner.match(/left|right/i) || corner.match(/center/) || ['inherit'])[0].toLowerCase();
+ this.y = (corner.match(/top|bottom|center/i) || ['inherit'])[0].toLowerCase();
+ this.forceY = !!forceY;
+
+ var f = corner.charAt(0);
+ this.precedance = f === 't' || f === 'b' ? Y : X;
+}).prototype;
+
+C.invert = function(z, center) {
+ this[z] = this[z] === LEFT ? RIGHT : this[z] === RIGHT ? LEFT : center || this[z];
+};
+
+C.string = function(join) {
+ var x = this.x, y = this.y;
+
+ var result = x !== y ?
+ x === 'center' || y !== 'center' && (this.precedance === Y || this.forceY) ?
+ [y,x] :
+ [x,y] :
+ [x];
+
+ return join !== false ? result.join(' ') : result;
+};
+
+C.abbrev = function() {
+ var result = this.string(false);
+ return result[0].charAt(0) + (result[1] && result[1].charAt(0) || '');
+};
+
+C.clone = function() {
+ return new CORNER( this.string(), this.forceY );
+};
+
+;
+PROTOTYPE.toggle = function(state, event) {
+ var cache = this.cache,
+ options = this.options,
+ tooltip = this.tooltip;
+
+ // Try to prevent flickering when tooltip overlaps show element
+ if(event) {
+ if((/over|enter/).test(event.type) && cache.event && (/out|leave/).test(cache.event.type) &&
+ options.show.target.add(event.target).length === options.show.target.length &&
+ tooltip.has(event.relatedTarget).length) {
+ return this;
+ }
+
+ // Cache event
+ cache.event = $.event.fix(event);
+ }
+
+ // If we're currently waiting and we've just hidden... stop it
+ this.waiting && !state && (this.hiddenDuringWait = TRUE);
+
+ // Render the tooltip if showing and it isn't already
+ if(!this.rendered) { return state ? this.render(1) : this; }
+ else if(this.destroyed || this.disabled) { return this; }
+
+ var type = state ? 'show' : 'hide',
+ opts = this.options[type],
+ posOptions = this.options.position,
+ contentOptions = this.options.content,
+ width = this.tooltip.css('width'),
+ visible = this.tooltip.is(':visible'),
+ animate = state || opts.target.length === 1,
+ sameTarget = !event || opts.target.length < 2 || cache.target[0] === event.target,
+ identicalState, allow, after;
+
+ // Detect state if valid one isn't provided
+ if((typeof state).search('boolean|number')) { state = !visible; }
+
+ // Check if the tooltip is in an identical state to the new would-be state
+ identicalState = !tooltip.is(':animated') && visible === state && sameTarget;
+
+ // Fire tooltip(show/hide) event and check if destroyed
+ allow = !identicalState ? !!this._trigger(type, [90]) : NULL;
+
+ // Check to make sure the tooltip wasn't destroyed in the callback
+ if(this.destroyed) { return this; }
+
+ // If the user didn't stop the method prematurely and we're showing the tooltip, focus it
+ if(allow !== FALSE && state) { this.focus(event); }
+
+ // If the state hasn't changed or the user stopped it, return early
+ if(!allow || identicalState) { return this; }
+
+ // Set ARIA hidden attribute
+ $.attr(tooltip[0], 'aria-hidden', !!!state);
+
+ // Execute state specific properties
+ if(state) {
+ // Store show origin coordinates
+ this.mouse && (cache.origin = $.event.fix(this.mouse));
+
+ // Update tooltip content & title if it's a dynamic function
+ if($.isFunction(contentOptions.text)) { this._updateContent(contentOptions.text, FALSE); }
+ if($.isFunction(contentOptions.title)) { this._updateTitle(contentOptions.title, FALSE); }
+
+ // Cache mousemove events for positioning purposes (if not already tracking)
+ if(!trackingBound && posOptions.target === 'mouse' && posOptions.adjust.mouse) {
+ $(document).bind('mousemove.'+NAMESPACE, this._storeMouse);
+ trackingBound = TRUE;
+ }
+
+ // Update the tooltip position (set width first to prevent viewport/max-width issues)
+ if(!width) { tooltip.css('width', tooltip.outerWidth(FALSE)); }
+ this.reposition(event, arguments[2]);
+ if(!width) { tooltip.css('width', ''); }
+
+ // Hide other tooltips if tooltip is solo
+ if(!!opts.solo) {
+ (typeof opts.solo === 'string' ? $(opts.solo) : $(SELECTOR, opts.solo))
+ .not(tooltip).not(opts.target).qtip('hide', new $.Event('tooltipsolo'));
+ }
+ }
+ else {
+ // Clear show timer if we're hiding
+ clearTimeout(this.timers.show);
+
+ // Remove cached origin on hide
+ delete cache.origin;
+
+ // Remove mouse tracking event if not needed (all tracking qTips are hidden)
+ if(trackingBound && !$(SELECTOR+'[tracking="true"]:visible', opts.solo).not(tooltip).length) {
+ $(document).unbind('mousemove.'+NAMESPACE);
+ trackingBound = FALSE;
+ }
+
+ // Blur the tooltip
+ this.blur(event);
+ }
+
+ // Define post-animation, state specific properties
+ after = $.proxy(function() {
+ if(state) {
+ // Prevent antialias from disappearing in IE by removing filter
+ if(BROWSER.ie) { tooltip[0].style.removeAttribute('filter'); }
+
+ // Remove overflow setting to prevent tip bugs
+ tooltip.css('overflow', '');
+
+ // Autofocus elements if enabled
+ if('string' === typeof opts.autofocus) {
+ $(this.options.show.autofocus, tooltip).focus();
+ }
+
+ // If set, hide tooltip when inactive for delay period
+ this.options.show.target.trigger('qtip-'+this.id+'-inactive');
+ }
+ else {
+ // Reset CSS states
+ tooltip.css({
+ display: '',
+ visibility: '',
+ opacity: '',
+ left: '',
+ top: ''
+ });
+ }
+
+ // tooltipvisible/tooltiphidden events
+ this._trigger(state ? 'visible' : 'hidden');
+ }, this);
+
+ // If no effect type is supplied, use a simple toggle
+ if(opts.effect === FALSE || animate === FALSE) {
+ tooltip[ type ]();
+ after();
+ }
+
+ // Use custom function if provided
+ else if($.isFunction(opts.effect)) {
+ tooltip.stop(1, 1);
+ opts.effect.call(tooltip, this);
+ tooltip.queue('fx', function(n) {
+ after(); n();
+ });
+ }
+
+ // Use basic fade function by default
+ else { tooltip.fadeTo(90, state ? 1 : 0, after); }
+
+ // If inactive hide method is set, active it
+ if(state) { opts.target.trigger('qtip-'+this.id+'-inactive'); }
+
+ return this;
+};
+
+PROTOTYPE.show = function(event) { return this.toggle(TRUE, event); };
+
+PROTOTYPE.hide = function(event) { return this.toggle(FALSE, event); };
+;PROTOTYPE.focus = function(event) {
+ if(!this.rendered || this.destroyed) { return this; }
+
+ var qtips = $(SELECTOR),
+ tooltip = this.tooltip,
+ curIndex = parseInt(tooltip[0].style.zIndex, 10),
+ newIndex = QTIP.zindex + qtips.length;
+
+ // Only update the z-index if it has changed and tooltip is not already focused
+ if(!tooltip.hasClass(CLASS_FOCUS)) {
+ // tooltipfocus event
+ if(this._trigger('focus', [newIndex], event)) {
+ // Only update z-index's if they've changed
+ if(curIndex !== newIndex) {
+ // Reduce our z-index's and keep them properly ordered
+ qtips.each(function() {
+ if(this.style.zIndex > curIndex) {
+ this.style.zIndex = this.style.zIndex - 1;
+ }
+ });
+
+ // Fire blur event for focused tooltip
+ qtips.filter('.' + CLASS_FOCUS).qtip('blur', event);
+ }
+
+ // Set the new z-index
+ tooltip.addClass(CLASS_FOCUS)[0].style.zIndex = newIndex;
+ }
+ }
+
+ return this;
+};
+
+PROTOTYPE.blur = function(event) {
+ if(!this.rendered || this.destroyed) { return this; }
+
+ // Set focused status to FALSE
+ this.tooltip.removeClass(CLASS_FOCUS);
+
+ // tooltipblur event
+ this._trigger('blur', [ this.tooltip.css('zIndex') ], event);
+
+ return this;
+};
+;PROTOTYPE.disable = function(state) {
+ if(this.destroyed) { return this; }
+
+ // If 'toggle' is passed, toggle the current state
+ if(state === 'toggle') {
+ state = !(this.rendered ? this.tooltip.hasClass(CLASS_DISABLED) : this.disabled);
+ }
+
+ // Disable if no state passed
+ else if('boolean' !== typeof state) {
+ state = TRUE;
+ }
+
+ if(this.rendered) {
+ this.tooltip.toggleClass(CLASS_DISABLED, state)
+ .attr('aria-disabled', state);
+ }
+
+ this.disabled = !!state;
+
+ return this;
+};
+
+PROTOTYPE.enable = function() { return this.disable(FALSE); };
+;PROTOTYPE._createButton = function()
+{
+ var self = this,
+ elements = this.elements,
+ tooltip = elements.tooltip,
+ button = this.options.content.button,
+ isString = typeof button === 'string',
+ close = isString ? button : 'Close tooltip';
+
+ if(elements.button) { elements.button.remove(); }
+
+ // Use custom button if one was supplied by user, else use default
+ if(button.jquery) {
+ elements.button = button;
+ }
+ else {
+ elements.button = $('<a />', {
+ 'class': 'qtip-close ' + (this.options.style.widget ? '' : NAMESPACE+'-icon'),
+ 'title': close,
+ 'aria-label': close
+ })
+ .prepend(
+ $('<span />', {
+ 'class': 'ui-icon ui-icon-close',
+ 'html': '&times;'
+ })
+ );
+ }
+
+ // Create button and setup attributes
+ elements.button.appendTo(elements.titlebar || tooltip)
+ .attr('role', 'button')
+ .click(function(event) {
+ if(!tooltip.hasClass(CLASS_DISABLED)) { self.hide(event); }
+ return FALSE;
+ });
+};
+
+PROTOTYPE._updateButton = function(button)
+{
+ // Make sure tooltip is rendered and if not, return
+ if(!this.rendered) { return FALSE; }
+
+ var elem = this.elements.button;
+ if(button) { this._createButton(); }
+ else { elem.remove(); }
+};
+;// Widget class creator
+function createWidgetClass(cls) {
+ return WIDGET.concat('').join(cls ? '-'+cls+' ' : ' ');
+}
+
+// Widget class setter method
+PROTOTYPE._setWidget = function()
+{
+ var on = this.options.style.widget,
+ elements = this.elements,
+ tooltip = elements.tooltip,
+ disabled = tooltip.hasClass(CLASS_DISABLED);
+
+ tooltip.removeClass(CLASS_DISABLED);
+ CLASS_DISABLED = on ? 'ui-state-disabled' : 'qtip-disabled';
+ tooltip.toggleClass(CLASS_DISABLED, disabled);
+
+ tooltip.toggleClass('ui-helper-reset '+createWidgetClass(), on).toggleClass(CLASS_DEFAULT, this.options.style.def && !on);
+
+ if(elements.content) {
+ elements.content.toggleClass( createWidgetClass('content'), on);
+ }
+ if(elements.titlebar) {
+ elements.titlebar.toggleClass( createWidgetClass('header'), on);
+ }
+ if(elements.button) {
+ elements.button.toggleClass(NAMESPACE+'-icon', !on);
+ }
+};
+;function delay(callback, duration) {
+ // If tooltip has displayed, start hide timer
+ if(duration > 0) {
+ return setTimeout(
+ $.proxy(callback, this), duration
+ );
+ }
+ else{ callback.call(this); }
+}
+
+function showMethod(event) {
+ if(this.tooltip.hasClass(CLASS_DISABLED)) { return; }
+
+ // Clear hide timers
+ clearTimeout(this.timers.show);
+ clearTimeout(this.timers.hide);
+
+ // Start show timer
+ this.timers.show = delay.call(this,
+ function() { this.toggle(TRUE, event); },
+ this.options.show.delay
+ );
+}
+
+function hideMethod(event) {
+ if(this.tooltip.hasClass(CLASS_DISABLED) || this.destroyed) { return; }
+
+ // Check if new target was actually the tooltip element
+ var relatedTarget = $(event.relatedTarget),
+ ontoTooltip = relatedTarget.closest(SELECTOR)[0] === this.tooltip[0],
+ ontoTarget = relatedTarget[0] === this.options.show.target[0];
+
+ // Clear timers and stop animation queue
+ clearTimeout(this.timers.show);
+ clearTimeout(this.timers.hide);
+
+ // Prevent hiding if tooltip is fixed and event target is the tooltip.
+ // Or if mouse positioning is enabled and cursor momentarily overlaps
+ if(this !== relatedTarget[0] &&
+ (this.options.position.target === 'mouse' && ontoTooltip) ||
+ this.options.hide.fixed && (
+ (/mouse(out|leave|move)/).test(event.type) && (ontoTooltip || ontoTarget))
+ )
+ {
+ /* eslint-disable no-empty */
+ try {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ } catch(e) {}
+ /* eslint-enable no-empty */
+
+ return;
+ }
+
+ // If tooltip has displayed, start hide timer
+ this.timers.hide = delay.call(this,
+ function() { this.toggle(FALSE, event); },
+ this.options.hide.delay,
+ this
+ );
+}
+
+function inactiveMethod(event) {
+ if(this.tooltip.hasClass(CLASS_DISABLED) || !this.options.hide.inactive) { return; }
+
+ // Clear timer
+ clearTimeout(this.timers.inactive);
+
+ this.timers.inactive = delay.call(this,
+ function(){ this.hide(event); },
+ this.options.hide.inactive
+ );
+}
+
+function repositionMethod(event) {
+ if(this.rendered && this.tooltip[0].offsetWidth > 0) { this.reposition(event); }
+}
+
+// Store mouse coordinates
+PROTOTYPE._storeMouse = function(event) {
+ (this.mouse = $.event.fix(event)).type = 'mousemove';
+ return this;
+};
+
+// Bind events
+PROTOTYPE._bind = function(targets, events, method, suffix, context) {
+ if(!targets || !method || !events.length) { return; }
+ var ns = '.' + this._id + (suffix ? '-'+suffix : '');
+ $(targets).bind(
+ (events.split ? events : events.join(ns + ' ')) + ns,
+ $.proxy(method, context || this)
+ );
+ return this;
+};
+PROTOTYPE._unbind = function(targets, suffix) {
+ targets && $(targets).unbind('.' + this._id + (suffix ? '-'+suffix : ''));
+ return this;
+};
+
+// Global delegation helper
+function delegate(selector, events, method) {
+ $(document.body).delegate(selector,
+ (events.split ? events : events.join('.'+NAMESPACE + ' ')) + '.'+NAMESPACE,
+ function() {
+ var api = QTIP.api[ $.attr(this, ATTR_ID) ];
+ api && !api.disabled && method.apply(api, arguments);
+ }
+ );
+}
+// Event trigger
+PROTOTYPE._trigger = function(type, args, event) {
+ var callback = new $.Event('tooltip'+type);
+ callback.originalEvent = event && $.extend({}, event) || this.cache.event || NULL;
+
+ this.triggering = type;
+ this.tooltip.trigger(callback, [this].concat(args || []));
+ this.triggering = FALSE;
+
+ return !callback.isDefaultPrevented();
+};
+
+PROTOTYPE._bindEvents = function(showEvents, hideEvents, showTargets, hideTargets, showCallback, hideCallback) {
+ // Get tasrgets that lye within both
+ var similarTargets = showTargets.filter( hideTargets ).add( hideTargets.filter(showTargets) ),
+ toggleEvents = [];
+
+ // If hide and show targets are the same...
+ if(similarTargets.length) {
+
+ // Filter identical show/hide events
+ $.each(hideEvents, function(i, type) {
+ var showIndex = $.inArray(type, showEvents);
+
+ // Both events are identical, remove from both hide and show events
+ // and append to toggleEvents
+ showIndex > -1 && toggleEvents.push( showEvents.splice( showIndex, 1 )[0] );
+ });
+
+ // Toggle events are special case of identical show/hide events, which happen in sequence
+ if(toggleEvents.length) {
+ // Bind toggle events to the similar targets
+ this._bind(similarTargets, toggleEvents, function(event) {
+ var state = this.rendered ? this.tooltip[0].offsetWidth > 0 : false;
+ (state ? hideCallback : showCallback).call(this, event);
+ });
+
+ // Remove the similar targets from the regular show/hide bindings
+ showTargets = showTargets.not(similarTargets);
+ hideTargets = hideTargets.not(similarTargets);
+ }
+ }
+
+ // Apply show/hide/toggle events
+ this._bind(showTargets, showEvents, showCallback);
+ this._bind(hideTargets, hideEvents, hideCallback);
+};
+
+PROTOTYPE._assignInitialEvents = function(event) {
+ var options = this.options,
+ showTarget = options.show.target,
+ hideTarget = options.hide.target,
+ showEvents = options.show.event ? $.trim('' + options.show.event).split(' ') : [],
+ hideEvents = options.hide.event ? $.trim('' + options.hide.event).split(' ') : [];
+
+ // Catch remove/removeqtip events on target element to destroy redundant tooltips
+ this._bind(this.elements.target, ['remove', 'removeqtip'], function() {
+ this.destroy(true);
+ }, 'destroy');
+
+ /*
+ * Make sure hoverIntent functions properly by using mouseleave as a hide event if
+ * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
+ */
+ if(/mouse(over|enter)/i.test(options.show.event) && !/mouse(out|leave)/i.test(options.hide.event)) {
+ hideEvents.push('mouseleave');
+ }
+
+ /*
+ * Also make sure initial mouse targetting works correctly by caching mousemove coords
+ * on show targets before the tooltip has rendered. Also set onTarget when triggered to
+ * keep mouse tracking working.
+ */
+ this._bind(showTarget, 'mousemove', function(moveEvent) {
+ this._storeMouse(moveEvent);
+ this.cache.onTarget = TRUE;
+ });
+
+ // Define hoverIntent function
+ function hoverIntent(hoverEvent) {
+ // Only continue if tooltip isn't disabled
+ if(this.disabled || this.destroyed) { return FALSE; }
+
+ // Cache the event data
+ this.cache.event = hoverEvent && $.event.fix(hoverEvent);
+ this.cache.target = hoverEvent && $(hoverEvent.target);
+
+ // Start the event sequence
+ clearTimeout(this.timers.show);
+ this.timers.show = delay.call(this,
+ function() { this.render(typeof hoverEvent === 'object' || options.show.ready); },
+ options.prerender ? 0 : options.show.delay
+ );
+ }
+
+ // Filter and bind events
+ this._bindEvents(showEvents, hideEvents, showTarget, hideTarget, hoverIntent, function() {
+ if(!this.timers) { return FALSE; }
+ clearTimeout(this.timers.show);
+ });
+
+ // Prerendering is enabled, create tooltip now
+ if(options.show.ready || options.prerender) { hoverIntent.call(this, event); }
+};
+
+// Event assignment method
+PROTOTYPE._assignEvents = function() {
+ var self = this,
+ options = this.options,
+ posOptions = options.position,
+
+ tooltip = this.tooltip,
+ showTarget = options.show.target,
+ hideTarget = options.hide.target,
+ containerTarget = posOptions.container,
+ viewportTarget = posOptions.viewport,
+ documentTarget = $(document),
+ windowTarget = $(window),
+
+ showEvents = options.show.event ? $.trim('' + options.show.event).split(' ') : [],
+ hideEvents = options.hide.event ? $.trim('' + options.hide.event).split(' ') : [];
+
+
+ // Assign passed event callbacks
+ $.each(options.events, function(name, callback) {
+ self._bind(tooltip, name === 'toggle' ? ['tooltipshow','tooltiphide'] : ['tooltip'+name], callback, null, tooltip);
+ });
+
+ // Hide tooltips when leaving current window/frame (but not select/option elements)
+ if(/mouse(out|leave)/i.test(options.hide.event) && options.hide.leave === 'window') {
+ this._bind(documentTarget, ['mouseout', 'blur'], function(event) {
+ if(!/select|option/.test(event.target.nodeName) && !event.relatedTarget) {
+ this.hide(event);
+ }
+ });
+ }
+
+ // Enable hide.fixed by adding appropriate class
+ if(options.hide.fixed) {
+ hideTarget = hideTarget.add( tooltip.addClass(CLASS_FIXED) );
+ }
+
+ /*
+ * Make sure hoverIntent functions properly by using mouseleave to clear show timer if
+ * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
+ */
+ else if(/mouse(over|enter)/i.test(options.show.event)) {
+ this._bind(hideTarget, 'mouseleave', function() {
+ clearTimeout(this.timers.show);
+ });
+ }
+
+ // Hide tooltip on document mousedown if unfocus events are enabled
+ if(('' + options.hide.event).indexOf('unfocus') > -1) {
+ this._bind(containerTarget.closest('html'), ['mousedown', 'touchstart'], function(event) {
+ var elem = $(event.target),
+ enabled = this.rendered && !this.tooltip.hasClass(CLASS_DISABLED) && this.tooltip[0].offsetWidth > 0,
+ isAncestor = elem.parents(SELECTOR).filter(this.tooltip[0]).length > 0;
+
+ if(elem[0] !== this.target[0] && elem[0] !== this.tooltip[0] && !isAncestor &&
+ !this.target.has(elem[0]).length && enabled
+ ) {
+ this.hide(event);
+ }
+ });
+ }
+
+ // Check if the tooltip hides when inactive
+ if('number' === typeof options.hide.inactive) {
+ // Bind inactive method to show target(s) as a custom event
+ this._bind(showTarget, 'qtip-'+this.id+'-inactive', inactiveMethod, 'inactive');
+
+ // Define events which reset the 'inactive' event handler
+ this._bind(hideTarget.add(tooltip), QTIP.inactiveEvents, inactiveMethod);
+ }
+
+ // Filter and bind events
+ this._bindEvents(showEvents, hideEvents, showTarget, hideTarget, showMethod, hideMethod);
+
+ // Mouse movement bindings
+ this._bind(showTarget.add(tooltip), 'mousemove', function(event) {
+ // Check if the tooltip hides when mouse is moved a certain distance
+ if('number' === typeof options.hide.distance) {
+ var origin = this.cache.origin || {},
+ limit = this.options.hide.distance,
+ abs = Math.abs;
+
+ // Check if the movement has gone beyond the limit, and hide it if so
+ if(abs(event.pageX - origin.pageX) >= limit || abs(event.pageY - origin.pageY) >= limit) {
+ this.hide(event);
+ }
+ }
+
+ // Cache mousemove coords on show targets
+ this._storeMouse(event);
+ });
+
+ // Mouse positioning events
+ if(posOptions.target === 'mouse') {
+ // If mouse adjustment is on...
+ if(posOptions.adjust.mouse) {
+ // Apply a mouseleave event so we don't get problems with overlapping
+ if(options.hide.event) {
+ // Track if we're on the target or not
+ this._bind(showTarget, ['mouseenter', 'mouseleave'], function(event) {
+ if(!this.cache) {return FALSE; }
+ this.cache.onTarget = event.type === 'mouseenter';
+ });
+ }
+
+ // Update tooltip position on mousemove
+ this._bind(documentTarget, 'mousemove', function(event) {
+ // Update the tooltip position only if the tooltip is visible and adjustment is enabled
+ if(this.rendered && this.cache.onTarget && !this.tooltip.hasClass(CLASS_DISABLED) && this.tooltip[0].offsetWidth > 0) {
+ this.reposition(event);
+ }
+ });
+ }
+ }
+
+ // Adjust positions of the tooltip on window resize if enabled
+ if(posOptions.adjust.resize || viewportTarget.length) {
+ this._bind( $.event.special.resize ? viewportTarget : windowTarget, 'resize', repositionMethod );
+ }
+
+ // Adjust tooltip position on scroll of the window or viewport element if present
+ if(posOptions.adjust.scroll) {
+ this._bind( windowTarget.add(posOptions.container), 'scroll', repositionMethod );
+ }
+};
+
+// Un-assignment method
+PROTOTYPE._unassignEvents = function() {
+ var options = this.options,
+ showTargets = options.show.target,
+ hideTargets = options.hide.target,
+ targets = $.grep([
+ this.elements.target[0],
+ this.rendered && this.tooltip[0],
+ options.position.container[0],
+ options.position.viewport[0],
+ options.position.container.closest('html')[0], // unfocus
+ window,
+ document
+ ], function(i) {
+ return typeof i === 'object';
+ });
+
+ // Add show and hide targets if they're valid
+ if(showTargets && showTargets.toArray) {
+ targets = targets.concat(showTargets.toArray());
+ }
+ if(hideTargets && hideTargets.toArray) {
+ targets = targets.concat(hideTargets.toArray());
+ }
+
+ // Unbind the events
+ this._unbind(targets)
+ ._unbind(targets, 'destroy')
+ ._unbind(targets, 'inactive');
+};
+
+// Apply common event handlers using delegate (avoids excessive .bind calls!)
+$(function() {
+ delegate(SELECTOR, ['mouseenter', 'mouseleave'], function(event) {
+ var state = event.type === 'mouseenter',
+ tooltip = $(event.currentTarget),
+ target = $(event.relatedTarget || event.target),
+ options = this.options;
+
+ // On mouseenter...
+ if(state) {
+ // Focus the tooltip on mouseenter (z-index stacking)
+ this.focus(event);
+
+ // Clear hide timer on tooltip hover to prevent it from closing
+ tooltip.hasClass(CLASS_FIXED) && !tooltip.hasClass(CLASS_DISABLED) && clearTimeout(this.timers.hide);
+ }
+
+ // On mouseleave...
+ else {
+ // When mouse tracking is enabled, hide when we leave the tooltip and not onto the show target (if a hide event is set)
+ if(options.position.target === 'mouse' && options.position.adjust.mouse &&
+ options.hide.event && options.show.target && !target.closest(options.show.target[0]).length) {
+ this.hide(event);
+ }
+ }
+
+ // Add hover class
+ tooltip.toggleClass(CLASS_HOVER, state);
+ });
+
+ // Define events which reset the 'inactive' event handler
+ delegate('['+ATTR_ID+']', INACTIVE_EVENTS, inactiveMethod);
+});
+;// Initialization method
+function init(elem, id, opts) {
+ var obj, posOptions, attr, config, title,
+
+ // Setup element references
+ docBody = $(document.body),
+
+ // Use document body instead of document element if needed
+ newTarget = elem[0] === document ? docBody : elem,
+
+ // Grab metadata from element if plugin is present
+ metadata = elem.metadata ? elem.metadata(opts.metadata) : NULL,
+
+ // If metadata type if HTML5, grab 'name' from the object instead, or use the regular data object otherwise
+ metadata5 = opts.metadata.type === 'html5' && metadata ? metadata[opts.metadata.name] : NULL,
+
+ // Grab data from metadata.name (or data-qtipopts as fallback) using .data() method,
+ html5 = elem.data(opts.metadata.name || 'qtipopts');
+
+ // If we don't get an object returned attempt to parse it manualyl without parseJSON
+ /* eslint-disable no-empty */
+ try { html5 = typeof html5 === 'string' ? $.parseJSON(html5) : html5; }
+ catch(e) {}
+ /* eslint-enable no-empty */
+
+ // Merge in and sanitize metadata
+ config = $.extend(TRUE, {}, QTIP.defaults, opts,
+ typeof html5 === 'object' ? sanitizeOptions(html5) : NULL,
+ sanitizeOptions(metadata5 || metadata));
+
+ // Re-grab our positioning options now we've merged our metadata and set id to passed value
+ posOptions = config.position;
+ config.id = id;
+
+ // Setup missing content if none is detected
+ if('boolean' === typeof config.content.text) {
+ attr = elem.attr(config.content.attr);
+
+ // Grab from supplied attribute if available
+ if(config.content.attr !== FALSE && attr) { config.content.text = attr; }
+
+ // No valid content was found, abort render
+ else { return FALSE; }
+ }
+
+ // Setup target options
+ if(!posOptions.container.length) { posOptions.container = docBody; }
+ if(posOptions.target === FALSE) { posOptions.target = newTarget; }
+ if(config.show.target === FALSE) { config.show.target = newTarget; }
+ if(config.show.solo === TRUE) { config.show.solo = posOptions.container.closest('body'); }
+ if(config.hide.target === FALSE) { config.hide.target = newTarget; }
+ if(config.position.viewport === TRUE) { config.position.viewport = posOptions.container; }
+
+ // Ensure we only use a single container
+ posOptions.container = posOptions.container.eq(0);
+
+ // Convert position corner values into x and y strings
+ posOptions.at = new CORNER(posOptions.at, TRUE);
+ posOptions.my = new CORNER(posOptions.my);
+
+ // Destroy previous tooltip if overwrite is enabled, or skip element if not
+ if(elem.data(NAMESPACE)) {
+ if(config.overwrite) {
+ elem.qtip('destroy', true);
+ }
+ else if(config.overwrite === FALSE) {
+ return FALSE;
+ }
+ }
+
+ // Add has-qtip attribute
+ elem.attr(ATTR_HAS, id);
+
+ // Remove title attribute and store it if present
+ if(config.suppress && (title = elem.attr('title'))) {
+ // Final attr call fixes event delegation and IE default tooltip showing problem
+ elem.removeAttr('title').attr(oldtitle, title).attr('title', '');
+ }
+
+ // Initialize the tooltip and add API reference
+ obj = new QTip(elem, config, id, !!attr);
+ elem.data(NAMESPACE, obj);
+
+ return obj;
+}
+
+// jQuery $.fn extension method
+QTIP = $.fn.qtip = function(options, notation, newValue)
+{
+ var command = ('' + options).toLowerCase(), // Parse command
+ returned = NULL,
+ args = $.makeArray(arguments).slice(1),
+ event = args[args.length - 1],
+ opts = this[0] ? $.data(this[0], NAMESPACE) : NULL;
+
+ // Check for API request
+ if(!arguments.length && opts || command === 'api') {
+ return opts;
+ }
+
+ // Execute API command if present
+ else if('string' === typeof options) {
+ this.each(function() {
+ var api = $.data(this, NAMESPACE);
+ if(!api) { return TRUE; }
+
+ // Cache the event if possible
+ if(event && event.timeStamp) { api.cache.event = event; }
+
+ // Check for specific API commands
+ if(notation && (command === 'option' || command === 'options')) {
+ if(newValue !== undefined || $.isPlainObject(notation)) {
+ api.set(notation, newValue);
+ }
+ else {
+ returned = api.get(notation);
+ return FALSE;
+ }
+ }
+
+ // Execute API command
+ else if(api[command]) {
+ api[command].apply(api, args);
+ }
+ });
+
+ return returned !== NULL ? returned : this;
+ }
+
+ // No API commands. validate provided options and setup qTips
+ else if('object' === typeof options || !arguments.length) {
+ // Sanitize options first
+ opts = sanitizeOptions($.extend(TRUE, {}, options));
+
+ return this.each(function(i) {
+ var api, id;
+
+ // Find next available ID, or use custom ID if provided
+ id = $.isArray(opts.id) ? opts.id[i] : opts.id;
+ id = !id || id === FALSE || id.length < 1 || QTIP.api[id] ? QTIP.nextid++ : id;
+
+ // Initialize the qTip and re-grab newly sanitized options
+ api = init($(this), id, opts);
+ if(api === FALSE) { return TRUE; }
+ else { QTIP.api[id] = api; }
+
+ // Initialize plugins
+ $.each(PLUGINS, function() {
+ if(this.initialize === 'initialize') { this(api); }
+ });
+
+ // Assign initial pre-render events
+ api._assignInitialEvents(event);
+ });
+ }
+};
+
+// Expose class
+$.qtip = QTip;
+
+// Populated in render method
+QTIP.api = {};
+;$.each({
+ /* Allow other plugins to successfully retrieve the title of an element with a qTip applied */
+ attr: function(attr, val) {
+ if(this.length) {
+ var self = this[0],
+ title = 'title',
+ api = $.data(self, 'qtip');
+
+ if(attr === title && api && api.options && 'object' === typeof api && 'object' === typeof api.options && api.options.suppress) {
+ if(arguments.length < 2) {
+ return $.attr(self, oldtitle);
+ }
+
+ // If qTip is rendered and title was originally used as content, update it
+ if(api && api.options.content.attr === title && api.cache.attr) {
+ api.set('content.text', val);
+ }
+
+ // Use the regular attr method to set, then cache the result
+ return this.attr(oldtitle, val);
+ }
+ }
+
+ return $.fn['attr'+replaceSuffix].apply(this, arguments);
+ },
+
+ /* Allow clone to correctly retrieve cached title attributes */
+ clone: function(keepData) {
+ // Clone our element using the real clone method
+ var elems = $.fn['clone'+replaceSuffix].apply(this, arguments);
+
+ // Grab all elements with an oldtitle set, and change it to regular title attribute, if keepData is false
+ if(!keepData) {
+ elems.filter('['+oldtitle+']').attr('title', function() {
+ return $.attr(this, oldtitle);
+ })
+ .removeAttr(oldtitle);
+ }
+
+ return elems;
+ }
+}, function(name, func) {
+ if(!func || $.fn[name+replaceSuffix]) { return TRUE; }
+
+ var old = $.fn[name+replaceSuffix] = $.fn[name];
+ $.fn[name] = function() {
+ return func.apply(this, arguments) || old.apply(this, arguments);
+ };
+});
+
+/* Fire off 'removeqtip' handler in $.cleanData if jQuery UI not present (it already does similar).
+ * This snippet is taken directly from jQuery UI source code found here:
+ * http://code.jquery.com/ui/jquery-ui-git.js
+ */
+if(!$.ui) {
+ $['cleanData'+replaceSuffix] = $.cleanData;
+ $.cleanData = function( elems ) {
+ for(var i = 0, elem; (elem = $( elems[i] )).length; i++) {
+ if(elem.attr(ATTR_HAS)) {
+ /* eslint-disable no-empty */
+ try { elem.triggerHandler('removeqtip'); }
+ catch( e ) {}
+ /* eslint-enable no-empty */
+ }
+ }
+ $['cleanData'+replaceSuffix].apply(this, arguments);
+ };
+}
+;// qTip version
+QTIP.version = '3.0.3';
+
+// Base ID for all qTips
+QTIP.nextid = 0;
+
+// Inactive events array
+QTIP.inactiveEvents = INACTIVE_EVENTS;
+
+// Base z-index for all qTips
+QTIP.zindex = 15000;
+
+// Define configuration defaults
+QTIP.defaults = {
+ prerender: FALSE,
+ id: FALSE,
+ overwrite: TRUE,
+ suppress: TRUE,
+ content: {
+ text: TRUE,
+ attr: 'title',
+ title: FALSE,
+ button: FALSE
+ },
+ position: {
+ my: 'top left',
+ at: 'bottom right',
+ target: FALSE,
+ container: FALSE,
+ viewport: FALSE,
+ adjust: {
+ x: 0, y: 0,
+ mouse: TRUE,
+ scroll: TRUE,
+ resize: TRUE,
+ method: 'flipinvert flipinvert'
+ },
+ effect: function(api, pos) {
+ $(this).animate(pos, {
+ duration: 200,
+ queue: FALSE
+ });
+ }
+ },
+ show: {
+ target: FALSE,
+ event: 'mouseenter',
+ effect: TRUE,
+ delay: 90,
+ solo: FALSE,
+ ready: FALSE,
+ autofocus: FALSE
+ },
+ hide: {
+ target: FALSE,
+ event: 'mouseleave',
+ effect: TRUE,
+ delay: 0,
+ fixed: FALSE,
+ inactive: FALSE,
+ leave: 'window',
+ distance: FALSE
+ },
+ style: {
+ classes: '',
+ widget: FALSE,
+ width: FALSE,
+ height: FALSE,
+ def: TRUE
+ },
+ events: {
+ render: NULL,
+ move: NULL,
+ show: NULL,
+ hide: NULL,
+ toggle: NULL,
+ visible: NULL,
+ hidden: NULL,
+ focus: NULL,
+ blur: NULL
+ }
+};
+;}));
+}( window, document ));
diff --git a/www/wiki/vendor/onoi/shared-resources/res/jquery.qtip/extended/jquery.qtip.css b/www/wiki/vendor/onoi/shared-resources/res/jquery.qtip/extended/jquery.qtip.css
new file mode 100644
index 00000000..1da1a64e
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/jquery.qtip/extended/jquery.qtip.css
@@ -0,0 +1,557 @@
+/*
+ * qTip2 - Pretty powerful tooltips - v3.0.3
+ * http://qtip2.com
+ *
+ * Copyright (c) 2016
+ * Released under the MIT licenses
+ * http://jquery.org/license
+ *
+ * Date: Thu May 19 2016 09:02 EDT-0400
+ * Plugins: viewport svg
+ * Styles: core basic css3
+ */
+.qtip{
+ position: absolute;
+ left: -28000px;
+ top: -28000px;
+ display: none;
+
+ max-width: 280px;
+ min-width: 50px;
+
+ font-size: 10.5px;
+ line-height: 12px;
+
+ direction: ltr;
+
+ box-shadow: none;
+ padding: 0;
+}
+
+ .qtip-content{
+ position: relative;
+ padding: 5px 9px;
+ overflow: hidden;
+
+ text-align: left;
+ word-wrap: break-word;
+ }
+
+ .qtip-titlebar{
+ position: relative;
+ padding: 5px 35px 5px 10px;
+ overflow: hidden;
+
+ border-width: 0 0 1px;
+ font-weight: bold;
+ }
+
+ .qtip-titlebar + .qtip-content{ border-top-width: 0 !important; }
+
+ /* Default close button class */
+ .qtip-close{
+ position: absolute;
+ right: -9px; top: -9px;
+ z-index: 11; /* Overlap .qtip-tip */
+
+ cursor: pointer;
+ outline: medium none;
+
+ border: 1px solid transparent;
+ }
+
+ .qtip-titlebar .qtip-close{
+ right: 4px; top: 50%;
+ margin-top: -9px;
+ }
+
+ * html .qtip-titlebar .qtip-close{ top: 16px; } /* IE fix */
+
+ .qtip-titlebar .ui-icon,
+ .qtip-icon .ui-icon{
+ display: block;
+ text-indent: -1000em;
+ direction: ltr;
+ }
+
+ .qtip-icon, .qtip-icon .ui-icon{
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+ text-decoration: none;
+ }
+
+ .qtip-icon .ui-icon{
+ width: 18px;
+ height: 14px;
+
+ line-height: 14px;
+ text-align: center;
+ text-indent: 0;
+ font: normal bold 10px/13px Tahoma,sans-serif;
+
+ color: inherit;
+ background: transparent none no-repeat -100em -100em;
+ }
+
+/* Applied to 'focused' tooltips e.g. most recently displayed/interacted with */
+.qtip-focus{}
+
+/* Applied on hover of tooltips i.e. added/removed on mouseenter/mouseleave respectively */
+.qtip-hover{}
+
+/* Default tooltip style */
+.qtip-default{
+ border: 1px solid #F1D031;
+
+ background-color: #FFFFA3;
+ color: #555;
+}
+
+ .qtip-default .qtip-titlebar{
+ background-color: #FFEF93;
+ }
+
+ .qtip-default .qtip-icon{
+ border-color: #CCC;
+ background: #F1F1F1;
+ color: #777;
+ }
+
+ .qtip-default .qtip-titlebar .qtip-close{
+ border-color: #AAA;
+ color: #111;
+ }
+
+
+/*! Light tooltip style */
+.qtip-light{
+ background-color: white;
+ border-color: #E2E2E2;
+ color: #454545;
+}
+
+ .qtip-light .qtip-titlebar{
+ background-color: #f1f1f1;
+ }
+
+
+/*! Dark tooltip style */
+.qtip-dark{
+ background-color: #505050;
+ border-color: #303030;
+ color: #f3f3f3;
+}
+
+ .qtip-dark .qtip-titlebar{
+ background-color: #404040;
+ }
+
+ .qtip-dark .qtip-icon{
+ border-color: #444;
+ }
+
+ .qtip-dark .qtip-titlebar .ui-state-hover{
+ border-color: #303030;
+ }
+
+
+/*! Cream tooltip style */
+.qtip-cream{
+ background-color: #FBF7AA;
+ border-color: #F9E98E;
+ color: #A27D35;
+}
+
+ .qtip-cream .qtip-titlebar{
+ background-color: #F0DE7D;
+ }
+
+ .qtip-cream .qtip-close .qtip-icon{
+ background-position: -82px 0;
+ }
+
+
+/*! Red tooltip style */
+.qtip-red{
+ background-color: #F78B83;
+ border-color: #D95252;
+ color: #912323;
+}
+
+ .qtip-red .qtip-titlebar{
+ background-color: #F06D65;
+ }
+
+ .qtip-red .qtip-close .qtip-icon{
+ background-position: -102px 0;
+ }
+
+ .qtip-red .qtip-icon{
+ border-color: #D95252;
+ }
+
+ .qtip-red .qtip-titlebar .ui-state-hover{
+ border-color: #D95252;
+ }
+
+
+/*! Green tooltip style */
+.qtip-green{
+ background-color: #CAED9E;
+ border-color: #90D93F;
+ color: #3F6219;
+}
+
+ .qtip-green .qtip-titlebar{
+ background-color: #B0DE78;
+ }
+
+ .qtip-green .qtip-close .qtip-icon{
+ background-position: -42px 0;
+ }
+
+
+/*! Blue tooltip style */
+.qtip-blue{
+ background-color: #E5F6FE;
+ border-color: #ADD9ED;
+ color: #5E99BD;
+}
+
+ .qtip-blue .qtip-titlebar{
+ background-color: #D0E9F5;
+ }
+
+ .qtip-blue .qtip-close .qtip-icon{
+ background-position: -2px 0;
+ }
+
+
+.qtip-shadow{
+ -webkit-box-shadow: 1px 1px 3px 1px rgba(0, 0, 0, 0.15);
+ -moz-box-shadow: 1px 1px 3px 1px rgba(0, 0, 0, 0.15);
+ box-shadow: 1px 1px 3px 1px rgba(0, 0, 0, 0.15);
+}
+
+/* Add rounded corners to your tooltips in: FF3+, Chrome 2+, Opera 10.6+, IE9+, Safari 2+ */
+.qtip-rounded,
+.qtip-tipsy,
+.qtip-bootstrap{
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+ border-radius: 5px;
+}
+
+.qtip-rounded .qtip-titlebar{
+ -moz-border-radius: 4px 4px 0 0;
+ -webkit-border-radius: 4px 4px 0 0;
+ border-radius: 4px 4px 0 0;
+}
+
+/* Youtube tooltip style */
+.qtip-youtube{
+ -moz-border-radius: 2px;
+ -webkit-border-radius: 2px;
+ border-radius: 2px;
+
+ -webkit-box-shadow: 0 0 3px #333;
+ -moz-box-shadow: 0 0 3px #333;
+ box-shadow: 0 0 3px #333;
+
+ color: white;
+ border: 0 solid transparent;
+
+ background: #4A4A4A;
+ background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0,#4A4A4A),color-stop(100%,black));
+ background-image: -webkit-linear-gradient(top,#4A4A4A 0,black 100%);
+ background-image: -moz-linear-gradient(top,#4A4A4A 0,black 100%);
+ background-image: -ms-linear-gradient(top,#4A4A4A 0,black 100%);
+ background-image: -o-linear-gradient(top,#4A4A4A 0,black 100%);
+}
+
+ .qtip-youtube .qtip-titlebar{
+ background-color: #4A4A4A;
+ background-color: rgba(0,0,0,0);
+ }
+
+ .qtip-youtube .qtip-content{
+ padding: .75em;
+ font: 12px arial,sans-serif;
+
+ filter: progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#4a4a4a,EndColorStr=#000000);
+ -ms-filter: "progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#4a4a4a,EndColorStr=#000000);";
+ }
+
+ .qtip-youtube .qtip-icon{
+ border-color: #222;
+ }
+
+ .qtip-youtube .qtip-titlebar .ui-state-hover{
+ border-color: #303030;
+ }
+
+
+/* jQuery TOOLS Tooltip style */
+.qtip-jtools{
+ background: #232323;
+ background: rgba(0, 0, 0, 0.7);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#717171), to(#232323));
+ background-image: -moz-linear-gradient(top, #717171, #232323);
+ background-image: -webkit-linear-gradient(top, #717171, #232323);
+ background-image: -ms-linear-gradient(top, #717171, #232323);
+ background-image: -o-linear-gradient(top, #717171, #232323);
+
+ border: 2px solid #ddd;
+ border: 2px solid rgba(241,241,241,1);
+
+ -moz-border-radius: 2px;
+ -webkit-border-radius: 2px;
+ border-radius: 2px;
+
+ -webkit-box-shadow: 0 0 12px #333;
+ -moz-box-shadow: 0 0 12px #333;
+ box-shadow: 0 0 12px #333;
+}
+
+ /* IE Specific */
+ .qtip-jtools .qtip-titlebar{
+ background-color: transparent;
+ filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#717171,endColorstr=#4A4A4A);
+ -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#717171,endColorstr=#4A4A4A)";
+ }
+ .qtip-jtools .qtip-content{
+ filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#4A4A4A,endColorstr=#232323);
+ -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#4A4A4A,endColorstr=#232323)";
+ }
+
+ .qtip-jtools .qtip-titlebar,
+ .qtip-jtools .qtip-content{
+ background: transparent;
+ color: white;
+ border: 0 dashed transparent;
+ }
+
+ .qtip-jtools .qtip-icon{
+ border-color: #555;
+ }
+
+ .qtip-jtools .qtip-titlebar .ui-state-hover{
+ border-color: #333;
+ }
+
+
+/* Cluetip style */
+.qtip-cluetip{
+ -webkit-box-shadow: 4px 4px 5px rgba(0, 0, 0, 0.4);
+ -moz-box-shadow: 4px 4px 5px rgba(0, 0, 0, 0.4);
+ box-shadow: 4px 4px 5px rgba(0, 0, 0, 0.4);
+
+ background-color: #D9D9C2;
+ color: #111;
+ border: 0 dashed transparent;
+}
+
+ .qtip-cluetip .qtip-titlebar{
+ background-color: #87876A;
+ color: white;
+ border: 0 dashed transparent;
+ }
+
+ .qtip-cluetip .qtip-icon{
+ border-color: #808064;
+ }
+
+ .qtip-cluetip .qtip-titlebar .ui-state-hover{
+ border-color: #696952;
+ color: #696952;
+ }
+
+
+/* Tipsy style */
+.qtip-tipsy{
+ background: black;
+ background: rgba(0, 0, 0, .87);
+
+ color: white;
+ border: 0 solid transparent;
+
+ font-size: 11px;
+ font-family: 'Lucida Grande', sans-serif;
+ font-weight: bold;
+ line-height: 16px;
+ text-shadow: 0 1px black;
+}
+
+ .qtip-tipsy .qtip-titlebar{
+ padding: 6px 35px 0 10px;
+ background-color: transparent;
+ }
+
+ .qtip-tipsy .qtip-content{
+ padding: 6px 10px;
+ }
+
+ .qtip-tipsy .qtip-icon{
+ border-color: #222;
+ text-shadow: none;
+ }
+
+ .qtip-tipsy .qtip-titlebar .ui-state-hover{
+ border-color: #303030;
+ }
+
+
+/* Tipped style */
+.qtip-tipped{
+ border: 3px solid #959FA9;
+
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+
+ background-color: #F9F9F9;
+ color: #454545;
+
+ font-weight: normal;
+ font-family: serif;
+}
+
+ .qtip-tipped .qtip-titlebar{
+ border-bottom-width: 0;
+
+ color: white;
+ background: #3A79B8;
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#3A79B8), to(#2E629D));
+ background-image: -webkit-linear-gradient(top, #3A79B8, #2E629D);
+ background-image: -moz-linear-gradient(top, #3A79B8, #2E629D);
+ background-image: -ms-linear-gradient(top, #3A79B8, #2E629D);
+ background-image: -o-linear-gradient(top, #3A79B8, #2E629D);
+ filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#3A79B8,endColorstr=#2E629D);
+ -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#3A79B8,endColorstr=#2E629D)";
+ }
+
+ .qtip-tipped .qtip-icon{
+ border: 2px solid #285589;
+ background: #285589;
+ }
+
+ .qtip-tipped .qtip-icon .ui-icon{
+ background-color: #FBFBFB;
+ color: #555;
+ }
+
+
+/**
+ * Twitter Bootstrap style.
+ *
+ * Tested with IE 8, IE 9, Chrome 18, Firefox 9, Opera 11.
+ * Does not work with IE 7.
+ */
+.qtip-bootstrap{
+ /** Taken from Bootstrap body */
+ font-size: 14px;
+ line-height: 20px;
+ color: #333333;
+
+ /** Taken from Bootstrap .popover */
+ padding: 1px;
+ background-color: #ffffff;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+ -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ -webkit-background-clip: padding-box;
+ -moz-background-clip: padding;
+ background-clip: padding-box;
+}
+
+ .qtip-bootstrap .qtip-titlebar{
+ /** Taken from Bootstrap .popover-title */
+ padding: 8px 14px;
+ margin: 0;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 18px;
+ background-color: #f7f7f7;
+ border-bottom: 1px solid #ebebeb;
+ -webkit-border-radius: 5px 5px 0 0;
+ -moz-border-radius: 5px 5px 0 0;
+ border-radius: 5px 5px 0 0;
+ }
+
+ .qtip-bootstrap .qtip-titlebar .qtip-close{
+ /**
+ * Overrides qTip2:
+ * .qtip-titlebar .qtip-close{
+ * [...]
+ * right: 4px;
+ * top: 50%;
+ * [...]
+ * border-style: solid;
+ * }
+ */
+ right: 11px;
+ top: 45%;
+ border-style: none;
+ }
+
+ .qtip-bootstrap .qtip-content{
+ /** Taken from Bootstrap .popover-content */
+ padding: 9px 14px;
+ }
+
+ .qtip-bootstrap .qtip-icon{
+ /**
+ * Overrides qTip2:
+ * .qtip-default .qtip-icon {
+ * border-color: #CCC;
+ * background: #F1F1F1;
+ * color: #777;
+ * }
+ */
+ background: transparent;
+ }
+
+ .qtip-bootstrap .qtip-icon .ui-icon{
+ /**
+ * Overrides qTip2:
+ * .qtip-icon .ui-icon{
+ * width: 18px;
+ * height: 14px;
+ * }
+ */
+ width: auto;
+ height: auto;
+
+ /* Taken from Bootstrap .close */
+ float: right;
+ font-size: 20px;
+ font-weight: bold;
+ line-height: 18px;
+ color: #000000;
+ text-shadow: 0 1px 0 #ffffff;
+ opacity: 0.2;
+ filter: alpha(opacity=20);
+ }
+
+ .qtip-bootstrap .qtip-icon .ui-icon:hover{
+ /* Taken from Bootstrap .close:hover */
+ color: #000000;
+ text-decoration: none;
+ cursor: pointer;
+ opacity: 0.4;
+ filter: alpha(opacity=40);
+ }
+
+
+/* IE9 fix - removes all filters */
+.qtip:not(.ie9haxors) div.qtip-content,
+.qtip:not(.ie9haxors) div.qtip-titlebar{
+ filter: none;
+ -ms-filter: none;
+}
diff --git a/www/wiki/vendor/onoi/shared-resources/res/jquery.qtip/extended/jquery.qtip.js b/www/wiki/vendor/onoi/shared-resources/res/jquery.qtip/extended/jquery.qtip.js
new file mode 100644
index 00000000..d31692f7
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/jquery.qtip/extended/jquery.qtip.js
@@ -0,0 +1,2330 @@
+/*
+ * qTip2 - Pretty powerful tooltips - v3.0.3
+ * http://qtip2.com
+ *
+ * Copyright (c) 2016
+ * Released under the MIT licenses
+ * http://jquery.org/license
+ *
+ * Date: Thu May 19 2016 09:02 EDT-0400
+ * Plugins: viewport svg
+ * Styles: core basic css3
+ */
+/*global window: false, jQuery: false, console: false, define: false */
+
+/* Cache window, document, undefined */
+(function( window, document, undefined ) {
+
+// Uses AMD or browser globals to create a jQuery plugin.
+(function( factory ) {
+ "use strict";
+ if(typeof define === 'function' && define.amd) {
+ define(['jquery'], factory);
+ }
+ else if(jQuery && !jQuery.fn.qtip) {
+ factory(jQuery);
+ }
+}
+(function($) {
+ "use strict"; // Enable ECMAScript "strict" operation for this function. See more: http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
+;// Munge the primitives - Paul Irish tip
+var TRUE = true,
+FALSE = false,
+NULL = null,
+
+// Common variables
+X = 'x', Y = 'y',
+WIDTH = 'width',
+HEIGHT = 'height',
+
+// Positioning sides
+TOP = 'top',
+LEFT = 'left',
+BOTTOM = 'bottom',
+RIGHT = 'right',
+CENTER = 'center',
+
+// Position adjustment types
+FLIP = 'flip',
+FLIPINVERT = 'flipinvert',
+SHIFT = 'shift',
+
+// Shortcut vars
+QTIP, PROTOTYPE, CORNER, CHECKS,
+PLUGINS = {},
+NAMESPACE = 'qtip',
+ATTR_HAS = 'data-hasqtip',
+ATTR_ID = 'data-qtip-id',
+WIDGET = ['ui-widget', 'ui-tooltip'],
+SELECTOR = '.'+NAMESPACE,
+INACTIVE_EVENTS = 'click dblclick mousedown mouseup mousemove mouseleave mouseenter'.split(' '),
+
+CLASS_FIXED = NAMESPACE+'-fixed',
+CLASS_DEFAULT = NAMESPACE + '-default',
+CLASS_FOCUS = NAMESPACE + '-focus',
+CLASS_HOVER = NAMESPACE + '-hover',
+CLASS_DISABLED = NAMESPACE+'-disabled',
+
+replaceSuffix = '_replacedByqTip',
+oldtitle = 'oldtitle',
+trackingBound,
+
+// Browser detection
+BROWSER = {
+ /*
+ * IE version detection
+ *
+ * Adapted from: http://ajaxian.com/archives/attack-of-the-ie-conditional-comment
+ * Credit to James Padolsey for the original implemntation!
+ */
+ ie: (function() {
+ /* eslint-disable no-empty */
+ var v, i;
+ for (
+ v = 4, i = document.createElement('div');
+ (i.innerHTML = '<!--[if gt IE ' + v + ']><i></i><![endif]-->') && i.getElementsByTagName('i')[0];
+ v+=1
+ ) {}
+ return v > 4 ? v : NaN;
+ /* eslint-enable no-empty */
+ })(),
+
+ /*
+ * iOS version detection
+ */
+ iOS: parseFloat(
+ ('' + (/CPU.*OS ([0-9_]{1,5})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0,''])[1])
+ .replace('undefined', '3_2').replace('_', '.').replace('_', '')
+ ) || FALSE
+};
+;function QTip(target, options, id, attr) {
+ // Elements and ID
+ this.id = id;
+ this.target = target;
+ this.tooltip = NULL;
+ this.elements = { target: target };
+
+ // Internal constructs
+ this._id = NAMESPACE + '-' + id;
+ this.timers = { img: {} };
+ this.options = options;
+ this.plugins = {};
+
+ // Cache object
+ this.cache = {
+ event: {},
+ target: $(),
+ disabled: FALSE,
+ attr: attr,
+ onTooltip: FALSE,
+ lastClass: ''
+ };
+
+ // Set the initial flags
+ this.rendered = this.destroyed = this.disabled = this.waiting =
+ this.hiddenDuringWait = this.positioning = this.triggering = FALSE;
+}
+PROTOTYPE = QTip.prototype;
+
+PROTOTYPE._when = function(deferreds) {
+ return $.when.apply($, deferreds);
+};
+
+PROTOTYPE.render = function(show) {
+ if(this.rendered || this.destroyed) { return this; } // If tooltip has already been rendered, exit
+
+ var self = this,
+ options = this.options,
+ cache = this.cache,
+ elements = this.elements,
+ text = options.content.text,
+ title = options.content.title,
+ button = options.content.button,
+ posOptions = options.position,
+ deferreds = [];
+
+ // Add ARIA attributes to target
+ $.attr(this.target[0], 'aria-describedby', this._id);
+
+ // Create public position object that tracks current position corners
+ cache.posClass = this._createPosClass(
+ (this.position = { my: posOptions.my, at: posOptions.at }).my
+ );
+
+ // Create tooltip element
+ this.tooltip = elements.tooltip = $('<div/>', {
+ 'id': this._id,
+ 'class': [ NAMESPACE, CLASS_DEFAULT, options.style.classes, cache.posClass ].join(' '),
+ 'width': options.style.width || '',
+ 'height': options.style.height || '',
+ 'tracking': posOptions.target === 'mouse' && posOptions.adjust.mouse,
+
+ /* ARIA specific attributes */
+ 'role': 'alert',
+ 'aria-live': 'polite',
+ 'aria-atomic': FALSE,
+ 'aria-describedby': this._id + '-content',
+ 'aria-hidden': TRUE
+ })
+ .toggleClass(CLASS_DISABLED, this.disabled)
+ .attr(ATTR_ID, this.id)
+ .data(NAMESPACE, this)
+ .appendTo(posOptions.container)
+ .append(
+ // Create content element
+ elements.content = $('<div />', {
+ 'class': NAMESPACE + '-content',
+ 'id': this._id + '-content',
+ 'aria-atomic': TRUE
+ })
+ );
+
+ // Set rendered flag and prevent redundant reposition calls for now
+ this.rendered = -1;
+ this.positioning = TRUE;
+
+ // Create title...
+ if(title) {
+ this._createTitle();
+
+ // Update title only if its not a callback (called in toggle if so)
+ if(!$.isFunction(title)) {
+ deferreds.push( this._updateTitle(title, FALSE) );
+ }
+ }
+
+ // Create button
+ if(button) { this._createButton(); }
+
+ // Set proper rendered flag and update content if not a callback function (called in toggle)
+ if(!$.isFunction(text)) {
+ deferreds.push( this._updateContent(text, FALSE) );
+ }
+ this.rendered = TRUE;
+
+ // Setup widget classes
+ this._setWidget();
+
+ // Initialize 'render' plugins
+ $.each(PLUGINS, function(name) {
+ var instance;
+ if(this.initialize === 'render' && (instance = this(self))) {
+ self.plugins[name] = instance;
+ }
+ });
+
+ // Unassign initial events and assign proper events
+ this._unassignEvents();
+ this._assignEvents();
+
+ // When deferreds have completed
+ this._when(deferreds).then(function() {
+ // tooltiprender event
+ self._trigger('render');
+
+ // Reset flags
+ self.positioning = FALSE;
+
+ // Show tooltip if not hidden during wait period
+ if(!self.hiddenDuringWait && (options.show.ready || show)) {
+ self.toggle(TRUE, cache.event, FALSE);
+ }
+ self.hiddenDuringWait = FALSE;
+ });
+
+ // Expose API
+ QTIP.api[this.id] = this;
+
+ return this;
+};
+
+PROTOTYPE.destroy = function(immediate) {
+ // Set flag the signify destroy is taking place to plugins
+ // and ensure it only gets destroyed once!
+ if(this.destroyed) { return this.target; }
+
+ function process() {
+ if(this.destroyed) { return; }
+ this.destroyed = TRUE;
+
+ var target = this.target,
+ title = target.attr(oldtitle),
+ timer;
+
+ // Destroy tooltip if rendered
+ if(this.rendered) {
+ this.tooltip.stop(1,0).find('*').remove().end().remove();
+ }
+
+ // Destroy all plugins
+ $.each(this.plugins, function() {
+ this.destroy && this.destroy();
+ });
+
+ // Clear timers
+ for (timer in this.timers) {
+ if (this.timers.hasOwnProperty(timer)) {
+ clearTimeout(this.timers[timer]);
+ }
+ }
+
+ // Remove api object and ARIA attributes
+ target.removeData(NAMESPACE)
+ .removeAttr(ATTR_ID)
+ .removeAttr(ATTR_HAS)
+ .removeAttr('aria-describedby');
+
+ // Reset old title attribute if removed
+ if(this.options.suppress && title) {
+ target.attr('title', title).removeAttr(oldtitle);
+ }
+
+ // Remove qTip events associated with this API
+ this._unassignEvents();
+
+ // Remove ID from used id objects, and delete object references
+ // for better garbage collection and leak protection
+ this.options = this.elements = this.cache = this.timers =
+ this.plugins = this.mouse = NULL;
+
+ // Delete epoxsed API object
+ delete QTIP.api[this.id];
+ }
+
+ // If an immediate destroy is needed
+ if((immediate !== TRUE || this.triggering === 'hide') && this.rendered) {
+ this.tooltip.one('tooltiphidden', $.proxy(process, this));
+ !this.triggering && this.hide();
+ }
+
+ // If we're not in the process of hiding... process
+ else { process.call(this); }
+
+ return this.target;
+};
+;function invalidOpt(a) {
+ return a === NULL || $.type(a) !== 'object';
+}
+
+function invalidContent(c) {
+ return !($.isFunction(c) ||
+ c && c.attr ||
+ c.length ||
+ $.type(c) === 'object' && (c.jquery || c.then));
+}
+
+// Option object sanitizer
+function sanitizeOptions(opts) {
+ var content, text, ajax, once;
+
+ if(invalidOpt(opts)) { return FALSE; }
+
+ if(invalidOpt(opts.metadata)) {
+ opts.metadata = { type: opts.metadata };
+ }
+
+ if('content' in opts) {
+ content = opts.content;
+
+ if(invalidOpt(content) || content.jquery || content.done) {
+ text = invalidContent(content) ? FALSE : content;
+ content = opts.content = {
+ text: text
+ };
+ }
+ else { text = content.text; }
+
+ // DEPRECATED - Old content.ajax plugin functionality
+ // Converts it into the proper Deferred syntax
+ if('ajax' in content) {
+ ajax = content.ajax;
+ once = ajax && ajax.once !== FALSE;
+ delete content.ajax;
+
+ content.text = function(event, api) {
+ var loading = text || $(this).attr(api.options.content.attr) || 'Loading...',
+
+ deferred = $.ajax(
+ $.extend({}, ajax, { context: api })
+ )
+ .then(ajax.success, NULL, ajax.error)
+ .then(function(newContent) {
+ if(newContent && once) { api.set('content.text', newContent); }
+ return newContent;
+ },
+ function(xhr, status, error) {
+ if(api.destroyed || xhr.status === 0) { return; }
+ api.set('content.text', status + ': ' + error);
+ });
+
+ return !once ? (api.set('content.text', loading), deferred) : loading;
+ };
+ }
+
+ if('title' in content) {
+ if($.isPlainObject(content.title)) {
+ content.button = content.title.button;
+ content.title = content.title.text;
+ }
+
+ if(invalidContent(content.title || FALSE)) {
+ content.title = FALSE;
+ }
+ }
+ }
+
+ if('position' in opts && invalidOpt(opts.position)) {
+ opts.position = { my: opts.position, at: opts.position };
+ }
+
+ if('show' in opts && invalidOpt(opts.show)) {
+ opts.show = opts.show.jquery ? { target: opts.show } :
+ opts.show === TRUE ? { ready: TRUE } : { event: opts.show };
+ }
+
+ if('hide' in opts && invalidOpt(opts.hide)) {
+ opts.hide = opts.hide.jquery ? { target: opts.hide } : { event: opts.hide };
+ }
+
+ if('style' in opts && invalidOpt(opts.style)) {
+ opts.style = { classes: opts.style };
+ }
+
+ // Sanitize plugin options
+ $.each(PLUGINS, function() {
+ this.sanitize && this.sanitize(opts);
+ });
+
+ return opts;
+}
+
+// Setup builtin .set() option checks
+CHECKS = PROTOTYPE.checks = {
+ builtin: {
+ // Core checks
+ '^id$': function(obj, o, v, prev) {
+ var id = v === TRUE ? QTIP.nextid : v,
+ newId = NAMESPACE + '-' + id;
+
+ if(id !== FALSE && id.length > 0 && !$('#'+newId).length) {
+ this._id = newId;
+
+ if(this.rendered) {
+ this.tooltip[0].id = this._id;
+ this.elements.content[0].id = this._id + '-content';
+ this.elements.title[0].id = this._id + '-title';
+ }
+ }
+ else { obj[o] = prev; }
+ },
+ '^prerender': function(obj, o, v) {
+ v && !this.rendered && this.render(this.options.show.ready);
+ },
+
+ // Content checks
+ '^content.text$': function(obj, o, v) {
+ this._updateContent(v);
+ },
+ '^content.attr$': function(obj, o, v, prev) {
+ if(this.options.content.text === this.target.attr(prev)) {
+ this._updateContent( this.target.attr(v) );
+ }
+ },
+ '^content.title$': function(obj, o, v) {
+ // Remove title if content is null
+ if(!v) { return this._removeTitle(); }
+
+ // If title isn't already created, create it now and update
+ v && !this.elements.title && this._createTitle();
+ this._updateTitle(v);
+ },
+ '^content.button$': function(obj, o, v) {
+ this._updateButton(v);
+ },
+ '^content.title.(text|button)$': function(obj, o, v) {
+ this.set('content.'+o, v); // Backwards title.text/button compat
+ },
+
+ // Position checks
+ '^position.(my|at)$': function(obj, o, v){
+ if('string' === typeof v) {
+ this.position[o] = obj[o] = new CORNER(v, o === 'at');
+ }
+ },
+ '^position.container$': function(obj, o, v){
+ this.rendered && this.tooltip.appendTo(v);
+ },
+
+ // Show checks
+ '^show.ready$': function(obj, o, v) {
+ v && (!this.rendered && this.render(TRUE) || this.toggle(TRUE));
+ },
+
+ // Style checks
+ '^style.classes$': function(obj, o, v, p) {
+ this.rendered && this.tooltip.removeClass(p).addClass(v);
+ },
+ '^style.(width|height)': function(obj, o, v) {
+ this.rendered && this.tooltip.css(o, v);
+ },
+ '^style.widget|content.title': function() {
+ this.rendered && this._setWidget();
+ },
+ '^style.def': function(obj, o, v) {
+ this.rendered && this.tooltip.toggleClass(CLASS_DEFAULT, !!v);
+ },
+
+ // Events check
+ '^events.(render|show|move|hide|focus|blur)$': function(obj, o, v) {
+ this.rendered && this.tooltip[($.isFunction(v) ? '' : 'un') + 'bind']('tooltip'+o, v);
+ },
+
+ // Properties which require event reassignment
+ '^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)': function() {
+ if(!this.rendered) { return; }
+
+ // Set tracking flag
+ var posOptions = this.options.position;
+ this.tooltip.attr('tracking', posOptions.target === 'mouse' && posOptions.adjust.mouse);
+
+ // Reassign events
+ this._unassignEvents();
+ this._assignEvents();
+ }
+ }
+};
+
+// Dot notation converter
+function convertNotation(options, notation) {
+ var i = 0, obj, option = options,
+
+ // Split notation into array
+ levels = notation.split('.');
+
+ // Loop through
+ while(option = option[ levels[i++] ]) {
+ if(i < levels.length) { obj = option; }
+ }
+
+ return [obj || options, levels.pop()];
+}
+
+PROTOTYPE.get = function(notation) {
+ if(this.destroyed) { return this; }
+
+ var o = convertNotation(this.options, notation.toLowerCase()),
+ result = o[0][ o[1] ];
+
+ return result.precedance ? result.string() : result;
+};
+
+function setCallback(notation, args) {
+ var category, rule, match;
+
+ for(category in this.checks) {
+ if (!this.checks.hasOwnProperty(category)) { continue; }
+
+ for(rule in this.checks[category]) {
+ if (!this.checks[category].hasOwnProperty(rule)) { continue; }
+
+ if(match = (new RegExp(rule, 'i')).exec(notation)) {
+ args.push(match);
+
+ if(category === 'builtin' || this.plugins[category]) {
+ this.checks[category][rule].apply(
+ this.plugins[category] || this, args
+ );
+ }
+ }
+ }
+ }
+}
+
+var rmove = /^position\.(my|at|adjust|target|container|viewport)|style|content|show\.ready/i,
+ rrender = /^prerender|show\.ready/i;
+
+PROTOTYPE.set = function(option, value) {
+ if(this.destroyed) { return this; }
+
+ var rendered = this.rendered,
+ reposition = FALSE,
+ options = this.options,
+ name;
+
+ // Convert singular option/value pair into object form
+ if('string' === typeof option) {
+ name = option; option = {}; option[name] = value;
+ }
+ else { option = $.extend({}, option); }
+
+ // Set all of the defined options to their new values
+ $.each(option, function(notation, val) {
+ if(rendered && rrender.test(notation)) {
+ delete option[notation]; return;
+ }
+
+ // Set new obj value
+ var obj = convertNotation(options, notation.toLowerCase()), previous;
+ previous = obj[0][ obj[1] ];
+ obj[0][ obj[1] ] = val && val.nodeType ? $(val) : val;
+
+ // Also check if we need to reposition
+ reposition = rmove.test(notation) || reposition;
+
+ // Set the new params for the callback
+ option[notation] = [obj[0], obj[1], val, previous];
+ });
+
+ // Re-sanitize options
+ sanitizeOptions(options);
+
+ /*
+ * Execute any valid callbacks for the set options
+ * Also set positioning flag so we don't get loads of redundant repositioning calls.
+ */
+ this.positioning = TRUE;
+ $.each(option, $.proxy(setCallback, this));
+ this.positioning = FALSE;
+
+ // Update position if needed
+ if(this.rendered && this.tooltip[0].offsetWidth > 0 && reposition) {
+ this.reposition( options.position.target === 'mouse' ? NULL : this.cache.event );
+ }
+
+ return this;
+};
+;PROTOTYPE._update = function(content, element) {
+ var self = this,
+ cache = this.cache;
+
+ // Make sure tooltip is rendered and content is defined. If not return
+ if(!this.rendered || !content) { return FALSE; }
+
+ // Use function to parse content
+ if($.isFunction(content)) {
+ content = content.call(this.elements.target, cache.event, this) || '';
+ }
+
+ // Handle deferred content
+ if($.isFunction(content.then)) {
+ cache.waiting = TRUE;
+ return content.then(function(c) {
+ cache.waiting = FALSE;
+ return self._update(c, element);
+ }, NULL, function(e) {
+ return self._update(e, element);
+ });
+ }
+
+ // If content is null... return false
+ if(content === FALSE || !content && content !== '') { return FALSE; }
+
+ // Append new content if its a DOM array and show it if hidden
+ if(content.jquery && content.length > 0) {
+ element.empty().append(
+ content.css({ display: 'block', visibility: 'visible' })
+ );
+ }
+
+ // Content is a regular string, insert the new content
+ else { element.html(content); }
+
+ // Wait for content to be loaded, and reposition
+ return this._waitForContent(element).then(function(images) {
+ if(self.rendered && self.tooltip[0].offsetWidth > 0) {
+ self.reposition(cache.event, !images.length);
+ }
+ });
+};
+
+PROTOTYPE._waitForContent = function(element) {
+ var cache = this.cache;
+
+ // Set flag
+ cache.waiting = TRUE;
+
+ // If imagesLoaded is included, ensure images have loaded and return promise
+ return ( $.fn.imagesLoaded ? element.imagesLoaded() : new $.Deferred().resolve([]) )
+ .done(function() { cache.waiting = FALSE; })
+ .promise();
+};
+
+PROTOTYPE._updateContent = function(content, reposition) {
+ this._update(content, this.elements.content, reposition);
+};
+
+PROTOTYPE._updateTitle = function(content, reposition) {
+ if(this._update(content, this.elements.title, reposition) === FALSE) {
+ this._removeTitle(FALSE);
+ }
+};
+
+PROTOTYPE._createTitle = function()
+{
+ var elements = this.elements,
+ id = this._id+'-title';
+
+ // Destroy previous title element, if present
+ if(elements.titlebar) { this._removeTitle(); }
+
+ // Create title bar and title elements
+ elements.titlebar = $('<div />', {
+ 'class': NAMESPACE + '-titlebar ' + (this.options.style.widget ? createWidgetClass('header') : '')
+ })
+ .append(
+ elements.title = $('<div />', {
+ 'id': id,
+ 'class': NAMESPACE + '-title',
+ 'aria-atomic': TRUE
+ })
+ )
+ .insertBefore(elements.content)
+
+ // Button-specific events
+ .delegate('.qtip-close', 'mousedown keydown mouseup keyup mouseout', function(event) {
+ $(this).toggleClass('ui-state-active ui-state-focus', event.type.substr(-4) === 'down');
+ })
+ .delegate('.qtip-close', 'mouseover mouseout', function(event){
+ $(this).toggleClass('ui-state-hover', event.type === 'mouseover');
+ });
+
+ // Create button if enabled
+ if(this.options.content.button) { this._createButton(); }
+};
+
+PROTOTYPE._removeTitle = function(reposition)
+{
+ var elements = this.elements;
+
+ if(elements.title) {
+ elements.titlebar.remove();
+ elements.titlebar = elements.title = elements.button = NULL;
+
+ // Reposition if enabled
+ if(reposition !== FALSE) { this.reposition(); }
+ }
+};
+;PROTOTYPE._createPosClass = function(my) {
+ return NAMESPACE + '-pos-' + (my || this.options.position.my).abbrev();
+};
+
+PROTOTYPE.reposition = function(event, effect) {
+ if(!this.rendered || this.positioning || this.destroyed) { return this; }
+
+ // Set positioning flag
+ this.positioning = TRUE;
+
+ var cache = this.cache,
+ tooltip = this.tooltip,
+ posOptions = this.options.position,
+ target = posOptions.target,
+ my = posOptions.my,
+ at = posOptions.at,
+ viewport = posOptions.viewport,
+ container = posOptions.container,
+ adjust = posOptions.adjust,
+ method = adjust.method.split(' '),
+ tooltipWidth = tooltip.outerWidth(FALSE),
+ tooltipHeight = tooltip.outerHeight(FALSE),
+ targetWidth = 0,
+ targetHeight = 0,
+ type = tooltip.css('position'),
+ position = { left: 0, top: 0 },
+ visible = tooltip[0].offsetWidth > 0,
+ isScroll = event && event.type === 'scroll',
+ win = $(window),
+ doc = container[0].ownerDocument,
+ mouse = this.mouse,
+ pluginCalculations, offset, adjusted, newClass;
+
+ // Check if absolute position was passed
+ if($.isArray(target) && target.length === 2) {
+ // Force left top and set position
+ at = { x: LEFT, y: TOP };
+ position = { left: target[0], top: target[1] };
+ }
+
+ // Check if mouse was the target
+ else if(target === 'mouse') {
+ // Force left top to allow flipping
+ at = { x: LEFT, y: TOP };
+
+ // Use the mouse origin that caused the show event, if distance hiding is enabled
+ if((!adjust.mouse || this.options.hide.distance) && cache.origin && cache.origin.pageX) {
+ event = cache.origin;
+ }
+
+ // Use cached event for resize/scroll events
+ else if(!event || event && (event.type === 'resize' || event.type === 'scroll')) {
+ event = cache.event;
+ }
+
+ // Otherwise, use the cached mouse coordinates if available
+ else if(mouse && mouse.pageX) {
+ event = mouse;
+ }
+
+ // Calculate body and container offset and take them into account below
+ if(type !== 'static') { position = container.offset(); }
+ if(doc.body.offsetWidth !== (window.innerWidth || doc.documentElement.clientWidth)) {
+ offset = $(document.body).offset();
+ }
+
+ // Use event coordinates for position
+ position = {
+ left: event.pageX - position.left + (offset && offset.left || 0),
+ top: event.pageY - position.top + (offset && offset.top || 0)
+ };
+
+ // Scroll events are a pain, some browsers
+ if(adjust.mouse && isScroll && mouse) {
+ position.left -= (mouse.scrollX || 0) - win.scrollLeft();
+ position.top -= (mouse.scrollY || 0) - win.scrollTop();
+ }
+ }
+
+ // Target wasn't mouse or absolute...
+ else {
+ // Check if event targetting is being used
+ if(target === 'event') {
+ if(event && event.target && event.type !== 'scroll' && event.type !== 'resize') {
+ cache.target = $(event.target);
+ }
+ else if(!event.target) {
+ cache.target = this.elements.target;
+ }
+ }
+ else if(target !== 'event'){
+ cache.target = $(target.jquery ? target : this.elements.target);
+ }
+ target = cache.target;
+
+ // Parse the target into a jQuery object and make sure there's an element present
+ target = $(target).eq(0);
+ if(target.length === 0) { return this; }
+
+ // Check if window or document is the target
+ else if(target[0] === document || target[0] === window) {
+ targetWidth = BROWSER.iOS ? window.innerWidth : target.width();
+ targetHeight = BROWSER.iOS ? window.innerHeight : target.height();
+
+ if(target[0] === window) {
+ position = {
+ top: (viewport || target).scrollTop(),
+ left: (viewport || target).scrollLeft()
+ };
+ }
+ }
+
+ // Check if the target is an <AREA> element
+ else if(PLUGINS.imagemap && target.is('area')) {
+ pluginCalculations = PLUGINS.imagemap(this, target, at, PLUGINS.viewport ? method : FALSE);
+ }
+
+ // Check if the target is an SVG element
+ else if(PLUGINS.svg && target && target[0].ownerSVGElement) {
+ pluginCalculations = PLUGINS.svg(this, target, at, PLUGINS.viewport ? method : FALSE);
+ }
+
+ // Otherwise use regular jQuery methods
+ else {
+ targetWidth = target.outerWidth(FALSE);
+ targetHeight = target.outerHeight(FALSE);
+ position = target.offset();
+ }
+
+ // Parse returned plugin values into proper variables
+ if(pluginCalculations) {
+ targetWidth = pluginCalculations.width;
+ targetHeight = pluginCalculations.height;
+ offset = pluginCalculations.offset;
+ position = pluginCalculations.position;
+ }
+
+ // Adjust position to take into account offset parents
+ position = this.reposition.offset(target, position, container);
+
+ // Adjust for position.fixed tooltips (and also iOS scroll bug in v3.2-4.0 & v4.3-4.3.2)
+ if(BROWSER.iOS > 3.1 && BROWSER.iOS < 4.1 ||
+ BROWSER.iOS >= 4.3 && BROWSER.iOS < 4.33 ||
+ !BROWSER.iOS && type === 'fixed'
+ ){
+ position.left -= win.scrollLeft();
+ position.top -= win.scrollTop();
+ }
+
+ // Adjust position relative to target
+ if(!pluginCalculations || pluginCalculations && pluginCalculations.adjustable !== FALSE) {
+ position.left += at.x === RIGHT ? targetWidth : at.x === CENTER ? targetWidth / 2 : 0;
+ position.top += at.y === BOTTOM ? targetHeight : at.y === CENTER ? targetHeight / 2 : 0;
+ }
+ }
+
+ // Adjust position relative to tooltip
+ position.left += adjust.x + (my.x === RIGHT ? -tooltipWidth : my.x === CENTER ? -tooltipWidth / 2 : 0);
+ position.top += adjust.y + (my.y === BOTTOM ? -tooltipHeight : my.y === CENTER ? -tooltipHeight / 2 : 0);
+
+ // Use viewport adjustment plugin if enabled
+ if(PLUGINS.viewport) {
+ adjusted = position.adjusted = PLUGINS.viewport(
+ this, position, posOptions, targetWidth, targetHeight, tooltipWidth, tooltipHeight
+ );
+
+ // Apply offsets supplied by positioning plugin (if used)
+ if(offset && adjusted.left) { position.left += offset.left; }
+ if(offset && adjusted.top) { position.top += offset.top; }
+
+ // Apply any new 'my' position
+ if(adjusted.my) { this.position.my = adjusted.my; }
+ }
+
+ // Viewport adjustment is disabled, set values to zero
+ else { position.adjusted = { left: 0, top: 0 }; }
+
+ // Set tooltip position class if it's changed
+ if(cache.posClass !== (newClass = this._createPosClass(this.position.my))) {
+ cache.posClass = newClass;
+ tooltip.removeClass(cache.posClass).addClass(newClass);
+ }
+
+ // tooltipmove event
+ if(!this._trigger('move', [position, viewport.elem || viewport], event)) { return this; }
+ delete position.adjusted;
+
+ // If effect is disabled, target it mouse, no animation is defined or positioning gives NaN out, set CSS directly
+ if(effect === FALSE || !visible || isNaN(position.left) || isNaN(position.top) || target === 'mouse' || !$.isFunction(posOptions.effect)) {
+ tooltip.css(position);
+ }
+
+ // Use custom function if provided
+ else if($.isFunction(posOptions.effect)) {
+ posOptions.effect.call(tooltip, this, $.extend({}, position));
+ tooltip.queue(function(next) {
+ // Reset attributes to avoid cross-browser rendering bugs
+ $(this).css({ opacity: '', height: '' });
+ if(BROWSER.ie) { this.style.removeAttribute('filter'); }
+
+ next();
+ });
+ }
+
+ // Set positioning flag
+ this.positioning = FALSE;
+
+ return this;
+};
+
+// Custom (more correct for qTip!) offset calculator
+PROTOTYPE.reposition.offset = function(elem, pos, container) {
+ if(!container[0]) { return pos; }
+
+ var ownerDocument = $(elem[0].ownerDocument),
+ quirks = !!BROWSER.ie && document.compatMode !== 'CSS1Compat',
+ parent = container[0],
+ scrolled, position, parentOffset, overflow;
+
+ function scroll(e, i) {
+ pos.left += i * e.scrollLeft();
+ pos.top += i * e.scrollTop();
+ }
+
+ // Compensate for non-static containers offset
+ do {
+ if((position = $.css(parent, 'position')) !== 'static') {
+ if(position === 'fixed') {
+ parentOffset = parent.getBoundingClientRect();
+ scroll(ownerDocument, -1);
+ }
+ else {
+ parentOffset = $(parent).position();
+ parentOffset.left += parseFloat($.css(parent, 'borderLeftWidth')) || 0;
+ parentOffset.top += parseFloat($.css(parent, 'borderTopWidth')) || 0;
+ }
+
+ pos.left -= parentOffset.left + (parseFloat($.css(parent, 'marginLeft')) || 0);
+ pos.top -= parentOffset.top + (parseFloat($.css(parent, 'marginTop')) || 0);
+
+ // If this is the first parent element with an overflow of "scroll" or "auto", store it
+ if(!scrolled && (overflow = $.css(parent, 'overflow')) !== 'hidden' && overflow !== 'visible') { scrolled = $(parent); }
+ }
+ }
+ while(parent = parent.offsetParent);
+
+ // Compensate for containers scroll if it also has an offsetParent (or in IE quirks mode)
+ if(scrolled && (scrolled[0] !== ownerDocument[0] || quirks)) {
+ scroll(scrolled, 1);
+ }
+
+ return pos;
+};
+
+// Corner class
+var C = (CORNER = PROTOTYPE.reposition.Corner = function(corner, forceY) {
+ corner = ('' + corner).replace(/([A-Z])/, ' $1').replace(/middle/gi, CENTER).toLowerCase();
+ this.x = (corner.match(/left|right/i) || corner.match(/center/) || ['inherit'])[0].toLowerCase();
+ this.y = (corner.match(/top|bottom|center/i) || ['inherit'])[0].toLowerCase();
+ this.forceY = !!forceY;
+
+ var f = corner.charAt(0);
+ this.precedance = f === 't' || f === 'b' ? Y : X;
+}).prototype;
+
+C.invert = function(z, center) {
+ this[z] = this[z] === LEFT ? RIGHT : this[z] === RIGHT ? LEFT : center || this[z];
+};
+
+C.string = function(join) {
+ var x = this.x, y = this.y;
+
+ var result = x !== y ?
+ x === 'center' || y !== 'center' && (this.precedance === Y || this.forceY) ?
+ [y,x] :
+ [x,y] :
+ [x];
+
+ return join !== false ? result.join(' ') : result;
+};
+
+C.abbrev = function() {
+ var result = this.string(false);
+ return result[0].charAt(0) + (result[1] && result[1].charAt(0) || '');
+};
+
+C.clone = function() {
+ return new CORNER( this.string(), this.forceY );
+};
+
+;
+PROTOTYPE.toggle = function(state, event) {
+ var cache = this.cache,
+ options = this.options,
+ tooltip = this.tooltip;
+
+ // Try to prevent flickering when tooltip overlaps show element
+ if(event) {
+ if((/over|enter/).test(event.type) && cache.event && (/out|leave/).test(cache.event.type) &&
+ options.show.target.add(event.target).length === options.show.target.length &&
+ tooltip.has(event.relatedTarget).length) {
+ return this;
+ }
+
+ // Cache event
+ cache.event = $.event.fix(event);
+ }
+
+ // If we're currently waiting and we've just hidden... stop it
+ this.waiting && !state && (this.hiddenDuringWait = TRUE);
+
+ // Render the tooltip if showing and it isn't already
+ if(!this.rendered) { return state ? this.render(1) : this; }
+ else if(this.destroyed || this.disabled) { return this; }
+
+ var type = state ? 'show' : 'hide',
+ opts = this.options[type],
+ posOptions = this.options.position,
+ contentOptions = this.options.content,
+ width = this.tooltip.css('width'),
+ visible = this.tooltip.is(':visible'),
+ animate = state || opts.target.length === 1,
+ sameTarget = !event || opts.target.length < 2 || cache.target[0] === event.target,
+ identicalState, allow, after;
+
+ // Detect state if valid one isn't provided
+ if((typeof state).search('boolean|number')) { state = !visible; }
+
+ // Check if the tooltip is in an identical state to the new would-be state
+ identicalState = !tooltip.is(':animated') && visible === state && sameTarget;
+
+ // Fire tooltip(show/hide) event and check if destroyed
+ allow = !identicalState ? !!this._trigger(type, [90]) : NULL;
+
+ // Check to make sure the tooltip wasn't destroyed in the callback
+ if(this.destroyed) { return this; }
+
+ // If the user didn't stop the method prematurely and we're showing the tooltip, focus it
+ if(allow !== FALSE && state) { this.focus(event); }
+
+ // If the state hasn't changed or the user stopped it, return early
+ if(!allow || identicalState) { return this; }
+
+ // Set ARIA hidden attribute
+ $.attr(tooltip[0], 'aria-hidden', !!!state);
+
+ // Execute state specific properties
+ if(state) {
+ // Store show origin coordinates
+ this.mouse && (cache.origin = $.event.fix(this.mouse));
+
+ // Update tooltip content & title if it's a dynamic function
+ if($.isFunction(contentOptions.text)) { this._updateContent(contentOptions.text, FALSE); }
+ if($.isFunction(contentOptions.title)) { this._updateTitle(contentOptions.title, FALSE); }
+
+ // Cache mousemove events for positioning purposes (if not already tracking)
+ if(!trackingBound && posOptions.target === 'mouse' && posOptions.adjust.mouse) {
+ $(document).bind('mousemove.'+NAMESPACE, this._storeMouse);
+ trackingBound = TRUE;
+ }
+
+ // Update the tooltip position (set width first to prevent viewport/max-width issues)
+ if(!width) { tooltip.css('width', tooltip.outerWidth(FALSE)); }
+ this.reposition(event, arguments[2]);
+ if(!width) { tooltip.css('width', ''); }
+
+ // Hide other tooltips if tooltip is solo
+ if(!!opts.solo) {
+ (typeof opts.solo === 'string' ? $(opts.solo) : $(SELECTOR, opts.solo))
+ .not(tooltip).not(opts.target).qtip('hide', new $.Event('tooltipsolo'));
+ }
+ }
+ else {
+ // Clear show timer if we're hiding
+ clearTimeout(this.timers.show);
+
+ // Remove cached origin on hide
+ delete cache.origin;
+
+ // Remove mouse tracking event if not needed (all tracking qTips are hidden)
+ if(trackingBound && !$(SELECTOR+'[tracking="true"]:visible', opts.solo).not(tooltip).length) {
+ $(document).unbind('mousemove.'+NAMESPACE);
+ trackingBound = FALSE;
+ }
+
+ // Blur the tooltip
+ this.blur(event);
+ }
+
+ // Define post-animation, state specific properties
+ after = $.proxy(function() {
+ if(state) {
+ // Prevent antialias from disappearing in IE by removing filter
+ if(BROWSER.ie) { tooltip[0].style.removeAttribute('filter'); }
+
+ // Remove overflow setting to prevent tip bugs
+ tooltip.css('overflow', '');
+
+ // Autofocus elements if enabled
+ if('string' === typeof opts.autofocus) {
+ $(this.options.show.autofocus, tooltip).focus();
+ }
+
+ // If set, hide tooltip when inactive for delay period
+ this.options.show.target.trigger('qtip-'+this.id+'-inactive');
+ }
+ else {
+ // Reset CSS states
+ tooltip.css({
+ display: '',
+ visibility: '',
+ opacity: '',
+ left: '',
+ top: ''
+ });
+ }
+
+ // tooltipvisible/tooltiphidden events
+ this._trigger(state ? 'visible' : 'hidden');
+ }, this);
+
+ // If no effect type is supplied, use a simple toggle
+ if(opts.effect === FALSE || animate === FALSE) {
+ tooltip[ type ]();
+ after();
+ }
+
+ // Use custom function if provided
+ else if($.isFunction(opts.effect)) {
+ tooltip.stop(1, 1);
+ opts.effect.call(tooltip, this);
+ tooltip.queue('fx', function(n) {
+ after(); n();
+ });
+ }
+
+ // Use basic fade function by default
+ else { tooltip.fadeTo(90, state ? 1 : 0, after); }
+
+ // If inactive hide method is set, active it
+ if(state) { opts.target.trigger('qtip-'+this.id+'-inactive'); }
+
+ return this;
+};
+
+PROTOTYPE.show = function(event) { return this.toggle(TRUE, event); };
+
+PROTOTYPE.hide = function(event) { return this.toggle(FALSE, event); };
+;PROTOTYPE.focus = function(event) {
+ if(!this.rendered || this.destroyed) { return this; }
+
+ var qtips = $(SELECTOR),
+ tooltip = this.tooltip,
+ curIndex = parseInt(tooltip[0].style.zIndex, 10),
+ newIndex = QTIP.zindex + qtips.length;
+
+ // Only update the z-index if it has changed and tooltip is not already focused
+ if(!tooltip.hasClass(CLASS_FOCUS)) {
+ // tooltipfocus event
+ if(this._trigger('focus', [newIndex], event)) {
+ // Only update z-index's if they've changed
+ if(curIndex !== newIndex) {
+ // Reduce our z-index's and keep them properly ordered
+ qtips.each(function() {
+ if(this.style.zIndex > curIndex) {
+ this.style.zIndex = this.style.zIndex - 1;
+ }
+ });
+
+ // Fire blur event for focused tooltip
+ qtips.filter('.' + CLASS_FOCUS).qtip('blur', event);
+ }
+
+ // Set the new z-index
+ tooltip.addClass(CLASS_FOCUS)[0].style.zIndex = newIndex;
+ }
+ }
+
+ return this;
+};
+
+PROTOTYPE.blur = function(event) {
+ if(!this.rendered || this.destroyed) { return this; }
+
+ // Set focused status to FALSE
+ this.tooltip.removeClass(CLASS_FOCUS);
+
+ // tooltipblur event
+ this._trigger('blur', [ this.tooltip.css('zIndex') ], event);
+
+ return this;
+};
+;PROTOTYPE.disable = function(state) {
+ if(this.destroyed) { return this; }
+
+ // If 'toggle' is passed, toggle the current state
+ if(state === 'toggle') {
+ state = !(this.rendered ? this.tooltip.hasClass(CLASS_DISABLED) : this.disabled);
+ }
+
+ // Disable if no state passed
+ else if('boolean' !== typeof state) {
+ state = TRUE;
+ }
+
+ if(this.rendered) {
+ this.tooltip.toggleClass(CLASS_DISABLED, state)
+ .attr('aria-disabled', state);
+ }
+
+ this.disabled = !!state;
+
+ return this;
+};
+
+PROTOTYPE.enable = function() { return this.disable(FALSE); };
+;PROTOTYPE._createButton = function()
+{
+ var self = this,
+ elements = this.elements,
+ tooltip = elements.tooltip,
+ button = this.options.content.button,
+ isString = typeof button === 'string',
+ close = isString ? button : 'Close tooltip';
+
+ if(elements.button) { elements.button.remove(); }
+
+ // Use custom button if one was supplied by user, else use default
+ if(button.jquery) {
+ elements.button = button;
+ }
+ else {
+ elements.button = $('<a />', {
+ 'class': 'qtip-close ' + (this.options.style.widget ? '' : NAMESPACE+'-icon'),
+ 'title': close,
+ 'aria-label': close
+ })
+ .prepend(
+ $('<span />', {
+ 'class': 'ui-icon ui-icon-close',
+ 'html': '&times;'
+ })
+ );
+ }
+
+ // Create button and setup attributes
+ elements.button.appendTo(elements.titlebar || tooltip)
+ .attr('role', 'button')
+ .click(function(event) {
+ if(!tooltip.hasClass(CLASS_DISABLED)) { self.hide(event); }
+ return FALSE;
+ });
+};
+
+PROTOTYPE._updateButton = function(button)
+{
+ // Make sure tooltip is rendered and if not, return
+ if(!this.rendered) { return FALSE; }
+
+ var elem = this.elements.button;
+ if(button) { this._createButton(); }
+ else { elem.remove(); }
+};
+;// Widget class creator
+function createWidgetClass(cls) {
+ return WIDGET.concat('').join(cls ? '-'+cls+' ' : ' ');
+}
+
+// Widget class setter method
+PROTOTYPE._setWidget = function()
+{
+ var on = this.options.style.widget,
+ elements = this.elements,
+ tooltip = elements.tooltip,
+ disabled = tooltip.hasClass(CLASS_DISABLED);
+
+ tooltip.removeClass(CLASS_DISABLED);
+ CLASS_DISABLED = on ? 'ui-state-disabled' : 'qtip-disabled';
+ tooltip.toggleClass(CLASS_DISABLED, disabled);
+
+ tooltip.toggleClass('ui-helper-reset '+createWidgetClass(), on).toggleClass(CLASS_DEFAULT, this.options.style.def && !on);
+
+ if(elements.content) {
+ elements.content.toggleClass( createWidgetClass('content'), on);
+ }
+ if(elements.titlebar) {
+ elements.titlebar.toggleClass( createWidgetClass('header'), on);
+ }
+ if(elements.button) {
+ elements.button.toggleClass(NAMESPACE+'-icon', !on);
+ }
+};
+;function delay(callback, duration) {
+ // If tooltip has displayed, start hide timer
+ if(duration > 0) {
+ return setTimeout(
+ $.proxy(callback, this), duration
+ );
+ }
+ else{ callback.call(this); }
+}
+
+function showMethod(event) {
+ if(this.tooltip.hasClass(CLASS_DISABLED)) { return; }
+
+ // Clear hide timers
+ clearTimeout(this.timers.show);
+ clearTimeout(this.timers.hide);
+
+ // Start show timer
+ this.timers.show = delay.call(this,
+ function() { this.toggle(TRUE, event); },
+ this.options.show.delay
+ );
+}
+
+function hideMethod(event) {
+ if(this.tooltip.hasClass(CLASS_DISABLED) || this.destroyed) { return; }
+
+ // Check if new target was actually the tooltip element
+ var relatedTarget = $(event.relatedTarget),
+ ontoTooltip = relatedTarget.closest(SELECTOR)[0] === this.tooltip[0],
+ ontoTarget = relatedTarget[0] === this.options.show.target[0];
+
+ // Clear timers and stop animation queue
+ clearTimeout(this.timers.show);
+ clearTimeout(this.timers.hide);
+
+ // Prevent hiding if tooltip is fixed and event target is the tooltip.
+ // Or if mouse positioning is enabled and cursor momentarily overlaps
+ if(this !== relatedTarget[0] &&
+ (this.options.position.target === 'mouse' && ontoTooltip) ||
+ this.options.hide.fixed && (
+ (/mouse(out|leave|move)/).test(event.type) && (ontoTooltip || ontoTarget))
+ )
+ {
+ /* eslint-disable no-empty */
+ try {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ } catch(e) {}
+ /* eslint-enable no-empty */
+
+ return;
+ }
+
+ // If tooltip has displayed, start hide timer
+ this.timers.hide = delay.call(this,
+ function() { this.toggle(FALSE, event); },
+ this.options.hide.delay,
+ this
+ );
+}
+
+function inactiveMethod(event) {
+ if(this.tooltip.hasClass(CLASS_DISABLED) || !this.options.hide.inactive) { return; }
+
+ // Clear timer
+ clearTimeout(this.timers.inactive);
+
+ this.timers.inactive = delay.call(this,
+ function(){ this.hide(event); },
+ this.options.hide.inactive
+ );
+}
+
+function repositionMethod(event) {
+ if(this.rendered && this.tooltip[0].offsetWidth > 0) { this.reposition(event); }
+}
+
+// Store mouse coordinates
+PROTOTYPE._storeMouse = function(event) {
+ (this.mouse = $.event.fix(event)).type = 'mousemove';
+ return this;
+};
+
+// Bind events
+PROTOTYPE._bind = function(targets, events, method, suffix, context) {
+ if(!targets || !method || !events.length) { return; }
+ var ns = '.' + this._id + (suffix ? '-'+suffix : '');
+ $(targets).bind(
+ (events.split ? events : events.join(ns + ' ')) + ns,
+ $.proxy(method, context || this)
+ );
+ return this;
+};
+PROTOTYPE._unbind = function(targets, suffix) {
+ targets && $(targets).unbind('.' + this._id + (suffix ? '-'+suffix : ''));
+ return this;
+};
+
+// Global delegation helper
+function delegate(selector, events, method) {
+ $(document.body).delegate(selector,
+ (events.split ? events : events.join('.'+NAMESPACE + ' ')) + '.'+NAMESPACE,
+ function() {
+ var api = QTIP.api[ $.attr(this, ATTR_ID) ];
+ api && !api.disabled && method.apply(api, arguments);
+ }
+ );
+}
+// Event trigger
+PROTOTYPE._trigger = function(type, args, event) {
+ var callback = new $.Event('tooltip'+type);
+ callback.originalEvent = event && $.extend({}, event) || this.cache.event || NULL;
+
+ this.triggering = type;
+ this.tooltip.trigger(callback, [this].concat(args || []));
+ this.triggering = FALSE;
+
+ return !callback.isDefaultPrevented();
+};
+
+PROTOTYPE._bindEvents = function(showEvents, hideEvents, showTargets, hideTargets, showCallback, hideCallback) {
+ // Get tasrgets that lye within both
+ var similarTargets = showTargets.filter( hideTargets ).add( hideTargets.filter(showTargets) ),
+ toggleEvents = [];
+
+ // If hide and show targets are the same...
+ if(similarTargets.length) {
+
+ // Filter identical show/hide events
+ $.each(hideEvents, function(i, type) {
+ var showIndex = $.inArray(type, showEvents);
+
+ // Both events are identical, remove from both hide and show events
+ // and append to toggleEvents
+ showIndex > -1 && toggleEvents.push( showEvents.splice( showIndex, 1 )[0] );
+ });
+
+ // Toggle events are special case of identical show/hide events, which happen in sequence
+ if(toggleEvents.length) {
+ // Bind toggle events to the similar targets
+ this._bind(similarTargets, toggleEvents, function(event) {
+ var state = this.rendered ? this.tooltip[0].offsetWidth > 0 : false;
+ (state ? hideCallback : showCallback).call(this, event);
+ });
+
+ // Remove the similar targets from the regular show/hide bindings
+ showTargets = showTargets.not(similarTargets);
+ hideTargets = hideTargets.not(similarTargets);
+ }
+ }
+
+ // Apply show/hide/toggle events
+ this._bind(showTargets, showEvents, showCallback);
+ this._bind(hideTargets, hideEvents, hideCallback);
+};
+
+PROTOTYPE._assignInitialEvents = function(event) {
+ var options = this.options,
+ showTarget = options.show.target,
+ hideTarget = options.hide.target,
+ showEvents = options.show.event ? $.trim('' + options.show.event).split(' ') : [],
+ hideEvents = options.hide.event ? $.trim('' + options.hide.event).split(' ') : [];
+
+ // Catch remove/removeqtip events on target element to destroy redundant tooltips
+ this._bind(this.elements.target, ['remove', 'removeqtip'], function() {
+ this.destroy(true);
+ }, 'destroy');
+
+ /*
+ * Make sure hoverIntent functions properly by using mouseleave as a hide event if
+ * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
+ */
+ if(/mouse(over|enter)/i.test(options.show.event) && !/mouse(out|leave)/i.test(options.hide.event)) {
+ hideEvents.push('mouseleave');
+ }
+
+ /*
+ * Also make sure initial mouse targetting works correctly by caching mousemove coords
+ * on show targets before the tooltip has rendered. Also set onTarget when triggered to
+ * keep mouse tracking working.
+ */
+ this._bind(showTarget, 'mousemove', function(moveEvent) {
+ this._storeMouse(moveEvent);
+ this.cache.onTarget = TRUE;
+ });
+
+ // Define hoverIntent function
+ function hoverIntent(hoverEvent) {
+ // Only continue if tooltip isn't disabled
+ if(this.disabled || this.destroyed) { return FALSE; }
+
+ // Cache the event data
+ this.cache.event = hoverEvent && $.event.fix(hoverEvent);
+ this.cache.target = hoverEvent && $(hoverEvent.target);
+
+ // Start the event sequence
+ clearTimeout(this.timers.show);
+ this.timers.show = delay.call(this,
+ function() { this.render(typeof hoverEvent === 'object' || options.show.ready); },
+ options.prerender ? 0 : options.show.delay
+ );
+ }
+
+ // Filter and bind events
+ this._bindEvents(showEvents, hideEvents, showTarget, hideTarget, hoverIntent, function() {
+ if(!this.timers) { return FALSE; }
+ clearTimeout(this.timers.show);
+ });
+
+ // Prerendering is enabled, create tooltip now
+ if(options.show.ready || options.prerender) { hoverIntent.call(this, event); }
+};
+
+// Event assignment method
+PROTOTYPE._assignEvents = function() {
+ var self = this,
+ options = this.options,
+ posOptions = options.position,
+
+ tooltip = this.tooltip,
+ showTarget = options.show.target,
+ hideTarget = options.hide.target,
+ containerTarget = posOptions.container,
+ viewportTarget = posOptions.viewport,
+ documentTarget = $(document),
+ windowTarget = $(window),
+
+ showEvents = options.show.event ? $.trim('' + options.show.event).split(' ') : [],
+ hideEvents = options.hide.event ? $.trim('' + options.hide.event).split(' ') : [];
+
+
+ // Assign passed event callbacks
+ $.each(options.events, function(name, callback) {
+ self._bind(tooltip, name === 'toggle' ? ['tooltipshow','tooltiphide'] : ['tooltip'+name], callback, null, tooltip);
+ });
+
+ // Hide tooltips when leaving current window/frame (but not select/option elements)
+ if(/mouse(out|leave)/i.test(options.hide.event) && options.hide.leave === 'window') {
+ this._bind(documentTarget, ['mouseout', 'blur'], function(event) {
+ if(!/select|option/.test(event.target.nodeName) && !event.relatedTarget) {
+ this.hide(event);
+ }
+ });
+ }
+
+ // Enable hide.fixed by adding appropriate class
+ if(options.hide.fixed) {
+ hideTarget = hideTarget.add( tooltip.addClass(CLASS_FIXED) );
+ }
+
+ /*
+ * Make sure hoverIntent functions properly by using mouseleave to clear show timer if
+ * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
+ */
+ else if(/mouse(over|enter)/i.test(options.show.event)) {
+ this._bind(hideTarget, 'mouseleave', function() {
+ clearTimeout(this.timers.show);
+ });
+ }
+
+ // Hide tooltip on document mousedown if unfocus events are enabled
+ if(('' + options.hide.event).indexOf('unfocus') > -1) {
+ this._bind(containerTarget.closest('html'), ['mousedown', 'touchstart'], function(event) {
+ var elem = $(event.target),
+ enabled = this.rendered && !this.tooltip.hasClass(CLASS_DISABLED) && this.tooltip[0].offsetWidth > 0,
+ isAncestor = elem.parents(SELECTOR).filter(this.tooltip[0]).length > 0;
+
+ if(elem[0] !== this.target[0] && elem[0] !== this.tooltip[0] && !isAncestor &&
+ !this.target.has(elem[0]).length && enabled
+ ) {
+ this.hide(event);
+ }
+ });
+ }
+
+ // Check if the tooltip hides when inactive
+ if('number' === typeof options.hide.inactive) {
+ // Bind inactive method to show target(s) as a custom event
+ this._bind(showTarget, 'qtip-'+this.id+'-inactive', inactiveMethod, 'inactive');
+
+ // Define events which reset the 'inactive' event handler
+ this._bind(hideTarget.add(tooltip), QTIP.inactiveEvents, inactiveMethod);
+ }
+
+ // Filter and bind events
+ this._bindEvents(showEvents, hideEvents, showTarget, hideTarget, showMethod, hideMethod);
+
+ // Mouse movement bindings
+ this._bind(showTarget.add(tooltip), 'mousemove', function(event) {
+ // Check if the tooltip hides when mouse is moved a certain distance
+ if('number' === typeof options.hide.distance) {
+ var origin = this.cache.origin || {},
+ limit = this.options.hide.distance,
+ abs = Math.abs;
+
+ // Check if the movement has gone beyond the limit, and hide it if so
+ if(abs(event.pageX - origin.pageX) >= limit || abs(event.pageY - origin.pageY) >= limit) {
+ this.hide(event);
+ }
+ }
+
+ // Cache mousemove coords on show targets
+ this._storeMouse(event);
+ });
+
+ // Mouse positioning events
+ if(posOptions.target === 'mouse') {
+ // If mouse adjustment is on...
+ if(posOptions.adjust.mouse) {
+ // Apply a mouseleave event so we don't get problems with overlapping
+ if(options.hide.event) {
+ // Track if we're on the target or not
+ this._bind(showTarget, ['mouseenter', 'mouseleave'], function(event) {
+ if(!this.cache) {return FALSE; }
+ this.cache.onTarget = event.type === 'mouseenter';
+ });
+ }
+
+ // Update tooltip position on mousemove
+ this._bind(documentTarget, 'mousemove', function(event) {
+ // Update the tooltip position only if the tooltip is visible and adjustment is enabled
+ if(this.rendered && this.cache.onTarget && !this.tooltip.hasClass(CLASS_DISABLED) && this.tooltip[0].offsetWidth > 0) {
+ this.reposition(event);
+ }
+ });
+ }
+ }
+
+ // Adjust positions of the tooltip on window resize if enabled
+ if(posOptions.adjust.resize || viewportTarget.length) {
+ this._bind( $.event.special.resize ? viewportTarget : windowTarget, 'resize', repositionMethod );
+ }
+
+ // Adjust tooltip position on scroll of the window or viewport element if present
+ if(posOptions.adjust.scroll) {
+ this._bind( windowTarget.add(posOptions.container), 'scroll', repositionMethod );
+ }
+};
+
+// Un-assignment method
+PROTOTYPE._unassignEvents = function() {
+ var options = this.options,
+ showTargets = options.show.target,
+ hideTargets = options.hide.target,
+ targets = $.grep([
+ this.elements.target[0],
+ this.rendered && this.tooltip[0],
+ options.position.container[0],
+ options.position.viewport[0],
+ options.position.container.closest('html')[0], // unfocus
+ window,
+ document
+ ], function(i) {
+ return typeof i === 'object';
+ });
+
+ // Add show and hide targets if they're valid
+ if(showTargets && showTargets.toArray) {
+ targets = targets.concat(showTargets.toArray());
+ }
+ if(hideTargets && hideTargets.toArray) {
+ targets = targets.concat(hideTargets.toArray());
+ }
+
+ // Unbind the events
+ this._unbind(targets)
+ ._unbind(targets, 'destroy')
+ ._unbind(targets, 'inactive');
+};
+
+// Apply common event handlers using delegate (avoids excessive .bind calls!)
+$(function() {
+ delegate(SELECTOR, ['mouseenter', 'mouseleave'], function(event) {
+ var state = event.type === 'mouseenter',
+ tooltip = $(event.currentTarget),
+ target = $(event.relatedTarget || event.target),
+ options = this.options;
+
+ // On mouseenter...
+ if(state) {
+ // Focus the tooltip on mouseenter (z-index stacking)
+ this.focus(event);
+
+ // Clear hide timer on tooltip hover to prevent it from closing
+ tooltip.hasClass(CLASS_FIXED) && !tooltip.hasClass(CLASS_DISABLED) && clearTimeout(this.timers.hide);
+ }
+
+ // On mouseleave...
+ else {
+ // When mouse tracking is enabled, hide when we leave the tooltip and not onto the show target (if a hide event is set)
+ if(options.position.target === 'mouse' && options.position.adjust.mouse &&
+ options.hide.event && options.show.target && !target.closest(options.show.target[0]).length) {
+ this.hide(event);
+ }
+ }
+
+ // Add hover class
+ tooltip.toggleClass(CLASS_HOVER, state);
+ });
+
+ // Define events which reset the 'inactive' event handler
+ delegate('['+ATTR_ID+']', INACTIVE_EVENTS, inactiveMethod);
+});
+;// Initialization method
+function init(elem, id, opts) {
+ var obj, posOptions, attr, config, title,
+
+ // Setup element references
+ docBody = $(document.body),
+
+ // Use document body instead of document element if needed
+ newTarget = elem[0] === document ? docBody : elem,
+
+ // Grab metadata from element if plugin is present
+ metadata = elem.metadata ? elem.metadata(opts.metadata) : NULL,
+
+ // If metadata type if HTML5, grab 'name' from the object instead, or use the regular data object otherwise
+ metadata5 = opts.metadata.type === 'html5' && metadata ? metadata[opts.metadata.name] : NULL,
+
+ // Grab data from metadata.name (or data-qtipopts as fallback) using .data() method,
+ html5 = elem.data(opts.metadata.name || 'qtipopts');
+
+ // If we don't get an object returned attempt to parse it manualyl without parseJSON
+ /* eslint-disable no-empty */
+ try { html5 = typeof html5 === 'string' ? $.parseJSON(html5) : html5; }
+ catch(e) {}
+ /* eslint-enable no-empty */
+
+ // Merge in and sanitize metadata
+ config = $.extend(TRUE, {}, QTIP.defaults, opts,
+ typeof html5 === 'object' ? sanitizeOptions(html5) : NULL,
+ sanitizeOptions(metadata5 || metadata));
+
+ // Re-grab our positioning options now we've merged our metadata and set id to passed value
+ posOptions = config.position;
+ config.id = id;
+
+ // Setup missing content if none is detected
+ if('boolean' === typeof config.content.text) {
+ attr = elem.attr(config.content.attr);
+
+ // Grab from supplied attribute if available
+ if(config.content.attr !== FALSE && attr) { config.content.text = attr; }
+
+ // No valid content was found, abort render
+ else { return FALSE; }
+ }
+
+ // Setup target options
+ if(!posOptions.container.length) { posOptions.container = docBody; }
+ if(posOptions.target === FALSE) { posOptions.target = newTarget; }
+ if(config.show.target === FALSE) { config.show.target = newTarget; }
+ if(config.show.solo === TRUE) { config.show.solo = posOptions.container.closest('body'); }
+ if(config.hide.target === FALSE) { config.hide.target = newTarget; }
+ if(config.position.viewport === TRUE) { config.position.viewport = posOptions.container; }
+
+ // Ensure we only use a single container
+ posOptions.container = posOptions.container.eq(0);
+
+ // Convert position corner values into x and y strings
+ posOptions.at = new CORNER(posOptions.at, TRUE);
+ posOptions.my = new CORNER(posOptions.my);
+
+ // Destroy previous tooltip if overwrite is enabled, or skip element if not
+ if(elem.data(NAMESPACE)) {
+ if(config.overwrite) {
+ elem.qtip('destroy', true);
+ }
+ else if(config.overwrite === FALSE) {
+ return FALSE;
+ }
+ }
+
+ // Add has-qtip attribute
+ elem.attr(ATTR_HAS, id);
+
+ // Remove title attribute and store it if present
+ if(config.suppress && (title = elem.attr('title'))) {
+ // Final attr call fixes event delegatiom and IE default tooltip showing problem
+ elem.removeAttr('title').attr(oldtitle, title).attr('title', '');
+ }
+
+ // Initialize the tooltip and add API reference
+ obj = new QTip(elem, config, id, !!attr);
+ elem.data(NAMESPACE, obj);
+
+ return obj;
+}
+
+// jQuery $.fn extension method
+QTIP = $.fn.qtip = function(options, notation, newValue)
+{
+ var command = ('' + options).toLowerCase(), // Parse command
+ returned = NULL,
+ args = $.makeArray(arguments).slice(1),
+ event = args[args.length - 1],
+ opts = this[0] ? $.data(this[0], NAMESPACE) : NULL;
+
+ // Check for API request
+ if(!arguments.length && opts || command === 'api') {
+ return opts;
+ }
+
+ // Execute API command if present
+ else if('string' === typeof options) {
+ this.each(function() {
+ var api = $.data(this, NAMESPACE);
+ if(!api) { return TRUE; }
+
+ // Cache the event if possible
+ if(event && event.timeStamp) { api.cache.event = event; }
+
+ // Check for specific API commands
+ if(notation && (command === 'option' || command === 'options')) {
+ if(newValue !== undefined || $.isPlainObject(notation)) {
+ api.set(notation, newValue);
+ }
+ else {
+ returned = api.get(notation);
+ return FALSE;
+ }
+ }
+
+ // Execute API command
+ else if(api[command]) {
+ api[command].apply(api, args);
+ }
+ });
+
+ return returned !== NULL ? returned : this;
+ }
+
+ // No API commands. validate provided options and setup qTips
+ else if('object' === typeof options || !arguments.length) {
+ // Sanitize options first
+ opts = sanitizeOptions($.extend(TRUE, {}, options));
+
+ return this.each(function(i) {
+ var api, id;
+
+ // Find next available ID, or use custom ID if provided
+ id = $.isArray(opts.id) ? opts.id[i] : opts.id;
+ id = !id || id === FALSE || id.length < 1 || QTIP.api[id] ? QTIP.nextid++ : id;
+
+ // Initialize the qTip and re-grab newly sanitized options
+ api = init($(this), id, opts);
+ if(api === FALSE) { return TRUE; }
+ else { QTIP.api[id] = api; }
+
+ // Initialize plugins
+ $.each(PLUGINS, function() {
+ if(this.initialize === 'initialize') { this(api); }
+ });
+
+ // Assign initial pre-render events
+ api._assignInitialEvents(event);
+ });
+ }
+};
+
+// Expose class
+$.qtip = QTip;
+
+// Populated in render method
+QTIP.api = {};
+;$.each({
+ /* Allow other plugins to successfully retrieve the title of an element with a qTip applied */
+ attr: function(attr, val) {
+ if(this.length) {
+ var self = this[0],
+ title = 'title',
+ api = $.data(self, 'qtip');
+
+ if(attr === title && api && api.options && 'object' === typeof api && 'object' === typeof api.options && api.options.suppress) {
+ if(arguments.length < 2) {
+ return $.attr(self, oldtitle);
+ }
+
+ // If qTip is rendered and title was originally used as content, update it
+ if(api && api.options.content.attr === title && api.cache.attr) {
+ api.set('content.text', val);
+ }
+
+ // Use the regular attr method to set, then cache the result
+ return this.attr(oldtitle, val);
+ }
+ }
+
+ return $.fn['attr'+replaceSuffix].apply(this, arguments);
+ },
+
+ /* Allow clone to correctly retrieve cached title attributes */
+ clone: function(keepData) {
+ // Clone our element using the real clone method
+ var elems = $.fn['clone'+replaceSuffix].apply(this, arguments);
+
+ // Grab all elements with an oldtitle set, and change it to regular title attribute, if keepData is false
+ if(!keepData) {
+ elems.filter('['+oldtitle+']').attr('title', function() {
+ return $.attr(this, oldtitle);
+ })
+ .removeAttr(oldtitle);
+ }
+
+ return elems;
+ }
+}, function(name, func) {
+ if(!func || $.fn[name+replaceSuffix]) { return TRUE; }
+
+ var old = $.fn[name+replaceSuffix] = $.fn[name];
+ $.fn[name] = function() {
+ return func.apply(this, arguments) || old.apply(this, arguments);
+ };
+});
+
+/* Fire off 'removeqtip' handler in $.cleanData if jQuery UI not present (it already does similar).
+ * This snippet is taken directly from jQuery UI source code found here:
+ * http://code.jquery.com/ui/jquery-ui-git.js
+ */
+if(!$.ui) {
+ $['cleanData'+replaceSuffix] = $.cleanData;
+ $.cleanData = function( elems ) {
+ for(var i = 0, elem; (elem = $( elems[i] )).length; i++) {
+ if(elem.attr(ATTR_HAS)) {
+ /* eslint-disable no-empty */
+ try { elem.triggerHandler('removeqtip'); }
+ catch( e ) {}
+ /* eslint-enable no-empty */
+ }
+ }
+ $['cleanData'+replaceSuffix].apply(this, arguments);
+ };
+}
+;// qTip version
+QTIP.version = '3.0.3';
+
+// Base ID for all qTips
+QTIP.nextid = 0;
+
+// Inactive events array
+QTIP.inactiveEvents = INACTIVE_EVENTS;
+
+// Base z-index for all qTips
+QTIP.zindex = 15000;
+
+// Define configuration defaults
+QTIP.defaults = {
+ prerender: FALSE,
+ id: FALSE,
+ overwrite: TRUE,
+ suppress: TRUE,
+ content: {
+ text: TRUE,
+ attr: 'title',
+ title: FALSE,
+ button: FALSE
+ },
+ position: {
+ my: 'top left',
+ at: 'bottom right',
+ target: FALSE,
+ container: FALSE,
+ viewport: FALSE,
+ adjust: {
+ x: 0, y: 0,
+ mouse: TRUE,
+ scroll: TRUE,
+ resize: TRUE,
+ method: 'flipinvert flipinvert'
+ },
+ effect: function(api, pos) {
+ $(this).animate(pos, {
+ duration: 200,
+ queue: FALSE
+ });
+ }
+ },
+ show: {
+ target: FALSE,
+ event: 'mouseenter',
+ effect: TRUE,
+ delay: 90,
+ solo: FALSE,
+ ready: FALSE,
+ autofocus: FALSE
+ },
+ hide: {
+ target: FALSE,
+ event: 'mouseleave',
+ effect: TRUE,
+ delay: 0,
+ fixed: FALSE,
+ inactive: FALSE,
+ leave: 'window',
+ distance: FALSE
+ },
+ style: {
+ classes: '',
+ widget: FALSE,
+ width: FALSE,
+ height: FALSE,
+ def: TRUE
+ },
+ events: {
+ render: NULL,
+ move: NULL,
+ show: NULL,
+ hide: NULL,
+ toggle: NULL,
+ visible: NULL,
+ hidden: NULL,
+ focus: NULL,
+ blur: NULL
+ }
+};
+;PLUGINS.viewport = function(api, position, posOptions, targetWidth, targetHeight, elemWidth, elemHeight)
+{
+ var target = posOptions.target,
+ tooltip = api.elements.tooltip,
+ my = posOptions.my,
+ at = posOptions.at,
+ adjust = posOptions.adjust,
+ method = adjust.method.split(' '),
+ methodX = method[0],
+ methodY = method[1] || method[0],
+ viewport = posOptions.viewport,
+ container = posOptions.container,
+ adjusted = { left: 0, top: 0 },
+ fixed, newMy, containerOffset, containerStatic,
+ viewportWidth, viewportHeight, viewportScroll, viewportOffset;
+
+ // If viewport is not a jQuery element, or it's the window/document, or no adjustment method is used... return
+ if(!viewport.jquery || target[0] === window || target[0] === document.body || adjust.method === 'none') {
+ return adjusted;
+ }
+
+ // Cach container details
+ containerOffset = container.offset() || adjusted;
+ containerStatic = container.css('position') === 'static';
+
+ // Cache our viewport details
+ fixed = tooltip.css('position') === 'fixed';
+ viewportWidth = viewport[0] === window ? viewport.width() : viewport.outerWidth(FALSE);
+ viewportHeight = viewport[0] === window ? viewport.height() : viewport.outerHeight(FALSE);
+ viewportScroll = { left: fixed ? 0 : viewport.scrollLeft(), top: fixed ? 0 : viewport.scrollTop() };
+ viewportOffset = viewport.offset() || adjusted;
+
+ // Generic calculation method
+ function calculate(side, otherSide, type, adjustment, side1, side2, lengthName, targetLength, elemLength) {
+ var initialPos = position[side1],
+ mySide = my[side],
+ atSide = at[side],
+ isShift = type === SHIFT,
+ myLength = mySide === side1 ? elemLength : mySide === side2 ? -elemLength : -elemLength / 2,
+ atLength = atSide === side1 ? targetLength : atSide === side2 ? -targetLength : -targetLength / 2,
+ sideOffset = viewportScroll[side1] + viewportOffset[side1] - (containerStatic ? 0 : containerOffset[side1]),
+ overflow1 = sideOffset - initialPos,
+ overflow2 = initialPos + elemLength - (lengthName === WIDTH ? viewportWidth : viewportHeight) - sideOffset,
+ offset = myLength - (my.precedance === side || mySide === my[otherSide] ? atLength : 0) - (atSide === CENTER ? targetLength / 2 : 0);
+
+ // shift
+ if(isShift) {
+ offset = (mySide === side1 ? 1 : -1) * myLength;
+
+ // Adjust position but keep it within viewport dimensions
+ position[side1] += overflow1 > 0 ? overflow1 : overflow2 > 0 ? -overflow2 : 0;
+ position[side1] = Math.max(
+ -containerOffset[side1] + viewportOffset[side1],
+ initialPos - offset,
+ Math.min(
+ Math.max(
+ -containerOffset[side1] + viewportOffset[side1] + (lengthName === WIDTH ? viewportWidth : viewportHeight),
+ initialPos + offset
+ ),
+ position[side1],
+
+ // Make sure we don't adjust complete off the element when using 'center'
+ mySide === 'center' ? initialPos - myLength : 1E9
+ )
+ );
+
+ }
+
+ // flip/flipinvert
+ else {
+ // Update adjustment amount depending on if using flipinvert or flip
+ adjustment *= type === FLIPINVERT ? 2 : 0;
+
+ // Check for overflow on the left/top
+ if(overflow1 > 0 && (mySide !== side1 || overflow2 > 0)) {
+ position[side1] -= offset + adjustment;
+ newMy.invert(side, side1);
+ }
+
+ // Check for overflow on the bottom/right
+ else if(overflow2 > 0 && (mySide !== side2 || overflow1 > 0) ) {
+ position[side1] -= (mySide === CENTER ? -offset : offset) + adjustment;
+ newMy.invert(side, side2);
+ }
+
+ // Make sure we haven't made things worse with the adjustment and reset if so
+ if(position[side1] < viewportScroll[side1] && -position[side1] > overflow2) {
+ position[side1] = initialPos; newMy = my.clone();
+ }
+ }
+
+ return position[side1] - initialPos;
+ }
+
+ // Set newMy if using flip or flipinvert methods
+ if(methodX !== 'shift' || methodY !== 'shift') { newMy = my.clone(); }
+
+ // Adjust position based onviewport and adjustment options
+ adjusted = {
+ left: methodX !== 'none' ? calculate( X, Y, methodX, adjust.x, LEFT, RIGHT, WIDTH, targetWidth, elemWidth ) : 0,
+ top: methodY !== 'none' ? calculate( Y, X, methodY, adjust.y, TOP, BOTTOM, HEIGHT, targetHeight, elemHeight ) : 0,
+ my: newMy
+ };
+
+ return adjusted;
+};
+;PLUGINS.polys = {
+ // POLY area coordinate calculator
+ // Special thanks to Ed Cradock for helping out with this.
+ // Uses a binary search algorithm to find suitable coordinates.
+ polygon: function(baseCoords, corner) {
+ var result = {
+ width: 0, height: 0,
+ position: {
+ top: 1e10, right: 0,
+ bottom: 0, left: 1e10
+ },
+ adjustable: FALSE
+ },
+ i = 0, next,
+ coords = [],
+ compareX = 1, compareY = 1,
+ realX = 0, realY = 0,
+ newWidth, newHeight;
+
+ // First pass, sanitize coords and determine outer edges
+ i = baseCoords.length;
+ while(i--) {
+ next = [ parseInt(baseCoords[--i], 10), parseInt(baseCoords[i+1], 10) ];
+
+ if(next[0] > result.position.right){ result.position.right = next[0]; }
+ if(next[0] < result.position.left){ result.position.left = next[0]; }
+ if(next[1] > result.position.bottom){ result.position.bottom = next[1]; }
+ if(next[1] < result.position.top){ result.position.top = next[1]; }
+
+ coords.push(next);
+ }
+
+ // Calculate height and width from outer edges
+ newWidth = result.width = Math.abs(result.position.right - result.position.left);
+ newHeight = result.height = Math.abs(result.position.bottom - result.position.top);
+
+ // If it's the center corner...
+ if(corner.abbrev() === 'c') {
+ result.position = {
+ left: result.position.left + result.width / 2,
+ top: result.position.top + result.height / 2
+ };
+ }
+ else {
+ // Second pass, use a binary search algorithm to locate most suitable coordinate
+ while(newWidth > 0 && newHeight > 0 && compareX > 0 && compareY > 0)
+ {
+ newWidth = Math.floor(newWidth / 2);
+ newHeight = Math.floor(newHeight / 2);
+
+ if(corner.x === LEFT){ compareX = newWidth; }
+ else if(corner.x === RIGHT){ compareX = result.width - newWidth; }
+ else{ compareX += Math.floor(newWidth / 2); }
+
+ if(corner.y === TOP){ compareY = newHeight; }
+ else if(corner.y === BOTTOM){ compareY = result.height - newHeight; }
+ else{ compareY += Math.floor(newHeight / 2); }
+
+ i = coords.length;
+ while(i--)
+ {
+ if(coords.length < 2){ break; }
+
+ realX = coords[i][0] - result.position.left;
+ realY = coords[i][1] - result.position.top;
+
+ if(
+ corner.x === LEFT && realX >= compareX ||
+ corner.x === RIGHT && realX <= compareX ||
+ corner.x === CENTER && (realX < compareX || realX > result.width - compareX) ||
+ corner.y === TOP && realY >= compareY ||
+ corner.y === BOTTOM && realY <= compareY ||
+ corner.y === CENTER && (realY < compareY || realY > result.height - compareY)) {
+ coords.splice(i, 1);
+ }
+ }
+ }
+ result.position = { left: coords[0][0], top: coords[0][1] };
+ }
+
+ return result;
+ },
+
+ rect: function(ax, ay, bx, by) {
+ return {
+ width: Math.abs(bx - ax),
+ height: Math.abs(by - ay),
+ position: {
+ left: Math.min(ax, bx),
+ top: Math.min(ay, by)
+ }
+ };
+ },
+
+ _angles: {
+ tc: 3 / 2, tr: 7 / 4, tl: 5 / 4,
+ bc: 1 / 2, br: 1 / 4, bl: 3 / 4,
+ rc: 2, lc: 1, c: 0
+ },
+ ellipse: function(cx, cy, rx, ry, corner) {
+ var c = PLUGINS.polys._angles[ corner.abbrev() ],
+ rxc = c === 0 ? 0 : rx * Math.cos( c * Math.PI ),
+ rys = ry * Math.sin( c * Math.PI );
+
+ return {
+ width: rx * 2 - Math.abs(rxc),
+ height: ry * 2 - Math.abs(rys),
+ position: {
+ left: cx + rxc,
+ top: cy + rys
+ },
+ adjustable: FALSE
+ };
+ },
+ circle: function(cx, cy, r, corner) {
+ return PLUGINS.polys.ellipse(cx, cy, r, r, corner);
+ }
+};
+;PLUGINS.svg = function(api, svg, corner)
+{
+ var elem = svg[0],
+ root = $(elem.ownerSVGElement),
+ ownerDocument = elem.ownerDocument,
+ strokeWidth2 = (parseInt(svg.css('stroke-width'), 10) || 0) / 2,
+ frameOffset, mtx, transformed,
+ len, next, i, points,
+ result, position;
+
+ // Ascend the parentNode chain until we find an element with getBBox()
+ while(!elem.getBBox) { elem = elem.parentNode; }
+ if(!elem.getBBox || !elem.parentNode) { return FALSE; }
+
+ // Determine which shape calculation to use
+ switch(elem.nodeName) {
+ case 'ellipse':
+ case 'circle':
+ result = PLUGINS.polys.ellipse(
+ elem.cx.baseVal.value,
+ elem.cy.baseVal.value,
+ (elem.rx || elem.r).baseVal.value + strokeWidth2,
+ (elem.ry || elem.r).baseVal.value + strokeWidth2,
+ corner
+ );
+ break;
+
+ case 'line':
+ case 'polygon':
+ case 'polyline':
+ // Determine points object (line has none, so mimic using array)
+ points = elem.points || [
+ { x: elem.x1.baseVal.value, y: elem.y1.baseVal.value },
+ { x: elem.x2.baseVal.value, y: elem.y2.baseVal.value }
+ ];
+
+ for(result = [], i = -1, len = points.numberOfItems || points.length; ++i < len;) {
+ next = points.getItem ? points.getItem(i) : points[i];
+ result.push.apply(result, [next.x, next.y]);
+ }
+
+ result = PLUGINS.polys.polygon(result, corner);
+ break;
+
+ // Unknown shape or rectangle? Use bounding box
+ default:
+ result = elem.getBBox();
+ result = {
+ width: result.width,
+ height: result.height,
+ position: {
+ left: result.x,
+ top: result.y
+ }
+ };
+ break;
+ }
+
+ // Shortcut assignments
+ position = result.position;
+ root = root[0];
+
+ // Convert position into a pixel value
+ if(root.createSVGPoint) {
+ mtx = elem.getScreenCTM();
+ points = root.createSVGPoint();
+
+ points.x = position.left;
+ points.y = position.top;
+ transformed = points.matrixTransform( mtx );
+ position.left = transformed.x;
+ position.top = transformed.y;
+ }
+
+ // Check the element is not in a child document, and if so, adjust for frame elements offset
+ if(ownerDocument !== document && api.position.target !== 'mouse') {
+ frameOffset = $((ownerDocument.defaultView || ownerDocument.parentWindow).frameElement).offset();
+ if(frameOffset) {
+ position.left += frameOffset.left;
+ position.top += frameOffset.top;
+ }
+ }
+
+ // Adjust by scroll offset of owner document
+ ownerDocument = $(ownerDocument);
+ position.left += ownerDocument.scrollLeft();
+ position.top += ownerDocument.scrollTop();
+
+ return result;
+};
+;}));
+}( window, document ));
diff --git a/www/wiki/vendor/onoi/shared-resources/res/jquery.rangeSlider/ion.rangeSlider.css b/www/wiki/vendor/onoi/shared-resources/res/jquery.rangeSlider/ion.rangeSlider.css
new file mode 100644
index 00000000..2b2b328f
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/jquery.rangeSlider/ion.rangeSlider.css
@@ -0,0 +1,146 @@
+/* Ion.RangeSlider
+// css version 2.0.3
+// © 2013-2014 Denis Ineshin | IonDen.com
+// ===================================================================================================================*/
+
+/* =====================================================================================================================
+// RangeSlider */
+
+.irs {
+ position: relative; display: block;
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+ .irs-line {
+ position: relative; display: block;
+ overflow: hidden;
+ outline: none !important;
+ }
+ .irs-line-left, .irs-line-mid, .irs-line-right {
+ position: absolute; display: block;
+ top: 0;
+ }
+ .irs-line-left {
+ left: 0; width: 11%;
+ }
+ .irs-line-mid {
+ left: 9%; width: 82%;
+ }
+ .irs-line-right {
+ right: 0; width: 11%;
+ }
+
+ .irs-bar {
+ position: absolute; display: block;
+ left: 0; width: 0;
+ }
+ .irs-bar-edge {
+ position: absolute; display: block;
+ top: 0; left: 0;
+ }
+
+ .irs-shadow {
+ position: absolute; display: none;
+ left: 0; width: 0;
+ }
+
+ .irs-slider {
+ position: absolute; display: block;
+ cursor: default;
+ z-index: 1;
+ }
+ .irs-slider.single {
+
+ }
+ .irs-slider.from {
+
+ }
+ .irs-slider.to {
+
+ }
+ .irs-slider.type_last {
+ z-index: 2;
+ }
+
+ .irs-min {
+ position: absolute; display: block;
+ left: 0;
+ cursor: default;
+ }
+ .irs-max {
+ position: absolute; display: block;
+ right: 0;
+ cursor: default;
+ }
+
+ .irs-from, .irs-to, .irs-single {
+ position: absolute; display: block;
+ top: 0; left: 0;
+ cursor: default;
+ white-space: nowrap;
+ }
+
+.irs-grid {
+ position: absolute; display: none;
+ bottom: 0; left: 0;
+ width: 100%; height: 20px;
+}
+.irs-with-grid .irs-grid {
+ display: block;
+}
+ .irs-grid-pol {
+ position: absolute;
+ top: 0; left: 0;
+ width: 1px; height: 8px;
+ background: #000;
+ }
+ .irs-grid-pol.small {
+ height: 4px;
+ }
+ .irs-grid-text {
+ position: absolute;
+ bottom: 0; left: 0;
+ white-space: nowrap;
+ text-align: center;
+ font-size: 9px; line-height: 9px;
+ padding: 0 3px;
+ color: #000;
+ }
+
+.irs-disable-mask {
+ position: absolute; display: block;
+ top: 0; left: -1%;
+ width: 102%; height: 100%;
+ cursor: default;
+ background: rgba(0,0,0,0.0);
+ z-index: 2;
+}
+.irs-disabled {
+ opacity: 0.4;
+}
+.lt-ie9 .irs-disabled {
+ filter: alpha(opacity=40);
+}
+
+
+.irs-hidden-input {
+ position: absolute !important;
+ display: block !important;
+ top: 0 !important;
+ left: 0 !important;
+ width: 0 !important;
+ height: 0 !important;
+ font-size: 0 !important;
+ line-height: 0 !important;
+ padding: 0 !important;
+ margin: 0 !important;
+ outline: none !important;
+ z-index: -9999 !important;
+ background: none !important;
+ border-style: solid !important;
+ border-color: transparent !important;
+}
diff --git a/www/wiki/vendor/onoi/shared-resources/res/jquery.rangeSlider/ion.rangeSlider.js b/www/wiki/vendor/onoi/shared-resources/res/jquery.rangeSlider/ion.rangeSlider.js
new file mode 100644
index 00000000..1fd84896
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/jquery.rangeSlider/ion.rangeSlider.js
@@ -0,0 +1,2307 @@
+// Ion.RangeSlider
+// version 2.1.2 Build: 350
+// © Denis Ineshin, 2015
+// https://github.com/IonDen
+//
+// Project page: http://ionden.com/a/plugins/ion.rangeSlider/en.html
+// GitHub page: https://github.com/IonDen/ion.rangeSlider
+//
+// Released under MIT licence:
+// http://ionden.com/a/plugins/licence-en.html
+// =====================================================================================================================
+
+;(function ($, document, window, navigator, undefined) {
+ "use strict";
+
+ // =================================================================================================================
+ // Service
+
+ var plugin_count = 0;
+
+ // IE8 fix
+ var is_old_ie = (function () {
+ var n = navigator.userAgent,
+ r = /msie\s\d+/i,
+ v;
+ if (n.search(r) > 0) {
+ v = r.exec(n).toString();
+ v = v.split(" ")[1];
+ if (v < 9) {
+ $("html").addClass("lt-ie9");
+ return true;
+ }
+ }
+ return false;
+ } ());
+ if (!Function.prototype.bind) {
+ Function.prototype.bind = function bind(that) {
+
+ var target = this;
+ var slice = [].slice;
+
+ if (typeof target != "function") {
+ throw new TypeError();
+ }
+
+ var args = slice.call(arguments, 1),
+ bound = function () {
+
+ if (this instanceof bound) {
+
+ var F = function(){};
+ F.prototype = target.prototype;
+ var self = new F();
+
+ var result = target.apply(
+ self,
+ args.concat(slice.call(arguments))
+ );
+ if (Object(result) === result) {
+ return result;
+ }
+ return self;
+
+ } else {
+
+ return target.apply(
+ that,
+ args.concat(slice.call(arguments))
+ );
+
+ }
+
+ };
+
+ return bound;
+ };
+ }
+ if (!Array.prototype.indexOf) {
+ Array.prototype.indexOf = function(searchElement, fromIndex) {
+ var k;
+ if (this == null) {
+ throw new TypeError('"this" is null or not defined');
+ }
+ var O = Object(this);
+ var len = O.length >>> 0;
+ if (len === 0) {
+ return -1;
+ }
+ var n = +fromIndex || 0;
+ if (Math.abs(n) === Infinity) {
+ n = 0;
+ }
+ if (n >= len) {
+ return -1;
+ }
+ k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
+ while (k < len) {
+ if (k in O && O[k] === searchElement) {
+ return k;
+ }
+ k++;
+ }
+ return -1;
+ };
+ }
+
+
+
+ // =================================================================================================================
+ // Template
+
+ var base_html =
+ '<span class="irs">' +
+ '<span class="irs-line" tabindex="-1"><span class="irs-line-left"></span><span class="irs-line-mid"></span><span class="irs-line-right"></span></span>' +
+ '<span class="irs-min">0</span><span class="irs-max">1</span>' +
+ '<span class="irs-from">0</span><span class="irs-to">0</span><span class="irs-single">0</span>' +
+ '</span>' +
+ '<span class="irs-grid"></span>' +
+ '<span class="irs-bar"></span>';
+
+ var single_html =
+ '<span class="irs-bar-edge"></span>' +
+ '<span class="irs-shadow shadow-single"></span>' +
+ '<span class="irs-slider single"></span>';
+
+ var double_html =
+ '<span class="irs-shadow shadow-from"></span>' +
+ '<span class="irs-shadow shadow-to"></span>' +
+ '<span class="irs-slider from"></span>' +
+ '<span class="irs-slider to"></span>';
+
+ var disable_html =
+ '<span class="irs-disable-mask"></span>';
+
+
+
+ // =================================================================================================================
+ // Core
+
+ /**
+ * Main plugin constructor
+ *
+ * @param input {Object} link to base input element
+ * @param options {Object} slider config
+ * @param plugin_count {Number}
+ * @constructor
+ */
+ var IonRangeSlider = function (input, options, plugin_count) {
+ this.VERSION = "2.1.2";
+ this.input = input;
+ this.plugin_count = plugin_count;
+ this.current_plugin = 0;
+ this.calc_count = 0;
+ this.update_tm = 0;
+ this.old_from = 0;
+ this.old_to = 0;
+ this.old_min_interval = null;
+ this.raf_id = null;
+ this.dragging = false;
+ this.force_redraw = false;
+ this.no_diapason = false;
+ this.is_key = false;
+ this.is_update = false;
+ this.is_start = true;
+ this.is_finish = false;
+ this.is_active = false;
+ this.is_resize = false;
+ this.is_click = false;
+
+ // cache for links to all DOM elements
+ this.$cache = {
+ win: $(window),
+ body: $(document.body),
+ input: $(input),
+ cont: null,
+ rs: null,
+ min: null,
+ max: null,
+ from: null,
+ to: null,
+ single: null,
+ bar: null,
+ line: null,
+ s_single: null,
+ s_from: null,
+ s_to: null,
+ shad_single: null,
+ shad_from: null,
+ shad_to: null,
+ edge: null,
+ grid: null,
+ grid_labels: []
+ };
+
+ // storage for measure variables
+ this.coords = {
+ // left
+ x_gap: 0,
+ x_pointer: 0,
+
+ // width
+ w_rs: 0,
+ w_rs_old: 0,
+ w_handle: 0,
+
+ // percents
+ p_gap: 0,
+ p_gap_left: 0,
+ p_gap_right: 0,
+ p_step: 0,
+ p_pointer: 0,
+ p_handle: 0,
+ p_single_fake: 0,
+ p_single_real: 0,
+ p_from_fake: 0,
+ p_from_real: 0,
+ p_to_fake: 0,
+ p_to_real: 0,
+ p_bar_x: 0,
+ p_bar_w: 0,
+
+ // grid
+ grid_gap: 0,
+ big_num: 0,
+ big: [],
+ big_w: [],
+ big_p: [],
+ big_x: []
+ };
+
+ // storage for labels measure variables
+ this.labels = {
+ // width
+ w_min: 0,
+ w_max: 0,
+ w_from: 0,
+ w_to: 0,
+ w_single: 0,
+
+ // percents
+ p_min: 0,
+ p_max: 0,
+ p_from_fake: 0,
+ p_from_left: 0,
+ p_to_fake: 0,
+ p_to_left: 0,
+ p_single_fake: 0,
+ p_single_left: 0
+ };
+
+
+
+ /**
+ * get and validate config
+ */
+ var $inp = this.$cache.input,
+ val = $inp.prop("value"),
+ config, config_from_data, prop;
+
+ // default config
+ config = {
+ type: "single",
+
+ min: 10,
+ max: 100,
+ from: null,
+ to: null,
+ step: 1,
+
+ min_interval: 0,
+ max_interval: 0,
+ drag_interval: false,
+
+ values: [],
+ p_values: [],
+
+ from_fixed: false,
+ from_min: null,
+ from_max: null,
+ from_shadow: false,
+
+ to_fixed: false,
+ to_min: null,
+ to_max: null,
+ to_shadow: false,
+
+ prettify_enabled: true,
+ prettify_separator: " ",
+ prettify: null,
+
+ force_edges: false,
+
+ keyboard: false,
+ keyboard_step: 5,
+
+ grid: false,
+ grid_margin: true,
+ grid_num: 4,
+ grid_snap: false,
+
+ hide_min_max: false,
+ hide_from_to: false,
+
+ prefix: "",
+ postfix: "",
+ max_postfix: "",
+ decorate_both: true,
+ values_separator: " — ",
+
+ input_values_separator: ";",
+
+ disable: false,
+
+ onStart: null,
+ onChange: null,
+ onFinish: null,
+ onUpdate: null
+ };
+
+
+
+ // config from data-attributes extends js config
+ config_from_data = {
+ type: $inp.data("type"),
+
+ min: $inp.data("min"),
+ max: $inp.data("max"),
+ from: $inp.data("from"),
+ to: $inp.data("to"),
+ step: $inp.data("step"),
+
+ min_interval: $inp.data("minInterval"),
+ max_interval: $inp.data("maxInterval"),
+ drag_interval: $inp.data("dragInterval"),
+
+ values: $inp.data("values"),
+
+ from_fixed: $inp.data("fromFixed"),
+ from_min: $inp.data("fromMin"),
+ from_max: $inp.data("fromMax"),
+ from_shadow: $inp.data("fromShadow"),
+
+ to_fixed: $inp.data("toFixed"),
+ to_min: $inp.data("toMin"),
+ to_max: $inp.data("toMax"),
+ to_shadow: $inp.data("toShadow"),
+
+ prettify_enabled: $inp.data("prettifyEnabled"),
+ prettify_separator: $inp.data("prettifySeparator"),
+
+ force_edges: $inp.data("forceEdges"),
+
+ keyboard: $inp.data("keyboard"),
+ keyboard_step: $inp.data("keyboardStep"),
+
+ grid: $inp.data("grid"),
+ grid_margin: $inp.data("gridMargin"),
+ grid_num: $inp.data("gridNum"),
+ grid_snap: $inp.data("gridSnap"),
+
+ hide_min_max: $inp.data("hideMinMax"),
+ hide_from_to: $inp.data("hideFromTo"),
+
+ prefix: $inp.data("prefix"),
+ postfix: $inp.data("postfix"),
+ max_postfix: $inp.data("maxPostfix"),
+ decorate_both: $inp.data("decorateBoth"),
+ values_separator: $inp.data("valuesSeparator"),
+
+ input_values_separator: $inp.data("inputValuesSeparator"),
+
+ disable: $inp.data("disable")
+ };
+ config_from_data.values = config_from_data.values && config_from_data.values.split(",");
+
+ for (prop in config_from_data) {
+ if (config_from_data.hasOwnProperty(prop)) {
+ if (!config_from_data[prop] && config_from_data[prop] !== 0) {
+ delete config_from_data[prop];
+ }
+ }
+ }
+
+
+
+ // input value extends default config
+ if (val) {
+ val = val.split(config_from_data.input_values_separator || options.input_values_separator || ";");
+
+ if (val[0] && val[0] == +val[0]) {
+ val[0] = +val[0];
+ }
+ if (val[1] && val[1] == +val[1]) {
+ val[1] = +val[1];
+ }
+
+ if (options && options.values && options.values.length) {
+ config.from = val[0] && options.values.indexOf(val[0]);
+ config.to = val[1] && options.values.indexOf(val[1]);
+ } else {
+ config.from = val[0] && +val[0];
+ config.to = val[1] && +val[1];
+ }
+ }
+
+
+
+ // js config extends default config
+ $.extend(config, options);
+
+
+ // data config extends config
+ $.extend(config, config_from_data);
+ this.options = config;
+
+
+
+ // validate config, to be sure that all data types are correct
+ this.validate();
+
+
+
+ // default result object, returned to callbacks
+ this.result = {
+ input: this.$cache.input,
+ slider: null,
+
+ min: this.options.min,
+ max: this.options.max,
+
+ from: this.options.from,
+ from_percent: 0,
+ from_value: null,
+
+ to: this.options.to,
+ to_percent: 0,
+ to_value: null
+ };
+
+
+
+ this.init();
+ };
+
+ IonRangeSlider.prototype = {
+
+ /**
+ * Starts or updates the plugin instance
+ *
+ * @param is_update {boolean}
+ */
+ init: function (is_update) {
+ this.no_diapason = false;
+ this.coords.p_step = this.convertToPercent(this.options.step, true);
+
+ this.target = "base";
+
+ this.toggleInput();
+ this.append();
+ this.setMinMax();
+
+ if (is_update) {
+ this.force_redraw = true;
+ this.calc(true);
+
+ // callbacks called
+ this.callOnUpdate();
+ } else {
+ this.force_redraw = true;
+ this.calc(true);
+
+ // callbacks called
+ this.callOnStart();
+ }
+
+ this.updateScene();
+ },
+
+ /**
+ * Appends slider template to a DOM
+ */
+ append: function () {
+ var container_html = '<span class="irs js-irs-' + this.plugin_count + '"></span>';
+ this.$cache.input.before(container_html);
+ this.$cache.input.prop("readonly", true);
+ this.$cache.cont = this.$cache.input.prev();
+ this.result.slider = this.$cache.cont;
+
+ this.$cache.cont.html(base_html);
+ this.$cache.rs = this.$cache.cont.find(".irs");
+ this.$cache.min = this.$cache.cont.find(".irs-min");
+ this.$cache.max = this.$cache.cont.find(".irs-max");
+ this.$cache.from = this.$cache.cont.find(".irs-from");
+ this.$cache.to = this.$cache.cont.find(".irs-to");
+ this.$cache.single = this.$cache.cont.find(".irs-single");
+ this.$cache.bar = this.$cache.cont.find(".irs-bar");
+ this.$cache.line = this.$cache.cont.find(".irs-line");
+ this.$cache.grid = this.$cache.cont.find(".irs-grid");
+
+ if (this.options.type === "single") {
+ this.$cache.cont.append(single_html);
+ this.$cache.edge = this.$cache.cont.find(".irs-bar-edge");
+ this.$cache.s_single = this.$cache.cont.find(".single");
+ this.$cache.from[0].style.visibility = "hidden";
+ this.$cache.to[0].style.visibility = "hidden";
+ this.$cache.shad_single = this.$cache.cont.find(".shadow-single");
+ } else {
+ this.$cache.cont.append(double_html);
+ this.$cache.s_from = this.$cache.cont.find(".from");
+ this.$cache.s_to = this.$cache.cont.find(".to");
+ this.$cache.shad_from = this.$cache.cont.find(".shadow-from");
+ this.$cache.shad_to = this.$cache.cont.find(".shadow-to");
+
+ this.setTopHandler();
+ }
+
+ if (this.options.hide_from_to) {
+ this.$cache.from[0].style.display = "none";
+ this.$cache.to[0].style.display = "none";
+ this.$cache.single[0].style.display = "none";
+ }
+
+ this.appendGrid();
+
+ if (this.options.disable) {
+ this.appendDisableMask();
+ this.$cache.input[0].disabled = true;
+ } else {
+ this.$cache.cont.removeClass("irs-disabled");
+ this.$cache.input[0].disabled = false;
+ this.bindEvents();
+ }
+
+ if (this.options.drag_interval) {
+ this.$cache.bar[0].style.cursor = "ew-resize";
+ }
+ },
+
+ /**
+ * Determine which handler has a priority
+ * works only for double slider type
+ */
+ setTopHandler: function () {
+ var min = this.options.min,
+ max = this.options.max,
+ from = this.options.from,
+ to = this.options.to;
+
+ if (from > min && to === max) {
+ this.$cache.s_from.addClass("type_last");
+ } else if (to < max) {
+ this.$cache.s_to.addClass("type_last");
+ }
+ },
+
+ /**
+ * Determine which handles was clicked last
+ * and which handler should have hover effect
+ *
+ * @param target {String}
+ */
+ changeLevel: function (target) {
+ switch (target) {
+ case "single":
+ this.coords.p_gap = this.toFixed(this.coords.p_pointer - this.coords.p_single_fake);
+ break;
+ case "from":
+ this.coords.p_gap = this.toFixed(this.coords.p_pointer - this.coords.p_from_fake);
+ this.$cache.s_from.addClass("state_hover");
+ this.$cache.s_from.addClass("type_last");
+ this.$cache.s_to.removeClass("type_last");
+ break;
+ case "to":
+ this.coords.p_gap = this.toFixed(this.coords.p_pointer - this.coords.p_to_fake);
+ this.$cache.s_to.addClass("state_hover");
+ this.$cache.s_to.addClass("type_last");
+ this.$cache.s_from.removeClass("type_last");
+ break;
+ case "both":
+ this.coords.p_gap_left = this.toFixed(this.coords.p_pointer - this.coords.p_from_fake);
+ this.coords.p_gap_right = this.toFixed(this.coords.p_to_fake - this.coords.p_pointer);
+ this.$cache.s_to.removeClass("type_last");
+ this.$cache.s_from.removeClass("type_last");
+ break;
+ }
+ },
+
+ /**
+ * Then slider is disabled
+ * appends extra layer with opacity
+ */
+ appendDisableMask: function () {
+ this.$cache.cont.append(disable_html);
+ this.$cache.cont.addClass("irs-disabled");
+ },
+
+ /**
+ * Remove slider instance
+ * and ubind all events
+ */
+ remove: function () {
+ this.$cache.cont.remove();
+ this.$cache.cont = null;
+
+ this.$cache.line.off("keydown.irs_" + this.plugin_count);
+
+ this.$cache.body.off("touchmove.irs_" + this.plugin_count);
+ this.$cache.body.off("mousemove.irs_" + this.plugin_count);
+
+ this.$cache.win.off("touchend.irs_" + this.plugin_count);
+ this.$cache.win.off("mouseup.irs_" + this.plugin_count);
+
+ if (is_old_ie) {
+ this.$cache.body.off("mouseup.irs_" + this.plugin_count);
+ this.$cache.body.off("mouseleave.irs_" + this.plugin_count);
+ }
+
+ this.$cache.grid_labels = [];
+ this.coords.big = [];
+ this.coords.big_w = [];
+ this.coords.big_p = [];
+ this.coords.big_x = [];
+
+ cancelAnimationFrame(this.raf_id);
+ },
+
+ /**
+ * bind all slider events
+ */
+ bindEvents: function () {
+ if (this.no_diapason) {
+ return;
+ }
+
+ this.$cache.body.on("touchmove.irs_" + this.plugin_count, this.pointerMove.bind(this));
+ this.$cache.body.on("mousemove.irs_" + this.plugin_count, this.pointerMove.bind(this));
+
+ this.$cache.win.on("touchend.irs_" + this.plugin_count, this.pointerUp.bind(this));
+ this.$cache.win.on("mouseup.irs_" + this.plugin_count, this.pointerUp.bind(this));
+
+ this.$cache.line.on("touchstart.irs_" + this.plugin_count, this.pointerClick.bind(this, "click"));
+ this.$cache.line.on("mousedown.irs_" + this.plugin_count, this.pointerClick.bind(this, "click"));
+
+ if (this.options.drag_interval && this.options.type === "double") {
+ this.$cache.bar.on("touchstart.irs_" + this.plugin_count, this.pointerDown.bind(this, "both"));
+ this.$cache.bar.on("mousedown.irs_" + this.plugin_count, this.pointerDown.bind(this, "both"));
+ } else {
+ this.$cache.bar.on("touchstart.irs_" + this.plugin_count, this.pointerClick.bind(this, "click"));
+ this.$cache.bar.on("mousedown.irs_" + this.plugin_count, this.pointerClick.bind(this, "click"));
+ }
+
+ if (this.options.type === "single") {
+ this.$cache.single.on("touchstart.irs_" + this.plugin_count, this.pointerDown.bind(this, "single"));
+ this.$cache.s_single.on("touchstart.irs_" + this.plugin_count, this.pointerDown.bind(this, "single"));
+ this.$cache.shad_single.on("touchstart.irs_" + this.plugin_count, this.pointerClick.bind(this, "click"));
+
+ this.$cache.single.on("mousedown.irs_" + this.plugin_count, this.pointerDown.bind(this, "single"));
+ this.$cache.s_single.on("mousedown.irs_" + this.plugin_count, this.pointerDown.bind(this, "single"));
+ this.$cache.edge.on("mousedown.irs_" + this.plugin_count, this.pointerClick.bind(this, "click"));
+ this.$cache.shad_single.on("mousedown.irs_" + this.plugin_count, this.pointerClick.bind(this, "click"));
+ } else {
+ this.$cache.single.on("touchstart.irs_" + this.plugin_count, this.pointerDown.bind(this, null));
+ this.$cache.single.on("mousedown.irs_" + this.plugin_count, this.pointerDown.bind(this, null));
+
+ this.$cache.from.on("touchstart.irs_" + this.plugin_count, this.pointerDown.bind(this, "from"));
+ this.$cache.s_from.on("touchstart.irs_" + this.plugin_count, this.pointerDown.bind(this, "from"));
+ this.$cache.to.on("touchstart.irs_" + this.plugin_count, this.pointerDown.bind(this, "to"));
+ this.$cache.s_to.on("touchstart.irs_" + this.plugin_count, this.pointerDown.bind(this, "to"));
+ this.$cache.shad_from.on("touchstart.irs_" + this.plugin_count, this.pointerClick.bind(this, "click"));
+ this.$cache.shad_to.on("touchstart.irs_" + this.plugin_count, this.pointerClick.bind(this, "click"));
+
+ this.$cache.from.on("mousedown.irs_" + this.plugin_count, this.pointerDown.bind(this, "from"));
+ this.$cache.s_from.on("mousedown.irs_" + this.plugin_count, this.pointerDown.bind(this, "from"));
+ this.$cache.to.on("mousedown.irs_" + this.plugin_count, this.pointerDown.bind(this, "to"));
+ this.$cache.s_to.on("mousedown.irs_" + this.plugin_count, this.pointerDown.bind(this, "to"));
+ this.$cache.shad_from.on("mousedown.irs_" + this.plugin_count, this.pointerClick.bind(this, "click"));
+ this.$cache.shad_to.on("mousedown.irs_" + this.plugin_count, this.pointerClick.bind(this, "click"));
+ }
+
+ if (this.options.keyboard) {
+ this.$cache.line.on("keydown.irs_" + this.plugin_count, this.key.bind(this, "keyboard"));
+ }
+
+ if (is_old_ie) {
+ this.$cache.body.on("mouseup.irs_" + this.plugin_count, this.pointerUp.bind(this));
+ this.$cache.body.on("mouseleave.irs_" + this.plugin_count, this.pointerUp.bind(this));
+ }
+ },
+
+ /**
+ * Mousemove or touchmove
+ * only for handlers
+ *
+ * @param e {Object} event object
+ */
+ pointerMove: function (e) {
+ if (!this.dragging) {
+ return;
+ }
+
+ var x = e.pageX || e.originalEvent.touches && e.originalEvent.touches[0].pageX;
+ this.coords.x_pointer = x - this.coords.x_gap;
+
+ this.calc();
+ },
+
+ /**
+ * Mouseup or touchend
+ * only for handlers
+ *
+ * @param e {Object} event object
+ */
+ pointerUp: function (e) {
+ if (this.current_plugin !== this.plugin_count) {
+ return;
+ }
+
+ if (this.is_active) {
+ this.is_active = false;
+ } else {
+ return;
+ }
+
+ this.$cache.cont.find(".state_hover").removeClass("state_hover");
+
+ this.force_redraw = true;
+
+ if (is_old_ie) {
+ $("*").prop("unselectable", false);
+ }
+
+ this.updateScene();
+ this.restoreOriginalMinInterval();
+
+ // callbacks call
+ if ($.contains(this.$cache.cont[0], e.target) || this.dragging) {
+ this.is_finish = true;
+ this.callOnFinish();
+ }
+
+ this.dragging = false;
+ },
+
+ /**
+ * Mousedown or touchstart
+ * only for handlers
+ *
+ * @param target {String|null}
+ * @param e {Object} event object
+ */
+ pointerDown: function (target, e) {
+ e.preventDefault();
+ var x = e.pageX || e.originalEvent.touches && e.originalEvent.touches[0].pageX;
+ if (e.button === 2) {
+ return;
+ }
+
+ if (target === "both") {
+ this.setTempMinInterval();
+ }
+
+ if (!target) {
+ target = this.target;
+ }
+
+ this.current_plugin = this.plugin_count;
+ this.target = target;
+
+ this.is_active = true;
+ this.dragging = true;
+
+ this.coords.x_gap = this.$cache.rs.offset().left;
+ this.coords.x_pointer = x - this.coords.x_gap;
+
+ this.calcPointerPercent();
+ this.changeLevel(target);
+
+ if (is_old_ie) {
+ $("*").prop("unselectable", true);
+ }
+
+ this.$cache.line.trigger("focus");
+
+ this.updateScene();
+ },
+
+ /**
+ * Mousedown or touchstart
+ * for other slider elements, like diapason line
+ *
+ * @param target {String}
+ * @param e {Object} event object
+ */
+ pointerClick: function (target, e) {
+ e.preventDefault();
+ var x = e.pageX || e.originalEvent.touches && e.originalEvent.touches[0].pageX;
+ if (e.button === 2) {
+ return;
+ }
+
+ this.current_plugin = this.plugin_count;
+ this.target = target;
+
+ this.is_click = true;
+ this.coords.x_gap = this.$cache.rs.offset().left;
+ this.coords.x_pointer = +(x - this.coords.x_gap).toFixed();
+
+ this.force_redraw = true;
+ this.calc();
+
+ this.$cache.line.trigger("focus");
+ },
+
+ /**
+ * Keyborard controls for focused slider
+ *
+ * @param target {String}
+ * @param e {Object} event object
+ * @returns {boolean|undefined}
+ */
+ key: function (target, e) {
+ if (this.current_plugin !== this.plugin_count || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
+ return;
+ }
+
+ switch (e.which) {
+ case 83: // W
+ case 65: // A
+ case 40: // DOWN
+ case 37: // LEFT
+ e.preventDefault();
+ this.moveByKey(false);
+ break;
+
+ case 87: // S
+ case 68: // D
+ case 38: // UP
+ case 39: // RIGHT
+ e.preventDefault();
+ this.moveByKey(true);
+ break;
+ }
+
+ return true;
+ },
+
+ /**
+ * Move by key. Beta
+ * @todo refactor than have plenty of time
+ *
+ * @param right {boolean} direction to move
+ */
+ moveByKey: function (right) {
+ var p = this.coords.p_pointer;
+
+ if (right) {
+ p += this.options.keyboard_step;
+ } else {
+ p -= this.options.keyboard_step;
+ }
+
+ this.coords.x_pointer = this.toFixed(this.coords.w_rs / 100 * p);
+ this.is_key = true;
+ this.calc();
+ },
+
+ /**
+ * Set visibility and content
+ * of Min and Max labels
+ */
+ setMinMax: function () {
+ if (!this.options) {
+ return;
+ }
+
+ if (this.options.hide_min_max) {
+ this.$cache.min[0].style.display = "none";
+ this.$cache.max[0].style.display = "none";
+ return;
+ }
+
+ if (this.options.values.length) {
+ this.$cache.min.html(this.decorate(this.options.p_values[this.options.min]));
+ this.$cache.max.html(this.decorate(this.options.p_values[this.options.max]));
+ } else {
+ this.$cache.min.html(this.decorate(this._prettify(this.options.min), this.options.min));
+ this.$cache.max.html(this.decorate(this._prettify(this.options.max), this.options.max));
+ }
+
+ this.labels.w_min = this.$cache.min.outerWidth(false);
+ this.labels.w_max = this.$cache.max.outerWidth(false);
+ },
+
+ /**
+ * Then dragging interval, prevent interval collapsing
+ * using min_interval option
+ */
+ setTempMinInterval: function () {
+ var interval = this.result.to - this.result.from;
+
+ if (this.old_min_interval === null) {
+ this.old_min_interval = this.options.min_interval;
+ }
+
+ this.options.min_interval = interval;
+ },
+
+ /**
+ * Restore min_interval option to original
+ */
+ restoreOriginalMinInterval: function () {
+ if (this.old_min_interval !== null) {
+ this.options.min_interval = this.old_min_interval;
+ this.old_min_interval = null;
+ }
+ },
+
+
+
+ // =============================================================================================================
+ // Calculations
+
+ /**
+ * All calculations and measures start here
+ *
+ * @param update {boolean=}
+ */
+ calc: function (update) {
+ if (!this.options) {
+ return;
+ }
+
+ this.calc_count++;
+
+ if (this.calc_count === 10 || update) {
+ this.calc_count = 0;
+ this.coords.w_rs = this.$cache.rs.outerWidth(false);
+
+ this.calcHandlePercent();
+ }
+
+ if (!this.coords.w_rs) {
+ return;
+ }
+
+ this.calcPointerPercent();
+ var handle_x = this.getHandleX();
+
+ if (this.target === "click") {
+ this.coords.p_gap = this.coords.p_handle / 2;
+ handle_x = this.getHandleX();
+
+ if (this.options.drag_interval) {
+ this.target = "both_one";
+ } else {
+ this.target = this.chooseHandle(handle_x);
+ }
+ }
+
+ switch (this.target) {
+ case "base":
+ var w = (this.options.max - this.options.min) / 100,
+ f = (this.result.from - this.options.min) / w,
+ t = (this.result.to - this.options.min) / w;
+
+ this.coords.p_single_real = this.toFixed(f);
+ this.coords.p_from_real = this.toFixed(f);
+ this.coords.p_to_real = this.toFixed(t);
+
+ this.coords.p_single_real = this.checkDiapason(this.coords.p_single_real, this.options.from_min, this.options.from_max);
+ this.coords.p_from_real = this.checkDiapason(this.coords.p_from_real, this.options.from_min, this.options.from_max);
+ this.coords.p_to_real = this.checkDiapason(this.coords.p_to_real, this.options.to_min, this.options.to_max);
+
+ this.coords.p_single_fake = this.convertToFakePercent(this.coords.p_single_real);
+ this.coords.p_from_fake = this.convertToFakePercent(this.coords.p_from_real);
+ this.coords.p_to_fake = this.convertToFakePercent(this.coords.p_to_real);
+
+ this.target = null;
+
+ break;
+
+ case "single":
+ if (this.options.from_fixed) {
+ break;
+ }
+
+ this.coords.p_single_real = this.convertToRealPercent(handle_x);
+ this.coords.p_single_real = this.calcWithStep(this.coords.p_single_real);
+ this.coords.p_single_real = this.checkDiapason(this.coords.p_single_real, this.options.from_min, this.options.from_max);
+
+ this.coords.p_single_fake = this.convertToFakePercent(this.coords.p_single_real);
+
+ break;
+
+ case "from":
+ if (this.options.from_fixed) {
+ break;
+ }
+
+ this.coords.p_from_real = this.convertToRealPercent(handle_x);
+ this.coords.p_from_real = this.calcWithStep(this.coords.p_from_real);
+ if (this.coords.p_from_real > this.coords.p_to_real) {
+ this.coords.p_from_real = this.coords.p_to_real;
+ }
+ this.coords.p_from_real = this.checkDiapason(this.coords.p_from_real, this.options.from_min, this.options.from_max);
+ this.coords.p_from_real = this.checkMinInterval(this.coords.p_from_real, this.coords.p_to_real, "from");
+ this.coords.p_from_real = this.checkMaxInterval(this.coords.p_from_real, this.coords.p_to_real, "from");
+
+ this.coords.p_from_fake = this.convertToFakePercent(this.coords.p_from_real);
+
+ break;
+
+ case "to":
+ if (this.options.to_fixed) {
+ break;
+ }
+
+ this.coords.p_to_real = this.convertToRealPercent(handle_x);
+ this.coords.p_to_real = this.calcWithStep(this.coords.p_to_real);
+ if (this.coords.p_to_real < this.coords.p_from_real) {
+ this.coords.p_to_real = this.coords.p_from_real;
+ }
+ this.coords.p_to_real = this.checkDiapason(this.coords.p_to_real, this.options.to_min, this.options.to_max);
+ this.coords.p_to_real = this.checkMinInterval(this.coords.p_to_real, this.coords.p_from_real, "to");
+ this.coords.p_to_real = this.checkMaxInterval(this.coords.p_to_real, this.coords.p_from_real, "to");
+
+ this.coords.p_to_fake = this.convertToFakePercent(this.coords.p_to_real);
+
+ break;
+
+ case "both":
+ if (this.options.from_fixed || this.options.to_fixed) {
+ break;
+ }
+
+ handle_x = this.toFixed(handle_x + (this.coords.p_handle * 0.1));
+
+ this.coords.p_from_real = this.convertToRealPercent(handle_x) - this.coords.p_gap_left;
+ this.coords.p_from_real = this.calcWithStep(this.coords.p_from_real);
+ this.coords.p_from_real = this.checkDiapason(this.coords.p_from_real, this.options.from_min, this.options.from_max);
+ this.coords.p_from_real = this.checkMinInterval(this.coords.p_from_real, this.coords.p_to_real, "from");
+ this.coords.p_from_fake = this.convertToFakePercent(this.coords.p_from_real);
+
+ this.coords.p_to_real = this.convertToRealPercent(handle_x) + this.coords.p_gap_right;
+ this.coords.p_to_real = this.calcWithStep(this.coords.p_to_real);
+ this.coords.p_to_real = this.checkDiapason(this.coords.p_to_real, this.options.to_min, this.options.to_max);
+ this.coords.p_to_real = this.checkMinInterval(this.coords.p_to_real, this.coords.p_from_real, "to");
+ this.coords.p_to_fake = this.convertToFakePercent(this.coords.p_to_real);
+
+ break;
+
+ case "both_one":
+ if (this.options.from_fixed || this.options.to_fixed) {
+ break;
+ }
+
+ var real_x = this.convertToRealPercent(handle_x),
+ from = this.result.from_percent,
+ to = this.result.to_percent,
+ full = to - from,
+ half = full / 2,
+ new_from = real_x - half,
+ new_to = real_x + half;
+
+ if (new_from < 0) {
+ new_from = 0;
+ new_to = new_from + full;
+ }
+
+ if (new_to > 100) {
+ new_to = 100;
+ new_from = new_to - full;
+ }
+
+ this.coords.p_from_real = this.calcWithStep(new_from);
+ this.coords.p_from_real = this.checkDiapason(this.coords.p_from_real, this.options.from_min, this.options.from_max);
+ this.coords.p_from_fake = this.convertToFakePercent(this.coords.p_from_real);
+
+ this.coords.p_to_real = this.calcWithStep(new_to);
+ this.coords.p_to_real = this.checkDiapason(this.coords.p_to_real, this.options.to_min, this.options.to_max);
+ this.coords.p_to_fake = this.convertToFakePercent(this.coords.p_to_real);
+
+ break;
+ }
+
+ if (this.options.type === "single") {
+ this.coords.p_bar_x = (this.coords.p_handle / 2);
+ this.coords.p_bar_w = this.coords.p_single_fake;
+
+ this.result.from_percent = this.coords.p_single_real;
+ this.result.from = this.convertToValue(this.coords.p_single_real);
+
+ if (this.options.values.length) {
+ this.result.from_value = this.options.values[this.result.from];
+ }
+ } else {
+ this.coords.p_bar_x = this.toFixed(this.coords.p_from_fake + (this.coords.p_handle / 2));
+ this.coords.p_bar_w = this.toFixed(this.coords.p_to_fake - this.coords.p_from_fake);
+
+ this.result.from_percent = this.coords.p_from_real;
+ this.result.from = this.convertToValue(this.coords.p_from_real);
+ this.result.to_percent = this.coords.p_to_real;
+ this.result.to = this.convertToValue(this.coords.p_to_real);
+
+ if (this.options.values.length) {
+ this.result.from_value = this.options.values[this.result.from];
+ this.result.to_value = this.options.values[this.result.to];
+ }
+ }
+
+ this.calcMinMax();
+ this.calcLabels();
+ },
+
+
+ /**
+ * calculates pointer X in percent
+ */
+ calcPointerPercent: function () {
+ if (!this.coords.w_rs) {
+ this.coords.p_pointer = 0;
+ return;
+ }
+
+ if (this.coords.x_pointer < 0 || isNaN(this.coords.x_pointer) ) {
+ this.coords.x_pointer = 0;
+ } else if (this.coords.x_pointer > this.coords.w_rs) {
+ this.coords.x_pointer = this.coords.w_rs;
+ }
+
+ this.coords.p_pointer = this.toFixed(this.coords.x_pointer / this.coords.w_rs * 100);
+ },
+
+ convertToRealPercent: function (fake) {
+ var full = 100 - this.coords.p_handle;
+ return fake / full * 100;
+ },
+
+ convertToFakePercent: function (real) {
+ var full = 100 - this.coords.p_handle;
+ return real / 100 * full;
+ },
+
+ getHandleX: function () {
+ var max = 100 - this.coords.p_handle,
+ x = this.toFixed(this.coords.p_pointer - this.coords.p_gap);
+
+ if (x < 0) {
+ x = 0;
+ } else if (x > max) {
+ x = max;
+ }
+
+ return x;
+ },
+
+ calcHandlePercent: function () {
+ if (this.options.type === "single") {
+ this.coords.w_handle = this.$cache.s_single.outerWidth(false);
+ } else {
+ this.coords.w_handle = this.$cache.s_from.outerWidth(false);
+ }
+
+ this.coords.p_handle = this.toFixed(this.coords.w_handle / this.coords.w_rs * 100);
+ },
+
+ /**
+ * Find closest handle to pointer click
+ *
+ * @param real_x {Number}
+ * @returns {String}
+ */
+ chooseHandle: function (real_x) {
+ if (this.options.type === "single") {
+ return "single";
+ } else {
+ var m_point = this.coords.p_from_real + ((this.coords.p_to_real - this.coords.p_from_real) / 2);
+ if (real_x >= m_point) {
+ return this.options.to_fixed ? "from" : "to";
+ } else {
+ return this.options.from_fixed ? "to" : "from";
+ }
+ }
+ },
+
+ /**
+ * Measure Min and Max labels width in percent
+ */
+ calcMinMax: function () {
+ if (!this.coords.w_rs) {
+ return;
+ }
+
+ this.labels.p_min = this.labels.w_min / this.coords.w_rs * 100;
+ this.labels.p_max = this.labels.w_max / this.coords.w_rs * 100;
+ },
+
+ /**
+ * Measure labels width and X in percent
+ */
+ calcLabels: function () {
+ if (!this.coords.w_rs || this.options.hide_from_to) {
+ return;
+ }
+
+ if (this.options.type === "single") {
+
+ this.labels.w_single = this.$cache.single.outerWidth(false);
+ this.labels.p_single_fake = this.labels.w_single / this.coords.w_rs * 100;
+ this.labels.p_single_left = this.coords.p_single_fake + (this.coords.p_handle / 2) - (this.labels.p_single_fake / 2);
+ this.labels.p_single_left = this.checkEdges(this.labels.p_single_left, this.labels.p_single_fake);
+
+ } else {
+
+ this.labels.w_from = this.$cache.from.outerWidth(false);
+ this.labels.p_from_fake = this.labels.w_from / this.coords.w_rs * 100;
+ this.labels.p_from_left = this.coords.p_from_fake + (this.coords.p_handle / 2) - (this.labels.p_from_fake / 2);
+ this.labels.p_from_left = this.toFixed(this.labels.p_from_left);
+ this.labels.p_from_left = this.checkEdges(this.labels.p_from_left, this.labels.p_from_fake);
+
+ this.labels.w_to = this.$cache.to.outerWidth(false);
+ this.labels.p_to_fake = this.labels.w_to / this.coords.w_rs * 100;
+ this.labels.p_to_left = this.coords.p_to_fake + (this.coords.p_handle / 2) - (this.labels.p_to_fake / 2);
+ this.labels.p_to_left = this.toFixed(this.labels.p_to_left);
+ this.labels.p_to_left = this.checkEdges(this.labels.p_to_left, this.labels.p_to_fake);
+
+ this.labels.w_single = this.$cache.single.outerWidth(false);
+ this.labels.p_single_fake = this.labels.w_single / this.coords.w_rs * 100;
+ this.labels.p_single_left = ((this.labels.p_from_left + this.labels.p_to_left + this.labels.p_to_fake) / 2) - (this.labels.p_single_fake / 2);
+ this.labels.p_single_left = this.toFixed(this.labels.p_single_left);
+ this.labels.p_single_left = this.checkEdges(this.labels.p_single_left, this.labels.p_single_fake);
+
+ }
+ },
+
+
+
+ // =============================================================================================================
+ // Drawings
+
+ /**
+ * Main function called in request animation frame
+ * to update everything
+ */
+ updateScene: function () {
+ if (this.raf_id) {
+ cancelAnimationFrame(this.raf_id);
+ this.raf_id = null;
+ }
+
+ clearTimeout(this.update_tm);
+ this.update_tm = null;
+
+ if (!this.options) {
+ return;
+ }
+
+ this.drawHandles();
+
+ if (this.is_active) {
+ this.raf_id = requestAnimationFrame(this.updateScene.bind(this));
+ } else {
+ this.update_tm = setTimeout(this.updateScene.bind(this), 300);
+ }
+ },
+
+ /**
+ * Draw handles
+ */
+ drawHandles: function () {
+ this.coords.w_rs = this.$cache.rs.outerWidth(false);
+
+ if (!this.coords.w_rs) {
+ return;
+ }
+
+ if (this.coords.w_rs !== this.coords.w_rs_old) {
+ this.target = "base";
+ this.is_resize = true;
+ }
+
+ if (this.coords.w_rs !== this.coords.w_rs_old || this.force_redraw) {
+ this.setMinMax();
+ this.calc(true);
+ this.drawLabels();
+ if (this.options.grid) {
+ this.calcGridMargin();
+ this.calcGridLabels();
+ }
+ this.force_redraw = true;
+ this.coords.w_rs_old = this.coords.w_rs;
+ this.drawShadow();
+ }
+
+ if (!this.coords.w_rs) {
+ return;
+ }
+
+ if (!this.dragging && !this.force_redraw && !this.is_key) {
+ return;
+ }
+
+ if (this.old_from !== this.result.from || this.old_to !== this.result.to || this.force_redraw || this.is_key) {
+
+ this.drawLabels();
+
+ this.$cache.bar[0].style.left = this.coords.p_bar_x + "%";
+ this.$cache.bar[0].style.width = this.coords.p_bar_w + "%";
+
+ if (this.options.type === "single") {
+ this.$cache.s_single[0].style.left = this.coords.p_single_fake + "%";
+
+ this.$cache.single[0].style.left = this.labels.p_single_left + "%";
+
+ if (this.options.values.length) {
+ this.$cache.input.prop("value", this.result.from_value);
+ } else {
+ this.$cache.input.prop("value", this.result.from);
+ }
+ this.$cache.input.data("from", this.result.from);
+ } else {
+ this.$cache.s_from[0].style.left = this.coords.p_from_fake + "%";
+ this.$cache.s_to[0].style.left = this.coords.p_to_fake + "%";
+
+ if (this.old_from !== this.result.from || this.force_redraw) {
+ this.$cache.from[0].style.left = this.labels.p_from_left + "%";
+ }
+ if (this.old_to !== this.result.to || this.force_redraw) {
+ this.$cache.to[0].style.left = this.labels.p_to_left + "%";
+ }
+
+ this.$cache.single[0].style.left = this.labels.p_single_left + "%";
+
+ if (this.options.values.length) {
+ this.$cache.input.prop("value", this.result.from_value + this.options.input_values_separator + this.result.to_value);
+ } else {
+ this.$cache.input.prop("value", this.result.from + this.options.input_values_separator + this.result.to);
+ }
+ this.$cache.input.data("from", this.result.from);
+ this.$cache.input.data("to", this.result.to);
+ }
+
+ if ((this.old_from !== this.result.from || this.old_to !== this.result.to) && !this.is_start) {
+ this.$cache.input.trigger("change");
+ }
+
+ this.old_from = this.result.from;
+ this.old_to = this.result.to;
+
+ // callbacks call
+ if (!this.is_resize && !this.is_update && !this.is_start && !this.is_finish) {
+ this.callOnChange();
+ }
+ if (this.is_key || this.is_click) {
+ this.is_key = false;
+ this.is_click = false;
+ this.callOnFinish();
+ }
+
+ this.is_update = false;
+ this.is_resize = false;
+ this.is_finish = false;
+ }
+
+ this.is_start = false;
+ this.is_key = false;
+ this.is_click = false;
+ this.force_redraw = false;
+ },
+
+ /**
+ * Draw labels
+ * measure labels collisions
+ * collapse close labels
+ */
+ drawLabels: function () {
+ if (!this.options) {
+ return;
+ }
+
+ var values_num = this.options.values.length,
+ p_values = this.options.p_values,
+ text_single,
+ text_from,
+ text_to;
+
+ if (this.options.hide_from_to) {
+ return;
+ }
+
+ if (this.options.type === "single") {
+
+ if (values_num) {
+ text_single = this.decorate(p_values[this.result.from]);
+ this.$cache.single.html(text_single);
+ } else {
+ text_single = this.decorate(this._prettify(this.result.from), this.result.from);
+ this.$cache.single.html(text_single);
+ }
+
+ this.calcLabels();
+
+ if (this.labels.p_single_left < this.labels.p_min + 1) {
+ this.$cache.min[0].style.visibility = "hidden";
+ } else {
+ this.$cache.min[0].style.visibility = "visible";
+ }
+
+ if (this.labels.p_single_left + this.labels.p_single_fake > 100 - this.labels.p_max - 1) {
+ this.$cache.max[0].style.visibility = "hidden";
+ } else {
+ this.$cache.max[0].style.visibility = "visible";
+ }
+
+ } else {
+
+ if (values_num) {
+
+ if (this.options.decorate_both) {
+ text_single = this.decorate(p_values[this.result.from]);
+ text_single += this.options.values_separator;
+ text_single += this.decorate(p_values[this.result.to]);
+ } else {
+ text_single = this.decorate(p_values[this.result.from] + this.options.values_separator + p_values[this.result.to]);
+ }
+ text_from = this.decorate(p_values[this.result.from]);
+ text_to = this.decorate(p_values[this.result.to]);
+
+ this.$cache.single.html(text_single);
+ this.$cache.from.html(text_from);
+ this.$cache.to.html(text_to);
+
+ } else {
+
+ if (this.options.decorate_both) {
+ text_single = this.decorate(this._prettify(this.result.from), this.result.from);
+ text_single += this.options.values_separator;
+ text_single += this.decorate(this._prettify(this.result.to), this.result.to);
+ } else {
+ text_single = this.decorate(this._prettify(this.result.from) + this.options.values_separator + this._prettify(this.result.to), this.result.to);
+ }
+ text_from = this.decorate(this._prettify(this.result.from), this.result.from);
+ text_to = this.decorate(this._prettify(this.result.to), this.result.to);
+
+ this.$cache.single.html(text_single);
+ this.$cache.from.html(text_from);
+ this.$cache.to.html(text_to);
+
+ }
+
+ this.calcLabels();
+
+ var min = Math.min(this.labels.p_single_left, this.labels.p_from_left),
+ single_left = this.labels.p_single_left + this.labels.p_single_fake,
+ to_left = this.labels.p_to_left + this.labels.p_to_fake,
+ max = Math.max(single_left, to_left);
+
+ if (this.labels.p_from_left + this.labels.p_from_fake >= this.labels.p_to_left) {
+ this.$cache.from[0].style.visibility = "hidden";
+ this.$cache.to[0].style.visibility = "hidden";
+ this.$cache.single[0].style.visibility = "visible";
+
+ if (this.result.from === this.result.to) {
+ if (this.target === "from") {
+ this.$cache.from[0].style.visibility = "visible";
+ } else if (this.target === "to") {
+ this.$cache.to[0].style.visibility = "visible";
+ }
+ this.$cache.single[0].style.visibility = "hidden";
+ max = to_left;
+ } else {
+ this.$cache.from[0].style.visibility = "hidden";
+ this.$cache.to[0].style.visibility = "hidden";
+ this.$cache.single[0].style.visibility = "visible";
+ max = Math.max(single_left, to_left);
+ }
+ } else {
+ this.$cache.from[0].style.visibility = "visible";
+ this.$cache.to[0].style.visibility = "visible";
+ this.$cache.single[0].style.visibility = "hidden";
+ }
+
+ if (min < this.labels.p_min + 1) {
+ this.$cache.min[0].style.visibility = "hidden";
+ } else {
+ this.$cache.min[0].style.visibility = "visible";
+ }
+
+ if (max > 100 - this.labels.p_max - 1) {
+ this.$cache.max[0].style.visibility = "hidden";
+ } else {
+ this.$cache.max[0].style.visibility = "visible";
+ }
+
+ }
+ },
+
+ /**
+ * Draw shadow intervals
+ */
+ drawShadow: function () {
+ var o = this.options,
+ c = this.$cache,
+
+ is_from_min = typeof o.from_min === "number" && !isNaN(o.from_min),
+ is_from_max = typeof o.from_max === "number" && !isNaN(o.from_max),
+ is_to_min = typeof o.to_min === "number" && !isNaN(o.to_min),
+ is_to_max = typeof o.to_max === "number" && !isNaN(o.to_max),
+
+ from_min,
+ from_max,
+ to_min,
+ to_max;
+
+ if (o.type === "single") {
+ if (o.from_shadow && (is_from_min || is_from_max)) {
+ from_min = this.convertToPercent(is_from_min ? o.from_min : o.min);
+ from_max = this.convertToPercent(is_from_max ? o.from_max : o.max) - from_min;
+ from_min = this.toFixed(from_min - (this.coords.p_handle / 100 * from_min));
+ from_max = this.toFixed(from_max - (this.coords.p_handle / 100 * from_max));
+ from_min = from_min + (this.coords.p_handle / 2);
+
+ c.shad_single[0].style.display = "block";
+ c.shad_single[0].style.left = from_min + "%";
+ c.shad_single[0].style.width = from_max + "%";
+ } else {
+ c.shad_single[0].style.display = "none";
+ }
+ } else {
+ if (o.from_shadow && (is_from_min || is_from_max)) {
+ from_min = this.convertToPercent(is_from_min ? o.from_min : o.min);
+ from_max = this.convertToPercent(is_from_max ? o.from_max : o.max) - from_min;
+ from_min = this.toFixed(from_min - (this.coords.p_handle / 100 * from_min));
+ from_max = this.toFixed(from_max - (this.coords.p_handle / 100 * from_max));
+ from_min = from_min + (this.coords.p_handle / 2);
+
+ c.shad_from[0].style.display = "block";
+ c.shad_from[0].style.left = from_min + "%";
+ c.shad_from[0].style.width = from_max + "%";
+ } else {
+ c.shad_from[0].style.display = "none";
+ }
+
+ if (o.to_shadow && (is_to_min || is_to_max)) {
+ to_min = this.convertToPercent(is_to_min ? o.to_min : o.min);
+ to_max = this.convertToPercent(is_to_max ? o.to_max : o.max) - to_min;
+ to_min = this.toFixed(to_min - (this.coords.p_handle / 100 * to_min));
+ to_max = this.toFixed(to_max - (this.coords.p_handle / 100 * to_max));
+ to_min = to_min + (this.coords.p_handle / 2);
+
+ c.shad_to[0].style.display = "block";
+ c.shad_to[0].style.left = to_min + "%";
+ c.shad_to[0].style.width = to_max + "%";
+ } else {
+ c.shad_to[0].style.display = "none";
+ }
+ }
+ },
+
+
+
+ // =============================================================================================================
+ // Callbacks
+
+ callOnStart: function () {
+ if (this.options.onStart && typeof this.options.onStart === "function") {
+ this.options.onStart(this.result);
+ }
+ },
+ callOnChange: function () {
+ if (this.options.onChange && typeof this.options.onChange === "function") {
+ this.options.onChange(this.result);
+ }
+ },
+ callOnFinish: function () {
+ if (this.options.onFinish && typeof this.options.onFinish === "function") {
+ this.options.onFinish(this.result);
+ }
+ },
+ callOnUpdate: function () {
+ if (this.options.onUpdate && typeof this.options.onUpdate === "function") {
+ this.options.onUpdate(this.result);
+ }
+ },
+
+
+
+ // =============================================================================================================
+ // Service methods
+
+ toggleInput: function () {
+ this.$cache.input.toggleClass("irs-hidden-input");
+ },
+
+ /**
+ * Convert real value to percent
+ *
+ * @param value {Number} X in real
+ * @param no_min {boolean=} don't use min value
+ * @returns {Number} X in percent
+ */
+ convertToPercent: function (value, no_min) {
+ var diapason = this.options.max - this.options.min,
+ one_percent = diapason / 100,
+ val, percent;
+
+ if (!diapason) {
+ this.no_diapason = true;
+ return 0;
+ }
+
+ if (no_min) {
+ val = value;
+ } else {
+ val = value - this.options.min;
+ }
+
+ percent = val / one_percent;
+
+ return this.toFixed(percent);
+ },
+
+ /**
+ * Convert percent to real values
+ *
+ * @param percent {Number} X in percent
+ * @returns {Number} X in real
+ */
+ convertToValue: function (percent) {
+ var min = this.options.min,
+ max = this.options.max,
+ min_decimals = min.toString().split(".")[1],
+ max_decimals = max.toString().split(".")[1],
+ min_length, max_length,
+ avg_decimals = 0,
+ abs = 0;
+
+ if (percent === 0) {
+ return this.options.min;
+ }
+ if (percent === 100) {
+ return this.options.max;
+ }
+
+
+ if (min_decimals) {
+ min_length = min_decimals.length;
+ avg_decimals = min_length;
+ }
+ if (max_decimals) {
+ max_length = max_decimals.length;
+ avg_decimals = max_length;
+ }
+ if (min_length && max_length) {
+ avg_decimals = (min_length >= max_length) ? min_length : max_length;
+ }
+
+ if (min < 0) {
+ abs = Math.abs(min);
+ min = +(min + abs).toFixed(avg_decimals);
+ max = +(max + abs).toFixed(avg_decimals);
+ }
+
+ var number = ((max - min) / 100 * percent) + min,
+ string = this.options.step.toString().split(".")[1],
+ result;
+
+ if (string) {
+ number = +number.toFixed(string.length);
+ } else {
+ number = number / this.options.step;
+ number = number * this.options.step;
+
+ number = +number.toFixed(0);
+ }
+
+ if (abs) {
+ number -= abs;
+ }
+
+ if (string) {
+ result = +number.toFixed(string.length);
+ } else {
+ result = this.toFixed(number);
+ }
+
+ if (result < this.options.min) {
+ result = this.options.min;
+ } else if (result > this.options.max) {
+ result = this.options.max;
+ }
+
+ return result;
+ },
+
+ /**
+ * Round percent value with step
+ *
+ * @param percent {Number}
+ * @returns percent {Number} rounded
+ */
+ calcWithStep: function (percent) {
+ var rounded = Math.round(percent / this.coords.p_step) * this.coords.p_step;
+
+ if (rounded > 100) {
+ rounded = 100;
+ }
+ if (percent === 100) {
+ rounded = 100;
+ }
+
+ return this.toFixed(rounded);
+ },
+
+ checkMinInterval: function (p_current, p_next, type) {
+ var o = this.options,
+ current,
+ next;
+
+ if (!o.min_interval) {
+ return p_current;
+ }
+
+ current = this.convertToValue(p_current);
+ next = this.convertToValue(p_next);
+
+ if (type === "from") {
+
+ if (next - current < o.min_interval) {
+ current = next - o.min_interval;
+ }
+
+ } else {
+
+ if (current - next < o.min_interval) {
+ current = next + o.min_interval;
+ }
+
+ }
+
+ return this.convertToPercent(current);
+ },
+
+ checkMaxInterval: function (p_current, p_next, type) {
+ var o = this.options,
+ current,
+ next;
+
+ if (!o.max_interval) {
+ return p_current;
+ }
+
+ current = this.convertToValue(p_current);
+ next = this.convertToValue(p_next);
+
+ if (type === "from") {
+
+ if (next - current > o.max_interval) {
+ current = next - o.max_interval;
+ }
+
+ } else {
+
+ if (current - next > o.max_interval) {
+ current = next + o.max_interval;
+ }
+
+ }
+
+ return this.convertToPercent(current);
+ },
+
+ checkDiapason: function (p_num, min, max) {
+ var num = this.convertToValue(p_num),
+ o = this.options;
+
+ if (typeof min !== "number") {
+ min = o.min;
+ }
+
+ if (typeof max !== "number") {
+ max = o.max;
+ }
+
+ if (num < min) {
+ num = min;
+ }
+
+ if (num > max) {
+ num = max;
+ }
+
+ return this.convertToPercent(num);
+ },
+
+ toFixed: function (num) {
+ num = num.toFixed(9);
+ return +num;
+ },
+
+ _prettify: function (num) {
+ if (!this.options.prettify_enabled) {
+ return num;
+ }
+
+ if (this.options.prettify && typeof this.options.prettify === "function") {
+ return this.options.prettify(num);
+ } else {
+ return this.prettify(num);
+ }
+ },
+
+ prettify: function (num) {
+ var n = num.toString();
+ return n.replace(/(\d{1,3}(?=(?:\d\d\d)+(?!\d)))/g, "$1" + this.options.prettify_separator);
+ },
+
+ checkEdges: function (left, width) {
+ if (!this.options.force_edges) {
+ return this.toFixed(left);
+ }
+
+ if (left < 0) {
+ left = 0;
+ } else if (left > 100 - width) {
+ left = 100 - width;
+ }
+
+ return this.toFixed(left);
+ },
+
+ validate: function () {
+ var o = this.options,
+ r = this.result,
+ v = o.values,
+ vl = v.length,
+ value,
+ i;
+
+ if (typeof o.min === "string") o.min = +o.min;
+ if (typeof o.max === "string") o.max = +o.max;
+ if (typeof o.from === "string") o.from = +o.from;
+ if (typeof o.to === "string") o.to = +o.to;
+ if (typeof o.step === "string") o.step = +o.step;
+
+ if (typeof o.from_min === "string") o.from_min = +o.from_min;
+ if (typeof o.from_max === "string") o.from_max = +o.from_max;
+ if (typeof o.to_min === "string") o.to_min = +o.to_min;
+ if (typeof o.to_max === "string") o.to_max = +o.to_max;
+
+ if (typeof o.keyboard_step === "string") o.keyboard_step = +o.keyboard_step;
+ if (typeof o.grid_num === "string") o.grid_num = +o.grid_num;
+
+ if (o.max < o.min) {
+ o.max = o.min;
+ }
+
+ if (vl) {
+ o.p_values = [];
+ o.min = 0;
+ o.max = vl - 1;
+ o.step = 1;
+ o.grid_num = o.max;
+ o.grid_snap = true;
+
+
+ for (i = 0; i < vl; i++) {
+ value = +v[i];
+
+ if (!isNaN(value)) {
+ v[i] = value;
+ value = this._prettify(value);
+ } else {
+ value = v[i];
+ }
+
+ o.p_values.push(value);
+ }
+ }
+
+ if (typeof o.from !== "number" || isNaN(o.from)) {
+ o.from = o.min;
+ }
+
+ if (typeof o.to !== "number" || isNaN(o.from)) {
+ o.to = o.max;
+ }
+
+ if (o.type === "single") {
+
+ if (o.from < o.min) {
+ o.from = o.min;
+ }
+
+ if (o.from > o.max) {
+ o.from = o.max;
+ }
+
+ } else {
+
+ if (o.from < o.min || o.from > o.max) {
+ o.from = o.min;
+ }
+ if (o.to > o.max || o.to < o.min) {
+ o.to = o.max;
+ }
+ if (o.from > o.to) {
+ o.from = o.to;
+ }
+
+ }
+
+ if (typeof o.step !== "number" || isNaN(o.step) || !o.step || o.step < 0) {
+ o.step = 1;
+ }
+
+ if (typeof o.keyboard_step !== "number" || isNaN(o.keyboard_step) || !o.keyboard_step || o.keyboard_step < 0) {
+ o.keyboard_step = 5;
+ }
+
+ if (typeof o.from_min === "number" && o.from < o.from_min) {
+ o.from = o.from_min;
+ }
+
+ if (typeof o.from_max === "number" && o.from > o.from_max) {
+ o.from = o.from_max;
+ }
+
+ if (typeof o.to_min === "number" && o.to < o.to_min) {
+ o.to = o.to_min;
+ }
+
+ if (typeof o.to_max === "number" && o.from > o.to_max) {
+ o.to = o.to_max;
+ }
+
+ if (r) {
+ if (r.min !== o.min) {
+ r.min = o.min;
+ }
+
+ if (r.max !== o.max) {
+ r.max = o.max;
+ }
+
+ if (r.from < r.min || r.from > r.max) {
+ r.from = o.from;
+ }
+
+ if (r.to < r.min || r.to > r.max) {
+ r.to = o.to;
+ }
+ }
+
+ if (typeof o.min_interval !== "number" || isNaN(o.min_interval) || !o.min_interval || o.min_interval < 0) {
+ o.min_interval = 0;
+ }
+
+ if (typeof o.max_interval !== "number" || isNaN(o.max_interval) || !o.max_interval || o.max_interval < 0) {
+ o.max_interval = 0;
+ }
+
+ if (o.min_interval && o.min_interval > o.max - o.min) {
+ o.min_interval = o.max - o.min;
+ }
+
+ if (o.max_interval && o.max_interval > o.max - o.min) {
+ o.max_interval = o.max - o.min;
+ }
+ },
+
+ decorate: function (num, original) {
+ var decorated = "",
+ o = this.options;
+
+ if (o.prefix) {
+ decorated += o.prefix;
+ }
+
+ decorated += num;
+
+ if (o.max_postfix) {
+ if (o.values.length && num === o.p_values[o.max]) {
+ decorated += o.max_postfix;
+ if (o.postfix) {
+ decorated += " ";
+ }
+ } else if (original === o.max) {
+ decorated += o.max_postfix;
+ if (o.postfix) {
+ decorated += " ";
+ }
+ }
+ }
+
+ if (o.postfix) {
+ decorated += o.postfix;
+ }
+
+ return decorated;
+ },
+
+ updateFrom: function () {
+ this.result.from = this.options.from;
+ this.result.from_percent = this.convertToPercent(this.result.from);
+ if (this.options.values) {
+ this.result.from_value = this.options.values[this.result.from];
+ }
+ },
+
+ updateTo: function () {
+ this.result.to = this.options.to;
+ this.result.to_percent = this.convertToPercent(this.result.to);
+ if (this.options.values) {
+ this.result.to_value = this.options.values[this.result.to];
+ }
+ },
+
+ updateResult: function () {
+ this.result.min = this.options.min;
+ this.result.max = this.options.max;
+ this.updateFrom();
+ this.updateTo();
+ },
+
+
+ // =============================================================================================================
+ // Grid
+
+ appendGrid: function () {
+ if (!this.options.grid) {
+ return;
+ }
+
+ var o = this.options,
+ i, z,
+
+ total = o.max - o.min,
+ big_num = o.grid_num,
+ big_p = 0,
+ big_w = 0,
+
+ small_max = 4,
+ local_small_max,
+ small_p,
+ small_w = 0,
+
+ result,
+ html = '';
+
+
+
+ this.calcGridMargin();
+
+ if (o.grid_snap) {
+ big_num = total / o.step;
+ big_p = this.toFixed(o.step / (total / 100));
+ } else {
+ big_p = this.toFixed(100 / big_num);
+ }
+
+ if (big_num > 4) {
+ small_max = 3;
+ }
+ if (big_num > 7) {
+ small_max = 2;
+ }
+ if (big_num > 14) {
+ small_max = 1;
+ }
+ if (big_num > 28) {
+ small_max = 0;
+ }
+
+ for (i = 0; i < big_num + 1; i++) {
+ local_small_max = small_max;
+
+ big_w = this.toFixed(big_p * i);
+
+ if (big_w > 100) {
+ big_w = 100;
+
+ local_small_max -= 2;
+ if (local_small_max < 0) {
+ local_small_max = 0;
+ }
+ }
+ this.coords.big[i] = big_w;
+
+ small_p = (big_w - (big_p * (i - 1))) / (local_small_max + 1);
+
+ for (z = 1; z <= local_small_max; z++) {
+ if (big_w === 0) {
+ break;
+ }
+
+ small_w = this.toFixed(big_w - (small_p * z));
+
+ html += '<span class="irs-grid-pol small" style="left: ' + small_w + '%"></span>';
+ }
+
+ html += '<span class="irs-grid-pol" style="left: ' + big_w + '%"></span>';
+
+ result = this.convertToValue(big_w);
+ if (o.values.length) {
+ result = o.p_values[result];
+ } else {
+ result = this._prettify(result);
+ }
+
+ html += '<span class="irs-grid-text js-grid-text-' + i + '" style="left: ' + big_w + '%">' + result + '</span>';
+ }
+ this.coords.big_num = Math.ceil(big_num + 1);
+
+
+
+ this.$cache.cont.addClass("irs-with-grid");
+ this.$cache.grid.html(html);
+ this.cacheGridLabels();
+ },
+
+ cacheGridLabels: function () {
+ var $label, i,
+ num = this.coords.big_num;
+
+ for (i = 0; i < num; i++) {
+ $label = this.$cache.grid.find(".js-grid-text-" + i);
+ this.$cache.grid_labels.push($label);
+ }
+
+ this.calcGridLabels();
+ },
+
+ calcGridLabels: function () {
+ var i, label, start = [], finish = [],
+ num = this.coords.big_num;
+
+ for (i = 0; i < num; i++) {
+ this.coords.big_w[i] = this.$cache.grid_labels[i].outerWidth(false);
+ this.coords.big_p[i] = this.toFixed(this.coords.big_w[i] / this.coords.w_rs * 100);
+ this.coords.big_x[i] = this.toFixed(this.coords.big_p[i] / 2);
+
+ start[i] = this.toFixed(this.coords.big[i] - this.coords.big_x[i]);
+ finish[i] = this.toFixed(start[i] + this.coords.big_p[i]);
+ }
+
+ if (this.options.force_edges) {
+ if (start[0] < -this.coords.grid_gap) {
+ start[0] = -this.coords.grid_gap;
+ finish[0] = this.toFixed(start[0] + this.coords.big_p[0]);
+
+ this.coords.big_x[0] = this.coords.grid_gap;
+ }
+
+ if (finish[num - 1] > 100 + this.coords.grid_gap) {
+ finish[num - 1] = 100 + this.coords.grid_gap;
+ start[num - 1] = this.toFixed(finish[num - 1] - this.coords.big_p[num - 1]);
+
+ this.coords.big_x[num - 1] = this.toFixed(this.coords.big_p[num - 1] - this.coords.grid_gap);
+ }
+ }
+
+ this.calcGridCollision(2, start, finish);
+ this.calcGridCollision(4, start, finish);
+
+ for (i = 0; i < num; i++) {
+ label = this.$cache.grid_labels[i][0];
+ label.style.marginLeft = -this.coords.big_x[i] + "%";
+ }
+ },
+
+ // Collisions Calc Beta
+ // TODO: Refactor then have plenty of time
+ calcGridCollision: function (step, start, finish) {
+ var i, next_i, label,
+ num = this.coords.big_num;
+
+ for (i = 0; i < num; i += step) {
+ next_i = i + (step / 2);
+ if (next_i >= num) {
+ break;
+ }
+
+ label = this.$cache.grid_labels[next_i][0];
+
+ if (finish[i] <= start[next_i]) {
+ label.style.visibility = "visible";
+ } else {
+ label.style.visibility = "hidden";
+ }
+ }
+ },
+
+ calcGridMargin: function () {
+ if (!this.options.grid_margin) {
+ return;
+ }
+
+ this.coords.w_rs = this.$cache.rs.outerWidth(false);
+ if (!this.coords.w_rs) {
+ return;
+ }
+
+ if (this.options.type === "single") {
+ this.coords.w_handle = this.$cache.s_single.outerWidth(false);
+ } else {
+ this.coords.w_handle = this.$cache.s_from.outerWidth(false);
+ }
+ this.coords.p_handle = this.toFixed(this.coords.w_handle / this.coords.w_rs * 100);
+ this.coords.grid_gap = this.toFixed((this.coords.p_handle / 2) - 0.1);
+
+ this.$cache.grid[0].style.width = this.toFixed(100 - this.coords.p_handle) + "%";
+ this.$cache.grid[0].style.left = this.coords.grid_gap + "%";
+ },
+
+
+
+ // =============================================================================================================
+ // Public methods
+
+ update: function (options) {
+ if (!this.input) {
+ return;
+ }
+
+ this.is_update = true;
+
+ this.options.from = this.result.from;
+ this.options.to = this.result.to;
+
+ this.options = $.extend(this.options, options);
+ this.validate();
+ this.updateResult(options);
+
+ this.toggleInput();
+ this.remove();
+ this.init(true);
+ },
+
+ reset: function () {
+ if (!this.input) {
+ return;
+ }
+
+ this.updateResult();
+ this.update();
+ },
+
+ destroy: function () {
+ if (!this.input) {
+ return;
+ }
+
+ this.toggleInput();
+ this.$cache.input.prop("readonly", false);
+ $.data(this.input, "ionRangeSlider", null);
+
+ this.remove();
+ this.input = null;
+ this.options = null;
+ }
+ };
+
+ $.fn.ionRangeSlider = function (options) {
+ return this.each(function() {
+ if (!$.data(this, "ionRangeSlider")) {
+ $.data(this, "ionRangeSlider", new IonRangeSlider(this, options, plugin_count++));
+ }
+ });
+ };
+
+
+
+ // =================================================================================================================
+ // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
+ // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
+
+ // requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
+
+ // MIT license
+
+ (function() {
+ var lastTime = 0;
+ var vendors = ['ms', 'moz', 'webkit', 'o'];
+ for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
+ window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
+ window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
+ || window[vendors[x]+'CancelRequestAnimationFrame'];
+ }
+
+ if (!window.requestAnimationFrame)
+ window.requestAnimationFrame = function(callback, element) {
+ var currTime = new Date().getTime();
+ var timeToCall = Math.max(0, 16 - (currTime - lastTime));
+ var id = window.setTimeout(function() { callback(currTime + timeToCall); },
+ timeToCall);
+ lastTime = currTime + timeToCall;
+ return id;
+ };
+
+ if (!window.cancelAnimationFrame)
+ window.cancelAnimationFrame = function(id) {
+ clearTimeout(id);
+ };
+ }());
+
+} (jQuery, document, window, navigator));
diff --git a/www/wiki/vendor/onoi/shared-resources/res/jquery.rangeSlider/ion.rangeSlider.skinFlat.css b/www/wiki/vendor/onoi/shared-resources/res/jquery.rangeSlider/ion.rangeSlider.skinFlat.css
new file mode 100644
index 00000000..b748dbd8
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/jquery.rangeSlider/ion.rangeSlider.skinFlat.css
@@ -0,0 +1,106 @@
+/* Ion.RangeSlider, Flat UI Skin
+// css version 2.0.3
+// © Denis Ineshin, 2014 https://github.com/IonDen
+// ===================================================================================================================*/
+
+/* =====================================================================================================================
+// Skin details */
+
+.irs-line-mid,
+.irs-line-left,
+.irs-line-right,
+.irs-bar,
+.irs-bar-edge,
+.irs-slider {
+ background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQQAAAC0CAYAAABytVLLAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAjpJREFUeNrs3bEJwkAUgOFcSOEENoIruJnDuIizOEIkFrGzszs94SKpg8lJvg/Eq6/4eULMa9qujxXAW+0KAEEABAEQBEAQAEEABAEQBEAQAEEABAH4vRCjvzIAJgRAEABBAAQBEARAEABBAAQBEARAEABBAAQBEARAEABBAAQBEASgcE0+XG93r06CldrvtmGYEMQA1i03ILRdLwZANUwIAIIACAIgCIAgAIIACAIwIQj5CSVg3VIL6nxwHbDuGKRv25+B708GVwAIAiAIgCAAggAIAiAIgCAAggAIAiAIgCAAggAIAiAIgCAAggCUqjlenl6ZBJgQAEEABAEQBEAQAEEABAEQBEAQAEEABAGYi+3PgAkBEARAEABBAAQBEARAEABBAAQBEARAEABBAAQBEARAEABBAAQBKF+TD3NvgT4dNsH1Q4ETgpXwwCcIYgCMJgQAQQAEARAEQBAAQQAEAZgQBE8MAqMJQRSAxPZnYDwhAAgCIAiAIACCAAgCIAiAIACCAAgCIAiAIACCAAgCIAiAIACCAPxdENLeR7sfQRAAQQAEAUAQAEEABAEQBEAQAEEABAEQBEAQAEEABAEQBEAQAEEAFhdi9EIkwIQACAIgCIAgAIIACAIgCIAgAIIACAIgCEAZQQjnR0wf1wSCAAgCIAiAILgCQBAAQQAEARAEQBAAQQAEARAEQBAAQQAEARAEQBCARdn+DJgQAEEABAEQBEAQAEEABAEQBEAQAEEABAGY00uAAQCjm0I0DSbLXwAAAABJRU5ErkJggg==') repeat-x;
+}
+
+.irs {
+ height: 40px;
+}
+.irs-with-grid {
+ height: 60px;
+}
+.irs-line {
+ height: 12px; top: 25px;
+}
+ .irs-line-left {
+ height: 12px;
+ background-position: 0 -30px;
+ }
+ .irs-line-mid {
+ height: 12px;
+ background-position: 0 0;
+ }
+ .irs-line-right {
+ height: 12px;
+ background-position: 100% -30px;
+ }
+
+.irs-bar {
+ height: 12px; top: 25px;
+ background-position: 0 -60px;
+}
+ .irs-bar-edge {
+ top: 25px;
+ height: 12px; width: 9px;
+ background-position: 0 -90px;
+ }
+
+.irs-shadow {
+ height: 3px; top: 34px;
+ background: #000;
+ opacity: 0.25;
+}
+.lt-ie9 .irs-shadow {
+ filter: alpha(opacity=25);
+}
+
+.irs-slider {
+ width: 16px; height: 18px;
+ top: 22px;
+ background-position: 0 -120px;
+}
+.irs-slider.state_hover, .irs-slider:hover {
+ background-position: 0 -150px;
+}
+
+.irs-min, .irs-max {
+ color: #999;
+ font-size: 10px; line-height: 1.333;
+ text-shadow: none;
+ top: 0; padding: 1px 3px;
+ background: #e1e4e9;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.irs-from, .irs-to, .irs-single {
+ color: #fff;
+ font-size: 10px; line-height: 1.333;
+ text-shadow: none;
+ padding: 1px 5px;
+ background: #ed5565;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+.irs-from:after, .irs-to:after, .irs-single:after {
+ position: absolute; display: block; content: "";
+ bottom: -6px; left: 50%;
+ width: 0; height: 0;
+ margin-left: -3px;
+ overflow: hidden;
+ border: 3px solid transparent;
+ border-top-color: #ed5565;
+}
+
+
+.irs-grid-pol {
+ background: #e1e4e9;
+}
+.irs-grid-text {
+ color: #999;
+}
+
+.irs-disabled {
+}
diff --git a/www/wiki/vendor/onoi/shared-resources/res/jquery.rangeSlider/ion.rangeSlider.skinModern.css b/www/wiki/vendor/onoi/shared-resources/res/jquery.rangeSlider/ion.rangeSlider.skinModern.css
new file mode 100644
index 00000000..1b3f112d
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/jquery.rangeSlider/ion.rangeSlider.skinModern.css
@@ -0,0 +1,116 @@
+/* Ion.RangeSlider, Modern Skin
+// css version 2.0.3
+// © Denis Ineshin, 2014 https://github.com/IonDen
+// ===================================================================================================================*/
+
+/* =====================================================================================================================
+// Skin details */
+
+.irs-line-mid,
+.irs-line-left,
+.irs-line-right,
+.irs-bar,
+.irs-bar-edge,
+.irs-slider {
+ background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQQAAAC0CAMAAABFa6L5AAAAP1BMVEUAAADr7fDLztTAw8ogtCbQ09wgtCavs7sgtCbR1uAgtCb///+vs7vy8vL6+vq/w8jq6uudo6pUU1Pf4OSnq7LGlxnBAAAACXRSTlMAO/yEuwb37h5FOTJkAAABYElEQVR42u3ZTXKjMBCAUQFxMop+bXz/sw44nqnKLgtn1e91wQG+ElrQqVJTJosgggj/pUJJAAAAAAAAr3LZPmpoH9slbSPHto4trTm8IULOaypzz7HtJV1KeJcEAAAAAADwKp98iiCCCCJ8iwAAAAAAAMAL/Xl7t5t5s6BK6V0EJ8GdAAAAAAAAwC9Z1iVFt4wxoldYRu45+FlY1lxKiV1hWddejlkjV8hng35WyCms0Uo/lTZSWKP/EzlC620c00OfBBGeEaYIIojwiPAUPUJvPfhJmL2d+gwcYVtnO811S3Ft40vkBgAAAD+11MDbp6flfr9Hr7DcRx/BKxwNSikj9Bex1NrLMTVyhVGfW+ka+B/jdT630vOawrq23ls/X7EjtNu8tegR5u2Y2BHmEWE/IoS+E0Q4I7RHhBY8QpvHiNCOJ3SEvbVHhj1whK3u87TXyBvZ7folcgMAAIAf+AtxIcIEmjgMVQAAAABJRU5ErkJggg==') repeat-x;
+}
+
+.irs {
+ height: 50px;
+}
+.irs-with-grid {
+ height: 70px;
+}
+.irs-line {
+ height: 6px; top: 25px;
+}
+ .irs-line-left {
+ height: 6px;
+ background-position: 0 -30px;
+ }
+ .irs-line-mid {
+ height: 6px;
+ background-position: 0 0;
+ }
+ .irs-line-right {
+ height: 6px;
+ background-position: 100% -30px;
+ }
+
+.irs-bar {
+ height: 6px; top: 25px;
+ background-position: 0 -60px;
+}
+ .irs-bar-edge {
+ top: 25px;
+ height: 6px; width: 6px;
+ background-position: 0 -90px;
+ }
+
+.irs-shadow {
+ height: 5px; top: 25px;
+ background: #000;
+ opacity: 0.25;
+}
+.lt-ie9 .irs-shadow {
+ filter: alpha(opacity=25);
+}
+
+.irs-slider {
+ width: 11px; height: 18px;
+ top: 31px;
+ background-position: 0 -120px;
+}
+.irs-slider.state_hover, .irs-slider:hover {
+ background-position: 0 -150px;
+}
+
+.irs-min, .irs-max {
+ color: #999;
+ font-size: 10px; line-height: 1.333;
+ text-shadow: none;
+ top: 0; padding: 1px 3px;
+ background: #e1e4e9;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.irs-from, .irs-to, .irs-single {
+ color: #fff;
+ font-size: 10px; line-height: 1.333;
+ text-shadow: none;
+ padding: 1px 5px;
+ background: #20b426;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+.irs-from:after, .irs-to:after, .irs-single:after {
+ position: absolute; display: block; content: "";
+ bottom: -6px; left: 50%;
+ width: 0; height: 0;
+ margin-left: -3px;
+ overflow: hidden;
+ border: 3px solid transparent;
+ border-top-color: #20b426;
+}
+
+.irs-grid {
+ height: 34px;
+}
+.irs-grid-pol {
+ background: #c0c0c0;
+}
+.irs-grid-text {
+ bottom: 12px;
+ color: #c0c0c0;
+}
+
+.irs-disable-mask {
+
+}
+.irs-disabled {
+
+}
+.lt-ie9 .irs-disabled {
+
+}
diff --git a/www/wiki/vendor/onoi/shared-resources/res/localForage/localforage.js b/www/wiki/vendor/onoi/shared-resources/res/localForage/localforage.js
new file mode 100644
index 00000000..a11030ff
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/localForage/localforage.js
@@ -0,0 +1,2313 @@
+/*!
+ localForage -- Offline Storage, Improved
+ Version 1.5.0
+ https://localforage.github.io/localForage
+ (c) 2013-2017 Mozilla, Apache License 2.0
+*/
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.localforage = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw (f.code="MODULE_NOT_FOUND", f)}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
+(function (global){
+'use strict';
+var Mutation = global.MutationObserver || global.WebKitMutationObserver;
+
+var scheduleDrain;
+
+{
+ if (Mutation) {
+ var called = 0;
+ var observer = new Mutation(nextTick);
+ var element = global.document.createTextNode('');
+ observer.observe(element, {
+ characterData: true
+ });
+ scheduleDrain = function () {
+ element.data = (called = ++called % 2);
+ };
+ } else if (!global.setImmediate && typeof global.MessageChannel !== 'undefined') {
+ var channel = new global.MessageChannel();
+ channel.port1.onmessage = nextTick;
+ scheduleDrain = function () {
+ channel.port2.postMessage(0);
+ };
+ } else if ('document' in global && 'onreadystatechange' in global.document.createElement('script')) {
+ scheduleDrain = function () {
+
+ // Create a <script> element; its readystatechange event will be fired asynchronously once it is inserted
+ // into the document. Do so, thus queuing up the task. Remember to clean up once it's been called.
+ var scriptEl = global.document.createElement('script');
+ scriptEl.onreadystatechange = function () {
+ nextTick();
+
+ scriptEl.onreadystatechange = null;
+ scriptEl.parentNode.removeChild(scriptEl);
+ scriptEl = null;
+ };
+ global.document.documentElement.appendChild(scriptEl);
+ };
+ } else {
+ scheduleDrain = function () {
+ setTimeout(nextTick, 0);
+ };
+ }
+}
+
+var draining;
+var queue = [];
+//named nextTick for less confusing stack traces
+function nextTick() {
+ draining = true;
+ var i, oldQueue;
+ var len = queue.length;
+ while (len) {
+ oldQueue = queue;
+ queue = [];
+ i = -1;
+ while (++i < len) {
+ oldQueue[i]();
+ }
+ len = queue.length;
+ }
+ draining = false;
+}
+
+module.exports = immediate;
+function immediate(task) {
+ if (queue.push(task) === 1 && !draining) {
+ scheduleDrain();
+ }
+}
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],2:[function(_dereq_,module,exports){
+'use strict';
+var immediate = _dereq_(1);
+
+/* istanbul ignore next */
+function INTERNAL() {}
+
+var handlers = {};
+
+var REJECTED = ['REJECTED'];
+var FULFILLED = ['FULFILLED'];
+var PENDING = ['PENDING'];
+
+module.exports = exports = Promise;
+
+function Promise(resolver) {
+ if (typeof resolver !== 'function') {
+ throw new TypeError('resolver must be a function');
+ }
+ this.state = PENDING;
+ this.queue = [];
+ this.outcome = void 0;
+ if (resolver !== INTERNAL) {
+ safelyResolveThenable(this, resolver);
+ }
+}
+
+Promise.prototype["catch"] = function (onRejected) {
+ return this.then(null, onRejected);
+};
+Promise.prototype.then = function (onFulfilled, onRejected) {
+ if (typeof onFulfilled !== 'function' && this.state === FULFILLED ||
+ typeof onRejected !== 'function' && this.state === REJECTED) {
+ return this;
+ }
+ var promise = new this.constructor(INTERNAL);
+ if (this.state !== PENDING) {
+ var resolver = this.state === FULFILLED ? onFulfilled : onRejected;
+ unwrap(promise, resolver, this.outcome);
+ } else {
+ this.queue.push(new QueueItem(promise, onFulfilled, onRejected));
+ }
+
+ return promise;
+};
+function QueueItem(promise, onFulfilled, onRejected) {
+ this.promise = promise;
+ if (typeof onFulfilled === 'function') {
+ this.onFulfilled = onFulfilled;
+ this.callFulfilled = this.otherCallFulfilled;
+ }
+ if (typeof onRejected === 'function') {
+ this.onRejected = onRejected;
+ this.callRejected = this.otherCallRejected;
+ }
+}
+QueueItem.prototype.callFulfilled = function (value) {
+ handlers.resolve(this.promise, value);
+};
+QueueItem.prototype.otherCallFulfilled = function (value) {
+ unwrap(this.promise, this.onFulfilled, value);
+};
+QueueItem.prototype.callRejected = function (value) {
+ handlers.reject(this.promise, value);
+};
+QueueItem.prototype.otherCallRejected = function (value) {
+ unwrap(this.promise, this.onRejected, value);
+};
+
+function unwrap(promise, func, value) {
+ immediate(function () {
+ var returnValue;
+ try {
+ returnValue = func(value);
+ } catch (e) {
+ return handlers.reject(promise, e);
+ }
+ if (returnValue === promise) {
+ handlers.reject(promise, new TypeError('Cannot resolve promise with itself'));
+ } else {
+ handlers.resolve(promise, returnValue);
+ }
+ });
+}
+
+handlers.resolve = function (self, value) {
+ var result = tryCatch(getThen, value);
+ if (result.status === 'error') {
+ return handlers.reject(self, result.value);
+ }
+ var thenable = result.value;
+
+ if (thenable) {
+ safelyResolveThenable(self, thenable);
+ } else {
+ self.state = FULFILLED;
+ self.outcome = value;
+ var i = -1;
+ var len = self.queue.length;
+ while (++i < len) {
+ self.queue[i].callFulfilled(value);
+ }
+ }
+ return self;
+};
+handlers.reject = function (self, error) {
+ self.state = REJECTED;
+ self.outcome = error;
+ var i = -1;
+ var len = self.queue.length;
+ while (++i < len) {
+ self.queue[i].callRejected(error);
+ }
+ return self;
+};
+
+function getThen(obj) {
+ // Make sure we only access the accessor once as required by the spec
+ var then = obj && obj.then;
+ if (obj && typeof obj === 'object' && typeof then === 'function') {
+ return function appyThen() {
+ then.apply(obj, arguments);
+ };
+ }
+}
+
+function safelyResolveThenable(self, thenable) {
+ // Either fulfill, reject or reject with error
+ var called = false;
+ function onError(value) {
+ if (called) {
+ return;
+ }
+ called = true;
+ handlers.reject(self, value);
+ }
+
+ function onSuccess(value) {
+ if (called) {
+ return;
+ }
+ called = true;
+ handlers.resolve(self, value);
+ }
+
+ function tryToUnwrap() {
+ thenable(onSuccess, onError);
+ }
+
+ var result = tryCatch(tryToUnwrap);
+ if (result.status === 'error') {
+ onError(result.value);
+ }
+}
+
+function tryCatch(func, value) {
+ var out = {};
+ try {
+ out.value = func(value);
+ out.status = 'success';
+ } catch (e) {
+ out.status = 'error';
+ out.value = e;
+ }
+ return out;
+}
+
+exports.resolve = resolve;
+function resolve(value) {
+ if (value instanceof this) {
+ return value;
+ }
+ return handlers.resolve(new this(INTERNAL), value);
+}
+
+exports.reject = reject;
+function reject(reason) {
+ var promise = new this(INTERNAL);
+ return handlers.reject(promise, reason);
+}
+
+exports.all = all;
+function all(iterable) {
+ var self = this;
+ if (Object.prototype.toString.call(iterable) !== '[object Array]') {
+ return this.reject(new TypeError('must be an array'));
+ }
+
+ var len = iterable.length;
+ var called = false;
+ if (!len) {
+ return this.resolve([]);
+ }
+
+ var values = new Array(len);
+ var resolved = 0;
+ var i = -1;
+ var promise = new this(INTERNAL);
+
+ while (++i < len) {
+ allResolver(iterable[i], i);
+ }
+ return promise;
+ function allResolver(value, i) {
+ self.resolve(value).then(resolveFromAll, function (error) {
+ if (!called) {
+ called = true;
+ handlers.reject(promise, error);
+ }
+ });
+ function resolveFromAll(outValue) {
+ values[i] = outValue;
+ if (++resolved === len && !called) {
+ called = true;
+ handlers.resolve(promise, values);
+ }
+ }
+ }
+}
+
+exports.race = race;
+function race(iterable) {
+ var self = this;
+ if (Object.prototype.toString.call(iterable) !== '[object Array]') {
+ return this.reject(new TypeError('must be an array'));
+ }
+
+ var len = iterable.length;
+ var called = false;
+ if (!len) {
+ return this.resolve([]);
+ }
+
+ var i = -1;
+ var promise = new this(INTERNAL);
+
+ while (++i < len) {
+ resolver(iterable[i]);
+ }
+ return promise;
+ function resolver(value) {
+ self.resolve(value).then(function (response) {
+ if (!called) {
+ called = true;
+ handlers.resolve(promise, response);
+ }
+ }, function (error) {
+ if (!called) {
+ called = true;
+ handlers.reject(promise, error);
+ }
+ });
+ }
+}
+
+},{"1":1}],3:[function(_dereq_,module,exports){
+(function (global){
+'use strict';
+if (typeof global.Promise !== 'function') {
+ global.Promise = _dereq_(2);
+}
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"2":2}],4:[function(_dereq_,module,exports){
+'use strict';
+
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+function getIDB() {
+ /* global indexedDB,webkitIndexedDB,mozIndexedDB,OIndexedDB,msIndexedDB */
+ try {
+ if (typeof indexedDB !== 'undefined') {
+ return indexedDB;
+ }
+ if (typeof webkitIndexedDB !== 'undefined') {
+ return webkitIndexedDB;
+ }
+ if (typeof mozIndexedDB !== 'undefined') {
+ return mozIndexedDB;
+ }
+ if (typeof OIndexedDB !== 'undefined') {
+ return OIndexedDB;
+ }
+ if (typeof msIndexedDB !== 'undefined') {
+ return msIndexedDB;
+ }
+ } catch (e) {}
+}
+
+var idb = getIDB();
+
+function isIndexedDBValid() {
+ try {
+ // Initialize IndexedDB; fall back to vendor-prefixed versions
+ // if needed.
+ if (!idb) {
+ return false;
+ }
+ // We mimic PouchDB here;
+ //
+ // We test for openDatabase because IE Mobile identifies itself
+ // as Safari. Oh the lulz...
+ var isSafari = typeof openDatabase !== 'undefined' && /(Safari|iPhone|iPad|iPod)/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent) && !/BlackBerry/.test(navigator.platform);
+
+ var hasFetch = typeof fetch === 'function' && fetch.toString().indexOf('[native code') !== -1;
+
+ // Safari <10.1 does not meet our requirements for IDB support (#5572)
+ // since Safari 10.1 shipped with fetch, we can use that to detect it
+ return (!isSafari || hasFetch) && typeof indexedDB !== 'undefined' &&
+ // some outdated implementations of IDB that appear on Samsung
+ // and HTC Android devices <4.4 are missing IDBKeyRange
+ typeof IDBKeyRange !== 'undefined';
+ } catch (e) {
+ return false;
+ }
+}
+
+function isWebSQLValid() {
+ return typeof openDatabase === 'function';
+}
+
+function isLocalStorageValid() {
+ try {
+ return typeof localStorage !== 'undefined' && 'setItem' in localStorage && localStorage.setItem;
+ } catch (e) {
+ return false;
+ }
+}
+
+// Abstracts constructing a Blob object, so it also works in older
+// browsers that don't support the native Blob constructor. (i.e.
+// old QtWebKit versions, at least).
+// Abstracts constructing a Blob object, so it also works in older
+// browsers that don't support the native Blob constructor. (i.e.
+// old QtWebKit versions, at least).
+function createBlob(parts, properties) {
+ /* global BlobBuilder,MSBlobBuilder,MozBlobBuilder,WebKitBlobBuilder */
+ parts = parts || [];
+ properties = properties || {};
+ try {
+ return new Blob(parts, properties);
+ } catch (e) {
+ if (e.name !== 'TypeError') {
+ throw e;
+ }
+ var Builder = typeof BlobBuilder !== 'undefined' ? BlobBuilder : typeof MSBlobBuilder !== 'undefined' ? MSBlobBuilder : typeof MozBlobBuilder !== 'undefined' ? MozBlobBuilder : WebKitBlobBuilder;
+ var builder = new Builder();
+ for (var i = 0; i < parts.length; i += 1) {
+ builder.append(parts[i]);
+ }
+ return builder.getBlob(properties.type);
+ }
+}
+
+// This is CommonJS because lie is an external dependency, so Rollup
+// can just ignore it.
+if (typeof Promise === 'undefined') {
+ // In the "nopromises" build this will just throw if you don't have
+ // a global promise object, but it would throw anyway later.
+ _dereq_(3);
+}
+var Promise$1 = Promise;
+
+function executeCallback(promise, callback) {
+ if (callback) {
+ promise.then(function (result) {
+ callback(null, result);
+ }, function (error) {
+ callback(error);
+ });
+ }
+}
+
+function executeTwoCallbacks(promise, callback, errorCallback) {
+ if (typeof callback === 'function') {
+ promise.then(callback);
+ }
+
+ if (typeof errorCallback === 'function') {
+ promise["catch"](errorCallback);
+ }
+}
+
+// Some code originally from async_storage.js in
+// [Gaia](https://github.com/mozilla-b2g/gaia).
+
+var DETECT_BLOB_SUPPORT_STORE = 'local-forage-detect-blob-support';
+var supportsBlobs;
+var dbContexts;
+var toString = Object.prototype.toString;
+
+// Transform a binary string to an array buffer, because otherwise
+// weird stuff happens when you try to work with the binary string directly.
+// It is known.
+// From http://stackoverflow.com/questions/14967647/ (continues on next line)
+// encode-decode-image-with-base64-breaks-image (2013-04-21)
+function _binStringToArrayBuffer(bin) {
+ var length = bin.length;
+ var buf = new ArrayBuffer(length);
+ var arr = new Uint8Array(buf);
+ for (var i = 0; i < length; i++) {
+ arr[i] = bin.charCodeAt(i);
+ }
+ return buf;
+}
+
+//
+// Blobs are not supported in all versions of IndexedDB, notably
+// Chrome <37 and Android <5. In those versions, storing a blob will throw.
+//
+// Various other blob bugs exist in Chrome v37-42 (inclusive).
+// Detecting them is expensive and confusing to users, and Chrome 37-42
+// is at very low usage worldwide, so we do a hacky userAgent check instead.
+//
+// content-type bug: https://code.google.com/p/chromium/issues/detail?id=408120
+// 404 bug: https://code.google.com/p/chromium/issues/detail?id=447916
+// FileReader bug: https://code.google.com/p/chromium/issues/detail?id=447836
+//
+// Code borrowed from PouchDB. See:
+// https://github.com/pouchdb/pouchdb/blob/master/packages/node_modules/pouchdb-adapter-idb/src/blobSupport.js
+//
+function _checkBlobSupportWithoutCaching(idb) {
+ return new Promise$1(function (resolve) {
+ var txn = idb.transaction(DETECT_BLOB_SUPPORT_STORE, 'readwrite');
+ var blob = createBlob(['']);
+ txn.objectStore(DETECT_BLOB_SUPPORT_STORE).put(blob, 'key');
+
+ txn.onabort = function (e) {
+ // If the transaction aborts now its due to not being able to
+ // write to the database, likely due to the disk being full
+ e.preventDefault();
+ e.stopPropagation();
+ resolve(false);
+ };
+
+ txn.oncomplete = function () {
+ var matchedChrome = navigator.userAgent.match(/Chrome\/(\d+)/);
+ var matchedEdge = navigator.userAgent.match(/Edge\//);
+ // MS Edge pretends to be Chrome 42:
+ // https://msdn.microsoft.com/en-us/library/hh869301%28v=vs.85%29.aspx
+ resolve(matchedEdge || !matchedChrome || parseInt(matchedChrome[1], 10) >= 43);
+ };
+ })["catch"](function () {
+ return false; // error, so assume unsupported
+ });
+}
+
+function _checkBlobSupport(idb) {
+ if (typeof supportsBlobs === 'boolean') {
+ return Promise$1.resolve(supportsBlobs);
+ }
+ return _checkBlobSupportWithoutCaching(idb).then(function (value) {
+ supportsBlobs = value;
+ return supportsBlobs;
+ });
+}
+
+function _deferReadiness(dbInfo) {
+ var dbContext = dbContexts[dbInfo.name];
+
+ // Create a deferred object representing the current database operation.
+ var deferredOperation = {};
+
+ deferredOperation.promise = new Promise$1(function (resolve) {
+ deferredOperation.resolve = resolve;
+ });
+
+ // Enqueue the deferred operation.
+ dbContext.deferredOperations.push(deferredOperation);
+
+ // Chain its promise to the database readiness.
+ if (!dbContext.dbReady) {
+ dbContext.dbReady = deferredOperation.promise;
+ } else {
+ dbContext.dbReady = dbContext.dbReady.then(function () {
+ return deferredOperation.promise;
+ });
+ }
+}
+
+function _advanceReadiness(dbInfo) {
+ var dbContext = dbContexts[dbInfo.name];
+
+ // Dequeue a deferred operation.
+ var deferredOperation = dbContext.deferredOperations.pop();
+
+ // Resolve its promise (which is part of the database readiness
+ // chain of promises).
+ if (deferredOperation) {
+ deferredOperation.resolve();
+ }
+}
+
+function _getConnection(dbInfo, upgradeNeeded) {
+ return new Promise$1(function (resolve, reject) {
+
+ if (dbInfo.db) {
+ if (upgradeNeeded) {
+ _deferReadiness(dbInfo);
+ dbInfo.db.close();
+ } else {
+ return resolve(dbInfo.db);
+ }
+ }
+
+ var dbArgs = [dbInfo.name];
+
+ if (upgradeNeeded) {
+ dbArgs.push(dbInfo.version);
+ }
+
+ var openreq = idb.open.apply(idb, dbArgs);
+
+ if (upgradeNeeded) {
+ openreq.onupgradeneeded = function (e) {
+ var db = openreq.result;
+ try {
+ db.createObjectStore(dbInfo.storeName);
+ if (e.oldVersion <= 1) {
+ // Added when support for blob shims was added
+ db.createObjectStore(DETECT_BLOB_SUPPORT_STORE);
+ }
+ } catch (ex) {
+ if (ex.name === 'ConstraintError') {
+ console.warn('The database "' + dbInfo.name + '"' + ' has been upgraded from version ' + e.oldVersion + ' to version ' + e.newVersion + ', but the storage "' + dbInfo.storeName + '" already exists.');
+ } else {
+ throw ex;
+ }
+ }
+ };
+ }
+
+ openreq.onerror = function (e) {
+ e.preventDefault();
+ reject(openreq.error);
+ };
+
+ openreq.onsuccess = function () {
+ resolve(openreq.result);
+ _advanceReadiness(dbInfo);
+ };
+ });
+}
+
+function _getOriginalConnection(dbInfo) {
+ return _getConnection(dbInfo, false);
+}
+
+function _getUpgradedConnection(dbInfo) {
+ return _getConnection(dbInfo, true);
+}
+
+function _isUpgradeNeeded(dbInfo, defaultVersion) {
+ if (!dbInfo.db) {
+ return true;
+ }
+
+ var isNewStore = !dbInfo.db.objectStoreNames.contains(dbInfo.storeName);
+ var isDowngrade = dbInfo.version < dbInfo.db.version;
+ var isUpgrade = dbInfo.version > dbInfo.db.version;
+
+ if (isDowngrade) {
+ // If the version is not the default one
+ // then warn for impossible downgrade.
+ if (dbInfo.version !== defaultVersion) {
+ console.warn('The database "' + dbInfo.name + '"' + ' can\'t be downgraded from version ' + dbInfo.db.version + ' to version ' + dbInfo.version + '.');
+ }
+ // Align the versions to prevent errors.
+ dbInfo.version = dbInfo.db.version;
+ }
+
+ if (isUpgrade || isNewStore) {
+ // If the store is new then increment the version (if needed).
+ // This will trigger an "upgradeneeded" event which is required
+ // for creating a store.
+ if (isNewStore) {
+ var incVersion = dbInfo.db.version + 1;
+ if (incVersion > dbInfo.version) {
+ dbInfo.version = incVersion;
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+// encode a blob for indexeddb engines that don't support blobs
+function _encodeBlob(blob) {
+ return new Promise$1(function (resolve, reject) {
+ var reader = new FileReader();
+ reader.onerror = reject;
+ reader.onloadend = function (e) {
+ var base64 = btoa(e.target.result || '');
+ resolve({
+ __local_forage_encoded_blob: true,
+ data: base64,
+ type: blob.type
+ });
+ };
+ reader.readAsBinaryString(blob);
+ });
+}
+
+// decode an encoded blob
+function _decodeBlob(encodedBlob) {
+ var arrayBuff = _binStringToArrayBuffer(atob(encodedBlob.data));
+ return createBlob([arrayBuff], { type: encodedBlob.type });
+}
+
+// is this one of our fancy encoded blobs?
+function _isEncodedBlob(value) {
+ return value && value.__local_forage_encoded_blob;
+}
+
+// Specialize the default `ready()` function by making it dependent
+// on the current database operations. Thus, the driver will be actually
+// ready when it's been initialized (default) *and* there are no pending
+// operations on the database (initiated by some other instances).
+function _fullyReady(callback) {
+ var self = this;
+
+ var promise = self._initReady().then(function () {
+ var dbContext = dbContexts[self._dbInfo.name];
+
+ if (dbContext && dbContext.dbReady) {
+ return dbContext.dbReady;
+ }
+ });
+
+ executeTwoCallbacks(promise, callback, callback);
+ return promise;
+}
+
+// Open the IndexedDB database (automatically creates one if one didn't
+// previously exist), using any options set in the config.
+function _initStorage(options) {
+ var self = this;
+ var dbInfo = {
+ db: null
+ };
+
+ if (options) {
+ for (var i in options) {
+ dbInfo[i] = options[i];
+ }
+ }
+
+ // Initialize a singleton container for all running localForages.
+ if (!dbContexts) {
+ dbContexts = {};
+ }
+
+ // Get the current context of the database;
+ var dbContext = dbContexts[dbInfo.name];
+
+ // ...or create a new context.
+ if (!dbContext) {
+ dbContext = {
+ // Running localForages sharing a database.
+ forages: [],
+ // Shared database.
+ db: null,
+ // Database readiness (promise).
+ dbReady: null,
+ // Deferred operations on the database.
+ deferredOperations: []
+ };
+ // Register the new context in the global container.
+ dbContexts[dbInfo.name] = dbContext;
+ }
+
+ // Register itself as a running localForage in the current context.
+ dbContext.forages.push(self);
+
+ // Replace the default `ready()` function with the specialized one.
+ if (!self._initReady) {
+ self._initReady = self.ready;
+ self.ready = _fullyReady;
+ }
+
+ // Create an array of initialization states of the related localForages.
+ var initPromises = [];
+
+ function ignoreErrors() {
+ // Don't handle errors here,
+ // just makes sure related localForages aren't pending.
+ return Promise$1.resolve();
+ }
+
+ for (var j = 0; j < dbContext.forages.length; j++) {
+ var forage = dbContext.forages[j];
+ if (forage !== self) {
+ // Don't wait for itself...
+ initPromises.push(forage._initReady()["catch"](ignoreErrors));
+ }
+ }
+
+ // Take a snapshot of the related localForages.
+ var forages = dbContext.forages.slice(0);
+
+ // Initialize the connection process only when
+ // all the related localForages aren't pending.
+ return Promise$1.all(initPromises).then(function () {
+ dbInfo.db = dbContext.db;
+ // Get the connection or open a new one without upgrade.
+ return _getOriginalConnection(dbInfo);
+ }).then(function (db) {
+ dbInfo.db = db;
+ if (_isUpgradeNeeded(dbInfo, self._defaultConfig.version)) {
+ // Reopen the database for upgrading.
+ return _getUpgradedConnection(dbInfo);
+ }
+ return db;
+ }).then(function (db) {
+ dbInfo.db = dbContext.db = db;
+ self._dbInfo = dbInfo;
+ // Share the final connection amongst related localForages.
+ for (var k = 0; k < forages.length; k++) {
+ var forage = forages[k];
+ if (forage !== self) {
+ // Self is already up-to-date.
+ forage._dbInfo.db = dbInfo.db;
+ forage._dbInfo.version = dbInfo.version;
+ }
+ }
+ });
+}
+
+function getItem(key, callback) {
+ var self = this;
+
+ // Cast the key to a string, as that's all we can set as a key.
+ if (typeof key !== 'string') {
+ console.warn(key + ' used as a key, but it is not a string.');
+ key = String(key);
+ }
+
+ var promise = new Promise$1(function (resolve, reject) {
+ self.ready().then(function () {
+ var dbInfo = self._dbInfo;
+ var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly').objectStore(dbInfo.storeName);
+ var req = store.get(key);
+
+ req.onsuccess = function () {
+ var value = req.result;
+ if (value === undefined) {
+ value = null;
+ }
+ if (_isEncodedBlob(value)) {
+ value = _decodeBlob(value);
+ }
+ resolve(value);
+ };
+
+ req.onerror = function () {
+ reject(req.error);
+ };
+ })["catch"](reject);
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+}
+
+// Iterate over all items stored in database.
+function iterate(iterator, callback) {
+ var self = this;
+
+ var promise = new Promise$1(function (resolve, reject) {
+ self.ready().then(function () {
+ var dbInfo = self._dbInfo;
+ var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly').objectStore(dbInfo.storeName);
+
+ var req = store.openCursor();
+ var iterationNumber = 1;
+
+ req.onsuccess = function () {
+ var cursor = req.result;
+
+ if (cursor) {
+ var value = cursor.value;
+ if (_isEncodedBlob(value)) {
+ value = _decodeBlob(value);
+ }
+ var result = iterator(value, cursor.key, iterationNumber++);
+
+ if (result !== void 0) {
+ resolve(result);
+ } else {
+ cursor["continue"]();
+ }
+ } else {
+ resolve();
+ }
+ };
+
+ req.onerror = function () {
+ reject(req.error);
+ };
+ })["catch"](reject);
+ });
+
+ executeCallback(promise, callback);
+
+ return promise;
+}
+
+function setItem(key, value, callback) {
+ var self = this;
+
+ // Cast the key to a string, as that's all we can set as a key.
+ if (typeof key !== 'string') {
+ console.warn(key + ' used as a key, but it is not a string.');
+ key = String(key);
+ }
+
+ var promise = new Promise$1(function (resolve, reject) {
+ var dbInfo;
+ self.ready().then(function () {
+ dbInfo = self._dbInfo;
+ if (toString.call(value) === '[object Blob]') {
+ return _checkBlobSupport(dbInfo.db).then(function (blobSupport) {
+ if (blobSupport) {
+ return value;
+ }
+ return _encodeBlob(value);
+ });
+ }
+ return value;
+ }).then(function (value) {
+ var transaction = dbInfo.db.transaction(dbInfo.storeName, 'readwrite');
+ var store = transaction.objectStore(dbInfo.storeName);
+ var req = store.put(value, key);
+
+ // The reason we don't _save_ null is because IE 10 does
+ // not support saving the `null` type in IndexedDB. How
+ // ironic, given the bug below!
+ // See: https://github.com/mozilla/localForage/issues/161
+ if (value === null) {
+ value = undefined;
+ }
+
+ transaction.oncomplete = function () {
+ // Cast to undefined so the value passed to
+ // callback/promise is the same as what one would get out
+ // of `getItem()` later. This leads to some weirdness
+ // (setItem('foo', undefined) will return `null`), but
+ // it's not my fault localStorage is our baseline and that
+ // it's weird.
+ if (value === undefined) {
+ value = null;
+ }
+
+ resolve(value);
+ };
+ transaction.onabort = transaction.onerror = function () {
+ var err = req.error ? req.error : req.transaction.error;
+ reject(err);
+ };
+ })["catch"](reject);
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+}
+
+function removeItem(key, callback) {
+ var self = this;
+
+ // Cast the key to a string, as that's all we can set as a key.
+ if (typeof key !== 'string') {
+ console.warn(key + ' used as a key, but it is not a string.');
+ key = String(key);
+ }
+
+ var promise = new Promise$1(function (resolve, reject) {
+ self.ready().then(function () {
+ var dbInfo = self._dbInfo;
+ var transaction = dbInfo.db.transaction(dbInfo.storeName, 'readwrite');
+ var store = transaction.objectStore(dbInfo.storeName);
+
+ // We use a Grunt task to make this safe for IE and some
+ // versions of Android (including those used by Cordova).
+ // Normally IE won't like `.delete()` and will insist on
+ // using `['delete']()`, but we have a build step that
+ // fixes this for us now.
+ var req = store["delete"](key);
+ transaction.oncomplete = function () {
+ resolve();
+ };
+
+ transaction.onerror = function () {
+ reject(req.error);
+ };
+
+ // The request will be also be aborted if we've exceeded our storage
+ // space.
+ transaction.onabort = function () {
+ var err = req.error ? req.error : req.transaction.error;
+ reject(err);
+ };
+ })["catch"](reject);
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+}
+
+function clear(callback) {
+ var self = this;
+
+ var promise = new Promise$1(function (resolve, reject) {
+ self.ready().then(function () {
+ var dbInfo = self._dbInfo;
+ var transaction = dbInfo.db.transaction(dbInfo.storeName, 'readwrite');
+ var store = transaction.objectStore(dbInfo.storeName);
+ var req = store.clear();
+
+ transaction.oncomplete = function () {
+ resolve();
+ };
+
+ transaction.onabort = transaction.onerror = function () {
+ var err = req.error ? req.error : req.transaction.error;
+ reject(err);
+ };
+ })["catch"](reject);
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+}
+
+function length(callback) {
+ var self = this;
+
+ var promise = new Promise$1(function (resolve, reject) {
+ self.ready().then(function () {
+ var dbInfo = self._dbInfo;
+ var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly').objectStore(dbInfo.storeName);
+ var req = store.count();
+
+ req.onsuccess = function () {
+ resolve(req.result);
+ };
+
+ req.onerror = function () {
+ reject(req.error);
+ };
+ })["catch"](reject);
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+}
+
+function key(n, callback) {
+ var self = this;
+
+ var promise = new Promise$1(function (resolve, reject) {
+ if (n < 0) {
+ resolve(null);
+
+ return;
+ }
+
+ self.ready().then(function () {
+ var dbInfo = self._dbInfo;
+ var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly').objectStore(dbInfo.storeName);
+
+ var advanced = false;
+ var req = store.openCursor();
+ req.onsuccess = function () {
+ var cursor = req.result;
+ if (!cursor) {
+ // this means there weren't enough keys
+ resolve(null);
+
+ return;
+ }
+
+ if (n === 0) {
+ // We have the first key, return it if that's what they
+ // wanted.
+ resolve(cursor.key);
+ } else {
+ if (!advanced) {
+ // Otherwise, ask the cursor to skip ahead n
+ // records.
+ advanced = true;
+ cursor.advance(n);
+ } else {
+ // When we get here, we've got the nth key.
+ resolve(cursor.key);
+ }
+ }
+ };
+
+ req.onerror = function () {
+ reject(req.error);
+ };
+ })["catch"](reject);
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+}
+
+function keys(callback) {
+ var self = this;
+
+ var promise = new Promise$1(function (resolve, reject) {
+ self.ready().then(function () {
+ var dbInfo = self._dbInfo;
+ var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly').objectStore(dbInfo.storeName);
+
+ var req = store.openCursor();
+ var keys = [];
+
+ req.onsuccess = function () {
+ var cursor = req.result;
+
+ if (!cursor) {
+ resolve(keys);
+ return;
+ }
+
+ keys.push(cursor.key);
+ cursor["continue"]();
+ };
+
+ req.onerror = function () {
+ reject(req.error);
+ };
+ })["catch"](reject);
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+}
+
+var asyncStorage = {
+ _driver: 'asyncStorage',
+ _initStorage: _initStorage,
+ iterate: iterate,
+ getItem: getItem,
+ setItem: setItem,
+ removeItem: removeItem,
+ clear: clear,
+ length: length,
+ key: key,
+ keys: keys
+};
+
+// Sadly, the best way to save binary data in WebSQL/localStorage is serializing
+// it to Base64, so this is how we store it to prevent very strange errors with less
+// verbose ways of binary <-> string data storage.
+var BASE_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+
+var BLOB_TYPE_PREFIX = '~~local_forage_type~';
+var BLOB_TYPE_PREFIX_REGEX = /^~~local_forage_type~([^~]+)~/;
+
+var SERIALIZED_MARKER = '__lfsc__:';
+var SERIALIZED_MARKER_LENGTH = SERIALIZED_MARKER.length;
+
+// OMG the serializations!
+var TYPE_ARRAYBUFFER = 'arbf';
+var TYPE_BLOB = 'blob';
+var TYPE_INT8ARRAY = 'si08';
+var TYPE_UINT8ARRAY = 'ui08';
+var TYPE_UINT8CLAMPEDARRAY = 'uic8';
+var TYPE_INT16ARRAY = 'si16';
+var TYPE_INT32ARRAY = 'si32';
+var TYPE_UINT16ARRAY = 'ur16';
+var TYPE_UINT32ARRAY = 'ui32';
+var TYPE_FLOAT32ARRAY = 'fl32';
+var TYPE_FLOAT64ARRAY = 'fl64';
+var TYPE_SERIALIZED_MARKER_LENGTH = SERIALIZED_MARKER_LENGTH + TYPE_ARRAYBUFFER.length;
+
+var toString$1 = Object.prototype.toString;
+
+function stringToBuffer(serializedString) {
+ // Fill the string into a ArrayBuffer.
+ var bufferLength = serializedString.length * 0.75;
+ var len = serializedString.length;
+ var i;
+ var p = 0;
+ var encoded1, encoded2, encoded3, encoded4;
+
+ if (serializedString[serializedString.length - 1] === '=') {
+ bufferLength--;
+ if (serializedString[serializedString.length - 2] === '=') {
+ bufferLength--;
+ }
+ }
+
+ var buffer = new ArrayBuffer(bufferLength);
+ var bytes = new Uint8Array(buffer);
+
+ for (i = 0; i < len; i += 4) {
+ encoded1 = BASE_CHARS.indexOf(serializedString[i]);
+ encoded2 = BASE_CHARS.indexOf(serializedString[i + 1]);
+ encoded3 = BASE_CHARS.indexOf(serializedString[i + 2]);
+ encoded4 = BASE_CHARS.indexOf(serializedString[i + 3]);
+
+ /*jslint bitwise: true */
+ bytes[p++] = encoded1 << 2 | encoded2 >> 4;
+ bytes[p++] = (encoded2 & 15) << 4 | encoded3 >> 2;
+ bytes[p++] = (encoded3 & 3) << 6 | encoded4 & 63;
+ }
+ return buffer;
+}
+
+// Converts a buffer to a string to store, serialized, in the backend
+// storage library.
+function bufferToString(buffer) {
+ // base64-arraybuffer
+ var bytes = new Uint8Array(buffer);
+ var base64String = '';
+ var i;
+
+ for (i = 0; i < bytes.length; i += 3) {
+ /*jslint bitwise: true */
+ base64String += BASE_CHARS[bytes[i] >> 2];
+ base64String += BASE_CHARS[(bytes[i] & 3) << 4 | bytes[i + 1] >> 4];
+ base64String += BASE_CHARS[(bytes[i + 1] & 15) << 2 | bytes[i + 2] >> 6];
+ base64String += BASE_CHARS[bytes[i + 2] & 63];
+ }
+
+ if (bytes.length % 3 === 2) {
+ base64String = base64String.substring(0, base64String.length - 1) + '=';
+ } else if (bytes.length % 3 === 1) {
+ base64String = base64String.substring(0, base64String.length - 2) + '==';
+ }
+
+ return base64String;
+}
+
+// Serialize a value, afterwards executing a callback (which usually
+// instructs the `setItem()` callback/promise to be executed). This is how
+// we store binary data with localStorage.
+function serialize(value, callback) {
+ var valueType = '';
+ if (value) {
+ valueType = toString$1.call(value);
+ }
+
+ // Cannot use `value instanceof ArrayBuffer` or such here, as these
+ // checks fail when running the tests using casper.js...
+ //
+ // TODO: See why those tests fail and use a better solution.
+ if (value && (valueType === '[object ArrayBuffer]' || value.buffer && toString$1.call(value.buffer) === '[object ArrayBuffer]')) {
+ // Convert binary arrays to a string and prefix the string with
+ // a special marker.
+ var buffer;
+ var marker = SERIALIZED_MARKER;
+
+ if (value instanceof ArrayBuffer) {
+ buffer = value;
+ marker += TYPE_ARRAYBUFFER;
+ } else {
+ buffer = value.buffer;
+
+ if (valueType === '[object Int8Array]') {
+ marker += TYPE_INT8ARRAY;
+ } else if (valueType === '[object Uint8Array]') {
+ marker += TYPE_UINT8ARRAY;
+ } else if (valueType === '[object Uint8ClampedArray]') {
+ marker += TYPE_UINT8CLAMPEDARRAY;
+ } else if (valueType === '[object Int16Array]') {
+ marker += TYPE_INT16ARRAY;
+ } else if (valueType === '[object Uint16Array]') {
+ marker += TYPE_UINT16ARRAY;
+ } else if (valueType === '[object Int32Array]') {
+ marker += TYPE_INT32ARRAY;
+ } else if (valueType === '[object Uint32Array]') {
+ marker += TYPE_UINT32ARRAY;
+ } else if (valueType === '[object Float32Array]') {
+ marker += TYPE_FLOAT32ARRAY;
+ } else if (valueType === '[object Float64Array]') {
+ marker += TYPE_FLOAT64ARRAY;
+ } else {
+ callback(new Error('Failed to get type for BinaryArray'));
+ }
+ }
+
+ callback(marker + bufferToString(buffer));
+ } else if (valueType === '[object Blob]') {
+ // Conver the blob to a binaryArray and then to a string.
+ var fileReader = new FileReader();
+
+ fileReader.onload = function () {
+ // Backwards-compatible prefix for the blob type.
+ var str = BLOB_TYPE_PREFIX + value.type + '~' + bufferToString(this.result);
+
+ callback(SERIALIZED_MARKER + TYPE_BLOB + str);
+ };
+
+ fileReader.readAsArrayBuffer(value);
+ } else {
+ try {
+ callback(JSON.stringify(value));
+ } catch (e) {
+ console.error("Couldn't convert value into a JSON string: ", value);
+
+ callback(null, e);
+ }
+ }
+}
+
+// Deserialize data we've inserted into a value column/field. We place
+// special markers into our strings to mark them as encoded; this isn't
+// as nice as a meta field, but it's the only sane thing we can do whilst
+// keeping localStorage support intact.
+//
+// Oftentimes this will just deserialize JSON content, but if we have a
+// special marker (SERIALIZED_MARKER, defined above), we will extract
+// some kind of arraybuffer/binary data/typed array out of the string.
+function deserialize(value) {
+ // If we haven't marked this string as being specially serialized (i.e.
+ // something other than serialized JSON), we can just return it and be
+ // done with it.
+ if (value.substring(0, SERIALIZED_MARKER_LENGTH) !== SERIALIZED_MARKER) {
+ return JSON.parse(value);
+ }
+
+ // The following code deals with deserializing some kind of Blob or
+ // TypedArray. First we separate out the type of data we're dealing
+ // with from the data itself.
+ var serializedString = value.substring(TYPE_SERIALIZED_MARKER_LENGTH);
+ var type = value.substring(SERIALIZED_MARKER_LENGTH, TYPE_SERIALIZED_MARKER_LENGTH);
+
+ var blobType;
+ // Backwards-compatible blob type serialization strategy.
+ // DBs created with older versions of localForage will simply not have the blob type.
+ if (type === TYPE_BLOB && BLOB_TYPE_PREFIX_REGEX.test(serializedString)) {
+ var matcher = serializedString.match(BLOB_TYPE_PREFIX_REGEX);
+ blobType = matcher[1];
+ serializedString = serializedString.substring(matcher[0].length);
+ }
+ var buffer = stringToBuffer(serializedString);
+
+ // Return the right type based on the code/type set during
+ // serialization.
+ switch (type) {
+ case TYPE_ARRAYBUFFER:
+ return buffer;
+ case TYPE_BLOB:
+ return createBlob([buffer], { type: blobType });
+ case TYPE_INT8ARRAY:
+ return new Int8Array(buffer);
+ case TYPE_UINT8ARRAY:
+ return new Uint8Array(buffer);
+ case TYPE_UINT8CLAMPEDARRAY:
+ return new Uint8ClampedArray(buffer);
+ case TYPE_INT16ARRAY:
+ return new Int16Array(buffer);
+ case TYPE_UINT16ARRAY:
+ return new Uint16Array(buffer);
+ case TYPE_INT32ARRAY:
+ return new Int32Array(buffer);
+ case TYPE_UINT32ARRAY:
+ return new Uint32Array(buffer);
+ case TYPE_FLOAT32ARRAY:
+ return new Float32Array(buffer);
+ case TYPE_FLOAT64ARRAY:
+ return new Float64Array(buffer);
+ default:
+ throw new Error('Unkown type: ' + type);
+ }
+}
+
+var localforageSerializer = {
+ serialize: serialize,
+ deserialize: deserialize,
+ stringToBuffer: stringToBuffer,
+ bufferToString: bufferToString
+};
+
+/*
+ * Includes code from:
+ *
+ * base64-arraybuffer
+ * https://github.com/niklasvh/base64-arraybuffer
+ *
+ * Copyright (c) 2012 Niklas von Hertzen
+ * Licensed under the MIT license.
+ */
+// Open the WebSQL database (automatically creates one if one didn't
+// previously exist), using any options set in the config.
+function _initStorage$1(options) {
+ var self = this;
+ var dbInfo = {
+ db: null
+ };
+
+ if (options) {
+ for (var i in options) {
+ dbInfo[i] = typeof options[i] !== 'string' ? options[i].toString() : options[i];
+ }
+ }
+
+ var dbInfoPromise = new Promise$1(function (resolve, reject) {
+ // Open the database; the openDatabase API will automatically
+ // create it for us if it doesn't exist.
+ try {
+ dbInfo.db = openDatabase(dbInfo.name, String(dbInfo.version), dbInfo.description, dbInfo.size);
+ } catch (e) {
+ return reject(e);
+ }
+
+ // Create our key/value table if it doesn't exist.
+ dbInfo.db.transaction(function (t) {
+ t.executeSql('CREATE TABLE IF NOT EXISTS ' + dbInfo.storeName + ' (id INTEGER PRIMARY KEY, key unique, value)', [], function () {
+ self._dbInfo = dbInfo;
+ resolve();
+ }, function (t, error) {
+ reject(error);
+ });
+ });
+ });
+
+ dbInfo.serializer = localforageSerializer;
+ return dbInfoPromise;
+}
+
+function getItem$1(key, callback) {
+ var self = this;
+
+ // Cast the key to a string, as that's all we can set as a key.
+ if (typeof key !== 'string') {
+ console.warn(key + ' used as a key, but it is not a string.');
+ key = String(key);
+ }
+
+ var promise = new Promise$1(function (resolve, reject) {
+ self.ready().then(function () {
+ var dbInfo = self._dbInfo;
+ dbInfo.db.transaction(function (t) {
+ t.executeSql('SELECT * FROM ' + dbInfo.storeName + ' WHERE key = ? LIMIT 1', [key], function (t, results) {
+ var result = results.rows.length ? results.rows.item(0).value : null;
+
+ // Check to see if this is serialized content we need to
+ // unpack.
+ if (result) {
+ result = dbInfo.serializer.deserialize(result);
+ }
+
+ resolve(result);
+ }, function (t, error) {
+
+ reject(error);
+ });
+ });
+ })["catch"](reject);
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+}
+
+function iterate$1(iterator, callback) {
+ var self = this;
+
+ var promise = new Promise$1(function (resolve, reject) {
+ self.ready().then(function () {
+ var dbInfo = self._dbInfo;
+
+ dbInfo.db.transaction(function (t) {
+ t.executeSql('SELECT * FROM ' + dbInfo.storeName, [], function (t, results) {
+ var rows = results.rows;
+ var length = rows.length;
+
+ for (var i = 0; i < length; i++) {
+ var item = rows.item(i);
+ var result = item.value;
+
+ // Check to see if this is serialized content
+ // we need to unpack.
+ if (result) {
+ result = dbInfo.serializer.deserialize(result);
+ }
+
+ result = iterator(result, item.key, i + 1);
+
+ // void(0) prevents problems with redefinition
+ // of `undefined`.
+ if (result !== void 0) {
+ resolve(result);
+ return;
+ }
+ }
+
+ resolve();
+ }, function (t, error) {
+ reject(error);
+ });
+ });
+ })["catch"](reject);
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+}
+
+function _setItem(key, value, callback, retriesLeft) {
+ var self = this;
+
+ // Cast the key to a string, as that's all we can set as a key.
+ if (typeof key !== 'string') {
+ console.warn(key + ' used as a key, but it is not a string.');
+ key = String(key);
+ }
+
+ var promise = new Promise$1(function (resolve, reject) {
+ self.ready().then(function () {
+ // The localStorage API doesn't return undefined values in an
+ // "expected" way, so undefined is always cast to null in all
+ // drivers. See: https://github.com/mozilla/localForage/pull/42
+ if (value === undefined) {
+ value = null;
+ }
+
+ // Save the original value to pass to the callback.
+ var originalValue = value;
+
+ var dbInfo = self._dbInfo;
+ dbInfo.serializer.serialize(value, function (value, error) {
+ if (error) {
+ reject(error);
+ } else {
+ dbInfo.db.transaction(function (t) {
+ t.executeSql('INSERT OR REPLACE INTO ' + dbInfo.storeName + ' (key, value) VALUES (?, ?)', [key, value], function () {
+ resolve(originalValue);
+ }, function (t, error) {
+ reject(error);
+ });
+ }, function (sqlError) {
+ // The transaction failed; check
+ // to see if it's a quota error.
+ if (sqlError.code === sqlError.QUOTA_ERR) {
+ // We reject the callback outright for now, but
+ // it's worth trying to re-run the transaction.
+ // Even if the user accepts the prompt to use
+ // more storage on Safari, this error will
+ // be called.
+ //
+ // Try to re-run the transaction.
+ if (retriesLeft > 0) {
+ resolve(_setItem.apply(self, [key, originalValue, callback, retriesLeft - 1]));
+ return;
+ }
+ reject(sqlError);
+ }
+ });
+ }
+ });
+ })["catch"](reject);
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+}
+
+function setItem$1(key, value, callback) {
+ return _setItem.apply(this, [key, value, callback, 1]);
+}
+
+function removeItem$1(key, callback) {
+ var self = this;
+
+ // Cast the key to a string, as that's all we can set as a key.
+ if (typeof key !== 'string') {
+ console.warn(key + ' used as a key, but it is not a string.');
+ key = String(key);
+ }
+
+ var promise = new Promise$1(function (resolve, reject) {
+ self.ready().then(function () {
+ var dbInfo = self._dbInfo;
+ dbInfo.db.transaction(function (t) {
+ t.executeSql('DELETE FROM ' + dbInfo.storeName + ' WHERE key = ?', [key], function () {
+ resolve();
+ }, function (t, error) {
+
+ reject(error);
+ });
+ });
+ })["catch"](reject);
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+}
+
+// Deletes every item in the table.
+// TODO: Find out if this resets the AUTO_INCREMENT number.
+function clear$1(callback) {
+ var self = this;
+
+ var promise = new Promise$1(function (resolve, reject) {
+ self.ready().then(function () {
+ var dbInfo = self._dbInfo;
+ dbInfo.db.transaction(function (t) {
+ t.executeSql('DELETE FROM ' + dbInfo.storeName, [], function () {
+ resolve();
+ }, function (t, error) {
+ reject(error);
+ });
+ });
+ })["catch"](reject);
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+}
+
+// Does a simple `COUNT(key)` to get the number of items stored in
+// localForage.
+function length$1(callback) {
+ var self = this;
+
+ var promise = new Promise$1(function (resolve, reject) {
+ self.ready().then(function () {
+ var dbInfo = self._dbInfo;
+ dbInfo.db.transaction(function (t) {
+ // Ahhh, SQL makes this one soooooo easy.
+ t.executeSql('SELECT COUNT(key) as c FROM ' + dbInfo.storeName, [], function (t, results) {
+ var result = results.rows.item(0).c;
+
+ resolve(result);
+ }, function (t, error) {
+
+ reject(error);
+ });
+ });
+ })["catch"](reject);
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+}
+
+// Return the key located at key index X; essentially gets the key from a
+// `WHERE id = ?`. This is the most efficient way I can think to implement
+// this rarely-used (in my experience) part of the API, but it can seem
+// inconsistent, because we do `INSERT OR REPLACE INTO` on `setItem()`, so
+// the ID of each key will change every time it's updated. Perhaps a stored
+// procedure for the `setItem()` SQL would solve this problem?
+// TODO: Don't change ID on `setItem()`.
+function key$1(n, callback) {
+ var self = this;
+
+ var promise = new Promise$1(function (resolve, reject) {
+ self.ready().then(function () {
+ var dbInfo = self._dbInfo;
+ dbInfo.db.transaction(function (t) {
+ t.executeSql('SELECT key FROM ' + dbInfo.storeName + ' WHERE id = ? LIMIT 1', [n + 1], function (t, results) {
+ var result = results.rows.length ? results.rows.item(0).key : null;
+ resolve(result);
+ }, function (t, error) {
+ reject(error);
+ });
+ });
+ })["catch"](reject);
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+}
+
+function keys$1(callback) {
+ var self = this;
+
+ var promise = new Promise$1(function (resolve, reject) {
+ self.ready().then(function () {
+ var dbInfo = self._dbInfo;
+ dbInfo.db.transaction(function (t) {
+ t.executeSql('SELECT key FROM ' + dbInfo.storeName, [], function (t, results) {
+ var keys = [];
+
+ for (var i = 0; i < results.rows.length; i++) {
+ keys.push(results.rows.item(i).key);
+ }
+
+ resolve(keys);
+ }, function (t, error) {
+
+ reject(error);
+ });
+ });
+ })["catch"](reject);
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+}
+
+var webSQLStorage = {
+ _driver: 'webSQLStorage',
+ _initStorage: _initStorage$1,
+ iterate: iterate$1,
+ getItem: getItem$1,
+ setItem: setItem$1,
+ removeItem: removeItem$1,
+ clear: clear$1,
+ length: length$1,
+ key: key$1,
+ keys: keys$1
+};
+
+// Config the localStorage backend, using options set in the config.
+function _initStorage$2(options) {
+ var self = this;
+ var dbInfo = {};
+ if (options) {
+ for (var i in options) {
+ dbInfo[i] = options[i];
+ }
+ }
+
+ dbInfo.keyPrefix = dbInfo.name + '/';
+
+ if (dbInfo.storeName !== self._defaultConfig.storeName) {
+ dbInfo.keyPrefix += dbInfo.storeName + '/';
+ }
+
+ self._dbInfo = dbInfo;
+ dbInfo.serializer = localforageSerializer;
+
+ return Promise$1.resolve();
+}
+
+// Remove all keys from the datastore, effectively destroying all data in
+// the app's key/value store!
+function clear$2(callback) {
+ var self = this;
+ var promise = self.ready().then(function () {
+ var keyPrefix = self._dbInfo.keyPrefix;
+
+ for (var i = localStorage.length - 1; i >= 0; i--) {
+ var key = localStorage.key(i);
+
+ if (key.indexOf(keyPrefix) === 0) {
+ localStorage.removeItem(key);
+ }
+ }
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+}
+
+// Retrieve an item from the store. Unlike the original async_storage
+// library in Gaia, we don't modify return values at all. If a key's value
+// is `undefined`, we pass that value to the callback function.
+function getItem$2(key, callback) {
+ var self = this;
+
+ // Cast the key to a string, as that's all we can set as a key.
+ if (typeof key !== 'string') {
+ console.warn(key + ' used as a key, but it is not a string.');
+ key = String(key);
+ }
+
+ var promise = self.ready().then(function () {
+ var dbInfo = self._dbInfo;
+ var result = localStorage.getItem(dbInfo.keyPrefix + key);
+
+ // If a result was found, parse it from the serialized
+ // string into a JS object. If result isn't truthy, the key
+ // is likely undefined and we'll pass it straight to the
+ // callback.
+ if (result) {
+ result = dbInfo.serializer.deserialize(result);
+ }
+
+ return result;
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+}
+
+// Iterate over all items in the store.
+function iterate$2(iterator, callback) {
+ var self = this;
+
+ var promise = self.ready().then(function () {
+ var dbInfo = self._dbInfo;
+ var keyPrefix = dbInfo.keyPrefix;
+ var keyPrefixLength = keyPrefix.length;
+ var length = localStorage.length;
+
+ // We use a dedicated iterator instead of the `i` variable below
+ // so other keys we fetch in localStorage aren't counted in
+ // the `iterationNumber` argument passed to the `iterate()`
+ // callback.
+ //
+ // See: github.com/mozilla/localForage/pull/435#discussion_r38061530
+ var iterationNumber = 1;
+
+ for (var i = 0; i < length; i++) {
+ var key = localStorage.key(i);
+ if (key.indexOf(keyPrefix) !== 0) {
+ continue;
+ }
+ var value = localStorage.getItem(key);
+
+ // If a result was found, parse it from the serialized
+ // string into a JS object. If result isn't truthy, the
+ // key is likely undefined and we'll pass it straight
+ // to the iterator.
+ if (value) {
+ value = dbInfo.serializer.deserialize(value);
+ }
+
+ value = iterator(value, key.substring(keyPrefixLength), iterationNumber++);
+
+ if (value !== void 0) {
+ return value;
+ }
+ }
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+}
+
+// Same as localStorage's key() method, except takes a callback.
+function key$2(n, callback) {
+ var self = this;
+ var promise = self.ready().then(function () {
+ var dbInfo = self._dbInfo;
+ var result;
+ try {
+ result = localStorage.key(n);
+ } catch (error) {
+ result = null;
+ }
+
+ // Remove the prefix from the key, if a key is found.
+ if (result) {
+ result = result.substring(dbInfo.keyPrefix.length);
+ }
+
+ return result;
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+}
+
+function keys$2(callback) {
+ var self = this;
+ var promise = self.ready().then(function () {
+ var dbInfo = self._dbInfo;
+ var length = localStorage.length;
+ var keys = [];
+
+ for (var i = 0; i < length; i++) {
+ if (localStorage.key(i).indexOf(dbInfo.keyPrefix) === 0) {
+ keys.push(localStorage.key(i).substring(dbInfo.keyPrefix.length));
+ }
+ }
+
+ return keys;
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+}
+
+// Supply the number of keys in the datastore to the callback function.
+function length$2(callback) {
+ var self = this;
+ var promise = self.keys().then(function (keys) {
+ return keys.length;
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+}
+
+// Remove an item from the store, nice and simple.
+function removeItem$2(key, callback) {
+ var self = this;
+
+ // Cast the key to a string, as that's all we can set as a key.
+ if (typeof key !== 'string') {
+ console.warn(key + ' used as a key, but it is not a string.');
+ key = String(key);
+ }
+
+ var promise = self.ready().then(function () {
+ var dbInfo = self._dbInfo;
+ localStorage.removeItem(dbInfo.keyPrefix + key);
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+}
+
+// Set a key's value and run an optional callback once the value is set.
+// Unlike Gaia's implementation, the callback function is passed the value,
+// in case you want to operate on that value only after you're sure it
+// saved, or something like that.
+function setItem$2(key, value, callback) {
+ var self = this;
+
+ // Cast the key to a string, as that's all we can set as a key.
+ if (typeof key !== 'string') {
+ console.warn(key + ' used as a key, but it is not a string.');
+ key = String(key);
+ }
+
+ var promise = self.ready().then(function () {
+ // Convert undefined values to null.
+ // https://github.com/mozilla/localForage/pull/42
+ if (value === undefined) {
+ value = null;
+ }
+
+ // Save the original value to pass to the callback.
+ var originalValue = value;
+
+ return new Promise$1(function (resolve, reject) {
+ var dbInfo = self._dbInfo;
+ dbInfo.serializer.serialize(value, function (value, error) {
+ if (error) {
+ reject(error);
+ } else {
+ try {
+ localStorage.setItem(dbInfo.keyPrefix + key, value);
+ resolve(originalValue);
+ } catch (e) {
+ // localStorage capacity exceeded.
+ // TODO: Make this a specific error/event.
+ if (e.name === 'QuotaExceededError' || e.name === 'NS_ERROR_DOM_QUOTA_REACHED') {
+ reject(e);
+ }
+ reject(e);
+ }
+ }
+ });
+ });
+ });
+
+ executeCallback(promise, callback);
+ return promise;
+}
+
+var localStorageWrapper = {
+ _driver: 'localStorageWrapper',
+ _initStorage: _initStorage$2,
+ // Default API, from Gaia/localStorage.
+ iterate: iterate$2,
+ getItem: getItem$2,
+ setItem: setItem$2,
+ removeItem: removeItem$2,
+ clear: clear$2,
+ length: length$2,
+ key: key$2,
+ keys: keys$2
+};
+
+// Custom drivers are stored here when `defineDriver()` is called.
+// They are shared across all instances of localForage.
+var CustomDrivers = {};
+
+var DriverType = {
+ INDEXEDDB: 'asyncStorage',
+ LOCALSTORAGE: 'localStorageWrapper',
+ WEBSQL: 'webSQLStorage'
+};
+
+var DefaultDriverOrder = [DriverType.INDEXEDDB, DriverType.WEBSQL, DriverType.LOCALSTORAGE];
+
+var LibraryMethods = ['clear', 'getItem', 'iterate', 'key', 'keys', 'length', 'removeItem', 'setItem'];
+
+var DefaultConfig = {
+ description: '',
+ driver: DefaultDriverOrder.slice(),
+ name: 'localforage',
+ // Default DB size is _JUST UNDER_ 5MB, as it's the highest size
+ // we can use without a prompt.
+ size: 4980736,
+ storeName: 'keyvaluepairs',
+ version: 1.0
+};
+
+var driverSupport = {};
+// Check to see if IndexedDB is available and if it is the latest
+// implementation; it's our preferred backend library. We use "_spec_test"
+// as the name of the database because it's not the one we'll operate on,
+// but it's useful to make sure its using the right spec.
+// See: https://github.com/mozilla/localForage/issues/128
+driverSupport[DriverType.INDEXEDDB] = isIndexedDBValid();
+
+driverSupport[DriverType.WEBSQL] = isWebSQLValid();
+
+driverSupport[DriverType.LOCALSTORAGE] = isLocalStorageValid();
+
+var isArray = Array.isArray || function (arg) {
+ return Object.prototype.toString.call(arg) === '[object Array]';
+};
+
+function callWhenReady(localForageInstance, libraryMethod) {
+ localForageInstance[libraryMethod] = function () {
+ var _args = arguments;
+ return localForageInstance.ready().then(function () {
+ return localForageInstance[libraryMethod].apply(localForageInstance, _args);
+ });
+ };
+}
+
+function extend() {
+ for (var i = 1; i < arguments.length; i++) {
+ var arg = arguments[i];
+
+ if (arg) {
+ for (var key in arg) {
+ if (arg.hasOwnProperty(key)) {
+ if (isArray(arg[key])) {
+ arguments[0][key] = arg[key].slice();
+ } else {
+ arguments[0][key] = arg[key];
+ }
+ }
+ }
+ }
+ }
+
+ return arguments[0];
+}
+
+function isLibraryDriver(driverName) {
+ for (var driver in DriverType) {
+ if (DriverType.hasOwnProperty(driver) && DriverType[driver] === driverName) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+var LocalForage = function () {
+ function LocalForage(options) {
+ _classCallCheck(this, LocalForage);
+
+ this.INDEXEDDB = DriverType.INDEXEDDB;
+ this.LOCALSTORAGE = DriverType.LOCALSTORAGE;
+ this.WEBSQL = DriverType.WEBSQL;
+
+ this._defaultConfig = extend({}, DefaultConfig);
+ this._config = extend({}, this._defaultConfig, options);
+ this._driverSet = null;
+ this._initDriver = null;
+ this._ready = false;
+ this._dbInfo = null;
+
+ this._wrapLibraryMethodsWithReady();
+ this.setDriver(this._config.driver)["catch"](function () {});
+ }
+
+ // Set any config values for localForage; can be called anytime before
+ // the first API call (e.g. `getItem`, `setItem`).
+ // We loop through options so we don't overwrite existing config
+ // values.
+
+
+ LocalForage.prototype.config = function config(options) {
+ // If the options argument is an object, we use it to set values.
+ // Otherwise, we return either a specified config value or all
+ // config values.
+ if ((typeof options === 'undefined' ? 'undefined' : _typeof(options)) === 'object') {
+ // If localforage is ready and fully initialized, we can't set
+ // any new configuration values. Instead, we return an error.
+ if (this._ready) {
+ return new Error("Can't call config() after localforage " + 'has been used.');
+ }
+
+ for (var i in options) {
+ if (i === 'storeName') {
+ options[i] = options[i].replace(/\W/g, '_');
+ }
+
+ if (i === 'version' && typeof options[i] !== 'number') {
+ return new Error('Database version must be a number.');
+ }
+
+ this._config[i] = options[i];
+ }
+
+ // after all config options are set and
+ // the driver option is used, try setting it
+ if ('driver' in options && options.driver) {
+ return this.setDriver(this._config.driver);
+ }
+
+ return true;
+ } else if (typeof options === 'string') {
+ return this._config[options];
+ } else {
+ return this._config;
+ }
+ };
+
+ // Used to define a custom driver, shared across all instances of
+ // localForage.
+
+
+ LocalForage.prototype.defineDriver = function defineDriver(driverObject, callback, errorCallback) {
+ var promise = new Promise$1(function (resolve, reject) {
+ try {
+ var driverName = driverObject._driver;
+ var complianceError = new Error('Custom driver not compliant; see ' + 'https://mozilla.github.io/localForage/#definedriver');
+ var namingError = new Error('Custom driver name already in use: ' + driverObject._driver);
+
+ // A driver name should be defined and not overlap with the
+ // library-defined, default drivers.
+ if (!driverObject._driver) {
+ reject(complianceError);
+ return;
+ }
+ if (isLibraryDriver(driverObject._driver)) {
+ reject(namingError);
+ return;
+ }
+
+ var customDriverMethods = LibraryMethods.concat('_initStorage');
+ for (var i = 0; i < customDriverMethods.length; i++) {
+ var customDriverMethod = customDriverMethods[i];
+ if (!customDriverMethod || !driverObject[customDriverMethod] || typeof driverObject[customDriverMethod] !== 'function') {
+ reject(complianceError);
+ return;
+ }
+ }
+
+ var supportPromise = Promise$1.resolve(true);
+ if ('_support' in driverObject) {
+ if (driverObject._support && typeof driverObject._support === 'function') {
+ supportPromise = driverObject._support();
+ } else {
+ supportPromise = Promise$1.resolve(!!driverObject._support);
+ }
+ }
+
+ supportPromise.then(function (supportResult) {
+ driverSupport[driverName] = supportResult;
+ CustomDrivers[driverName] = driverObject;
+ resolve();
+ }, reject);
+ } catch (e) {
+ reject(e);
+ }
+ });
+
+ executeTwoCallbacks(promise, callback, errorCallback);
+ return promise;
+ };
+
+ LocalForage.prototype.driver = function driver() {
+ return this._driver || null;
+ };
+
+ LocalForage.prototype.getDriver = function getDriver(driverName, callback, errorCallback) {
+ var self = this;
+ var getDriverPromise = Promise$1.resolve().then(function () {
+ if (isLibraryDriver(driverName)) {
+ switch (driverName) {
+ case self.INDEXEDDB:
+ return asyncStorage;
+ case self.LOCALSTORAGE:
+ return localStorageWrapper;
+ case self.WEBSQL:
+ return webSQLStorage;
+ }
+ } else if (CustomDrivers[driverName]) {
+ return CustomDrivers[driverName];
+ } else {
+ throw new Error('Driver not found.');
+ }
+ });
+ executeTwoCallbacks(getDriverPromise, callback, errorCallback);
+ return getDriverPromise;
+ };
+
+ LocalForage.prototype.getSerializer = function getSerializer(callback) {
+ var serializerPromise = Promise$1.resolve(localforageSerializer);
+ executeTwoCallbacks(serializerPromise, callback);
+ return serializerPromise;
+ };
+
+ LocalForage.prototype.ready = function ready(callback) {
+ var self = this;
+
+ var promise = self._driverSet.then(function () {
+ if (self._ready === null) {
+ self._ready = self._initDriver();
+ }
+
+ return self._ready;
+ });
+
+ executeTwoCallbacks(promise, callback, callback);
+ return promise;
+ };
+
+ LocalForage.prototype.setDriver = function setDriver(drivers, callback, errorCallback) {
+ var self = this;
+
+ if (!isArray(drivers)) {
+ drivers = [drivers];
+ }
+
+ var supportedDrivers = this._getSupportedDrivers(drivers);
+
+ function setDriverToConfig() {
+ self._config.driver = self.driver();
+ }
+
+ function extendSelfWithDriver(driver) {
+ self._extend(driver);
+ setDriverToConfig();
+
+ self._ready = self._initStorage(self._config);
+ return self._ready;
+ }
+
+ function initDriver(supportedDrivers) {
+ return function () {
+ var currentDriverIndex = 0;
+
+ function driverPromiseLoop() {
+ while (currentDriverIndex < supportedDrivers.length) {
+ var driverName = supportedDrivers[currentDriverIndex];
+ currentDriverIndex++;
+
+ self._dbInfo = null;
+ self._ready = null;
+
+ return self.getDriver(driverName).then(extendSelfWithDriver)["catch"](driverPromiseLoop);
+ }
+
+ setDriverToConfig();
+ var error = new Error('No available storage method found.');
+ self._driverSet = Promise$1.reject(error);
+ return self._driverSet;
+ }
+
+ return driverPromiseLoop();
+ };
+ }
+
+ // There might be a driver initialization in progress
+ // so wait for it to finish in order to avoid a possible
+ // race condition to set _dbInfo
+ var oldDriverSetDone = this._driverSet !== null ? this._driverSet["catch"](function () {
+ return Promise$1.resolve();
+ }) : Promise$1.resolve();
+
+ this._driverSet = oldDriverSetDone.then(function () {
+ var driverName = supportedDrivers[0];
+ self._dbInfo = null;
+ self._ready = null;
+
+ return self.getDriver(driverName).then(function (driver) {
+ self._driver = driver._driver;
+ setDriverToConfig();
+ self._wrapLibraryMethodsWithReady();
+ self._initDriver = initDriver(supportedDrivers);
+ });
+ })["catch"](function () {
+ setDriverToConfig();
+ var error = new Error('No available storage method found.');
+ self._driverSet = Promise$1.reject(error);
+ return self._driverSet;
+ });
+
+ executeTwoCallbacks(this._driverSet, callback, errorCallback);
+ return this._driverSet;
+ };
+
+ LocalForage.prototype.supports = function supports(driverName) {
+ return !!driverSupport[driverName];
+ };
+
+ LocalForage.prototype._extend = function _extend(libraryMethodsAndProperties) {
+ extend(this, libraryMethodsAndProperties);
+ };
+
+ LocalForage.prototype._getSupportedDrivers = function _getSupportedDrivers(drivers) {
+ var supportedDrivers = [];
+ for (var i = 0, len = drivers.length; i < len; i++) {
+ var driverName = drivers[i];
+ if (this.supports(driverName)) {
+ supportedDrivers.push(driverName);
+ }
+ }
+ return supportedDrivers;
+ };
+
+ LocalForage.prototype._wrapLibraryMethodsWithReady = function _wrapLibraryMethodsWithReady() {
+ // Add a stub for each driver API method that delays the call to the
+ // corresponding driver method until localForage is ready. These stubs
+ // will be replaced by the driver methods as soon as the driver is
+ // loaded, so there is no performance impact.
+ for (var i = 0; i < LibraryMethods.length; i++) {
+ callWhenReady(this, LibraryMethods[i]);
+ }
+ };
+
+ LocalForage.prototype.createInstance = function createInstance(options) {
+ return new LocalForage(options);
+ };
+
+ return LocalForage;
+}();
+
+// The actual localForage object that we expose as a module or via a
+// global. It's extended by pulling in one of our other libraries.
+
+
+var localforage_js = new LocalForage();
+
+module.exports = localforage_js;
+
+},{"3":3}]},{},[4])(4)
+}); \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/shared-resources/res/localForage/localforage.min.js b/www/wiki/vendor/onoi/shared-resources/res/localForage/localforage.min.js
new file mode 100644
index 00000000..d8761e23
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/localForage/localforage.min.js
@@ -0,0 +1,7 @@
+/*!
+ localForage -- Offline Storage, Improved
+ Version 1.5.0
+ https://localforage.github.io/localForage
+ (c) 2013-2017 Mozilla, Apache License 2.0
+*/
+!function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,b.localforage=a()}}(function(){return function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b,c){(function(a){"use strict";function c(){k=!0;for(var a,b,c=l.length;c;){for(b=l,l=[],a=-1;++a<c;)b[a]();c=l.length}k=!1}function d(a){1!==l.push(a)||k||e()}var e,f=a.MutationObserver||a.WebKitMutationObserver;if(f){var g=0,h=new f(c),i=a.document.createTextNode("");h.observe(i,{characterData:!0}),e=function(){i.data=g=++g%2}}else if(a.setImmediate||"undefined"==typeof a.MessageChannel)e="document"in a&&"onreadystatechange"in a.document.createElement("script")?function(){var b=a.document.createElement("script");b.onreadystatechange=function(){c(),b.onreadystatechange=null,b.parentNode.removeChild(b),b=null},a.document.documentElement.appendChild(b)}:function(){setTimeout(c,0)};else{var j=new a.MessageChannel;j.port1.onmessage=c,e=function(){j.port2.postMessage(0)}}var k,l=[];b.exports=d}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],2:[function(a,b,c){"use strict";function d(){}function e(a){if("function"!=typeof a)throw new TypeError("resolver must be a function");this.state=s,this.queue=[],this.outcome=void 0,a!==d&&i(this,a)}function f(a,b,c){this.promise=a,"function"==typeof b&&(this.onFulfilled=b,this.callFulfilled=this.otherCallFulfilled),"function"==typeof c&&(this.onRejected=c,this.callRejected=this.otherCallRejected)}function g(a,b,c){o(function(){var d;try{d=b(c)}catch(e){return p.reject(a,e)}d===a?p.reject(a,new TypeError("Cannot resolve promise with itself")):p.resolve(a,d)})}function h(a){var b=a&&a.then;return a&&"object"==typeof a&&"function"==typeof b?function(){b.apply(a,arguments)}:void 0}function i(a,b){function c(b){f||(f=!0,p.reject(a,b))}function d(b){f||(f=!0,p.resolve(a,b))}function e(){b(d,c)}var f=!1,g=j(e);"error"===g.status&&c(g.value)}function j(a,b){var c={};try{c.value=a(b),c.status="success"}catch(d){c.status="error",c.value=d}return c}function k(a){return a instanceof this?a:p.resolve(new this(d),a)}function l(a){var b=new this(d);return p.reject(b,a)}function m(a){function b(a,b){function d(a){g[b]=a,++h!==e||f||(f=!0,p.resolve(j,g))}c.resolve(a).then(d,function(a){f||(f=!0,p.reject(j,a))})}var c=this;if("[object Array]"!==Object.prototype.toString.call(a))return this.reject(new TypeError("must be an array"));var e=a.length,f=!1;if(!e)return this.resolve([]);for(var g=new Array(e),h=0,i=-1,j=new this(d);++i<e;)b(a[i],i);return j}function n(a){function b(a){c.resolve(a).then(function(a){f||(f=!0,p.resolve(h,a))},function(a){f||(f=!0,p.reject(h,a))})}var c=this;if("[object Array]"!==Object.prototype.toString.call(a))return this.reject(new TypeError("must be an array"));var e=a.length,f=!1;if(!e)return this.resolve([]);for(var g=-1,h=new this(d);++g<e;)b(a[g]);return h}var o=a(1),p={},q=["REJECTED"],r=["FULFILLED"],s=["PENDING"];b.exports=c=e,e.prototype["catch"]=function(a){return this.then(null,a)},e.prototype.then=function(a,b){if("function"!=typeof a&&this.state===r||"function"!=typeof b&&this.state===q)return this;var c=new this.constructor(d);if(this.state!==s){var e=this.state===r?a:b;g(c,e,this.outcome)}else this.queue.push(new f(c,a,b));return c},f.prototype.callFulfilled=function(a){p.resolve(this.promise,a)},f.prototype.otherCallFulfilled=function(a){g(this.promise,this.onFulfilled,a)},f.prototype.callRejected=function(a){p.reject(this.promise,a)},f.prototype.otherCallRejected=function(a){g(this.promise,this.onRejected,a)},p.resolve=function(a,b){var c=j(h,b);if("error"===c.status)return p.reject(a,c.value);var d=c.value;if(d)i(a,d);else{a.state=r,a.outcome=b;for(var e=-1,f=a.queue.length;++e<f;)a.queue[e].callFulfilled(b)}return a},p.reject=function(a,b){a.state=q,a.outcome=b;for(var c=-1,d=a.queue.length;++c<d;)a.queue[c].callRejected(b);return a},c.resolve=k,c.reject=l,c.all=m,c.race=n},{1:1}],3:[function(a,b,c){(function(b){"use strict";"function"!=typeof b.Promise&&(b.Promise=a(2))}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{2:2}],4:[function(a,b,c){"use strict";function d(a,b){if(!(a instanceof b))throw new TypeError("Cannot call a class as a function")}function e(){try{if("undefined"!=typeof indexedDB)return indexedDB;if("undefined"!=typeof webkitIndexedDB)return webkitIndexedDB;if("undefined"!=typeof mozIndexedDB)return mozIndexedDB;if("undefined"!=typeof OIndexedDB)return OIndexedDB;if("undefined"!=typeof msIndexedDB)return msIndexedDB}catch(a){}}function f(){try{if(!ga)return!1;var a="undefined"!=typeof openDatabase&&/(Safari|iPhone|iPad|iPod)/.test(navigator.userAgent)&&!/Chrome/.test(navigator.userAgent)&&!/BlackBerry/.test(navigator.platform),b="function"==typeof fetch&&-1!==fetch.toString().indexOf("[native code");return(!a||b)&&"undefined"!=typeof indexedDB&&"undefined"!=typeof IDBKeyRange}catch(c){return!1}}function g(){return"function"==typeof openDatabase}function h(){try{return"undefined"!=typeof localStorage&&"setItem"in localStorage&&localStorage.setItem}catch(a){return!1}}function i(a,b){a=a||[],b=b||{};try{return new Blob(a,b)}catch(c){if("TypeError"!==c.name)throw c;for(var d="undefined"!=typeof BlobBuilder?BlobBuilder:"undefined"!=typeof MSBlobBuilder?MSBlobBuilder:"undefined"!=typeof MozBlobBuilder?MozBlobBuilder:WebKitBlobBuilder,e=new d,f=0;f<a.length;f+=1)e.append(a[f]);return e.getBlob(b.type)}}function j(a,b){b&&a.then(function(a){b(null,a)},function(a){b(a)})}function k(a,b,c){"function"==typeof b&&a.then(b),"function"==typeof c&&a["catch"](c)}function l(a){for(var b=a.length,c=new ArrayBuffer(b),d=new Uint8Array(c),e=0;b>e;e++)d[e]=a.charCodeAt(e);return c}function m(a){return new ja(function(b){var c=a.transaction(ka,"readwrite"),d=i([""]);c.objectStore(ka).put(d,"key"),c.onabort=function(a){a.preventDefault(),a.stopPropagation(),b(!1)},c.oncomplete=function(){var a=navigator.userAgent.match(/Chrome\/(\d+)/),c=navigator.userAgent.match(/Edge\//);b(c||!a||parseInt(a[1],10)>=43)}})["catch"](function(){return!1})}function n(a){return"boolean"==typeof ha?ja.resolve(ha):m(a).then(function(a){return ha=a})}function o(a){var b=ia[a.name],c={};c.promise=new ja(function(a){c.resolve=a}),b.deferredOperations.push(c),b.dbReady?b.dbReady=b.dbReady.then(function(){return c.promise}):b.dbReady=c.promise}function p(a){var b=ia[a.name],c=b.deferredOperations.pop();c&&c.resolve()}function q(a,b){return new ja(function(c,d){if(a.db){if(!b)return c(a.db);o(a),a.db.close()}var e=[a.name];b&&e.push(a.version);var f=ga.open.apply(ga,e);b&&(f.onupgradeneeded=function(b){var c=f.result;try{c.createObjectStore(a.storeName),b.oldVersion<=1&&c.createObjectStore(ka)}catch(d){if("ConstraintError"!==d.name)throw d;console.warn('The database "'+a.name+'" has been upgraded from version '+b.oldVersion+" to version "+b.newVersion+', but the storage "'+a.storeName+'" already exists.')}}),f.onerror=function(a){a.preventDefault(),d(f.error)},f.onsuccess=function(){c(f.result),p(a)}})}function r(a){return q(a,!1)}function s(a){return q(a,!0)}function t(a,b){if(!a.db)return!0;var c=!a.db.objectStoreNames.contains(a.storeName),d=a.version<a.db.version,e=a.version>a.db.version;if(d&&(a.version!==b&&console.warn('The database "'+a.name+"\" can't be downgraded from version "+a.db.version+" to version "+a.version+"."),a.version=a.db.version),e||c){if(c){var f=a.db.version+1;f>a.version&&(a.version=f)}return!0}return!1}function u(a){return new ja(function(b,c){var d=new FileReader;d.onerror=c,d.onloadend=function(c){var d=btoa(c.target.result||"");b({__local_forage_encoded_blob:!0,data:d,type:a.type})},d.readAsBinaryString(a)})}function v(a){var b=l(atob(a.data));return i([b],{type:a.type})}function w(a){return a&&a.__local_forage_encoded_blob}function x(a){var b=this,c=b._initReady().then(function(){var a=ia[b._dbInfo.name];return a&&a.dbReady?a.dbReady:void 0});return k(c,a,a),c}function y(a){function b(){return ja.resolve()}var c=this,d={db:null};if(a)for(var e in a)d[e]=a[e];ia||(ia={});var f=ia[d.name];f||(f={forages:[],db:null,dbReady:null,deferredOperations:[]},ia[d.name]=f),f.forages.push(c),c._initReady||(c._initReady=c.ready,c.ready=x);for(var g=[],h=0;h<f.forages.length;h++){var i=f.forages[h];i!==c&&g.push(i._initReady()["catch"](b))}var j=f.forages.slice(0);return ja.all(g).then(function(){return d.db=f.db,r(d)}).then(function(a){return d.db=a,t(d,c._defaultConfig.version)?s(d):a}).then(function(a){d.db=f.db=a,c._dbInfo=d;for(var b=0;b<j.length;b++){var e=j[b];e!==c&&(e._dbInfo.db=d.db,e._dbInfo.version=d.version)}})}function z(a,b){var c=this;"string"!=typeof a&&(console.warn(a+" used as a key, but it is not a string."),a=String(a));var d=new ja(function(b,d){c.ready().then(function(){var e=c._dbInfo,f=e.db.transaction(e.storeName,"readonly").objectStore(e.storeName),g=f.get(a);g.onsuccess=function(){var a=g.result;void 0===a&&(a=null),w(a)&&(a=v(a)),b(a)},g.onerror=function(){d(g.error)}})["catch"](d)});return j(d,b),d}function A(a,b){var c=this,d=new ja(function(b,d){c.ready().then(function(){var e=c._dbInfo,f=e.db.transaction(e.storeName,"readonly").objectStore(e.storeName),g=f.openCursor(),h=1;g.onsuccess=function(){var c=g.result;if(c){var d=c.value;w(d)&&(d=v(d));var e=a(d,c.key,h++);void 0!==e?b(e):c["continue"]()}else b()},g.onerror=function(){d(g.error)}})["catch"](d)});return j(d,b),d}function B(a,b,c){var d=this;"string"!=typeof a&&(console.warn(a+" used as a key, but it is not a string."),a=String(a));var e=new ja(function(c,e){var f;d.ready().then(function(){return f=d._dbInfo,"[object Blob]"===la.call(b)?n(f.db).then(function(a){return a?b:u(b)}):b}).then(function(b){var d=f.db.transaction(f.storeName,"readwrite"),g=d.objectStore(f.storeName),h=g.put(b,a);null===b&&(b=void 0),d.oncomplete=function(){void 0===b&&(b=null),c(b)},d.onabort=d.onerror=function(){var a=h.error?h.error:h.transaction.error;e(a)}})["catch"](e)});return j(e,c),e}function C(a,b){var c=this;"string"!=typeof a&&(console.warn(a+" used as a key, but it is not a string."),a=String(a));var d=new ja(function(b,d){c.ready().then(function(){var e=c._dbInfo,f=e.db.transaction(e.storeName,"readwrite"),g=f.objectStore(e.storeName),h=g["delete"](a);f.oncomplete=function(){b()},f.onerror=function(){d(h.error)},f.onabort=function(){var a=h.error?h.error:h.transaction.error;d(a)}})["catch"](d)});return j(d,b),d}function D(a){var b=this,c=new ja(function(a,c){b.ready().then(function(){var d=b._dbInfo,e=d.db.transaction(d.storeName,"readwrite"),f=e.objectStore(d.storeName),g=f.clear();e.oncomplete=function(){a()},e.onabort=e.onerror=function(){var a=g.error?g.error:g.transaction.error;c(a)}})["catch"](c)});return j(c,a),c}function E(a){var b=this,c=new ja(function(a,c){b.ready().then(function(){var d=b._dbInfo,e=d.db.transaction(d.storeName,"readonly").objectStore(d.storeName),f=e.count();f.onsuccess=function(){a(f.result)},f.onerror=function(){c(f.error)}})["catch"](c)});return j(c,a),c}function F(a,b){var c=this,d=new ja(function(b,d){return 0>a?void b(null):void c.ready().then(function(){var e=c._dbInfo,f=e.db.transaction(e.storeName,"readonly").objectStore(e.storeName),g=!1,h=f.openCursor();h.onsuccess=function(){var c=h.result;return c?void(0===a?b(c.key):g?b(c.key):(g=!0,c.advance(a))):void b(null)},h.onerror=function(){d(h.error)}})["catch"](d)});return j(d,b),d}function G(a){var b=this,c=new ja(function(a,c){b.ready().then(function(){var d=b._dbInfo,e=d.db.transaction(d.storeName,"readonly").objectStore(d.storeName),f=e.openCursor(),g=[];f.onsuccess=function(){var b=f.result;return b?(g.push(b.key),void b["continue"]()):void a(g)},f.onerror=function(){c(f.error)}})["catch"](c)});return j(c,a),c}function H(a){var b,c,d,e,f,g=.75*a.length,h=a.length,i=0;"="===a[a.length-1]&&(g--,"="===a[a.length-2]&&g--);var j=new ArrayBuffer(g),k=new Uint8Array(j);for(b=0;h>b;b+=4)c=na.indexOf(a[b]),d=na.indexOf(a[b+1]),e=na.indexOf(a[b+2]),f=na.indexOf(a[b+3]),k[i++]=c<<2|d>>4,k[i++]=(15&d)<<4|e>>2,k[i++]=(3&e)<<6|63&f;return j}function I(a){var b,c=new Uint8Array(a),d="";for(b=0;b<c.length;b+=3)d+=na[c[b]>>2],d+=na[(3&c[b])<<4|c[b+1]>>4],d+=na[(15&c[b+1])<<2|c[b+2]>>6],d+=na[63&c[b+2]];return c.length%3===2?d=d.substring(0,d.length-1)+"=":c.length%3===1&&(d=d.substring(0,d.length-2)+"=="),d}function J(a,b){var c="";if(a&&(c=Ea.call(a)),a&&("[object ArrayBuffer]"===c||a.buffer&&"[object ArrayBuffer]"===Ea.call(a.buffer))){var d,e=qa;a instanceof ArrayBuffer?(d=a,e+=sa):(d=a.buffer,"[object Int8Array]"===c?e+=ua:"[object Uint8Array]"===c?e+=va:"[object Uint8ClampedArray]"===c?e+=wa:"[object Int16Array]"===c?e+=xa:"[object Uint16Array]"===c?e+=za:"[object Int32Array]"===c?e+=ya:"[object Uint32Array]"===c?e+=Aa:"[object Float32Array]"===c?e+=Ba:"[object Float64Array]"===c?e+=Ca:b(new Error("Failed to get type for BinaryArray"))),b(e+I(d))}else if("[object Blob]"===c){var f=new FileReader;f.onload=function(){var c=oa+a.type+"~"+I(this.result);b(qa+ta+c)},f.readAsArrayBuffer(a)}else try{b(JSON.stringify(a))}catch(g){console.error("Couldn't convert value into a JSON string: ",a),b(null,g)}}function K(a){if(a.substring(0,ra)!==qa)return JSON.parse(a);var b,c=a.substring(Da),d=a.substring(ra,Da);if(d===ta&&pa.test(c)){var e=c.match(pa);b=e[1],c=c.substring(e[0].length)}var f=H(c);switch(d){case sa:return f;case ta:return i([f],{type:b});case ua:return new Int8Array(f);case va:return new Uint8Array(f);case wa:return new Uint8ClampedArray(f);case xa:return new Int16Array(f);case za:return new Uint16Array(f);case ya:return new Int32Array(f);case Aa:return new Uint32Array(f);case Ba:return new Float32Array(f);case Ca:return new Float64Array(f);default:throw new Error("Unkown type: "+d)}}function L(a){var b=this,c={db:null};if(a)for(var d in a)c[d]="string"!=typeof a[d]?a[d].toString():a[d];var e=new ja(function(a,d){try{c.db=openDatabase(c.name,String(c.version),c.description,c.size)}catch(e){return d(e)}c.db.transaction(function(e){e.executeSql("CREATE TABLE IF NOT EXISTS "+c.storeName+" (id INTEGER PRIMARY KEY, key unique, value)",[],function(){b._dbInfo=c,a()},function(a,b){d(b)})})});return c.serializer=Fa,e}function M(a,b){var c=this;"string"!=typeof a&&(console.warn(a+" used as a key, but it is not a string."),a=String(a));var d=new ja(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){c.executeSql("SELECT * FROM "+e.storeName+" WHERE key = ? LIMIT 1",[a],function(a,c){var d=c.rows.length?c.rows.item(0).value:null;d&&(d=e.serializer.deserialize(d)),b(d)},function(a,b){d(b)})})})["catch"](d)});return j(d,b),d}function N(a,b){var c=this,d=new ja(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){c.executeSql("SELECT * FROM "+e.storeName,[],function(c,d){for(var f=d.rows,g=f.length,h=0;g>h;h++){var i=f.item(h),j=i.value;if(j&&(j=e.serializer.deserialize(j)),j=a(j,i.key,h+1),void 0!==j)return void b(j)}b()},function(a,b){d(b)})})})["catch"](d)});return j(d,b),d}function O(a,b,c,d){var e=this;"string"!=typeof a&&(console.warn(a+" used as a key, but it is not a string."),a=String(a));var f=new ja(function(f,g){e.ready().then(function(){void 0===b&&(b=null);var h=b,i=e._dbInfo;i.serializer.serialize(b,function(b,j){j?g(j):i.db.transaction(function(c){c.executeSql("INSERT OR REPLACE INTO "+i.storeName+" (key, value) VALUES (?, ?)",[a,b],function(){f(h)},function(a,b){g(b)})},function(b){if(b.code===b.QUOTA_ERR){if(d>0)return void f(O.apply(e,[a,h,c,d-1]));g(b)}})})})["catch"](g)});return j(f,c),f}function P(a,b,c){return O.apply(this,[a,b,c,1])}function Q(a,b){var c=this;"string"!=typeof a&&(console.warn(a+" used as a key, but it is not a string."),a=String(a));var d=new ja(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){c.executeSql("DELETE FROM "+e.storeName+" WHERE key = ?",[a],function(){b()},function(a,b){d(b)})})})["catch"](d)});return j(d,b),d}function R(a){var b=this,c=new ja(function(a,c){b.ready().then(function(){var d=b._dbInfo;d.db.transaction(function(b){b.executeSql("DELETE FROM "+d.storeName,[],function(){a()},function(a,b){c(b)})})})["catch"](c)});return j(c,a),c}function S(a){var b=this,c=new ja(function(a,c){b.ready().then(function(){var d=b._dbInfo;d.db.transaction(function(b){b.executeSql("SELECT COUNT(key) as c FROM "+d.storeName,[],function(b,c){var d=c.rows.item(0).c;a(d)},function(a,b){c(b)})})})["catch"](c)});return j(c,a),c}function T(a,b){var c=this,d=new ja(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){c.executeSql("SELECT key FROM "+e.storeName+" WHERE id = ? LIMIT 1",[a+1],function(a,c){var d=c.rows.length?c.rows.item(0).key:null;b(d)},function(a,b){d(b)})})})["catch"](d)});return j(d,b),d}function U(a){var b=this,c=new ja(function(a,c){b.ready().then(function(){var d=b._dbInfo;d.db.transaction(function(b){b.executeSql("SELECT key FROM "+d.storeName,[],function(b,c){for(var d=[],e=0;e<c.rows.length;e++)d.push(c.rows.item(e).key);a(d)},function(a,b){c(b)})})})["catch"](c)});return j(c,a),c}function V(a){var b=this,c={};if(a)for(var d in a)c[d]=a[d];return c.keyPrefix=c.name+"/",c.storeName!==b._defaultConfig.storeName&&(c.keyPrefix+=c.storeName+"/"),b._dbInfo=c,c.serializer=Fa,ja.resolve()}function W(a){var b=this,c=b.ready().then(function(){for(var a=b._dbInfo.keyPrefix,c=localStorage.length-1;c>=0;c--){var d=localStorage.key(c);0===d.indexOf(a)&&localStorage.removeItem(d)}});return j(c,a),c}function X(a,b){var c=this;"string"!=typeof a&&(console.warn(a+" used as a key, but it is not a string."),a=String(a));var d=c.ready().then(function(){var b=c._dbInfo,d=localStorage.getItem(b.keyPrefix+a);return d&&(d=b.serializer.deserialize(d)),d});return j(d,b),d}function Y(a,b){var c=this,d=c.ready().then(function(){for(var b=c._dbInfo,d=b.keyPrefix,e=d.length,f=localStorage.length,g=1,h=0;f>h;h++){var i=localStorage.key(h);if(0===i.indexOf(d)){var j=localStorage.getItem(i);if(j&&(j=b.serializer.deserialize(j)),j=a(j,i.substring(e),g++),void 0!==j)return j}}});return j(d,b),d}function Z(a,b){var c=this,d=c.ready().then(function(){var b,d=c._dbInfo;try{b=localStorage.key(a)}catch(e){b=null}return b&&(b=b.substring(d.keyPrefix.length)),b});return j(d,b),d}function $(a){var b=this,c=b.ready().then(function(){for(var a=b._dbInfo,c=localStorage.length,d=[],e=0;c>e;e++)0===localStorage.key(e).indexOf(a.keyPrefix)&&d.push(localStorage.key(e).substring(a.keyPrefix.length));return d});return j(c,a),c}function _(a){var b=this,c=b.keys().then(function(a){return a.length});return j(c,a),c}function aa(a,b){var c=this;"string"!=typeof a&&(console.warn(a+" used as a key, but it is not a string."),a=String(a));var d=c.ready().then(function(){var b=c._dbInfo;localStorage.removeItem(b.keyPrefix+a)});return j(d,b),d}function ba(a,b,c){var d=this;"string"!=typeof a&&(console.warn(a+" used as a key, but it is not a string."),a=String(a));var e=d.ready().then(function(){void 0===b&&(b=null);var c=b;return new ja(function(e,f){var g=d._dbInfo;g.serializer.serialize(b,function(b,d){if(d)f(d);else try{localStorage.setItem(g.keyPrefix+a,b),e(c)}catch(h){"QuotaExceededError"!==h.name&&"NS_ERROR_DOM_QUOTA_REACHED"!==h.name||f(h),f(h)}})})});return j(e,c),e}function ca(a,b){a[b]=function(){var c=arguments;return a.ready().then(function(){return a[b].apply(a,c)})}}function da(){for(var a=1;a<arguments.length;a++){var b=arguments[a];if(b)for(var c in b)b.hasOwnProperty(c)&&(Oa(b[c])?arguments[0][c]=b[c].slice():arguments[0][c]=b[c])}return arguments[0]}function ea(a){for(var b in Ja)if(Ja.hasOwnProperty(b)&&Ja[b]===a)return!0;return!1}var fa="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol?"symbol":typeof a},ga=e();"undefined"==typeof Promise&&a(3);var ha,ia,ja=Promise,ka="local-forage-detect-blob-support",la=Object.prototype.toString,ma={_driver:"asyncStorage",_initStorage:y,iterate:A,getItem:z,setItem:B,removeItem:C,clear:D,length:E,key:F,keys:G},na="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",oa="~~local_forage_type~",pa=/^~~local_forage_type~([^~]+)~/,qa="__lfsc__:",ra=qa.length,sa="arbf",ta="blob",ua="si08",va="ui08",wa="uic8",xa="si16",ya="si32",za="ur16",Aa="ui32",Ba="fl32",Ca="fl64",Da=ra+sa.length,Ea=Object.prototype.toString,Fa={serialize:J,deserialize:K,stringToBuffer:H,bufferToString:I},Ga={_driver:"webSQLStorage",_initStorage:L,iterate:N,getItem:M,setItem:P,removeItem:Q,clear:R,length:S,key:T,keys:U},Ha={_driver:"localStorageWrapper",_initStorage:V,iterate:Y,getItem:X,setItem:ba,removeItem:aa,clear:W,length:_,key:Z,keys:$},Ia={},Ja={INDEXEDDB:"asyncStorage",LOCALSTORAGE:"localStorageWrapper",WEBSQL:"webSQLStorage"},Ka=[Ja.INDEXEDDB,Ja.WEBSQL,Ja.LOCALSTORAGE],La=["clear","getItem","iterate","key","keys","length","removeItem","setItem"],Ma={description:"",driver:Ka.slice(),name:"localforage",size:4980736,storeName:"keyvaluepairs",version:1},Na={};Na[Ja.INDEXEDDB]=f(),Na[Ja.WEBSQL]=g(),Na[Ja.LOCALSTORAGE]=h();var Oa=Array.isArray||function(a){return"[object Array]"===Object.prototype.toString.call(a)},Pa=function(){function a(b){d(this,a),this.INDEXEDDB=Ja.INDEXEDDB,this.LOCALSTORAGE=Ja.LOCALSTORAGE,this.WEBSQL=Ja.WEBSQL,this._defaultConfig=da({},Ma),this._config=da({},this._defaultConfig,b),this._driverSet=null,this._initDriver=null,this._ready=!1,this._dbInfo=null,this._wrapLibraryMethodsWithReady(),this.setDriver(this._config.driver)["catch"](function(){})}return a.prototype.config=function(a){if("object"===("undefined"==typeof a?"undefined":fa(a))){if(this._ready)return new Error("Can't call config() after localforage has been used.");for(var b in a){if("storeName"===b&&(a[b]=a[b].replace(/\W/g,"_")),"version"===b&&"number"!=typeof a[b])return new Error("Database version must be a number.");this._config[b]=a[b]}return"driver"in a&&a.driver?this.setDriver(this._config.driver):!0}return"string"==typeof a?this._config[a]:this._config},a.prototype.defineDriver=function(a,b,c){var d=new ja(function(b,c){try{var d=a._driver,e=new Error("Custom driver not compliant; see https://mozilla.github.io/localForage/#definedriver"),f=new Error("Custom driver name already in use: "+a._driver);if(!a._driver)return void c(e);if(ea(a._driver))return void c(f);for(var g=La.concat("_initStorage"),h=0;h<g.length;h++){var i=g[h];if(!i||!a[i]||"function"!=typeof a[i])return void c(e)}var j=ja.resolve(!0);"_support"in a&&(j=a._support&&"function"==typeof a._support?a._support():ja.resolve(!!a._support)),j.then(function(c){Na[d]=c,Ia[d]=a,b()},c)}catch(k){c(k)}});return k(d,b,c),d},a.prototype.driver=function(){return this._driver||null},a.prototype.getDriver=function(a,b,c){var d=this,e=ja.resolve().then(function(){if(!ea(a)){if(Ia[a])return Ia[a];throw new Error("Driver not found.")}switch(a){case d.INDEXEDDB:return ma;case d.LOCALSTORAGE:return Ha;case d.WEBSQL:return Ga}});return k(e,b,c),e},a.prototype.getSerializer=function(a){var b=ja.resolve(Fa);return k(b,a),b},a.prototype.ready=function(a){var b=this,c=b._driverSet.then(function(){return null===b._ready&&(b._ready=b._initDriver()),b._ready});return k(c,a,a),c},a.prototype.setDriver=function(a,b,c){function d(){g._config.driver=g.driver()}function e(a){return g._extend(a),d(),g._ready=g._initStorage(g._config),g._ready}function f(a){return function(){function b(){for(;c<a.length;){var f=a[c];return c++,g._dbInfo=null,g._ready=null,g.getDriver(f).then(e)["catch"](b)}d();var h=new Error("No available storage method found.");return g._driverSet=ja.reject(h),g._driverSet}var c=0;return b()}}var g=this;Oa(a)||(a=[a]);var h=this._getSupportedDrivers(a),i=null!==this._driverSet?this._driverSet["catch"](function(){return ja.resolve()}):ja.resolve();return this._driverSet=i.then(function(){var a=h[0];return g._dbInfo=null,g._ready=null,g.getDriver(a).then(function(a){g._driver=a._driver,d(),g._wrapLibraryMethodsWithReady(),g._initDriver=f(h)})})["catch"](function(){d();var a=new Error("No available storage method found.");return g._driverSet=ja.reject(a),g._driverSet}),k(this._driverSet,b,c),this._driverSet},a.prototype.supports=function(a){return!!Na[a]},a.prototype._extend=function(a){da(this,a)},a.prototype._getSupportedDrivers=function(a){for(var b=[],c=0,d=a.length;d>c;c++){var e=a[c];this.supports(e)&&b.push(e)}return b},a.prototype._wrapLibraryMethodsWithReady=function(){for(var a=0;a<La.length;a++)ca(this,La[a])},a.prototype.createInstance=function(b){return new a(b)},a}(),Qa=new Pa;b.exports=Qa},{3:3}]},{},[4])(4)}); \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/shared-resources/res/md5/jquery.md5.js b/www/wiki/vendor/onoi/shared-resources/res/md5/jquery.md5.js
new file mode 100644
index 00000000..da60ff56
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/md5/jquery.md5.js
@@ -0,0 +1,275 @@
+/*
+ * JavaScript MD5 1.1.0
+ * https://github.com/blueimp/JavaScript-MD5
+ *
+ * Copyright 2011, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ *
+ * Based on
+ * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
+ * Digest Algorithm, as defined in RFC 1321.
+ * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for more info.
+ */
+
+/*jslint bitwise: true */
+/*global unescape, define */
+
+(function ($) {
+ 'use strict';
+
+ /*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+ function safe_add(x, y) {
+ var lsw = (x & 0xFFFF) + (y & 0xFFFF),
+ msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+ return (msw << 16) | (lsw & 0xFFFF);
+ }
+
+ /*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+ function bit_rol(num, cnt) {
+ return (num << cnt) | (num >>> (32 - cnt));
+ }
+
+ /*
+ * These functions implement the four basic operations the algorithm uses.
+ */
+ function md5_cmn(q, a, b, x, s, t) {
+ return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b);
+ }
+ function md5_ff(a, b, c, d, x, s, t) {
+ return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
+ }
+ function md5_gg(a, b, c, d, x, s, t) {
+ return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
+ }
+ function md5_hh(a, b, c, d, x, s, t) {
+ return md5_cmn(b ^ c ^ d, a, b, x, s, t);
+ }
+ function md5_ii(a, b, c, d, x, s, t) {
+ return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
+ }
+
+ /*
+ * Calculate the MD5 of an array of little-endian words, and a bit length.
+ */
+ function binl_md5(x, len) {
+ /* append padding */
+ x[len >> 5] |= 0x80 << (len % 32);
+ x[(((len + 64) >>> 9) << 4) + 14] = len;
+
+ var i, olda, oldb, oldc, oldd,
+ a = 1732584193,
+ b = -271733879,
+ c = -1732584194,
+ d = 271733878;
+
+ for (i = 0; i < x.length; i += 16) {
+ olda = a;
+ oldb = b;
+ oldc = c;
+ oldd = d;
+
+ a = md5_ff(a, b, c, d, x[i], 7, -680876936);
+ d = md5_ff(d, a, b, c, x[i + 1], 12, -389564586);
+ c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819);
+ b = md5_ff(b, c, d, a, x[i + 3], 22, -1044525330);
+ a = md5_ff(a, b, c, d, x[i + 4], 7, -176418897);
+ d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426);
+ c = md5_ff(c, d, a, b, x[i + 6], 17, -1473231341);
+ b = md5_ff(b, c, d, a, x[i + 7], 22, -45705983);
+ a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416);
+ d = md5_ff(d, a, b, c, x[i + 9], 12, -1958414417);
+ c = md5_ff(c, d, a, b, x[i + 10], 17, -42063);
+ b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162);
+ a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682);
+ d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101);
+ c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290);
+ b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329);
+
+ a = md5_gg(a, b, c, d, x[i + 1], 5, -165796510);
+ d = md5_gg(d, a, b, c, x[i + 6], 9, -1069501632);
+ c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713);
+ b = md5_gg(b, c, d, a, x[i], 20, -373897302);
+ a = md5_gg(a, b, c, d, x[i + 5], 5, -701558691);
+ d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083);
+ c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335);
+ b = md5_gg(b, c, d, a, x[i + 4], 20, -405537848);
+ a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438);
+ d = md5_gg(d, a, b, c, x[i + 14], 9, -1019803690);
+ c = md5_gg(c, d, a, b, x[i + 3], 14, -187363961);
+ b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501);
+ a = md5_gg(a, b, c, d, x[i + 13], 5, -1444681467);
+ d = md5_gg(d, a, b, c, x[i + 2], 9, -51403784);
+ c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473);
+ b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734);
+
+ a = md5_hh(a, b, c, d, x[i + 5], 4, -378558);
+ d = md5_hh(d, a, b, c, x[i + 8], 11, -2022574463);
+ c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562);
+ b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556);
+ a = md5_hh(a, b, c, d, x[i + 1], 4, -1530992060);
+ d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353);
+ c = md5_hh(c, d, a, b, x[i + 7], 16, -155497632);
+ b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640);
+ a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174);
+ d = md5_hh(d, a, b, c, x[i], 11, -358537222);
+ c = md5_hh(c, d, a, b, x[i + 3], 16, -722521979);
+ b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189);
+ a = md5_hh(a, b, c, d, x[i + 9], 4, -640364487);
+ d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835);
+ c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520);
+ b = md5_hh(b, c, d, a, x[i + 2], 23, -995338651);
+
+ a = md5_ii(a, b, c, d, x[i], 6, -198630844);
+ d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415);
+ c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905);
+ b = md5_ii(b, c, d, a, x[i + 5], 21, -57434055);
+ a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571);
+ d = md5_ii(d, a, b, c, x[i + 3], 10, -1894986606);
+ c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523);
+ b = md5_ii(b, c, d, a, x[i + 1], 21, -2054922799);
+ a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359);
+ d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744);
+ c = md5_ii(c, d, a, b, x[i + 6], 15, -1560198380);
+ b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649);
+ a = md5_ii(a, b, c, d, x[i + 4], 6, -145523070);
+ d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379);
+ c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259);
+ b = md5_ii(b, c, d, a, x[i + 9], 21, -343485551);
+
+ a = safe_add(a, olda);
+ b = safe_add(b, oldb);
+ c = safe_add(c, oldc);
+ d = safe_add(d, oldd);
+ }
+ return [a, b, c, d];
+ }
+
+ /*
+ * Convert an array of little-endian words to a string
+ */
+ function binl2rstr(input) {
+ var i,
+ output = '';
+ for (i = 0; i < input.length * 32; i += 8) {
+ output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xFF);
+ }
+ return output;
+ }
+
+ /*
+ * Convert a raw string to an array of little-endian words
+ * Characters >255 have their high-byte silently ignored.
+ */
+ function rstr2binl(input) {
+ var i,
+ output = [];
+ output[(input.length >> 2) - 1] = undefined;
+ for (i = 0; i < output.length; i += 1) {
+ output[i] = 0;
+ }
+ for (i = 0; i < input.length * 8; i += 8) {
+ output[i >> 5] |= (input.charCodeAt(i / 8) & 0xFF) << (i % 32);
+ }
+ return output;
+ }
+
+ /*
+ * Calculate the MD5 of a raw string
+ */
+ function rstr_md5(s) {
+ return binl2rstr(binl_md5(rstr2binl(s), s.length * 8));
+ }
+
+ /*
+ * Calculate the HMAC-MD5, of a key and some data (raw strings)
+ */
+ function rstr_hmac_md5(key, data) {
+ var i,
+ bkey = rstr2binl(key),
+ ipad = [],
+ opad = [],
+ hash;
+ ipad[15] = opad[15] = undefined;
+ if (bkey.length > 16) {
+ bkey = binl_md5(bkey, key.length * 8);
+ }
+ for (i = 0; i < 16; i += 1) {
+ ipad[i] = bkey[i] ^ 0x36363636;
+ opad[i] = bkey[i] ^ 0x5C5C5C5C;
+ }
+ hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
+ return binl2rstr(binl_md5(opad.concat(hash), 512 + 128));
+ }
+
+ /*
+ * Convert a raw string to a hex string
+ */
+ function rstr2hex(input) {
+ var hex_tab = '0123456789abcdef',
+ output = '',
+ x,
+ i;
+ for (i = 0; i < input.length; i += 1) {
+ x = input.charCodeAt(i);
+ output += hex_tab.charAt((x >>> 4) & 0x0F) +
+ hex_tab.charAt(x & 0x0F);
+ }
+ return output;
+ }
+
+ /*
+ * Encode a string as utf-8
+ */
+ function str2rstr_utf8(input) {
+ return unescape(encodeURIComponent(input));
+ }
+
+ /*
+ * Take string arguments and return either raw or hex encoded strings
+ */
+ function raw_md5(s) {
+ return rstr_md5(str2rstr_utf8(s));
+ }
+ function hex_md5(s) {
+ return rstr2hex(raw_md5(s));
+ }
+ function raw_hmac_md5(k, d) {
+ return rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d));
+ }
+ function hex_hmac_md5(k, d) {
+ return rstr2hex(raw_hmac_md5(k, d));
+ }
+
+ function md5(string, key, raw) {
+ if (!key) {
+ if (!raw) {
+ return hex_md5(string);
+ }
+ return raw_md5(string);
+ }
+ if (!raw) {
+ return hex_hmac_md5(key, string);
+ }
+ return raw_hmac_md5(key, string);
+ }
+
+ if (typeof define === 'function' && define.amd) {
+ define(function () {
+ return md5;
+ });
+ } else {
+ $.md5 = md5;
+ }
+}(this));
+
diff --git a/www/wiki/vendor/onoi/shared-resources/res/md5/md5.js b/www/wiki/vendor/onoi/shared-resources/res/md5/md5.js
new file mode 100644
index 00000000..4be80f81
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/md5/md5.js
@@ -0,0 +1,279 @@
+/*
+ * JavaScript MD5
+ * https://github.com/blueimp/JavaScript-MD5
+ *
+ * Copyright 2011, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ *
+ * Based on
+ * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
+ * Digest Algorithm, as defined in RFC 1321.
+ * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for more info.
+ */
+
+/*global unescape, define, module */
+
+;(function ($) {
+ 'use strict'
+
+ /*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+ function safe_add (x, y) {
+ var lsw = (x & 0xFFFF) + (y & 0xFFFF)
+ var msw = (x >> 16) + (y >> 16) + (lsw >> 16)
+ return (msw << 16) | (lsw & 0xFFFF)
+ }
+
+ /*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+ function bit_rol (num, cnt) {
+ return (num << cnt) | (num >>> (32 - cnt))
+ }
+
+ /*
+ * These functions implement the four basic operations the algorithm uses.
+ */
+ function md5_cmn (q, a, b, x, s, t) {
+ return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b)
+ }
+ function md5_ff (a, b, c, d, x, s, t) {
+ return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t)
+ }
+ function md5_gg (a, b, c, d, x, s, t) {
+ return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t)
+ }
+ function md5_hh (a, b, c, d, x, s, t) {
+ return md5_cmn(b ^ c ^ d, a, b, x, s, t)
+ }
+ function md5_ii (a, b, c, d, x, s, t) {
+ return md5_cmn(c ^ (b | (~d)), a, b, x, s, t)
+ }
+
+ /*
+ * Calculate the MD5 of an array of little-endian words, and a bit length.
+ */
+ function binl_md5 (x, len) {
+ /* append padding */
+ x[len >> 5] |= 0x80 << (len % 32)
+ x[(((len + 64) >>> 9) << 4) + 14] = len
+
+ var i
+ var olda
+ var oldb
+ var oldc
+ var oldd
+ var a = 1732584193
+ var b = -271733879
+ var c = -1732584194
+ var d = 271733878
+
+ for (i = 0; i < x.length; i += 16) {
+ olda = a
+ oldb = b
+ oldc = c
+ oldd = d
+
+ a = md5_ff(a, b, c, d, x[i], 7, -680876936)
+ d = md5_ff(d, a, b, c, x[i + 1], 12, -389564586)
+ c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819)
+ b = md5_ff(b, c, d, a, x[i + 3], 22, -1044525330)
+ a = md5_ff(a, b, c, d, x[i + 4], 7, -176418897)
+ d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426)
+ c = md5_ff(c, d, a, b, x[i + 6], 17, -1473231341)
+ b = md5_ff(b, c, d, a, x[i + 7], 22, -45705983)
+ a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416)
+ d = md5_ff(d, a, b, c, x[i + 9], 12, -1958414417)
+ c = md5_ff(c, d, a, b, x[i + 10], 17, -42063)
+ b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162)
+ a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682)
+ d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101)
+ c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290)
+ b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329)
+
+ a = md5_gg(a, b, c, d, x[i + 1], 5, -165796510)
+ d = md5_gg(d, a, b, c, x[i + 6], 9, -1069501632)
+ c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713)
+ b = md5_gg(b, c, d, a, x[i], 20, -373897302)
+ a = md5_gg(a, b, c, d, x[i + 5], 5, -701558691)
+ d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083)
+ c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335)
+ b = md5_gg(b, c, d, a, x[i + 4], 20, -405537848)
+ a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438)
+ d = md5_gg(d, a, b, c, x[i + 14], 9, -1019803690)
+ c = md5_gg(c, d, a, b, x[i + 3], 14, -187363961)
+ b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501)
+ a = md5_gg(a, b, c, d, x[i + 13], 5, -1444681467)
+ d = md5_gg(d, a, b, c, x[i + 2], 9, -51403784)
+ c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473)
+ b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734)
+
+ a = md5_hh(a, b, c, d, x[i + 5], 4, -378558)
+ d = md5_hh(d, a, b, c, x[i + 8], 11, -2022574463)
+ c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562)
+ b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556)
+ a = md5_hh(a, b, c, d, x[i + 1], 4, -1530992060)
+ d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353)
+ c = md5_hh(c, d, a, b, x[i + 7], 16, -155497632)
+ b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640)
+ a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174)
+ d = md5_hh(d, a, b, c, x[i], 11, -358537222)
+ c = md5_hh(c, d, a, b, x[i + 3], 16, -722521979)
+ b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189)
+ a = md5_hh(a, b, c, d, x[i + 9], 4, -640364487)
+ d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835)
+ c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520)
+ b = md5_hh(b, c, d, a, x[i + 2], 23, -995338651)
+
+ a = md5_ii(a, b, c, d, x[i], 6, -198630844)
+ d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415)
+ c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905)
+ b = md5_ii(b, c, d, a, x[i + 5], 21, -57434055)
+ a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571)
+ d = md5_ii(d, a, b, c, x[i + 3], 10, -1894986606)
+ c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523)
+ b = md5_ii(b, c, d, a, x[i + 1], 21, -2054922799)
+ a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359)
+ d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744)
+ c = md5_ii(c, d, a, b, x[i + 6], 15, -1560198380)
+ b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649)
+ a = md5_ii(a, b, c, d, x[i + 4], 6, -145523070)
+ d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379)
+ c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259)
+ b = md5_ii(b, c, d, a, x[i + 9], 21, -343485551)
+
+ a = safe_add(a, olda)
+ b = safe_add(b, oldb)
+ c = safe_add(c, oldc)
+ d = safe_add(d, oldd)
+ }
+ return [a, b, c, d]
+ }
+
+ /*
+ * Convert an array of little-endian words to a string
+ */
+ function binl2rstr (input) {
+ var i
+ var output = ''
+ for (i = 0; i < input.length * 32; i += 8) {
+ output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xFF)
+ }
+ return output
+ }
+
+ /*
+ * Convert a raw string to an array of little-endian words
+ * Characters >255 have their high-byte silently ignored.
+ */
+ function rstr2binl (input) {
+ var i
+ var output = []
+ output[(input.length >> 2) - 1] = undefined
+ for (i = 0; i < output.length; i += 1) {
+ output[i] = 0
+ }
+ for (i = 0; i < input.length * 8; i += 8) {
+ output[i >> 5] |= (input.charCodeAt(i / 8) & 0xFF) << (i % 32)
+ }
+ return output
+ }
+
+ /*
+ * Calculate the MD5 of a raw string
+ */
+ function rstr_md5 (s) {
+ return binl2rstr(binl_md5(rstr2binl(s), s.length * 8))
+ }
+
+ /*
+ * Calculate the HMAC-MD5, of a key and some data (raw strings)
+ */
+ function rstr_hmac_md5 (key, data) {
+ var i
+ var bkey = rstr2binl(key)
+ var ipad = []
+ var opad = []
+ var hash
+ ipad[15] = opad[15] = undefined
+ if (bkey.length > 16) {
+ bkey = binl_md5(bkey, key.length * 8)
+ }
+ for (i = 0; i < 16; i += 1) {
+ ipad[i] = bkey[i] ^ 0x36363636
+ opad[i] = bkey[i] ^ 0x5C5C5C5C
+ }
+ hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8)
+ return binl2rstr(binl_md5(opad.concat(hash), 512 + 128))
+ }
+
+ /*
+ * Convert a raw string to a hex string
+ */
+ function rstr2hex (input) {
+ var hex_tab = '0123456789abcdef'
+ var output = ''
+ var x
+ var i
+ for (i = 0; i < input.length; i += 1) {
+ x = input.charCodeAt(i)
+ output += hex_tab.charAt((x >>> 4) & 0x0F) +
+ hex_tab.charAt(x & 0x0F)
+ }
+ return output
+ }
+
+ /*
+ * Encode a string as utf-8
+ */
+ function str2rstr_utf8 (input) {
+ return unescape(encodeURIComponent(input))
+ }
+
+ /*
+ * Take string arguments and return either raw or hex encoded strings
+ */
+ function raw_md5 (s) {
+ return rstr_md5(str2rstr_utf8(s))
+ }
+ function hex_md5 (s) {
+ return rstr2hex(raw_md5(s))
+ }
+ function raw_hmac_md5 (k, d) {
+ return rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))
+ }
+ function hex_hmac_md5 (k, d) {
+ return rstr2hex(raw_hmac_md5(k, d))
+ }
+
+ function md5 (string, key, raw) {
+ if (!key) {
+ if (!raw) {
+ return hex_md5(string)
+ }
+ return raw_md5(string)
+ }
+ if (!raw) {
+ return hex_hmac_md5(key, string)
+ }
+ return raw_hmac_md5(key, string)
+ }
+
+ if (typeof define === 'function' && define.amd) {
+ define(function () {
+ return md5
+ })
+ } else if (typeof module === 'object' && module.exports) {
+ module.exports = md5
+ } else {
+ $.md5 = md5
+ }
+}(this))
diff --git a/www/wiki/vendor/onoi/shared-resources/res/onoi.blobstore.js b/www/wiki/vendor/onoi/shared-resources/res/onoi.blobstore.js
new file mode 100644
index 00000000..db88275c
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/onoi.blobstore.js
@@ -0,0 +1,173 @@
+/**
+ * Simple storage engine with time eviction
+ *
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+
+/*global jQuery */
+/*jslint white: true */
+
+( function( $ ) {
+
+ 'use strict';
+
+ /**
+ * @since 1.0
+ * @constructor
+ *
+ * @param namespace {string}
+ * @param description {string}
+ */
+ var FORAGE = function ( namespace, description ) {
+
+ this.exception = function StorageException( message ) {
+ this.message = message;
+ this.name = "StorageException";
+ }
+
+ if ( namespace === undefined || namespace.indexOf( ':' ) < 0 ) {
+ throw new this.exception( "Invalid storage name ('" + namespace + "'), expected a `:`." );
+ };
+
+ var array = namespace.split( ':' );
+
+ var config ={
+ name: array.shift(),
+ storeName: array.join( ':' ),
+ description : description
+ };
+
+ this.namespace = namespace;
+ this.store = typeof( localforage ) !== "undefined" ? localforage.createInstance( config ) : null;
+ };
+
+ /**
+ * @since 1.0
+ *
+ * @param key {string}
+ * @param value {string}
+ * @param ttl {integer}
+ *
+ * @return boolean
+ */
+ FORAGE.prototype.set = function( key, value, ttl ) {
+
+ if( this.store === null ) {
+ return false;
+ }
+
+ var now = new Date(),
+ item = {
+ ttl : ( ttl * 1000 ) || 0, // in seconds
+ time : now.getTime(),
+ value : value
+ };
+
+ this.store.setItem( key, item , function( err, value ) {} );
+ };
+
+ /**
+ * @since 1.0
+ *
+ * @param key {string}
+ * @param callback {Object}
+ *
+ * @return null|mixed
+ */
+ FORAGE.prototype.get = function( key, callback ) {
+
+ if ( typeof callback !== "function" ) {
+ throw new this.exception( "Expected a function as callback." );
+ };
+
+ var store = this.store;
+
+ if( store === null ) {
+ return callback( null );
+ }
+
+ store.getItem( key, function( err, value ) {
+
+ var item = value,
+ now = new Date();
+
+ if ( item === null ) {
+ return callback( null );
+ };
+
+ if ( item.ttl && item.ttl + item.time < now.getTime() ) {
+ store.removeItem( key );
+ return callback( null );
+ }
+
+ callback( item.value );
+ } );
+
+ return null;
+ };
+
+ /**
+ * @since 1.0
+ * @constructor
+ *
+ * @param namespace {string}
+ * @param engine {string}
+ * @param description {string}
+ *
+ * @return {this}
+ */
+ var blobstore = function ( namespace, engine, description ) {
+
+ this.VERSION = 1;
+
+ this.namespace = namespace;
+ this.engine = engine;
+
+ if ( this.engine === '' || this.engine === undefined ) {
+ this.engine = new FORAGE( namespace, ( description === undefined ? '' : description ) );
+ }
+
+ return this;
+ };
+
+ /**
+ * @since 1.0
+ *
+ * @param key {string}
+ * @param value {string}
+ * @param ttl {integer}
+ */
+ blobstore.prototype.getNamespace = function() {
+ return this.namespace;
+ };
+
+ /**
+ * @since 1.0
+ *
+ * @param key {string}
+ * @param value {string}
+ * @param ttl {integer}
+ */
+ blobstore.prototype.set = function( key, value, ttl ) {
+ this.engine.set( key, value, ttl );
+ };
+
+ /**
+ * @since 1.0
+ *
+ * @param key {string}
+ * @param callback {Object}
+ *
+ * @return null|mixed
+ */
+ blobstore.prototype.get = function( key, callback ) {
+ return this.engine.get( key, callback );
+ };
+
+ window.onoi = window.onoi || {};
+ window.onoi.blobstore = blobstore;
+
+}( jQuery ) );
diff --git a/www/wiki/vendor/onoi/shared-resources/res/onoi.clipboard.js b/www/wiki/vendor/onoi/shared-resources/res/onoi.clipboard.js
new file mode 100644
index 00000000..299e58ad
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/onoi.clipboard.js
@@ -0,0 +1,29 @@
+/**
+ * @license GNU GPL v2+
+ * @since 0.3
+ *
+ * @author mwjames
+ */
+
+/*global jQuery */
+/*jslint white: true */
+
+( function( $ ) {
+
+ 'use strict';
+
+ /**
+ * @since 0.3
+ */
+ $( document ).ready( function() {
+
+ // `data-onoi-clipboard-field` defines the field which holds the text value
+ new Clipboard( '.clipboard', {
+ text: function( trigger ) {
+ return trigger.getAttribute( trigger.getAttribute( 'data-onoi-clipboard-field' ) );
+ }
+ } );
+
+ } );
+
+}( jQuery ) );
diff --git a/www/wiki/vendor/onoi/shared-resources/res/onoi.util.css b/www/wiki/vendor/onoi/shared-resources/res/onoi.util.css
new file mode 100644
index 00000000..656704ed
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/onoi.util.css
@@ -0,0 +1,21 @@
+/**
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+.onoi-loading-image-dots {
+ background:
+ url('data:image/gif;base64,R0lGODlhIgAUAMQRAOjp6dTW19ze3+Xm54iKjevs7IKEh/f396Olp5ianPz8/Judn9fY2YGDhp+ho4CChdHT1P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/wtYTVAgRGF0YVhNUDw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDoxRThFNDZFQ0RFQjJFMjExQUZCNUREQjU1MEFCRTI5OCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDozNkVFNURCOUIyREYxMUUyQTBEQUNFNUIzREVGNjg0MyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozNkVFNURCOEIyREYxMUUyQTBEQUNFNUIzREVGNjg0MyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyMDhFNDZFQ0RFQjJFMjExQUZCNUREQjU1MEFCRTI5OCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoxRThFNDZFQ0RFQjJFMjExQUZCNUREQjU1MEFCRTI5OCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAkyABEALAAAAAAiABQAAAVeYCSOZGmeaKqubOu+cCzPdM0iBIGYgCAUPB8Q5XgYHw4SAMKEAJRNJ8pwfBhIgagAq0U1qg1ukyFmkk+L6oI0iD5H7ebbdEgYEweSYhAIDEp7fX82hIWGh4iJiosoIQAh+QQJMgARACwAAAAAIgAUAAAFZGAkjmRpnmiqrmzrvnAsz3TNFoIAmAhBICZArpACQI6Q3cjxaD4cJCNSaRIgIQGSwfkwkAJXAYpxzY4a3Mb3ykBJjwPSgrsgDa5U0yAQGChIBwlNCQckCnt9NoqLjI2Oj5CRKSEAIfkEBTIAEQAsAAAAACIAFAAABWNgJI5kaZ5oqq5s675wLM90zRaCAJi4biIEAiIFgBghu1HxmBQ5HtCHAyU4QgKk6hE7MkQfBhTDyhWNt6TGt4FaGgckNwQ+WnwXqUEgMFCU9Hx+IwcJUAkHNomKi4yNjo+QKCEAOw==')
+ no-repeat
+ left center;
+ padding: 5px 0 5px 35px;
+}
+
+.onoi-loading-image-circle {
+ background:
+ url('data:image/gif;base64,R0lGODlhEAAQAPQAAP///wAAAPDw8IqKiuDg4EZGRnp6egAAAFhYWCQkJKysrL6+vhQUFJycnAQEBDY2NmhoaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAAFdyAgAgIJIeWoAkRCCMdBkKtIHIngyMKsErPBYbADpkSCwhDmQCBethRB6Vj4kFCkQPG4IlWDgrNRIwnO4UKBXDufzQvDMaoSDBgFb886MiQadgNABAokfCwzBA8LCg0Egl8jAggGAA1kBIA1BAYzlyILczULC2UhACH5BAkKAAAALAAAAAAQABAAAAV2ICACAmlAZTmOREEIyUEQjLKKxPHADhEvqxlgcGgkGI1DYSVAIAWMx+lwSKkICJ0QsHi9RgKBwnVTiRQQgwF4I4UFDQQEwi6/3YSGWRRmjhEETAJfIgMFCnAKM0KDV4EEEAQLiF18TAYNXDaSe3x6mjidN1s3IQAh+QQJCgAAACwAAAAAEAAQAAAFeCAgAgLZDGU5jgRECEUiCI+yioSDwDJyLKsXoHFQxBSHAoAAFBhqtMJg8DgQBgfrEsJAEAg4YhZIEiwgKtHiMBgtpg3wbUZXGO7kOb1MUKRFMysCChAoggJCIg0GC2aNe4gqQldfL4l/Ag1AXySJgn5LcoE3QXI3IQAh+QQJCgAAACwAAAAAEAAQAAAFdiAgAgLZNGU5joQhCEjxIssqEo8bC9BRjy9Ag7GILQ4QEoE0gBAEBcOpcBA0DoxSK/e8LRIHn+i1cK0IyKdg0VAoljYIg+GgnRrwVS/8IAkICyosBIQpBAMoKy9dImxPhS+GKkFrkX+TigtLlIyKXUF+NjagNiEAIfkECQoAAAAsAAAAABAAEAAABWwgIAICaRhlOY4EIgjH8R7LKhKHGwsMvb4AAy3WODBIBBKCsYA9TjuhDNDKEVSERezQEL0WrhXucRUQGuik7bFlngzqVW9LMl9XWvLdjFaJtDFqZ1cEZUB0dUgvL3dgP4WJZn4jkomWNpSTIyEAIfkECQoAAAAsAAAAABAAEAAABX4gIAICuSxlOY6CIgiD8RrEKgqGOwxwUrMlAoSwIzAGpJpgoSDAGifDY5kopBYDlEpAQBwevxfBtRIUGi8xwWkDNBCIwmC9Vq0aiQQDQuK+VgQPDXV9hCJjBwcFYU5pLwwHXQcMKSmNLQcIAExlbH8JBwttaX0ABAcNbWVbKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICSRBlOY7CIghN8zbEKsKoIjdFzZaEgUBHKChMJtRwcWpAWoWnifm6ESAMhO8lQK0EEAV3rFopIBCEcGwDKAqPh4HUrY4ICHH1dSoTFgcHUiZjBhAJB2AHDykpKAwHAwdzf19KkASIPl9cDgcnDkdtNwiMJCshACH5BAkKAAAALAAAAAAQABAAAAV3ICACAkkQZTmOAiosiyAoxCq+KPxCNVsSMRgBsiClWrLTSWFoIQZHl6pleBh6suxKMIhlvzbAwkBWfFWrBQTxNLq2RG2yhSUkDs2b63AYDAoJXAcFRwADeAkJDX0AQCsEfAQMDAIPBz0rCgcxky0JRWE1AmwpKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICKZzkqJ4nQZxLqZKv4NqNLKK2/Q4Ek4lFXChsg5ypJjs1II3gEDUSRInEGYAw6B6zM4JhrDAtEosVkLUtHA7RHaHAGJQEjsODcEg0FBAFVgkQJQ1pAwcDDw8KcFtSInwJAowCCA6RIwqZAgkPNgVpWndjdyohACH5BAkKAAAALAAAAAAQABAAAAV5ICACAimc5KieLEuUKvm2xAKLqDCfC2GaO9eL0LABWTiBYmA06W6kHgvCqEJiAIJiu3gcvgUsscHUERm+kaCxyxa+zRPk0SgJEgfIvbAdIAQLCAYlCj4DBw0IBQsMCjIqBAcPAooCBg9pKgsJLwUFOhCZKyQDA3YqIQAh+QQJCgAAACwAAAAAEAAQAAAFdSAgAgIpnOSonmxbqiThCrJKEHFbo8JxDDOZYFFb+A41E4H4OhkOipXwBElYITDAckFEOBgMQ3arkMkUBdxIUGZpEb7kaQBRlASPg0FQQHAbEEMGDSVEAA1QBhAED1E0NgwFAooCDWljaQIQCE5qMHcNhCkjIQAh+QQJCgAAACwAAAAAEAAQAAAFeSAgAgIpnOSoLgxxvqgKLEcCC65KEAByKK8cSpA4DAiHQ/DkKhGKh4ZCtCyZGo6F6iYYPAqFgYy02xkSaLEMV34tELyRYNEsCQyHlvWkGCzsPgMCEAY7Cg04Uk48LAsDhRA8MVQPEF0GAgqYYwSRlycNcWskCkApIyEAOwAAAAAAAAAAAA==')
+ no-repeat
+ left center;
+ padding: 15px 0 15px 15px;
+}
diff --git a/www/wiki/vendor/onoi/shared-resources/res/onoi.util.js b/www/wiki/vendor/onoi/shared-resources/res/onoi.util.js
new file mode 100644
index 00000000..6de7be5b
--- /dev/null
+++ b/www/wiki/vendor/onoi/shared-resources/res/onoi.util.js
@@ -0,0 +1,98 @@
+/**
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+
+/*global jQuery */
+/*jslint white: true */
+
+( function( $ ) {
+
+ 'use strict';
+
+ /**
+ * @since 0.2
+ * @constructor
+ *
+ * @return {this}
+ */
+ var util = function () {
+ this.VERSION = '0.2';
+ return this;
+ };
+
+ /**
+ * @since 0.2
+ * @method
+ *
+ * @param {string} classReplacement
+ * @param {string} type
+ *
+ * @return {string}
+ */
+ util.prototype.getLoadingImg = function( classReplacement, type ) {
+
+ if ( type === undefined ) {
+ type = circle;
+ }
+
+ var element = '<div class="class-replacement"><span class="class-replacement-loading onoi-loading-image-' + type + '" alt="Loading..." /></span>';
+
+ return element.replace( /class-replacement/g, ( classReplacement === undefined ? 'onoi' : classReplacement ) );
+ };
+
+ /**
+ * @since 0.2
+ * @method
+ *
+ * @param {string} value
+ *
+ * @return {string}
+ */
+ util.prototype.md5 = function( value ) {
+ return md5( value );
+ };
+
+ /**
+ * @since 0.2
+ * @method
+ * @credit http://www.abeautifulsite.net/parsing-urls-in-javascript/
+ *
+ * @param {string} url
+ *
+ * @return {Object}
+ */
+ util.prototype.parseURL = function ( url ) {
+
+ var parser = document.createElement('a'),
+ searchObject = {},
+ queries, split, i;
+
+ // Let the browser do the work
+ parser.href = url;
+
+ // Convert query string to object
+ queries = parser.search.replace(/^\?/, '').split('&');
+ for( i = 0; i < queries.length; i++ ) {
+ split = queries[i].split('=');
+ searchObject[split[0]] = split[1];
+ }
+
+ return {
+ protocol: parser.protocol,
+ host: parser.host,
+ hostname: parser.hostname,
+ port: parser.port,
+ pathname: parser.pathname,
+ search: parser.search,
+ searchObject: searchObject,
+ hash: parser.hash
+ };
+ }
+
+ window.onoi = window.onoi || {};
+ window.onoi.util = util;
+
+}( jQuery ) );
diff --git a/www/wiki/vendor/onoi/tesa/.gitignore b/www/wiki/vendor/onoi/tesa/.gitignore
new file mode 100644
index 00000000..09ec1c53
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/.gitignore
@@ -0,0 +1,10 @@
+*~
+*.kate-swp
+
+!.*
+.idea/
+
+composer.phar
+composer.lock
+
+vendor/ \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/tesa/.scrutinizer.yml b/www/wiki/vendor/onoi/tesa/.scrutinizer.yml
new file mode 100644
index 00000000..dfdcb235
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/.scrutinizer.yml
@@ -0,0 +1,18 @@
+filter:
+ excluded_paths:
+ - 'vendor/*'
+
+tools:
+ php_mess_detector:
+ config:
+ controversial_rules: { superglobals: false }
+ php_cpd: true
+ php_pdepend: true
+ php_code_coverage: false
+ php_code_sniffer: true
+ php_cs_fixer: true
+ php_loc: true
+ php_analyzer: true
+ sensiolabs_security_checker: true
+ external_code_coverage:
+ timeout: 600
diff --git a/www/wiki/vendor/onoi/tesa/.travis.yml b/www/wiki/vendor/onoi/tesa/.travis.yml
new file mode 100644
index 00000000..8f5d8180
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/.travis.yml
@@ -0,0 +1,21 @@
+# http://docs.travis-ci.com/user/workers/container-based-infrastructure/
+sudo: false
+
+language: php
+
+matrix:
+ include:
+ - env: TYPE=coverage
+ php: 5.6
+ - env: TYPE=UNIT;
+ php: 5.3
+ - env: TYPE=UNIT;
+ php: hhvm
+ - env: TYPE=UNIT;
+ php: 7
+
+script:
+ - bash ./tests/travis/run-tests.sh
+
+after_success:
+ - bash ./tests/travis/upload-coverage-report.sh
diff --git a/www/wiki/vendor/onoi/tesa/LICENSE b/www/wiki/vendor/onoi/tesa/LICENSE
new file mode 100644
index 00000000..d6a93266
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/LICENSE
@@ -0,0 +1,340 @@
+GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ {description}
+ Copyright (C) {year} {fullname}
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ {signature of Ty Coon}, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
+
diff --git a/www/wiki/vendor/onoi/tesa/README.md b/www/wiki/vendor/onoi/tesa/README.md
new file mode 100644
index 00000000..ca65472f
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/README.md
@@ -0,0 +1,118 @@
+# Tesa (text sanitizer)
+
+[![Build Status](https://secure.travis-ci.org/onoi/tesa.svg?branch=master)](http://travis-ci.org/onoi/tesa)
+[![Code Coverage](https://scrutinizer-ci.com/g/onoi/tesa/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/onoi/tesa/?branch=master)
+[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/onoi/tesa/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/onoi/tesa/?branch=master)
+[![Latest Stable Version](https://poser.pugx.org/onoi/tesa/version.png)](https://packagist.org/packages/onoi/tesa)
+[![Packagist download count](https://poser.pugx.org/onoi/tesa/d/total.png)](https://packagist.org/packages/onoi/tesa)
+[![Dependency Status](https://www.versioneye.com/php/onoi:tesa/badge.png)](https://www.versioneye.com/php/onoi:tesa)
+
+The library contains a small collection of helper classes to support sanitization
+of text or string elements of arbitrary length with the aim to improve
+search match confidence during a query execution that is required by [Semantic MediaWiki][smw]
+project and is deployed independently.
+
+## Requirements
+
+- PHP 5.3 / HHVM 3.5 or later
+- Recommended to enable the [ICU][icu] extension
+
+## Installation
+
+The recommended installation method for this library is by adding
+the following dependency to your [composer.json][composer].
+
+```json
+{
+ "require": {
+ "onoi/tesa": "~0.1"
+ }
+}
+```
+
+## Usage
+
+```php
+use Onoi\Tesa\SanitizerFactory;
+use Onoi\Tesa\Transliterator;
+use Onoi\Tesa\Sanitizer;
+
+$sanitizerFactory = new SanitizerFactory();
+
+$sanitizer = $sanitizerFactory->newSanitizer( 'A string that contains ...' );
+
+$sanitizer->reduceLengthTo( 200 );
+$sanitizer->toLowercase();
+
+$sanitizer->replace(
+ array( "'", "http://", "https://", "mailto:", "tel:" ),
+ array( '' )
+);
+
+$sanitizer->setOption( Sanitizer::MIN_LENGTH, 4 );
+$sanitizer->setOption( Sanitizer::WHITELIST, array( 'that' ) );
+
+$sanitizer->applyTransliteration(
+ Transliterator::DIACRITICS | Transliterator::GREEK
+);
+
+$text = $sanitizer->sanitizeWith(
+ $sanitizerFactory->newGenericTokenizer(),
+ $sanitizerFactory->newNullStopwordAnalyzer(),
+ $sanitizerFactory->newNullSynonymizer()
+);
+
+```
+
+- `SanitizerFactory` is expected to be the sole entry point for services and instances
+ when used outside of this library
+- `IcuWordBoundaryTokenizer` is a preferred tokenizer in case the [ICU][icu] extension is available
+- `NGramTokenizer` is provided to increase CJK match confidence in case the
+ back-end does not provide an explicit ngram tokenizer
+- `StopwordAnalyzer` together with a `LanguageDetector` is provided as a means to
+ reduce ambiguity of frequent "noise" words from a possible search index
+- `Synonymizer` currently only provides an interface
+
+## Contribution and support
+
+If you want to contribute work to the project please subscribe to the
+developers mailing list and have a look at the [contribution guidelinee](/CONTRIBUTING.md). A list
+of people who have made contributions in the past can be found [here][contributors].
+
+* [File an issue](https://github.com/onoi/tesa/issues)
+* [Submit a pull request](https://github.com/onoi/tesa/pulls)
+
+## Tests
+
+The library provides unit tests that covers the core-functionality normally run by the
+[continues integration platform][travis]. Tests can also be executed manually using the
+`composer phpunit` command from the root directory.
+
+## Release notes
+
+- 0.1.0 Initial release (2016-08-07)
+ - Added `SanitizerFactory` with support for a
+ - `Tokenizer`, `LanguageDetector`, `Synonymizer`, and `StopwordAnalyzer` interface
+
+## Acknowledgments
+
+- The `Transliterator` uses the same diacritics conversion table as http://jsperf.com/latinize
+ (except the German diaeresis ä, ü, and ö)
+- The stopwords used by the `StopwordAnalyzer` have been collected from different sources, each `json`
+ file identifies its origin
+- `CdbStopwordAnalyzer` relies on `wikimedia/cdb` to avoid using an external database or cache
+ layer (with extra stopwords being available [here](https://github.com/6/stopwords-json))
+- `JaTinySegmenterTokenizer` is based on the work of Taku Kudo and his [tiny_segmenter.js](http://chasen.org/~taku/software/TinySegmenter)
+- `TextCatLanguageDetector` uses the [`wikimedia/textcat`][textcat] library to make predictions about a language
+
+## License
+
+[GNU General Public License 2.0 or later][license].
+
+[composer]: https://getcomposer.org/
+[contributors]: https://github.com/onoi/tesa/graphs/contributors
+[license]: https://www.gnu.org/copyleft/gpl.html
+[travis]: https://travis-ci.org/onoi/tesa
+[smw]: https://github.com/SemanticMediaWiki/SemanticMediaWiki/
+[icu]: http://php.net/manual/en/intro.intl.php
+[textcat]: https://github.com/wikimedia/wikimedia-textcat
diff --git a/www/wiki/vendor/onoi/tesa/composer.json b/www/wiki/vendor/onoi/tesa/composer.json
new file mode 100644
index 00000000..2461b108
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/composer.json
@@ -0,0 +1,33 @@
+{
+ "name": "onoi/tesa",
+ "type": "library",
+ "description": "A simple library to sanitize text elements",
+ "keywords": [
+ "transliteration"
+ ],
+ "homepage": "https://github.com/onoi/tesa",
+ "license": "GPL-2.0+",
+ "authors": [
+ {
+ "name": "mwjames",
+ "homepage": "https://github.com/mwjames"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.2",
+ "ext-mbstring": "*",
+ "wikimedia/cdb": "~1.0",
+ "wikimedia/textcat": "~1.1"
+ },
+ "autoload": {
+ "psr-4": {
+ "Onoi\\Tesa\\": "src/"
+ }
+ },
+ "config": {
+ "process-timeout": 0
+ },
+ "scripts":{
+ "phpunit": "phpunit -c phpunit.xml.dist"
+ }
+}
diff --git a/www/wiki/vendor/onoi/tesa/phpunit.xml.dist b/www/wiki/vendor/onoi/tesa/phpunit.xml.dist
new file mode 100644
index 00000000..6d71bd49
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/phpunit.xml.dist
@@ -0,0 +1,27 @@
+<phpunit backupGlobals="false"
+ backupStaticAttributes="false"
+ bootstrap="tests/bootstrap.php"
+ cacheTokens="false"
+ colors="true"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ stopOnError="false"
+ stopOnFailure="false"
+ stopOnIncomplete="false"
+ stopOnSkipped="false"
+ verbose="true">
+ <testsuites>
+ <testsuite name="tesa-unit">
+ <directory>tests/phpunit/Unit</directory>
+ </testsuite>
+ <testsuite name="tesa-integration">
+ <directory>tests/phpunit/Integration</directory>
+ </testsuite>
+ </testsuites>
+ <filter>
+ <whitelist addUncoveredFilesFromWhitelist="true">
+ <directory suffix=".php">src</directory>
+ </whitelist>
+ </filter>
+</phpunit>
diff --git a/www/wiki/vendor/onoi/tesa/src/CharacterExaminer.php b/www/wiki/vendor/onoi/tesa/src/CharacterExaminer.php
new file mode 100644
index 00000000..d10c140c
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/CharacterExaminer.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Onoi\Tesa;
+
+/**
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class CharacterExaminer {
+
+ const CYRILLIC = 'CYRILLIC';
+ const LATIN = 'LATIN';
+ const HIRAGANA_KATAKANA = 'HIRAGANA_KATAKANA';
+ const HANGUL = 'HANGUL';
+ const CJK_UNIFIED = 'CJK_UNIFIED';
+ const HAN = 'HAN';
+
+ /**
+ * @see http://jrgraphix.net/research/unicode_blocks.php
+ * @since 0.1
+ *
+ * @param string $type
+ * @param string $text
+ *
+ * @return boolean
+ */
+ public static function contains( $type, $text ) {
+
+ if ( $type === self::CYRILLIC ) {
+ return preg_match('/\p{Cyrillic}/u', $text ) > 0;
+ }
+
+ if ( $type === self::LATIN ) {
+ return preg_match('/\p{Latin}/u', $text ) > 0;
+ }
+
+ if ( $type === self::HAN ) {
+ return preg_match('/\p{Han}/u', $text ) > 0;
+ }
+
+ if ( $type === self::HIRAGANA_KATAKANA ) {
+ return preg_match('/[\x{3040}-\x{309F}]/u', $text ) > 0 || preg_match('/[\x{30A0}-\x{30FF}]/u', $text ) > 0; // isHiragana || isKatakana
+ }
+
+ if ( $type === self::HANGUL ) {
+ return preg_match('/[\x{3130}-\x{318F}]/u', $text ) > 0 || preg_match('/[\x{AC00}-\x{D7AF}]/u', $text ) > 0;
+ }
+
+ // @see https://en.wikipedia.org/wiki/CJK_Unified_Ideographs
+ // Chinese, Japanese and Korean (CJK) scripts share common characters
+ // known as CJK characters
+
+ if ( $type === self::CJK_UNIFIED ) {
+ return preg_match('/[\x{4e00}-\x{9fa5}]/u', $text ) > 0;
+ }
+
+ return false;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/src/LanguageDetector/LanguageDetector.php b/www/wiki/vendor/onoi/tesa/src/LanguageDetector/LanguageDetector.php
new file mode 100644
index 00000000..fdbf5999
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/LanguageDetector/LanguageDetector.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Onoi\Tesa\LanguageDetector;
+
+/**
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+interface LanguageDetector {
+
+ /**
+ * @since 0.1
+ *
+ * @param string $text
+ *
+ * @return string|null
+ */
+ public function detect( $text );
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/src/LanguageDetector/NullLanguageDetector.php b/www/wiki/vendor/onoi/tesa/src/LanguageDetector/NullLanguageDetector.php
new file mode 100644
index 00000000..ac156306
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/LanguageDetector/NullLanguageDetector.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Onoi\Tesa\LanguageDetector;
+
+/**
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class NullLanguageDetector implements LanguageDetector {
+
+ /**
+ * @since 0.1
+ *
+ * @param string $word
+ *
+ * @return null
+ */
+ public function detect( $text ) {
+ return null;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/src/LanguageDetector/TextCatLanguageDetector.php b/www/wiki/vendor/onoi/tesa/src/LanguageDetector/TextCatLanguageDetector.php
new file mode 100644
index 00000000..69affad4
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/LanguageDetector/TextCatLanguageDetector.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Onoi\Tesa\LanguageDetector;
+
+use TextCat;
+
+/**
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class TextCatLanguageDetector implements LanguageDetector {
+
+ const VERION = '0.1';
+
+ /**
+ * @var TextCat
+ */
+ private $textCat;
+
+ /**
+ * @var array|null
+ */
+ private $languageCandidates = null;
+
+ /**
+ * @since 0.1
+ *
+ * @param TextCat|null $textCat
+ */
+ public function __construct( TextCat $textCat = null ) {
+ $this->textCat = $textCat;
+
+ if ( $this->textCat === null ) {
+ $this->textCat = new TextCat();
+ }
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @param array $languageCandidates
+ */
+ public function setLanguageCandidates( array $languageCandidates ) {
+ $this->languageCandidates = $languageCandidates;
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @param string $text
+ *
+ * @return string|null
+ */
+ public function detect( $text ) {
+
+ $languages = $this->textCat->classify( $text, $this->languageCandidates );
+ reset( $languages );
+
+ // For now, only return the best match
+ return key( $languages );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/src/Normalizer.php b/www/wiki/vendor/onoi/tesa/src/Normalizer.php
new file mode 100644
index 00000000..f017d21f
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/Normalizer.php
@@ -0,0 +1,103 @@
+<?php
+
+namespace Onoi\Tesa;
+
+use Onoi\Tesa\Tokenizer\Tokenizer;
+use Onoi\Tesa\Synonymizer\Synonymizer;
+use Onoi\Tesa\StopwordAnalyzer\StopwordAnalyzer;
+use RuntimeException;
+
+/**
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class Normalizer {
+
+ /**
+ * @since 0.1
+ *
+ * @param string $text
+ *
+ * @param integer $flag
+ */
+ public static function applyTransliteration( $text, $flag = Transliterator::DIACRITICS ) {
+ return Transliterator::transliterate( $text, $flag );
+ }
+
+ /**
+ * @see Localizer::convertDoubleWidth
+ *
+ * @since 0.1
+ *
+ * @param string $text
+ *
+ * @return string
+ */
+ public static function convertDoubleWidth( $text ) {
+ static $full = null;
+ static $half = null;
+
+ //,。/?《》〈〉;:“”"〃'`[]{}\|~!-=_+)(()*…—─%¥#
+ //,./?«»();:“”
+
+ if ( $full === null ) {
+ $fullWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+ $halfWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+ // http://php.net/manual/en/function.str-split.php, mb_str_split
+ $length = mb_strlen( $fullWidth, "UTF-8" );
+ $full = array();
+
+ for ( $i = 0; $i < $length; $i += 1 ) {
+ $full[] = mb_substr( $fullWidth, $i, 1, "UTF-8" );
+ }
+
+ $half = str_split( $halfWidth );
+ }
+
+ return str_replace( $full, $half, trim( $text ) );
+ }
+
+
+ /**
+ * @since 0.1
+ *
+ * @param string $text
+ *
+ * @return string
+ */
+ public static function toLowercase( $text ) {
+ return mb_strtolower( $text, mb_detect_encoding( $text ) );
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @param string $text
+ * @param integer|null $length
+ *
+ * @return string
+ */
+ public static function reduceLengthTo( $text, $length = null ) {
+
+ if ( $length === null || mb_strlen( $text ) <= $length ) {
+ return $text;
+ }
+
+ $encoding = mb_detect_encoding( $text );
+ $lastWholeWordPosition = $length;
+
+ if ( strpos( $text, ' ' ) !== false ) {
+ $lastWholeWordPosition = strrpos( mb_substr( $text, 0, $length, $encoding ), ' ' ); // last whole word
+ }
+
+ if ( $lastWholeWordPosition > 0 ) {
+ $length = $lastWholeWordPosition;
+ }
+
+ return mb_substr( $text, 0, $length, $encoding );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/src/Sanitizer.php b/www/wiki/vendor/onoi/tesa/src/Sanitizer.php
new file mode 100644
index 00000000..0ab23c69
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/Sanitizer.php
@@ -0,0 +1,178 @@
+<?php
+
+namespace Onoi\Tesa;
+
+use Onoi\Tesa\Tokenizer\Tokenizer;
+use Onoi\Tesa\Synonymizer\Synonymizer;
+use Onoi\Tesa\StopwordAnalyzer\StopwordAnalyzer;
+use RuntimeException;
+
+/**
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class Sanitizer {
+
+ const WHITELIST = 'WHITELIST';
+ const MIN_LENGTH = 'MIN_LENGTH';
+
+ /**
+ * Any change to the content of its data files should be reflected in a
+ * version change (the version number does not necessarily correlate with
+ * the library version)
+ */
+ const VERSION = '0.2';
+
+ /**
+ * @var string
+ */
+ private $string = '';
+
+ /**
+ * @var array
+ */
+ private $whiteList = array();
+
+ /**
+ * @var array
+ */
+ private $minLength = 3;
+
+ /**
+ * @since 0.1
+ *
+ * @param string $string
+ */
+ public function __construct( $string = '' ) {
+ $this->setText( $string );
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @param string $name
+ * @param mixed $value
+ */
+ public function setOption( $name, $value ) {
+
+ if ( $name === self::WHITELIST && is_array( $value ) && $value !== array() ) {
+ $this->whiteList = array_fill_keys( $value, true );
+ }
+
+ if ( $name === self::MIN_LENGTH ) {
+ $this->minLength = (int)$value;
+ }
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @param string $string
+ */
+ public function setText( $string ) {
+ $this->string = $string;
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @param integer $flag
+ */
+ public function applyTransliteration( $flag = Transliterator::DIACRITICS ) {
+ $this->string = Normalizer::applyTransliteration( $this->string, $flag );
+ }
+
+ /**
+ * @see Localizer::convertDoubleWidth
+ *
+ * @since 0.1
+ *
+ * @param integer $flag
+ */
+ public function convertDoubleWidth() {
+ $this->string = Normalizer::convertDoubleWidth( $this->string );
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @param Tokenizer $tokenizer
+ * @param StopwordAnalyzer $stopwordAnalyzer
+ *
+ * @return string
+ */
+ public function sanitizeWith( Tokenizer $tokenizer, StopwordAnalyzer $stopwordAnalyzer, Synonymizer $synonymizer ) {
+
+ // Treat non-words tokenizers (Ja,Zh*) differently
+ $minLength = $tokenizer->isWordTokenizer() ? $this->minLength : 1;
+
+ $words = $tokenizer->tokenize( $this->string );
+
+ if ( !$words || !is_array( $words ) ) {
+ return $this->string;
+ }
+
+ $index = array();
+ $pos = 0;
+
+ foreach ( $words as $key => $word ) {
+
+ $word = $synonymizer->synonymize( $word );
+
+ // If it is not an exemption and less than the required minimum length
+ // or identified as stop word it is removed
+ if ( !isset( $this->whiteList[$word] ) && ( mb_strlen( $word ) < $minLength || $stopwordAnalyzer->isStopWord( $word ) ) ) {
+ continue;
+ }
+
+ // Simple proximity, check for same words appearing next to each other
+ if ( isset( $index[$pos-1] ) && $index[$pos-1] === $word ) {
+ continue;
+ }
+
+ $index[] = trim( $word );
+ $pos++;
+ }
+
+ return implode( ' ' , $index );
+ }
+
+ /**
+ * @since 0.1
+ */
+ public function toLowercase() {
+ $this->string = Normalizer::toLowercase( $this->string );
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @param integer $length
+ */
+ public function reduceLengthTo( $length ) {
+ $this->string = Normalizer::reduceLengthTo( $this->string, $length );
+ }
+
+ /**
+ * @see http://www.phpwact.org/php/i18n/utf-8#str_replace
+ * @since 0.1
+ *
+ * @param string $search
+ * @param string $replace
+ */
+ public function replace( $search, $replace ) {
+ $this->string = str_replace( $search, $replace, $this->string );
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @return string
+ */
+ public function __toString() {
+ return $this->string;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/src/SanitizerFactory.php b/www/wiki/vendor/onoi/tesa/src/SanitizerFactory.php
new file mode 100644
index 00000000..f2a04018
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/SanitizerFactory.php
@@ -0,0 +1,257 @@
+<?php
+
+namespace Onoi\Tesa;
+
+use Onoi\Tesa\StopwordAnalyzer\StopwordAnalyzer;
+use Onoi\Tesa\StopwordAnalyzer\NullStopwordAnalyzer;
+use Onoi\Tesa\StopwordAnalyzer\CdbStopwordAnalyzer;
+use Onoi\Tesa\StopwordAnalyzer\ArrayStopwordAnalyzer;
+use Onoi\Tesa\Synonymizer\Synonymizer;
+use Onoi\Tesa\Synonymizer\NullSynonymizer;
+use Onoi\Tesa\LanguageDetector\NullLanguageDetector;
+use Onoi\Tesa\LanguageDetector\TextCatLanguageDetector;
+use Onoi\Tesa\Tokenizer\CJKSimpleCharacterRegExTokenizer;
+use Onoi\Tesa\Tokenizer\Tokenizer;
+use Onoi\Tesa\Tokenizer\GenericRegExTokenizer;
+use Onoi\Tesa\Tokenizer\JaCompoundGroupTokenizer;
+use Onoi\Tesa\Tokenizer\IcuWordBoundaryTokenizer;
+use Onoi\Tesa\Tokenizer\NGramTokenizer;
+use Onoi\Tesa\Tokenizer\JaTinySegmenterTokenizer;
+use Onoi\Tesa\Tokenizer\PunctuationRegExTokenizer;
+
+/**
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class SanitizerFactory {
+
+ /**
+ * @since 0.1
+ *
+ * @return Sanitizer
+ */
+ public function newSanitizer( $text = '' ) {
+ return new Sanitizer( $text );
+ }
+
+ /* StopwordAnalyzer */
+
+ /**
+ * @since 0.1
+ *
+ * @param string|null $languageCode
+ *
+ * @return StopwordAnalyzer
+ */
+ public function newStopwordAnalyzerByLanguage( $languageCode = null ) {
+
+ if ( $languageCode === null ) {
+ return $this->newNullStopwordAnalyzer();
+ }
+
+ $cdbStopwordAnalyzer = $this->newCdbStopwordAnalyzer(
+ $languageCode
+ );
+
+ return $cdbStopwordAnalyzer->isAvailable() ? $cdbStopwordAnalyzer : $this->newNullStopwordAnalyzer();;
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @return StopwordAnalyzer
+ */
+ public function newCdbStopwordAnalyzer( $languageCode = null ) {
+ return new CdbStopwordAnalyzer( CdbStopwordAnalyzer::getTargetByLanguage( $languageCode ) );
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @param array $stopwords;
+ *
+ * @return StopwordAnalyzer
+ */
+ public function newArrayStopwordAnalyzer( array $stopwords = array() ) {
+ return new ArrayStopwordAnalyzer( $stopwords );
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @return StopwordAnalyzer
+ */
+ public function newNullStopwordAnalyzer() {
+ return new NullStopwordAnalyzer();
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @param string|null $languageCode
+ *
+ * @return Synonymizer
+ */
+ public function newSynonymizerByLanguage( $languageCode = null ) {
+
+ if ( $languageCode === null ) {
+ return $this->newNullSynonymizer();
+ }
+
+ return $this->newNullSynonymizer();;
+ }
+
+ /* Synonymizer */
+
+ /**
+ * @since 0.1
+ *
+ * @return Synonymizer
+ */
+ public function newNullSynonymizer() {
+ return new NullSynonymizer();
+ }
+
+ /* LanguageDetector */
+
+ /**
+ * @since 0.1
+ *
+ * @return NullLanguageDetector
+ */
+ public function newNullLanguageDetector() {
+ return new NullLanguageDetector();
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @return TextCatLanguageDetector
+ */
+ public function newTextCatLanguageDetector() {
+ return new TextCatLanguageDetector();
+ }
+
+ /* Tokenizer */
+
+ /**
+ * @since 0.1
+ *
+ * @param string $text
+ * @param string|null $languageCode
+ *
+ * @return Tokenizer
+ */
+ public function newPreferredTokenizerByLanguage( $text, $languageCode = null ) {
+
+ $tokenizer = $this->newIcuWordBoundaryTokenizer();
+
+ if ( !$tokenizer->isAvailable() && CharacterExaminer::contains( CharacterExaminer::CJK_UNIFIED, $text ) ) {
+ return $this->newCJKMatchableTokenizer( $text );
+ } elseif( !$tokenizer->isAvailable() ) {
+ return $this->newGenericRegExTokenizer( $tokenizer );
+ }
+
+ $tokenizer->setLocale( $languageCode );
+
+ $tokenizer->setWordTokenizerAttribute(
+ !CharacterExaminer::contains( CharacterExaminer::CJK_UNIFIED, $text )
+ );
+
+ return $this->newGenericRegExTokenizer( $tokenizer );
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @param string $text
+ *
+ * @return Tokenizer
+ */
+ public function newCJKMatchableTokenizer( $text ) {
+
+ $tokenizer = null;
+
+ if ( CharacterExaminer::contains( CharacterExaminer::HIRAGANA_KATAKANA, $text ) ) {
+ $tokenizer = $this->newJaTinySegmenterTokenizer();
+ } else {
+ $tokenizer = $this->newNGramTokenizer( $tokenizer );
+ }
+
+ $tokenizer = $this->newCJKSimpleCharacterRegExTokenizer( $tokenizer );
+
+ return $this->newGenericRegExTokenizer( $tokenizer );
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @param Tokenizer|null $tokenizer
+ *
+ * @return Tokenizer
+ */
+ public function newIcuWordBoundaryTokenizer( Tokenizer $tokenizer = null ) {
+ return new IcuWordBoundaryTokenizer( $tokenizer );
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @param Tokenizer|null $tokenizer
+ *
+ * @return Tokenizer
+ */
+ public function newGenericRegExTokenizer( Tokenizer $tokenizer = null ) {
+ return new GenericRegExTokenizer( $tokenizer );
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @param Tokenizer|null $tokenizer
+ *
+ * @return Tokenizer
+ */
+ public function newPunctuationRegExTokenizer( Tokenizer $tokenizer = null ) {
+ return new PunctuationRegExTokenizer( $tokenizer );
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @return Tokenizer
+ */
+ public function newJaCompoundGroupTokenizer( Tokenizer $tokinizer = null ) {
+ return new JaCompoundGroupTokenizer( $tokinizer );
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @return Tokenizer
+ */
+ public function newJaTinySegmenterTokenizer( Tokenizer $tokinizer = null ) {
+ return new JaTinySegmenterTokenizer( $tokinizer );
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @return Tokenizer
+ */
+ public function newCJKSimpleCharacterRegExTokenizer( Tokenizer $tokinizer = null ) {
+ return new CJKSimpleCharacterRegExTokenizer( $tokinizer );
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @return Tokenizer
+ */
+ public function newNGramTokenizer( Tokenizer $tokinizer = null, $ngram = 2 ) {
+ return new NGramTokenizer( $tokinizer, $ngram );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/ArrayStopwordAnalyzer.php b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/ArrayStopwordAnalyzer.php
new file mode 100644
index 00000000..ca85ce81
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/ArrayStopwordAnalyzer.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Onoi\Tesa\StopwordAnalyzer;
+
+/**
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class ArrayStopwordAnalyzer implements StopwordAnalyzer {
+
+ /**
+ * Any change to the content of its data files should be reflected in a
+ * version change (the version number does not necessarily correlate with
+ * the library version)
+ */
+ const VERSION = '0.1';
+
+ /**
+ * @var Cdb
+ */
+ private $stopwords;
+
+ /**
+ * @since 0.1
+ *
+ * @param array $stopwords
+ */
+ public function __construct( array $stopwords = array() ) {
+ $this->stopwords = array_flip( $stopwords );
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @param string $word
+ *
+ * @return boolean
+ */
+ public function isStopWord( $word ) {
+ return isset( $this->stopwords[$word] );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/CdbStopwordAnalyzer.php b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/CdbStopwordAnalyzer.php
new file mode 100644
index 00000000..a91f3f14
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/CdbStopwordAnalyzer.php
@@ -0,0 +1,126 @@
+<?php
+
+namespace Onoi\Tesa\StopwordAnalyzer;
+
+use Cdb\Reader;
+use Cdb\Writer;
+use Exception;
+use RuntimeException;
+
+/**
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class CdbStopwordAnalyzer implements StopwordAnalyzer {
+
+ /**
+ * Any change to the content of its data files should be reflected in a
+ * version change (the version number does not necessarily correlate with
+ * the library version)
+ */
+ const VERSION = '0.1.cdb';
+
+ /**
+ * @var Cdb
+ */
+ private $cdb;
+
+ /**
+ * @since 0.1
+ *
+ * @param string $target
+ */
+ public function __construct( $target ) {
+ try {
+ $this->cdb = Reader::open( $target );
+ } catch( Exception $e ) {
+ // Do nothing
+ }
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @return boolean
+ */
+ public function isAvailable() {
+ return $this->cdb !== null;
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @param string $language
+ *
+ * @return string
+ */
+ public static function getLocation() {
+ return str_replace( array( '\\', '/' ), DIRECTORY_SEPARATOR, __DIR__ . '/data/' );
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @param string $language
+ *
+ * @return string
+ */
+ public static function getTargetByLanguage( $language ) {
+ return self::getLocation() . 'cdb/' . strtolower( $language ) . '.cdb';
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @param string $word
+ *
+ * @return boolean
+ */
+ public function isStopWord( $word ) {
+
+ if ( $this->cdb !== null && $this->cdb->get( $word ) !== false ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @param string $location
+ * @param string $language
+ *
+ * @return boolean
+ */
+ public static function createCdbByLanguage( $location, $language ) {
+
+ $language = strtolower( $language );
+ $source = $location . $language . '.json';
+
+ if ( !file_exists( $source ) ) {
+ throw new RuntimeException( "{$source} is not available." );
+ }
+
+ $contents = json_decode( file_get_contents( $source ), true );
+
+ if ( !isset( $contents['list'] ) ) {
+ throw new RuntimeException( "JSON is missing the `list` index." );
+ }
+
+ $writer = Writer::open(
+ self::getTargetByLanguage( $language )
+ );
+
+ foreach ( $contents['list'] as $words ) {
+ $writer->set( trim( $words ), true );
+ }
+
+ $writer->close();
+
+ return true;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/NullStopwordAnalyzer.php b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/NullStopwordAnalyzer.php
new file mode 100644
index 00000000..d946b248
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/NullStopwordAnalyzer.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Onoi\Tesa\StopwordAnalyzer;
+
+/**
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class NullStopwordAnalyzer implements StopwordAnalyzer {
+
+ /**
+ * @since 0.1
+ *
+ * @param string $word
+ *
+ * @return boolean
+ */
+ public function isStopWord( $word ) {
+ return false;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/README.md b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/README.md
new file mode 100644
index 00000000..f3d01237
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/README.md
@@ -0,0 +1,40 @@
+# StopwordAnalyzer
+
+This interface provides the means to filter specific frequent words (or characters) from a text corpus.
+
+```php
+
+use Onoi\Tesa\SanitizerFactory;
+
+$sanitizerFactory = new SanitizerFactory();
+
+$stopwordAnalyzer = $sanitizerFactory->newStopwordAnalyzerByLanguage(
+ 'en'
+);
+
+$stopwordAnalyzer->isStopWord( 'foo' );
+
+```
+
+## CdbStopwordAnalyzer
+
+`CdbStopwordAnalyzer` uses [cdb][cdb] as storage backend to allow for an instant access to a list of
+stopwords on a per language basis.
+
+Adding a new set of stopwords to a language only requires to place a JSON file into the the `/data` folder
+and extend the `CdbStopwordAnalyzerTest` which ensures that listed languages and their JSON files are
+converted into a corresponding cdb file.
+
+```
+{
+ "@source": "...",
+ "version": "0.1",
+ "list":[
+ "a",
+ "about",
+ "..."
+ ]
+}
+```
+
+[cdb]: https://en.wikipedia.org/wiki/Cdb_(software) \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/StopwordAnalyzer.php b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/StopwordAnalyzer.php
new file mode 100644
index 00000000..2461a028
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/StopwordAnalyzer.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Onoi\Tesa\StopwordAnalyzer;
+
+/**
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+interface StopwordAnalyzer {
+
+ /**
+ * @since 0.1
+ *
+ * @param string $word
+ *
+ * @return boolean
+ */
+ public function isStopWord( $word );
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/de.cdb b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/de.cdb
new file mode 100644
index 00000000..4c3bdb4f
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/de.cdb
Binary files differ
diff --git a/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/en.cdb b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/en.cdb
new file mode 100644
index 00000000..016442fc
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/en.cdb
Binary files differ
diff --git a/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/es.cdb b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/es.cdb
new file mode 100644
index 00000000..0588d88c
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/es.cdb
Binary files differ
diff --git a/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/fr.cdb b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/fr.cdb
new file mode 100644
index 00000000..c8457fa5
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/fr.cdb
Binary files differ
diff --git a/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/ja.cdb b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/ja.cdb
new file mode 100644
index 00000000..2bb7c840
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/ja.cdb
Binary files differ
diff --git a/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/pt-br.cdb b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/pt-br.cdb
new file mode 100644
index 00000000..9eea08e1
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/pt-br.cdb
Binary files differ
diff --git a/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/pt.cdb b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/pt.cdb
new file mode 100644
index 00000000..f28484cd
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/pt.cdb
Binary files differ
diff --git a/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/zh.cdb b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/zh.cdb
new file mode 100644
index 00000000..9ec85405
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/cdb/zh.cdb
Binary files differ
diff --git a/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/de.json b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/de.json
new file mode 100644
index 00000000..9822cd68
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/de.json
@@ -0,0 +1,366 @@
+{
+ "@source": "http://www.ranks.nl/stopwords/german, https://github.com/SUL-DIG/hcrc_solr/stopwords_de.txt",
+ "version": "0.1",
+ "list":[
+ "aber",
+ "alle",
+ "allem",
+ "allen",
+ "aller",
+ "alles",
+ "als",
+ "also",
+ "am",
+ "an",
+ "ander",
+ "andere",
+ "anderem",
+ "anderen",
+ "anderer",
+ "anderes",
+ "anderm",
+ "andern",
+ "anderr",
+ "anders",
+ "auch",
+ "auf",
+ "aus",
+ "bei",
+ "bin",
+ "bis",
+ "bist",
+ "da",
+ "damit",
+ "dann",
+ "das",
+ "dasselbe",
+ "dazu",
+ "daß",
+ "dein",
+ "deine",
+ "deinem",
+ "deinen",
+ "deiner",
+ "deines",
+ "dem",
+ "demselben",
+ "den",
+ "denn",
+ "denselben",
+ "der",
+ "derer",
+ "derselbe",
+ "derselben",
+ "des",
+ "desselben",
+ "dessen",
+ "dich",
+ "die",
+ "dies",
+ "diese",
+ "dieselbe",
+ "dieselben",
+ "diesem",
+ "diesen",
+ "dieser",
+ "dieses",
+ "dir",
+ "doch",
+ "dort",
+ "du",
+ "durch",
+ "ein",
+ "eine",
+ "einem",
+ "einen",
+ "einer",
+ "eines",
+ "einig",
+ "einige",
+ "einigem",
+ "einigen",
+ "einiger",
+ "einiges",
+ "einmal",
+ "er",
+ "es",
+ "etwas",
+ "euch",
+ "euer",
+ "eure",
+ "eurem",
+ "euren",
+ "eurer",
+ "eures",
+ "für",
+ "gegen",
+ "gewesen",
+ "hab",
+ "habe",
+ "haben",
+ "hat",
+ "hatte",
+ "hatten",
+ "hier",
+ "hin",
+ "hinter",
+ "ich",
+ "ihm",
+ "ihn",
+ "ihnen",
+ "ihr",
+ "ihre",
+ "ihrem",
+ "ihren",
+ "ihrer",
+ "ihres",
+ "im",
+ "in",
+ "indem",
+ "ins",
+ "ist",
+ "jede",
+ "jedem",
+ "jeden",
+ "jeder",
+ "jedes",
+ "jene",
+ "jenem",
+ "jenen",
+ "jener",
+ "jenes",
+ "jetzt",
+ "kann",
+ "kein",
+ "keine",
+ "keinem",
+ "keinen",
+ "keiner",
+ "keines",
+ "können",
+ "könnte",
+ "machen",
+ "man",
+ "manche",
+ "manchem",
+ "manchen",
+ "mancher",
+ "manches",
+ "mein",
+ "meine",
+ "meinem",
+ "meinen",
+ "meiner",
+ "meines",
+ "mich",
+ "mir",
+ "mit",
+ "muss",
+ "musste",
+ "nach",
+ "nicht",
+ "nichts",
+ "noch",
+ "nun",
+ "nur",
+ "ob",
+ "oder",
+ "ohne",
+ "sehr",
+ "sein",
+ "seine",
+ "seinem",
+ "seinen",
+ "seiner",
+ "seines",
+ "selbst",
+ "sich",
+ "sie",
+ "sind",
+ "so",
+ "solche",
+ "solchem",
+ "solchen",
+ "solcher",
+ "solches",
+ "soll",
+ "sollte",
+ "sondern",
+ "sonst",
+ "um",
+ "und",
+ "uns",
+ "unse",
+ "unsem",
+ "unsen",
+ "unser",
+ "unses",
+ "unter",
+ "viel",
+ "vom",
+ "von",
+ "vor",
+ "war",
+ "waren",
+ "warst",
+ "was",
+ "weg",
+ "weil",
+ "weiter",
+ "welche",
+ "welchem",
+ "welchen",
+ "welcher",
+ "welches",
+ "wenn",
+ "werde",
+ "werden",
+ "wie",
+ "wieder",
+ "will",
+ "wir",
+ "wird",
+ "wirst",
+ "wo",
+ "wollen",
+ "wollte",
+ "während",
+ "würde",
+ "würden",
+ "zu",
+ "zum",
+ "zur",
+ "zwar",
+ "zwischen",
+ "über",
+ "aber",
+ "als",
+ "am",
+ "an",
+ "auch",
+ "auf",
+ "aus",
+ "bei",
+ "bin",
+ "bis",
+ "bist",
+ "da",
+ "dadurch",
+ "daher",
+ "darum",
+ "das",
+ "dass",
+ "daß",
+ "dein",
+ "deine",
+ "dem",
+ "den",
+ "der",
+ "des",
+ "deshalb",
+ "dessen",
+ "die",
+ "dies",
+ "dieser",
+ "dieses",
+ "doch",
+ "dort",
+ "du",
+ "durch",
+ "ein",
+ "eine",
+ "einem",
+ "einen",
+ "einer",
+ "eines",
+ "er",
+ "es",
+ "euer",
+ "eure",
+ "für",
+ "hatte",
+ "hatten",
+ "hattest",
+ "hattet",
+ "hier",
+ "hinter",
+ "ich",
+ "ihr",
+ "ihre",
+ "im",
+ "in",
+ "ist",
+ "ja",
+ "jede",
+ "jedem",
+ "jeden",
+ "jeder",
+ "jedes",
+ "jener",
+ "jenes",
+ "jetzt",
+ "kann",
+ "kannst",
+ "können",
+ "könnt",
+ "machen",
+ "mein",
+ "meine",
+ "mit",
+ "musst",
+ "muß",
+ "mußt",
+ "müssen",
+ "müßt",
+ "nach",
+ "nachdem",
+ "nein",
+ "nicht",
+ "nun",
+ "oder",
+ "seid",
+ "sein",
+ "seine",
+ "sich",
+ "sie",
+ "sind",
+ "soll",
+ "sollen",
+ "sollst",
+ "sollt",
+ "sonst",
+ "soweit",
+ "sowie",
+ "und",
+ "unser",
+ "unsere",
+ "unter",
+ "vom",
+ "von",
+ "vor",
+ "wann",
+ "warum",
+ "was",
+ "weiter",
+ "weitere",
+ "wenn",
+ "wer",
+ "werde",
+ "werden",
+ "werdet",
+ "weshalb",
+ "wie",
+ "wieder",
+ "wieso",
+ "wir",
+ "wird",
+ "wirst",
+ "wo",
+ "woher",
+ "wohin",
+ "zu",
+ "zum",
+ "zur",
+ "über"
+ ]
+} \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/en.json b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/en.json
new file mode 100644
index 00000000..bfb1e638
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/en.json
@@ -0,0 +1,1009 @@
+{
+ "@source": "http://www.lextek.com/manuals/onix/stopwords1.html, http://dev.mysql.com/doc/refman/5.7/en/fulltext-stopwords.html",
+ "version": "0.1",
+ "list":[
+ "a",
+ "about",
+ "an",
+ "are",
+ "as",
+ "at",
+ "be",
+ "by",
+ "com",
+ "org",
+ "de",
+ "en",
+ "for",
+ "from",
+ "how",
+ "i",
+ "in",
+ "is",
+ "it",
+ "la",
+ "of",
+ "on",
+ "or",
+ "that",
+ "the",
+ "this",
+ "to",
+ "was",
+ "what",
+ "when",
+ "where",
+ "who",
+ "will",
+ "with",
+ "und",
+ "the",
+ "www",
+
+ "a's",
+ "able",
+ "about",
+ "above",
+ "according",
+ "accordingly",
+ "across",
+ "actually",
+ "after",
+ "afterwards",
+ "again",
+ "against",
+ "ain't",
+ "all",
+ "allow",
+ "allows",
+ "almost",
+ "alone",
+ "along",
+ "already",
+ "also",
+ "although",
+ "always",
+ "am",
+ "among",
+ "amongst",
+ "an",
+ "and",
+ "another",
+ "any",
+ "anybody",
+ "anyhow",
+ "anyone",
+ "anything",
+ "anyway",
+ "anyways",
+ "anywhere",
+ "apart",
+ "appear",
+ "appreciate",
+ "appropriate",
+ "are",
+ "aren't",
+ "around",
+ "as",
+ "aside",
+ "ask",
+ "asking",
+ "associated",
+ "at",
+ "available",
+ "away",
+ "awfully",
+ "be",
+ "became",
+ "because",
+ "become",
+ "becomes",
+ "becoming",
+ "been",
+ "before",
+ "beforehand",
+ "behind",
+ "being",
+ "believe",
+ "below",
+ "beside",
+ "besides",
+ "best",
+ "better",
+ "between",
+ "beyond",
+ "both",
+ "brief",
+ "but",
+ "by",
+ "c'mon",
+ "c's",
+ "came",
+ "can",
+ "can't",
+ "cannot",
+ "cant",
+ "cause",
+ "causes",
+ "certain",
+ "certainly",
+ "changes",
+ "clearly",
+ "co",
+ "com",
+ "come",
+ "comes",
+ "concerning",
+ "consequently",
+ "consider",
+ "considering",
+ "contain",
+ "containing",
+ "contains",
+ "corresponding",
+ "could",
+ "couldn't",
+ "course",
+ "currently",
+ "definitely",
+ "described",
+ "despite",
+ "did",
+ "didn't",
+ "different",
+ "do",
+ "does",
+ "doesn't",
+ "doing",
+ "don't",
+ "done",
+ "down",
+ "downwards",
+ "during",
+ "each",
+ "edu",
+ "eg",
+ "eight",
+ "either",
+ "else",
+ "elsewhere",
+ "enough",
+ "entirely",
+ "especially",
+ "et",
+ "etc",
+ "even",
+ "ever",
+ "every",
+ "everybody",
+ "everyone",
+ "everything",
+ "everywhere",
+ "ex",
+ "exactly",
+
+ "except",
+ "far",
+ "few",
+ "fifth",
+ "first",
+ "five",
+ "followed",
+ "following",
+ "follows",
+ "for",
+ "former",
+ "formerly",
+ "forth",
+ "four",
+ "from",
+ "further",
+ "furthermore",
+ "get",
+ "gets",
+ "getting",
+ "given",
+ "gives",
+ "go",
+ "goes",
+ "going",
+ "gone",
+ "got",
+ "gotten",
+ "greetings",
+ "had",
+ "hadn't",
+ "happens",
+ "hardly",
+ "has",
+ "hasn't",
+ "have",
+ "haven't",
+ "having",
+ "he",
+ "he's",
+ "hello",
+ "help",
+ "hence",
+ "her",
+ "here",
+ "here's",
+ "hereafter",
+ "hereby",
+ "herein",
+ "hereupon",
+ "hers",
+ "herself",
+ "hi",
+ "him",
+ "himself",
+ "his",
+ "hither",
+ "hopefully",
+ "how",
+ "howbeit",
+ "however",
+ "i'd",
+ "i'll",
+ "i'm",
+ "i've",
+ "ie",
+ "if",
+ "ignored",
+ "immediate",
+ "in",
+ "inasmuch",
+ "inc",
+ "indeed",
+ "indicate",
+ "indicated",
+ "indicates",
+ "inner",
+ "insofar",
+ "instead",
+ "into",
+ "inward",
+ "is",
+ "isn't",
+ "it",
+ "it'd",
+ "it'll",
+ "it's",
+ "its",
+ "itself",
+ "just",
+ "keep",
+ "keeps",
+ "kept",
+ "know",
+ "known",
+ "knows",
+ "last",
+ "lately",
+ "later",
+ "latter",
+ "latterly",
+ "least",
+ "less",
+ "lest",
+ "let",
+ "let's",
+ "like",
+ "liked",
+ "likely",
+ "little",
+ "look",
+ "looking",
+ "looks",
+ "ltd",
+ "mainly",
+ "many",
+ "may",
+ "maybe",
+ "me",
+ "mean",
+ "meanwhile",
+ "merely",
+ "might",
+ "more",
+ "moreover",
+ "most",
+ "mostly",
+ "much",
+ "must",
+ "my",
+ "myself",
+ "name",
+ "namely",
+ "nd",
+ "near",
+ "nearly",
+ "necessary",
+ "need",
+ "needs",
+ "neither",
+ "never",
+ "nevertheless",
+ "new",
+ "next",
+ "nine",
+ "no",
+ "nobody",
+ "non",
+ "none",
+ "noone",
+ "nor",
+ "normally",
+ "not",
+ "nothing",
+ "novel",
+ "now",
+ "nowhere",
+ "obviously",
+ "of",
+ "off",
+ "often",
+ "oh",
+ "ok",
+ "okay",
+ "old",
+ "on",
+ "once",
+ "one",
+ "ones",
+ "only",
+ "onto",
+ "or",
+ "other",
+ "others",
+ "otherwise",
+ "ought",
+ "our",
+ "ours",
+ "ourselves",
+ "out",
+ "outside",
+ "over",
+ "overall",
+ "own",
+ "particular",
+ "particularly",
+ "per",
+ "perhaps",
+ "placed",
+ "please",
+ "plus",
+ "possible",
+ "presumably",
+ "probably",
+ "provides",
+ "que",
+ "quite",
+ "qv",
+ "rather",
+ "rd",
+ "re",
+ "really",
+ "reasonably",
+ "regarding",
+ "regardless",
+ "regards",
+ "relatively",
+ "respectively",
+ "right",
+ "said",
+ "same",
+ "saw",
+ "say",
+ "saying",
+ "says",
+ "second",
+ "secondly",
+ "see",
+ "seeing",
+ "seem",
+ "seemed",
+ "seeming",
+ "seems",
+ "seen",
+ "self",
+ "selves",
+ "sensible",
+ "sent",
+ "serious",
+ "seriously",
+ "seven",
+ "several",
+ "shall",
+ "she",
+ "should",
+ "shouldn't",
+ "since",
+ "six",
+ "so",
+ "some",
+ "somebody",
+ "somehow",
+ "someone",
+ "something",
+ "sometime",
+ "sometimes",
+ "somewhat",
+ "somewhere",
+ "soon",
+ "sorry",
+ "specified",
+ "specify",
+ "specifying",
+ "still",
+ "sub",
+ "such",
+ "sup",
+ "sure",
+ "t's",
+ "take",
+ "taken",
+ "tell",
+ "tends",
+ "th",
+ "than",
+ "thank",
+ "thanks",
+ "thanx",
+ "that",
+ "that's",
+ "thats",
+ "the",
+ "their",
+ "theirs",
+ "them",
+ "themselves",
+ "then",
+ "thence",
+ "there",
+ "there's",
+ "thereafter",
+ "thereby",
+ "therefore",
+ "therein",
+ "theres",
+ "thereupon",
+ "these",
+ "they",
+ "they'd",
+ "they'll",
+ "they're",
+ "they've",
+ "think",
+ "third",
+ "this",
+ "thorough",
+ "thoroughly",
+ "those",
+ "though",
+ "three",
+ "through",
+ "throughout",
+ "thru",
+ "thus",
+ "to",
+ "together",
+ "too",
+ "took",
+ "toward",
+ "towards",
+ "tried",
+ "tries",
+ "truly",
+ "try",
+ "trying",
+ "twice",
+ "two",
+ "un",
+ "under",
+ "unfortunately",
+ "unless",
+ "unlikely",
+ "until",
+ "unto",
+ "up",
+ "upon",
+ "us",
+ "use",
+ "used",
+ "useful",
+ "uses",
+ "using",
+ "usually",
+ "value",
+ "various",
+ "very",
+ "via",
+ "viz",
+ "vs",
+ "want",
+ "wants",
+ "was",
+ "wasn't",
+ "way",
+ "we",
+ "we'd",
+ "we'll",
+ "we're",
+ "we've",
+ "welcome",
+ "well",
+ "went",
+ "were",
+ "weren't",
+ "what",
+ "what's",
+ "whatever",
+ "when",
+ "whence",
+ "whenever",
+ "where",
+ "where's",
+ "whereafter",
+ "whereas",
+ "whereby",
+ "wherein",
+ "whereupon",
+ "wherever",
+ "whether",
+ "which",
+ "while",
+ "whither",
+ "who",
+ "who's",
+ "whoever",
+ "whole",
+ "whom",
+ "whose",
+ "why",
+ "will",
+ "willing",
+ "wish",
+ "with",
+ "within",
+ "without",
+ "won't",
+ "wonder",
+ "would",
+ "wouldn't",
+ "yes",
+ "yet",
+ "you",
+ "you'd",
+ "you'll",
+ "you're",
+ "you've",
+ "your",
+ "yours",
+ "yourself",
+ "yourselves",
+ "zero",
+
+ "above",
+ "across",
+ "after",
+ "again",
+ "against",
+ "all",
+ "almost",
+ "alone",
+ "along",
+ "already",
+ "also",
+ "although",
+ "always",
+ "among",
+ "an",
+ "and",
+ "another",
+ "any",
+ "anybody",
+ "anyone",
+ "anything",
+ "anywhere",
+ "are",
+ "areas",
+ "around",
+ "as",
+ "ask",
+ "asked",
+ "asking",
+ "asks",
+ "at",
+ "away",
+ "b",
+ "back",
+ "backed",
+ "backing",
+ "backs",
+ "be",
+ "became",
+ "because",
+ "become",
+ "becomes",
+ "been",
+ "before",
+ "began",
+ "behind",
+ "being",
+ "beings",
+ "best",
+ "better",
+ "between",
+ "big",
+ "both",
+ "but",
+ "by",
+ "c",
+ "came",
+ "can",
+ "cannot",
+ "case",
+ "cases",
+ "certain",
+ "certainly",
+ "clear",
+ "clearly",
+ "come",
+ "could",
+ "d",
+ "did",
+ "differ",
+ "different",
+ "differently",
+ "do",
+ "does",
+ "done",
+ "down",
+ "down",
+ "downed",
+ "downing",
+ "downs",
+ "during",
+ "e",
+ "each",
+ "early",
+ "either",
+ "end",
+ "ended",
+ "ending",
+ "ends",
+ "enough",
+ "even",
+ "evenly",
+ "ever",
+ "every",
+ "everybody",
+ "everyone",
+ "everything",
+ "everywhere",
+ "f",
+ "face",
+ "faces",
+ "fact",
+ "facts",
+ "far",
+ "felt",
+ "few",
+ "find",
+ "finds",
+ "first",
+ "for",
+ "four",
+ "from",
+ "full",
+ "fully",
+ "further",
+ "furthered",
+ "furthering",
+ "furthers",
+ "g",
+ "gave",
+ "general",
+ "generally",
+ "get",
+ "gets",
+ "give",
+ "given",
+ "gives",
+ "go",
+ "going",
+ "good",
+ "goods",
+ "got",
+ "great",
+ "greater",
+ "greatest",
+ "group",
+ "grouped",
+ "grouping",
+ "groups",
+ "h",
+ "had",
+ "has",
+ "have",
+ "having",
+ "he",
+ "her",
+ "here",
+ "herself",
+ "high",
+ "high",
+ "high",
+ "higher",
+ "highest",
+ "him",
+ "himself",
+ "his",
+ "how",
+ "however",
+ "i",
+ "if",
+ "important",
+ "in",
+ "interest",
+ "interested",
+ "interesting",
+ "interests",
+ "into",
+ "is",
+ "it",
+ "its",
+ "itself",
+ "j",
+ "just",
+ "k",
+ "keep",
+ "keeps",
+ "kind",
+ "knew",
+ "know",
+ "known",
+ "knows",
+ "l",
+ "large",
+ "largely",
+ "last",
+ "later",
+ "latest",
+ "least",
+ "less",
+ "let",
+ "lets",
+ "like",
+ "likely",
+ "long",
+ "longer",
+ "longest",
+ "m",
+ "made",
+ "make",
+ "making",
+ "man",
+ "many",
+ "may",
+ "me",
+ "members",
+ "men",
+ "might",
+ "more",
+ "most",
+ "mostly",
+ "mr",
+ "mrs",
+ "much",
+ "must",
+ "my",
+ "myself",
+ "n",
+ "necessary",
+ "need",
+ "needed",
+ "needing",
+ "needs",
+ "never",
+ "new",
+ "new",
+ "newer",
+ "newest",
+ "next",
+ "no",
+ "nobody",
+ "non",
+ "noone",
+ "not",
+ "nothing",
+ "now",
+ "nowhere",
+ "numbers",
+ "o",
+ "of",
+ "off",
+ "often",
+ "old",
+ "older",
+ "oldest",
+ "on",
+ "once",
+ "one",
+ "only",
+ "opened",
+ "opening",
+ "opens",
+ "or",
+ "ordered",
+ "ordering",
+ "orders",
+ "other",
+ "others",
+ "our",
+ "out",
+ "over",
+ "p",
+ "parted",
+ "parting",
+ "parts",
+ "per",
+ "perhaps",
+ "place",
+ "places",
+ "point",
+ "pointed",
+ "pointing",
+ "points",
+ "possible",
+ "present",
+ "presented",
+ "presenting",
+ "presents",
+ "problem",
+ "problems",
+ "put",
+ "puts",
+ "q",
+ "quite",
+ "r",
+ "rather",
+ "really",
+ "right",
+ "right",
+ "room",
+ "rooms",
+ "s",
+ "said",
+ "same",
+ "saw",
+ "say",
+ "says",
+ "second",
+ "seconds",
+ "see",
+ "seem",
+ "seemed",
+ "seeming",
+ "seems",
+ "sees",
+ "several",
+ "shall",
+ "she",
+ "should",
+ "show",
+ "showed",
+ "showing",
+ "shows",
+ "side",
+ "sides",
+ "since",
+ "small",
+ "smaller",
+ "smallest",
+ "so",
+ "some",
+ "somebody",
+ "someone",
+ "something",
+ "somewhere",
+ "state",
+ "states",
+ "still",
+ "still",
+ "such",
+ "sure",
+ "t",
+ "take",
+ "taken",
+ "than",
+ "that",
+ "the",
+ "their",
+ "them",
+ "then",
+ "there",
+ "therefore",
+ "these",
+ "they",
+ "thing",
+ "things",
+ "think",
+ "thinks",
+ "this",
+ "those",
+ "though",
+ "thought",
+ "thoughts",
+ "three",
+ "through",
+ "thus",
+ "to",
+ "today",
+ "together",
+ "too",
+ "took",
+ "toward",
+ "turn",
+ "turned",
+ "turning",
+ "turns",
+ "two",
+ "u",
+ "under",
+ "until",
+ "up",
+ "upon",
+ "us",
+ "use",
+ "used",
+ "uses",
+ "v",
+ "very",
+ "w",
+ "want",
+ "wanted",
+ "wanting",
+ "wants",
+ "was",
+ "way",
+ "ways",
+ "we",
+ "well",
+ "wells",
+ "went",
+ "were",
+ "what",
+ "when",
+ "where",
+ "whether",
+ "which",
+ "while",
+ "who",
+ "whole",
+ "whose",
+ "why",
+ "will",
+ "with",
+ "within",
+ "without",
+ "work",
+ "worked",
+ "working",
+ "works",
+ "would",
+ "x",
+ "y",
+ "year",
+ "years",
+ "yet",
+ "you",
+ "young",
+ "younger",
+ "youngest",
+ "your",
+ "yours",
+ "z"
+ ]
+} \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/es.json b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/es.json
new file mode 100644
index 00000000..4cf1a9e1
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/es.json
@@ -0,0 +1,185 @@
+{
+ "@source": "http://www.ranks.nl/stopwords/spanish",
+ "version": "0.1",
+ "list":[
+ "alguna",
+ "algunas",
+ "alguno",
+ "algunos",
+ "algún",
+ "ambos",
+ "ampleamos",
+ "ante",
+ "antes",
+ "aquel",
+ "aquellas",
+ "aquellos",
+ "aqui",
+ "arriba",
+ "atras",
+ "bajo",
+ "bastante",
+ "bien",
+ "cada",
+ "cierta",
+ "ciertas",
+ "cierto",
+ "ciertos",
+ "como",
+ "con",
+ "conseguimos",
+ "conseguir",
+ "consigo",
+ "consigue",
+ "consiguen",
+ "consigues",
+ "cual",
+ "cuando",
+ "dentro",
+ "desde",
+ "donde",
+ "dos",
+ "el",
+ "ellas",
+ "ellos",
+ "empleais",
+ "emplean",
+ "emplear",
+ "empleas",
+ "empleo",
+ "en",
+ "encima",
+ "entonces",
+ "entre",
+ "era",
+ "eramos",
+ "eran",
+ "eras",
+ "eres",
+ "es",
+ "esta",
+ "estaba",
+ "estado",
+ "estais",
+ "estamos",
+ "estan",
+ "estoy",
+ "fin",
+ "fue",
+ "fueron",
+ "fui",
+ "fuimos",
+ "gueno",
+ "ha",
+ "hace",
+ "haceis",
+ "hacemos",
+ "hacen",
+ "hacer",
+ "haces",
+ "hago",
+ "incluso",
+ "intenta",
+ "intentais",
+ "intentamos",
+ "intentan",
+ "intentar",
+ "intentas",
+ "intento",
+ "ir",
+ "la",
+ "largo",
+ "las",
+ "lo",
+ "los",
+ "mientras",
+ "mio",
+ "modo",
+ "muchos",
+ "muy",
+ "nos",
+ "nosotros",
+ "otro",
+ "para",
+ "pero",
+ "podeis",
+ "podemos",
+ "poder",
+ "podria",
+ "podriais",
+ "podriamos",
+ "podrian",
+ "podrias",
+ "por",
+ "por",
+ "porque",
+ "primero",
+ "puede",
+ "pueden",
+ "puedo",
+ "quien",
+ "qué",
+ "sabe",
+ "sabeis",
+ "sabemos",
+ "saben",
+ "saber",
+ "sabes",
+ "ser",
+ "si",
+ "siendo",
+ "sin",
+ "sobre",
+ "sois",
+ "solamente",
+ "solo",
+ "somos",
+ "soy",
+ "su",
+ "sus",
+ "también",
+ "teneis",
+ "tenemos",
+ "tener",
+ "tengo",
+ "tiempo",
+ "tiene",
+ "tienen",
+ "todo",
+ "trabaja",
+ "trabajais",
+ "trabajamos",
+ "trabajan",
+ "trabajar",
+ "trabajas",
+ "trabajo",
+ "tras",
+ "tuyo",
+ "ultimo",
+ "un",
+ "una",
+ "unas",
+ "uno",
+ "unos",
+ "usa",
+ "usais",
+ "usamos",
+ "usan",
+ "usar",
+ "usas",
+ "uso",
+ "va",
+ "vais",
+ "valor",
+ "vamos",
+ "van",
+ "vaya",
+ "verdad",
+ "verdadera",
+ "verdadero",
+ "vosotras",
+ "vosotros",
+ "voy",
+ "yo"
+ ]
+} \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/fr.json b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/fr.json
new file mode 100644
index 00000000..c8b7b23f
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/fr.json
@@ -0,0 +1,122 @@
+{
+ "@source": "http://www.ranks.nl/stopwords/french",
+ "version": "0.1",
+ "list":[
+ "alors",
+ "au",
+ "aucuns",
+ "aussi",
+ "autre",
+ "avant",
+ "avec",
+ "avoir",
+ "bon",
+ "car",
+ "ce",
+ "cela",
+ "ces",
+ "ceux",
+ "chaque",
+ "ci",
+ "comme",
+ "comment",
+ "dans",
+ "dedans",
+ "dehors",
+ "depuis",
+ "des",
+ "devrait",
+ "doit",
+ "donc",
+ "dos",
+ "du",
+ "début",
+ "elle",
+ "elles",
+ "en",
+ "encore",
+ "essai",
+ "est",
+ "et",
+ "eu",
+ "fait",
+ "faites",
+ "fois",
+ "font",
+ "hors",
+ "ici",
+ "il",
+ "ils",
+ "je",
+ "juste",
+ "la",
+ "le",
+ "les",
+ "leur",
+ "là",
+ "ma",
+ "maintenant",
+ "mais",
+ "mes",
+ "mine",
+ "moins",
+ "mon",
+ "mot",
+ "même",
+ "ni",
+ "nommés",
+ "notre",
+ "nous",
+ "ou",
+ "où",
+ "par",
+ "parce",
+ "pas",
+ "peu",
+ "peut",
+ "plupart",
+ "pour",
+ "pourquoi",
+ "quand",
+ "que",
+ "quel",
+ "quelle",
+ "quelles",
+ "quels",
+ "qui",
+ "sa",
+ "sans",
+ "ses",
+ "seulement",
+ "si",
+ "sien",
+ "son",
+ "sont",
+ "sous",
+ "soyez",
+ "sujet",
+ "sur",
+ "ta",
+ "tandis",
+ "tellement",
+ "tels",
+ "tes",
+ "ton",
+ "tous",
+ "tout",
+ "trop",
+ "très",
+ "tu",
+ "voient",
+ "vont",
+ "votre",
+ "vous",
+ "vu",
+ "ça",
+ "étaient",
+ "état",
+ "étions",
+ "été",
+ "être"
+ ]
+} \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/ja.json b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/ja.json
new file mode 100644
index 00000000..9e23b80a
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/ja.json
@@ -0,0 +1,115 @@
+{
+ "@source": "https://github.com/SUL-DIG/hcrc_solr/blob/master/conf/stopwords_all_lang.txt, stopwords_ja.txt",
+ "version": "0.1",
+ "list":[
+ "あっ",
+ "あり",
+ "ある",
+ "い",
+ "いう",
+ "いる",
+ "う",
+ "うち",
+ "お",
+ "および",
+ "おり",
+ "か",
+ "かつて",
+ "から",
+ "が",
+ "き",
+ "ここ",
+ "こと",
+ "この",
+ "これ",
+ "これら",
+ "さ",
+ "さらに",
+ "し",
+ "しかし",
+ "する",
+ "ず",
+ "せ",
+ "せる",
+ "そして",
+ "その",
+ "その他",
+ "その後",
+ "それ",
+ "それぞれ",
+ "た",
+ "ただし",
+ "たち",
+ "ため",
+ "たり",
+ "だ",
+ "だっ",
+ "つ",
+ "て",
+ "で",
+ "でき",
+ "できる",
+ "です",
+ "では",
+ "でも",
+ "と",
+ "という",
+ "といった",
+ "とき",
+ "ところ",
+ "として",
+ "とともに",
+ "とも",
+ "と共に",
+ "な",
+ "ない",
+ "なお",
+ "なかっ",
+ "ながら",
+ "なく",
+ "なっ",
+ "など",
+ "なら",
+ "なり",
+ "なる",
+ "に",
+ "において",
+ "における",
+ "について",
+ "にて",
+ "によって",
+ "により",
+ "による",
+ "に対して",
+ "に対する",
+ "に関する",
+ "の",
+ "ので",
+ "のみ",
+ "は",
+ "ば",
+ "へ",
+ "ほか",
+ "ほとんど",
+ "ほど",
+ "ます",
+ "また",
+ "または",
+ "まで",
+ "も",
+ "もの",
+ "ものの",
+ "や",
+ "よう",
+ "より",
+ "ら",
+ "られ",
+ "られる",
+ "れ",
+ "れる",
+ "を",
+ "ん",
+ "及び",
+ "特に"
+ ]
+} \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/pt-br.json b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/pt-br.json
new file mode 100644
index 00000000..5eeccd4b
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/pt-br.json
@@ -0,0 +1,132 @@
+{
+ "@source": "http://www.ranks.nl/stopwords/brazilian",
+ "version": "0.1",
+ "list": [
+ "a",
+ "ainda",
+ "alem",
+ "ambas",
+ "ambos",
+ "antes",
+ "ao",
+ "aonde",
+ "aos",
+ "apos",
+ "aquele",
+ "aqueles",
+ "as",
+ "assim",
+ "com",
+ "como",
+ "contra",
+ "contudo",
+ "cuja",
+ "cujas",
+ "cujo",
+ "cujos",
+ "da",
+ "das",
+ "de",
+ "dela",
+ "dele",
+ "deles",
+ "demais",
+ "depois",
+ "desde",
+ "desta",
+ "deste",
+ "dispoe",
+ "dispoem",
+ "diversa",
+ "diversas",
+ "diversos",
+ "do",
+ "dos",
+ "durante",
+ "e",
+ "ela",
+ "elas",
+ "ele",
+ "eles",
+ "em",
+ "entao",
+ "entre",
+ "essa\tessas",
+ "esse",
+ "esses",
+ "esta",
+ "estas",
+ "este",
+ "estes",
+ "ha",
+ "isso",
+ "isto",
+ "logo",
+ "mais",
+ "mas",
+ "mediante",
+ "menos",
+ "mesma",
+ "mesmas",
+ "mesmo",
+ "mesmos",
+ "na",
+ "nao",
+ "nas",
+ "nas",
+ "nem",
+ "nesse",
+ "neste",
+ "nos",
+ "o",
+ "os",
+ "ou",
+ "outra",
+ "outras",
+ "outro",
+ "outros",
+ "pelas",
+ "pelas",
+ "pelo",
+ "pelos",
+ "perante",
+ "pois",
+ "por",
+ "porque",
+ "portanto",
+ "propios",
+ "proprio",
+ "quais",
+ "qual",
+ "qualquer",
+ "quando",
+ "quanto\tque",
+ "quem",
+ "quer",
+ "se",
+ "seja",
+ "sem",
+ "sendo",
+ "seu",
+ "seus",
+ "sob",
+ "sobre",
+ "sua",
+ "suas",
+ "tal",
+ "tambem",
+ "teu",
+ "teus",
+ "toda",
+ "todas",
+ "todo",
+ "todos",
+ "tua",
+ "tuas",
+ "tudo",
+ "um",
+ "uma",
+ "umas",
+ "uns"
+ ]
+}
diff --git a/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/pt.json b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/pt.json
new file mode 100644
index 00000000..496dafa7
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/pt.json
@@ -0,0 +1,151 @@
+{
+ "@source": "http://www.ranks.nl/stopwords/portugese",
+ "version": "0.1",
+ "list": [
+ "último",
+ "é",
+ "acerca",
+ "agora",
+ "algmas",
+ "alguns",
+ "ali",
+ "ambos",
+ "antes",
+ "apontar",
+ "aquela",
+ "aquelas",
+ "aquele",
+ "aqueles",
+ "aqui",
+ "atrás",
+ "bem",
+ "bom",
+ "cada",
+ "caminho",
+ "cima",
+ "com",
+ "como",
+ "comprido",
+ "conhecido",
+ "corrente",
+ "das",
+ "debaixo",
+ "dentro",
+ "desde",
+ "desligado",
+ "deve",
+ "devem",
+ "deverá",
+ "direita",
+ "diz",
+ "dizer",
+ "dois",
+ "dos",
+ "e",
+ "ela",
+ "ele",
+ "eles",
+ "em",
+ "enquanto",
+ "então",
+ "está",
+ "estão",
+ "estado",
+ "estar\testará",
+ "este",
+ "estes",
+ "esteve",
+ "estive",
+ "estivemos",
+ "estiveram",
+ "eu",
+ "fará",
+ "faz",
+ "fazer",
+ "fazia",
+ "fez",
+ "fim",
+ "foi",
+ "fora",
+ "horas",
+ "iniciar",
+ "inicio",
+ "ir",
+ "irá",
+ "ista",
+ "iste",
+ "isto",
+ "ligado",
+ "maioria",
+ "maiorias",
+ "mais",
+ "mas",
+ "mesmo",
+ "meu",
+ "muito",
+ "muitos",
+ "nós",
+ "não",
+ "nome",
+ "nosso",
+ "novo",
+ "o",
+ "onde",
+ "os",
+ "ou",
+ "outro",
+ "para",
+ "parte",
+ "pegar",
+ "pelo",
+ "pessoas",
+ "pode",
+ "poderá\tpodia",
+ "por",
+ "porque",
+ "povo",
+ "promeiro",
+ "quê",
+ "qual",
+ "qualquer",
+ "quando",
+ "quem",
+ "quieto",
+ "são",
+ "saber",
+ "sem",
+ "ser",
+ "seu",
+ "somente",
+ "têm",
+ "tal",
+ "também",
+ "tem",
+ "tempo",
+ "tenho",
+ "tentar",
+ "tentaram",
+ "tente",
+ "tentei",
+ "teu",
+ "teve",
+ "tipo",
+ "tive",
+ "todos",
+ "trabalhar",
+ "trabalho",
+ "tu",
+ "um",
+ "uma",
+ "umas",
+ "uns",
+ "usa",
+ "usar",
+ "valor",
+ "veja",
+ "ver",
+ "verdade",
+ "verdadeiro",
+ "você"
+ ]
+}
diff --git a/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/zh.json b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/zh.json
new file mode 100644
index 00000000..6179e7ee
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/StopwordAnalyzer/data/zh.json
@@ -0,0 +1,5 @@
+{
+ "@source": "https://raw.githubusercontent.com/6/stopwords-json/master/dist/zh.json",
+ "version": "0.1",
+ "list":["、","。","〈","〉","《","》","一","一切","一则","一方面","一旦","一来","一样","一般","七","万一","三","上下","不仅","不但","不光","不单","不只","不如","不怕","不惟","不成","不拘","不比","不然","不特","不独","不管","不论","不过","不问","与","与其","与否","与此同时","且","两者","个","临","为","为了","为什么","为何","为着","乃","乃至","么","之","之一","之所以","之类","乌乎","乎","乘","九","也","也好","也罢","了","二","于","于是","于是乎","云云","五","人家","什么","什么样","从","从而","他","他人","他们","以","以便","以免","以及","以至","以至于","以致","们","任","任何","任凭","似的","但","但是","何","何况","何处","何时","作为","你","你们","使得","例如","依","依照","俺","俺们","倘","倘使","倘或","倘然","倘若","借","假使","假如","假若","像","八","六","兮","关于","其","其一","其中","其二","其他","其余","其它","其次","具体地说","具体说来","再者","再说","冒","冲","况且","几","几时","凭","凭借","则","别","别的","别说","到","前后","前者","加之","即","即令","即使","即便","即或","即若","又","及","及其","及至","反之","反过来","反过来说","另","另一方面","另外","只是","只有","只要","只限","叫","叮咚","可","可以","可是","可见","各","各个","各位","各种","各自","同","同时","向","向着","吓","吗","否则","吧","吧哒","吱","呀","呃","呕","呗","呜","呜呼","呢","呵","呸","呼哧","咋","和","咚","咦","咱","咱们","咳","哇","哈","哈哈","哉","哎","哎呀","哎哟","哗","哟","哦","哩","哪","哪个","哪些","哪儿","哪天","哪年","哪怕","哪样","哪边","哪里","哼","哼唷","唉","啊","啐","啥","啦","啪达","喂","喏","喔唷","嗡嗡","嗬","嗯","嗳","嘎","嘎登","嘘","嘛","嘻","嘿","四","因","因为","因此","因而","固然","在","在下","地","多","多少","她","她们","如","如上所述","如何","如其","如果","如此","如若","宁","宁可","宁愿","宁肯","它","它们","对","对于","将","尔后","尚且","就","就是","就是说","尽","尽管","岂但","己","并","并且","开外","开始","归","当","当着","彼","彼此","往","待","得","怎","怎么","怎么办","怎么样","怎样","总之","总的来看","总的来说","总的说来","总而言之","恰恰相反","您","慢说","我","我们","或","或是","或者","所","所以","打","把","抑或","拿","按","按照","换句话说","换言之","据","接着","故","故此","旁人","无宁","无论","既","既是","既然","时候","是","是的","替","有","有些","有关","有的","望","朝","朝着","本","本着","来","来着","极了","果然","果真","某","某个","某些","根据","正如","此","此外","此间","毋宁","每","每当","比","比如","比方","沿","沿着","漫说","焉","然则","然后","然而","照","照着","甚么","甚而","甚至","用","由","由于","由此可见","的","的话","相对而言","省得","着","着呢","矣","离","第","等","等等","管","紧接着","纵","纵令","纵使","纵然","经","经过","结果","给","继而","综上所述","罢了","者","而","而且","而况","而外","而已","而是","而言","能","腾","自","自个儿","自从","自各儿","自家","自己","自身","至","至于","若","若是","若非","莫若","虽","虽则","虽然","虽说","被","要","要不","要不是","要不然","要么","要是","让","论","设使","设若","该","诸位","谁","谁知","赶","起","起见","趁","趁着","越是","跟","较","较之","边","过","还是","还有","这","这个","这么","这么些","这么样","这么点儿","这些","这会儿","这儿","这就是说","这时","这样","这边","这里","进而","连","连同","通过","遵照","那","那个","那么","那么些","那么样","那些","那会儿","那儿","那时","那样","那边","那里","鄙人","鉴于","阿","除","除了","除此之外","除非","随","随着","零","非但","非徒","靠","顺","顺着","首先","︿","!","#","$","%","&","(",")","*","+",",","0","1","2","3","4","5","6","7","8","9",":",";","<",">","?","@","[","]","{","|","}","~","¥"]
+} \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/tesa/src/Synonymizer/NullSynonymizer.php b/www/wiki/vendor/onoi/tesa/src/Synonymizer/NullSynonymizer.php
new file mode 100644
index 00000000..cd1910fd
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/Synonymizer/NullSynonymizer.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Onoi\Tesa\Synonymizer;
+
+use Cdb\Reader;
+
+/**
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class NullSynonymizer implements Synonymizer {
+
+ /**
+ * @since 0.1
+ *
+ * @param string $word
+ *
+ * @return string
+ */
+ public function synonymize( $word ) {
+ return $word;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/src/Synonymizer/Synonymizer.php b/www/wiki/vendor/onoi/tesa/src/Synonymizer/Synonymizer.php
new file mode 100644
index 00000000..664c7660
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/Synonymizer/Synonymizer.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Onoi\Tesa\Synonymizer;
+
+/**
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+interface Synonymizer {
+
+ /**
+ * @since 0.1
+ *
+ * @param string $word
+ *
+ * @return string
+ */
+ public function synonymize( $word );
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/src/Tokenizer/CJKSimpleCharacterRegExTokenizer.php b/www/wiki/vendor/onoi/tesa/src/Tokenizer/CJKSimpleCharacterRegExTokenizer.php
new file mode 100644
index 00000000..7260b476
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/Tokenizer/CJKSimpleCharacterRegExTokenizer.php
@@ -0,0 +1,84 @@
+<?php
+
+namespace Onoi\Tesa\Tokenizer;
+
+/**
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class CJKSimpleCharacterRegExTokenizer implements Tokenizer {
+
+ /**
+ * @var Tokenizer
+ */
+ private $tokenizer;
+
+ /**
+ * @var string
+ */
+ private $patternExemption = '';
+
+ /**
+ * @since 0.1
+ *
+ * @param Tokenizer $tokenizer
+ */
+ public function __construct( Tokenizer $tokenizer = null ) {
+ $this->tokenizer = $tokenizer;
+ }
+
+ /**
+ * @since 0.1
+ *
+ * {@inheritDoc}
+ */
+ public function setOption( $name, $value ) {
+
+ if ( $this->tokenizer !== null ) {
+ $this->tokenizer->setOption( $name, $value );
+ }
+
+ if ( $name === self::REGEX_EXEMPTION ) {
+ $this->patternExemption = $value;
+ }
+ }
+
+ /**
+ * @since 0.1
+ *
+ * {@inheritDoc}
+ */
+ public function isWordTokenizer() {
+ return false;
+ }
+
+ /**
+ * @since 0.1
+ *
+ * {@inheritDoc}
+ */
+ public function tokenize( $string ) {
+
+ if ( $this->tokenizer !== null ) {
+ $string = implode( " ", $this->tokenizer->tokenize( $string ) );
+ }
+
+ // Filter is based on https://github.com/kitech/cms-drupal/blob/master/modules/csplitter/filter.txt
+ $pattern = str_replace(
+ $this->patternExemption,
+ '',
+ '([\s\、,,。/?《》〈〉;:“”"〃'`[]{}\|~!-=_+)(()*…—─%¥…◆★◇□■【】#·啊吧把并被才从的得当对但到地而该过个给还和叫将就可来了啦里没你您哪那呢去却让使是时省随他我为现县向像象要由矣已以也又与于在之这则最乃\/\(\)\[\]{}<>\r\n"]|(?<!\d)\.(?!\d))'
+ );
+
+ $result = preg_split( '/' . $pattern . '/u', $string, null, PREG_SPLIT_NO_EMPTY );
+
+ if ( $result !== false ) {
+ return $result;
+ }
+
+ return array();
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/src/Tokenizer/GenericRegExTokenizer.php b/www/wiki/vendor/onoi/tesa/src/Tokenizer/GenericRegExTokenizer.php
new file mode 100644
index 00000000..235ceea6
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/Tokenizer/GenericRegExTokenizer.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Onoi\Tesa\Tokenizer;
+
+/**
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class GenericRegExTokenizer implements Tokenizer {
+
+ /**
+ * @var Tokenizer
+ */
+ private $tokenizer;
+
+ /**
+ * @var string
+ */
+ private $patternExemption = '';
+
+ /**
+ * @since 0.1
+ *
+ * @param Tokenizer|null $tokenizer
+ */
+ public function __construct( Tokenizer $tokenizer = null ) {
+ $this->tokenizer = $tokenizer;
+ }
+
+ /**
+ * @since 0.1
+ *
+ * {@inheritDoc}
+ */
+ public function setOption( $name, $value ) {
+
+ if ( $this->tokenizer !== null ) {
+ $this->tokenizer->setOption( $name, $value );
+ }
+
+ if ( $name === self::REGEX_EXEMPTION ) {
+ $this->patternExemption = $value;
+ }
+ }
+
+ /**
+ * @since 0.1
+ *
+ * {@inheritDoc}
+ */
+ public function isWordTokenizer() {
+ return $this->tokenizer !== null ? $this->tokenizer->isWordTokenizer() :true;
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @param string $string
+ *
+ * @return array|false
+ */
+ public function tokenize( $string ) {
+
+ if ( $this->tokenizer !== null ) {
+ $string = implode( " ", $this->tokenizer->tokenize( $string ) );
+ }
+
+ // (?<=\p{L})(?=\p{N}) to split alphanumeric and numeric
+
+ $pattern = str_replace(
+ $this->patternExemption,
+ '',
+ '([\s\-_,:;?!%\'\|\/\(\)\[\]{}<>\r\n"]|(?<!\d)\.(?!\d)|(?<=\p{L})(?=\p{N}))'
+ );
+
+ $result = preg_split( '/' . $pattern . '/u', $string, null, PREG_SPLIT_NO_EMPTY );
+
+ if ( $result === false ) {
+ $result = array();
+ }
+
+ return $result;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/src/Tokenizer/IcuWordBoundaryTokenizer.php b/www/wiki/vendor/onoi/tesa/src/Tokenizer/IcuWordBoundaryTokenizer.php
new file mode 100644
index 00000000..ddb567dd
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/Tokenizer/IcuWordBoundaryTokenizer.php
@@ -0,0 +1,133 @@
+<?php
+
+namespace Onoi\Tesa\Tokenizer;
+
+use IntlRuleBasedBreakIterator;
+
+/**
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class IcuWordBoundaryTokenizer implements Tokenizer {
+
+ /**
+ * @var Tokenizer
+ */
+ private $tokenizer;
+
+ /**
+ * @var string
+ */
+ private $locale = 'en';
+
+ /**
+ * @var string
+ */
+ private $isWordTokenizer = true;
+
+ /**
+ * @since 0.1
+ *
+ * @param Tokenizer|null $tokenizer
+ */
+ public function __construct( Tokenizer $tokenizer = null ) {
+ $this->tokenizer = $tokenizer;
+ }
+
+ /**
+ * @since 0.1
+ *
+ * {@inheritDoc}
+ */
+ public function setOption( $name, $value ) {
+ if ( $this->tokenizer !== null ) {
+ $this->tokenizer->setOption( $name, $value );
+ }
+ }
+
+ /**
+ * @since 0.1
+ *
+ * {@inheritDoc}
+ */
+ public function isWordTokenizer() {
+ return $this->isWordTokenizer;
+ }
+
+ /**
+ * @since 0.1
+ *
+ * {@inheritDoc}
+ */
+ public function setWordTokenizerAttribute( $usesWordBoundaries ) {
+ return $this->isWordTokenizer = $usesWordBoundaries;
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @return boolean
+ */
+ public function isAvailable() {
+ return class_exists( 'IntlRuleBasedBreakIterator' );
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @param string $locale
+ */
+ public function setLocale( $locale ) {
+ $this->locale = $locale;
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @param string $string
+ *
+ * @return array|false
+ */
+ public function tokenize( $string ) {
+
+ if ( $this->tokenizer !== null ) {
+ $string = implode( " ", $this->tokenizer->tokenize( $string ) );
+ }
+
+ if ( !$this->isAvailable() ) {
+ return $this->tokenizer !== null ? $this->tokenizer->tokenize( $string ) : array( $string );
+ }
+
+ return $this->createTokens( $string );
+ }
+
+ private function createTokens( $string ) {
+
+ $tokens = array();
+
+ if ( $tokenizer = IntlRuleBasedBreakIterator::createWordInstance( $this->locale ) ) {
+ $tokenizer->setText( $string );
+ $prev = 0;
+
+ foreach ( $tokenizer as $token ) {
+
+ if ( $token == 0 ) {
+ continue;
+ }
+
+ $res = substr( $string, $prev, $token - $prev );
+
+ if ( $res !== '' && $res !== ' ' ) {
+ $tokens[] = $res;
+ }
+
+ $prev = $token;
+ }
+ }
+
+ return $tokens;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/src/Tokenizer/JaCompoundGroupTokenizer.php b/www/wiki/vendor/onoi/tesa/src/Tokenizer/JaCompoundGroupTokenizer.php
new file mode 100644
index 00000000..9a0ed143
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/Tokenizer/JaCompoundGroupTokenizer.php
@@ -0,0 +1,228 @@
+<?php
+
+namespace Onoi\Tesa\Tokenizer;
+
+use Onoi\Tesa\CharacterExaminer;
+
+/**
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class JaCompoundGroupTokenizer implements Tokenizer {
+
+ /**
+ * @var array
+ */
+ private $compound = array(
+ "あっ",
+ "あり",
+ "ある",
+ "い",
+ "いう",
+ "いる",
+ "う",
+ "うち",
+ "お",
+ "および",
+ "おり",
+ "か",
+ "かつて",
+ "から",
+ "が",
+ "き",
+ "ここ",
+ "こと",
+ "この",
+ "これ",
+ "これら",
+ "さ",
+ "さらに",
+ "し",
+ "しかし",
+ "する",
+ "ず",
+ "せ",
+ "せる",
+ "そして",
+ "その",
+ "その他",
+ "その後",
+ "それ",
+ "それぞれ",
+ "た",
+ "ただし",
+ "たち",
+ "ため",
+ "たり",
+ "だ",
+ "だっ",
+ "つ",
+ "て",
+ "で",
+ "でき",
+ "できる",
+ "です",
+ "では",
+ "でも",
+ "と",
+ "という",
+ "といった",
+ "とき",
+ "ところ",
+ "として",
+ "とともに",
+ "とも",
+ "と共に",
+ "な",
+ "ない",
+ "なお",
+ "なかっ",
+ "ながら",
+ "なく",
+ "なっ",
+ "など",
+ "なら",
+ "なり",
+ "なる",
+ "に",
+ "において",
+ "における",
+ "について",
+ "にて",
+ "によって",
+ "により",
+ "による",
+ "に対して",
+ "に対する",
+ "に関する",
+ "の",
+ "ので",
+ "のみ",
+ "は",
+ "ば",
+ "へ",
+ "ほか",
+ "ほとんど",
+ "ほど",
+ "ます",
+ "また",
+ "または",
+ "まで",
+ "も",
+ "もの",
+ "ものの",
+ "や",
+ "よう",
+ "より",
+ "ら",
+ "られ",
+ "られる",
+ "れ",
+ "れる",
+ "を",
+ "ん",
+ "及び",
+ "特に",
+ "、",
+ "。",
+ "「",
+ "」"
+ );
+
+ /**
+ * @var Tokenizer
+ */
+ private $tokenizer;
+
+ /**
+ * @since 0.1
+ *
+ * @param Tokenizer $tokenizer
+ */
+ public function __construct( Tokenizer $tokenizer = null ) {
+ $this->tokenizer = $tokenizer;
+ }
+
+ /**
+ * @since 0.1
+ *
+ * {@inheritDoc}
+ */
+ public function setOption( $name, $value ) {
+ if ( $this->tokenizer !== null ) {
+ $this->tokenizer->setOption( $name, $value );
+ }
+ }
+
+ /**
+ * @since 0.1
+ *
+ * {@inheritDoc}
+ */
+ public function isWordTokenizer() {
+ return false;
+ }
+
+ /**
+ * @since 0.1
+ *
+ * {@inheritDoc}
+ */
+ public function tokenize( $string ) {
+
+ if ( $this->tokenizer !== null ) {
+ $string = implode( " ", $this->tokenizer->tokenize( $string ) );
+ }
+
+ $result = explode( " " , $this->splitByCharacterGroup(
+ str_replace( $this->compound, ' ', $string ) )
+ );
+
+ foreach ( $result as $key => $value ) {
+ if ( $value === '' ) {
+ unset( $result[$key] );
+ }
+
+ // Single katakana/hiragana are exempted
+ if ( mb_strlen( $value ) === 1 && CharacterExaminer::contains( CharacterExaminer::HIRAGANA_KATAKANA, $value ) ) {
+ unset( $result[$key] );
+ }
+ }
+
+ if ( $result !== false ) {
+ return array_values( $result );
+ }
+
+ return array();
+ }
+
+ /**
+ * @see MediaWiki LanguageJa::segmentByWord
+ *
+ * @since 0.1
+ *
+ * {@inheritDoc}
+ */
+ public function splitByCharacterGroup( $string ) {
+
+ // Space strings of like hiragana/katakana/kanji
+ $hiragana = '(?:\xe3(?:\x81[\x80-\xbf]|\x82[\x80-\x9f]))'; # U3040-309f
+ $katakana = '(?:\xe3(?:\x82[\xa0-\xbf]|\x83[\x80-\xbf]))'; # U30a0-30ff
+ $kanji = '(?:\xe3[\x88-\xbf][\x80-\xbf]'
+ . '|[\xe4-\xe8][\x80-\xbf]{2}'
+ . '|\xe9[\x80-\xa5][\x80-\xbf]'
+ . '|\xe9\xa6[\x80-\x99])';
+ # U3200-9999 = \xe3\x88\x80-\xe9\xa6\x99
+
+ $reg = "/({$hiragana}+|{$katakana}+|{$kanji}+)/";
+
+ return $this->insertSpace( $string, $reg );
+ }
+
+ private function insertSpace( $string, $pattern ) {
+ return preg_replace( '/ +/', ' ', preg_replace( $pattern, " $1 ", $string ) );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/src/Tokenizer/JaTinySegmenterTokenizer.php b/www/wiki/vendor/onoi/tesa/src/Tokenizer/JaTinySegmenterTokenizer.php
new file mode 100644
index 00000000..d73aacec
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/Tokenizer/JaTinySegmenterTokenizer.php
@@ -0,0 +1,267 @@
+<?php
+
+namespace Onoi\Tesa\Tokenizer;
+
+use RuntimeException;
+
+/**
+ * PHP Version of the TinySegmenter as a super compact Japanese tokenizer.
+ * - https://github.com/setchi/codeute/blob/71c09c86cd1ce1cf9c8ca4d20b1db60b3784227a/fuel/app/classes/model/lib/tiny_segmenter.php
+ *
+ * TinySegmenter was originally developed by Taku Kudo <taku(at)chasen.org>.
+ * Pulished under the BSD license http://chasen.org/~taku/software/TinySegmenter/LICENCE.txt
+ *
+ * PHP Version was developed by xnights <programming.magic(at)gmail.com>.
+ * For details, see http://programming-magic.com/?id=172
+ *
+ * The model is based on the http://research.nii.ac.jp/src/list.html corpus
+ * together with an optimized L1-norm regularization.
+ *
+ * - https://github.com/shogo82148/TinySegmenterMaker
+ *
+ * @since 0.1
+ */
+class JaTinySegmenterTokenizer implements Tokenizer {
+
+ private $patterns_ = array(
+ "[一二三四五六七八九十百千万億兆]"=>"M", // numbers (japanese)
+ "[一-龠々〆ヵヶ]"=>"H", // kanji & misc characters
+ "[ぁ-ん]"=>"I", // hiragana
+ "[ァ-ヴーア-ン゙ー]"=>"K", // katakana
+ "[a-zA-Za-zA-Z]"=>"A", // ascii / romaji letters
+ "[0-90-9]"=>"N", // ascii / romaji numbers
+ );
+
+ /**
+ * @var Tokenizer
+ */
+ private $tokenizer;
+
+ /**
+ * This is kept static on purpose.
+ * @var array
+ */
+ private static $model;
+
+ /**
+ * @var string
+ */
+ private $modelFile;
+
+ /**
+ * @since 0.1
+ *
+ * @param Tokenizer $tokenizer
+ */
+ public function __construct( Tokenizer $tokenizer = null ) {
+ $this->tokenizer = $tokenizer;
+ }
+
+ /**
+ * @since 0.1
+ *
+ * {@inheritDoc}
+ */
+ public function setOption( $name, $value ) {
+ if ( $this->tokenizer !== null ) {
+ $this->tokenizer->setOption( $name, $value );
+ }
+ }
+
+ /**
+ * @since 0.1
+ *
+ * {@inheritDoc}
+ */
+ public function isWordTokenizer() {
+ return false;
+ }
+
+ /**
+ * @since 0.1
+ *
+ * {@inheritDoc}
+ */
+ public function tokenize( $string ) {
+
+ if ( $this->tokenizer !== null ) {
+ $string = implode( " ", $this->tokenizer->tokenize( $string ) );
+ }
+
+ return $this->loadModel()->segment( $string );
+ }
+
+ private function loadModel() {
+
+ if ( self::$model !== null ) {
+ return $this;
+ }
+
+ $contents = null;
+ $file = __DIR__ . '/model/rwcp.model.json';
+
+ if ( ( $contents = @file_get_contents( $file ) ) !== false ) {
+ self::$model = json_decode( $contents, true );
+ }
+
+ if ( $contents === false || json_last_error() !== JSON_ERROR_NONE ) {
+ throw new RuntimeException( "Couldn't read the model from {$file}." );
+ }
+
+ return $this;
+ }
+
+ protected function segment( $input, $encoding = null ) {
+
+ if ( !$input ) {
+ return array();
+ }
+
+ if ( !$encoding ) {
+ $encoding = mb_detect_encoding( $input );
+ }
+
+ if ( $encoding !== 'UTF-8' ) {
+ $input = mb_convert_encoding( $input, 'UTF-8', $encoding );
+ }
+
+ $result = array();
+ $seg = array( "B3", "B2", "B1" );
+
+ $ctype = array( "O", "O", "O" );
+ $o = $this->mb_string_to_array_( $input );
+
+ for ( $i = 0; $i<count($o); ++$i ) {
+ $seg[] = $o[$i];
+ $ctype[] = $this->ctype_( $o[$i] );
+ }
+
+ $seg[] = "E1";
+ $seg[] = "E2";
+ $seg[] = "E3";
+ $ctype[] = "O";
+ $ctype[] = "O";
+ $ctype[] = "O";
+ $word = $seg[3];
+ $p1 = "U";
+ $p2 = "U";
+ $p3 = "U";
+
+ for($i = 4; $i<count($seg)-3; ++$i){
+ $score = self::$model["BIAS"];
+ $w1 = $seg[$i-3];
+ $w2 = $seg[$i-2];
+ $w3 = $seg[$i-1];
+ $w4 = $seg[$i];
+ $w5 = $seg[$i+1];
+ $w6 = $seg[$i+2];
+ $c1 = $ctype[$i-3];
+ $c2 = $ctype[$i-2];
+ $c3 = $ctype[$i-1];
+ $c4 = $ctype[$i];
+ $c5 = $ctype[$i+1];
+ $c6 = $ctype[$i+2];
+ $score += $this->ts_(@self::$model["UP1"][$p1]);
+ $score += $this->ts_(@self::$model["UP2"][$p2]);
+ $score += $this->ts_(@self::$model["UP3"][$p3]);
+ $score += $this->ts_(@self::$model["BP1"][$p1 . $p2]);
+ $score += $this->ts_(@self::$model["BP2"][$p2 . $p3]);
+ $score += $this->ts_(@self::$model["UW1"][$w1]);
+ $score += $this->ts_(@self::$model["UW2"][$w2]);
+ $score += $this->ts_(@self::$model["UW3"][$w3]);
+ $score += $this->ts_(@self::$model["UW4"][$w4]);
+ $score += $this->ts_(@self::$model["UW5"][$w5]);
+ $score += $this->ts_(@self::$model["UW6"][$w6]);
+ $score += $this->ts_(@self::$model["BW1"][$w2 . $w3]);
+ $score += $this->ts_(@self::$model["BW2"][$w3 . $w4]);
+ $score += $this->ts_(@self::$model["BW3"][$w4 . $w5]);
+ $score += $this->ts_(@self::$model["TW1"][$w1 . $w2 . $w3]);
+ $score += $this->ts_(@self::$model["TW2"][$w2 . $w3 . $w4]);
+ $score += $this->ts_(@self::$model["TW3"][$w3 . $w4 . $w5]);
+ $score += $this->ts_(@self::$model["TW4"][$w4 . $w5 . $w6]);
+ $score += $this->ts_(@self::$model["UC1"][$c1]);
+ $score += $this->ts_(@self::$model["UC2"][$c2]);
+ $score += $this->ts_(@self::$model["UC3"][$c3]);
+ $score += $this->ts_(@self::$model["UC4"][$c4]);
+ $score += $this->ts_(@self::$model["UC5"][$c5]);
+ $score += $this->ts_(@self::$model["UC6"][$c6]);
+ $score += $this->ts_(@self::$model["BC1"][$c2 . $c3]);
+ $score += $this->ts_(@self::$model["BC2"][$c3 . $c4]);
+ $score += $this->ts_(@self::$model["BC3"][$c4 . $c5]);
+ $score += $this->ts_(@self::$model["TC1"][$c1 . $c2 . $c3]);
+ $score += $this->ts_(@self::$model["TC2"][$c2 . $c3 . $c4]);
+ $score += $this->ts_(@self::$model["TC3"][$c3 . $c4 . $c5]);
+ $score += $this->ts_(@self::$model["TC4"][$c4 . $c5 . $c6]);
+ // $score += $this->ts_(@self::$model["TC5"][$c4 . $c5 . $c6]);
+ $score += $this->ts_(@self::$model["UQ1"][$p1 . $c1]);
+ $score += $this->ts_(@self::$model["UQ2"][$p2 . $c2]);
+ $score += $this->ts_(@self::$model["UQ1"][$p3 . $c3]);
+ $score += $this->ts_(@self::$model["BQ1"][$p2 . $c2 . $c3]);
+ $score += $this->ts_(@self::$model["BQ2"][$p2 . $c3 . $c4]);
+ $score += $this->ts_(@self::$model["BQ3"][$p3 . $c2 . $c3]);
+ $score += $this->ts_(@self::$model["BQ4"][$p3 . $c3 . $c4]);
+ $score += $this->ts_(@self::$model["TQ1"][$p2 . $c1 . $c2 . $c3]);
+ $score += $this->ts_(@self::$model["TQ2"][$p2 . $c2 . $c3 . $c4]);
+ $score += $this->ts_(@self::$model["TQ3"][$p3 . $c1 . $c2 . $c3]);
+ $score += $this->ts_(@self::$model["TQ4"][$p3 . $c2 . $c3 . $c4]);
+
+ $p = "O";
+
+ if ( $score > 0 ) {
+
+ if ( $word !== '' && $word !== ' ' ) {
+ $result[] = $word;
+ }
+
+ $word = "";
+ $p = "B";
+ }
+
+ $p1 = $p2;
+ $p2 = $p3;
+ $p3 = $p;
+
+ if ( $seg[$i] !== '' && $seg[$i] !== ' ' ) {
+ $word .= $seg[$i];
+ }
+ }
+
+ $result[] = $word;
+
+ if ( $encoding !== 'UTF-8') {
+ foreach( $result as &$str ) {
+ $str = mb_convert_encoding( $str, $encoding, 'UTF-8' );
+ }
+ }
+
+ return $result;
+ }
+
+ private function ctype_( $str ) {
+
+ foreach( $this->patterns_ as $pattern => $type ) {
+ if( preg_match( '/'.$pattern.'/u', $str ) ) {
+ return $type;
+ }
+ }
+
+ return "O";
+ }
+
+ private function ts_( $v ) {
+ return $v ? $v : 0;
+ }
+
+ private function mb_string_to_array_( $str, $encoding = 'UTF-8' ) {
+
+ $result = array();
+ $length = mb_strlen( $str, $encoding );
+
+ for ( $i=0; $i < $length; ++$i ) {
+ $result[] = mb_substr( $str, $i, 1, $encoding );
+ }
+
+ return $result;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/src/Tokenizer/NGramTokenizer.php b/www/wiki/vendor/onoi/tesa/src/Tokenizer/NGramTokenizer.php
new file mode 100644
index 00000000..6f6f7676
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/Tokenizer/NGramTokenizer.php
@@ -0,0 +1,129 @@
+<?php
+
+namespace Onoi\Tesa\Tokenizer;
+
+/**
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class NGramTokenizer implements Tokenizer {
+
+ /**
+ * @var Tokenizer
+ */
+ private $tokenizer;
+
+ /**
+ * @var integer
+ */
+ private $ngramSize = 2;
+
+ /**
+ * @var boolean
+ */
+ private $withMarker = false;
+
+ /**
+ * @since 0.1
+ *
+ * @param Tokenizer $tokenizer
+ * @param integer $ngramSize
+ */
+ public function __construct( Tokenizer $tokenizer = null, $ngramSize = 2 ) {
+ $this->tokenizer = $tokenizer;
+ $this->ngramSize = (int)$ngramSize;
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @param boolean $withMarker
+ */
+ public function withMarker( $withMarker ) {
+ $this->withMarker = (bool)$withMarker;
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @param integer $ngramSize
+ */
+ public function setNgramSize( $ngramSize ) {
+ $this->ngramSize = (int)$ngramSize;
+ }
+
+ /**
+ * @since 0.1
+ *
+ * {@inheritDoc}
+ */
+ public function setOption( $name, $value ) {
+ if ( $this->tokenizer !== null ) {
+ $this->tokenizer->setOption( $name, $value );
+ }
+ }
+
+ /**
+ * @since 0.1
+ *
+ * {@inheritDoc}
+ */
+ public function isWordTokenizer() {
+ return false;
+ }
+
+ /**
+ * @since 0.1
+ *
+ * {@inheritDoc}
+ */
+ public function tokenize( $string ) {
+
+ if ( $this->tokenizer !== null ) {
+ $string = implode( " ", $this->tokenizer->tokenize( $string ) );
+ }
+
+ $result = $this->createNGrams( $string, $this->ngramSize, $this->withMarker );
+
+ if ( $result !== false ) {
+ return $result;
+ }
+
+ return array();
+ }
+
+ private function createNGrams( $text, $ngramSize, $withMarker ) {
+
+ $ngramList = array();
+
+ // Identify the beginning-of-word and end-of-word
+ if ( $withMarker ) {
+ $text = '_' . str_replace( ' ', '_', $text );
+ }
+
+ $text = mb_strtolower( $text );
+ $textLength = mb_strlen( $text, 'UTF-8' );
+
+ for ( $i = 0; $i < $textLength; ++$i ) {
+
+ // Those failing the length of the ngramSize are skipped
+ if ( !$withMarker && $i + $ngramSize > $textLength ) {
+ continue;
+ }
+
+ $ngram = mb_substr( $text, $i, $ngramSize, 'UTF-8' );
+
+ // str_pad has issues with utf-8 length
+ if ( $withMarker && ( $ngl = mb_strlen( $ngram, 'UTF-8' ) ) < $ngramSize ) {
+ $ngram = $ngram . str_repeat( '_', ( $ngramSize - $ngl ) );
+ }
+
+ $ngramList[] = $ngram;
+ }
+
+ return array_values( $ngramList );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/src/Tokenizer/PunctuationRegExTokenizer.php b/www/wiki/vendor/onoi/tesa/src/Tokenizer/PunctuationRegExTokenizer.php
new file mode 100644
index 00000000..3716da50
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/Tokenizer/PunctuationRegExTokenizer.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace Onoi\Tesa\Tokenizer;
+
+/**
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class PunctuationRegExTokenizer implements Tokenizer {
+
+ /**
+ * @var Tokenizer
+ */
+ private $tokenizer;
+
+ /**
+ * @var string
+ */
+ private $patternExemption = '';
+
+ /**
+ * @since 0.1
+ *
+ * @param Tokenizer|null $tokenizer
+ */
+ public function __construct( Tokenizer $tokenizer = null ) {
+ $this->tokenizer = $tokenizer;
+ }
+
+ /**
+ * @since 0.1
+ *
+ * {@inheritDoc}
+ */
+ public function setOption( $name, $value ) {
+
+ if ( $this->tokenizer !== null ) {
+ $this->tokenizer->setOption( $name, $value );
+ }
+
+ if ( $name === self::REGEX_EXEMPTION ) {
+ $this->patternExemption = $value;
+ }
+ }
+
+ /**
+ * @since 0.1
+ *
+ * {@inheritDoc}
+ */
+ public function isWordTokenizer() {
+ return $this->tokenizer !== null ? $this->tokenizer->isWordTokenizer() : true;
+ }
+
+ /**
+ * @since 0.1
+ *
+ * @param string $string
+ *
+ * @return array|false
+ */
+ public function tokenize( $string ) {
+
+ if ( $this->tokenizer !== null ) {
+ $string = implode( " ", $this->tokenizer->tokenize( $string ) );
+ }
+
+ $pattern = str_replace(
+ $this->patternExemption,
+ '',
+ '_-・,、;:!?.。…◆★◇□■()【】《》〈〉;:“”"〃'`[]{}「」@*\/&#%`^+<=>|~≪≫─$"_\-・,、;:!?.。()[\]{}「」@*\/&#%`^+<=>|~«»$"\s'
+ );
+
+ $result = preg_split( '/[' . $pattern . ']+/u', $string, null, PREG_SPLIT_NO_EMPTY );
+
+ if ( $result === false ) {
+ $result = array();
+ }
+
+ return $result;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/src/Tokenizer/Tokenizer.php b/www/wiki/vendor/onoi/tesa/src/Tokenizer/Tokenizer.php
new file mode 100644
index 00000000..edc46ac8
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/Tokenizer/Tokenizer.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Onoi\Tesa\Tokenizer;
+
+/**
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+interface Tokenizer {
+
+ /**
+ * Under some circumstances (used as search term) specific characters should
+ * be exempted from the regular expression.
+ */
+ const REGEX_EXEMPTION = 'regex.exemption';
+
+ /**
+ * @since 0.1
+ *
+ * @param string $name
+ * @param mixed $value
+ */
+ public function setOption( $name, $value );
+
+ /**
+ * Some simple tokenizer may not rely on whitespaces to build
+ * tokens.
+ *
+ * @since 0.1
+ *
+ * @return boolean
+ */
+ public function isWordTokenizer();
+
+ /**
+ * @since 0.1
+ *
+ * @param string $string
+ *
+ * @return array|false
+ */
+ public function tokenize( $string );
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/src/Tokenizer/model/rwcp.model.json b/www/wiki/vendor/onoi/tesa/src/Tokenizer/model/rwcp.model.json
new file mode 100644
index 00000000..d67655fd
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/Tokenizer/model/rwcp.model.json
@@ -0,0 +1,45 @@
+{
+ "BIAS":-332,
+ "BC1": {"HH":6,"II":2461,"KH":406,"OH":-1378},
+ "BC2": {"AA":-3267,"AI":2744,"AN":-878,"HH":-4070,"HM":-1711,"HN":4012,"HO":3761,"IA":1327,"IH":-1184,"II":-1332,"IK":1721,"IO":5492,"KI":3831,"KK":-8741,"MH":-3132,"MK":3334,"OO":-2920},
+ "BC3": {"HH":996,"HI":626,"HK":-721,"HN":-1307,"HO":-836,"IH":-301,"KK":2762,"MK":1079,"MM":4034,"OA":-1652,"OH":266},
+ "BP1": {"BB":295,"OB":304,"OO":-125,"UB":352},
+ "BP2": {"BO":60,"OO":-1762},
+ "BQ1": {"BHH":1150,"BHM":1521,"BII":-1158,"BIM":886,"BMH":1208,"BNH":449,"BOH":-91,"BOO":-2597,"OHI":451,"OIH":-296,"OKA":1851,"OKH":-1020,"OKK":904,"OOO":2965},
+ "BQ2": {"BHH":118,"BHI":-1159,"BHM":466,"BIH":-919,"BKK":-1720,"BKO":864,"OHH":-1139,"OHM":-181,"OIH":153,"UHI":-1146},
+ "BQ3": {"BHH":-792,"BHI":2664,"BII":-299,"BKI":419,"BMH":937,"BMM":8335,"BNN":998,"BOH":775,"OHH":2174,"OHM":439,"OII":280,"OKH":1798,"OKI":-793,"OKO":-2242,"OMH":-2402,"OOO":11699},
+ "BQ4": {"BHH":-3895,"BIH":3761,"BII":-4654,"BIK":1348,"BKK":-1806,"BMI":-3385,"BOO":-12396,"OAH":926,"OHH":266,"OHK":-2036,"ONN":-973},
+ "BW1": {",と":660,",同":727,"B1あ":1404,"B1同":542,"、と":660,"、同":727,"」と":1682,"あっ":1505,"いう":1743,"いっ":-2055,"いる":672,"うし":-4817,"うん":665,"から":3472,"がら":600,"こう":-790,"こと":2083,"こん":-1262,"さら":-4143,"さん":4573,"した":2641,"して":1104,"すで":-3399,"そこ":1977,"それ":-871,"たち":1122,"ため":601,"った":3463,"つい":-802,"てい":805,"てき":1249,"でき":1127,"です":3445,"では":844,"とい":-4915,"とみ":1922,"どこ":3887,"ない":5713,"なっ":3015,"など":7379,"なん":-1113,"にし":2468,"には":1498,"にも":1671,"に対":-912,"の一":-501,"の中":741,"ませ":2448,"まで":1711,"まま":2600,"まる":-2155,"やむ":-1947,"よっ":-2565,"れた":2369,"れで":-913,"をし":1860,"を見":731,"亡く":-1886,"京都":2558,"取り":-2784,"大き":-2604,"大阪":1497,"平方":-2314,"引き":-1336,"日本":-195,"本当":-2423,"毎日":-2113,"目指":-724,"B1あ":1404,"B1同":542,"」と":1682},
+ "BW2": {"..":-11822,"11":-669,"――":-5730,"−−":-13175,"いう":-1609,"うか":2490,"かし":-1350,"かも":-602,"から":-7194,"かれ":4612,"がい":853,"がら":-3198,"きた":1941,"くな":-1597,"こと":-8392,"この":-4193,"させ":4533,"され":13168,"さん":-3977,"しい":-1819,"しか":-545,"した":5078,"して":972,"しな":939,"その":-3744,"たい":-1253,"たた":-662,"ただ":-3857,"たち":-786,"たと":1224,"たは":-939,"った":4589,"って":1647,"っと":-2094,"てい":6144,"てき":3640,"てく":2551,"ては":-3110,"ても":-3065,"でい":2666,"でき":-1528,"でし":-3828,"です":-4761,"でも":-4203,"とい":1890,"とこ":-1746,"とと":-2279,"との":720,"とみ":5168,"とも":-3941,"ない":-2488,"なが":-1313,"など":-6509,"なの":2614,"なん":3099,"にお":-1615,"にし":2748,"にな":2454,"によ":-7236,"に対":-14943,"に従":-4688,"に関":-11388,"のか":2093,"ので":-7059,"のに":-6041,"のの":-6125,"はい":1073,"はが":-1033,"はず":-2532,"ばれ":1813,"まし":-1316,"まで":-6621,"まれ":5409,"めて":-3153,"もい":2230,"もの":-10713,"らか":-944,"らし":-1611,"らに":-1897,"りし":651,"りま":1620,"れた":4270,"れて":849,"れば":4114,"ろう":6067,"われ":7901,"を通":-11877,"んだ":728,"んな":-4115,"一人":602,"一方":-1375,"一日":970,"一部":-1051,"上が":-4479,"会社":-1116,"出て":2163,"分の":-7758,"同党":970,"同日":-913,"大阪":-2471,"委員":-1250,"少な":-1050,"年度":-8669,"年間":-1626,"府県":-2363,"手権":-1982,"新聞":-4066,"日新":-722,"日本":-7068,"日米":3372,"曜日":-601,"朝鮮":-2355,"本人":-2697,"東京":-1543,"然と":-1384,"社会":-1276,"立て":-990,"第に":-1612,"米国":-4268,"11":-669},
+ "BW3": {"あた":-2194,"あり":719,"ある":3846,"い.":-1185,"い。":-1185,"いい":5308,"いえ":2079,"いく":3029,"いた":2056,"いっ":1883,"いる":5600,"いわ":1527,"うち":1117,"うと":4798,"えと":1454,"か.":2857,"か。":2857,"かけ":-743,"かっ":-4098,"かに":-669,"から":6520,"かり":-2670,"が,":1816,"が、":1816,"がき":-4855,"がけ":-1127,"がっ":-913,"がら":-4977,"がり":-2064,"きた":1645,"けど":1374,"こと":7397,"この":1542,"ころ":-2757,"さい":-714,"さを":976,"し,":1557,"し、":1557,"しい":-3714,"した":3562,"して":1449,"しな":2608,"しま":1200,"す.":-1310,"す。":-1310,"する":6521,"ず,":3426,"ず、":3426,"ずに":841,"そう":428,"た.":8875,"た。":8875,"たい":-594,"たの":812,"たり":-1183,"たる":-853,"だ.":4098,"だ。":4098,"だっ":1004,"った":-4748,"って":300,"てい":6240,"てお":855,"ても":302,"です":1437,"でに":-1482,"では":2295,"とう":-1387,"とし":2266,"との":541,"とも":-3543,"どう":4664,"ない":1796,"なく":-903,"など":2135,"に,":-1021,"に、":-1021,"にし":1771,"にな":1906,"には":2644,"の,":-724,"の、":-724,"の子":-1000,"は,":1337,"は、":1337,"べき":2181,"まし":1113,"ます":6943,"まっ":-1549,"まで":6154,"まれ":-793,"らし":1479,"られ":6820,"るる":3818,"れ,":854,"れ、":854,"れた":1850,"れて":1375,"れば":-3246,"れる":1091,"われ":-605,"んだ":606,"んで":798,"カ月":990,"会議":860,"入り":1232,"大会":2217,"始め":1681,"市":965,"新聞":-5055,"日,":974,"日、":974,"社会":2024,"カ月":990},
+ "TC1": {"AAA":1093,"HHH":1029,"HHM":580,"HII":998,"HOH":-390,"HOM":-331,"IHI":1169,"IOH":-142,"IOI":-1015,"IOM":467,"MMH":187,"OOI":-1832},
+ "TC2": {"HHO":2088,"HII":-1023,"HMM":-1154,"IHI":-1965,"KKH":703,"OII":-2649},
+ "TC3": {"AAA":-294,"HHH":346,"HHI":-341,"HII":-1088,"HIK":731,"HOH":-1486,"IHH":128,"IHI":-3041,"IHO":-1935,"IIH":-825,"IIM":-1035,"IOI":-542,"KHH":-1216,"KKA":491,"KKH":-1217,"KOK":-1009,"MHH":-2694,"MHM":-457,"MHO":123,"MMH":-471,"NNH":-1689,"NNO":662,"OHO":-3393},
+ "TC4": {"HHH":-203,"HHI":1344,"HHK":365,"HHM":-122,"HHN":182,"HHO":669,"HIH":804,"HII":679,"HOH":446,"IHH":695,"IHO":-2324,"IIH":321,"III":1497,"IIO":656,"IOO":54,"KAK":4845,"KKA":3386,"KKK":3065,"MHH":-405,"MHI":201,"MMH":-241,"MMM":661,"MOM":841},
+ "TQ1": {"BHHH":-227,"BHHI":316,"BHIH":-132,"BIHH":60,"BIII":1595,"BNHH":-744,"BOHH":225,"BOOO":-908,"OAKK":482,"OHHH":281,"OHIH":249,"OIHI":200,"OIIH":-68},
+ "TQ2": {"BIHH":-1401,"BIII":-1033,"BKAK":-543,"BOOO":-5591},
+ "TQ3": {"BHHH":478,"BHHM":-1073,"BHIH":222,"BHII":-504,"BIIH":-116,"BIII":-105,"BMHI":-863,"BMHM":-464,"BOMH":620,"OHHH":346,"OHHI":1729,"OHII":997,"OHMH":481,"OIHH":623,"OIIH":1344,"OKAK":2792,"OKHH":587,"OKKA":679,"OOHH":110,"OOII":-685},
+ "TQ4": {"BHHH":-721,"BHHM":-3604,"BHII":-966,"BIIH":-607,"BIII":-2181,"OAAA":-2763,"OAKK":180,"OHHH":-294,"OHHI":2446,"OHHO":480,"OHIH":-1573,"OIHH":1935,"OIHI":-493,"OIIH":626,"OIII":-4007,"OKAK":-8156},
+ "TW1": {"につい":-4681,"東京都":2026},
+ "TW2": {"ある程":-2049,"いった":-1256,"ころが":-2434,"しょう":3873,"その後":-4430,"だって":-1049,"ていた":1833,"として":-4657,"ともに":-4517,"もので":1882,"一気に":-792,"初めて":-1512,"同時に":-8097,"大きな":-1255,"対して":-2721,"社会党":-3216},
+ "TW3": {"いただ":-1734,"してい":1314,"として":-4314,"につい":-5483,"にとっ":-5989,"に当た":-6247,"ので,":-727,"ので、":-727,"のもの":-600,"れから":-3752,"十二月":-2287},
+ "TW4": {"いう.":8576,"いう。":8576,"からな":-2348,"してい":2958,"たが,":1516,"たが、":1516,"ている":1538,"という":1349,"ました":5543,"ません":1097,"ようと":-4258,"よると":5865},
+ "UC1": {"A":484,"K":93,"M":645,"O":-505},
+ "UC2": {"A":819,"H":1059,"I":409,"M":3987,"N":5775,"O":646},
+ "UC3": {"A":-1370,"I":2311},
+ "UC4": {"A":-2643,"H":1809,"I":-1032,"K":-3450,"M":3565,"N":3876,"O":6646},
+ "UC5": {"H":313,"I":-1238,"K":-799,"M":539,"O":-831},
+ "UC6": {"H":-506,"I":-253,"K":87,"M":247,"O":-387},
+ "UP1": {"O":-214},
+ "UP2": {"B":69,"O":935},
+ "UP3": {"B":189},
+ "UQ1": {"BH":21,"BI":-12,"BK":-99,"BN":142,"BO":-56,"OH":-95,"OI":477,"OK":410,"OO":-2422},
+ "UQ2": {"BH":216,"BI":113,"OK":1759},
+ "UQ3": {"BA":-479,"BH":42,"BI":1913,"BK":-7198,"BM":3160,"BN":6427,"BO":14761,"OI":-827,"ON":-3212},
+ "UW1": {",":156,"、":156,"「":-463,"あ":-941,"う":-127,"が":-553,"き":121,"こ":505,"で":-201,"と":-547,"ど":-123,"に":-789,"の":-185,"は":-847,"も":-466,"や":-470,"よ":182,"ら":-292,"り":208,"れ":169,"を":-446,"ん":-137,"・":-135,"主":-402,"京":-268,"区":-912,"午":871,"国":-460,"大":561,"委":729,"市":-411,"日":-141,"理":361,"生":-408,"県":-386,"都":-718,"「":-463,"・":-135},
+ "UW2": {",":-829,"、":-829,"〇":892,"「":-645,"」":3145,"あ":-538,"い":505,"う":134,"お":-502,"か":1454,"が":-856,"く":-412,"こ":1141,"さ":878,"ざ":540,"し":1529,"す":-675,"せ":300,"そ":-1011,"た":188,"だ":1837,"つ":-949,"て":-291,"で":-268,"と":-981,"ど":1273,"な":1063,"に":-1764,"の":130,"は":-409,"ひ":-1273,"べ":1261,"ま":600,"も":-1263,"や":-402,"よ":1639,"り":-579,"る":-694,"れ":571,"を":-2516,"ん":2095,"ア":-587,"カ":306,"キ":568,"ッ":831,"三":-758,"不":-2150,"世":-302,"中":-968,"主":-861,"事":492,"人":-123,"会":978,"保":362,"入":548,"初":-3025,"副":-1566,"北":-3414,"区":-422,"大":-1769,"天":-865,"太":-483,"子":-1519,"学":760,"実":1023,"小":-2009,"市":-813,"年":-1060,"強":1067,"手":-1519,"揺":-1033,"政":1522,"文":-1355,"新":-1682,"日":-1815,"明":-1462,"最":-630,"朝":-1843,"本":-1650,"東":-931,"果":-665,"次":-2378,"民":-180,"気":-1740,"理":752,"発":529,"目":-1584,"相":-242,"県":-1165,"立":-763,"第":810,"米":509,"自":-1353,"行":838,"西":-744,"見":-3874,"調":1010,"議":1198,"込":3041,"開":1758,"間":-1257,"「":-645,"」":3145,"ッ":831,"ア":-587,"カ":306,"キ":568},
+ "UW3": {",":4889,"1":-800,"−":-1723,"、":4889,"々":-2311,"〇":5827,"」":2670,"〓":-3573,"あ":-2696,"い":1006,"う":2342,"え":1983,"お":-4864,"か":-1163,"が":3271,"く":1004,"け":388,"げ":401,"こ":-3552,"ご":-3116,"さ":-1058,"し":-395,"す":584,"せ":3685,"そ":-5228,"た":842,"ち":-521,"っ":-1444,"つ":-1081,"て":6167,"で":2318,"と":1691,"ど":-899,"な":-2788,"に":2745,"の":4056,"は":4555,"ひ":-2171,"ふ":-1798,"へ":1199,"ほ":-5516,"ま":-4384,"み":-120,"め":1205,"も":2323,"や":-788,"よ":-202,"ら":727,"り":649,"る":5905,"れ":2773,"わ":-1207,"を":6620,"ん":-518,"ア":551,"グ":1319,"ス":874,"ッ":-1350,"ト":521,"ム":1109,"ル":1591,"ロ":2201,"ン":278,"・":-3794,"一":-1619,"下":-1759,"世":-2087,"両":3815,"中":653,"主":-758,"予":-1193,"二":974,"人":2742,"今":792,"他":1889,"以":-1368,"低":811,"何":4265,"作":-361,"保":-2439,"元":4858,"党":3593,"全":1574,"公":-3030,"六":755,"共":-1880,"円":5807,"再":3095,"分":457,"初":2475,"別":1129,"前":2286,"副":4437,"力":365,"動":-949,"務":-1872,"化":1327,"北":-1038,"区":4646,"千":-2309,"午":-783,"協":-1006,"口":483,"右":1233,"各":3588,"合":-241,"同":3906,"和":-837,"員":4513,"国":642,"型":1389,"場":1219,"外":-241,"妻":2016,"学":-1356,"安":-423,"実":-1008,"家":1078,"小":-513,"少":-3102,"州":1155,"市":3197,"平":-1804,"年":2416,"広":-1030,"府":1605,"度":1452,"建":-2352,"当":-3885,"得":1905,"思":-1291,"性":1822,"戸":-488,"指":-3973,"政":-2013,"教":-1479,"数":3222,"文":-1489,"新":1764,"日":2099,"旧":5792,"昨":-661,"時":-1248,"曜":-951,"最":-937,"月":4125,"期":360,"李":3094,"村":364,"東":-805,"核":5156,"森":2438,"業":484,"氏":2613,"民":-1694,"決":-1073,"法":1868,"海":-495,"無":979,"物":461,"特":-3850,"生":-273,"用":914,"町":1215,"的":7313,"直":-1835,"省":792,"県":6293,"知":-1528,"私":4231,"税":401,"立":-960,"第":1201,"米":7767,"系":3066,"約":3663,"級":1384,"統":-4229,"総":1163,"線":1255,"者":6457,"能":725,"自":-2869,"英":785,"見":1044,"調":-562,"財":-733,"費":1777,"車":1835,"軍":1375,"込":-1504,"通":-1136,"選":-681,"郎":1026,"郡":4404,"部":1200,"金":2163,"長":421,"開":-1432,"間":1302,"関":-1282,"雨":2009,"電":-1045,"非":2066,"駅":1620,"1":-800,"」":2670,"・":-3794,"ッ":-1350,"ア":551,"グ":1319,"ス":874,"ト":521,"ム":1109,"ル":1591,"ロ":2201,"ン":278},
+ "UW4": {",":3930,".":3508,"―":-4841,"、":3930,"。":3508,"〇":4999,"「":1895,"」":3798,"〓":-5156,"あ":4752,"い":-3435,"う":-640,"え":-2514,"お":2405,"か":530,"が":6006,"き":-4482,"ぎ":-3821,"く":-3788,"け":-4376,"げ":-4734,"こ":2255,"ご":1979,"さ":2864,"し":-843,"じ":-2506,"す":-731,"ず":1251,"せ":181,"そ":4091,"た":5034,"だ":5408,"ち":-3654,"っ":-5882,"つ":-1659,"て":3994,"で":7410,"と":4547,"な":5433,"に":6499,"ぬ":1853,"ね":1413,"の":7396,"は":8578,"ば":1940,"ひ":4249,"び":-4134,"ふ":1345,"へ":6665,"べ":-744,"ほ":1464,"ま":1051,"み":-2082,"む":-882,"め":-5046,"も":4169,"ゃ":-2666,"や":2795,"ょ":-1544,"よ":3351,"ら":-2922,"り":-9726,"る":-14896,"れ":-2613,"ろ":-4570,"わ":-1783,"を":13150,"ん":-2352,"カ":2145,"コ":1789,"セ":1287,"ッ":-724,"ト":-403,"メ":-1635,"ラ":-881,"リ":-541,"ル":-856,"ン":-3637,"・":-4371,"ー":-11870,"一":-2069,"中":2210,"予":782,"事":-190,"井":-1768,"人":1036,"以":544,"会":950,"体":-1286,"作":530,"側":4292,"先":601,"党":-2006,"共":-1212,"内":584,"円":788,"初":1347,"前":1623,"副":3879,"力":-302,"動":-740,"務":-2715,"化":776,"区":4517,"協":1013,"参":1555,"合":-1834,"和":-681,"員":-910,"器":-851,"回":1500,"国":-619,"園":-1200,"地":866,"場":-1410,"塁":-2094,"士":-1413,"多":1067,"大":571,"子":-4802,"学":-1397,"定":-1057,"寺":-809,"小":1910,"屋":-1328,"山":-1500,"島":-2056,"川":-2667,"市":2771,"年":374,"庁":-4556,"後":456,"性":553,"感":916,"所":-1566,"支":856,"改":787,"政":2182,"教":704,"文":522,"方":-856,"日":1798,"時":1829,"最":845,"月":-9066,"木":-485,"来":-442,"校":-360,"業":-1043,"氏":5388,"民":-2716,"気":-910,"沢":-939,"済":-543,"物":-735,"率":672,"球":-1267,"生":-1286,"産":-1101,"田":-2900,"町":1826,"的":2586,"目":922,"省":-3485,"県":2997,"空":-867,"立":-2112,"第":788,"米":2937,"系":786,"約":2171,"経":1146,"統":-1169,"総":940,"線":-994,"署":749,"者":2145,"能":-730,"般":-852,"行":-792,"規":792,"警":-1184,"議":-244,"谷":-1000,"賞":730,"車":-1481,"軍":1158,"輪":-1433,"込":-3370,"近":929,"道":-1291,"選":2596,"郎":-4866,"都":1192,"野":-1100,"銀":-2213,"長":357,"間":-2344,"院":-2297,"際":-2604,"電":-878,"領":-1659,"題":-792,"館":-1984,"首":1749,"高":2120,"「":1895,"」":3798,"・":-4371,"ッ":-724,"ー":-11870,"カ":2145,"コ":1789,"セ":1287,"ト":-403,"メ":-1635,"ラ":-881,"リ":-541,"ル":-856,"ン":-3637},
+ "UW5": {",":465,".":-299,"1":-514,"E2":-32768,"]":-2762,"、":465,"。":-299,"「":363,"あ":1655,"い":331,"う":-503,"え":1199,"お":527,"か":647,"が":-421,"き":1624,"ぎ":1971,"く":312,"げ":-983,"さ":-1537,"し":-1371,"す":-852,"だ":-1186,"ち":1093,"っ":52,"つ":921,"て":-18,"で":-850,"と":-127,"ど":1682,"な":-787,"に":-1224,"の":-635,"は":-578,"べ":1001,"み":502,"め":865,"ゃ":3350,"ょ":854,"り":-208,"る":429,"れ":504,"わ":419,"を":-1264,"ん":327,"イ":241,"ル":451,"ン":-343,"中":-871,"京":722,"会":-1153,"党":-654,"務":3519,"区":-901,"告":848,"員":2104,"大":-1296,"学":-548,"定":1785,"嵐":-1304,"市":-2991,"席":921,"年":1763,"思":872,"所":-814,"挙":1618,"新":-1682,"日":218,"月":-4353,"査":932,"格":1356,"機":-1508,"氏":-1347,"田":240,"町":-3912,"的":-3149,"相":1319,"省":-1052,"県":-4003,"研":-997,"社":-278,"空":-813,"統":1955,"者":-2233,"表":663,"語":-1073,"議":1219,"選":-1018,"郎":-368,"長":786,"間":1191,"題":2368,"館":-689,"1":-514,"E2":-32768,"「":363,"イ":241,"ル":451,"ン":-343},
+ "UW6": {",":227,".":808,"1":-270,"E1":306,"、":227,"。":808,"あ":-307,"う":189,"か":241,"が":-73,"く":-121,"こ":-200,"じ":1782,"す":383,"た":-428,"っ":573,"て":-1014,"で":101,"と":-105,"な":-253,"に":-149,"の":-417,"は":-236,"も":-206,"り":187,"る":-135,"を":195,"ル":-673,"ン":-496,"一":-277,"中":201,"件":-800,"会":624,"前":302,"区":1792,"員":-1212,"委":798,"学":-960,"市":887,"広":-695,"後":535,"業":-697,"相":753,"社":-507,"福":974,"空":-822,"者":1811,"連":463,"郎":1082,"1":-270,"E1":306,"ル":-673,"ン":-496}
+}
diff --git a/www/wiki/vendor/onoi/tesa/src/Transliterator.php b/www/wiki/vendor/onoi/tesa/src/Transliterator.php
new file mode 100644
index 00000000..5a442384
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/src/Transliterator.php
@@ -0,0 +1,302 @@
+<?php
+
+namespace Onoi\Tesa;
+
+/**
+ * Romanization and transliteration support for a selected group of characters.
+ *
+ * @see http://www.loc.gov/catdir/cpso/roman.html
+ * @see https://en.wikipedia.org/wiki/List_of_ISO_transliterations
+ *
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class Transliterator {
+
+ /**
+ * Any change to the mapping of this file should be reflected in a version
+ * change (the version number does not necessarily correlate with the
+ * library version)
+ */
+ const VERSION = '0.2';
+
+ /**
+ * Transliterator supported option flags
+ */
+ const NONE = 0x1;
+ const DIACRITICS = 0x2;
+ const GREEK = 0x4;
+
+ /**
+ * Convert diacritics (e.g. accents) of a string to a Latin representation
+ *
+ * @see http://jsperf.com/latinize
+ *
+ * - Changed the German conversion rules for the diaeresis representation
+ * (Ü to Ue, Ä to ae etc.)
+ * - Added ß/ss
+ *
+ *
+ * @var array
+ */
+ private static $diacriticsMap = array(
+ "Á" => "A", "Ă" => "A", "Ắ" => "A", "Ặ" => "A", "Ằ" => "A", "Ẳ" => "A", "Ẵ" => "A",
+ "Ǎ" => "A", "Â" => "A", "Ấ" => "A", "Ậ" => "A", "Ầ" => "A", "Ẩ" => "A", "Ẫ" => "A",
+ "Ä" => "AE", "Ǟ" => "A", "Ȧ" => "A", "Ǡ" => "A", "Ạ" => "A", "Ȁ" => "A", "À" => "A",
+ "Ả" => "A", "Ȃ" => "A", "Ā" => "A", "Ą" => "A", "Å" => "A", "Ǻ" => "A", "Ḁ" => "A",
+ "Ⱥ" => "A", "Ã" => "A", "Ꜳ" => "AA", "Æ" => "AE", "Ǽ" => "AE", "Ǣ" => "AE", "Ꜵ" => "AO",
+ "Ꜷ" => "AU", "Ꜹ" => "AV", "Ꜻ" => "AV", "Ꜽ" => "AY", "Ḃ" => "B", "Ḅ" => "B", "Ɓ" => "B",
+ "Ḇ" => "B", "Ƀ" => "B", "Ƃ" => "B", "Ć" => "C", "Č" => "C", "Ç" => "C", "Ḉ" => "C",
+ "Ĉ" => "C", "Ċ" => "C", "Ƈ" => "C", "Ȼ" => "C", "Ď" => "D", "Ḑ" => "D", "Ḓ" => "D",
+ "Ḋ" => "D", "Ḍ" => "D", "Ɗ" => "D", "Ḏ" => "D", "Dz" => "D", "Dž" => "D", "Đ" => "D",
+ "Ƌ" => "D", "DZ" => "DZ", "DŽ" => "DZ", "É" => "E", "Ĕ" => "E", "Ě" => "E", "Ȩ" => "E",
+ "Ḝ" => "E", "Ê" => "E", "Ế" => "E", "Ệ" => "E", "Ề" => "E", "Ể" => "E", "Ễ" => "E",
+ "Ḙ" => "E", "Ë" => "E", "Ė" => "E", "Ẹ" => "E", "Ȅ" => "E", "È" => "E", "Ẻ" => "E",
+ "Ȇ" => "E", "Ē" => "E", "Ḗ" => "E", "Ḕ" => "E", "Ę" => "E", "Ɇ" => "E", "Ẽ" => "E",
+ "Ḛ" => "E", "Ꝫ" => "ET", "Ḟ" => "F", "Ƒ" => "F", "Ǵ" => "G", "Ğ" => "G", "Ǧ" => "G",
+ "Ģ" => "G", "Ĝ" => "G", "Ġ" => "G", "Ɠ" => "G", "Ḡ" => "G", "Ǥ" => "G", "Ḫ" => "H",
+ "Ȟ" => "H", "Ḩ" => "H", "Ĥ" => "H", "Ⱨ" => "H", "Ḧ" => "H", "Ḣ" => "H", "Ḥ" => "H",
+ "Ħ" => "H", "Í" => "I", "Ĭ" => "I", "Ǐ" => "I", "Î" => "I", "Ï" => "I", "Ḯ" => "I",
+ "İ" => "I", "Ị" => "I", "Ȉ" => "I", "Ì" => "I", "Ỉ" => "I", "Ȋ" => "I", "Ī" => "I",
+ "Į" => "I", "Ɨ" => "I", "Ĩ" => "I", "Ḭ" => "I", "Ꝺ" => "D", "Ꝼ" => "F", "Ᵹ" => "G",
+ "Ꞃ" => "R", "Ꞅ" => "S", "Ꞇ" => "T", "Ꝭ" => "IS", "Ĵ" => "J", "Ɉ" => "J", "Ḱ" => "K",
+ "Ǩ" => "K", "Ķ" => "K", "Ⱪ" => "K", "Ꝃ" => "K", "Ḳ" => "K", "Ƙ" => "K", "Ḵ" => "K",
+ "Ꝁ" => "K", "Ꝅ" => "K", "Ĺ" => "L", "Ƚ" => "L", "Ľ" => "L", "Ļ" => "L", "Ḽ" => "L",
+ "Ḷ" => "L", "Ḹ" => "L", "Ⱡ" => "L", "Ꝉ" => "L", "Ḻ" => "L", "Ŀ" => "L", "Ɫ" => "L",
+ "Lj" => "L", "Ł" => "L", "LJ" => "LJ", "Ḿ" => "M", "Ṁ" => "M", "Ṃ" => "M", "Ɱ" => "M",
+ "Ń" => "N", "Ň" => "N", "Ņ" => "N", "Ṋ" => "N", "Ṅ" => "N", "Ṇ" => "N", "Ǹ" => "N",
+ "Ɲ" => "N", "Ṉ" => "N", "Ƞ" => "N", "Nj" => "N", "Ñ" => "N", "NJ" => "NJ", "Ó" => "O",
+ "Ŏ" => "O", "Ǒ" => "O", "Ô" => "O", "Ố" => "O", "Ộ" => "O", "Ồ" => "O", "Ổ" => "O",
+ "Ỗ" => "O", "Ö" => "OE", "Ȫ" => "O", "Ȯ" => "O", "Ȱ" => "O", "Ọ" => "O", "Ő" => "O",
+ "Ȍ" => "O", "Ò" => "O", "Ỏ" => "O", "Ơ" => "O", "Ớ" => "O", "Ợ" => "O", "Ờ" => "O",
+ "Ở" => "O", "Ỡ" => "O", "Ȏ" => "O", "Ꝋ" => "O", "Ꝍ" => "O", "Ō" => "O", "Ṓ" => "O",
+ "Ṑ" => "O", "Ɵ" => "O", "Ǫ" => "O", "Ǭ" => "O", "Ø" => "O", "Ǿ" => "O", "Õ" => "O",
+ "Ṍ" => "O", "Ṏ" => "O", "Ȭ" => "O", "Ƣ" => "OI", "Ꝏ" => "OO", "Ɛ" => "E", "Ɔ" => "O",
+ "Ȣ" => "OU", "Ṕ" => "P", "Ṗ" => "P", "Ꝓ" => "P", "Ƥ" => "P", "Ꝕ" => "P", "Ᵽ" => "P",
+ "Ꝑ" => "P", "Ꝙ" => "Q", "Ꝗ" => "Q", "Ŕ" => "R", "Ř" => "R", "Ŗ" => "R", "Ṙ" => "R",
+ "Ṛ" => "R", "Ṝ" => "R", "Ȑ" => "R", "Ȓ" => "R", "Ṟ" => "R", "Ɍ" => "R", "Ɽ" => "R",
+ "Ꜿ" => "C", "Ǝ" => "E", "Ś" => "S", "Ṥ" => "S", "Š" => "S", "Ṧ" => "S", "Ş" => "S",
+ "Ŝ" => "S", "Ș" => "S", "Ṡ" => "S", "Ṣ" => "S", "Ṩ" => "S", "ß" => "ss",
+ "Ť" => "T", "Ţ" => "T",
+ "Ṱ" => "T", "Ț" => "T", "Ⱦ" => "T", "Ṫ" => "T", "Ṭ" => "T", "Ƭ" => "T", "Ṯ" => "T",
+ "Ʈ" => "T", "Ŧ" => "T", "Ɐ" => "A", "Ꞁ" => "L", "Ɯ" => "M", "Ʌ" => "V", "Ꜩ" => "TZ",
+ "Ú" => "U", "Ŭ" => "U", "Ǔ" => "U", "Û" => "U", "Ṷ" => "U", "Ü" => "UE", "Ǘ" => "U",
+ "Ǚ" => "U", "Ǜ" => "U", "Ǖ" => "U", "Ṳ" => "U", "Ụ" => "U", "Ű" => "U", "Ȕ" => "U",
+ "Ù" => "U", "Ủ" => "U", "Ư" => "U", "Ứ" => "U", "Ự" => "U", "Ừ" => "U", "Ử" => "U",
+ "Ữ" => "U", "Ȗ" => "U", "Ū" => "U", "Ṻ" => "U", "Ų" => "U", "Ů" => "U", "Ũ" => "U",
+ "Ṹ" => "U", "Ṵ" => "U", "Ꝟ" => "V", "Ṿ" => "V", "Ʋ" => "V", "Ṽ" => "V", "Ꝡ" => "VY",
+ "Ẃ" => "W", "Ŵ" => "W", "Ẅ" => "W", "Ẇ" => "W", "Ẉ" => "W", "Ẁ" => "W", "Ⱳ" => "W",
+ "Ẍ" => "X", "Ẋ" => "X", "Ý" => "Y", "Ŷ" => "Y", "Ÿ" => "Y", "Ẏ" => "Y", "Ỵ" => "Y",
+ "Ỳ" => "Y", "Ƴ" => "Y", "Ỷ" => "Y", "Ỿ" => "Y", "Ȳ" => "Y", "Ɏ" => "Y", "Ỹ" => "Y",
+ "Ź" => "Z", "Ž" => "Z", "Ẑ" => "Z", "Ⱬ" => "Z", "Ż" => "Z", "Ẓ" => "Z", "Ȥ" => "Z",
+ "Ẕ" => "Z", "Ƶ" => "Z", "IJ" => "IJ", "Œ" => "OE", "ᴀ" => "A", "ᴁ" => "AE", "ʙ" => "B",
+ "ᴃ" => "B", "ᴄ" => "C", "ᴅ" => "D", "ᴇ" => "E", "ꜰ" => "F", "ɢ" => "G", "ʛ" => "G",
+ "ʜ" => "H", "ɪ" => "I", "ʁ" => "R", "ᴊ" => "J", "ᴋ" => "K", "ʟ" => "L", "ᴌ" => "L",
+ "ᴍ" => "M", "ɴ" => "N", "ᴏ" => "O", "ɶ" => "OE", "ᴐ" => "O", "ᴕ" => "OU", "ᴘ" => "P",
+ "ʀ" => "R", "ᴎ" => "N", "ᴙ" => "R", "ꜱ" => "S", "ᴛ" => "T", "ⱻ" => "E", "ᴚ" => "R",
+ "ᴜ" => "U", "ᴠ" => "V", "ᴡ" => "W", "ʏ" => "Y", "ᴢ" => "Z", "á" => "a", "ă" => "a",
+ "ắ" => "a", "ặ" => "a", "ằ" => "a", "ẳ" => "a", "ẵ" => "a", "ǎ" => "a", "â" => "a",
+ "ấ" => "a", "ậ" => "a", "ầ" => "a", "ẩ" => "a", "ẫ" => "a", "ä" => "ae", "ǟ" => "a",
+ "ȧ" => "a", "ǡ" => "a", "ạ" => "a", "ȁ" => "a", "à" => "a", "ả" => "a", "ȃ" => "a",
+ "ā" => "a", "ą" => "a", "ᶏ" => "a", "ẚ" => "a", "å" => "a", "ǻ" => "a", "ḁ" => "a",
+ "ⱥ" => "a", "ã" => "a", "ꜳ" => "aa", "æ" => "ae", "ǽ" => "ae", "ǣ" => "ae", "ꜵ" => "ao",
+ "ꜷ" => "au", "ꜹ" => "av", "ꜻ" => "av", "ꜽ" => "ay", "ḃ" => "b", "ḅ" => "b", "ɓ" => "b",
+ "ḇ" => "b", "ᵬ" => "b", "ᶀ" => "b", "ƀ" => "b", "ƃ" => "b", "ɵ" => "o", "ć" => "c",
+ "č" => "c", "ç" => "c", "ḉ" => "c", "ĉ" => "c", "ɕ" => "c", "ċ" => "c", "ƈ" => "c",
+ "ȼ" => "c", "ď" => "d", "ḑ" => "d", "ḓ" => "d", "ȡ" => "d", "ḋ" => "d", "ḍ" => "d",
+ "ɗ" => "d", "ᶑ" => "d", "ḏ" => "d", "ᵭ" => "d", "ᶁ" => "d", "đ" => "d", "ɖ" => "d",
+ "ƌ" => "d", "ı" => "i", "ȷ" => "j", "ɟ" => "j", "ʄ" => "j", "dz" => "dz", "dž" => "dz",
+ "é" => "e", "ĕ" => "e", "ě" => "e", "ȩ" => "e", "ḝ" => "e", "ê" => "e", "ế" => "e",
+ "ệ" => "e", "ề" => "e", "ể" => "e", "ễ" => "e", "ḙ" => "e", "ë" => "e", "ė" => "e",
+ "ẹ" => "e", "ȅ" => "e", "è" => "e", "ẻ" => "e", "ȇ" => "e", "ē" => "e", "ḗ" => "e",
+ "ḕ" => "e", "ⱸ" => "e", "ę" => "e", "ᶒ" => "e", "ɇ" => "e", "ẽ" => "e", "ḛ" => "e",
+ "ꝫ" => "et", "ḟ" => "f", "ƒ" => "f", "ᵮ" => "f", "ᶂ" => "f", "ǵ" => "g", "ğ" => "g",
+ "ǧ" => "g", "ģ" => "g", "ĝ" => "g", "ġ" => "g", "ɠ" => "g", "ḡ" => "g", "ᶃ" => "g",
+ "ǥ" => "g", "ḫ" => "h", "ȟ" => "h", "ḩ" => "h", "ĥ" => "h", "ⱨ" => "h", "ḧ" => "h",
+ "ḣ" => "h", "ḥ" => "h", "ɦ" => "h", "ẖ" => "h", "ħ" => "h", "ƕ" => "hv", "í" => "i",
+ "ĭ" => "i", "ǐ" => "i", "î" => "i", "ï" => "i", "ḯ" => "i", "ị" => "i", "ȉ" => "i",
+ "ì" => "i", "ỉ" => "i", "ȋ" => "i", "ī" => "i", "į" => "i", "ᶖ" => "i", "ɨ" => "i",
+ "ĩ" => "i", "ḭ" => "i", "ꝺ" => "d", "ꝼ" => "f", "ᵹ" => "g", "ꞃ" => "r", "ꞅ" => "s",
+ "ꞇ" => "t", "ꝭ" => "is", "ǰ" => "j", "ĵ" => "j", "ʝ" => "j", "ɉ" => "j", "ḱ" => "k",
+ "ǩ" => "k", "ķ" => "k", "ⱪ" => "k", "ꝃ" => "k", "ḳ" => "k", "ƙ" => "k", "ḵ" => "k",
+ "ᶄ" => "k", "ꝁ" => "k", "ꝅ" => "k", "ĺ" => "l", "ƚ" => "l", "ɬ" => "l", "ľ" => "l",
+ "ļ" => "l", "ḽ" => "l", "ȴ" => "l", "ḷ" => "l", "ḹ" => "l", "ⱡ" => "l", "ꝉ" => "l",
+ "ḻ" => "l", "ŀ" => "l", "ɫ" => "l", "ᶅ" => "l", "ɭ" => "l", "ł" => "l", "lj" => "lj",
+ "ſ" => "s", "ẜ" => "s", "ẛ" => "s", "ẝ" => "s", "ḿ" => "m", "ṁ" => "m", "ṃ" => "m",
+ "ɱ" => "m", "ᵯ" => "m", "ᶆ" => "m", "ń" => "n", "ň" => "n", "ņ" => "n", "ṋ" => "n",
+ "ȵ" => "n", "ṅ" => "n", "ṇ" => "n", "ǹ" => "n", "ɲ" => "n", "ṉ" => "n", "ƞ" => "n",
+ "ᵰ" => "n", "ᶇ" => "n", "ɳ" => "n", "ñ" => "n", "nj" => "nj", "ó" => "o", "ŏ" => "o",
+ "ǒ" => "o", "ô" => "o", "ố" => "o", "ộ" => "o", "ồ" => "o", "ổ" => "o", "ỗ" => "o",
+ "ö" => "oe", "ȫ" => "o", "ȯ" => "o", "ȱ" => "o", "ọ" => "o", "ő" => "o", "ȍ" => "o",
+ "ò" => "o", "ỏ" => "o", "ơ" => "o", "ớ" => "o", "ợ" => "o", "ờ" => "o", "ở" => "o",
+ "ỡ" => "o", "ȏ" => "o", "ꝋ" => "o", "ꝍ" => "o", "ⱺ" => "o", "ō" => "o", "ṓ" => "o",
+ "ṑ" => "o", "ǫ" => "o", "ǭ" => "o", "ø" => "o", "ǿ" => "o", "õ" => "o", "ṍ" => "o",
+ "ṏ" => "o", "ȭ" => "o", "ƣ" => "oi", "ꝏ" => "oo", "ɛ" => "e", "ᶓ" => "e", "ɔ" => "o",
+ "ᶗ" => "o", "ȣ" => "ou", "ṕ" => "p", "ṗ" => "p", "ꝓ" => "p", "ƥ" => "p", "ᵱ" => "p",
+ "ᶈ" => "p", "ꝕ" => "p", "ᵽ" => "p", "ꝑ" => "p", "ꝙ" => "q", "ʠ" => "q", "ɋ" => "q",
+ "ꝗ" => "q", "ŕ" => "r", "ř" => "r", "ŗ" => "r", "ṙ" => "r", "ṛ" => "r", "ṝ" => "r",
+ "ȑ" => "r", "ɾ" => "r", "ᵳ" => "r", "ȓ" => "r", "ṟ" => "r", "ɼ" => "r", "ᵲ" => "r",
+ "ᶉ" => "r", "ɍ" => "r", "ɽ" => "r", "ↄ" => "c", "ꜿ" => "c", "ɘ" => "e", "ɿ" => "r",
+ "ś" => "s", "ṥ" => "s", "š" => "s", "ṧ" => "s", "ş" => "s", "ŝ" => "s", "ș" => "s",
+ "ṡ" => "s", "ṣ" => "s", "ṩ" => "s", "ʂ" => "s", "ᵴ" => "s", "ᶊ" => "s", "ȿ" => "s",
+ "ɡ" => "g", "ᴑ" => "o", "ᴓ" => "o", "ᴝ" => "u", "ť" => "t", "ţ" => "t", "ṱ" => "t",
+ "ț" => "t", "ȶ" => "t", "ẗ" => "t", "ⱦ" => "t", "ṫ" => "t", "ṭ" => "t", "ƭ" => "t",
+ "ṯ" => "t", "ᵵ" => "t", "ƫ" => "t", "ʈ" => "t", "ŧ" => "t", "ᵺ" => "th", "ɐ" => "a",
+ "ᴂ" => "ae", "ǝ" => "e", "ᵷ" => "g", "ɥ" => "h", "ʮ" => "h", "ʯ" => "h", "ᴉ" => "i",
+ "ʞ" => "k", "ꞁ" => "l", "ɯ" => "m", "ɰ" => "m", "ᴔ" => "oe", "ɹ" => "r", "ɻ" => "r",
+ "ɺ" => "r", "ⱹ" => "r", "ʇ" => "t", "ʌ" => "v", "ʍ" => "w", "ʎ" => "y", "ꜩ" => "tz",
+ "ú" => "u", "ŭ" => "u", "ǔ" => "u", "û" => "u", "ṷ" => "u", "ü" => "ue", "ǘ" => "u",
+ "ǚ" => "u", "ǜ" => "u", "ǖ" => "u", "ṳ" => "u", "ụ" => "u", "ű" => "u", "ȕ" => "u",
+ "ù" => "u", "ủ" => "u", "ư" => "u", "ứ" => "u", "ự" => "u", "ừ" => "u", "ử" => "u",
+ "ữ" => "u", "ȗ" => "u", "ū" => "u", "ṻ" => "u", "ų" => "u", "ᶙ" => "u", "ů" => "u",
+ "ũ" => "u", "ṹ" => "u", "ṵ" => "u", "ᵫ" => "ue", "ꝸ" => "um", "ⱴ" => "v", "ꝟ" => "v",
+ "ṿ" => "v", "ʋ" => "v", "ᶌ" => "v", "ⱱ" => "v", "ṽ" => "v", "ꝡ" => "vy", "ẃ" => "w",
+ "ŵ" => "w", "ẅ" => "w", "ẇ" => "w", "ẉ" => "w", "ẁ" => "w", "ⱳ" => "w", "ẘ" => "w",
+ "ẍ" => "x", "ẋ" => "x", "ᶍ" => "x", "ý" => "y", "ŷ" => "y", "ÿ" => "y", "ẏ" => "y",
+ "ỵ" => "y", "ỳ" => "y", "ƴ" => "y", "ỷ" => "y", "ỿ" => "y", "ȳ" => "y", "ẙ" => "y",
+ "ɏ" => "y", "ỹ" => "y", "ź" => "z", "ž" => "z", "ẑ" => "z", "ʑ" => "z", "ⱬ" => "z",
+ "ż" => "z", "ẓ" => "z", "ȥ" => "z", "ẕ" => "z", "ᵶ" => "z", "ᶎ" => "z", "ʐ" => "z",
+ "ƶ" => "z", "ɀ" => "z", "ff" => "ff", "ffi" => "ffi", "ffl" => "ffl", "fi" => "fi",
+ "fl" => "fl", "ij" => "ij", "œ" => "oe", "st" => "st", "ₐ" => "a", "ₑ" => "e", "ᵢ" => "i",
+ "ⱼ" => "j", "ₒ" => "o", "ᵣ" => "r", "ᵤ" => "u", "ᵥ" => "v", "ₓ" => "x"
+ );
+
+ /**
+ * @see http://en.wikipedia.org/wiki/ISO_843
+ * @see https://en.wikipedia.org/wiki/Romanization_of_Greek
+ *
+ * Resolving diacritic letters can be done using it with option (
+ * Transliterator::DIACRITICS | Transliterator::GREEK )
+ *
+ * @var array
+ */
+ private static $greekMap = array(
+
+ "ου"=> "ou",
+ "Ου"=> "Ou",
+ "Οι"=> "Oi",
+ "οι"=> "oi",
+ "Υι"=> "Yi",
+ "υι"=> "yi",
+ "Ού"=> "Oú",
+ "ού"=> "oú",
+
+ "Α" => "A",
+ "Ά" => "Á",
+ "Β" => "V",
+ "Γ" => "G",
+ "Δ" => "D",
+ "Ε" => "E",
+ "Έ" => "É",
+ "Ζ" => "Z",
+ "Η" => "Ī",
+ "Ή" => "Ī́",
+ "Θ" => "Th",
+ "Ι" => "I",
+ "Ί" => "Í",
+ "Ϊ" => "Ï",
+ "ΐ" => "ḯ",
+ "Κ" => "K",
+ "Λ" => "L",
+ "Μ" => "M",
+ "Ν" => "N",
+ "Ξ" => "X",
+ "Ο" => "O",
+ "Ό" => "Ó",
+ "Π" => "P",
+ "Ρ" => "R",
+ "Σ" => "S",
+ "ς" => "s",
+ "Τ" => "T",
+ "Υ" => "Y",
+ "Ύ" => "Ý",
+ "Ϋ" => "Ÿ",
+ "ΰ" => "ÿ́",
+ "Φ" => "F",
+ "Χ" => "Ch",
+ "Ψ" => "Ps",
+ "Ω" => "Ō",
+ "Ώ" => "Ṓ",
+ "α" => "a",
+ "ά" => "á",
+ "β" => "v",
+ "γ" => "g",
+ "δ" => "d",
+ "ε" => "e",
+ "έ" => "é",
+ "ζ" => "z",
+ "η" => "ī",
+ "ή" => "ī́",
+ "θ" => "th",
+ "ι" => "i",
+ "ί" => "í",
+ "ϊ" => "ï",
+ "κ" => "k",
+ "λ" => "l",
+ "μ" => "m",
+ "ν" => "n",
+ "ξ" => "x",
+ "ο" => "o",
+ "ό" => "ó",
+ "π" => "p",
+ "ρ" => "r",
+ "σ" => "s",
+ "τ" => "t",
+ "υ" => "y",
+ "ύ" => "ý",
+ "ϋ" => "ÿ",
+ "φ" => "f",
+ "χ" => "ch",
+ "ψ" => "ps",
+ "ω" => "ō",
+ "ώ" => "ṓ",
+ );
+
+ /**
+ * @var array
+ */
+ private static $transliterationMap = array();
+
+ /**
+ * @since 0.1
+ *
+ * @param string $string
+ * @param integer $flag
+ *
+ * @return string
+ */
+ public static function transliterate( $string, $flag = self::DIACRITICS ) {
+
+ if ( $flag === ( $flag | self::NONE ) ) {
+ return $string;
+ }
+
+ if ( !isset( self::$transliterationMap[$flag] ) ) {
+ $transliterationMap = array();
+
+ if ( $flag === ( $flag | self::GREEK ) ) {
+ $transliterationMap += self::$greekMap;
+ }
+
+ if ( $flag === ( $flag | self::DIACRITICS ) ) {
+ $transliterationMap += self::$diacriticsMap;
+ }
+
+ // Keep it static for the duration of a request to avoid negative
+ // performance impact
+ self::$transliterationMap[$flag]['k'] = array_keys( $transliterationMap );
+ self::$transliterationMap[$flag]['v'] = array_values( $transliterationMap );
+
+ unset( $transliterationMap );
+ }
+
+ return str_replace(
+ self::$transliterationMap[$flag]['k'],
+ self::$transliterationMap[$flag]['v'],
+ $string
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/tests/bootstrap.php b/www/wiki/vendor/onoi/tesa/tests/bootstrap.php
new file mode 100644
index 00000000..a8a29313
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/tests/bootstrap.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+
+error_reporting( E_ALL | E_STRICT );
+date_default_timezone_set( 'UTC' );
+
+if ( PHP_SAPI !== 'cli' ) {
+ die( 'Not an entry point' );
+}
+
+if ( is_readable( $path = __DIR__ . '/../vendor/autoload.php' ) ) {
+ print( "\nUsing the tesa vendor autoloader ...\n\n" );
+} elseif ( is_readable( $path = __DIR__ . '/../../../autoload.php' ) ) {
+ print( "\nUsing another local vendor autoloader ...\n\n" );
+} else {
+ die( 'The test suite requires a Composer based deployement.' );
+}
+
+print sprintf( "%-25s%s\n\n", "ICU (intl) extension:", ( extension_loaded( 'intl' ) ? INTL_ICU_VERSION : '(disabled)' ) );
+
+$autoLoader = require $path;
+$autoLoader->addPsr4( 'Onoi\\Tesa\\Tests\\', __DIR__ . '/phpunit/Unit' );
+$autoLoader->addPsr4( 'Onoi\\Tesa\\Tests\\Integration\\', __DIR__ . '/phpunit/Integration' );
+unset( $autoLoader );
diff --git a/www/wiki/vendor/onoi/tesa/tests/phpunit/Fixtures/StopwordAnalyzer/invalid.json b/www/wiki/vendor/onoi/tesa/tests/phpunit/Fixtures/StopwordAnalyzer/invalid.json
new file mode 100644
index 00000000..60b25d5f
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/tests/phpunit/Fixtures/StopwordAnalyzer/invalid.json
@@ -0,0 +1,5 @@
+{
+ "@source": "Foo",
+ "version": "0.1",
+ "invalid": [],
+} \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/tesa/tests/phpunit/Fixtures/StopwordAnalyzer/missingindex.json b/www/wiki/vendor/onoi/tesa/tests/phpunit/Fixtures/StopwordAnalyzer/missingindex.json
new file mode 100644
index 00000000..1396668c
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/tests/phpunit/Fixtures/StopwordAnalyzer/missingindex.json
@@ -0,0 +1,4 @@
+{
+ "@source": "Foo",
+ "version": "0.1"
+} \ No newline at end of file
diff --git a/www/wiki/vendor/onoi/tesa/tests/phpunit/Integration/CombinedNGramTokenizerTest.php b/www/wiki/vendor/onoi/tesa/tests/phpunit/Integration/CombinedNGramTokenizerTest.php
new file mode 100644
index 00000000..3849b918
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/tests/phpunit/Integration/CombinedNGramTokenizerTest.php
@@ -0,0 +1,109 @@
+<?php
+
+namespace Onoi\Tesa\Tests\Integration;
+
+use Onoi\Tesa\SanitizerFactory;
+use Onoi\Tesa\Tokenizer\NGramTokenizer;
+
+/**
+ * @group onoi-tesa
+ *
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class CombinedNGramTokenizerTest extends \PHPUnit_Framework_TestCase {
+
+ /**
+ * @dataProvider textProvider
+ */
+ public function testNgramWithBeginEndMarker( $languageCode, $ngramSize, $text, $expected ) {
+
+ $sanitizerFactory = new SanitizerFactory();
+
+ $tokenizer = $sanitizerFactory->newNGramTokenizer(
+ $sanitizerFactory->newGenericRegExTokenizer()
+ );
+
+ $tokenizer->withMarker( true );
+ $tokenizer->setNgramSize( $ngramSize );
+
+ $tokens = $tokenizer->tokenize( $text );
+
+ $this->assertEquals(
+ $expected,
+ $tokens
+ );
+ }
+
+ public function textProvider() {
+
+ // https://en.wikipedia.org/wiki/Stop_words
+ $provider[] = array(
+ 'en',
+ '2',
+ //
+ 'In computing, stop words are words which are filtered ...',
+ //
+ array(
+ 0 => '_i',
+ 1 => 'in',
+ 2 => 'n_',
+ 3 => '_c',
+ 4 => 'co',
+ 5 => 'om',
+ 6 => 'mp',
+ 7 => 'pu',
+ 8 => 'ut',
+ 9 => 'ti',
+ 10 => 'in',
+ 11 => 'ng',
+ 12 => 'g_',
+ 13 => '_s',
+ 14 => 'st',
+ 15 => 'to',
+ 16 => 'op',
+ 17 => 'p_',
+ 18 => '_w',
+ 19 => 'wo',
+ 20 => 'or',
+ 21 => 'rd',
+ 22 => 'ds',
+ 23 => 's_',
+ 24 => '_a',
+ 25 => 'ar',
+ 26 => 're',
+ 27 => 'e_',
+ 28 => '_w',
+ 29 => 'wo',
+ 30 => 'or',
+ 31 => 'rd',
+ 32 => 'ds',
+ 33 => 's_',
+ 34 => '_w',
+ 35 => 'wh',
+ 36 => 'hi',
+ 37 => 'ic',
+ 38 => 'ch',
+ 39 => 'h_',
+ 40 => '_a',
+ 41 => 'ar',
+ 42 => 're',
+ 43 => 'e_',
+ 44 => '_f',
+ 45 => 'fi',
+ 46 => 'il',
+ 47 => 'lt',
+ 48 => 'te',
+ 49 => 'er',
+ 50 => 're',
+ 51 => 'ed',
+ 52 => 'd_',
+ )
+ );
+
+ return $provider;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/tests/phpunit/Integration/CombinedSanitizerTextStopwordTest.php b/www/wiki/vendor/onoi/tesa/tests/phpunit/Integration/CombinedSanitizerTextStopwordTest.php
new file mode 100644
index 00000000..a834b90f
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/tests/phpunit/Integration/CombinedSanitizerTextStopwordTest.php
@@ -0,0 +1,102 @@
+<?php
+
+namespace Onoi\Tesa\Tests\Integration;
+
+use Onoi\Tesa\SanitizerFactory;
+
+/**
+ * @group onoi-tesa
+ *
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class CombinedSanitizerTextStopwordTest extends \PHPUnit_Framework_TestCase {
+
+ /**
+ * @dataProvider textByLanguageProvider
+ */
+ public function testByLanguage( $languageCode, $text, $expected ) {
+
+ $sanitizerFactory = new SanitizerFactory();
+
+ $sanitizer = $sanitizerFactory->newSanitizer( $text );
+ $sanitizer->toLowercase();
+
+ $text = $sanitizer->sanitizeWith(
+ $sanitizerFactory->newGenericRegExTokenizer(),
+ $sanitizerFactory->newCdbStopwordAnalyzer( $languageCode ),
+ $sanitizerFactory->newNullSynonymizer()
+ );
+
+ $this->assertEquals(
+ $expected,
+ $text
+ );
+ }
+
+ public function textByLanguageProvider() {
+
+ // https://en.wikipedia.org/wiki/Stop_words
+ $provider[] = array(
+ 'en',
+ //
+ 'In computing, stop words are words which are filtered out before or after processing of ' .
+ 'natural language data (text).[1] Though stop words usually refer to the most common words ' .
+ 'in a language, there is no single universal list of stop words used by all natural language ' .
+ 'processing tools, and indeed not all tools even use such a list. Some tools specifically avoid '.
+ 'removing these stop words to support phrase search.',
+ //
+ 'computing stop words filtered processing natural language data text stop words refer common ' .
+ 'words language single universal list stop words natural language processing tools list tools ' .
+ 'specifically avoid removing stop words support phrase search'
+ );
+
+ // https://es.wikipedia.org/wiki/Palabra_vac%C3%ADa
+ $provider[] = array(
+ 'es',
+ //
+ 'Palabras vacías es el nombre que reciben las palabras sin significado como artículos, pronombres, ' .
+ 'preposiciones, etc. que son filtradas antes o después del procesamiento de datos en lenguaje natural ' .
+ '(texto). A Hans Peter Luhn, uno de los pioneros en recuperación de información, se le atribuye la ' .
+ 'acuñación de la locución inglesa stop words y el uso del concepto en su diseño. Está controlada por ' .
+ 'introducción humana y no automática.',
+ //
+ 'palabras vacías nombre que reciben palabras significado artículos pronombres preposiciones etc que ' .
+ 'son filtradas después del procesamiento datos lenguaje natural texto hans peter luhn pioneros ' .
+ 'recuperación información atribuye acuñación locución inglesa stop words del concepto diseño está ' .
+ 'controlada introducción humana automática'
+ );
+
+ // https://de.wikipedia.org/wiki/Stoppwort
+ $provider[] = array(
+ 'de',
+ //
+ 'Stoppwörter nennt man im Information Retrieval Wörter, die bei einer Volltextindexierung nicht beachtet ' .
+ 'werden, da sie sehr häufig auftreten und gewöhnlich keine Relevanz für die Erfassung des Dokumentinhalts ' .
+ 'besitzen.',
+ //
+ 'stoppwörter nennt information retrieval wörter volltextindexierung beachtet häufig auftreten gewöhnlich ' .
+ 'relevanz erfassung dokumentinhalts besitzen'
+ );
+
+ // https://en.wikipedia.org/wiki/Query_expansion
+ $provider[] = array(
+ 'en',
+ //
+ "The goal of query expansion in this regard is by increasing recall, precision can potentially increase " .
+ "(rather than decrease as mathematically equated), by including in the result set pages which are more " .
+ "relevant (of higher quality), or at least equally relevant. Pages which would not be included in the " .
+ "result set, which have the potential to be more relevant to the user's desired query, are included, and " .
+ "without query expansion would not have, regardless of relevance.",
+ //
+ "goal query expansion regard increasing recall precision potentially increase decrease mathematically " .
+ "equated including result set pages relevant quality equally relevant pages included result set potential " .
+ "relevant user desired query included query expansion relevance"
+ );
+
+ return $provider;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/tests/phpunit/Integration/JaTokenizerTest.php b/www/wiki/vendor/onoi/tesa/tests/phpunit/Integration/JaTokenizerTest.php
new file mode 100644
index 00000000..dcbf31ce
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/tests/phpunit/Integration/JaTokenizerTest.php
@@ -0,0 +1,118 @@
+<?php
+
+namespace Onoi\Tesa\Tests\Integration;
+
+use Onoi\Tesa\SanitizerFactory;
+
+/**
+ * @group onoi-tesa
+ *
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class JaTokenizerTest extends \PHPUnit_Framework_TestCase {
+
+ /**
+ * @dataProvider icuTextProvider
+ */
+ public function testIcuWordBoundaryTokenizer( $text, $expected ) {
+
+ $sanitizerFactory = new SanitizerFactory();
+
+ $tokenier = $sanitizerFactory->newIcuWordBoundaryTokenizer(
+ $sanitizerFactory->newGenericRegExTokenizer()
+ );
+
+ if ( !$tokenier->isAvailable() || INTL_ICU_VERSION != '54.1' ) {
+ $this->markTestSkipped( 'ICU extension is not available or does not match the expected version constraint.' );
+ }
+
+ $this->assertEquals(
+ $expected,
+ $tokenier->tokenize( $text )
+ );
+ }
+
+ /**
+ * @dataProvider tinyTextProvider
+ */
+ public function testJaTinySegmenterTokenizer( $text, $expected ) {
+
+ $sanitizerFactory = new SanitizerFactory();
+
+ $tokenier = $sanitizerFactory->newJaTinySegmenterTokenizer(
+ $sanitizerFactory->newPunctuationRegExTokenizer()
+ );
+
+ $this->assertEquals(
+ $expected,
+ $tokenier->tokenize( $text )
+ );
+ }
+
+ public function icuTextProvider() {
+
+ // https://github.com/NaturalNode/natural/blob/master/spec/tokenizer_ja_spec.js
+
+ $provider[] = array(
+ "計算機科学における字句解析 (じくかいせき、英: Lexical Analysis) とは、ソースコードを構成する文字の並びを、トークン (token) の並びに変換することをいう。\n" .
+ "ここでいう「トークン」とは、意味を持つコードの最小単位のこと。字句解析を行うプログラムは、字句解析器 (lexical analyzer, 略称:lexer) と呼ばれる。\n" .
+ "字句解析器はスキャナ (scanner) とトークナイザ (tokenizer) から構成される。\n",
+ //
+ array(
+ '計算機', '科学', 'における', '字句', '解析', 'じ', 'く', 'かい',
+ 'せき', '、', '英', 'Lexical', 'Analysis', 'と', 'は', '、', 'ソース', 'コード', 'を',
+ '構成', 'する', '文字', 'の', '並び', 'を', '、', 'トーク',
+ 'ン', 'token', 'の', '並びに', '変換', 'する', 'こと', 'を',
+ 'いう', '。', 'ここ', 'で', 'いう', '「', 'トーク', 'ン',
+ '」', 'と', 'は', '、', '意味', 'を', '持つ', 'コード',
+ 'の', '最小', '単位', 'の', 'こと', '。', '字句', '解析',
+ 'を', '行う', 'プログラム', 'は', '、', '字句', '解析', '器',
+ 'lexical', 'analyzer', '略称', ':',
+ 'lexer', 'と', '呼ばれる', '。', '字句', '解析', '器', 'は',
+ 'スキャナ', 'scanner', 'と', 'トーク','ナ', 'イザ', 'tokenizer', 'から',
+ '構成', 'さ', 'れる', '。'
+ )
+ );
+
+ return $provider;
+ }
+
+ public function tinyTextProvider() {
+
+ // https://github.com/NaturalNode/natural/blob/master/spec/tokenizer_ja_spec.js
+ /*
+ ['計算', '機科', '学', 'に', 'おける', '字句', '解析',
+ 'じくかい', 'せき', '英', 'Lexical', 'Analysis', 'と', 'は', 'ソースコード',
+ 'を', '構成', 'する', '文字', 'の', '並び', 'を', 'トークン', 'token', 'の',
+ '並び', 'に', '変換', 'する', 'こと', 'を', 'いう', 'ここ', 'でいう', 'トークン',
+ 'と', 'は', '意味', 'を', '持つ', 'コード', 'の', '最小', '単位', 'の', 'こと',
+ '字句', '解析', 'を', '行う', 'プログラム', 'は', '字句', '解析', '器', 'lexical',
+ 'analyzer', '略称', 'lexer', 'と', '呼ば', 'れる', '字句', '解析', '器', 'は',
+ 'スキャナ', 'scanner', 'と', 'トークナイザ', 'tokenizer', 'から', '構成', 'さ',
+ 'れる']
+ */
+
+ $provider[] = array(
+ "計算機科学における字句解析 (じくかいせき、英: Lexical Analysis) とは、ソースコードを構成する文字の並びを、トークン (token) の並びに変換することをいう。\n" .
+ "ここでいう「トークン」とは、意味を持つコードの最小単位のこと。字句解析を行うプログラムは、字句解析器 (lexical analyzer, 略称:lexer) と呼ばれる。\n" .
+ "字句解析器はスキャナ (scanner) とトークナイザ (tokenizer) から構成される。\n",
+ //
+ array(
+ '計算', '機科', '学', 'に', 'おける', '字句', '解析', 'じくかい','せき','英',
+ 'Lexical', 'Analysis', 'と', 'は', 'ソースコード', 'を', '構成', 'する',
+ '文字', 'の', '並び', 'を', 'トークン', 'token', 'の', '並び', 'に', '変換',
+ 'する', 'こと', 'をいう', 'ここ', 'でいう', 'トークン', 'と', 'は', '意味', 'を',
+ '持つ', 'コード', 'の', '最小', '単位', 'の', 'こと', '字句', '解析', 'を',
+ '行う', 'プログラム', 'は', '字句', '解析', '器', 'lexical', 'analyzer',
+ '略称', 'lexer', 'と', '呼ば', 'れる', '字句', '解析', '器', 'は', 'スキャナ', 'scanner',
+ 'と', 'トークナイザ', 'tokenizer', 'から', '構成', 'さ', 'れる',
+ )
+ );
+
+ return $provider;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/CharacterExaminerTest.php b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/CharacterExaminerTest.php
new file mode 100644
index 00000000..b9ad0935
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/CharacterExaminerTest.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Onoi\Tesa\Tests;
+
+use Onoi\Tesa\CharacterExaminer;
+
+/**
+ * @covers \Onoi\Tesa\CharacterExaminer
+ * @group onoi-tesa
+ *
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class CharacterExaminerTest extends \PHPUnit_Framework_TestCase {
+
+ public function testToContainKoreanCharacters() {
+ $this->assertTrue(
+ CharacterExaminer::contains( CharacterExaminer::HANGUL, '한국어 텍스트의 예' )
+ );
+
+ $this->assertFalse(
+ CharacterExaminer::contains( CharacterExaminer::HAN, '한국어 텍스트의 예' )
+ );
+ }
+
+ public function testToContainJapaneseCharacters() {
+
+ $this->assertTrue(
+ CharacterExaminer::contains( CharacterExaminer::LATIN, '脳のIQテスト' )
+ );
+
+ $this->assertTrue(
+ CharacterExaminer::contains( CharacterExaminer::HIRAGANA_KATAKANA, '脳のIQテスト' )
+ );
+
+ $this->assertTrue(
+ CharacterExaminer::contains( CharacterExaminer::HAN, '脳のIQテスト' )
+ );
+ }
+
+ public function testToContainChineseCharacters() {
+
+ $this->assertFalse(
+ CharacterExaminer::contains( CharacterExaminer::LATIN, '才可以过关' )
+ );
+
+ $this->assertTrue(
+ CharacterExaminer::contains( CharacterExaminer::CJK_UNIFIED, '才可以过关' )
+ );
+
+ $this->assertTrue(
+ CharacterExaminer::contains( CharacterExaminer::HAN, '才可以过关' )
+ );
+ }
+
+ public function testToContainCyrillic() {
+
+ $this->assertFalse(
+ CharacterExaminer::contains( CharacterExaminer::LATIN, 'Привет' )
+ );
+
+ $this->assertTrue(
+ CharacterExaminer::contains( CharacterExaminer::CYRILLIC, 'Привет' )
+ );
+ }
+
+ public function testToContainUnknownCharacters() {
+ $this->assertFalse(
+ CharacterExaminer::contains( 'Foo', '鿩' )
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/LanguageDetector/NullLanguageDetectorTest.php b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/LanguageDetector/NullLanguageDetectorTest.php
new file mode 100644
index 00000000..863303f0
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/LanguageDetector/NullLanguageDetectorTest.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Onoi\Tesa\Tests\LanguageDetector;
+
+use Onoi\Tesa\LanguageDetector\NullLanguageDetector;
+
+/**
+ * @covers \Onoi\Tesa\LanguageDetector\NullLanguageDetector
+ * @group onoi-tesa
+ *
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class NullLanguageDetectorTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\LanguageDetector\NullLanguageDetector',
+ new NullLanguageDetector()
+ );
+ }
+
+ public function testIsStopWord() {
+
+ $instance = new NullLanguageDetector();
+
+ $this->assertNull(
+ $instance->detect( 'Foo' )
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/LanguageDetector/TextCatLanguageDetectorTest.php b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/LanguageDetector/TextCatLanguageDetectorTest.php
new file mode 100644
index 00000000..7b1642d5
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/LanguageDetector/TextCatLanguageDetectorTest.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Onoi\Tesa\Tests\LanguageDetector;
+
+use Onoi\Tesa\LanguageDetector\TextCatLanguageDetector;
+
+/**
+ * @covers \Onoi\Tesa\LanguageDetector\TextCatLanguageDetector
+ * @group onoi-tesa
+ *
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class TextCatLanguageDetectorTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\LanguageDetector\TextCatLanguageDetector',
+ new TextCatLanguageDetector()
+ );
+ }
+
+ public function testDetectOnMock() {
+
+ $languageCandidates = array( 'en', 'de', 'fr', 'es', 'ja', 'zh' );
+
+ $textCat = $this->getMockBuilder( '\TextCat' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $textCat->expects( $this->once() )
+ ->method( 'classify' )
+ ->with(
+ $this->equalTo( 'Foo' ),
+ $this->equalTo( $languageCandidates ) )
+ ->will( $this->returnValue( array() ) );
+
+ $instance = new TextCatLanguageDetector( $textCat );
+ $instance->setLanguageCandidates(
+ $languageCandidates
+ );
+
+ $instance->detect( 'Foo' );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/NormalizerTest.php b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/NormalizerTest.php
new file mode 100644
index 00000000..23fe4a8c
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/NormalizerTest.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Onoi\Tesa\Tests;
+
+use Onoi\Tesa\Normalizer;
+
+/**
+ * @covers \Onoi\Tesa\Normalizer
+ * @group onoi-tesa
+ *
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class NormalizerTest extends \PHPUnit_Framework_TestCase {
+
+ public function testTransliteration() {
+
+ $this->assertEquals(
+ 'AAAAAEAaaaaaeaOOOOOOEOoooooeoEEEEeeeeðCcÐIIIIiiiiUUUUEuuuueNnSsYyyZz',
+ Normalizer::applyTransliteration( 'ÀÁÂÃÄÅàáâãäåÒÓÔÕÕÖØòóôõöøÈÉÊËèéêëðÇçÐÌÍÎÏìíîïÙÚÛÜùúûüÑñŠšŸÿýŽž' )
+ );
+ }
+
+ public function testConvertDoubleWidth() {
+
+ $this->assertEquals(
+ '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
+ Normalizer::convertDoubleWidth( '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' )
+ );
+ }
+
+ public function testReduceLengthTo() {
+
+ $this->assertEquals(
+ 'ABC',
+ Normalizer::reduceLengthTo( 'ABCDEF', 3 )
+ );
+
+ $this->assertEquals(
+ 'ABCDEF',
+ Normalizer::reduceLengthTo( 'ABCDEF' )
+ );
+
+ $this->assertEquals(
+ 'ABCD',
+ Normalizer::reduceLengthTo( 'ABCD EF', 4 )
+ );
+
+ $this->assertEquals(
+ 'ABC',
+ Normalizer::reduceLengthTo( 'ABC D EF', 4 )
+ );
+
+ $this->assertEquals(
+ 'ABCD',
+ Normalizer::reduceLengthTo( 'ABCD EF', 5 )
+ );
+
+ $this->assertEquals(
+ 'abc def gh',
+ Normalizer::reduceLengthTo( 'abc def gh in 123', 12 )
+ );
+ }
+
+ public function testToLowercase() {
+
+ $this->assertEquals(
+ 'abcdef',
+ Normalizer::toLowercase( 'ABCDEF' )
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/SanitizerFactoryTest.php b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/SanitizerFactoryTest.php
new file mode 100644
index 00000000..0e560706
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/SanitizerFactoryTest.php
@@ -0,0 +1,194 @@
+<?php
+
+namespace Onoi\Tesa\Tests;
+
+use Onoi\Tesa\SanitizerFactory;
+
+/**
+ * @covers \Onoi\Tesa\SanitizerFactory
+ * @group onoi-tesa
+ *
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class SanitizerFactoryTest extends \PHPUnit_Framework_TestCase {
+
+ private $sanitizerFactory;
+
+ protected function setUp() {
+ $this->sanitizerFactory = new SanitizerFactory();
+ }
+
+ public function testCanConstructSanitizer() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\Sanitizer',
+ $this->sanitizerFactory->newSanitizer()
+ );
+ }
+
+ /* StopwordAnalyzer */
+
+ public function testCanConstructStopwordAnalyzerByNullLanguage() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\StopwordAnalyzer\StopwordAnalyzer',
+ $this->sanitizerFactory->newStopwordAnalyzerByLanguage( null )
+ );
+ }
+
+ public function testCanConstructStopwordAnalyzerByEnLanguage() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\StopwordAnalyzer\CdbStopwordAnalyzer',
+ $this->sanitizerFactory->newStopwordAnalyzerByLanguage( 'EN' )
+ );
+ }
+
+ public function testCanConstructCdbStopwordAnalyzer() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\StopwordAnalyzer\CdbStopwordAnalyzer',
+ $this->sanitizerFactory->newCdbStopwordAnalyzer()
+ );
+ }
+
+ public function testCanConstructArrayStopwordAnalyzer() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\StopwordAnalyzer\ArrayStopwordAnalyzer',
+ $this->sanitizerFactory->newArrayStopwordAnalyzer()
+ );
+ }
+
+ public function testCanConstructNullStopwordAnalyzer() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\StopwordAnalyzer\NullStopwordAnalyzer',
+ $this->sanitizerFactory->newNullStopwordAnalyzer()
+ );
+ }
+
+ /* Synonymizer */
+
+ public function testCanConstructSynonymizerByLanguage() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\Synonymizer\Synonymizer',
+ $this->sanitizerFactory->newSynonymizerByLanguage()
+ );
+ }
+
+ public function testCanConstructNullSynonymizer() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\Synonymizer\NullSynonymizer',
+ $this->sanitizerFactory->newNullSynonymizer()
+ );
+ }
+
+ /* LanguageDetector */
+
+ public function testCanConstructNullLanguageDetector() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\LanguageDetector\NullLanguageDetector',
+ $this->sanitizerFactory->newNullLanguageDetector()
+ );
+ }
+
+ public function testCanConstructTextCatLanguageDetector() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\LanguageDetector\TextCatLanguageDetector',
+ $this->sanitizerFactory->newTextCatLanguageDetector()
+ );
+ }
+
+ /* Tokenizer */
+
+ public function testCanConstructPreferredTokenizerByLanguage() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\Tokenizer\Tokenizer',
+ $this->sanitizerFactory->newPreferredTokenizerByLanguage( 'テスト' )
+ );
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\Tokenizer\Tokenizer',
+ $this->sanitizerFactory->newPreferredTokenizerByLanguage( '在延安更名为新' )
+ );
+ }
+
+ public function testCanConstructnewCJKMatchableTokenizer() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\Tokenizer\Tokenizer',
+ $this->sanitizerFactory->newCJKMatchableTokenizer( 'テスト' )
+ );
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\Tokenizer\Tokenizer',
+ $this->sanitizerFactory->newCJKMatchableTokenizer( '在延安更名为新' )
+ );
+ }
+
+ public function testCanConstructIcuWordBoundaryTokenizer() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\Tokenizer\IcuWordBoundaryTokenizer',
+ $this->sanitizerFactory->newIcuWordBoundaryTokenizer()
+ );
+ }
+
+ public function testCanConstructGenericRegExTokenizer() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\Tokenizer\GenericRegExTokenizer',
+ $this->sanitizerFactory->newGenericRegExTokenizer()
+ );
+ }
+
+ public function testCanConstructPunctuationRegExTokenizer() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\Tokenizer\PunctuationRegExTokenizer',
+ $this->sanitizerFactory->newPunctuationRegExTokenizer()
+ );
+ }
+
+ public function testCanConstructJaCompoundGroupTokenizer() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\Tokenizer\JaCompoundGroupTokenizer',
+ $this->sanitizerFactory->newJaCompoundGroupTokenizer()
+ );
+ }
+
+ public function testCanConstructJaTinySegmenterTokenizer() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\Tokenizer\JaTinySegmenterTokenizer',
+ $this->sanitizerFactory->newJaTinySegmenterTokenizer()
+ );
+ }
+
+ public function testCanConstructCJKSimpleCharacterRegExTokenizer() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\Tokenizer\CJKSimpleCharacterRegExTokenizer',
+ $this->sanitizerFactory->newCJKSimpleCharacterRegExTokenizer()
+ );
+ }
+
+ public function testCanConstructNGramTokenizer() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\Tokenizer\NGramTokenizer',
+ $this->sanitizerFactory->newNGramTokenizer()
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/SanitizerTest.php b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/SanitizerTest.php
new file mode 100644
index 00000000..99411d15
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/SanitizerTest.php
@@ -0,0 +1,222 @@
+<?php
+
+namespace Onoi\Tesa\Tests;
+
+use Onoi\Tesa\Sanitizer;
+use Onoi\Tesa\SanitizerFactory;
+
+/**
+ * @covers \Onoi\Tesa\Sanitizer
+ * @group onoi-tesa
+ *
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class SanitizerTest extends \PHPUnit_Framework_TestCase {
+
+ private $sanitizerFactory;
+
+ protected function setUp() {
+ $this->sanitizerFactory = new SanitizerFactory();
+ }
+
+ public function testTransliteration() {
+
+ $instance = new Sanitizer( 'ÀÁÂÃÄÅàáâãäåÒÓÔÕÕÖØòóôõöøÈÉÊËèéêëðÇçÐÌÍÎÏìíîïÙÚÛÜùúûüÑñŠšŸÿýŽž' );
+ $instance->applyTransliteration();
+
+ $this->assertEquals(
+ 'AAAAAEAaaaaaeaOOOOOOEOoooooeoEEEEeeeeðCcÐIIIIiiiiUUUUEuuuueNnSsYyyZz',
+ $instance
+ );
+ }
+
+ public function testToLowercase() {
+
+ $instance = new Sanitizer( 'ÀÁÂÃÄÅ ABC 텍스트의 テスト часто הוא פשוט' );
+ $instance->toLowercase();
+
+ $this->assertEquals(
+ 'àáâãäå abc 텍스트의 テスト часто הוא פשוט',
+ $instance
+ );
+ }
+
+ public function testReduceLengthTo() {
+
+ $instance = new Sanitizer( 'ABCDEF' );
+ $instance->reduceLengthTo( 3 );
+
+ $this->assertEquals(
+ 3,
+ mb_strlen( $instance )
+ );
+
+ $instance->reduceLengthTo( 10 );
+
+ $this->assertEquals(
+ 3,
+ mb_strlen( $instance )
+ );
+ }
+
+ public function testReduceLengthToNearestWholeWordForLatinString() {
+
+ $instance = new Sanitizer( 'abc def gh in 123' );
+ $instance->reduceLengthTo( 12 );
+
+ $this->assertEquals(
+ 10,
+ mb_strlen( $instance )
+ );
+
+ $this->assertEquals(
+ 'abc def gh',
+ $instance
+ );
+ }
+
+ public function testReduceLengthToNearestWholeWordForNonLatinString() {
+
+ if ( version_compare( phpversion(), '5.4', '<' ) ) {
+ $this->markTestSkipped(
+ "Boo, PHP 5.3 returns with `Failed asserting that 9 matches expected 3`"
+ );
+ }
+
+ $instance = new Sanitizer( '一 二 三' );
+ $instance->reduceLengthTo( 3 );
+
+ $this->assertEquals(
+ 3,
+ mb_strlen( $instance )
+ );
+
+ $this->assertEquals(
+ '一 二',
+ $instance
+ );
+ }
+
+ public function testReplace() {
+
+ $instance = new Sanitizer( 'テスト' );
+ $instance->replace( array( 'テスト' ), array( 'Test' ) );
+
+ $this->assertEquals(
+ 'Test',
+ $instance
+ );
+ }
+
+ public function testSanitizeWithSimpleStopwordList() {
+
+ $text = 'Foo bar foobar';
+
+ $tokenizer = $this->getMockBuilder( '\Onoi\Tesa\Tokenizer\Tokenizer' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $tokenizer->expects( $this->once() )
+ ->method( 'tokenize' )
+ ->with( $this->equalTo( $text ) )
+ ->will( $this->returnValue( array( 'Foo', 'bar', 'foobar' ) ) );
+
+ $synonymizer = $this->getMockBuilder( '\Onoi\Tesa\Synonymizer\Synonymizer' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $synonymizer->expects( $this->any() )
+ ->method( 'synonymize' )
+ ->will($this->returnArgument( 0 ) );
+
+ $instance = new Sanitizer( $text );
+
+ $stopwordAnalyzer = $this->sanitizerFactory->newArrayStopwordAnalyzer(
+ array( 'bar' )
+ );
+
+ $this->assertEquals(
+ 'Foo foobar',
+ $instance->sanitizeWith( $tokenizer, $stopwordAnalyzer, $synonymizer )
+ );
+ }
+
+ public function testSanitizeByStopwordsToIncludeExemptionWithMinLengthRestriction() {
+
+ $text = 'Foo bar foobar';
+
+ $tokenizer = $this->getMockBuilder( '\Onoi\Tesa\Tokenizer\Tokenizer' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $tokenizer->expects( $this->once() )
+ ->method( 'isWordTokenizer' )
+ ->will( $this->returnValue( true ) );
+
+ $tokenizer->expects( $this->once() )
+ ->method( 'tokenize' )
+ ->with( $this->equalTo( $text ) )
+ ->will( $this->returnValue( array( 'Foo', 'bar', 'foobar' ) ) );
+
+ $synonymizer = $this->getMockBuilder( '\Onoi\Tesa\Synonymizer\Synonymizer' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $synonymizer->expects( $this->any() )
+ ->method( 'synonymize' )
+ ->will($this->returnArgument( 0 ) );
+
+ $instance = new Sanitizer( $text );
+
+ $stopwordAnalyzer = $this->sanitizerFactory->newArrayStopwordAnalyzer(
+ array( 'bar' )
+ );
+
+ $instance->setOption( Sanitizer::MIN_LENGTH, 4 );
+ $instance->setOption( Sanitizer::WHITELIST, array( 'bar' ) );
+
+ $this->assertEquals(
+ 'bar foobar',
+ $instance->sanitizeWith( $tokenizer, $stopwordAnalyzer, $synonymizer )
+ );
+ }
+
+ public function testTrySanitizeByStopwordsWithProximityCheck() {
+
+ $text = 'foo foo テスト テスト';
+
+ $tokenizer = $this->getMockBuilder( '\Onoi\Tesa\Tokenizer\Tokenizer' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $tokenizer->expects( $this->once() )
+ ->method( 'isWordTokenizer' )
+ ->will( $this->returnValue( true ) );
+
+ $tokenizer->expects( $this->once() )
+ ->method( 'tokenize' )
+ ->with( $this->equalTo( $text ) )
+ ->will( $this->returnValue( array( 'foo', 'foo', 'テスト', 'テスト' ) ) );
+
+ $synonymizer = $this->getMockBuilder( '\Onoi\Tesa\Synonymizer\Synonymizer' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $synonymizer->expects( $this->any() )
+ ->method( 'synonymize' )
+ ->will($this->returnArgument( 0 ) );
+
+ $instance = new Sanitizer( $text );
+
+ $stopwordAnalyzer = $this->sanitizerFactory->newArrayStopwordAnalyzer();
+
+ $this->assertEquals(
+ 'foo テスト',
+ $instance->sanitizeWith( $tokenizer, $stopwordAnalyzer, $synonymizer )
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/StopwordAnalyzer/ArrayStopwordAnalyzerTest.php b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/StopwordAnalyzer/ArrayStopwordAnalyzerTest.php
new file mode 100644
index 00000000..89661200
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/StopwordAnalyzer/ArrayStopwordAnalyzerTest.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Onoi\Tesa\Tests\StopwordAnalyzer;
+
+use Onoi\Tesa\StopwordAnalyzer\ArrayStopwordAnalyzer;
+
+/**
+ * @covers \Onoi\Tesa\StopwordAnalyzer\ArrayStopwordAnalyzer
+ * @group onoi-tesa
+ *
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class ArrayStopwordAnalyzerTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\StopwordAnalyzer\ArrayStopwordAnalyzer',
+ new ArrayStopwordAnalyzer()
+ );
+ }
+
+ /**
+ * @dataProvider stopWordsProvider
+ */
+ public function testIsStopWord( $defaultList, $word, $expected ) {
+
+ $instance = new ArrayStopwordAnalyzer( $defaultList );
+
+ $this->assertEquals(
+ $expected,
+ $instance->isStopWord( $word )
+ );
+ }
+
+ public function stopWordsProvider() {
+
+ $defaultList = array( 'Foo', 'かつて', 'bAR' );
+
+ $provider[] = array(
+ $defaultList,
+ 'Foo',
+ true
+ );
+
+ $provider[] = array(
+ $defaultList,
+ 'かつて',
+ true
+ );
+
+ $provider[] = array(
+ $defaultList,
+ 'bar',
+ false
+ );
+
+ return $provider;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/StopwordAnalyzer/CdbStopwordAnalyzerTest.php b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/StopwordAnalyzer/CdbStopwordAnalyzerTest.php
new file mode 100644
index 00000000..50846c74
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/StopwordAnalyzer/CdbStopwordAnalyzerTest.php
@@ -0,0 +1,168 @@
+<?php
+
+namespace Onoi\Tesa\Tests\StopwordAnalyzer;
+
+use Onoi\Tesa\StopwordAnalyzer\CdbStopwordAnalyzer;
+
+/**
+ * @covers \Onoi\Tesa\StopwordAnalyzer\CdbStopwordAnalyzer
+ * @group onoi-tesa
+ *
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class CdbStopwordAnalyzerTest extends \PHPUnit_Framework_TestCase {
+
+ public function testTryToCreateCdbByLanguageOnInvalidLanguageThrowsException() {
+
+ $this->setExpectedException( 'RuntimeException' );
+
+ CdbStopwordAnalyzer::createCdbByLanguage(
+ CdbStopwordAnalyzer::getLocation(),
+ 'foo'
+ );
+ }
+
+ public function testTryToCreateCdbByLanguageOnInvalidJsonIndexThrowsException() {
+
+ $this->setExpectedException( 'RuntimeException' );
+
+ CdbStopwordAnalyzer::createCdbByLanguage(
+ __DIR__ . '/../../Fixtures/StopwordAnalyzer/',
+ 'missingindex'
+ );
+ }
+
+ public function testTryToCreateCdbByLanguageOnInvalidJsonThrowsException() {
+
+ $this->setExpectedException( 'RuntimeException' );
+
+ CdbStopwordAnalyzer::createCdbByLanguage(
+ __DIR__ . '/../../Fixtures/StopwordAnalyzer/',
+ 'invalid'
+ );
+ }
+
+ /**
+ * @dataProvider languageProvider
+ */
+ public function testCreateCdbByLanguage( $languageCode ) {
+
+ $res = CdbStopwordAnalyzer::createCdbByLanguage(
+ CdbStopwordAnalyzer::getLocation(),
+ $languageCode
+ );
+
+ $this->assertTrue(
+ $res
+ );
+ }
+
+ /**
+ * @dataProvider stopWordProvider
+ */
+ public function testIsStopWord( $languageCode , $word, $expected ) {
+
+ $instane = new CdbStopwordAnalyzer(
+ CdbStopwordAnalyzer::getTargetByLanguage( $languageCode )
+ );
+
+ $this->assertEquals(
+ $expected,
+ $instane->isStopWord( $word )
+ );
+ }
+
+ public function languageProvider() {
+
+ $provider[] = array(
+ 'en',
+ );
+
+ $provider[] = array(
+ 'de'
+ );
+
+ $provider[] = array(
+ 'ja'
+ );
+
+ $provider[] = array(
+ 'zh'
+ );
+
+ $provider[] = array(
+ 'es'
+ );
+
+ $provider[] = array(
+ 'fr'
+ );
+
+ $provider[] = array(
+ 'pt'
+ );
+
+ $provider[] = array(
+ 'pt-br'
+ );
+
+ return $provider;
+ }
+
+ public function stopWordProvider() {
+
+ $provider[] = array(
+ 'en',
+ 'Foo',
+ false
+ );
+
+ $provider[] = array(
+ 'en',
+ 'the',
+ true
+ );
+
+ $provider[] = array(
+ 'ja',
+ 'それぞれ',
+ true
+ );
+
+ $provider[] = array(
+ 'zh',
+ '不单',
+ true
+ );
+
+ $provider[] = array(
+ 'es',
+ 'arriba',
+ true
+ );
+
+ $provider[] = array(
+ 'fr',
+ 'devrait',
+ true
+ );
+
+ $provider[] = array(
+ 'pt',
+ 'conhecido',
+ true
+ );
+
+ $provider[] = array(
+ 'pt-br',
+ 'mediante',
+ true
+ );
+
+ return $provider;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/StopwordAnalyzer/NullStopwordAnalyzerTest.php b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/StopwordAnalyzer/NullStopwordAnalyzerTest.php
new file mode 100644
index 00000000..39b543d3
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/StopwordAnalyzer/NullStopwordAnalyzerTest.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Onoi\Tesa\Tests\StopwordAnalyzer;
+
+use Onoi\Tesa\StopwordAnalyzer\NullStopwordAnalyzer;
+
+/**
+ * @covers \Onoi\Tesa\StopwordAnalyzer\NullStopwordAnalyzer
+ * @group onoi-tesa
+ *
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class NullStopwordAnalyzerTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\StopwordAnalyzer\NullStopwordAnalyzer',
+ new NullStopwordAnalyzer()
+ );
+ }
+
+ public function testIsStopWord() {
+
+ $instance = new NullStopwordAnalyzer();
+
+ $this->assertFalse(
+ $instance->isStopWord( 'Foo' )
+ );
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/CJKSimpleCharacterRegExTokenizerTest.php b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/CJKSimpleCharacterRegExTokenizerTest.php
new file mode 100644
index 00000000..062adba2
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/CJKSimpleCharacterRegExTokenizerTest.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Onoi\Tesa\Tests;
+
+use Onoi\Tesa\Tokenizer\CJKSimpleCharacterRegExTokenizer;
+
+/**
+ * @covers \Onoi\Tesa\Tokenizer\CJKSimpleCharacterRegExTokenizer
+ * @group onoi-tesa
+ *
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class CJKSimpleCharacterRegExTokenizerTest extends \PHPUnit_Framework_TestCase {
+
+ public function testUnknownOption() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\Tokenizer\CJKSimpleCharacterRegExTokenizer',
+ new CJKSimpleCharacterRegExTokenizer()
+ );
+ }
+
+ /**
+ * @dataProvider stringProvider
+ */
+ public function testTokenize( $string, $expected ) {
+
+ $instance = new CJKSimpleCharacterRegExTokenizer();
+
+ $this->assertEquals(
+ $expected,
+ $instance->tokenize( $string )
+ );
+
+ $this->assertFalse(
+ $instance->isWordTokenizer()
+ );
+ }
+
+ public function testTokenizeWithEnabledExemptionList() {
+
+ $string = '《红色中华》报改名为《新中华报》的同时,在';
+
+ $tokenizer = $this->getMockBuilder( '\Onoi\Tesa\Tokenizer\Tokenizer' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $tokenizer->expects( $this->once() )
+ ->method( 'setOption' );
+
+ $tokenizer->expects( $this->once() )
+ ->method( 'tokenize' )
+ ->with( $this->equalTo( $string ) )
+ ->will( $this->returnValue( array( $string ) ) );
+
+ $instance = new CJKSimpleCharacterRegExTokenizer( $tokenizer );
+
+ $instance->setOption(
+ CJKSimpleCharacterRegExTokenizer::REGEX_EXEMPTION,
+ array( '《', '》', ',')
+ );
+
+ $this->assertEquals(
+ array( '《红色中华》报改名', '《新中华报》', '同', ',' ),
+ $instance->tokenize( $string )
+ );
+ }
+
+ public function stringProvider() {
+
+ $provider[] = array(
+ '《红色中华》报改名为《新中华报》的同时,在延安更名为新华通讯社。但是当时,新华社和《新中华报》还是同一个机构。',
+ array( '红色中华', '报改名', '新中华报', '同', '延安更名', '新华通讯社', '新华社', '新中华报', '同一', '机构' )
+ );
+
+ $provider[] = array(
+ '江泽民在北京人民大会堂会见参加全国法院工作会议和全国法院系统打击经济犯罪先进集体表彰大会代表时要求大家要充分认识打击经济犯罪的艰巨性和长期性',
+ array( '江泽民', '北京人民大会堂会见参加全国法院工作会议', '全国法院系统打击经济犯罪先进集体表彰大会代表', '求大家', '充分认识打击经济犯罪', '艰巨性', '长期性' )
+ );
+
+ return $provider;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/GenericRegExTokenizerTest.php b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/GenericRegExTokenizerTest.php
new file mode 100644
index 00000000..7ba89083
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/GenericRegExTokenizerTest.php
@@ -0,0 +1,94 @@
+<?php
+
+namespace Onoi\Tesa\Tests;
+
+use Onoi\Tesa\Tokenizer\GenericRegExTokenizer;
+
+/**
+ * @covers \Onoi\Tesa\Tokenizer\GenericRegExTokenizer
+ * @group onoi-tesa
+ *
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class GenericRegExTokenizerTest extends \PHPUnit_Framework_TestCase {
+
+ public function testUnknownOption() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\Tokenizer\GenericRegExTokenizer',
+ new GenericRegExTokenizer()
+ );
+ }
+
+ /**
+ * @dataProvider stringProvider
+ */
+ public function testTokenize( $string, $expected ) {
+
+ $instance = new GenericRegExTokenizer();
+
+ $this->assertEquals(
+ $expected,
+ $instance->tokenize( $string )
+ );
+ }
+
+ public function testTokenizeWithEnabledExemptionList() {
+
+ $string = "It's a test string (that has no);";
+
+ $tokenizer = $this->getMockBuilder( '\Onoi\Tesa\Tokenizer\Tokenizer' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $tokenizer->expects( $this->once() )
+ ->method( 'setOption' );
+
+ $tokenizer->expects( $this->once() )
+ ->method( 'tokenize' )
+ ->with( $this->equalTo( $string ) )
+ ->will( $this->returnValue( array( $string ) ) );
+
+ $instance = new GenericRegExTokenizer( $tokenizer );
+
+ $instance->setOption(
+ GenericRegExTokenizer::REGEX_EXEMPTION,
+ array( '\(', '\)', "'", ';')
+ );
+
+ $this->assertEquals(
+ array( "It's", 'a', 'test', 'string', '(that', 'has', 'no);' ),
+ $instance->tokenize( $string )
+ );
+ }
+
+ public function stringProvider() {
+
+ $provider[] = array(
+ "It's a test string (that has no);deep meaning except0",
+ array( 'It', 's', 'a', 'test', 'string', 'that', 'has', 'no', 'deep', 'meaning', 'except', '0' )
+ );
+
+ $provider[] = array(
+ "Привет, мир! Меня зовут д'Артаньян %) цуацуа123123",
+ array( 'Привет', 'мир', 'Меня', 'зовут', 'д', "Артаньян", 'цуацуа', '123123' )
+ );
+
+ $provider[] = array(
+ "[[Действует на возбудителей]] Brucella spp., Legionella pneumophila, Salmonella typhi,(за исключением остальных рифампицинов) не отмечено. ...
+
+Фармакокинетика[править | править вики-текст]
+Рифампицин хорошо всасывается из желудочно-кишечного тракта.",
+ array(
+ 'Действует', 'на', 'возбудителей', 'Brucella', 'spp', 'Legionella', 'pneumophila', 'Salmonella', 'typhi', 'за', 'исключением', 'остальных', 'рифампицинов',
+ 'не', 'отмечено', 'Фармакокинетика', 'править', 'править', 'вики', 'текст', 'Рифампицин', 'хорошо', 'всасывается', 'из', 'желудочно', 'кишечного', 'тракта'
+ )
+ );
+
+ return $provider;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/IcuWordBoundaryTokenizerTest.php b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/IcuWordBoundaryTokenizerTest.php
new file mode 100644
index 00000000..2ac9cc3c
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/IcuWordBoundaryTokenizerTest.php
@@ -0,0 +1,173 @@
+<?php
+
+namespace Onoi\Tesa\Tests;
+
+use Onoi\Tesa\Tokenizer\IcuWordBoundaryTokenizer;
+
+/**
+ * @covers \Onoi\Tesa\Tokenizer\IcuWordBoundaryTokenizer
+ * @group onoi-tesa
+ *
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class IcuWordBoundaryTokenizerTest extends \PHPUnit_Framework_TestCase {
+
+ protected function setUp() {
+ $instance = new IcuWordBoundaryTokenizer();
+
+ if ( !$instance->isAvailable() || INTL_ICU_VERSION != '54.1' ) {
+ $this->markTestSkipped( 'ICU extension is not available or does not match the expected version constraint.' );
+ }
+ }
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\Tokenizer\IcuWordBoundaryTokenizer',
+ new IcuWordBoundaryTokenizer()
+ );
+ }
+
+ /**
+ * @dataProvider stringProvider
+ */
+ public function testTokenize( $string, $expected ) {
+
+ $instance = new IcuWordBoundaryTokenizer();
+
+ $this->assertEquals(
+ $expected,
+ $instance->tokenize( $string )
+ );
+ }
+
+ public function testSetOption() {
+
+ $tokenizer = $this->getMockBuilder( '\Onoi\Tesa\Tokenizer\Tokenizer' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $tokenizer->expects( $this->once() )
+ ->method( 'setOption' );
+
+ $instance = new IcuWordBoundaryTokenizer(
+ $tokenizer
+ );
+
+ $instance->setOption(
+ IcuWordBoundaryTokenizer::REGEX_EXEMPTION,
+ array( 'Foo' )
+ );
+ }
+
+ public function testGeneralSetters() {
+
+ $tokenizer = $this->getMockBuilder( '\Onoi\Tesa\Tokenizer\Tokenizer' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $instance = new IcuWordBoundaryTokenizer(
+ $tokenizer
+ );
+
+ $instance->setLocale( 'en' );
+ $instance->setWordTokenizerAttribute( false );
+
+ $this->assertFalse(
+ $instance->isWordTokenizer()
+ );
+ }
+
+ public function stringProvider() {
+
+ $provider[] = array(
+ "安全テスト",
+ array( '安全', 'テスト' )
+ );
+
+ // Would expect 'すもも', 'も', 'もも', 'も', 'もも', 'の', 'うち', '。'
+ $provider[] = array(
+ "すもももももももものうち。",
+ array( 'すもも', 'も', 'も', 'も', 'も', 'も', 'もの', 'うち', '。' )
+ );
+
+ $provider[] = array(
+ "李も桃も桃のうち。",
+ array( '李', 'も', '桃', 'も', '桃', 'の', 'うち', '。' )
+ );
+
+ $provider[] = array(
+ "إسرائيل",
+ array( 'إسرائيل' )
+ );
+
+ $provider[] = array(
+ "검색엔ㅇㅏ진",
+ array( '검색엔', 'ㅇㅏ', '진' )
+ );
+
+ $provider[] = array(
+ "검색엔ㅇㅏ진1234abcdfrA",
+ array( '검색엔', 'ㅇㅏ', '진', '1234abcdfrA' )
+ );
+
+ $provider[] = array(
+ "1234abcdfrA",
+ array( '1234abcdfrA' )
+ );
+
+ $provider[] = array(
+ "公明執ようなSNSもストーカー行為の対象に",
+ array(
+ '公明', '執よう','な','SNS', 'も',
+ 'ストーカー', '行為', 'の', '対象', 'に'
+ )
+ );
+
+ $provider[] = array(
+ "公明執",
+ array( '公明', '執' )
+ );
+
+ $provider[] = array(
+ "IQテスト",
+ array( 'IQ', 'テスト' )
+ );
+
+ $provider[] = array(
+ "foo テスト bar",
+ array( 'foo', 'テスト', 'bar' )
+ );
+
+ $provider[] = array(
+ "foo テスト bar 123abc ^&'",
+ array( 'foo', 'テスト', 'bar', '123abc', '^', '&', "'" )
+ );
+
+ $provider[] = array(
+ "was discovered in 1957 and first sold as a medication in 1971",
+ array(
+ 'was', 'discovered', 'in', '1957', 'and',
+ 'first', 'sold', 'as', 'a', 'medication', 'in', '1971'
+ )
+ );
+
+ // See JaTinySegmenterTokenizerTest for comparison
+ $provider[] = array(
+ '日本語の新聞記事であれば文字単位で95%程度の精度で分かち書きが行えます。 ',
+ array(
+ '日本語', 'の', '新聞', '記事', 'で',
+ 'あれ', 'ば', '文字', '単位',
+ 'で', '95', '%', '程度',
+ 'の', '精度', 'で', '分かち書き',
+ 'が', '行', 'え', 'ます', '。'
+ )
+ );
+
+ return $provider;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/JaCompoundGroupTokenizerTest.php b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/JaCompoundGroupTokenizerTest.php
new file mode 100644
index 00000000..17c4718d
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/JaCompoundGroupTokenizerTest.php
@@ -0,0 +1,90 @@
+<?php
+
+namespace Onoi\Tesa\Tests;
+
+use Onoi\Tesa\Tokenizer\JaCompoundGroupTokenizer;
+
+/**
+ * @covers \Onoi\Tesa\Tokenizer\JaCompoundGroupTokenizer
+ * @group onoi-tesa
+ *
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class JaCompoundGroupTokenizerTest extends \PHPUnit_Framework_TestCase {
+
+ public function testUnknownOption() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\Tokenizer\JaCompoundGroupTokenizer',
+ new JaCompoundGroupTokenizer()
+ );
+ }
+
+ /**
+ * @dataProvider stringProvider
+ */
+ public function testTokenize( $string, $expected ) {
+
+ if ( version_compare( phpversion(), '5.4', '<' ) ) {
+ $this->markTestSkipped(
+ "Boo, PHP 5.3 returns with unexpected results"
+ );
+ }
+
+ $instance = new JaCompoundGroupTokenizer();
+
+ $this->assertEquals(
+ $expected,
+ $instance->tokenize( $string )
+ );
+ }
+
+ public function testTokenizeWithOption() {
+
+ if ( version_compare( phpversion(), '5.4', '<' ) ) {
+ $this->markTestSkipped(
+ "Ehh, PHP 5.3 returns with unexpected results"
+ );
+ }
+
+ $string = 'と歓声を上げていました';
+
+ $tokenizer = $this->getMockBuilder( '\Onoi\Tesa\Tokenizer\Tokenizer' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $tokenizer->expects( $this->once() )
+ ->method( 'setOption' );
+
+ $tokenizer->expects( $this->once() )
+ ->method( 'tokenize' )
+ ->with( $this->equalTo( $string ) )
+ ->will( $this->returnValue( array( $string ) ) );
+
+ $instance = new JaCompoundGroupTokenizer( $tokenizer );
+
+ $instance->setOption(
+ JaCompoundGroupTokenizer::REGEX_EXEMPTION,
+ array( 'Foo' )
+ );
+
+ $this->assertEquals(
+ array( '歓声', '上' ),
+ $instance->tokenize( $string )
+ );
+ }
+
+ public function stringProvider() {
+
+ $provider[] = array(
+ 'と歓声を上げていました。 十勝農業改良普及センターによりますと',
+ array( '歓声', '上', '十勝農業改良普及', 'センター' )
+ );
+
+ return $provider;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/JaTinySegmenterTokenizerTest.php b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/JaTinySegmenterTokenizerTest.php
new file mode 100644
index 00000000..fd906899
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/JaTinySegmenterTokenizerTest.php
@@ -0,0 +1,191 @@
+<?php
+
+namespace Onoi\Tesa\Tests;
+
+use Onoi\Tesa\Tokenizer\JaTinySegmenterTokenizer;
+
+/**
+ * @covers \Onoi\Tesa\Tokenizer\JaTinySegmenterTokenizer
+ * @group onoi-tesa
+ *
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class JaTinySegmenterTokenizerTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $tokenizer = $this->getMockBuilder( '\Onoi\Tesa\Tokenizer\Tokenizer' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\Tokenizer\JaTinySegmenterTokenizer',
+ new JaTinySegmenterTokenizer( $tokenizer )
+ );
+ }
+
+ /**
+ * @dataProvider stringProvider
+ */
+ public function testTokenize( $string, $expected ) {
+
+ $instance = new JaTinySegmenterTokenizer();
+
+ $this->assertEquals(
+ $expected,
+ $instance->tokenize( $string )
+ );
+ }
+
+ public function stringProvider() {
+
+ $provider[] = array(
+ '極めてコンパクトな日本語分かち書きソフトウェアです。',
+ array(
+ '極め', // should be 極めて
+ 'て',
+ 'コンパクト',
+ 'な',
+ '日本',
+ '語分',
+ 'かち',
+ '書き',
+ 'ソフトウェア',
+ 'です',
+ '。'
+ )
+ );
+
+ $provider[] = array(
+ '日本語の新聞記事であれば文字単位で95%程度の精度で分かち書きが行えます。 ',
+ array(
+ '日本語',
+ 'の',
+ '新聞',
+ '記事',
+ 'で',
+ 'あれ',
+ 'ば',
+ '文字',
+ '単位',
+ 'で',
+ '9',
+ '5',
+ '%',
+ '程度',
+ 'の',
+ '精度',
+ 'で',
+ '分かち',
+ '書き',
+ 'が',
+ '行え',
+ 'ます',
+ '。'
+
+ )
+ );
+
+ $provider[] = array(
+ '私の名前は中野です',
+ array(
+ '私',
+ 'の',
+ '名前',
+ 'は',
+ '中野',
+ 'です'
+ )
+ );
+
+ $provider[] = array(
+ 'TinySegmenterは25kBで書かれています。',
+ array(
+ 'TinySegmenter',
+ 'は',
+ '2',
+ '5',
+ 'kB',
+ 'で',
+ '書か',
+ 'れ',
+ 'て',
+ 'い',
+ 'ます',
+ '。'
+ )
+ );
+
+ $provider[] = array(
+ '隣の客はAK47振りかざしてギャアギャアわめきたてる客だ。',
+ array(
+ '隣',
+ 'の',
+ '客',
+ 'は',
+ 'AK',
+ '4',
+ '7',
+ '振り',
+ 'かざ', // should be かざし
+ 'し',
+ 'て',
+ 'ギャアギャア',
+ 'わめき',
+ 'た',
+ 'てる',
+ '客',
+ 'だ',
+ '。'
+ )
+ );
+
+ // See JaCompoundGroupTokenizerTest for comparison
+ $provider[] = array(
+ 'と歓声を上げていました。 十勝農業改良普及センターによりますと',
+ array(
+ 'と',
+ '歓声',
+ 'を',
+ '上げ',
+ 'て',
+ 'い',
+ 'まし',
+ 'た',
+ '。',
+ '十勝農業',
+ '改良',
+ '普及',
+ 'センター',
+ 'により',
+ 'ます',
+ 'と'
+ )
+ );
+
+ // See IcuWordBoundaryTokenizerTest
+ $provider[] = array(
+ "公明執ようなSNSもストーカー行為の対象に",
+ array(
+ '公明執',
+ 'よう',
+ 'な',
+ 'SNS',
+ 'も',
+ 'ストーカー',
+ '行為',
+ 'の',
+ '対象',
+ 'に'
+ )
+ );
+
+ // https://github.com/chezou/TinySegmenter.jl/blob/master/test/timemachineu8j.txt
+
+ return $provider;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/NGramTokenizerTest.php b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/NGramTokenizerTest.php
new file mode 100644
index 00000000..9f826047
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/NGramTokenizerTest.php
@@ -0,0 +1,224 @@
+<?php
+
+namespace Onoi\Tesa\Tests;
+
+use Onoi\Tesa\Tokenizer\NGramTokenizer;
+
+/**
+ * @covers \Onoi\Tesa\Tokenizer\NGramTokenizer
+ * @group onoi-tesa
+ *
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class NGramTokenizerTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\Tokenizer\NGramTokenizer',
+ new NGramTokenizer()
+ );
+ }
+
+ /**
+ * @dataProvider stringProvider
+ */
+ public function testTokenize( $string, $ngram, $expected ) {
+
+ if ( version_compare( phpversion(), '5.4', '<' ) ) {
+ $this->markTestSkipped(
+ "Ehh, PHP 5.3 returns with unexpected results"
+ );
+ }
+
+ $instance = new NGramTokenizer( null, $ngram );
+
+ $this->assertEquals(
+ $expected,
+ $instance->tokenize( $string )
+ );
+
+ $this->assertFalse(
+ $instance->isWordTokenizer()
+ );
+ }
+
+ public function testTokenizeWithStartEndMarker() {
+
+ // http://cloudmark.github.io/Language-Detection
+ $string = 'TEXT';
+
+ $expected = array(
+ '_tex',
+ 'text',
+ 'ext_',
+ 'xt__',
+ 't___'
+ );
+
+ $instance = new NGramTokenizer( null, 4 );
+ $instance->withMarker( true );
+
+ $this->assertEquals(
+ $expected,
+ $instance->tokenize( $string )
+ );
+ }
+
+ public function testTokenizeWithStartEndMarker2() {
+
+ $string = '教授は';
+
+ $expected = array(
+ '_教授',
+ '教授は',
+ '授は_',
+ 'は__'
+ );
+
+ $instance = new NGramTokenizer( null, 3 );
+ $instance->withMarker( true );
+
+ $this->assertEquals(
+ $expected,
+ $instance->tokenize( $string )
+ );
+ }
+ public function testTokenizeWithOption() {
+
+ $string = '红色中华';
+
+ $tokenizer = $this->getMockBuilder( '\Onoi\Tesa\Tokenizer\Tokenizer' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $tokenizer->expects( $this->once() )
+ ->method( 'setOption' );
+
+ $tokenizer->expects( $this->once() )
+ ->method( 'tokenize' )
+ ->with( $this->equalTo( $string ) )
+ ->will( $this->returnValue( array( $string ) ) );
+
+ $instance = new NGramTokenizer( $tokenizer );
+
+ $instance->setOption(
+ NGramTokenizer::REGEX_EXEMPTION,
+ array( 'Foo' )
+ );
+
+ $this->assertEquals(
+ array( '红色', '色中', '中华' ),
+ $instance->tokenize( $string )
+ );
+ }
+
+ public function stringProvider() {
+
+ $provider[] = array(
+ 'TEXT',
+ '4',
+ array(
+ 'text'
+ )
+ );
+
+ $provider[] = array(
+ '12345678',
+ '2',
+ array(
+ '12',
+ '23',
+ '34',
+ '45',
+ '56',
+ '67',
+ '78'
+ )
+ );
+
+ $provider[] = array(
+ '12345678',
+ '3',
+ array(
+ '123',
+ '234',
+ '345',
+ '456',
+ '567',
+ '678'
+ )
+ );
+
+ $provider[] = array(
+ 'hello',
+ '3',
+ array(
+ 'hel',
+ 'ell',
+ 'llo'
+ )
+ );
+
+ $provider[] = array(
+ 'Hello World!',
+ '3',
+ array(
+ 'hel',
+ 'ell',
+ 'llo',
+ 'lo ',
+ 'o w',
+ ' wo',
+ 'wor',
+ 'orl',
+ 'rld',
+ 'ld!'
+ )
+ );
+
+ $provider[] = array(
+ 'Новости',
+ '3',
+ array(
+ 'нов',
+ 'ово',
+ 'вос',
+ 'ост',
+ 'сти'
+ )
+ );
+
+ $provider[] = array(
+ '1時36分更新',
+ '3',
+ array(
+ '1時3',
+ '時36',
+ '36分',
+ '6分更',
+ '分更新'
+ )
+ );
+
+ $provider[] = array(
+ 'こんにちは世界!',
+ '2',
+ array(
+ 'こん',
+ 'んに',
+ 'にち',
+ 'ちは',
+ 'は世',
+ '世界',
+ '界!'
+ )
+ );
+
+ return $provider;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/PunctuationRegExTokenizerTest.php b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/PunctuationRegExTokenizerTest.php
new file mode 100644
index 00000000..089cb81a
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/Tokenizer/PunctuationRegExTokenizerTest.php
@@ -0,0 +1,117 @@
+<?php
+
+namespace Onoi\Tesa\Tests;
+
+use Onoi\Tesa\Tokenizer\PunctuationRegExTokenizer;
+
+/**
+ * @covers \Onoi\Tesa\Tokenizer\PunctuationRegExTokenizer
+ * @group onoi-tesa
+ *
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class PunctuationRegExTokenizerTest extends \PHPUnit_Framework_TestCase {
+
+ public function testCanConstruct() {
+
+ $this->assertInstanceOf(
+ '\Onoi\Tesa\Tokenizer\PunctuationRegExTokenizer',
+ new PunctuationRegExTokenizer()
+ );
+ }
+
+ /**
+ * @dataProvider stringProvider
+ */
+ public function testTokenize( $string, $patternExemption, $expected ) {
+
+ $instance = new PunctuationRegExTokenizer();
+
+ $instance->setOption(
+ PunctuationRegExTokenizer::REGEX_EXEMPTION,
+ $patternExemption
+ );
+
+ $this->assertEquals(
+ $expected,
+ $instance->tokenize( $string )
+ );
+
+ $this->assertTrue(
+ $instance->isWordTokenizer()
+ );
+ }
+
+ public function testisWordTokenizerFromInheritTokenizer() {
+
+ $tokenizer = $this->getMockBuilder( '\Onoi\Tesa\Tokenizer\Tokenizer' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $tokenizer->expects( $this->once() )
+ ->method( 'isWordTokenizer' )
+ ->will( $this->returnValue( false ) );
+
+ $instance = new PunctuationRegExTokenizer( $tokenizer );
+
+ $this->assertFalse(
+ $instance->isWordTokenizer()
+ );
+ }
+
+ public function testTokenizeWithOption() {
+
+ $string = '123, 345';
+
+ $tokenizer = $this->getMockBuilder( '\Onoi\Tesa\Tokenizer\Tokenizer' )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $tokenizer->expects( $this->once() )
+ ->method( 'setOption' );
+
+ $tokenizer->expects( $this->once() )
+ ->method( 'tokenize' )
+ ->with( $this->equalTo( $string ) )
+ ->will( $this->returnValue( array( $string ) ) );
+
+ $instance = new PunctuationRegExTokenizer( $tokenizer );
+
+ $instance->setOption(
+ PunctuationRegExTokenizer::REGEX_EXEMPTION,
+ array( ',' )
+ );
+
+ $this->assertEquals(
+ array( '123,', '345' ),
+ $instance->tokenize( $string )
+ );
+ }
+
+ public function stringProvider() {
+
+ $provider[] = array(
+ '123, 345^456&[foo:bar]',
+ '',
+ array( '123', '345', '456', 'foo', 'bar' )
+ );
+
+ $provider[] = array(
+ '123, 345^456&[foo:bar]',
+ array( ',', '&' ),
+ array( '123,', '345', '456&', 'foo', 'bar' )
+ );
+
+ $provider[] = array(
+ '123, 345^456&[foo:bar] 3.',
+ array( ',', '&' ),
+ array( '123,', '345', '456&', 'foo', 'bar', '3' )
+ );
+
+ return $provider;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/TransliteratorTest.php b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/TransliteratorTest.php
new file mode 100644
index 00000000..4b2b422d
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/tests/phpunit/Unit/TransliteratorTest.php
@@ -0,0 +1,162 @@
+<?php
+
+namespace Onoi\Tesa\Tests;
+
+use Onoi\Tesa\Transliterator;
+
+/**
+ * @covers \Onoi\Tesa\Transliterator
+ * @group onoi-tesa
+ *
+ * @license GNU GPL v2+
+ * @since 0.1
+ *
+ * @author mwjames
+ */
+class TransliteratorTest extends \PHPUnit_Framework_TestCase {
+
+ /**
+ * @dataProvider characterProvider
+ */
+ public function testTransliteration( $input, $flag, $expected ) {
+
+ $this->assertEquals(
+ $expected,
+ Transliterator::transliterate( $input, $flag )
+ );
+ }
+
+ public function testTransliterationWithoutOptionFlag() {
+
+ $this->assertEquals(
+ 'aaaaaea',
+ Transliterator::transliterate( 'àáâãäå' )
+ );
+ }
+
+ public function characterProvider() {
+
+ $provider[] = array(
+ 'Foo',
+ 'unknownFlag',
+ 'Foo',
+ );
+
+ $provider[] = array(
+ 'ÀÁÂÃÄÅàáâãäåÒÓÔÕÕÖØòóôõöøÈÉÊËèéêëðÇçÐÌÍÎÏìíîïÙÚÛÜùúûüÑñŠšŸÿýŽž',
+ Transliterator::NONE,
+ 'ÀÁÂÃÄÅàáâãäåÒÓÔÕÕÖØòóôõöøÈÉÊËèéêëðÇçÐÌÍÎÏìíîïÙÚÛÜùúûüÑñŠšŸÿýŽž',
+ );
+
+ $provider[] = array(
+ 'ÀÁÂÃÄÅàáâãäåÒÓÔÕÕÖØòóôõöøÈÉÊËèéêëðÇçÐÌÍÎÏìíîïÙÚÛÜùúûüÑñŠšŸÿýŽž',
+ Transliterator::DIACRITICS,
+ 'AAAAAEAaaaaaeaOOOOOOEOoooooeoEEEEeeeeðCcÐIIIIiiiiUUUUEuuuueNnSsYyyZz'
+ );
+
+ $provider[] = array(
+ 'ỆᶍǍᶆṔƚÉ áéíóúýčďěňřšťžů',
+ Transliterator::DIACRITICS,
+ 'ExAmPlE aeiouycdenrstzu'
+ );
+
+ $provider[] = array(
+ 'àáâãäå',
+ Transliterator::DIACRITICS,
+ 'aaaaaea'
+ );
+
+ $provider[] = array(
+ 'èéêë',
+ Transliterator::DIACRITICS,
+ 'eeee'
+ );
+
+ $provider[] = array(
+ 'òóôõö',
+ Transliterator::DIACRITICS,
+ 'oooooe'
+ );
+
+ $provider[] = array(
+ 'ùúûü',
+ Transliterator::DIACRITICS,
+ 'uuuue'
+ );
+
+ $provider[] = array(
+ 'ç',
+ Transliterator::DIACRITICS,
+ 'c'
+ );
+
+ $provider[] = array(
+ 'æ',
+ Transliterator::DIACRITICS,
+ 'ae'
+ );
+
+ $provider[] = array(
+ 'ñ',
+ Transliterator::DIACRITICS,
+ 'n'
+ );
+
+ $provider[] = array(
+ 'œ',
+ Transliterator::DIACRITICS,
+ 'oe'
+ );
+
+ $provider[] = array(
+ 'ýÿ',
+ Transliterator::DIACRITICS,
+ 'yy'
+ );
+
+ $provider[] = array(
+ 'ß',
+ Transliterator::DIACRITICS,
+ 'ss'
+ );
+
+ $provider[] = array(
+ 'Vilʹândimaa',
+ Transliterator::DIACRITICS,
+ 'Vilʹandimaa'
+ );
+
+ $provider[] = array(
+ 'Ελληνική Δημοκρατία',
+ Transliterator::GREEK,
+ 'Ellīnikī́ Dīmokratía'
+ );
+
+ $provider[] = array(
+ 'Ελληνική Δημοκρατία',
+ Transliterator::DIACRITICS | Transliterator::GREEK,
+ 'Ellinikí Dimokratia'
+ );
+
+ $provider[] = array(
+ 'Γκ γκ γξ Ει ει Ηυ Μπ μπ',
+ Transliterator::GREEK,
+ 'Gk gk gx Ei ei Īy Mp mp'
+ );
+
+ $provider[] = array(
+ 'Μετατροπή του ελληνικού αλφαβήτου με λατινικούς χαρακτήρες',
+ Transliterator::GREEK,
+ 'Metatropī́ tou ellīnikoú alfavī́tou me latinikoús charaktī́res',
+ );
+
+ $provider[] = array(
+ 'Ελληνικός Οργανισμός Τυποποίησης',
+ Transliterator::GREEK,
+ 'Ellīnikós Organismós Typopoíīsīs',
+ );
+
+ return $provider;
+ }
+
+}
diff --git a/www/wiki/vendor/onoi/tesa/tests/travis/run-tests.sh b/www/wiki/vendor/onoi/tesa/tests/travis/run-tests.sh
new file mode 100644
index 00000000..dfe3b37e
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/tests/travis/run-tests.sh
@@ -0,0 +1,15 @@
+#! /bin/bash
+set -ex
+
+BASE_PATH=$(pwd)
+
+composer install
+composer dump-autoload
+composer validate --no-interaction
+
+if [ "$TYPE" == "coverage" ]
+then
+ composer phpunit -- --coverage-clover $BASE_PATH/build/coverage.clover
+else
+ composer phpunit
+fi
diff --git a/www/wiki/vendor/onoi/tesa/tests/travis/upload-coverage-report.sh b/www/wiki/vendor/onoi/tesa/tests/travis/upload-coverage-report.sh
new file mode 100644
index 00000000..aff95cf7
--- /dev/null
+++ b/www/wiki/vendor/onoi/tesa/tests/travis/upload-coverage-report.sh
@@ -0,0 +1,10 @@
+#! /bin/bash
+set -ex
+
+BASE_PATH=$(pwd)
+
+if [ "$TYPE" == "coverage" ]
+then
+ wget https://scrutinizer-ci.com/ocular.phar
+ php ocular.phar code-coverage:upload --format=php-clover $BASE_PATH/build/coverage.clover
+fi \ No newline at end of file