From f6e0439162c8d008e76fc433fd0f2bc65493001b Mon Sep 17 00:00:00 2001
From: Dan Feder
Date: Tue, 8 Jan 2019 15:51:54 -0500
Subject: [PATCH] 1546980132: Proposed release for 7.x-1.16.1 (#75)
* Update to Drupal 7.61. For more information, see https://www.drupal.org/project/drupal/releases/7.61
* Update to PHP 7.2. For details see https://pantheon.io/blog/php-72-everywhere/
* 7.x-1.16.1 release
---
CHANGELOG.txt | 23 +-
MAINTAINERS.txt | 4 +-
includes/bootstrap.inc | 8 +-
includes/common.inc | 14 +-
includes/common.inc.orig | 8516 +++++++++++++++++
includes/file.inc | 2 +-
includes/form.inc | 12 +-
includes/install.inc | 2 +-
includes/menu.inc | 9 +-
includes/module.inc | 12 +-
includes/theme.inc | 36 +-
misc/tabledrag.js | 42 +-
modules/book/book.module | 11 +-
modules/field/modules/list/list.install | 9 +-
modules/image/image.admin.inc | 3 +-
modules/locale/locale.test | 6 +-
modules/simpletest/tests/form.test | 53 +
modules/simpletest/tests/form_test.module | 18 +
modules/simpletest/tests/image.test | 8 +-
.../themes/test_theme/theme-settings.php | 32 +
modules/system/system.admin.inc | 23 +-
modules/system/system.api.php | 4 +-
modules/system/system.tar.inc | 87 +-
modules/system/system.test | 24 +
modules/taxonomy/taxonomy.module | 8 +-
modules/user/user.module | 4 +-
modules/user/user.test | 7 +
pantheon.upstream.yml | 2 +-
profiles/dkan/.github/ISSUE_TEMPLATE.md | 22 +
profiles/dkan/CHANGELOG.txt | 37 +
profiles/dkan/README.md | 2 +-
profiles/dkan/dkan.info | 3 +-
profiles/dkan/dkan.install | 342 +-
.../data_and_content/data_management.rst | 137 +-
.../dkan/docs/admin/people/dkan_workflow.rst | 166 -
profiles/dkan/docs/apis/datastore-api.rst | 6 +-
profiles/dkan/docs/apis/rest-api.rst | 3 +-
profiles/dkan/docs/components/datastore.rst | 225 +-
profiles/dkan/docs/components/permissions.rst | 2 +-
profiles/dkan/docs/components/workflow.rst | 80 +-
.../docs/development/display-num-datasets.rst | 159 +
profiles/dkan/docs/development/index.rst | 1 +
.../dkan/docs/introduction/dkan-sites.rst | 6 +-
profiles/dkan/docs/releases/notes/1.15.4.md | 2 +-
profiles/dkan/docs/releases/notes/1.15.5.md | 4 +
profiles/dkan/docs/releases/notes/1.16.md | 47 +
profiles/dkan/docs/releases/notes/index.rst | 2 +
profiles/dkan/drupal-org-core.make | 2 +-
profiles/dkan/drupal-org.make | 57 +-
.../dkan/modules/contrib/autoload/README.md | 76 +
.../modules/contrib/autoload/autoload.api.php | 21 +
.../contrib/autoload/autoload.cache.inc | 386 +
.../contrib/autoload/autoload.drush.inc | 46 +
.../modules/contrib/autoload/autoload.info | 13 +
.../modules/contrib/autoload/autoload.install | 17 +
.../modules/contrib/autoload/autoload.module | 70 +
.../src/Tests/Unit/AutoloadTestBase.php | 80 +
.../autoload/src/Tests/Unit/CacheTest.php | 83 +
.../autoload/src/Tests/Unit/CustomTest.php | 64 +
.../autoload/src/Tests/Unit/DrupalTest.php | 31 +
.../autoload/src/Tests/Unit/EntityTest.php | 38 +
.../src/Tests/Unit/ExtensionsTest.php | 51 +
.../contrib/autoload/tests/autoload.php | 43 +
.../autoload_test_custom.info | 26 +
.../autoload_test_custom.module | 6 +
.../psr-0/Autoload/Tests/PSR0.inc | 12 +
.../psr-4-single-level-namespace/PSR4.inc | 12 +
.../tests/autoload_test_custom/psr-4/PSR4.inc | 12 +
.../tests/autoload_test_custom/tests/Test.inc | 12 +
.../wrong-namespace/WrongNamespace.php | 12 +
.../autoload_test_drupal.info | 13 +
.../autoload_test_drupal.module | 6 +
.../lib/Drupal/autoload_test_drupal/PSR0.php | 12 +
.../tests/autoload_test_drupal/src/PSR4.inc | 12 +
.../autoload_test_entity.info | 14 +
.../autoload_test_entity.install | 28 +
.../autoload_test_entity.module | 22 +
.../autoload_test_entity_ui.info | 13 +
.../autoload_test_entity_ui.module | 13 +
.../src/ViewsController.php | 10 +
.../autoload_test_extensions.info | 13 +
.../autoload_test_extensions.module | 6 +
.../autoload_test_extensions/src/PSR4.test | 12 +
.../autoload_test_lookup.info | 10 +
.../autoload_test_lookup.module | 16 +
.../better_exposed_filters.api.php | 8 +-
.../better_exposed_filters.css | 8 +-
.../better_exposed_filters.info | 8 +-
.../better_exposed_filters.install | 92 +-
.../better_exposed_filters.js | 26 +-
.../better_exposed_filters.theme | 17 +-
.../better_exposed_filters.views.inc | 1 +
...er_exposed_filters_exposed_form_plugin.inc | 155 +-
.../bef_test_content/bef_test_content.info | 7 +-
.../bef_test_content/bef_test_content.install | 162 +-
.../tests/better_exposed_filters.test | 12 +
.../tests/better_exposed_filters_TestBase.php | 552 +-
.../dkan/modules/contrib/date/CHANGELOG.txt | 1797 ++--
.../contrib/date/date.devel_generate.inc | 2 +-
.../dkan/modules/contrib/date/date.field.inc | 2 +-
profiles/dkan/modules/contrib/date/date.info | 8 +-
.../date/date_all_day/date_all_day.info | 8 +-
.../contrib/date/date_api/date_api.info | 8 +-
.../contrib/date/date_api/date_api.module | 10 +-
.../contrib/date/date_api/date_api_ical.inc | 2 +-
.../contrib/date/date_api/date_api_sql.inc | 6 +-
.../date/date_context/date_context.info | 8 +-
.../modules/contrib/date/date_elements.inc | 3 +
.../date/date_migrate/date_migrate.info | 8 +-
.../date_migrate_example.info | 8 +-
.../contrib/date/date_popup/date_popup.info | 8 +-
.../contrib/date/date_repeat/date_repeat.info | 8 +-
.../date/date_repeat/date_repeat_calc.inc | 8 +-
.../date_repeat_field/date_repeat_field.info | 8 +-
.../date_repeat_field.module | 2 +-
.../contrib/date/date_tools/date_tools.info | 8 +-
.../contrib/date/date_views/date_views.info | 8 +-
.../includes/date_views_plugin_pager.inc | 4 +-
.../date/tests/date_test/date_test.info | 8 +-
.../dkan/modules/contrib/diff/DiffEngine.php | 26 +-
.../dkan/modules/contrib/diff/diff.admin.inc | 13 +-
.../dkan/modules/contrib/diff/diff.api.php | 16 +
profiles/dkan/modules/contrib/diff/diff.info | 7 +-
.../dkan/modules/contrib/diff/diff.install | 11 +-
.../dkan/modules/contrib/diff/diff.module | 3 +-
.../contrib/field_group_table/PATCHES.txt | 1 +
.../field_group_table.module | 2 +-
.../contrib/field_hidden/CHANGELOG.txt | 5 +
.../field_hidden/field_hidden.admin.inc | 61 +-
.../contrib/field_hidden/field_hidden.info | 7 +-
.../field_hidden_migrate.info | 7 +-
.../modules/contrib/file_entity/README.txt | 27 +
.../contrib/file_entity/file_entity.admin.inc | 7 +
.../contrib/file_entity/file_entity.api.php | 16 +
.../contrib/file_entity/file_entity.field.inc | 45 +-
.../contrib/file_entity/file_entity.file.inc | 14 +-
.../contrib/file_entity/file_entity.info | 6 +-
.../contrib/file_entity/file_entity.install | 21 +-
.../contrib/file_entity/file_entity.module | 5 +
.../contrib/file_entity/file_entity.pages.inc | 56 +-
.../file_entity/file_entity.pathauto.inc | 5 +
.../plugins/content_types/file_display.inc | 7 +-
.../file_entity/tests/file_entity_test.info | 6 +-
.../modules/contrib/honeypot/honeypot.info | 7 +-
.../modules/contrib/honeypot/honeypot.install | 2 +-
.../modules/contrib/honeypot/honeypot.module | 18 +-
.../modules/contrib/honeypot/honeypot.test | 34 +
.../contrib/honeypot/tests/honeypot_test.info | 7 +-
.../modules/contrib/libraries/CHANGELOG.txt | 23 +-
.../contrib/libraries/css/libraries.admin.css | 3 +
.../contrib/libraries/libraries.admin.inc | 141 +-
.../contrib/libraries/libraries.api.php | 2 +
.../contrib/libraries/libraries.drush.inc | 80 +-
.../modules/contrib/libraries/libraries.info | 7 +-
.../contrib/libraries/libraries.install | 10 +
.../contrib/libraries/libraries.module | 148 +-
.../contrib/libraries/libraries.theme.inc | 36 +
.../tests/LibrariesAdminWebTest.test | 27 +-
.../example_info_file.libraries.info | 7 +-
.../libraries_test_module.info | 7 +-
.../libraries_test_theme.info | 7 +-
.../contrib/media/includes/media.fields.inc | 8 +-
.../dkan/modules/contrib/media/media.info | 6 +-
.../dkan/modules/contrib/media/media.module | 21 +-
.../modules/contrib/media/media.views.inc | 2 +-
.../includes/media_bulk_upload.pages.inc | 1 +
.../media_bulk_upload/media_bulk_upload.info | 6 +-
.../media_internet/media_internet.info | 6 +-
.../tests/media_internet_test.info | 6 +-
.../media_migrate_file_types.info | 6 +-
.../includes/media_wysiwyg.filter.inc | 12 +-
.../includes/media_wysiwyg.pages.inc | 1 +
.../modules/media_wysiwyg/media_wysiwyg.info | 6 +-
.../media_wysiwyg_view_mode.info | 6 +-
.../media/modules/mediafield/mediafield.info | 6 +-
.../media/tests/media_module_test.info | 6 +-
.../display-options-background.png} | Bin
.../menu_block/{ => css}/menu-block.admin.css | 2 +-
.../contrib/menu_block/{ => js}/menu-block.js | 2 +-
.../contrib/menu_block/menu_block.admin.inc | 17 +-
.../contrib/menu_block/menu_block.info | 7 +-
.../contrib/menu_block/menu_block.module | 22 +-
.../contrib/menu_block/menu_block_export.info | 7 +-
.../content_types/menu_tree/menu_tree.inc | 4 +-
.../contrib/migrate/includes/migration.inc | 14 +-
.../dkan/modules/contrib/migrate/migrate.info | 6 +-
.../migrate_example/migrate_example.info | 6 +-
.../migrate_example_oracle.info | 6 +-
.../migrate_example_baseball.info | 6 +-
.../migrate/migrate_ui/migrate_ui.info | 6 +-
.../contrib/migrate/plugins/sources/json.inc | 2 +-
.../contrib/panopoly_images/CHANGELOG.txt | 16 +
.../panopoly_images/panopoly_images.info | 6 +-
.../contrib/panopoly_widgets/CHANGELOG.txt | 19 +
...nopoly_widgets.features.field_instance.inc | 8 +-
.../panopoly_widgets/panopoly_widgets.info | 6 +-
.../panopoly_widgets/panopoly_widgets.install | 54 +
.../panopoly_widgets/panopoly_widgets.make | 4 +-
.../panopoly_widgets.spotlight.inc | 26 +-
.../contrib/recline/backend.ckan_get.js | 2 +-
.../modules/contrib/recline/composer.json | 221 +
.../contrib/recline/dkan-module-init.sh | 6 +-
.../contrib/recline/js/restdataview.js | 4 +-
.../recline/lib/highlight/highlight.pack.js | 1 -
.../recline/lib/highlight/styles/default.css | 155 -
.../modules/contrib/recline/recline.theme.inc | 54 +-
.../modules/contrib/search_api/CHANGELOG.txt | 22 +
.../search_api_facetapi.info | 6 +-
.../contrib/search_api_views/README.txt | 15 +
.../includes/handler_filter_date.inc | 47 +-
.../includes/handler_filter_numeric.inc | 217 +
.../includes/handler_filter_options.inc | 1 +
.../includes/plugin_cache.inc | 11 +-
.../includes/plugin_content_cache.inc | 146 +
.../search_api_views/search_api_views.info | 8 +-
.../search_api_views.views.inc | 13 +
.../includes/callback_add_aggregation.inc | 8 +-
.../contrib/search_api/search_api.admin.inc | 49 +-
.../contrib/search_api/search_api.drush.inc | 80 +
.../contrib/search_api/search_api.info | 6 +-
.../contrib/search_api/search_api.install | 45 +
.../contrib/search_api/search_api.module | 127 +-
.../search_api/tests/search_api_test.info | 6 +-
.../search_api/tests/search_api_test_2.info | 6 +-
.../contrib/search_api_db/CHANGELOG.txt | 11 +
.../modules/contrib/search_api_db/PATCHES.txt | 4 -
.../contrib/search_api_db/search_api_db.info | 7 +-
.../modules/contrib/search_api_db/service.inc | 84 +-
.../modules/contrib/tablefield/README.txt | 25 +-
.../contrib/tablefield/tablefield.info | 7 +-
.../contrib/tablefield/tablefield.install | 13 +
.../contrib/tablefield/tablefield.module | 187 +-
.../contrib/tablefield/themeless/README.txt | 26 -
.../themeless/tablefield_themeless.info | 12 -
.../themeless/tablefield_themeless.module | 108 -
.../modules/contrib/taxonomy_menu/LICENSE.txt | 0
.../modules/contrib/taxonomy_menu/README.txt | 6 +-
.../modules/contrib/taxonomy_menu/UPGRADE.txt | 18 -
.../taxonomy_menu/taxonomy_menu.batch.inc | 103 +-
.../taxonomy_menu/taxonomy_menu.database.inc | 143 +-
.../contrib/taxonomy_menu/taxonomy_menu.info | 20 +-
.../taxonomy_menu/taxonomy_menu.install | 51 +-
.../taxonomy_menu/taxonomy_menu.module | 621 +-
.../contrib/taxonomy_menu/taxonomy_menu.test | 22 +-
.../modules/contrib/uuid/uuid.features.inc | 1 +
profiles/dkan/modules/contrib/uuid/uuid.info | 6 +-
.../contrib/uuid/uuid_path/uuid_path.info | 6 +-
.../uuid_services/uuid_services.admin.inc | 8 +
.../uuid/uuid_services/uuid_services.info | 12 +-
.../uuid/uuid_services/uuid_services.install | 14 +
.../uuid/uuid_services/uuid_services.module | 23 +-
.../uuid_services_example.info | 6 +-
.../contrib/workbench_moderation/PATCHES.txt | 5 +
.../workbench_moderation.features.inc | 18 +-
.../workbench_moderation.module | 38 +-
.../workbench_moderation.node.inc | 2 +-
.../modules/contrib/xautoload/.travis.yml | 5 +
.../contrib/{date => xautoload}/LICENSE.txt | 0
.../dkan/modules/contrib/xautoload/README.md | 3 +
.../lib/FinderPlugin/CheckIncludePath.php | 17 +
.../legacy/lib/FinderPlugin/Interface.php | 51 +
.../legacy/lib/InjectedAPI/hookXautoload.php | 248 +
.../Tests/EnvironmentSnapshotMaker.php | 62 +
.../xautoload/Tests/XAutoloadUnitTestCase.php | 130 +
.../xautoload/Tests/XAutoloadWebTestCase.php | 264 +
.../contrib/xautoload/phpunit.xml.dist | 19 +
.../src/Adapter/ClassFinderAdapter.php | 287 +
.../Adapter/ClassFinderAdapterInterface.php | 56 +
.../src/Adapter/DrupalExtensionAdapter.php | 174 +
.../src/Adapter/LocalDirectoryAdapter.php | 278 +
.../src/CacheManager/CacheManager.php | 68 +
.../CacheManagerObserverInterface.php | 14 +
.../CacheMissLoaderSetFinder.php | 48 +
.../CacheMissObserverInterface.php | 29 +
.../xautoload/src/ClassFinder/ClassFinder.php | 468 +
.../src/ClassFinder/ClassFinderInterface.php | 30 +
.../CommonRegistrationInterface.php | 81 +
.../ExtendedClassFinderInterface.php | 215 +
.../src/ClassFinder/GenericPrefixMap.php | 242 +
.../InjectedApi/AbstractInjectedApi.php | 50 +
.../InjectedApi/CollectFilesInjectedApi.php | 147 +
.../InjectedApi/FindFileInjectedApi.php | 145 +
.../InjectedApi/InjectedApiInterface.php | 138 +
.../LoadClassGetFileInjectedApi.php | 135 +
.../InjectedApi/LoadClassInjectedAPI.php | 105 +
.../Plugin/DrupalCoreRegistryPlugin.php | 79 +
.../DrupalExtensionNamespaceFinderPlugin.php | 219 +
.../DrupalExtensionUnderscoreFinderPlugin.php | 53 +
.../Plugin/FinderPluginInterface.php | 65 +
.../ClassFinder/Plugin/Psr4FinderPlugin.php | 34 +
.../src/ClassFinder/ProxyClassFinder.php | 90 +
.../ClassLoader/AbstractCachedClassLoader.php | 51 +
.../src/ClassLoader/AbstractClassLoader.php | 41 +
.../AbstractClassLoaderDecorator.php | 41 +
.../AbstractQueuedCachedClassLoader.php | 137 +
.../src/ClassLoader/ApcClassLoader.php | 39 +
.../src/ClassLoader/ApcuClassLoader.php | 38 +
.../ApcuQueuedCachedClassLoader.php | 52 +
.../CacheNotSupportedException.php | 5 +
.../src/ClassLoader/ClassLoaderInterface.php | 30 +
.../src/ClassLoader/DbCacheClassLoader.php | 58 +
.../src/ClassLoader/WinCacheClassLoader.php | 39 +
.../src/ClassLoader/XCacheClassLoader.php | 40 +
.../xautoload/src/DIC/ServiceContainer.php | 76 +
.../src/DIC/ServiceContainerInterface.php | 47 +
.../xautoload/src/DIC/ServiceFactory.php | 174 +
.../DefaultDirectoryBehavior.php | 13 +
.../DirectoryBehaviorInterface.php | 9 +
.../Psr0DirectoryBehavior.php | 13 +
.../src/Discovery/CachedClassMapGenerator.php | 45 +
.../src/Discovery/ClassMapGenerator.php | 47 +
.../Discovery/ClassMapGeneratorInterface.php | 14 +
.../xautoload/src/Discovery/ComposerDir.php | 81 +
.../xautoload/src/Discovery/ComposerJson.php | 132 +
.../src/Discovery/ComposerJsonTargetDir.php | 118 +
.../xautoload/src/Discovery/FileInspector.php | 98 +
.../src/Discovery/WildcardFileFinder.php | 211 +
.../src/DrupalSystem/DrupalSystem.php | 178 +
.../DrupalSystem/DrupalSystemInterface.php | 145 +
.../src/Libraries/LibrariesFinderPlugin.php | 127 +
.../src/Libraries/LibrariesInfoAlter.php | 55 +
.../src/Libraries/LibrariesOnInit.php | 91 +
.../Libraries/LibrariesPreLoadCallback.php | 51 +
.../Libraries/LibraryCacheMissObserver.php | 45 +
.../Libraries/SerializableClosureWrapper.php | 85 +
.../modules/contrib/xautoload/src/Main.php | 110 +
.../Phases/DrupalCoreRegistryRegistrator.php | 57 +
.../src/Phases/DrupalPhaseControl.php | 171 +
.../src/Phases/ExtensionNamespaces.php | 253 +
.../xautoload/src/Phases/HookXautoload.php | 111 +
.../src/Phases/HookXautoloadEarly.php | 110 +
.../src/Phases/PhaseObserverInterface.php | 63 +
.../modules/contrib/xautoload/src/Util.php | 202 +
.../contrib/xautoload/tests/bootstrap.php | 37 +
.../ComposerTargetDirTestLib/Foo.php | 8 +
.../ComposerTargetDirTestLib/composer.json | 8 +
.../.libraries/ComposerTestLib/composer.json | 8 +
.../.libraries/ComposerTestLib/other/Foo.php | 9 +
.../.libraries/ComposerTestLib/psr4/Foo.php | 9 +
.../fixtures/.libraries/testlib/src/Foo.php | 5 +
.../.modules/libraries/libraries.module | 12 +
.../fixtures/.modules/system/system.module | 1 +
.../fixtures/.modules/testmod/psr4/Foo.php | 5 +
.../fixtures/.modules/testmod/testmod.install | 16 +
.../fixtures/.modules/testmod/testmod.module | 91 +
.../.modules/testmod_pearflat/lib/Foo.php | 3 +
.../testmod_pearflat/testmod_pearflat.install | 15 +
.../testmod_pearflat/testmod_pearflat.module | 18 +
.../lib/Drupal/testmod_psr0_lib/Foo.php | 5 +
.../testmod_psr0_lib/testmod_psr0_lib.install | 19 +
.../testmod_psr0_lib/testmod_psr0_lib.module | 19 +
.../.modules/testmod_psr4_custom/psr4/Foo.php | 5 +
.../testmod_psr4_custom.install | 19 +
.../testmod_psr4_custom.module | 21 +
.../.modules/testmod_psr4_src/src/Foo.php | 5 +
.../testmod_psr4_src/testmod_psr4_src.install | 19 +
.../testmod_psr4_src/testmod_psr4_src.module | 19 +
.../WildcardFileFinder/handlers/bar.inc | 1 +
.../WildcardFileFinder/handlers/bar.php | 1 +
.../WildcardFileFinder/handlers/foo.inc | 1 +
.../WildcardFileFinder/handlers/sub/foo.inc | 1 +
.../fixtures/WildcardFileFinder/misc/abc | 0
.../fixtures/WildcardFileFinder/misc/foo.bar | 0
.../fixtures/WildcardFileFinder/misc/sub/xyz | 0
.../WildcardFileFinder/modules/foo.inc | 1 +
.../WildcardFileFinder/modules/sub/foo.inc | 1 +
.../modules/sub/sub/foo.inc | 1 +
.../modules/sub/sub/sub/foo.inc | 1 +
.../fixtures/WildcardFileFinder/tests/foo.inc | 1 +
.../WildcardFileFinder/tests/foo.test | 1 +
.../WildcardFileFinder/tests/sub/foo.test | 1 +
.../WildcardFileFinder/tests/sub/sub/foo.test | 1 +
.../tests/sub/sub/sub/foo.test | 1 +
.../xautoload/tests/scripts/drupal-apc.php | 16 +
.../tests/src/BasicIntegrityTest.php | 56 +
.../tests/src/ClassFinderAdapterTest.php | 35 +
.../xautoload/tests/src/ClassLoaderTest.php | 210 +
.../xautoload/tests/src/ComposerJsonTest.php | 35 +
.../xautoload/tests/src/DiscoveryTest.php | 63 +
.../DrupalBootTest/AbstractDrupalBootTest.php | 221 +
.../src/DrupalBootTest/DrupalBootHookTest.php | 172 +
.../src/DrupalBootTest/DrupalBootTest.php | 157 +
.../src/Example/AbstractExampleModules.php | 106 +
.../tests/src/Example/ExampleModules.php | 52 +
.../src/Example/HookTestExampleModules.php | 56 +
.../tests/src/Filesystem/StreamWrapper.php | 159 +
.../src/Filesystem/VirtualFilesystem.php | 273 +
.../tests/src/Mock/MockDrupalSystem.php | 246 +
.../xautoload/tests/src/Util/CallLog.php | 75 +
.../xautoload/tests/src/Util/HackyLog.php | 23 +
.../tests/src/Util/StaticCallLog.php | 74 +
.../tests/src/VirtualDrupal/Cache.php | 68 +
.../src/VirtualDrupal/DrupalBootstrap.php | 120 +
.../DrupalComponentContainer.php | 268 +
.../src/VirtualDrupal/DrupalEnvironment.php | 106 +
.../src/VirtualDrupal/DrupalGetFilename.php | 97 +
.../tests/src/VirtualDrupal/DrupalLoad.php | 46 +
.../tests/src/VirtualDrupal/DrupalStatic.php | 88 +
.../VirtualDrupal/ExampleModulesInterface.php | 48 +
.../tests/src/VirtualDrupal/HookSystem.php | 71 +
.../tests/src/VirtualDrupal/LibrariesInfo.php | 198 +
.../tests/src/VirtualDrupal/LibrariesLoad.php | 337 +
.../VirtualDrupal/ModuleBuildDependencies.php | 190 +
.../tests/src/VirtualDrupal/ModuleEnable.php | 221 +
.../src/VirtualDrupal/ModuleImplements.php | 197 +
.../tests/src/VirtualDrupal/ModuleList.php | 124 +
.../tests/src/VirtualDrupal/PureFunctions.php | 39 +
.../VirtualDrupal/SystemBuildModuleData.php | 110 +
.../tests/src/VirtualDrupal/SystemList.php | 123 +
.../src/VirtualDrupal/SystemListLoader.php | 156 +
.../src/VirtualDrupal/SystemListReset.php | 39 +
.../VirtualDrupal/SystemRebuildModuleData.php | 92 +
.../tests/src/VirtualDrupal/SystemTable.php | 330 +
.../SystemUpdateBootstrapStatus.php | 49 +
.../Drupal/xautoload_test_1/ExampleClass.php | 5 +
.../tests/test_1/xautoload_test_1.info | 14 +
.../tests/test_1/xautoload_test_1.module | 43 +
.../tests/test_2/lib/ExampleClass.php | 3 +
.../tests/test_2/xautoload_test_2.info | 14 +
.../tests/test_2/xautoload_test_2.module | 45 +
.../tests/test_3/lib/ExampleClass.php | 5 +
.../tests/test_3/xautoload_test_3.info | 14 +
.../tests/test_3/xautoload_test_3.module | 48 +
.../tests/test_4/testlib/src/TestClass.php | 7 +
.../tests/test_4/xautoload_test_4.info | 11 +
.../tests/test_4/xautoload_test_4.install | 9 +
.../tests/test_4/xautoload_test_4.module | 26 +
.../test_5/src/FooNamespace/Foo_Class.php | 9 +
.../tests/test_5/xautoload_test_5.info | 11 +
.../tests/test_5/xautoload_test_5.install | 9 +
.../tests/test_5/xautoload_test_5.module | 17 +
.../contrib/xautoload/xautoload.api.php | 95 +
.../contrib/xautoload/xautoload.early.inc | 14 +
.../contrib/xautoload/xautoload.early.lib.inc | 125 +
.../contrib/xautoload/xautoload.emulate.inc | 71 +
.../modules/contrib/xautoload/xautoload.info | 11 +
.../contrib/xautoload/xautoload.install | 35 +
.../contrib/xautoload/xautoload.libraries.inc | 15 +
.../contrib/xautoload/xautoload.module | 198 +
.../contrib/xautoload/xautoload.system.inc | 41 +
.../contrib/xautoload/xautoload.ui.inc | 89 +
.../dkan_data_dashboard.info | 2 +-
.../dkan/dkan_data_story/dkan_data_story.info | 2 +-
.../dkan/dkan_dataset/dkan_dataset.info | 2 +-
.../dkan/dkan_dataset/dkan_dataset.module | 99 +-
...aset_content_types.features.field_base.inc | 50 +-
..._content_types.features.field_instance.inc | 21 +-
.../dkan_dataset_content_types.info | 2 +-
...an_dataset_content_types.license_field.inc | 16 +-
.../dkan_dataset_content_types.module | 43 +-
.../dkan_dataset_groups.info | 2 +-
.../dkan_dataset_groups_perms.info | 2 +-
.../dkan_dataset_rest_api.info | 6 +-
.../dkan_dataset_rest_api.module | 10 -
.../dkan_dataset_rest_api.services.inc | 34 +
.../dkan_dataset_voting.info | 2 +-
.../dkan_dataset/tests/dkan_dataset_test.info | 2 +-
.../modules/dkan/dkan_datastore/README.md | 68 -
.../dkan_datastore/dkan_datastore.drush.inc | 35 +
.../dkan_datastore.features.inc | 9 -
.../dkan_datastore.feeds_importer_default.inc | 95 -
.../dkan/dkan_datastore/dkan_datastore.info | 14 +-
.../dkan/dkan_datastore/dkan_datastore.module | 698 +-
.../dkan_datastore/dkan_datastore.pages.inc | 188 +-
.../dkan_datastore/includes/Datastore.inc | 247 -
.../dkan_datastore/includes/DkanDatastore.inc | 689 --
.../includes/DkanDatastoreFastImport.inc | 78 -
.../dkan_datastore_api.drush.inc | 245 -
.../dkan_datastore_api.info | 3 +-
.../dkan_datastore_api.module | 556 +-
.../dkan_datastore_fast_import.info | 2 +-
.../dkan_datastore_fast_import.install | 29 +-
.../dkan_datastore_fast_import.js | 9 -
.../dkan_datastore_fast_import.module | 322 +-
.../src/FastImport.php | 67 +
.../dkan_datastore_simple_import.info | 6 +
.../dkan_datastore_simple_import.module | 31 +
.../src/SimpleImport.php | 163 +
.../resources/dkan_datastore_resource.inc | 374 +
.../src/LockableDrupalVariables.php | 158 +
.../dkan_datastore/src/Manager/Factory.php | 107 +
.../dkan/dkan_datastore/src/Manager/Info.php | 56 +
.../dkan_datastore/src/Manager/Manager.php | 363 +
.../src/Manager/ManagerInterface.php | 120 +
.../Page/Component/ManagerConfiguration.php | 95 +
.../src/Page/Component/ManagerSelection.php | 69 +
.../src/Page/Component/Status.php | 88 +
.../dkan/dkan_datastore/src/Page/Page.php | 258 +
.../dkan/dkan_datastore/src/Parser/Base.php | 77 +
.../dkan/dkan_datastore/src/Parser/Csv.php | 216 +
.../dkan_datastore/src/Parser/CsvBase.php | 60 +
.../dkan/dkan_datastore/src/Parser/Field.php | 93 +
.../dkan_datastore/src/Parser/QuotedField.php | 175 +
.../dkan/dkan_datastore/src/Resource.php | 128 +
.../dkan/dkan_datastore/src/StateMachine.php | 223 +
.../dkan_environment/dkan_environment.info | 2 +-
.../dkan_environment/dkan_environment.module | 6 +-
.../dkan/dkan_fixtures/dkan_fixtures.info | 2 +-
.../resource/Polling_Places_Madison_0.csv | 118 -
.../dkan_default_content.info | 2 +-
.../dkan_harvest.features.field_base.inc | 6 +-
.../dkan/dkan_harvest/dkan_harvest.info | 2 +-
.../dkan_harvest/dkan_harvest.migrate.inc | 19 +-
.../css/dkan_harvest_dashboard.css | 9 +
.../dkan_harvest_dashboard.info | 2 +-
.../dkan_harvest_dashboard.module | 1 -
.../dkan_harvest_dashboard.views_default.inc | 271 +-
.../dkan_harvest_datajson.info | 2 +-
.../dkan_harvest_datajson.migrate.inc | 42 +-
.../dkan_harvest_datajson.module | 8 +-
.../dkan_harvest_test/dkan_harvest_test.info | 2 +-
.../dkan/modules/dkan/dkan_ipe/dkan_ipe.info | 2 +-
.../dkan_linkchecker/dkan_linkchecker.info | 2 +-
.../modules/dkan/dkan_migrate_base/README.md | 8 +-
.../dkan_migrate_base/dkan_migrate_base.info | 2 +-
.../dkan_migrate_base_example.info | 2 +-
...permissions.features.roles_permissions.inc | 3 -
.../dkan_permissions/dkan_permissions.info | 2 +-
.../dkan/dkan_plugins/dkan_plugins.info | 2 +-
.../dkan/dkan_sitewide/dkan_sitewide.info | 2 +-
.../dkan_sitewide_context.info | 2 +-
.../dkan_sitewide_demo_front.info | 2 +-
.../dkan_sitewide_menu.info | 2 +-
.../dkan_sitewide_panelizer.info | 2 +-
.../dkan_sitewide_panels.info | 2 +-
.../dkan_sitewide_roles_perms.info | 2 +-
.../dkan_sitewide_search_db.info | 2 +-
.../dkan_sitewide_user.info | 2 +-
.../modules/facet_icons/facet_icons.info | 2 +-
.../modules/dkan/dkan_topics/dkan_topics.info | 2 +-
.../dkan_default_topics.info | 2 +-
...dkan_workflow.features.workbench_email.inc | 240 +-
.../dkan/dkan_workflow/dkan_workflow.info | 3 +-
.../dkan/dkan_workflow/dkan_workflow.module | 32 +-
.../dkan_workflow_permissions.info | 2 +-
.../views_dkan_workflow_tree.info | 2 +-
.../open_data_federal_extras.info | 2 +-
.../open_data_schema_map_dkan.features.inc | 16 +-
.../open_data_schema_map_dkan.info | 2 +-
.../DKANExtension/Context/DatasetContext.php | 3 +
.../dkan/test/features/dataset.author.feature | 2 +-
profiles/dkan/test/features/datastore.feature | Bin 5365 -> 3088 bytes
.../features/datastore.pages.access.feature | Bin 0 -> 2834 bytes
.../features/datastore_fast_import.feature | 95 -
.../dkan/test/features/dkan_harvest.feature | 10 +-
.../{page.editor.feature => page.sm.feature} | 2 +-
.../dkan/test/features/resource.admin.feature | 67 +-
.../test/features/resource.author.feature | 127 +-
.../test/features/resource.editor.feature | 63 +-
profiles/dkan/test/features/search.feature | 7 +-
.../dkan/test/features/user.editor.feature | 6 +-
.../DkanDatastoreCsvParserTest.php | 378 +
.../dkan_datastore/DkanDatastoreTest.php | 201 +-
.../DkanDatastoreAPITest.php | 88 +-
.../DkanDatastoreFastImportTest.php | 190 -
.../files/null_check.csv | 3 -
.../files/polling_places.csv | 118 -
.../DatajsonHarvestMigrationScenariosTest.php | 4 +-
.../DatajsonHarvestMigrationTest.php | 2 +-
profiles/dkan/test/phpunit/phpunit.xml | 1 +
.../assets/css/nuboot_radix.style.css | 5 -
.../themes/nuboot_radix/nuboot_radix.info | 2 +-
.../scss/components/_harvest.scss | 12 +-
sites/default/default.settings.php | 17 +
564 files changed, 34410 insertions(+), 7136 deletions(-)
create mode 100644 includes/common.inc.orig
create mode 100644 modules/simpletest/tests/themes/test_theme/theme-settings.php
create mode 100644 profiles/dkan/.github/ISSUE_TEMPLATE.md
delete mode 100644 profiles/dkan/docs/admin/people/dkan_workflow.rst
create mode 100644 profiles/dkan/docs/development/display-num-datasets.rst
create mode 100644 profiles/dkan/docs/releases/notes/1.15.5.md
create mode 100644 profiles/dkan/docs/releases/notes/1.16.md
create mode 100644 profiles/dkan/modules/contrib/autoload/README.md
create mode 100644 profiles/dkan/modules/contrib/autoload/autoload.api.php
create mode 100644 profiles/dkan/modules/contrib/autoload/autoload.cache.inc
create mode 100644 profiles/dkan/modules/contrib/autoload/autoload.drush.inc
create mode 100644 profiles/dkan/modules/contrib/autoload/autoload.info
create mode 100644 profiles/dkan/modules/contrib/autoload/autoload.install
create mode 100644 profiles/dkan/modules/contrib/autoload/autoload.module
create mode 100644 profiles/dkan/modules/contrib/autoload/src/Tests/Unit/AutoloadTestBase.php
create mode 100644 profiles/dkan/modules/contrib/autoload/src/Tests/Unit/CacheTest.php
create mode 100644 profiles/dkan/modules/contrib/autoload/src/Tests/Unit/CustomTest.php
create mode 100644 profiles/dkan/modules/contrib/autoload/src/Tests/Unit/DrupalTest.php
create mode 100644 profiles/dkan/modules/contrib/autoload/src/Tests/Unit/EntityTest.php
create mode 100644 profiles/dkan/modules/contrib/autoload/src/Tests/Unit/ExtensionsTest.php
create mode 100644 profiles/dkan/modules/contrib/autoload/tests/autoload.php
create mode 100644 profiles/dkan/modules/contrib/autoload/tests/autoload_test_custom/autoload_test_custom.info
create mode 100644 profiles/dkan/modules/contrib/autoload/tests/autoload_test_custom/autoload_test_custom.module
create mode 100644 profiles/dkan/modules/contrib/autoload/tests/autoload_test_custom/psr-0/Autoload/Tests/PSR0.inc
create mode 100644 profiles/dkan/modules/contrib/autoload/tests/autoload_test_custom/psr-4-single-level-namespace/PSR4.inc
create mode 100644 profiles/dkan/modules/contrib/autoload/tests/autoload_test_custom/psr-4/PSR4.inc
create mode 100644 profiles/dkan/modules/contrib/autoload/tests/autoload_test_custom/tests/Test.inc
create mode 100644 profiles/dkan/modules/contrib/autoload/tests/autoload_test_custom/wrong-namespace/WrongNamespace.php
create mode 100644 profiles/dkan/modules/contrib/autoload/tests/autoload_test_drupal/autoload_test_drupal.info
create mode 100644 profiles/dkan/modules/contrib/autoload/tests/autoload_test_drupal/autoload_test_drupal.module
create mode 100644 profiles/dkan/modules/contrib/autoload/tests/autoload_test_drupal/lib/Drupal/autoload_test_drupal/PSR0.php
create mode 100644 profiles/dkan/modules/contrib/autoload/tests/autoload_test_drupal/src/PSR4.inc
create mode 100644 profiles/dkan/modules/contrib/autoload/tests/autoload_test_entity/autoload_test_entity.info
create mode 100644 profiles/dkan/modules/contrib/autoload/tests/autoload_test_entity/autoload_test_entity.install
create mode 100644 profiles/dkan/modules/contrib/autoload/tests/autoload_test_entity/autoload_test_entity.module
create mode 100644 profiles/dkan/modules/contrib/autoload/tests/autoload_test_entity/modules/autoload_test_entity_ui/autoload_test_entity_ui.info
create mode 100644 profiles/dkan/modules/contrib/autoload/tests/autoload_test_entity/modules/autoload_test_entity_ui/autoload_test_entity_ui.module
create mode 100644 profiles/dkan/modules/contrib/autoload/tests/autoload_test_entity/modules/autoload_test_entity_ui/src/ViewsController.php
create mode 100644 profiles/dkan/modules/contrib/autoload/tests/autoload_test_extensions/autoload_test_extensions.info
create mode 100644 profiles/dkan/modules/contrib/autoload/tests/autoload_test_extensions/autoload_test_extensions.module
create mode 100644 profiles/dkan/modules/contrib/autoload/tests/autoload_test_extensions/src/PSR4.test
create mode 100644 profiles/dkan/modules/contrib/autoload/tests/autoload_test_lookup/autoload_test_lookup.info
create mode 100644 profiles/dkan/modules/contrib/autoload/tests/autoload_test_lookup/autoload_test_lookup.module
create mode 100644 profiles/dkan/modules/contrib/file_entity/README.txt
create mode 100644 profiles/dkan/modules/contrib/libraries/css/libraries.admin.css
create mode 100644 profiles/dkan/modules/contrib/libraries/libraries.theme.inc
rename profiles/dkan/modules/contrib/menu_block/{menu-block-background-display-options.png => css/display-options-background.png} (100%)
rename profiles/dkan/modules/contrib/menu_block/{ => css}/menu-block.admin.css (96%)
rename profiles/dkan/modules/contrib/menu_block/{ => js}/menu-block.js (95%)
create mode 100644 profiles/dkan/modules/contrib/recline/composer.json
delete mode 100644 profiles/dkan/modules/contrib/recline/lib/highlight/highlight.pack.js
delete mode 100644 profiles/dkan/modules/contrib/recline/lib/highlight/styles/default.css
create mode 100644 profiles/dkan/modules/contrib/search_api/contrib/search_api_views/includes/handler_filter_numeric.inc
create mode 100644 profiles/dkan/modules/contrib/search_api/contrib/search_api_views/includes/plugin_content_cache.inc
delete mode 100644 profiles/dkan/modules/contrib/search_api_db/PATCHES.txt
delete mode 100644 profiles/dkan/modules/contrib/tablefield/themeless/README.txt
delete mode 100644 profiles/dkan/modules/contrib/tablefield/themeless/tablefield_themeless.info
delete mode 100644 profiles/dkan/modules/contrib/tablefield/themeless/tablefield_themeless.module
mode change 100755 => 100644 profiles/dkan/modules/contrib/taxonomy_menu/LICENSE.txt
delete mode 100644 profiles/dkan/modules/contrib/taxonomy_menu/UPGRADE.txt
create mode 100644 profiles/dkan/modules/contrib/uuid/uuid_services/uuid_services.install
create mode 100644 profiles/dkan/modules/contrib/workbench_moderation/PATCHES.txt
create mode 100644 profiles/dkan/modules/contrib/xautoload/.travis.yml
rename profiles/dkan/modules/contrib/{date => xautoload}/LICENSE.txt (100%)
create mode 100644 profiles/dkan/modules/contrib/xautoload/README.md
create mode 100644 profiles/dkan/modules/contrib/xautoload/legacy/lib/FinderPlugin/CheckIncludePath.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/legacy/lib/FinderPlugin/Interface.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/legacy/lib/InjectedAPI/hookXautoload.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/lib/Drupal/xautoload/Tests/EnvironmentSnapshotMaker.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/lib/Drupal/xautoload/Tests/XAutoloadUnitTestCase.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/lib/Drupal/xautoload/Tests/XAutoloadWebTestCase.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/phpunit.xml.dist
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/Adapter/ClassFinderAdapter.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/Adapter/ClassFinderAdapterInterface.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/Adapter/DrupalExtensionAdapter.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/Adapter/LocalDirectoryAdapter.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/CacheManager/CacheManager.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/CacheManager/CacheManagerObserverInterface.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/CacheMissObserver/CacheMissLoaderSetFinder.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/CacheMissObserver/CacheMissObserverInterface.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassFinder/ClassFinder.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassFinder/ClassFinderInterface.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassFinder/CommonRegistrationInterface.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassFinder/ExtendedClassFinderInterface.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassFinder/GenericPrefixMap.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/AbstractInjectedApi.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/CollectFilesInjectedApi.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/FindFileInjectedApi.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/InjectedApiInterface.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/LoadClassGetFileInjectedApi.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/LoadClassInjectedAPI.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/DrupalCoreRegistryPlugin.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/DrupalExtensionNamespaceFinderPlugin.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/DrupalExtensionUnderscoreFinderPlugin.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/FinderPluginInterface.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/Psr4FinderPlugin.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassFinder/ProxyClassFinder.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassLoader/AbstractCachedClassLoader.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassLoader/AbstractClassLoader.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassLoader/AbstractClassLoaderDecorator.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassLoader/AbstractQueuedCachedClassLoader.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassLoader/ApcClassLoader.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassLoader/ApcuClassLoader.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassLoader/ApcuQueuedCachedClassLoader.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassLoader/CacheNotSupportedException.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassLoader/ClassLoaderInterface.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassLoader/DbCacheClassLoader.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassLoader/WinCacheClassLoader.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/ClassLoader/XCacheClassLoader.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/DIC/ServiceContainer.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/DIC/ServiceContainerInterface.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/DIC/ServiceFactory.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/DirectoryBehavior/DefaultDirectoryBehavior.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/DirectoryBehavior/DirectoryBehaviorInterface.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/DirectoryBehavior/Psr0DirectoryBehavior.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/Discovery/CachedClassMapGenerator.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/Discovery/ClassMapGenerator.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/Discovery/ClassMapGeneratorInterface.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/Discovery/ComposerDir.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/Discovery/ComposerJson.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/Discovery/ComposerJsonTargetDir.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/Discovery/FileInspector.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/Discovery/WildcardFileFinder.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/DrupalSystem/DrupalSystem.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/DrupalSystem/DrupalSystemInterface.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/Libraries/LibrariesFinderPlugin.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/Libraries/LibrariesInfoAlter.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/Libraries/LibrariesOnInit.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/Libraries/LibrariesPreLoadCallback.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/Libraries/LibraryCacheMissObserver.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/Libraries/SerializableClosureWrapper.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/Main.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/Phases/DrupalCoreRegistryRegistrator.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/Phases/DrupalPhaseControl.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/Phases/ExtensionNamespaces.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/Phases/HookXautoload.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/Phases/HookXautoloadEarly.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/Phases/PhaseObserverInterface.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/src/Util.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/bootstrap.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/.libraries/ComposerTargetDirTestLib/Foo.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/.libraries/ComposerTargetDirTestLib/composer.json
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/.libraries/ComposerTestLib/composer.json
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/.libraries/ComposerTestLib/other/Foo.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/.libraries/ComposerTestLib/psr4/Foo.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/.libraries/testlib/src/Foo.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/.modules/libraries/libraries.module
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/.modules/system/system.module
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod/psr4/Foo.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod/testmod.install
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod/testmod.module
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_pearflat/lib/Foo.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_pearflat/testmod_pearflat.install
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_pearflat/testmod_pearflat.module
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_psr0_lib/lib/Drupal/testmod_psr0_lib/Foo.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_psr0_lib/testmod_psr0_lib.install
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_psr0_lib/testmod_psr0_lib.module
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_psr4_custom/psr4/Foo.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_psr4_custom/testmod_psr4_custom.install
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_psr4_custom/testmod_psr4_custom.module
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_psr4_src/src/Foo.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_psr4_src/testmod_psr4_src.install
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_psr4_src/testmod_psr4_src.module
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/handlers/bar.inc
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/handlers/bar.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/handlers/foo.inc
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/handlers/sub/foo.inc
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/misc/abc
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/misc/foo.bar
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/misc/sub/xyz
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/modules/foo.inc
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/modules/sub/foo.inc
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/modules/sub/sub/foo.inc
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/modules/sub/sub/sub/foo.inc
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/tests/foo.inc
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/tests/foo.test
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/tests/sub/foo.test
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/tests/sub/sub/foo.test
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/tests/sub/sub/sub/foo.test
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/scripts/drupal-apc.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/BasicIntegrityTest.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/ClassFinderAdapterTest.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/ClassLoaderTest.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/ComposerJsonTest.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/DiscoveryTest.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/DrupalBootTest/AbstractDrupalBootTest.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/DrupalBootTest/DrupalBootHookTest.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/DrupalBootTest/DrupalBootTest.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/Example/AbstractExampleModules.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/Example/ExampleModules.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/Example/HookTestExampleModules.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/Filesystem/StreamWrapper.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/Filesystem/VirtualFilesystem.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/Mock/MockDrupalSystem.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/Util/CallLog.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/Util/HackyLog.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/Util/StaticCallLog.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/Cache.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalBootstrap.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalComponentContainer.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalEnvironment.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalGetFilename.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalLoad.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalStatic.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ExampleModulesInterface.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/HookSystem.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/LibrariesInfo.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/LibrariesLoad.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleBuildDependencies.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleEnable.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleImplements.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleList.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/PureFunctions.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemBuildModuleData.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemList.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemListLoader.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemListReset.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemRebuildModuleData.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemTable.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemUpdateBootstrapStatus.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/test_1/lib/Drupal/xautoload_test_1/ExampleClass.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/test_1/xautoload_test_1.info
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/test_1/xautoload_test_1.module
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/test_2/lib/ExampleClass.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/test_2/xautoload_test_2.info
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/test_2/xautoload_test_2.module
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/test_3/lib/ExampleClass.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/test_3/xautoload_test_3.info
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/test_3/xautoload_test_3.module
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/test_4/testlib/src/TestClass.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/test_4/xautoload_test_4.info
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/test_4/xautoload_test_4.install
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/test_4/xautoload_test_4.module
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/test_5/src/FooNamespace/Foo_Class.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/test_5/xautoload_test_5.info
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/test_5/xautoload_test_5.install
create mode 100644 profiles/dkan/modules/contrib/xautoload/tests/test_5/xautoload_test_5.module
create mode 100644 profiles/dkan/modules/contrib/xautoload/xautoload.api.php
create mode 100644 profiles/dkan/modules/contrib/xautoload/xautoload.early.inc
create mode 100644 profiles/dkan/modules/contrib/xautoload/xautoload.early.lib.inc
create mode 100644 profiles/dkan/modules/contrib/xautoload/xautoload.emulate.inc
create mode 100644 profiles/dkan/modules/contrib/xautoload/xautoload.info
create mode 100644 profiles/dkan/modules/contrib/xautoload/xautoload.install
create mode 100644 profiles/dkan/modules/contrib/xautoload/xautoload.libraries.inc
create mode 100644 profiles/dkan/modules/contrib/xautoload/xautoload.module
create mode 100644 profiles/dkan/modules/contrib/xautoload/xautoload.system.inc
create mode 100644 profiles/dkan/modules/contrib/xautoload/xautoload.ui.inc
create mode 100644 profiles/dkan/modules/dkan/dkan_datastore/dkan_datastore.drush.inc
delete mode 100644 profiles/dkan/modules/dkan/dkan_datastore/dkan_datastore.feeds_importer_default.inc
delete mode 100644 profiles/dkan/modules/dkan/dkan_datastore/includes/Datastore.inc
delete mode 100644 profiles/dkan/modules/dkan/dkan_datastore/includes/DkanDatastore.inc
delete mode 100644 profiles/dkan/modules/dkan/dkan_datastore/includes/DkanDatastoreFastImport.inc
delete mode 100644 profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_api/dkan_datastore_api.drush.inc
delete mode 100644 profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/dkan_datastore_fast_import.js
create mode 100644 profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/src/FastImport.php
create mode 100644 profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_simple_import/dkan_datastore_simple_import.info
create mode 100644 profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_simple_import/dkan_datastore_simple_import.module
create mode 100644 profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_simple_import/src/SimpleImport.php
create mode 100644 profiles/dkan/modules/dkan/dkan_datastore/resources/dkan_datastore_resource.inc
create mode 100644 profiles/dkan/modules/dkan/dkan_datastore/src/LockableDrupalVariables.php
create mode 100644 profiles/dkan/modules/dkan/dkan_datastore/src/Manager/Factory.php
create mode 100644 profiles/dkan/modules/dkan/dkan_datastore/src/Manager/Info.php
create mode 100644 profiles/dkan/modules/dkan/dkan_datastore/src/Manager/Manager.php
create mode 100644 profiles/dkan/modules/dkan/dkan_datastore/src/Manager/ManagerInterface.php
create mode 100644 profiles/dkan/modules/dkan/dkan_datastore/src/Page/Component/ManagerConfiguration.php
create mode 100644 profiles/dkan/modules/dkan/dkan_datastore/src/Page/Component/ManagerSelection.php
create mode 100644 profiles/dkan/modules/dkan/dkan_datastore/src/Page/Component/Status.php
create mode 100644 profiles/dkan/modules/dkan/dkan_datastore/src/Page/Page.php
create mode 100644 profiles/dkan/modules/dkan/dkan_datastore/src/Parser/Base.php
create mode 100644 profiles/dkan/modules/dkan/dkan_datastore/src/Parser/Csv.php
create mode 100644 profiles/dkan/modules/dkan/dkan_datastore/src/Parser/CsvBase.php
create mode 100644 profiles/dkan/modules/dkan/dkan_datastore/src/Parser/Field.php
create mode 100644 profiles/dkan/modules/dkan/dkan_datastore/src/Parser/QuotedField.php
create mode 100644 profiles/dkan/modules/dkan/dkan_datastore/src/Resource.php
create mode 100644 profiles/dkan/modules/dkan/dkan_datastore/src/StateMachine.php
delete mode 100644 profiles/dkan/modules/dkan/dkan_fixtures/modules/dkan_default_content/data/files/resource/Polling_Places_Madison_0.csv
create mode 100644 profiles/dkan/test/features/datastore.pages.access.feature
delete mode 100644 profiles/dkan/test/features/datastore_fast_import.feature
rename profiles/dkan/test/features/{page.editor.feature => page.sm.feature} (91%)
create mode 100644 profiles/dkan/test/phpunit/dkan_datastore/DkanDatastoreCsvParserTest.php
delete mode 100644 profiles/dkan/test/phpunit/dkan_datastore_fast_import/DkanDatastoreFastImportTest.php
delete mode 100644 profiles/dkan/test/phpunit/dkan_datastore_fast_import/files/null_check.csv
delete mode 100644 profiles/dkan/test/phpunit/dkan_datastore_fast_import/files/polling_places.csv
diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index fa3f693946b..09094ad42dd 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -1,3 +1,20 @@
+Drupal 7.61, 2018-11-07
+-----------------------
+- File upload validation functions and hook_file_validate() implementations are
+ now always passed the correct file URI.
+- The default form cache expiration of 6 hours is now configurable (API
+ addition: https://www.drupal.org/node/2857751).
+- Allowed callers of drupal_http_request() to optionally specify an explicit
+ Host header.
+- Allowed the + character to appear in usernames.
+- PHP 7.2: Fixed Archive_Tar incompatibility.
+- PHP 7.2: Removed deprecated function each().
+- PHP 7.2: Avoid count() calls on uncountable variables.
+- PHP 7.2: Removed deprecated create_function() call.
+- PHP 7.2: Make sure variables are arrays in theme_links().
+- Fixed theme-settings.php not being loaded on cached forms
+- Fixed problem with IE11 & Chrome(PointerEvents enabled) & some Firefox scroll to the top of the page after dragging the bottom item with jquery 1.5 <-> 1.11
+
Drupal 7.60, 2018-10-18
------------------------
- Fixed security issues. See SA-CORE-2018-006.
@@ -8,7 +25,7 @@ Drupal 7.59, 2018-04-25
Drupal 7.58, 2018-03-28
-----------------------
-- Fixed security issues (multiple vulnerabilities). See SA-CORE-2018-002.
+- Fixed security issues (remote code execution). See SA-CORE-2018-002.
Drupal 7.57, 2018-02-21
-----------------------
@@ -1031,7 +1048,7 @@ Drupal 7.1, 2011-05-25
----------------------
- Fixed security issues (Cross site scripting, File access bypass), see SA-CORE-2011-001.
-Drupal 7.0, 2011-01-05
+Drupal 7.0, 2011-01-05
----------------------
- Database:
* Fully rewritten database layer utilizing PHP 5's PDO abstraction layer.
@@ -1530,7 +1547,7 @@ Drupal 5.20, 2009-09-16
Drupal 5.19, 2009-07-01
-----------------------
- Fixed security issues (Cross site scripting and Password leakage in URL), see
- SA-CORE-2009-007.
+ SA-CORE-2009-007.
- Fixed a variety of small bugs.
Drupal 5.18, 2009-05-13
diff --git a/MAINTAINERS.txt b/MAINTAINERS.txt
index 5603a432915..460658c1765 100644
--- a/MAINTAINERS.txt
+++ b/MAINTAINERS.txt
@@ -15,6 +15,7 @@ The branch maintainers for Drupal 7 are:
- Fabian Franz 'Fabianx' https://www.drupal.org/u/fabianx
- David Rothstein 'David_Rothstein' https://www.drupal.org/u/david_rothstein
- Stefan Ruijsenaars 'stefan.r' https://www.drupal.org/u/stefanr-0
+- (provisional) Pol Dellaiera 'Pol' https://www.drupal.org/u/pol
Component maintainers
@@ -44,10 +45,9 @@ Cron system
- Derek Wright 'dww' https://www.drupal.org/u/dww
Database system
-- Larry Garfield 'Crell' https://www.drupal.org/u/crell
+- ?
- MySQL driver
- - Larry Garfield 'Crell' https://www.drupal.org/u/crell
- David Strauss 'David Strauss' https://www.drupal.org/u/david-strauss
- PostgreSQL driver
diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc
index 837cb70bbd1..cacfeb09153 100644
--- a/includes/bootstrap.inc
+++ b/includes/bootstrap.inc
@@ -8,7 +8,7 @@
/**
* The current system version.
*/
-define('VERSION', '7.60');
+define('VERSION', '7.61');
/**
* Core API compatibility.
@@ -3826,8 +3826,12 @@ function _drupal_shutdown_function() {
chdir(DRUPAL_ROOT);
try {
- while (list($key, $callback) = each($callbacks)) {
+ // Manually iterate over the array instead of using a foreach loop.
+ // A foreach operates on a copy of the array, so any shutdown functions that
+ // were added from other shutdown functions would never be called.
+ while ($callback = current($callbacks)) {
call_user_func_array($callback['callback'], $callback['arguments']);
+ next($callbacks);
}
}
catch (Exception $exception) {
diff --git a/includes/common.inc b/includes/common.inc
index e708c52cbd4..a7d95656ab0 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -867,8 +867,10 @@ function drupal_http_request($url, array $options = array()) {
// Make the socket connection to a proxy server.
$socket = 'tcp://' . $proxy_server . ':' . variable_get('proxy_port', 8080);
// The Host header still needs to match the real request.
- $options['headers']['Host'] = $uri['host'];
- $options['headers']['Host'] .= isset($uri['port']) && $uri['port'] != 80 ? ':' . $uri['port'] : '';
+ if (!isset($options['headers']['Host'])) {
+ $options['headers']['Host'] = $uri['host'];
+ $options['headers']['Host'] .= isset($uri['port']) && $uri['port'] != 80 ? ':' . $uri['port'] : '';
+ }
break;
case 'http':
@@ -878,14 +880,18 @@ function drupal_http_request($url, array $options = array()) {
// RFC 2616: "non-standard ports MUST, default ports MAY be included".
// We don't add the standard port to prevent from breaking rewrite rules
// checking the host that do not take into account the port number.
- $options['headers']['Host'] = $uri['host'] . ($port != 80 ? ':' . $port : '');
+ if (!isset($options['headers']['Host'])) {
+ $options['headers']['Host'] = $uri['host'] . ($port != 80 ? ':' . $port : '');
+ }
break;
case 'https':
// Note: Only works when PHP is compiled with OpenSSL support.
$port = isset($uri['port']) ? $uri['port'] : 443;
$socket = 'ssl://' . $uri['host'] . ':' . $port;
- $options['headers']['Host'] = $uri['host'] . ($port != 443 ? ':' . $port : '');
+ if (!isset($options['headers']['Host'])) {
+ $options['headers']['Host'] = $uri['host'] . ($port != 443 ? ':' . $port : '');
+ }
break;
default:
diff --git a/includes/common.inc.orig b/includes/common.inc.orig
new file mode 100644
index 00000000000..7e030467849
--- /dev/null
+++ b/includes/common.inc.orig
@@ -0,0 +1,8516 @@
+ $uri) {
+ $xml_rdf_namespaces[] = 'xmlns:' . $prefix . '="' . $uri . '"';
+ }
+ }
+ return count($xml_rdf_namespaces) ? "\n " . implode("\n ", $xml_rdf_namespaces) : '';
+}
+
+/**
+ * Adds output to the HEAD tag of the HTML page.
+ *
+ * This function can be called as long as the headers aren't sent. Pass no
+ * arguments (or NULL for both) to retrieve the currently stored elements.
+ *
+ * @param $data
+ * A renderable array. If the '#type' key is not set then 'html_tag' will be
+ * added as the default '#type'.
+ * @param $key
+ * A unique string key to allow implementations of hook_html_head_alter() to
+ * identify the element in $data. Required if $data is not NULL.
+ *
+ * @return
+ * An array of all stored HEAD elements.
+ *
+ * @see theme_html_tag()
+ */
+function drupal_add_html_head($data = NULL, $key = NULL) {
+ $stored_head = &drupal_static(__FUNCTION__);
+
+ if (!isset($stored_head)) {
+ // Make sure the defaults, including Content-Type, come first.
+ $stored_head = _drupal_default_html_head();
+ }
+
+ if (isset($data) && isset($key)) {
+ if (!isset($data['#type'])) {
+ $data['#type'] = 'html_tag';
+ }
+ $stored_head[$key] = $data;
+ }
+ return $stored_head;
+}
+
+/**
+ * Returns elements that are always displayed in the HEAD tag of the HTML page.
+ */
+function _drupal_default_html_head() {
+ // Add default elements. Make sure the Content-Type comes first because the
+ // IE browser may be vulnerable to XSS via encoding attacks from any content
+ // that comes before this META tag, such as a TITLE tag.
+ $elements['system_meta_content_type'] = array(
+ '#type' => 'html_tag',
+ '#tag' => 'meta',
+ '#attributes' => array(
+ 'http-equiv' => 'Content-Type',
+ 'content' => 'text/html; charset=utf-8',
+ ),
+ // Security: This always has to be output first.
+ '#weight' => -1000,
+ );
+ // Show Drupal and the major version number in the META GENERATOR tag.
+ // Get the major version.
+ list($version, ) = explode('.', VERSION);
+ $elements['system_meta_generator'] = array(
+ '#type' => 'html_tag',
+ '#tag' => 'meta',
+ '#attributes' => array(
+ 'name' => 'Generator',
+ 'content' => 'Drupal ' . $version . ' (http://drupal.org)',
+ ),
+ );
+ // Also send the generator in the HTTP header.
+ $elements['system_meta_generator']['#attached']['drupal_add_http_header'][] = array('X-Generator', $elements['system_meta_generator']['#attributes']['content']);
+ return $elements;
+}
+
+/**
+ * Retrieves output to be displayed in the HEAD tag of the HTML page.
+ */
+function drupal_get_html_head() {
+ $elements = drupal_add_html_head();
+ drupal_alter('html_head', $elements);
+ return drupal_render($elements);
+}
+
+/**
+ * Adds a feed URL for the current page.
+ *
+ * This function can be called as long the HTML header hasn't been sent.
+ *
+ * @param $url
+ * An internal system path or a fully qualified external URL of the feed.
+ * @param $title
+ * The title of the feed.
+ */
+function drupal_add_feed($url = NULL, $title = '') {
+ $stored_feed_links = &drupal_static(__FUNCTION__, array());
+
+ if (isset($url)) {
+ $stored_feed_links[$url] = theme('feed_icon', array('url' => $url, 'title' => $title));
+
+ drupal_add_html_head_link(array(
+ 'rel' => 'alternate',
+ 'type' => 'application/rss+xml',
+ 'title' => $title,
+ // Force the URL to be absolute, for consistency with other tags
+ // output by Drupal.
+ 'href' => url($url, array('absolute' => TRUE)),
+ ));
+ }
+ return $stored_feed_links;
+}
+
+/**
+ * Gets the feed URLs for the current page.
+ *
+ * @param $delimiter
+ * A delimiter to split feeds by.
+ */
+function drupal_get_feeds($delimiter = "\n") {
+ $feeds = drupal_add_feed();
+ return implode($feeds, $delimiter);
+}
+
+/**
+ * @defgroup http_handling HTTP handling
+ * @{
+ * Functions to properly handle HTTP responses.
+ */
+
+/**
+ * Processes a URL query parameter array to remove unwanted elements.
+ *
+ * @param $query
+ * (optional) An array to be processed. Defaults to $_GET.
+ * @param $exclude
+ * (optional) A list of $query array keys to remove. Use "parent[child]" to
+ * exclude nested items. Defaults to array('q').
+ * @param $parent
+ * Internal use only. Used to build the $query array key for nested items.
+ *
+ * @return
+ * An array containing query parameters, which can be used for url().
+ */
+function drupal_get_query_parameters(array $query = NULL, array $exclude = array('q'), $parent = '') {
+ // Set defaults, if none given.
+ if (!isset($query)) {
+ $query = $_GET;
+ }
+ // If $exclude is empty, there is nothing to filter.
+ if (empty($exclude)) {
+ return $query;
+ }
+ elseif (!$parent) {
+ $exclude = array_flip($exclude);
+ }
+
+ $params = array();
+ foreach ($query as $key => $value) {
+ $string_key = ($parent ? $parent . '[' . $key . ']' : $key);
+ if (isset($exclude[$string_key])) {
+ continue;
+ }
+
+ if (is_array($value)) {
+ $params[$key] = drupal_get_query_parameters($value, $exclude, $string_key);
+ }
+ else {
+ $params[$key] = $value;
+ }
+ }
+
+ return $params;
+}
+
+/**
+ * Splits a URL-encoded query string into an array.
+ *
+ * @param $query
+ * The query string to split.
+ *
+ * @return
+ * An array of URL decoded couples $param_name => $value.
+ */
+function drupal_get_query_array($query) {
+ $result = array();
+ if (!empty($query)) {
+ foreach (explode('&', $query) as $param) {
+ $param = explode('=', $param, 2);
+ $result[$param[0]] = isset($param[1]) ? rawurldecode($param[1]) : '';
+ }
+ }
+ return $result;
+}
+
+/**
+ * Parses an array into a valid, rawurlencoded query string.
+ *
+ * This differs from http_build_query() as we need to rawurlencode() (instead of
+ * urlencode()) all query parameters.
+ *
+ * @param $query
+ * The query parameter array to be processed, e.g. $_GET.
+ * @param $parent
+ * Internal use only. Used to build the $query array key for nested items.
+ *
+ * @return
+ * A rawurlencoded string which can be used as or appended to the URL query
+ * string.
+ *
+ * @see drupal_get_query_parameters()
+ * @ingroup php_wrappers
+ */
+function drupal_http_build_query(array $query, $parent = '') {
+ $params = array();
+
+ foreach ($query as $key => $value) {
+ $key = $parent ? $parent . rawurlencode('[' . $key . ']') : rawurlencode($key);
+
+ // Recurse into children.
+ if (is_array($value)) {
+ $params[] = drupal_http_build_query($value, $key);
+ }
+ // If a query parameter value is NULL, only append its key.
+ elseif (!isset($value)) {
+ $params[] = $key;
+ }
+ else {
+ // For better readability of paths in query strings, we decode slashes.
+ $params[] = $key . '=' . str_replace('%2F', '/', rawurlencode($value));
+ }
+ }
+
+ return implode('&', $params);
+}
+
+/**
+ * Prepares a 'destination' URL query parameter for use with drupal_goto().
+ *
+ * Used to direct the user back to the referring page after completing a form.
+ * By default the current URL is returned. If a destination exists in the
+ * previous request, that destination is returned. As such, a destination can
+ * persist across multiple pages.
+ *
+ * @return
+ * An associative array containing the key:
+ * - destination: The path provided via the destination query string or, if
+ * not available, the current path.
+ *
+ * @see current_path()
+ * @see drupal_goto()
+ */
+function drupal_get_destination() {
+ $destination = &drupal_static(__FUNCTION__);
+
+ if (isset($destination)) {
+ return $destination;
+ }
+
+ if (isset($_GET['destination'])) {
+ $destination = array('destination' => $_GET['destination']);
+ }
+ else {
+ $path = $_GET['q'];
+ $query = drupal_http_build_query(drupal_get_query_parameters());
+ if ($query != '') {
+ $path .= '?' . $query;
+ }
+ $destination = array('destination' => $path);
+ }
+ return $destination;
+}
+
+/**
+ * Parses a URL string into its path, query, and fragment components.
+ *
+ * This function splits both internal paths like @code node?b=c#d @endcode and
+ * external URLs like @code https://example.com/a?b=c#d @endcode into their
+ * component parts. See
+ * @link http://tools.ietf.org/html/rfc3986#section-3 RFC 3986 @endlink for an
+ * explanation of what the component parts are.
+ *
+ * Note that, unlike the RFC, when passed an external URL, this function
+ * groups the scheme, authority, and path together into the path component.
+ *
+ * @param string $url
+ * The internal path or external URL string to parse.
+ *
+ * @return array
+ * An associative array containing:
+ * - path: The path component of $url. If $url is an external URL, this
+ * includes the scheme, authority, and path.
+ * - query: An array of query parameters from $url, if they exist.
+ * - fragment: The fragment component from $url, if it exists.
+ *
+ * @see drupal_goto()
+ * @see l()
+ * @see url()
+ * @see http://tools.ietf.org/html/rfc3986
+ *
+ * @ingroup php_wrappers
+ */
+function drupal_parse_url($url) {
+ $options = array(
+ 'path' => NULL,
+ 'query' => array(),
+ 'fragment' => '',
+ );
+
+ // External URLs: not using parse_url() here, so we do not have to rebuild
+ // the scheme, host, and path without having any use for it.
+ if (strpos($url, '://') !== FALSE) {
+ // Split off everything before the query string into 'path'.
+ $parts = explode('?', $url);
+ $options['path'] = $parts[0];
+ // If there is a query string, transform it into keyed query parameters.
+ if (isset($parts[1])) {
+ $query_parts = explode('#', $parts[1]);
+ parse_str($query_parts[0], $options['query']);
+ // Take over the fragment, if there is any.
+ if (isset($query_parts[1])) {
+ $options['fragment'] = $query_parts[1];
+ }
+ }
+ }
+ // Internal URLs.
+ else {
+ // parse_url() does not support relative URLs, so make it absolute. E.g. the
+ // relative URL "foo/bar:1" isn't properly parsed.
+ $parts = parse_url('http://example.com/' . $url);
+ // Strip the leading slash that was just added.
+ $options['path'] = substr($parts['path'], 1);
+ if (isset($parts['query'])) {
+ parse_str($parts['query'], $options['query']);
+ }
+ if (isset($parts['fragment'])) {
+ $options['fragment'] = $parts['fragment'];
+ }
+ }
+ // The 'q' parameter contains the path of the current page if clean URLs are
+ // disabled. It overrides the 'path' of the URL when present, even if clean
+ // URLs are enabled, due to how Apache rewriting rules work. The path
+ // parameter must be a string.
+ if (isset($options['query']['q']) && is_string($options['query']['q'])) {
+ $options['path'] = $options['query']['q'];
+ unset($options['query']['q']);
+ }
+
+ return $options;
+}
+
+/**
+ * Encodes a Drupal path for use in a URL.
+ *
+ * For aesthetic reasons slashes are not escaped.
+ *
+ * Note that url() takes care of calling this function, so a path passed to that
+ * function should not be encoded in advance.
+ *
+ * @param $path
+ * The Drupal path to encode.
+ */
+function drupal_encode_path($path) {
+ return str_replace('%2F', '/', rawurlencode($path));
+}
+
+/**
+ * Sends the user to a different page.
+ *
+ * This issues an on-site HTTP redirect. The function makes sure the redirected
+ * URL is formatted correctly.
+ *
+ * Usually the redirected URL is constructed from this function's input
+ * parameters. However you may override that behavior by setting a
+ * destination in either the $_REQUEST-array (i.e. by using
+ * the query string of an URI) This is used to direct the user back to
+ * the proper page after completing a form. For example, after editing
+ * a post on the 'admin/content'-page or after having logged on using the
+ * 'user login'-block in a sidebar. The function drupal_get_destination()
+ * can be used to help set the destination URL.
+ *
+ * Drupal will ensure that messages set by drupal_set_message() and other
+ * session data are written to the database before the user is redirected.
+ *
+ * This function ends the request; use it instead of a return in your menu
+ * callback.
+ *
+ * @param $path
+ * (optional) A Drupal path or a full URL, which will be passed to url() to
+ * compute the redirect for the URL.
+ * @param $options
+ * (optional) An associative array of additional URL options to pass to url().
+ * @param $http_response_code
+ * (optional) The HTTP status code to use for the redirection, defaults to
+ * 302. The valid values for 3xx redirection status codes are defined in
+ * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3 RFC 2616 @endlink
+ * and the
+ * @link http://tools.ietf.org/html/draft-reschke-http-status-308-07 draft for the new HTTP status codes: @endlink
+ * - 301: Moved Permanently (the recommended value for most redirects).
+ * - 302: Found (default in Drupal and PHP, sometimes used for spamming search
+ * engines).
+ * - 303: See Other.
+ * - 304: Not Modified.
+ * - 305: Use Proxy.
+ * - 307: Temporary Redirect.
+ *
+ * @see drupal_get_destination()
+ * @see url()
+ */
+function drupal_goto($path = '', array $options = array(), $http_response_code = 302) {
+ // A destination in $_GET always overrides the function arguments.
+ // We do not allow absolute URLs to be passed via $_GET, as this can be an attack vector.
+ if (isset($_GET['destination']) && !url_is_external($_GET['destination'])) {
+ $destination = drupal_parse_url($_GET['destination']);
+ $path = $destination['path'];
+ $options['query'] = $destination['query'];
+ $options['fragment'] = $destination['fragment'];
+ }
+
+ // In some cases modules call drupal_goto(current_path()). We need to ensure
+ // that such a redirect is not to an external URL.
+ if ($path === current_path() && empty($options['external']) && url_is_external($path)) {
+ // Force url() to generate a non-external URL.
+ $options['external'] = FALSE;
+ }
+
+ drupal_alter('drupal_goto', $path, $options, $http_response_code);
+
+ // The 'Location' HTTP header must be absolute.
+ $options['absolute'] = TRUE;
+
+ $url = url($path, $options);
+
+ header('Location: ' . $url, TRUE, $http_response_code);
+
+ // The "Location" header sends a redirect status code to the HTTP daemon. In
+ // some cases this can be wrong, so we make sure none of the code below the
+ // drupal_goto() call gets executed upon redirection.
+ drupal_exit($url);
+}
+
+/**
+ * Delivers a "site is under maintenance" message to the browser.
+ *
+ * Page callback functions wanting to report a "site offline" message should
+ * return MENU_SITE_OFFLINE instead of calling drupal_site_offline(). However,
+ * functions that are invoked in contexts where that return value might not
+ * bubble up to menu_execute_active_handler() should call drupal_site_offline().
+ */
+function drupal_site_offline() {
+ drupal_deliver_page(MENU_SITE_OFFLINE);
+}
+
+/**
+ * Delivers a "page not found" error to the browser.
+ *
+ * Page callback functions wanting to report a "page not found" message should
+ * return MENU_NOT_FOUND instead of calling drupal_not_found(). However,
+ * functions that are invoked in contexts where that return value might not
+ * bubble up to menu_execute_active_handler() should call drupal_not_found().
+ */
+function drupal_not_found() {
+ drupal_deliver_page(MENU_NOT_FOUND);
+}
+
+/**
+ * Delivers an "access denied" error to the browser.
+ *
+ * Page callback functions wanting to report an "access denied" message should
+ * return MENU_ACCESS_DENIED instead of calling drupal_access_denied(). However,
+ * functions that are invoked in contexts where that return value might not
+ * bubble up to menu_execute_active_handler() should call
+ * drupal_access_denied().
+ */
+function drupal_access_denied() {
+ drupal_deliver_page(MENU_ACCESS_DENIED);
+}
+
+/**
+ * Performs an HTTP request.
+ *
+ * This is a flexible and powerful HTTP client implementation. Correctly
+ * handles GET, POST, PUT or any other HTTP requests. Handles redirects.
+ *
+ * @param $url
+ * A string containing a fully qualified URI.
+ * @param array $options
+ * (optional) An array that can have one or more of the following elements:
+ * - headers: An array containing request headers to send as name/value pairs.
+ * - method: A string containing the request method. Defaults to 'GET'.
+ * - data: A string containing the request body, formatted as
+ * 'param=value¶m=value&...'; to generate this, use http_build_query().
+ * Defaults to NULL.
+ * - max_redirects: An integer representing how many times a redirect
+ * may be followed. Defaults to 3.
+ * - timeout: A float representing the maximum number of seconds the function
+ * call may take. The default is 30 seconds. If a timeout occurs, the error
+ * code is set to the HTTP_REQUEST_TIMEOUT constant.
+ * - context: A context resource created with stream_context_create().
+ *
+ * @return object
+ * An object that can have one or more of the following components:
+ * - request: A string containing the request body that was sent.
+ * - code: An integer containing the response status code, or the error code
+ * if an error occurred.
+ * - protocol: The response protocol (e.g. HTTP/1.1 or HTTP/1.0).
+ * - status_message: The status message from the response, if a response was
+ * received.
+ * - redirect_code: If redirected, an integer containing the initial response
+ * status code.
+ * - redirect_url: If redirected, a string containing the URL of the redirect
+ * target.
+ * - error: If an error occurred, the error message. Otherwise not set.
+ * - headers: An array containing the response headers as name/value pairs.
+ * HTTP header names are case-insensitive (RFC 2616, section 4.2), so for
+ * easy access the array keys are returned in lower case.
+ * - data: A string containing the response body that was received.
+ *
+ * @see http_build_query()
+ */
+function drupal_http_request($url, array $options = array()) {
+ // Allow an alternate HTTP client library to replace Drupal's default
+ // implementation.
+ $override_function = variable_get('drupal_http_request_function', FALSE);
+ if (!empty($override_function) && function_exists($override_function)) {
+ return $override_function($url, $options);
+ }
+
+ $result = new stdClass();
+
+ // Parse the URL and make sure we can handle the schema.
+ $uri = @parse_url($url);
+
+ if ($uri == FALSE) {
+ $result->error = 'unable to parse URL';
+ $result->code = -1001;
+ return $result;
+ }
+
+ if (!isset($uri['scheme'])) {
+ $result->error = 'missing schema';
+ $result->code = -1002;
+ return $result;
+ }
+
+ timer_start(__FUNCTION__);
+
+ // Merge the default options.
+ $options += array(
+ 'headers' => array(),
+ 'method' => 'GET',
+ 'data' => NULL,
+ 'max_redirects' => 3,
+ 'timeout' => 30.0,
+ 'context' => NULL,
+ );
+
+ // Merge the default headers.
+ $options['headers'] += array(
+ 'User-Agent' => 'Drupal (+http://drupal.org/)',
+ );
+
+ // stream_socket_client() requires timeout to be a float.
+ $options['timeout'] = (float) $options['timeout'];
+
+ // Use a proxy if one is defined and the host is not on the excluded list.
+ $proxy_server = variable_get('proxy_server', '');
+ if ($proxy_server && _drupal_http_use_proxy($uri['host'])) {
+ // Set the scheme so we open a socket to the proxy server.
+ $uri['scheme'] = 'proxy';
+ // Set the path to be the full URL.
+ $uri['path'] = $url;
+ // Since the URL is passed as the path, we won't use the parsed query.
+ unset($uri['query']);
+
+ // Add in username and password to Proxy-Authorization header if needed.
+ if ($proxy_username = variable_get('proxy_username', '')) {
+ $proxy_password = variable_get('proxy_password', '');
+ $options['headers']['Proxy-Authorization'] = 'Basic ' . base64_encode($proxy_username . (!empty($proxy_password) ? ":" . $proxy_password : ''));
+ }
+ // Some proxies reject requests with any User-Agent headers, while others
+ // require a specific one.
+ $proxy_user_agent = variable_get('proxy_user_agent', '');
+ // The default value matches neither condition.
+ if ($proxy_user_agent === NULL) {
+ unset($options['headers']['User-Agent']);
+ }
+ elseif ($proxy_user_agent) {
+ $options['headers']['User-Agent'] = $proxy_user_agent;
+ }
+ }
+
+ switch ($uri['scheme']) {
+ case 'proxy':
+ // Make the socket connection to a proxy server.
+ $socket = 'tcp://' . $proxy_server . ':' . variable_get('proxy_port', 8080);
+ // The Host header still needs to match the real request.
+ if (!isset($options['headers']['Host'])) {
+ $options['headers']['Host'] = $uri['host'];
+ $options['headers']['Host'] .= isset($uri['port']) && $uri['port'] != 80 ? ':' . $uri['port'] : '';
+ }
+ break;
+
+ case 'http':
+ case 'feed':
+ $port = isset($uri['port']) ? $uri['port'] : 80;
+ $socket = 'tcp://' . $uri['host'] . ':' . $port;
+ // RFC 2616: "non-standard ports MUST, default ports MAY be included".
+ // We don't add the standard port to prevent from breaking rewrite rules
+ // checking the host that do not take into account the port number.
+ if (!isset($options['headers']['Host'])) {
+ $options['headers']['Host'] = $uri['host'] . ($port != 80 ? ':' . $port : '');
+ }
+ break;
+
+ case 'https':
+ // Note: Only works when PHP is compiled with OpenSSL support.
+ $port = isset($uri['port']) ? $uri['port'] : 443;
+ $socket = 'ssl://' . $uri['host'] . ':' . $port;
+ if (!isset($options['headers']['Host'])) {
+ $options['headers']['Host'] = $uri['host'] . ($port != 443 ? ':' . $port : '');
+ }
+ break;
+
+ default:
+ $result->error = 'invalid schema ' . $uri['scheme'];
+ $result->code = -1003;
+ return $result;
+ }
+
+ if (empty($options['context'])) {
+ $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout']);
+ }
+ else {
+ // Create a stream with context. Allows verification of a SSL certificate.
+ $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout'], STREAM_CLIENT_CONNECT, $options['context']);
+ }
+
+ // Make sure the socket opened properly.
+ if (!$fp) {
+ // When a network error occurs, we use a negative number so it does not
+ // clash with the HTTP status codes.
+ $result->code = -$errno;
+ $result->error = trim($errstr) ? trim($errstr) : t('Error opening socket @socket', array('@socket' => $socket));
+
+ // Mark that this request failed. This will trigger a check of the web
+ // server's ability to make outgoing HTTP requests the next time that
+ // requirements checking is performed.
+ // See system_requirements().
+ variable_set('drupal_http_request_fails', TRUE);
+
+ return $result;
+ }
+
+ // Construct the path to act on.
+ $path = isset($uri['path']) ? $uri['path'] : '/';
+ if (isset($uri['query'])) {
+ $path .= '?' . $uri['query'];
+ }
+
+ // Only add Content-Length if we actually have any content or if it is a POST
+ // or PUT request. Some non-standard servers get confused by Content-Length in
+ // at least HEAD/GET requests, and Squid always requires Content-Length in
+ // POST/PUT requests.
+ $content_length = strlen($options['data']);
+ if ($content_length > 0 || $options['method'] == 'POST' || $options['method'] == 'PUT') {
+ $options['headers']['Content-Length'] = $content_length;
+ }
+
+ // If the server URL has a user then attempt to use basic authentication.
+ if (isset($uri['user'])) {
+ $options['headers']['Authorization'] = 'Basic ' . base64_encode($uri['user'] . (isset($uri['pass']) ? ':' . $uri['pass'] : ':'));
+ }
+
+ // If the database prefix is being used by SimpleTest to run the tests in a copied
+ // database then set the user-agent header to the database prefix so that any
+ // calls to other Drupal pages will run the SimpleTest prefixed database. The
+ // user-agent is used to ensure that multiple testing sessions running at the
+ // same time won't interfere with each other as they would if the database
+ // prefix were stored statically in a file or database variable.
+ $test_info = &$GLOBALS['drupal_test_info'];
+ if (!empty($test_info['test_run_id'])) {
+ $options['headers']['User-Agent'] = drupal_generate_test_ua($test_info['test_run_id']);
+ }
+
+ $request = $options['method'] . ' ' . $path . " HTTP/1.0\r\n";
+ foreach ($options['headers'] as $name => $value) {
+ $request .= $name . ': ' . trim($value) . "\r\n";
+ }
+ $request .= "\r\n" . $options['data'];
+ $result->request = $request;
+ // Calculate how much time is left of the original timeout value.
+ $timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000;
+ if ($timeout > 0) {
+ stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1)));
+ fwrite($fp, $request);
+ }
+
+ // Fetch response. Due to PHP bugs like http://bugs.php.net/bug.php?id=43782
+ // and http://bugs.php.net/bug.php?id=46049 we can't rely on feof(), but
+ // instead must invoke stream_get_meta_data() each iteration.
+ $info = stream_get_meta_data($fp);
+ $alive = !$info['eof'] && !$info['timed_out'];
+ $response = '';
+
+ while ($alive) {
+ // Calculate how much time is left of the original timeout value.
+ $timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000;
+ if ($timeout <= 0) {
+ $info['timed_out'] = TRUE;
+ break;
+ }
+ stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1)));
+ $chunk = fread($fp, 1024);
+ $response .= $chunk;
+ $info = stream_get_meta_data($fp);
+ $alive = !$info['eof'] && !$info['timed_out'] && $chunk;
+ }
+ fclose($fp);
+
+ if ($info['timed_out']) {
+ $result->code = HTTP_REQUEST_TIMEOUT;
+ $result->error = 'request timed out';
+ return $result;
+ }
+ // Parse response headers from the response body.
+ // Be tolerant of malformed HTTP responses that separate header and body with
+ // \n\n or \r\r instead of \r\n\r\n.
+ list($response, $result->data) = preg_split("/\r\n\r\n|\n\n|\r\r/", $response, 2);
+ $response = preg_split("/\r\n|\n|\r/", $response);
+
+ // Parse the response status line.
+ $response_status_array = _drupal_parse_response_status(trim(array_shift($response)));
+ $result->protocol = $response_status_array['http_version'];
+ $result->status_message = $response_status_array['reason_phrase'];
+ $code = $response_status_array['response_code'];
+
+ $result->headers = array();
+
+ // Parse the response headers.
+ while ($line = trim(array_shift($response))) {
+ list($name, $value) = explode(':', $line, 2);
+ $name = strtolower($name);
+ if (isset($result->headers[$name]) && $name == 'set-cookie') {
+ // RFC 2109: the Set-Cookie response header comprises the token Set-
+ // Cookie:, followed by a comma-separated list of one or more cookies.
+ $result->headers[$name] .= ',' . trim($value);
+ }
+ else {
+ $result->headers[$name] = trim($value);
+ }
+ }
+
+ $responses = array(
+ 100 => 'Continue',
+ 101 => 'Switching Protocols',
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 307 => 'Temporary Redirect',
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Time-out',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Large',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested range not satisfiable',
+ 417 => 'Expectation Failed',
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Time-out',
+ 505 => 'HTTP Version not supported',
+ );
+ // RFC 2616 states that all unknown HTTP codes must be treated the same as the
+ // base code in their class.
+ if (!isset($responses[$code])) {
+ $code = floor($code / 100) * 100;
+ }
+ $result->code = $code;
+
+ switch ($code) {
+ case 200: // OK
+ case 201: // Created
+ case 202: // Accepted
+ case 203: // Non-Authoritative Information
+ case 204: // No Content
+ case 205: // Reset Content
+ case 206: // Partial Content
+ case 304: // Not modified
+ break;
+ case 301: // Moved permanently
+ case 302: // Moved temporarily
+ case 307: // Moved temporarily
+ $location = $result->headers['location'];
+ $options['timeout'] -= timer_read(__FUNCTION__) / 1000;
+ if ($options['timeout'] <= 0) {
+ $result->code = HTTP_REQUEST_TIMEOUT;
+ $result->error = 'request timed out';
+ }
+ elseif ($options['max_redirects']) {
+ // Redirect to the new location.
+ $options['max_redirects']--;
+ $result = drupal_http_request($location, $options);
+ $result->redirect_code = $code;
+ }
+ if (!isset($result->redirect_url)) {
+ $result->redirect_url = $location;
+ }
+ break;
+ default:
+ $result->error = $result->status_message;
+ }
+
+ return $result;
+}
+
+/**
+ * Splits an HTTP response status line into components.
+ *
+ * See the @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html status line definition @endlink
+ * in RFC 2616.
+ *
+ * @param string $respone
+ * The response status line, for example 'HTTP/1.1 500 Internal Server Error'.
+ *
+ * @return array
+ * Keyed array containing the component parts. If the response is malformed,
+ * all possible parts will be extracted. 'reason_phrase' could be empty.
+ * Possible keys:
+ * - 'http_version'
+ * - 'response_code'
+ * - 'reason_phrase'
+ */
+function _drupal_parse_response_status($response) {
+ $response_array = explode(' ', trim($response), 3);
+ // Set up empty values.
+ $result = array(
+ 'reason_phrase' => '',
+ );
+ $result['http_version'] = $response_array[0];
+ $result['response_code'] = $response_array[1];
+ if (isset($response_array[2])) {
+ $result['reason_phrase'] = $response_array[2];
+ }
+ return $result;
+}
+
+/**
+ * Helper function for determining hosts excluded from needing a proxy.
+ *
+ * @return
+ * TRUE if a proxy should be used for this host.
+ */
+function _drupal_http_use_proxy($host) {
+ $proxy_exceptions = variable_get('proxy_exceptions', array('localhost', '127.0.0.1'));
+ return !in_array(strtolower($host), $proxy_exceptions, TRUE);
+}
+
+/**
+ * @} End of "HTTP handling".
+ */
+
+/**
+ * Strips slashes from a string or array of strings.
+ *
+ * Callback for array_walk() within fix_gpx_magic().
+ *
+ * @param $item
+ * An individual string or array of strings from superglobals.
+ */
+function _fix_gpc_magic(&$item) {
+ if (is_array($item)) {
+ array_walk($item, '_fix_gpc_magic');
+ }
+ else {
+ $item = stripslashes($item);
+ }
+}
+
+/**
+ * Strips slashes from $_FILES items.
+ *
+ * Callback for array_walk() within fix_gpc_magic().
+ *
+ * The tmp_name key is skipped keys since PHP generates single backslashes for
+ * file paths on Windows systems.
+ *
+ * @param $item
+ * An item from $_FILES.
+ * @param $key
+ * The key for the item within $_FILES.
+ *
+ * @see http://php.net/manual/features.file-upload.php#42280
+ */
+function _fix_gpc_magic_files(&$item, $key) {
+ if ($key != 'tmp_name') {
+ if (is_array($item)) {
+ array_walk($item, '_fix_gpc_magic_files');
+ }
+ else {
+ $item = stripslashes($item);
+ }
+ }
+}
+
+/**
+ * Fixes double-escaping caused by "magic quotes" in some PHP installations.
+ *
+ * @see _fix_gpc_magic()
+ * @see _fix_gpc_magic_files()
+ */
+function fix_gpc_magic() {
+ static $fixed = FALSE;
+ if (!$fixed && ini_get('magic_quotes_gpc')) {
+ array_walk($_GET, '_fix_gpc_magic');
+ array_walk($_POST, '_fix_gpc_magic');
+ array_walk($_COOKIE, '_fix_gpc_magic');
+ array_walk($_REQUEST, '_fix_gpc_magic');
+ array_walk($_FILES, '_fix_gpc_magic_files');
+ }
+ $fixed = TRUE;
+}
+
+/**
+ * @defgroup validation Input validation
+ * @{
+ * Functions to validate user input.
+ */
+
+/**
+ * Verifies the syntax of the given e-mail address.
+ *
+ * This uses the
+ * @link http://php.net/manual/filter.filters.validate.php PHP e-mail validation filter. @endlink
+ *
+ * @param $mail
+ * A string containing an e-mail address.
+ *
+ * @return
+ * TRUE if the address is in a valid format.
+ */
+function valid_email_address($mail) {
+ return (bool)filter_var($mail, FILTER_VALIDATE_EMAIL);
+}
+
+/**
+ * Verifies the syntax of the given URL.
+ *
+ * This function should only be used on actual URLs. It should not be used for
+ * Drupal menu paths, which can contain arbitrary characters.
+ * Valid values per RFC 3986.
+ * @param $url
+ * The URL to verify.
+ * @param $absolute
+ * Whether the URL is absolute (beginning with a scheme such as "http:").
+ *
+ * @return
+ * TRUE if the URL is in a valid format.
+ */
+function valid_url($url, $absolute = FALSE) {
+ if ($absolute) {
+ return (bool)preg_match("
+ /^ # Start at the beginning of the text
+ (?:ftp|https?|feed):\/\/ # Look for ftp, http, https or feed schemes
+ (?: # Userinfo (optional) which is typically
+ (?:(?:[\w\.\-\+!$&'\(\)*\+,;=]|%[0-9a-f]{2})+:)* # a username or a username and password
+ (?:[\w\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})+@ # combination
+ )?
+ (?:
+ (?:[a-z0-9\-\.]|%[0-9a-f]{2})+ # A domain name or a IPv4 address
+ |(?:\[(?:[0-9a-f]{0,4}:)*(?:[0-9a-f]{0,4})\]) # or a well formed IPv6 address
+ )
+ (?::[0-9]+)? # Server port number (optional)
+ (?:[\/|\?]
+ (?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2}) # The path and query (optional)
+ *)?
+ $/xi", $url);
+ }
+ else {
+ return (bool)preg_match("/^(?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})+$/i", $url);
+ }
+}
+
+/**
+ * @} End of "defgroup validation".
+ */
+
+/**
+ * Registers an event for the current visitor to the flood control mechanism.
+ *
+ * @param $name
+ * The name of an event.
+ * @param $window
+ * Optional number of seconds before this event expires. Defaults to 3600 (1
+ * hour). Typically uses the same value as the flood_is_allowed() $window
+ * parameter. Expired events are purged on cron run to prevent the flood table
+ * from growing indefinitely.
+ * @param $identifier
+ * Optional identifier (defaults to the current user's IP address).
+ */
+function flood_register_event($name, $window = 3600, $identifier = NULL) {
+ if (!isset($identifier)) {
+ $identifier = ip_address();
+ }
+ db_insert('flood')
+ ->fields(array(
+ 'event' => $name,
+ 'identifier' => $identifier,
+ 'timestamp' => REQUEST_TIME,
+ 'expiration' => REQUEST_TIME + $window,
+ ))
+ ->execute();
+}
+
+/**
+ * Makes the flood control mechanism forget an event for the current visitor.
+ *
+ * @param $name
+ * The name of an event.
+ * @param $identifier
+ * Optional identifier (defaults to the current user's IP address).
+ */
+function flood_clear_event($name, $identifier = NULL) {
+ if (!isset($identifier)) {
+ $identifier = ip_address();
+ }
+ db_delete('flood')
+ ->condition('event', $name)
+ ->condition('identifier', $identifier)
+ ->execute();
+}
+
+/**
+ * Checks whether a user is allowed to proceed with the specified event.
+ *
+ * Events can have thresholds saying that each user can only do that event
+ * a certain number of times in a time window. This function verifies that the
+ * current user has not exceeded this threshold.
+ *
+ * @param $name
+ * The unique name of the event.
+ * @param $threshold
+ * The maximum number of times each user can do this event per time window.
+ * @param $window
+ * Number of seconds in the time window for this event (default is 3600
+ * seconds, or 1 hour).
+ * @param $identifier
+ * Unique identifier of the current user. Defaults to their IP address.
+ *
+ * @return
+ * TRUE if the user is allowed to proceed. FALSE if they have exceeded the
+ * threshold and should not be allowed to proceed.
+ */
+function flood_is_allowed($name, $threshold, $window = 3600, $identifier = NULL) {
+ if (!isset($identifier)) {
+ $identifier = ip_address();
+ }
+ $number = db_query("SELECT COUNT(*) FROM {flood} WHERE event = :event AND identifier = :identifier AND timestamp > :timestamp", array(
+ ':event' => $name,
+ ':identifier' => $identifier,
+ ':timestamp' => REQUEST_TIME - $window))
+ ->fetchField();
+ return ($number < $threshold);
+}
+
+/**
+ * @defgroup sanitization Sanitization functions
+ * @{
+ * Functions to sanitize values.
+ *
+ * See http://drupal.org/writing-secure-code for information
+ * on writing secure code.
+ */
+
+/**
+ * Strips dangerous protocols (e.g. 'javascript:') from a URI.
+ *
+ * This function must be called for all URIs within user-entered input prior
+ * to being output to an HTML attribute value. It is often called as part of
+ * check_url() or filter_xss(), but those functions return an HTML-encoded
+ * string, so this function can be called independently when the output needs to
+ * be a plain-text string for passing to t(), l(), drupal_attributes(), or
+ * another function that will call check_plain() separately.
+ *
+ * @param $uri
+ * A plain-text URI that might contain dangerous protocols.
+ *
+ * @return
+ * A plain-text URI stripped of dangerous protocols. As with all plain-text
+ * strings, this return value must not be output to an HTML page without
+ * check_plain() being called on it. However, it can be passed to functions
+ * expecting plain-text strings.
+ *
+ * @see check_url()
+ */
+function drupal_strip_dangerous_protocols($uri) {
+ static $allowed_protocols;
+
+ if (!isset($allowed_protocols)) {
+ $allowed_protocols = array_flip(variable_get('filter_allowed_protocols', array('ftp', 'http', 'https', 'irc', 'mailto', 'news', 'nntp', 'rtsp', 'sftp', 'ssh', 'tel', 'telnet', 'webcal')));
+ }
+
+ // Iteratively remove any invalid protocol found.
+ do {
+ $before = $uri;
+ $colonpos = strpos($uri, ':');
+ if ($colonpos > 0) {
+ // We found a colon, possibly a protocol. Verify.
+ $protocol = substr($uri, 0, $colonpos);
+ // If a colon is preceded by a slash, question mark or hash, it cannot
+ // possibly be part of the URL scheme. This must be a relative URL, which
+ // inherits the (safe) protocol of the base document.
+ if (preg_match('![/?#]!', $protocol)) {
+ break;
+ }
+ // Check if this is a disallowed protocol. Per RFC2616, section 3.2.3
+ // (URI Comparison) scheme comparison must be case-insensitive.
+ if (!isset($allowed_protocols[strtolower($protocol)])) {
+ $uri = substr($uri, $colonpos + 1);
+ }
+ }
+ } while ($before != $uri);
+
+ return $uri;
+}
+
+/**
+ * Strips dangerous protocols from a URI and encodes it for output to HTML.
+ *
+ * @param $uri
+ * A plain-text URI that might contain dangerous protocols.
+ *
+ * @return
+ * A URI stripped of dangerous protocols and encoded for output to an HTML
+ * attribute value. Because it is already encoded, it should not be set as a
+ * value within a $attributes array passed to drupal_attributes(), because
+ * drupal_attributes() expects those values to be plain-text strings. To pass
+ * a filtered URI to drupal_attributes(), call
+ * drupal_strip_dangerous_protocols() instead.
+ *
+ * @see drupal_strip_dangerous_protocols()
+ */
+function check_url($uri) {
+ return check_plain(drupal_strip_dangerous_protocols($uri));
+}
+
+/**
+ * Applies a very permissive XSS/HTML filter for admin-only use.
+ *
+ * Use only for fields where it is impractical to use the
+ * whole filter system, but where some (mainly inline) mark-up
+ * is desired (so check_plain() is not acceptable).
+ *
+ * Allows all tags that can be used inside an HTML body, save
+ * for scripts and styles.
+ */
+function filter_xss_admin($string) {
+ return filter_xss($string, array('a', 'abbr', 'acronym', 'address', 'article', 'aside', 'b', 'bdi', 'bdo', 'big', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'command', 'dd', 'del', 'details', 'dfn', 'div', 'dl', 'dt', 'em', 'figcaption', 'figure', 'footer', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'i', 'img', 'ins', 'kbd', 'li', 'mark', 'menu', 'meter', 'nav', 'ol', 'output', 'p', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'small', 'span', 'strong', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time', 'tr', 'tt', 'u', 'ul', 'var', 'wbr'));
+}
+
+/**
+ * Filters HTML to prevent cross-site-scripting (XSS) vulnerabilities.
+ *
+ * Based on kses by Ulf Harnhammar, see http://sourceforge.net/projects/kses.
+ * For examples of various XSS attacks, see: http://ha.ckers.org/xss.html.
+ *
+ * This code does four things:
+ * - Removes characters and constructs that can trick browsers.
+ * - Makes sure all HTML entities are well-formed.
+ * - Makes sure all HTML tags and attributes are well-formed.
+ * - Makes sure no HTML tags contain URLs with a disallowed protocol (e.g.
+ * javascript:).
+ *
+ * @param $string
+ * The string with raw HTML in it. It will be stripped of everything that can
+ * cause an XSS attack.
+ * @param $allowed_tags
+ * An array of allowed tags.
+ *
+ * @return
+ * An XSS safe version of $string, or an empty string if $string is not
+ * valid UTF-8.
+ *
+ * @see drupal_validate_utf8()
+ */
+function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd')) {
+ // Only operate on valid UTF-8 strings. This is necessary to prevent cross
+ // site scripting issues on Internet Explorer 6.
+ if (!drupal_validate_utf8($string)) {
+ return '';
+ }
+ // Store the text format.
+ _filter_xss_split($allowed_tags, TRUE);
+ // Remove NULL characters (ignored by some browsers).
+ $string = str_replace(chr(0), '', $string);
+ // Remove Netscape 4 JS entities.
+ $string = preg_replace('%&\s*\{[^}]*(\}\s*;?|$)%', '', $string);
+
+ // Defuse all HTML entities.
+ $string = str_replace('&', '&', $string);
+ // Change back only well-formed entities in our whitelist:
+ // Decimal numeric entities.
+ $string = preg_replace('/&#([0-9]+;)/', '\1', $string);
+ // Hexadecimal numeric entities.
+ $string = preg_replace('/&#[Xx]0*((?:[0-9A-Fa-f]{2})+;)/', '\1', $string);
+ // Named entities.
+ $string = preg_replace('/&([A-Za-z][A-Za-z0-9]*;)/', '&\1', $string);
+
+ return preg_replace_callback('%
+ (
+ <(?=[^a-zA-Z!/]) # a lone <
+ | # or
+ # a comment
+ | # or
+ <[^>]*(>|$) # a string that starts with a <, up until the > or the end of the string
+ | # or
+ > # just a >
+ )%x', '_filter_xss_split', $string);
+}
+
+/**
+ * Processes an HTML tag.
+ *
+ * @param $m
+ * An array with various meaning depending on the value of $store.
+ * If $store is TRUE then the array contains the allowed tags.
+ * If $store is FALSE then the array has one element, the HTML tag to process.
+ * @param $store
+ * Whether to store $m.
+ *
+ * @return
+ * If the element isn't allowed, an empty string. Otherwise, the cleaned up
+ * version of the HTML element.
+ */
+function _filter_xss_split($m, $store = FALSE) {
+ static $allowed_html;
+
+ if ($store) {
+ $allowed_html = array_flip($m);
+ return;
+ }
+
+ $string = $m[1];
+
+ if (substr($string, 0, 1) != '<') {
+ // We matched a lone ">" character.
+ return '>';
+ }
+ elseif (strlen($string) == 1) {
+ // We matched a lone "<" character.
+ return '<';
+ }
+
+ if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9\-]+)([^>]*)>?|()$%', $string, $matches)) {
+ // Seriously malformed.
+ return '';
+ }
+
+ $slash = trim($matches[1]);
+ $elem = &$matches[2];
+ $attrlist = &$matches[3];
+ $comment = &$matches[4];
+
+ if ($comment) {
+ $elem = '!--';
+ }
+
+ if (!isset($allowed_html[strtolower($elem)])) {
+ // Disallowed HTML element.
+ return '';
+ }
+
+ if ($comment) {
+ return $comment;
+ }
+
+ if ($slash != '') {
+ return "$elem>";
+ }
+
+ // Is there a closing XHTML slash at the end of the attributes?
+ $attrlist = preg_replace('%(\s?)/\s*$%', '\1', $attrlist, -1, $count);
+ $xhtml_slash = $count ? ' /' : '';
+
+ // Clean up attributes.
+ $attr2 = implode(' ', _filter_xss_attributes($attrlist));
+ $attr2 = preg_replace('/[<>]/', '', $attr2);
+ $attr2 = strlen($attr2) ? ' ' . $attr2 : '';
+
+ return "<$elem$attr2$xhtml_slash>";
+}
+
+/**
+ * Processes a string of HTML attributes.
+ *
+ * @return
+ * Cleaned up version of the HTML attributes.
+ */
+function _filter_xss_attributes($attr) {
+ $attrarr = array();
+ $mode = 0;
+ $attrname = '';
+
+ while (strlen($attr) != 0) {
+ // Was the last operation successful?
+ $working = 0;
+
+ switch ($mode) {
+ case 0:
+ // Attribute name, href for instance.
+ if (preg_match('/^([-a-zA-Z]+)/', $attr, $match)) {
+ $attrname = strtolower($match[1]);
+ $skip = ($attrname == 'style' || substr($attrname, 0, 2) == 'on');
+ $working = $mode = 1;
+ $attr = preg_replace('/^[-a-zA-Z]+/', '', $attr);
+ }
+ break;
+
+ case 1:
+ // Equals sign or valueless ("selected").
+ if (preg_match('/^\s*=\s*/', $attr)) {
+ $working = 1; $mode = 2;
+ $attr = preg_replace('/^\s*=\s*/', '', $attr);
+ break;
+ }
+
+ if (preg_match('/^\s+/', $attr)) {
+ $working = 1; $mode = 0;
+ if (!$skip) {
+ $attrarr[] = $attrname;
+ }
+ $attr = preg_replace('/^\s+/', '', $attr);
+ }
+ break;
+
+ case 2:
+ // Attribute value, a URL after href= for instance.
+ if (preg_match('/^"([^"]*)"(\s+|$)/', $attr, $match)) {
+ $thisval = filter_xss_bad_protocol($match[1]);
+
+ if (!$skip) {
+ $attrarr[] = "$attrname=\"$thisval\"";
+ }
+ $working = 1;
+ $mode = 0;
+ $attr = preg_replace('/^"[^"]*"(\s+|$)/', '', $attr);
+ break;
+ }
+
+ if (preg_match("/^'([^']*)'(\s+|$)/", $attr, $match)) {
+ $thisval = filter_xss_bad_protocol($match[1]);
+
+ if (!$skip) {
+ $attrarr[] = "$attrname='$thisval'";
+ }
+ $working = 1; $mode = 0;
+ $attr = preg_replace("/^'[^']*'(\s+|$)/", '', $attr);
+ break;
+ }
+
+ if (preg_match("%^([^\s\"']+)(\s+|$)%", $attr, $match)) {
+ $thisval = filter_xss_bad_protocol($match[1]);
+
+ if (!$skip) {
+ $attrarr[] = "$attrname=\"$thisval\"";
+ }
+ $working = 1; $mode = 0;
+ $attr = preg_replace("%^[^\s\"']+(\s+|$)%", '', $attr);
+ }
+ break;
+ }
+
+ if ($working == 0) {
+ // Not well formed; remove and try again.
+ $attr = preg_replace('/
+ ^
+ (
+ "[^"]*("|$) # - a string that starts with a double quote, up until the next double quote or the end of the string
+ | # or
+ \'[^\']*(\'|$)| # - a string that starts with a quote, up until the next quote or the end of the string
+ | # or
+ \S # - a non-whitespace character
+ )* # any number of the above three
+ \s* # any number of whitespaces
+ /x', '', $attr);
+ $mode = 0;
+ }
+ }
+
+ // The attribute list ends with a valueless attribute like "selected".
+ if ($mode == 1 && !$skip) {
+ $attrarr[] = $attrname;
+ }
+ return $attrarr;
+}
+
+/**
+ * Processes an HTML attribute value and strips dangerous protocols from URLs.
+ *
+ * @param $string
+ * The string with the attribute value.
+ * @param $decode
+ * (deprecated) Whether to decode entities in the $string. Set to FALSE if the
+ * $string is in plain text, TRUE otherwise. Defaults to TRUE. This parameter
+ * is deprecated and will be removed in Drupal 8. To process a plain-text URI,
+ * call drupal_strip_dangerous_protocols() or check_url() instead.
+ *
+ * @return
+ * Cleaned up and HTML-escaped version of $string.
+ */
+function filter_xss_bad_protocol($string, $decode = TRUE) {
+ // Get the plain text representation of the attribute value (i.e. its meaning).
+ // @todo Remove the $decode parameter in Drupal 8, and always assume an HTML
+ // string that needs decoding.
+ if ($decode) {
+ if (!function_exists('decode_entities')) {
+ require_once DRUPAL_ROOT . '/includes/unicode.inc';
+ }
+
+ $string = decode_entities($string);
+ }
+ return check_plain(drupal_strip_dangerous_protocols($string));
+}
+
+/**
+ * @} End of "defgroup sanitization".
+ */
+
+/**
+ * @defgroup format Formatting
+ * @{
+ * Functions to format numbers, strings, dates, etc.
+ */
+
+/**
+ * Formats an RSS channel.
+ *
+ * Arbitrary elements may be added using the $args associative array.
+ */
+function format_rss_channel($title, $link, $description, $items, $langcode = NULL, $args = array()) {
+ global $language_content;
+ $langcode = $langcode ? $langcode : $language_content->language;
+
+ $output = "\n";
+ $output .= ' ' . check_plain($title) . "\n";
+ $output .= ' ' . check_url($link) . "\n";
+
+ // The RSS 2.0 "spec" doesn't indicate HTML can be used in the description.
+ // We strip all HTML tags, but need to prevent double encoding from properly
+ // escaped source data (such as & becoming &).
+ $output .= ' ' . check_plain(decode_entities(strip_tags($description))) . "\n";
+ $output .= ' ' . check_plain($langcode) . "\n";
+ $output .= format_xml_elements($args);
+ $output .= $items;
+ $output .= "\n";
+
+ return $output;
+}
+
+/**
+ * Formats a single RSS item.
+ *
+ * Arbitrary elements may be added using the $args associative array.
+ */
+function format_rss_item($title, $link, $description, $args = array()) {
+ $output = "\n";
+ $output .= ' ' . check_plain($title) . "\n";
+ $output .= ' ' . check_url($link) . "\n";
+ $output .= ' ' . check_plain($description) . "\n";
+ $output .= format_xml_elements($args);
+ $output .= "\n";
+
+ return $output;
+}
+
+/**
+ * Formats XML elements.
+ *
+ * @param $array
+ * An array where each item represents an element and is either a:
+ * - (key => value) pair (value)
+ * - Associative array with fields:
+ * - 'key': element name
+ * - 'value': element contents
+ * - 'attributes': associative array of element attributes
+ * - 'encoded': TRUE if 'value' is already encoded
+ *
+ * In both cases, 'value' can be a simple string, or it can be another array
+ * with the same format as $array itself for nesting.
+ *
+ * If 'encoded' is TRUE it is up to the caller to ensure that 'value' is either
+ * entity-encoded or CDATA-escaped. Using this option is not recommended when
+ * working with untrusted user input, since failing to escape the data
+ * correctly has security implications.
+ */
+function format_xml_elements($array) {
+ $output = '';
+ foreach ($array as $key => $value) {
+ if (is_numeric($key)) {
+ if ($value['key']) {
+ $output .= ' <' . $value['key'];
+ if (isset($value['attributes']) && is_array($value['attributes'])) {
+ $output .= drupal_attributes($value['attributes']);
+ }
+
+ if (isset($value['value']) && $value['value'] != '') {
+ $output .= '>' . (is_array($value['value']) ? format_xml_elements($value['value']) : (!empty($value['encoded']) ? $value['value'] : check_plain($value['value']))) . '' . $value['key'] . ">\n";
+ }
+ else {
+ $output .= " />\n";
+ }
+ }
+ }
+ else {
+ $output .= ' <' . $key . '>' . (is_array($value) ? format_xml_elements($value) : check_plain($value)) . "$key>\n";
+ }
+ }
+ return $output;
+}
+
+/**
+ * Formats a string containing a count of items.
+ *
+ * This function ensures that the string is pluralized correctly. Since t() is
+ * called by this function, make sure not to pass already-localized strings to
+ * it.
+ *
+ * For example:
+ * @code
+ * $output = format_plural($node->comment_count, '1 comment', '@count comments');
+ * @endcode
+ *
+ * Example with additional replacements:
+ * @code
+ * $output = format_plural($update_count,
+ * 'Changed the content type of 1 post from %old-type to %new-type.',
+ * 'Changed the content type of @count posts from %old-type to %new-type.',
+ * array('%old-type' => $info->old_type, '%new-type' => $info->new_type));
+ * @endcode
+ *
+ * @param $count
+ * The item count to display.
+ * @param $singular
+ * The string for the singular case. Make sure it is clear this is singular,
+ * to ease translation (e.g. use "1 new comment" instead of "1 new"). Do not
+ * use @count in the singular string.
+ * @param $plural
+ * The string for the plural case. Make sure it is clear this is plural, to
+ * ease translation. Use @count in place of the item count, as in
+ * "@count new comments".
+ * @param $args
+ * An associative array of replacements to make after translation. Instances
+ * of any key in this array are replaced with the corresponding value.
+ * Based on the first character of the key, the value is escaped and/or
+ * themed. See format_string(). Note that you do not need to include @count
+ * in this array; this replacement is done automatically for the plural case.
+ * @param $options
+ * An associative array of additional options. See t() for allowed keys.
+ *
+ * @return
+ * A translated string.
+ *
+ * @see t()
+ * @see format_string()
+ */
+function format_plural($count, $singular, $plural, array $args = array(), array $options = array()) {
+ $args['@count'] = $count;
+ if ($count == 1) {
+ return t($singular, $args, $options);
+ }
+
+ // Get the plural index through the gettext formula.
+ $index = (function_exists('locale_get_plural')) ? locale_get_plural($count, isset($options['langcode']) ? $options['langcode'] : NULL) : -1;
+ // If the index cannot be computed, use the plural as a fallback (which
+ // allows for most flexiblity with the replaceable @count value).
+ if ($index < 0) {
+ return t($plural, $args, $options);
+ }
+ else {
+ switch ($index) {
+ case "0":
+ return t($singular, $args, $options);
+ case "1":
+ return t($plural, $args, $options);
+ default:
+ unset($args['@count']);
+ $args['@count[' . $index . ']'] = $count;
+ return t(strtr($plural, array('@count' => '@count[' . $index . ']')), $args, $options);
+ }
+ }
+}
+
+/**
+ * Parses a given byte count.
+ *
+ * @param $size
+ * A size expressed as a number of bytes with optional SI or IEC binary unit
+ * prefix (e.g. 2, 3K, 5MB, 10G, 6GiB, 8 bytes, 9mbytes).
+ *
+ * @return
+ * An integer representation of the size in bytes.
+ */
+function parse_size($size) {
+ $unit = preg_replace('/[^bkmgtpezy]/i', '', $size); // Remove the non-unit characters from the size.
+ $size = preg_replace('/[^0-9\.]/', '', $size); // Remove the non-numeric characters from the size.
+ if ($unit) {
+ // Find the position of the unit in the ordered string which is the power of magnitude to multiply a kilobyte by.
+ return round($size * pow(DRUPAL_KILOBYTE, stripos('bkmgtpezy', $unit[0])));
+ }
+ else {
+ return round($size);
+ }
+}
+
+/**
+ * Generates a string representation for the given byte count.
+ *
+ * @param $size
+ * A size in bytes.
+ * @param $langcode
+ * Optional language code to translate to a language other than what is used
+ * to display the page.
+ *
+ * @return
+ * A translated string representation of the size.
+ */
+function format_size($size, $langcode = NULL) {
+ if ($size < DRUPAL_KILOBYTE) {
+ return format_plural($size, '1 byte', '@count bytes', array(), array('langcode' => $langcode));
+ }
+ else {
+ $size = $size / DRUPAL_KILOBYTE; // Convert bytes to kilobytes.
+ $units = array(
+ t('@size KB', array(), array('langcode' => $langcode)),
+ t('@size MB', array(), array('langcode' => $langcode)),
+ t('@size GB', array(), array('langcode' => $langcode)),
+ t('@size TB', array(), array('langcode' => $langcode)),
+ t('@size PB', array(), array('langcode' => $langcode)),
+ t('@size EB', array(), array('langcode' => $langcode)),
+ t('@size ZB', array(), array('langcode' => $langcode)),
+ t('@size YB', array(), array('langcode' => $langcode)),
+ );
+ foreach ($units as $unit) {
+ if (round($size, 2) >= DRUPAL_KILOBYTE) {
+ $size = $size / DRUPAL_KILOBYTE;
+ }
+ else {
+ break;
+ }
+ }
+ return str_replace('@size', round($size, 2), $unit);
+ }
+}
+
+/**
+ * Formats a time interval with the requested granularity.
+ *
+ * @param $interval
+ * The length of the interval in seconds.
+ * @param $granularity
+ * How many different units to display in the string.
+ * @param $langcode
+ * Optional language code to translate to a language other than
+ * what is used to display the page.
+ *
+ * @return
+ * A translated string representation of the interval.
+ */
+function format_interval($interval, $granularity = 2, $langcode = NULL) {
+ $units = array(
+ '1 year|@count years' => 31536000,
+ '1 month|@count months' => 2592000,
+ '1 week|@count weeks' => 604800,
+ '1 day|@count days' => 86400,
+ '1 hour|@count hours' => 3600,
+ '1 min|@count min' => 60,
+ '1 sec|@count sec' => 1
+ );
+ $output = '';
+ foreach ($units as $key => $value) {
+ $key = explode('|', $key);
+ if ($interval >= $value) {
+ $output .= ($output ? ' ' : '') . format_plural(floor($interval / $value), $key[0], $key[1], array(), array('langcode' => $langcode));
+ $interval %= $value;
+ $granularity--;
+ }
+
+ if ($granularity == 0) {
+ break;
+ }
+ }
+ return $output ? $output : t('0 sec', array(), array('langcode' => $langcode));
+}
+
+/**
+ * Formats a date, using a date type or a custom date format string.
+ *
+ * @param $timestamp
+ * A UNIX timestamp to format.
+ * @param $type
+ * (optional) The format to use, one of:
+ * - 'short', 'medium', or 'long' (the corresponding built-in date formats).
+ * - The name of a date type defined by a module in hook_date_format_types(),
+ * if it's been assigned a format.
+ * - The machine name of an administrator-defined date format.
+ * - 'custom', to use $format.
+ * Defaults to 'medium'.
+ * @param $format
+ * (optional) If $type is 'custom', a PHP date format string suitable for
+ * input to date(). Use a backslash to escape ordinary text, so it does not
+ * get interpreted as date format characters.
+ * @param $timezone
+ * (optional) Time zone identifier, as described at
+ * http://php.net/manual/timezones.php Defaults to the time zone used to
+ * display the page.
+ * @param $langcode
+ * (optional) Language code to translate to. Defaults to the language used to
+ * display the page.
+ *
+ * @return
+ * A translated date string in the requested format.
+ */
+function format_date($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL) {
+ // Use the advanced drupal_static() pattern, since this is called very often.
+ static $drupal_static_fast;
+ if (!isset($drupal_static_fast)) {
+ $drupal_static_fast['timezones'] = &drupal_static(__FUNCTION__);
+ }
+ $timezones = &$drupal_static_fast['timezones'];
+
+ if (!isset($timezone)) {
+ $timezone = date_default_timezone_get();
+ }
+ // Store DateTimeZone objects in an array rather than repeatedly
+ // constructing identical objects over the life of a request.
+ if (!isset($timezones[$timezone])) {
+ $timezones[$timezone] = timezone_open($timezone);
+ }
+
+ // Use the default langcode if none is set.
+ global $language;
+ if (empty($langcode)) {
+ $langcode = isset($language->language) ? $language->language : 'en';
+ }
+
+ switch ($type) {
+ case 'short':
+ $format = variable_get('date_format_short', 'm/d/Y - H:i');
+ break;
+
+ case 'long':
+ $format = variable_get('date_format_long', 'l, F j, Y - H:i');
+ break;
+
+ case 'custom':
+ // No change to format.
+ break;
+
+ case 'medium':
+ default:
+ // Retrieve the format of the custom $type passed.
+ if ($type != 'medium') {
+ $format = variable_get('date_format_' . $type, '');
+ }
+ // Fall back to 'medium'.
+ if ($format === '') {
+ $format = variable_get('date_format_medium', 'D, m/d/Y - H:i');
+ }
+ break;
+ }
+
+ // Create a DateTime object from the timestamp.
+ $date_time = date_create('@' . $timestamp);
+ // Set the time zone for the DateTime object.
+ date_timezone_set($date_time, $timezones[$timezone]);
+
+ // Encode markers that should be translated. 'A' becomes '\xEF\AA\xFF'.
+ // xEF and xFF are invalid UTF-8 sequences, and we assume they are not in the
+ // input string.
+ // Paired backslashes are isolated to prevent errors in read-ahead evaluation.
+ // The read-ahead expression ensures that A matches, but not \A.
+ $format = preg_replace(array('/\\\\\\\\/', '/(? $langcode,
+ );
+
+ if ($code == 'F') {
+ $options['context'] = 'Long month name';
+ }
+
+ if ($code == '') {
+ $cache[$langcode][$code][$string] = $string;
+ }
+ else {
+ $cache[$langcode][$code][$string] = t($string, array(), $options);
+ }
+ }
+ return $cache[$langcode][$code][$string];
+}
+
+/**
+ * Format a username.
+ *
+ * This is also the label callback implementation of
+ * callback_entity_info_label() for user_entity_info().
+ *
+ * By default, the passed-in object's 'name' property is used if it exists, or
+ * else, the site-defined value for the 'anonymous' variable. However, a module
+ * may override this by implementing hook_username_alter(&$name, $account).
+ *
+ * @see hook_username_alter()
+ *
+ * @param $account
+ * The account object for the user whose name is to be formatted.
+ *
+ * @return
+ * An unsanitized string with the username to display. The code receiving
+ * this result must ensure that check_plain() is called on it before it is
+ * printed to the page.
+ */
+function format_username($account) {
+ $name = !empty($account->name) ? $account->name : variable_get('anonymous', t('Anonymous'));
+ drupal_alter('username', $name, $account);
+ return $name;
+}
+
+/**
+ * @} End of "defgroup format".
+ */
+
+/**
+ * Generates an internal or external URL.
+ *
+ * When creating links in modules, consider whether l() could be a better
+ * alternative than url().
+ *
+ * @param $path
+ * (optional) The internal path or external URL being linked to, such as
+ * "node/34" or "http://example.com/foo". The default value is equivalent to
+ * passing in ''. A few notes:
+ * - If you provide a full URL, it will be considered an external URL.
+ * - If you provide only the path (e.g. "node/34"), it will be
+ * considered an internal link. In this case, it should be a system URL,
+ * and it will be replaced with the alias, if one exists. Additional query
+ * arguments for internal paths must be supplied in $options['query'], not
+ * included in $path.
+ * - If you provide an internal path and $options['alias'] is set to TRUE, the
+ * path is assumed already to be the correct path alias, and the alias is
+ * not looked up.
+ * - The special string '' generates a link to the site's base URL.
+ * - If your external URL contains a query (e.g. http://example.com/foo?a=b),
+ * then you can either URL encode the query keys and values yourself and
+ * include them in $path, or use $options['query'] to let this function
+ * URL encode them.
+ * @param $options
+ * (optional) An associative array of additional options, with the following
+ * elements:
+ * - 'query': An array of query key/value-pairs (without any URL-encoding) to
+ * append to the URL.
+ * - 'fragment': A fragment identifier (named anchor) to append to the URL.
+ * Do not include the leading '#' character.
+ * - 'absolute': Defaults to FALSE. Whether to force the output to be an
+ * absolute link (beginning with http:). Useful for links that will be
+ * displayed outside the site, such as in an RSS feed.
+ * - 'alias': Defaults to FALSE. Whether the given path is a URL alias
+ * already.
+ * - 'external': Whether the given path is an external URL.
+ * - 'language': An optional language object. If the path being linked to is
+ * internal to the site, $options['language'] is used to look up the alias
+ * for the URL. If $options['language'] is omitted, the global $language_url
+ * will be used.
+ * - 'https': Whether this URL should point to a secure location. If not
+ * defined, the current scheme is used, so the user stays on HTTP or HTTPS
+ * respectively. TRUE enforces HTTPS and FALSE enforces HTTP, but HTTPS can
+ * only be enforced when the variable 'https' is set to TRUE.
+ * - 'base_url': Only used internally, to modify the base URL when a language
+ * dependent URL requires so.
+ * - 'prefix': Only used internally, to modify the path when a language
+ * dependent URL requires so.
+ * - 'script': The script filename in Drupal's root directory to use when
+ * clean URLs are disabled, such as 'index.php'. Defaults to an empty
+ * string, as most modern web servers automatically find 'index.php'. If
+ * clean URLs are disabled, the value of $path is appended as query
+ * parameter 'q' to $options['script'] in the returned URL. When deploying
+ * Drupal on a web server that cannot be configured to automatically find
+ * index.php, then hook_url_outbound_alter() can be implemented to force
+ * this value to 'index.php'.
+ * - 'entity_type': The entity type of the object that called url(). Only
+ * set if url() is invoked by entity_uri().
+ * - 'entity': The entity object (such as a node) for which the URL is being
+ * generated. Only set if url() is invoked by entity_uri().
+ *
+ * @return
+ * A string containing a URL to the given path.
+ */
+function url($path = NULL, array $options = array()) {
+ // Merge in defaults.
+ $options += array(
+ 'fragment' => '',
+ 'query' => array(),
+ 'absolute' => FALSE,
+ 'alias' => FALSE,
+ 'prefix' => ''
+ );
+
+ // Determine whether this is an external link, but ensure that the current
+ // path is always treated as internal by default (to prevent external link
+ // injection vulnerabilities).
+ if (!isset($options['external'])) {
+ $options['external'] = $path === $_GET['q'] ? FALSE : url_is_external($path);
+ }
+
+ // Preserve the original path before altering or aliasing.
+ $original_path = $path;
+
+ // Allow other modules to alter the outbound URL and options.
+ drupal_alter('url_outbound', $path, $options, $original_path);
+
+ if (isset($options['fragment']) && $options['fragment'] !== '') {
+ $options['fragment'] = '#' . $options['fragment'];
+ }
+
+ if ($options['external']) {
+ // Split off the fragment.
+ if (strpos($path, '#') !== FALSE) {
+ list($path, $old_fragment) = explode('#', $path, 2);
+ // If $options contains no fragment, take it over from the path.
+ if (isset($old_fragment) && !$options['fragment']) {
+ $options['fragment'] = '#' . $old_fragment;
+ }
+ }
+ // Append the query.
+ if ($options['query']) {
+ $path .= (strpos($path, '?') !== FALSE ? '&' : '?') . drupal_http_build_query($options['query']);
+ }
+ if (isset($options['https']) && variable_get('https', FALSE)) {
+ if ($options['https'] === TRUE) {
+ $path = str_replace('http://', 'https://', $path);
+ }
+ elseif ($options['https'] === FALSE) {
+ $path = str_replace('https://', 'http://', $path);
+ }
+ }
+ // Reassemble.
+ return $path . $options['fragment'];
+ }
+
+ // Strip leading slashes from internal paths to prevent them becoming external
+ // URLs without protocol. /example.com should not be turned into
+ // //example.com.
+ $path = ltrim($path, '/');
+
+ global $base_url, $base_secure_url, $base_insecure_url;
+
+ // The base_url might be rewritten from the language rewrite in domain mode.
+ if (!isset($options['base_url'])) {
+ if (isset($options['https']) && variable_get('https', FALSE)) {
+ if ($options['https'] === TRUE) {
+ $options['base_url'] = $base_secure_url;
+ $options['absolute'] = TRUE;
+ }
+ elseif ($options['https'] === FALSE) {
+ $options['base_url'] = $base_insecure_url;
+ $options['absolute'] = TRUE;
+ }
+ }
+ else {
+ $options['base_url'] = $base_url;
+ }
+ }
+
+ // The special path '' links to the default front page.
+ if ($path == '') {
+ $path = '';
+ }
+ elseif (!empty($path) && !$options['alias']) {
+ $language = isset($options['language']) && isset($options['language']->language) ? $options['language']->language : '';
+ $alias = drupal_get_path_alias($original_path, $language);
+ if ($alias != $original_path) {
+ // Strip leading slashes from internal path aliases to prevent them
+ // becoming external URLs without protocol. /example.com should not be
+ // turned into //example.com.
+ $path = ltrim($alias, '/');
+ }
+ }
+
+ $base = $options['absolute'] ? $options['base_url'] . '/' : base_path();
+ $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix'];
+
+ // With Clean URLs.
+ if (!empty($GLOBALS['conf']['clean_url'])) {
+ $path = drupal_encode_path($prefix . $path);
+ if ($options['query']) {
+ return $base . $path . '?' . drupal_http_build_query($options['query']) . $options['fragment'];
+ }
+ else {
+ return $base . $path . $options['fragment'];
+ }
+ }
+ // Without Clean URLs.
+ else {
+ $path = $prefix . $path;
+ $query = array();
+ if (!empty($path)) {
+ $query['q'] = $path;
+ }
+ if ($options['query']) {
+ // We do not use array_merge() here to prevent overriding $path via query
+ // parameters.
+ $query += $options['query'];
+ }
+ $query = $query ? ('?' . drupal_http_build_query($query)) : '';
+ $script = isset($options['script']) ? $options['script'] : '';
+ return $base . $script . $query . $options['fragment'];
+ }
+}
+
+/**
+ * Returns TRUE if a path is external to Drupal (e.g. http://example.com).
+ *
+ * If a path cannot be assessed by Drupal's menu handler, then we must
+ * treat it as potentially insecure.
+ *
+ * @param $path
+ * The internal path or external URL being linked to, such as "node/34" or
+ * "http://example.com/foo".
+ *
+ * @return
+ * Boolean TRUE or FALSE, where TRUE indicates an external path.
+ */
+function url_is_external($path) {
+ $colonpos = strpos($path, ':');
+ // Some browsers treat \ as / so normalize to forward slashes.
+ $path = str_replace('\\', '/', $path);
+ // If the path starts with 2 slashes then it is always considered an external
+ // URL without an explicit protocol part.
+ return (strpos($path, '//') === 0)
+ // Leading control characters may be ignored or mishandled by browsers, so
+ // assume such a path may lead to an external location. The \p{C} character
+ // class matches all UTF-8 control, unassigned, and private characters.
+ || (preg_match('/^\p{C}/u', $path) !== 0)
+ // Avoid calling drupal_strip_dangerous_protocols() if there is any slash
+ // (/), hash (#) or question_mark (?) before the colon (:) occurrence - if
+ // any - as this would clearly mean it is not a URL.
+ || ($colonpos !== FALSE
+ && !preg_match('![/?#]!', substr($path, 0, $colonpos))
+ && drupal_strip_dangerous_protocols($path) == $path);
+}
+
+/**
+ * Formats an attribute string for an HTTP header.
+ *
+ * @param $attributes
+ * An associative array of attributes such as 'rel'.
+ *
+ * @return
+ * A ; separated string ready for insertion in a HTTP header. No escaping is
+ * performed for HTML entities, so this string is not safe to be printed.
+ *
+ * @see drupal_add_http_header()
+ */
+function drupal_http_header_attributes(array $attributes = array()) {
+ foreach ($attributes as $attribute => &$data) {
+ if (is_array($data)) {
+ $data = implode(' ', $data);
+ }
+ $data = $attribute . '="' . $data . '"';
+ }
+ return $attributes ? ' ' . implode('; ', $attributes) : '';
+}
+
+/**
+ * Converts an associative array to an XML/HTML tag attribute string.
+ *
+ * Each array key and its value will be formatted into an attribute string.
+ * If a value is itself an array, then its elements are concatenated to a single
+ * space-delimited string (for example, a class attribute with multiple values).
+ *
+ * Attribute values are sanitized by running them through check_plain().
+ * Attribute names are not automatically sanitized. When using user-supplied
+ * attribute names, it is strongly recommended to allow only white-listed names,
+ * since certain attributes carry security risks and can be abused.
+ *
+ * Examples of security aspects when using drupal_attributes:
+ * @code
+ * // By running the value in the following statement through check_plain,
+ * // the malicious script is neutralized.
+ * drupal_attributes(array('title' => t('')));
+ *
+ * // The statement below demonstrates dangerous use of drupal_attributes, and
+ * // will return an onmouseout attribute with JavaScript code that, when used
+ * // as attribute in a tag, will cause users to be redirected to another site.
+ * //
+ * // In this case, the 'onmouseout' attribute should not be whitelisted --
+ * // you don't want users to have the ability to add this attribute or others
+ * // that take JavaScript commands.
+ * drupal_attributes(array('onmouseout' => 'window.location="http://malicious.com/";')));
+ * @endcode
+ *
+ * @param $attributes
+ * An associative array of key-value pairs to be converted to attributes.
+ *
+ * @return
+ * A string ready for insertion in a tag (starts with a space).
+ *
+ * @ingroup sanitization
+ */
+function drupal_attributes(array $attributes = array()) {
+ foreach ($attributes as $attribute => &$data) {
+ $data = implode(' ', (array) $data);
+ $data = $attribute . '="' . check_plain($data) . '"';
+ }
+ return $attributes ? ' ' . implode(' ', $attributes) : '';
+}
+
+/**
+ * Formats an internal or external URL link as an HTML anchor tag.
+ *
+ * This function correctly handles aliased paths and adds an 'active' class
+ * attribute to links that point to the current page (for theming), so all
+ * internal links output by modules should be generated by this function if
+ * possible.
+ *
+ * However, for links enclosed in translatable text you should use t() and
+ * embed the HTML anchor tag directly in the translated string. For example:
+ * @code
+ * t('Visit the settings page', array('@url' => url('admin')));
+ * @endcode
+ * This keeps the context of the link title ('settings' in the example) for
+ * translators.
+ *
+ * @param string $text
+ * The translated link text for the anchor tag.
+ * @param string $path
+ * The internal path or external URL being linked to, such as "node/34" or
+ * "http://example.com/foo". After the url() function is called to construct
+ * the URL from $path and $options, the resulting URL is passed through
+ * check_plain() before it is inserted into the HTML anchor tag, to ensure
+ * well-formed HTML. See url() for more information and notes.
+ * @param array $options
+ * An associative array of additional options. Defaults to an empty array. It
+ * may contain the following elements.
+ * - 'attributes': An associative array of HTML attributes to apply to the
+ * anchor tag. If element 'class' is included, it must be an array; 'title'
+ * must be a string; other elements are more flexible, as they just need
+ * to work in a call to drupal_attributes($options['attributes']).
+ * - 'html' (default FALSE): Whether $text is HTML or just plain-text. For
+ * example, to make an image tag into a link, this must be set to TRUE, or
+ * you will see the escaped HTML image tag. $text is not sanitized if
+ * 'html' is TRUE. The calling function must ensure that $text is already
+ * safe.
+ * - 'language': An optional language object. If the path being linked to is
+ * internal to the site, $options['language'] is used to determine whether
+ * the link is "active", or pointing to the current page (the language as
+ * well as the path must match). This element is also used by url().
+ * - Additional $options elements used by the url() function.
+ *
+ * @return string
+ * An HTML string containing a link to the given path.
+ *
+ * @see url()
+ */
+function l($text, $path, array $options = array()) {
+ global $language_url;
+ static $use_theme = NULL;
+
+ // Merge in defaults.
+ $options += array(
+ 'attributes' => array(),
+ 'html' => FALSE,
+ );
+
+ // Append active class.
+ if (($path == $_GET['q'] || ($path == '' && drupal_is_front_page())) &&
+ (empty($options['language']) || $options['language']->language == $language_url->language)) {
+ $options['attributes']['class'][] = 'active';
+ }
+
+ // Remove all HTML and PHP tags from a tooltip. For best performance, we act only
+ // if a quick strpos() pre-check gave a suspicion (because strip_tags() is expensive).
+ if (isset($options['attributes']['title']) && strpos($options['attributes']['title'], '<') !== FALSE) {
+ $options['attributes']['title'] = strip_tags($options['attributes']['title']);
+ }
+
+ // Determine if rendering of the link is to be done with a theme function
+ // or the inline default. Inline is faster, but if the theme system has been
+ // loaded and a module or theme implements a preprocess or process function
+ // or overrides the theme_link() function, then invoke theme(). Preliminary
+ // benchmarks indicate that invoking theme() can slow down the l() function
+ // by 20% or more, and that some of the link-heavy Drupal pages spend more
+ // than 10% of the total page request time in the l() function.
+ if (!isset($use_theme) && function_exists('theme')) {
+ // Allow edge cases to prevent theme initialization and force inline link
+ // rendering.
+ if (variable_get('theme_link', TRUE)) {
+ drupal_theme_initialize();
+ $registry = theme_get_registry(FALSE);
+ // We don't want to duplicate functionality that's in theme(), so any
+ // hint of a module or theme doing anything at all special with the 'link'
+ // theme hook should simply result in theme() being called. This includes
+ // the overriding of theme_link() with an alternate function or template,
+ // the presence of preprocess or process functions, or the presence of
+ // include files.
+ $use_theme = !isset($registry['link']['function']) || ($registry['link']['function'] != 'theme_link');
+ $use_theme = $use_theme || !empty($registry['link']['preprocess functions']) || !empty($registry['link']['process functions']) || !empty($registry['link']['includes']);
+ }
+ else {
+ $use_theme = FALSE;
+ }
+ }
+ if ($use_theme) {
+ return theme('link', array('text' => $text, 'path' => $path, 'options' => $options));
+ }
+ // The result of url() is a plain-text URL. Because we are using it here
+ // in an HTML argument context, we need to encode it properly.
+ return '' . ($options['html'] ? $text : check_plain($text)) . '';
+}
+
+/**
+ * Delivers a page callback result to the browser in the appropriate format.
+ *
+ * This function is most commonly called by menu_execute_active_handler(), but
+ * can also be called by error conditions such as drupal_not_found(),
+ * drupal_access_denied(), and drupal_site_offline().
+ *
+ * When a user requests a page, index.php calls menu_execute_active_handler(),
+ * which calls the 'page callback' function registered in hook_menu(). The page
+ * callback function can return one of:
+ * - NULL: to indicate no content.
+ * - An integer menu status constant: to indicate an error condition.
+ * - A string of HTML content.
+ * - A renderable array of content.
+ * Returning a renderable array rather than a string of HTML is preferred,
+ * because that provides modules with more flexibility in customizing the final
+ * result.
+ *
+ * When the page callback returns its constructed content to
+ * menu_execute_active_handler(), this function gets called. The purpose of
+ * this function is to determine the most appropriate 'delivery callback'
+ * function to route the content to. The delivery callback function then
+ * sends the content to the browser in the needed format. The default delivery
+ * callback is drupal_deliver_html_page(), which delivers the content as an HTML
+ * page, complete with blocks in addition to the content. This default can be
+ * overridden on a per menu router item basis by setting 'delivery callback' in
+ * hook_menu() or hook_menu_alter(), and can also be overridden on a per request
+ * basis in hook_page_delivery_callback_alter().
+ *
+ * For example, the same page callback function can be used for an HTML
+ * version of the page and an Ajax version of the page. The page callback
+ * function just needs to decide what content is to be returned and the
+ * delivery callback function will send it as an HTML page or an Ajax
+ * response, as appropriate.
+ *
+ * In order for page callbacks to be reusable in different delivery formats,
+ * they should not issue any "print" or "echo" statements, but instead just
+ * return content.
+ *
+ * Also note that this function does not perform access checks. The delivery
+ * callback function specified in hook_menu(), hook_menu_alter(), or
+ * hook_page_delivery_callback_alter() will be called even if the router item
+ * access checks fail. This is intentional (it is needed for JSON and other
+ * purposes), but it has security implications. Do not call this function
+ * directly unless you understand the security implications, and be careful in
+ * writing delivery callbacks, so that they do not violate security. See
+ * drupal_deliver_html_page() for an example of a delivery callback that
+ * respects security.
+ *
+ * @param $page_callback_result
+ * The result of a page callback. Can be one of:
+ * - NULL: to indicate no content.
+ * - An integer menu status constant: to indicate an error condition.
+ * - A string of HTML content.
+ * - A renderable array of content.
+ * @param $default_delivery_callback
+ * (Optional) If given, it is the name of a delivery function most likely
+ * to be appropriate for the page request as determined by the calling
+ * function (e.g., menu_execute_active_handler()). If not given, it is
+ * determined from the menu router information of the current page.
+ *
+ * @see menu_execute_active_handler()
+ * @see hook_menu()
+ * @see hook_menu_alter()
+ * @see hook_page_delivery_callback_alter()
+ */
+function drupal_deliver_page($page_callback_result, $default_delivery_callback = NULL) {
+ if (!isset($default_delivery_callback) && ($router_item = menu_get_item())) {
+ $default_delivery_callback = $router_item['delivery_callback'];
+ }
+ $delivery_callback = !empty($default_delivery_callback) ? $default_delivery_callback : 'drupal_deliver_html_page';
+ // Give modules a chance to alter the delivery callback used, based on
+ // request-time context (e.g., HTTP request headers).
+ drupal_alter('page_delivery_callback', $delivery_callback);
+ if (function_exists($delivery_callback)) {
+ $delivery_callback($page_callback_result);
+ }
+ else {
+ // If a delivery callback is specified, but doesn't exist as a function,
+ // something is wrong, but don't print anything, since it's not known
+ // what format the response needs to be in.
+ watchdog('delivery callback not found', 'callback %callback not found: %q.', array('%callback' => $delivery_callback, '%q' => $_GET['q']), WATCHDOG_ERROR);
+ }
+}
+
+/**
+ * Packages and sends the result of a page callback to the browser as HTML.
+ *
+ * @param $page_callback_result
+ * The result of a page callback. Can be one of:
+ * - NULL: to indicate no content.
+ * - An integer menu status constant: to indicate an error condition.
+ * - A string of HTML content.
+ * - A renderable array of content.
+ *
+ * @see drupal_deliver_page()
+ */
+function drupal_deliver_html_page($page_callback_result) {
+ // Emit the correct charset HTTP header, but not if the page callback
+ // result is NULL, since that likely indicates that it printed something
+ // in which case, no further headers may be sent, and not if code running
+ // for this page request has already set the content type header.
+ if (isset($page_callback_result) && is_null(drupal_get_http_header('Content-Type'))) {
+ drupal_add_http_header('Content-Type', 'text/html; charset=utf-8');
+ }
+
+ // Send appropriate HTTP-Header for browsers and search engines.
+ global $language;
+ drupal_add_http_header('Content-Language', $language->language);
+
+ // By default, do not allow the site to be rendered in an iframe on another
+ // domain, but provide a variable to override this. If the code running for
+ // this page request already set the X-Frame-Options header earlier, don't
+ // overwrite it here.
+ $frame_options = variable_get('x_frame_options', 'SAMEORIGIN');
+ if ($frame_options && is_null(drupal_get_http_header('X-Frame-Options'))) {
+ drupal_add_http_header('X-Frame-Options', $frame_options);
+ }
+
+ // Menu status constants are integers; page content is a string or array.
+ if (is_int($page_callback_result)) {
+ // @todo: Break these up into separate functions?
+ switch ($page_callback_result) {
+ case MENU_NOT_FOUND:
+ // Print a 404 page.
+ drupal_add_http_header('Status', '404 Not Found');
+
+ watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
+
+ // Check for and return a fast 404 page if configured.
+ drupal_fast_404();
+
+ // Keep old path for reference, and to allow forms to redirect to it.
+ if (!isset($_GET['destination'])) {
+ // Make sure that the current path is not interpreted as external URL.
+ if (!url_is_external($_GET['q'])) {
+ $_GET['destination'] = $_GET['q'];
+ }
+ }
+
+ $path = drupal_get_normal_path(variable_get('site_404', ''));
+ if ($path && $path != $_GET['q']) {
+ // Custom 404 handler. Set the active item in case there are tabs to
+ // display, or other dependencies on the path.
+ menu_set_active_item($path);
+ $return = menu_execute_active_handler($path, FALSE);
+ }
+
+ if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
+ // Standard 404 handler.
+ drupal_set_title(t('Page not found'));
+ $return = t('The requested page "@path" could not be found.', array('@path' => request_uri()));
+ }
+
+ drupal_set_page_content($return);
+ $page = element_info('page');
+ print drupal_render_page($page);
+ break;
+
+ case MENU_ACCESS_DENIED:
+ // Print a 403 page.
+ drupal_add_http_header('Status', '403 Forbidden');
+ watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
+
+ // Keep old path for reference, and to allow forms to redirect to it.
+ if (!isset($_GET['destination'])) {
+ // Make sure that the current path is not interpreted as external URL.
+ if (!url_is_external($_GET['q'])) {
+ $_GET['destination'] = $_GET['q'];
+ }
+ }
+
+ $path = drupal_get_normal_path(variable_get('site_403', ''));
+ if ($path && $path != $_GET['q']) {
+ // Custom 403 handler. Set the active item in case there are tabs to
+ // display or other dependencies on the path.
+ menu_set_active_item($path);
+ $return = menu_execute_active_handler($path, FALSE);
+ }
+
+ if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
+ // Standard 403 handler.
+ drupal_set_title(t('Access denied'));
+ $return = t('You are not authorized to access this page.');
+ }
+
+ print drupal_render_page($return);
+ break;
+
+ case MENU_SITE_OFFLINE:
+ // Print a 503 page.
+ drupal_maintenance_theme();
+ drupal_add_http_header('Status', '503 Service unavailable');
+ drupal_set_title(t('Site under maintenance'));
+ print theme('maintenance_page', array('content' => filter_xss_admin(variable_get('maintenance_mode_message',
+ t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))))));
+ break;
+ }
+ }
+ elseif (isset($page_callback_result)) {
+ // Print anything besides a menu constant, assuming it's not NULL or
+ // undefined.
+ print drupal_render_page($page_callback_result);
+ }
+
+ // Perform end-of-request tasks.
+ drupal_page_footer();
+}
+
+/**
+ * Performs end-of-request tasks.
+ *
+ * This function sets the page cache if appropriate, and allows modules to
+ * react to the closing of the page by calling hook_exit().
+ */
+function drupal_page_footer() {
+ global $user;
+
+ module_invoke_all('exit');
+
+ // Commit the user session, if needed.
+ drupal_session_commit();
+
+ if (variable_get('cache', 0) && ($cache = drupal_page_set_cache())) {
+ drupal_serve_page_from_cache($cache);
+ }
+ else {
+ ob_flush();
+ }
+
+ _registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE);
+ drupal_cache_system_paths();
+ module_implements_write_cache();
+ drupal_file_scan_write_cache();
+ system_run_automated_cron();
+}
+
+/**
+ * Performs end-of-request tasks.
+ *
+ * In some cases page requests need to end without calling drupal_page_footer().
+ * In these cases, call drupal_exit() instead. There should rarely be a reason
+ * to call exit instead of drupal_exit();
+ *
+ * @param $destination
+ * If this function is called from drupal_goto(), then this argument
+ * will be a fully-qualified URL that is the destination of the redirect.
+ * This should be passed along to hook_exit() implementations.
+ */
+function drupal_exit($destination = NULL) {
+ if (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL) {
+ if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
+ module_invoke_all('exit', $destination);
+ }
+ drupal_session_commit();
+ }
+ exit;
+}
+
+/**
+ * Forms an associative array from a linear array.
+ *
+ * This function walks through the provided array and constructs an associative
+ * array out of it. The keys of the resulting array will be the values of the
+ * input array. The values will be the same as the keys unless a function is
+ * specified, in which case the output of the function is used for the values
+ * instead.
+ *
+ * @param $array
+ * A linear array.
+ * @param $function
+ * A name of a function to apply to all values before output.
+ *
+ * @return
+ * An associative array.
+ */
+function drupal_map_assoc($array, $function = NULL) {
+ // array_combine() fails with empty arrays:
+ // http://bugs.php.net/bug.php?id=34857.
+ $array = !empty($array) ? array_combine($array, $array) : array();
+ if (is_callable($function)) {
+ $array = array_map($function, $array);
+ }
+ return $array;
+}
+
+/**
+ * Attempts to set the PHP maximum execution time.
+ *
+ * This function is a wrapper around the PHP function set_time_limit().
+ * When called, set_time_limit() restarts the timeout counter from zero.
+ * In other words, if the timeout is the default 30 seconds, and 25 seconds
+ * into script execution a call such as set_time_limit(20) is made, the
+ * script will run for a total of 45 seconds before timing out.
+ *
+ * If the current time limit is not unlimited it is possible to decrease the
+ * total time limit if the sum of the new time limit and the current time spent
+ * running the script is inferior to the original time limit. It is inherent to
+ * the way set_time_limit() works, it should rather be called with an
+ * appropriate value every time you need to allocate a certain amount of time
+ * to execute a task than only once at the beginning of the script.
+ *
+ * Before calling set_time_limit(), we check if this function is available
+ * because it could be disabled by the server administrator. We also hide all
+ * the errors that could occur when calling set_time_limit(), because it is
+ * not possible to reliably ensure that PHP or a security extension will
+ * not issue a warning/error if they prevent the use of this function.
+ *
+ * @param $time_limit
+ * An integer specifying the new time limit, in seconds. A value of 0
+ * indicates unlimited execution time.
+ *
+ * @ingroup php_wrappers
+ */
+function drupal_set_time_limit($time_limit) {
+ if (function_exists('set_time_limit')) {
+ $current = ini_get('max_execution_time');
+ // Do not set time limit if it is currently unlimited.
+ if ($current != 0) {
+ @set_time_limit($time_limit);
+ }
+ }
+}
+
+/**
+ * Returns the path to a system item (module, theme, etc.).
+ *
+ * @param $type
+ * The type of the item (i.e. theme, theme_engine, module, profile).
+ * @param $name
+ * The name of the item for which the path is requested.
+ *
+ * @return
+ * The path to the requested item or an empty string if the item is not found.
+ */
+function drupal_get_path($type, $name) {
+ return dirname(drupal_get_filename($type, $name));
+}
+
+/**
+ * Returns the base URL path (i.e., directory) of the Drupal installation.
+ *
+ * base_path() adds a "/" to the beginning and end of the returned path if the
+ * path is not empty. At the very least, this will return "/".
+ *
+ * Examples:
+ * - http://example.com returns "/" because the path is empty.
+ * - http://example.com/drupal/folder returns "/drupal/folder/".
+ */
+function base_path() {
+ return $GLOBALS['base_path'];
+}
+
+/**
+ * Adds a LINK tag with a distinct 'rel' attribute to the page's HEAD.
+ *
+ * This function can be called as long the HTML header hasn't been sent, which
+ * on normal pages is up through the preprocess step of theme('html'). Adding
+ * a link will overwrite a prior link with the exact same 'rel' and 'href'
+ * attributes.
+ *
+ * @param $attributes
+ * Associative array of element attributes including 'href' and 'rel'.
+ * @param $header
+ * Optional flag to determine if a HTTP 'Link:' header should be sent.
+ */
+function drupal_add_html_head_link($attributes, $header = FALSE) {
+ $element = array(
+ '#tag' => 'link',
+ '#attributes' => $attributes,
+ );
+ $href = $attributes['href'];
+
+ if ($header) {
+ // Also add a HTTP header "Link:".
+ $href = '<' . check_plain($attributes['href']) . '>;';
+ unset($attributes['href']);
+ $element['#attached']['drupal_add_http_header'][] = array('Link', $href . drupal_http_header_attributes($attributes), TRUE);
+ }
+
+ drupal_add_html_head($element, 'drupal_add_html_head_link:' . $attributes['rel'] . ':' . $href);
+}
+
+/**
+ * Adds a cascading stylesheet to the stylesheet queue.
+ *
+ * Calling drupal_static_reset('drupal_add_css') will clear all cascading
+ * stylesheets added so far.
+ *
+ * If CSS aggregation/compression is enabled, all cascading style sheets added
+ * with $options['preprocess'] set to TRUE will be merged into one aggregate
+ * file and compressed by removing all extraneous white space.
+ * Preprocessed inline stylesheets will not be aggregated into this single file;
+ * instead, they are just compressed upon output on the page. Externally hosted
+ * stylesheets are never aggregated or compressed.
+ *
+ * The reason for aggregating the files is outlined quite thoroughly here:
+ * http://www.die.net/musings/page_load_time/ "Load fewer external objects. Due
+ * to request overhead, one bigger file just loads faster than two smaller ones
+ * half its size."
+ *
+ * $options['preprocess'] should be only set to TRUE when a file is required for
+ * all typical visitors and most pages of a site. It is critical that all
+ * preprocessed files are added unconditionally on every page, even if the
+ * files do not happen to be needed on a page. This is normally done by calling
+ * drupal_add_css() in a hook_init() implementation.
+ *
+ * Non-preprocessed files should only be added to the page when they are
+ * actually needed.
+ *
+ * @param $data
+ * (optional) The stylesheet data to be added, depending on what is passed
+ * through to the $options['type'] parameter:
+ * - 'file': The path to the CSS file relative to the base_path(), or a
+ * stream wrapper URI. For example: "modules/devel/devel.css" or
+ * "public://generated_css/stylesheet_1.css". Note that Modules should
+ * always prefix the names of their CSS files with the module name; for
+ * example, system-menus.css rather than simply menus.css. Themes can
+ * override module-supplied CSS files based on their filenames, and this
+ * prefixing helps prevent confusing name collisions for theme developers.
+ * See drupal_get_css() where the overrides are performed. Also, if the
+ * direction of the current language is right-to-left (Hebrew, Arabic,
+ * etc.), the function will also look for an RTL CSS file and append it to
+ * the list. The name of this file should have an '-rtl.css' suffix. For
+ * example, a CSS file called 'mymodule-name.css' will have a
+ * 'mymodule-name-rtl.css' file added to the list, if exists in the same
+ * directory. This CSS file should contain overrides for properties which
+ * should be reversed or otherwise different in a right-to-left display.
+ * - 'inline': A string of CSS that should be placed in the given scope. Note
+ * that it is better practice to use 'file' stylesheets, rather than
+ * 'inline', as the CSS would then be aggregated and cached.
+ * - 'external': The absolute path to an external CSS file that is not hosted
+ * on the local server. These files will not be aggregated if CSS
+ * aggregation is enabled.
+ * @param $options
+ * (optional) A string defining the 'type' of CSS that is being added in the
+ * $data parameter ('file', 'inline', or 'external'), or an array which can
+ * have any or all of the following keys:
+ * - 'type': The type of stylesheet being added. Available options are 'file',
+ * 'inline' or 'external'. Defaults to 'file'.
+ * - 'basename': Force a basename for the file being added. Modules are
+ * expected to use stylesheets with unique filenames, but integration of
+ * external libraries may make this impossible. The basename of
+ * 'modules/node/node.css' is 'node.css'. If the external library "node.js"
+ * ships with a 'node.css', then a different, unique basename would be
+ * 'node.js.css'.
+ * - 'group': A number identifying the group in which to add the stylesheet.
+ * Available constants are:
+ * - CSS_SYSTEM: Any system-layer CSS.
+ * - CSS_DEFAULT: (default) Any module-layer CSS.
+ * - CSS_THEME: Any theme-layer CSS.
+ * The group number serves as a weight: the markup for loading a stylesheet
+ * within a lower weight group is output to the page before the markup for
+ * loading a stylesheet within a higher weight group, so CSS within higher
+ * weight groups take precendence over CSS within lower weight groups.
+ * - 'every_page': For optimal front-end performance when aggregation is
+ * enabled, this should be set to TRUE if the stylesheet is present on every
+ * page of the website for users for whom it is present at all. This
+ * defaults to FALSE. It is set to TRUE for stylesheets added via module and
+ * theme .info files. Modules that add stylesheets within hook_init()
+ * implementations, or from other code that ensures that the stylesheet is
+ * added to all website pages, should also set this flag to TRUE. All
+ * stylesheets within the same group that have the 'every_page' flag set to
+ * TRUE and do not have 'preprocess' set to FALSE are aggregated together
+ * into a single aggregate file, and that aggregate file can be reused
+ * across a user's entire site visit, leading to faster navigation between
+ * pages. However, stylesheets that are only needed on pages less frequently
+ * visited, can be added by code that only runs for those particular pages,
+ * and that code should not set the 'every_page' flag. This minimizes the
+ * size of the aggregate file that the user needs to download when first
+ * visiting the website. Stylesheets without the 'every_page' flag are
+ * aggregated into a separate aggregate file. This other aggregate file is
+ * likely to change from page to page, and each new aggregate file needs to
+ * be downloaded when first encountered, so it should be kept relatively
+ * small by ensuring that most commonly needed stylesheets are added to
+ * every page.
+ * - 'weight': The weight of the stylesheet specifies the order in which the
+ * CSS will appear relative to other stylesheets with the same group and
+ * 'every_page' flag. The exact ordering of stylesheets is as follows:
+ * - First by group.
+ * - Then by the 'every_page' flag, with TRUE coming before FALSE.
+ * - Then by weight.
+ * - Then by the order in which the CSS was added. For example, all else
+ * being the same, a stylesheet added by a call to drupal_add_css() that
+ * happened later in the page request gets added to the page after one for
+ * which drupal_add_css() happened earlier in the page request.
+ * - 'media': The media type for the stylesheet, e.g., all, print, screen.
+ * Defaults to 'all'.
+ * - 'preprocess': If TRUE and CSS aggregation/compression is enabled, the
+ * styles will be aggregated and compressed. Defaults to TRUE.
+ * - 'browsers': An array containing information specifying which browsers
+ * should load the CSS item. See drupal_pre_render_conditional_comments()
+ * for details.
+ *
+ * @return
+ * An array of queued cascading stylesheets.
+ *
+ * @see drupal_get_css()
+ */
+function drupal_add_css($data = NULL, $options = NULL) {
+ $css = &drupal_static(__FUNCTION__, array());
+ $count = &drupal_static(__FUNCTION__ . '_count', 0);
+
+ // If the $css variable has been reset with drupal_static_reset(), there is
+ // no longer any CSS being tracked, so set the counter back to 0 also.
+ if (count($css) === 0) {
+ $count = 0;
+ }
+
+ // Construct the options, taking the defaults into consideration.
+ if (isset($options)) {
+ if (!is_array($options)) {
+ $options = array('type' => $options);
+ }
+ }
+ else {
+ $options = array();
+ }
+
+ // Create an array of CSS files for each media type first, since each type needs to be served
+ // to the browser differently.
+ if (isset($data)) {
+ $options += array(
+ 'type' => 'file',
+ 'group' => CSS_DEFAULT,
+ 'weight' => 0,
+ 'every_page' => FALSE,
+ 'media' => 'all',
+ 'preprocess' => TRUE,
+ 'data' => $data,
+ 'browsers' => array(),
+ );
+ $options['browsers'] += array(
+ 'IE' => TRUE,
+ '!IE' => TRUE,
+ );
+
+ // Files with a query string cannot be preprocessed.
+ if ($options['type'] === 'file' && $options['preprocess'] && strpos($options['data'], '?') !== FALSE) {
+ $options['preprocess'] = FALSE;
+ }
+
+ // Always add a tiny value to the weight, to conserve the insertion order.
+ $options['weight'] += $count / 1000;
+ $count++;
+
+ // Add the data to the CSS array depending on the type.
+ switch ($options['type']) {
+ case 'inline':
+ // For inline stylesheets, we don't want to use the $data as the array
+ // key as $data could be a very long string of CSS.
+ $css[] = $options;
+ break;
+ default:
+ // Local and external files must keep their name as the associative key
+ // so the same CSS file is not be added twice.
+ $css[$data] = $options;
+ }
+ }
+
+ return $css;
+}
+
+/**
+ * Returns a themed representation of all stylesheets to attach to the page.
+ *
+ * It loads the CSS in order, with 'module' first, then 'theme' afterwards.
+ * This ensures proper cascading of styles so themes can easily override
+ * module styles through CSS selectors.
+ *
+ * Themes may replace module-defined CSS files by adding a stylesheet with the
+ * same filename. For example, themes/bartik/system-menus.css would replace
+ * modules/system/system-menus.css. This allows themes to override complete
+ * CSS files, rather than specific selectors, when necessary.
+ *
+ * If the original CSS file is being overridden by a theme, the theme is
+ * responsible for supplying an accompanying RTL CSS file to replace the
+ * module's.
+ *
+ * @param $css
+ * (optional) An array of CSS files. If no array is provided, the default
+ * stylesheets array is used instead.
+ * @param $skip_alter
+ * (optional) If set to TRUE, this function skips calling drupal_alter() on
+ * $css, useful when the calling function passes a $css array that has already
+ * been altered.
+ *
+ * @return
+ * A string of XHTML CSS tags.
+ *
+ * @see drupal_add_css()
+ */
+function drupal_get_css($css = NULL, $skip_alter = FALSE) {
+ if (!isset($css)) {
+ $css = drupal_add_css();
+ }
+
+ // Allow modules and themes to alter the CSS items.
+ if (!$skip_alter) {
+ drupal_alter('css', $css);
+ }
+
+ // Sort CSS items, so that they appear in the correct order.
+ uasort($css, 'drupal_sort_css_js');
+
+ // Provide the page with information about the individual CSS files used,
+ // information not otherwise available when CSS aggregation is enabled. The
+ // setting is attached later in this function, but is set here, so that CSS
+ // files removed below are still considered "used" and prevented from being
+ // added in a later AJAX request.
+ // Skip if no files were added to the page or jQuery.extend() will overwrite
+ // the Drupal.settings.ajaxPageState.css object with an empty array.
+ if (!empty($css)) {
+ // Cast the array to an object to be on the safe side even if not empty.
+ $setting['ajaxPageState']['css'] = (object) array_fill_keys(array_keys($css), 1);
+ }
+
+ // Remove the overridden CSS files. Later CSS files override former ones.
+ $previous_item = array();
+ foreach ($css as $key => $item) {
+ if ($item['type'] == 'file') {
+ // If defined, force a unique basename for this file.
+ $basename = isset($item['basename']) ? $item['basename'] : drupal_basename($item['data']);
+ if (isset($previous_item[$basename])) {
+ // Remove the previous item that shared the same base name.
+ unset($css[$previous_item[$basename]]);
+ }
+ $previous_item[$basename] = $key;
+ }
+ }
+
+ // Render the HTML needed to load the CSS.
+ $styles = array(
+ '#type' => 'styles',
+ '#items' => $css,
+ );
+
+ if (!empty($setting)) {
+ $styles['#attached']['js'][] = array('type' => 'setting', 'data' => $setting);
+ }
+
+ return drupal_render($styles);
+}
+
+/**
+ * Sorts CSS and JavaScript resources.
+ *
+ * Callback for uasort() within:
+ * - drupal_get_css()
+ * - drupal_get_js()
+ *
+ * This sort order helps optimize front-end performance while providing modules
+ * and themes with the necessary control for ordering the CSS and JavaScript
+ * appearing on a page.
+ *
+ * @param $a
+ * First item for comparison. The compared items should be associative arrays
+ * of member items from drupal_add_css() or drupal_add_js().
+ * @param $b
+ * Second item for comparison.
+ *
+ * @see drupal_add_css()
+ * @see drupal_add_js()
+ */
+function drupal_sort_css_js($a, $b) {
+ // First order by group, so that, for example, all items in the CSS_SYSTEM
+ // group appear before items in the CSS_DEFAULT group, which appear before
+ // all items in the CSS_THEME group. Modules may create additional groups by
+ // defining their own constants.
+ if ($a['group'] < $b['group']) {
+ return -1;
+ }
+ elseif ($a['group'] > $b['group']) {
+ return 1;
+ }
+ // Within a group, order all infrequently needed, page-specific files after
+ // common files needed throughout the website. Separating this way allows for
+ // the aggregate file generated for all of the common files to be reused
+ // across a site visit without being cut by a page using a less common file.
+ elseif ($a['every_page'] && !$b['every_page']) {
+ return -1;
+ }
+ elseif (!$a['every_page'] && $b['every_page']) {
+ return 1;
+ }
+ // Finally, order by weight.
+ elseif ($a['weight'] < $b['weight']) {
+ return -1;
+ }
+ elseif ($a['weight'] > $b['weight']) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+}
+
+/**
+ * Default callback to group CSS items.
+ *
+ * This function arranges the CSS items that are in the #items property of the
+ * styles element into groups. Arranging the CSS items into groups serves two
+ * purposes. When aggregation is enabled, files within a group are aggregated
+ * into a single file, significantly improving page loading performance by
+ * minimizing network traffic overhead. When aggregation is disabled, grouping
+ * allows multiple files to be loaded from a single STYLE tag, enabling sites
+ * with many modules enabled or a complex theme being used to stay within IE's
+ * 31 CSS inclusion tag limit: http://drupal.org/node/228818.
+ *
+ * This function puts multiple items into the same group if they are groupable
+ * and if they are for the same 'media' and 'browsers'. Items of the 'file' type
+ * are groupable if their 'preprocess' flag is TRUE, items of the 'inline' type
+ * are always groupable, and items of the 'external' type are never groupable.
+ * This function also ensures that the process of grouping items does not change
+ * their relative order. This requirement may result in multiple groups for the
+ * same type, media, and browsers, if needed to accommodate other items in
+ * between.
+ *
+ * @param $css
+ * An array of CSS items, as returned by drupal_add_css(), but after
+ * alteration performed by drupal_get_css().
+ *
+ * @return
+ * An array of CSS groups. Each group contains the same keys (e.g., 'media',
+ * 'data', etc.) as a CSS item from the $css parameter, with the value of
+ * each key applying to the group as a whole. Each group also contains an
+ * 'items' key, which is the subset of items from $css that are in the group.
+ *
+ * @see drupal_pre_render_styles()
+ * @see system_element_info()
+ */
+function drupal_group_css($css) {
+ $groups = array();
+ // If a group can contain multiple items, we track the information that must
+ // be the same for each item in the group, so that when we iterate the next
+ // item, we can determine if it can be put into the current group, or if a
+ // new group needs to be made for it.
+ $current_group_keys = NULL;
+ // When creating a new group, we pre-increment $i, so by initializing it to
+ // -1, the first group will have index 0.
+ $i = -1;
+ foreach ($css as $item) {
+ // The browsers for which the CSS item needs to be loaded is part of the
+ // information that determines when a new group is needed, but the order of
+ // keys in the array doesn't matter, and we don't want a new group if all
+ // that's different is that order.
+ ksort($item['browsers']);
+
+ // If the item can be grouped with other items, set $group_keys to an array
+ // of information that must be the same for all items in its group. If the
+ // item can't be grouped with other items, set $group_keys to FALSE. We
+ // put items into a group that can be aggregated together: whether they will
+ // be aggregated is up to the _drupal_css_aggregate() function or an
+ // override of that function specified in hook_css_alter(), but regardless
+ // of the details of that function, a group represents items that can be
+ // aggregated. Since a group may be rendered with a single HTML tag, all
+ // items in the group must share the same information that would need to be
+ // part of that HTML tag.
+ switch ($item['type']) {
+ case 'file':
+ // Group file items if their 'preprocess' flag is TRUE.
+ // Help ensure maximum reuse of aggregate files by only grouping
+ // together items that share the same 'group' value and 'every_page'
+ // flag. See drupal_add_css() for details about that.
+ $group_keys = $item['preprocess'] ? array($item['type'], $item['group'], $item['every_page'], $item['media'], $item['browsers']) : FALSE;
+ break;
+ case 'inline':
+ // Always group inline items.
+ $group_keys = array($item['type'], $item['media'], $item['browsers']);
+ break;
+ case 'external':
+ // Do not group external items.
+ $group_keys = FALSE;
+ break;
+ }
+
+ // If the group keys don't match the most recent group we're working with,
+ // then a new group must be made.
+ if ($group_keys !== $current_group_keys) {
+ $i++;
+ // Initialize the new group with the same properties as the first item
+ // being placed into it. The item's 'data' and 'weight' properties are
+ // unique to the item and should not be carried over to the group.
+ $groups[$i] = $item;
+ unset($groups[$i]['data'], $groups[$i]['weight']);
+ $groups[$i]['items'] = array();
+ $current_group_keys = $group_keys ? $group_keys : NULL;
+ }
+
+ // Add the item to the current group.
+ $groups[$i]['items'][] = $item;
+ }
+ return $groups;
+}
+
+/**
+ * Default callback to aggregate CSS files and inline content.
+ *
+ * Having the browser load fewer CSS files results in much faster page loads
+ * than when it loads many small files. This function aggregates files within
+ * the same group into a single file unless the site-wide setting to do so is
+ * disabled (commonly the case during site development). To optimize download,
+ * it also compresses the aggregate files by removing comments, whitespace, and
+ * other unnecessary content. Additionally, this functions aggregates inline
+ * content together, regardless of the site-wide aggregation setting.
+ *
+ * @param $css_groups
+ * An array of CSS groups as returned by drupal_group_css(). This function
+ * modifies the group's 'data' property for each group that is aggregated.
+ *
+ * @see drupal_group_css()
+ * @see drupal_pre_render_styles()
+ * @see system_element_info()
+ */
+function drupal_aggregate_css(&$css_groups) {
+ $preprocess_css = (variable_get('preprocess_css', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update'));
+
+ // For each group that needs aggregation, aggregate its items.
+ foreach ($css_groups as $key => $group) {
+ switch ($group['type']) {
+ // If a file group can be aggregated into a single file, do so, and set
+ // the group's data property to the file path of the aggregate file.
+ case 'file':
+ if ($group['preprocess'] && $preprocess_css) {
+ $css_groups[$key]['data'] = drupal_build_css_cache($group['items']);
+ }
+ break;
+ // Aggregate all inline CSS content into the group's data property.
+ case 'inline':
+ $css_groups[$key]['data'] = '';
+ foreach ($group['items'] as $item) {
+ $css_groups[$key]['data'] .= drupal_load_stylesheet_content($item['data'], $item['preprocess']);
+ }
+ break;
+ }
+ }
+}
+
+/**
+ * #pre_render callback to add the elements needed for CSS tags to be rendered.
+ *
+ * For production websites, LINK tags are preferable to STYLE tags with @import
+ * statements, because:
+ * - They are the standard tag intended for linking to a resource.
+ * - On Firefox 2 and perhaps other browsers, CSS files included with @import
+ * statements don't get saved when saving the complete web page for offline
+ * use: http://drupal.org/node/145218.
+ * - On IE, if only LINK tags and no @import statements are used, all the CSS
+ * files are downloaded in parallel, resulting in faster page load, but if
+ * @import statements are used and span across multiple STYLE tags, all the
+ * ones from one STYLE tag must be downloaded before downloading begins for
+ * the next STYLE tag. Furthermore, IE7 does not support media declaration on
+ * the @import statement, so multiple STYLE tags must be used when different
+ * files are for different media types. Non-IE browsers always download in
+ * parallel, so this is an IE-specific performance quirk:
+ * http://www.stevesouders.com/blog/2009/04/09/dont-use-import/.
+ *
+ * However, IE has an annoying limit of 31 total CSS inclusion tags
+ * (http://drupal.org/node/228818) and LINK tags are limited to one file per
+ * tag, whereas STYLE tags can contain multiple @import statements allowing
+ * multiple files to be loaded per tag. When CSS aggregation is disabled, a
+ * Drupal site can easily have more than 31 CSS files that need to be loaded, so
+ * using LINK tags exclusively would result in a site that would display
+ * incorrectly in IE. Depending on different needs, different strategies can be
+ * employed to decide when to use LINK tags and when to use STYLE tags.
+ *
+ * The strategy employed by this function is to use LINK tags for all aggregate
+ * files and for all files that cannot be aggregated (e.g., if 'preprocess' is
+ * set to FALSE or the type is 'external'), and to use STYLE tags for groups
+ * of files that could be aggregated together but aren't (e.g., if the site-wide
+ * aggregation setting is disabled). This results in all LINK tags when
+ * aggregation is enabled, a guarantee that as many or only slightly more tags
+ * are used with aggregation disabled than enabled (so that if the limit were to
+ * be crossed with aggregation enabled, the site developer would also notice the
+ * problem while aggregation is disabled), and an easy way for a developer to
+ * view HTML source while aggregation is disabled and know what files will be
+ * aggregated together when aggregation becomes enabled.
+ *
+ * This function evaluates the aggregation enabled/disabled condition on a group
+ * by group basis by testing whether an aggregate file has been made for the
+ * group rather than by testing the site-wide aggregation setting. This allows
+ * this function to work correctly even if modules have implemented custom
+ * logic for grouping and aggregating files.
+ *
+ * @param $element
+ * A render array containing:
+ * - '#items': The CSS items as returned by drupal_add_css() and altered by
+ * drupal_get_css().
+ * - '#group_callback': A function to call to group #items to enable the use
+ * of fewer tags by aggregating files and/or using multiple @import
+ * statements within a single tag.
+ * - '#aggregate_callback': A function to call to aggregate the items within
+ * the groups arranged by the #group_callback function.
+ *
+ * @return
+ * A render array that will render to a string of XHTML CSS tags.
+ *
+ * @see drupal_get_css()
+ */
+function drupal_pre_render_styles($elements) {
+ // Group and aggregate the items.
+ if (isset($elements['#group_callback'])) {
+ $elements['#groups'] = $elements['#group_callback']($elements['#items']);
+ }
+ if (isset($elements['#aggregate_callback'])) {
+ $elements['#aggregate_callback']($elements['#groups']);
+ }
+
+ // A dummy query-string is added to filenames, to gain control over
+ // browser-caching. The string changes on every update or full cache
+ // flush, forcing browsers to load a new copy of the files, as the
+ // URL changed.
+ $query_string = variable_get('css_js_query_string', '0');
+
+ // For inline CSS to validate as XHTML, all CSS containing XHTML needs to be
+ // wrapped in CDATA. To make that backwards compatible with HTML 4, we need to
+ // comment out the CDATA-tag.
+ $embed_prefix = "\n\n";
+
+ // Defaults for LINK and STYLE elements.
+ $link_element_defaults = array(
+ '#type' => 'html_tag',
+ '#tag' => 'link',
+ '#attributes' => array(
+ 'type' => 'text/css',
+ 'rel' => 'stylesheet',
+ ),
+ );
+ $style_element_defaults = array(
+ '#type' => 'html_tag',
+ '#tag' => 'style',
+ '#attributes' => array(
+ 'type' => 'text/css',
+ ),
+ );
+
+ // Loop through each group.
+ foreach ($elements['#groups'] as $group) {
+ switch ($group['type']) {
+ // For file items, there are three possibilites.
+ // - The group has been aggregated: in this case, output a LINK tag for
+ // the aggregate file.
+ // - The group can be aggregated but has not been (most likely because
+ // the site administrator disabled the site-wide setting): in this case,
+ // output as few STYLE tags for the group as possible, using @import
+ // statement for each file in the group. This enables us to stay within
+ // IE's limit of 31 total CSS inclusion tags.
+ // - The group contains items not eligible for aggregation (their
+ // 'preprocess' flag has been set to FALSE): in this case, output a LINK
+ // tag for each file.
+ case 'file':
+ // The group has been aggregated into a single file: output a LINK tag
+ // for the aggregate file.
+ if (isset($group['data'])) {
+ $element = $link_element_defaults;
+ $element['#attributes']['href'] = file_create_url($group['data']);
+ $element['#attributes']['media'] = $group['media'];
+ $element['#browsers'] = $group['browsers'];
+ $elements[] = $element;
+ }
+ // The group can be aggregated, but hasn't been: combine multiple items
+ // into as few STYLE tags as possible.
+ elseif ($group['preprocess']) {
+ $import = array();
+ foreach ($group['items'] as $item) {
+ // A theme's .info file may have an entry for a file that doesn't
+ // exist as a way of overriding a module or base theme CSS file from
+ // being added to the page. Normally, file_exists() calls that need
+ // to run for every page request should be minimized, but this one
+ // is okay, because it only runs when CSS aggregation is disabled.
+ // On a server under heavy enough load that file_exists() calls need
+ // to be minimized, CSS aggregation should be enabled, in which case
+ // this code is not run. When aggregation is enabled,
+ // drupal_load_stylesheet() checks file_exists(), but only when
+ // building the aggregate file, which is then reused for many page
+ // requests.
+ if (file_exists($item['data'])) {
+ // The dummy query string needs to be added to the URL to control
+ // browser-caching. IE7 does not support a media type on the
+ // @import statement, so we instead specify the media for the
+ // group on the STYLE tag.
+ $import[] = '@import url("' . check_plain(file_create_url($item['data']) . '?' . $query_string) . '");';
+ }
+ }
+ // In addition to IE's limit of 31 total CSS inclusion tags, it also
+ // has a limit of 31 @import statements per STYLE tag.
+ while (!empty($import)) {
+ $import_batch = array_slice($import, 0, 31);
+ $import = array_slice($import, 31);
+ $element = $style_element_defaults;
+ // This simplifies the JavaScript regex, allowing each line
+ // (separated by \n) to be treated as a completely different string.
+ // This means that we can use ^ and $ on one line at a time, and not
+ // worry about style tags since they'll never match the regex.
+ $element['#value'] = "\n" . implode("\n", $import_batch) . "\n";
+ $element['#attributes']['media'] = $group['media'];
+ $element['#browsers'] = $group['browsers'];
+ $elements[] = $element;
+ }
+ }
+ // The group contains items ineligible for aggregation: output a LINK
+ // tag for each file.
+ else {
+ foreach ($group['items'] as $item) {
+ $element = $link_element_defaults;
+ // We do not check file_exists() here, because this code runs for
+ // files whose 'preprocess' is set to FALSE, and therefore, even
+ // when aggregation is enabled, and we want to avoid needlessly
+ // taxing a server that may be under heavy load. The file_exists()
+ // performed above for files whose 'preprocess' is TRUE is done for
+ // the benefit of theme .info files, but code that deals with files
+ // whose 'preprocess' is FALSE is responsible for ensuring the file
+ // exists.
+ // The dummy query string needs to be added to the URL to control
+ // browser-caching.
+ $query_string_separator = (strpos($item['data'], '?') !== FALSE) ? '&' : '?';
+ $element['#attributes']['href'] = file_create_url($item['data']) . $query_string_separator . $query_string;
+ $element['#attributes']['media'] = $item['media'];
+ $element['#browsers'] = $group['browsers'];
+ $elements[] = $element;
+ }
+ }
+ break;
+ // For inline content, the 'data' property contains the CSS content. If
+ // the group's 'data' property is set, then output it in a single STYLE
+ // tag. Otherwise, output a separate STYLE tag for each item.
+ case 'inline':
+ if (isset($group['data'])) {
+ $element = $style_element_defaults;
+ $element['#value'] = $group['data'];
+ $element['#value_prefix'] = $embed_prefix;
+ $element['#value_suffix'] = $embed_suffix;
+ $element['#attributes']['media'] = $group['media'];
+ $element['#browsers'] = $group['browsers'];
+ $elements[] = $element;
+ }
+ else {
+ foreach ($group['items'] as $item) {
+ $element = $style_element_defaults;
+ $element['#value'] = $item['data'];
+ $element['#value_prefix'] = $embed_prefix;
+ $element['#value_suffix'] = $embed_suffix;
+ $element['#attributes']['media'] = $item['media'];
+ $element['#browsers'] = $group['browsers'];
+ $elements[] = $element;
+ }
+ }
+ break;
+ // Output a LINK tag for each external item. The item's 'data' property
+ // contains the full URL.
+ case 'external':
+ foreach ($group['items'] as $item) {
+ $element = $link_element_defaults;
+ $element['#attributes']['href'] = $item['data'];
+ $element['#attributes']['media'] = $item['media'];
+ $element['#browsers'] = $group['browsers'];
+ $elements[] = $element;
+ }
+ break;
+ }
+ }
+
+ return $elements;
+}
+
+/**
+ * Aggregates and optimizes CSS files into a cache file in the files directory.
+ *
+ * The file name for the CSS cache file is generated from the hash of the
+ * aggregated contents of the files in $css. This forces proxies and browsers
+ * to download new CSS when the CSS changes.
+ *
+ * The cache file name is retrieved on a page load via a lookup variable that
+ * contains an associative array. The array key is the hash of the file names
+ * in $css while the value is the cache file name. The cache file is generated
+ * in two cases. First, if there is no file name value for the key, which will
+ * happen if a new file name has been added to $css or after the lookup
+ * variable is emptied to force a rebuild of the cache. Second, the cache file
+ * is generated if it is missing on disk. Old cache files are not deleted
+ * immediately when the lookup variable is emptied, but are deleted after a set
+ * period by drupal_delete_file_if_stale(). This ensures that files referenced
+ * by a cached page will still be available.
+ *
+ * @param $css
+ * An array of CSS files to aggregate and compress into one file.
+ *
+ * @return
+ * The URI of the CSS cache file, or FALSE if the file could not be saved.
+ */
+function drupal_build_css_cache($css) {
+ $data = '';
+ $uri = '';
+ $map = variable_get('drupal_css_cache_files', array());
+ // Create a new array so that only the file names are used to create the hash.
+ // This prevents new aggregates from being created unnecessarily.
+ $css_data = array();
+ foreach ($css as $css_file) {
+ $css_data[] = $css_file['data'];
+ }
+ $key = hash('sha256', serialize($css_data));
+ if (isset($map[$key])) {
+ $uri = $map[$key];
+ }
+
+ if (empty($uri) || !drupal_aggregated_file_exists($uri)) {
+ // Build aggregate CSS file.
+ foreach ($css as $stylesheet) {
+ // Only 'file' stylesheets can be aggregated.
+ if ($stylesheet['type'] == 'file') {
+ $contents = drupal_load_stylesheet($stylesheet['data'], TRUE);
+
+ // Build the base URL of this CSS file: start with the full URL.
+ $css_base_url = file_create_url($stylesheet['data']);
+ // Move to the parent.
+ $css_base_url = substr($css_base_url, 0, strrpos($css_base_url, '/'));
+ // Simplify to a relative URL if the stylesheet URL starts with the
+ // base URL of the website.
+ if (substr($css_base_url, 0, strlen($GLOBALS['base_root'])) == $GLOBALS['base_root']) {
+ $css_base_url = substr($css_base_url, strlen($GLOBALS['base_root']));
+ }
+
+ _drupal_build_css_path(NULL, $css_base_url . '/');
+ // Anchor all paths in the CSS with its base URL, ignoring external and absolute paths.
+ $data .= preg_replace_callback('/url\(\s*[\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\s*\)/i', '_drupal_build_css_path', $contents);
+ }
+ }
+
+ // Per the W3C specification at http://www.w3.org/TR/REC-CSS2/cascade.html#at-import,
+ // @import rules must proceed any other style, so we move those to the top.
+ $regexp = '/@import[^;]+;/i';
+ preg_match_all($regexp, $data, $matches);
+ $data = preg_replace($regexp, '', $data);
+ $data = implode('', $matches[0]) . $data;
+
+ // Prefix filename to prevent blocking by firewalls which reject files
+ // starting with "ad*".
+ $filename = 'css_' . drupal_hash_base64($data) . '.css';
+ // Create the css/ within the files folder.
+ $csspath = 'public://css';
+ $uri = $csspath . '/' . $filename;
+ // Create the CSS file.
+ file_prepare_directory($csspath, FILE_CREATE_DIRECTORY);
+ if (!file_exists($uri) && !file_unmanaged_save_data($data, $uri, FILE_EXISTS_REPLACE)) {
+ return FALSE;
+ }
+ // If CSS gzip compression is enabled, clean URLs are enabled (which means
+ // that rewrite rules are working) and the zlib extension is available then
+ // create a gzipped version of this file. This file is served conditionally
+ // to browsers that accept gzip using .htaccess rules.
+ if (variable_get('css_gzip_compression', TRUE) && variable_get('clean_url', 0) && extension_loaded('zlib')) {
+ if (!file_exists($uri . '.gz') && !file_unmanaged_save_data(gzencode($data, 9, FORCE_GZIP), $uri . '.gz', FILE_EXISTS_REPLACE)) {
+ return FALSE;
+ }
+ }
+ // Save the updated map.
+ $map[$key] = $uri;
+ variable_set('drupal_css_cache_files', $map);
+ }
+ return $uri;
+}
+
+/**
+ * Prefixes all paths within a CSS file for drupal_build_css_cache().
+ */
+function _drupal_build_css_path($matches, $base = NULL) {
+ $_base = &drupal_static(__FUNCTION__);
+ // Store base path for preg_replace_callback.
+ if (isset($base)) {
+ $_base = $base;
+ }
+
+ // Prefix with base and remove '../' segments where possible.
+ $path = $_base . $matches[1];
+ $last = '';
+ while ($path != $last) {
+ $last = $path;
+ $path = preg_replace('`(^|/)(?!\.\./)([^/]+)/\.\./`', '$1', $path);
+ }
+ return 'url(' . $path . ')';
+}
+
+/**
+ * Loads the stylesheet and resolves all @import commands.
+ *
+ * Loads a stylesheet and replaces @import commands with the contents of the
+ * imported file. Use this instead of file_get_contents when processing
+ * stylesheets.
+ *
+ * The returned contents are compressed removing white space and comments only
+ * when CSS aggregation is enabled. This optimization will not apply for
+ * color.module enabled themes with CSS aggregation turned off.
+ *
+ * @param $file
+ * Name of the stylesheet to be processed.
+ * @param $optimize
+ * Defines if CSS contents should be compressed or not.
+ * @param $reset_basepath
+ * Used internally to facilitate recursive resolution of @import commands.
+ *
+ * @return
+ * Contents of the stylesheet, including any resolved @import commands.
+ */
+function drupal_load_stylesheet($file, $optimize = NULL, $reset_basepath = TRUE) {
+ // These statics are not cache variables, so we don't use drupal_static().
+ static $_optimize, $basepath;
+ if ($reset_basepath) {
+ $basepath = '';
+ }
+ // Store the value of $optimize for preg_replace_callback with nested
+ // @import loops.
+ if (isset($optimize)) {
+ $_optimize = $optimize;
+ }
+
+ // Stylesheets are relative one to each other. Start by adding a base path
+ // prefix provided by the parent stylesheet (if necessary).
+ if ($basepath && !file_uri_scheme($file)) {
+ $file = $basepath . '/' . $file;
+ }
+ // Store the parent base path to restore it later.
+ $parent_base_path = $basepath;
+ // Set the current base path to process possible child imports.
+ $basepath = dirname($file);
+
+ // Load the CSS stylesheet. We suppress errors because themes may specify
+ // stylesheets in their .info file that don't exist in the theme's path,
+ // but are merely there to disable certain module CSS files.
+ $content = '';
+ if ($contents = @file_get_contents($file)) {
+ // Return the processed stylesheet.
+ $content = drupal_load_stylesheet_content($contents, $_optimize);
+ }
+
+ // Restore the parent base path as the file and its childen are processed.
+ $basepath = $parent_base_path;
+ return $content;
+}
+
+/**
+ * Processes the contents of a stylesheet for aggregation.
+ *
+ * @param $contents
+ * The contents of the stylesheet.
+ * @param $optimize
+ * (optional) Boolean whether CSS contents should be minified. Defaults to
+ * FALSE.
+ *
+ * @return
+ * Contents of the stylesheet including the imported stylesheets.
+ */
+function drupal_load_stylesheet_content($contents, $optimize = FALSE) {
+ // Remove multiple charset declarations for standards compliance (and fixing Safari problems).
+ $contents = preg_replace('/^@charset\s+[\'"](\S*?)\b[\'"];/i', '', $contents);
+
+ if ($optimize) {
+ // Perform some safe CSS optimizations.
+ // Regexp to match comment blocks.
+ $comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/';
+ // Regexp to match double quoted strings.
+ $double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"';
+ // Regexp to match single quoted strings.
+ $single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'";
+ // Strip all comment blocks, but keep double/single quoted strings.
+ $contents = preg_replace(
+ "<($double_quot|$single_quot)|$comment>Ss",
+ "$1",
+ $contents
+ );
+ // Remove certain whitespace.
+ // There are different conditions for removing leading and trailing
+ // whitespace.
+ // @see http://php.net/manual/regexp.reference.subpatterns.php
+ $contents = preg_replace('<
+ # Strip leading and trailing whitespace.
+ \s*([@{};,])\s*
+ # Strip only leading whitespace from:
+ # - Closing parenthesis: Retain "@media (bar) and foo".
+ | \s+([\)])
+ # Strip only trailing whitespace from:
+ # - Opening parenthesis: Retain "@media (bar) and foo".
+ # - Colon: Retain :pseudo-selectors.
+ | ([\(:])\s+
+ >xS',
+ // Only one of the three capturing groups will match, so its reference
+ // will contain the wanted value and the references for the
+ // two non-matching groups will be replaced with empty strings.
+ '$1$2$3',
+ $contents
+ );
+ // End the file with a new line.
+ $contents = trim($contents);
+ $contents .= "\n";
+ }
+
+ // Replaces @import commands with the actual stylesheet content.
+ // This happens recursively but omits external files.
+ $contents = preg_replace_callback('/@import\s*(?:url\(\s*)?[\'"]?(?![a-z]+:)(?!\/\/)([^\'"\()]+)[\'"]?\s*\)?\s*;/', '_drupal_load_stylesheet', $contents);
+ return $contents;
+}
+
+/**
+ * Loads stylesheets recursively and returns contents with corrected paths.
+ *
+ * This function is used for recursive loading of stylesheets and
+ * returns the stylesheet content with all url() paths corrected.
+ */
+function _drupal_load_stylesheet($matches) {
+ $filename = $matches[1];
+ // Load the imported stylesheet and replace @import commands in there as well.
+ $file = drupal_load_stylesheet($filename, NULL, FALSE);
+
+ // Determine the file's directory.
+ $directory = dirname($filename);
+ // If the file is in the current directory, make sure '.' doesn't appear in
+ // the url() path.
+ $directory = $directory == '.' ? '' : $directory .'/';
+
+ // Alter all internal url() paths. Leave external paths alone. We don't need
+ // to normalize absolute paths here (i.e. remove folder/... segments) because
+ // that will be done later.
+ return preg_replace('/url\(\s*([\'"]?)(?![a-z]+:|\/+)([^\'")]+)([\'"]?)\s*\)/i', 'url(\1' . $directory . '\2\3)', $file);
+}
+
+/**
+ * Deletes old cached CSS files.
+ */
+function drupal_clear_css_cache() {
+ variable_del('drupal_css_cache_files');
+ file_scan_directory('public://css', '/.*/', array('callback' => 'drupal_delete_file_if_stale'));
+}
+
+/**
+ * Callback to delete files modified more than a set time ago.
+ */
+function drupal_delete_file_if_stale($uri) {
+ // Default stale file threshold is 30 days.
+ if (REQUEST_TIME - filemtime($uri) > variable_get('drupal_stale_file_threshold', 2592000)) {
+ file_unmanaged_delete($uri);
+ }
+}
+
+/**
+ * Prepares a string for use as a CSS identifier (element, class, or ID name).
+ *
+ * http://www.w3.org/TR/CSS21/syndata.html#characters shows the syntax for valid
+ * CSS identifiers (including element names, classes, and IDs in selectors.)
+ *
+ * @param $identifier
+ * The identifier to clean.
+ * @param $filter
+ * An array of string replacements to use on the identifier.
+ *
+ * @return
+ * The cleaned identifier.
+ */
+function drupal_clean_css_identifier($identifier, $filter = array(' ' => '-', '_' => '-', '/' => '-', '[' => '-', ']' => '')) {
+ // Use the advanced drupal_static() pattern, since this is called very often.
+ static $drupal_static_fast;
+ if (!isset($drupal_static_fast)) {
+ $drupal_static_fast['allow_css_double_underscores'] = &drupal_static(__FUNCTION__ . ':allow_css_double_underscores');
+ }
+ $allow_css_double_underscores = &$drupal_static_fast['allow_css_double_underscores'];
+ if (!isset($allow_css_double_underscores)) {
+ $allow_css_double_underscores = variable_get('allow_css_double_underscores', FALSE);
+ }
+
+ // Preserve BEM-style double-underscores depending on custom setting.
+ if ($allow_css_double_underscores) {
+ $filter['__'] = '__';
+ }
+
+ // By default, we filter using Drupal's coding standards.
+ $identifier = strtr($identifier, $filter);
+
+ // Valid characters in a CSS identifier are:
+ // - the hyphen (U+002D)
+ // - a-z (U+0030 - U+0039)
+ // - A-Z (U+0041 - U+005A)
+ // - the underscore (U+005F)
+ // - 0-9 (U+0061 - U+007A)
+ // - ISO 10646 characters U+00A1 and higher
+ // We strip out any character not in the above list.
+ $identifier = preg_replace('/[^\x{002D}\x{0030}-\x{0039}\x{0041}-\x{005A}\x{005F}\x{0061}-\x{007A}\x{00A1}-\x{FFFF}]/u', '', $identifier);
+
+ return $identifier;
+}
+
+/**
+ * Prepares a string for use as a valid class name.
+ *
+ * Do not pass one string containing multiple classes as they will be
+ * incorrectly concatenated with dashes, i.e. "one two" will become "one-two".
+ *
+ * @param $class
+ * The class name to clean.
+ *
+ * @return
+ * The cleaned class name.
+ */
+function drupal_html_class($class) {
+ // The output of this function will never change, so this uses a normal
+ // static instead of drupal_static().
+ static $classes = array();
+
+ if (!isset($classes[$class])) {
+ $classes[$class] = drupal_clean_css_identifier(drupal_strtolower($class));
+ }
+ return $classes[$class];
+}
+
+/**
+ * Prepares a string for use as a valid HTML ID and guarantees uniqueness.
+ *
+ * This function ensures that each passed HTML ID value only exists once on the
+ * page. By tracking the already returned ids, this function enables forms,
+ * blocks, and other content to be output multiple times on the same page,
+ * without breaking (X)HTML validation.
+ *
+ * For already existing IDs, a counter is appended to the ID string. Therefore,
+ * JavaScript and CSS code should not rely on any value that was generated by
+ * this function and instead should rely on manually added CSS classes or
+ * similarly reliable constructs.
+ *
+ * Two consecutive hyphens separate the counter from the original ID. To manage
+ * uniqueness across multiple Ajax requests on the same page, Ajax requests
+ * POST an array of all IDs currently present on the page, which are used to
+ * prime this function's cache upon first invocation.
+ *
+ * To allow reverse-parsing of IDs submitted via Ajax, any multiple consecutive
+ * hyphens in the originally passed $id are replaced with a single hyphen.
+ *
+ * @param $id
+ * The ID to clean.
+ *
+ * @return
+ * The cleaned ID.
+ */
+function drupal_html_id($id) {
+ // If this is an Ajax request, then content returned by this page request will
+ // be merged with content already on the base page. The HTML IDs must be
+ // unique for the fully merged content. Therefore, initialize $seen_ids to
+ // take into account IDs that are already in use on the base page.
+ static $drupal_static_fast;
+ if (!isset($drupal_static_fast['seen_ids_init'])) {
+ $drupal_static_fast['seen_ids_init'] = &drupal_static(__FUNCTION__ . ':init');
+ }
+ $seen_ids_init = &$drupal_static_fast['seen_ids_init'];
+ if (!isset($seen_ids_init)) {
+ // Ideally, Drupal would provide an API to persist state information about
+ // prior page requests in the database, and we'd be able to add this
+ // function's $seen_ids static variable to that state information in order
+ // to have it properly initialized for this page request. However, no such
+ // page state API exists, so instead, ajax.js adds all of the in-use HTML
+ // IDs to the POST data of Ajax submissions. Direct use of $_POST is
+ // normally not recommended as it could open up security risks, but because
+ // the raw POST data is cast to a number before being returned by this
+ // function, this usage is safe.
+ if (empty($_POST['ajax_html_ids'])) {
+ $seen_ids_init = array();
+ }
+ else {
+ // This function ensures uniqueness by appending a counter to the base id
+ // requested by the calling function after the first occurrence of that
+ // requested id. $_POST['ajax_html_ids'] contains the ids as they were
+ // returned by this function, potentially with the appended counter, so
+ // we parse that to reconstruct the $seen_ids array.
+ if (isset($_POST['ajax_html_ids'][0]) && strpos($_POST['ajax_html_ids'][0], ',') === FALSE) {
+ $ajax_html_ids = $_POST['ajax_html_ids'];
+ }
+ else {
+ // jquery.form.js may send the server a comma-separated string as the
+ // first element of an array (see http://drupal.org/node/1575060), so
+ // we need to convert it to an array in that case.
+ $ajax_html_ids = explode(',', $_POST['ajax_html_ids'][0]);
+ }
+ foreach ($ajax_html_ids as $seen_id) {
+ // We rely on '--' being used solely for separating a base id from the
+ // counter, which this function ensures when returning an id.
+ $parts = explode('--', $seen_id, 2);
+ if (!empty($parts[1]) && is_numeric($parts[1])) {
+ list($seen_id, $i) = $parts;
+ }
+ else {
+ $i = 1;
+ }
+ if (!isset($seen_ids_init[$seen_id]) || ($i > $seen_ids_init[$seen_id])) {
+ $seen_ids_init[$seen_id] = $i;
+ }
+ }
+ }
+ }
+ if (!isset($drupal_static_fast['seen_ids'])) {
+ $drupal_static_fast['seen_ids'] = &drupal_static(__FUNCTION__, $seen_ids_init);
+ }
+ $seen_ids = &$drupal_static_fast['seen_ids'];
+
+ $id = strtr(drupal_strtolower($id), array(' ' => '-', '_' => '-', '[' => '-', ']' => ''));
+
+ // As defined in http://www.w3.org/TR/html4/types.html#type-name, HTML IDs can
+ // only contain letters, digits ([0-9]), hyphens ("-"), underscores ("_"),
+ // colons (":"), and periods ("."). We strip out any character not in that
+ // list. Note that the CSS spec doesn't allow colons or periods in identifiers
+ // (http://www.w3.org/TR/CSS21/syndata.html#characters), so we strip those two
+ // characters as well.
+ $id = preg_replace('/[^A-Za-z0-9\-_]/', '', $id);
+
+ // Removing multiple consecutive hyphens.
+ $id = preg_replace('/\-+/', '-', $id);
+ // Ensure IDs are unique by appending a counter after the first occurrence.
+ // The counter needs to be appended with a delimiter that does not exist in
+ // the base ID. Requiring a unique delimiter helps ensure that we really do
+ // return unique IDs and also helps us re-create the $seen_ids array during
+ // Ajax requests.
+ if (isset($seen_ids[$id])) {
+ $id = $id . '--' . ++$seen_ids[$id];
+ }
+ else {
+ $seen_ids[$id] = 1;
+ }
+
+ return $id;
+}
+
+/**
+ * Provides a standard HTML class name that identifies a page region.
+ *
+ * It is recommended that template preprocess functions apply this class to any
+ * page region that is output by the theme (Drupal core already handles this in
+ * the standard template preprocess implementation). Standardizing the class
+ * names in this way allows modules to implement certain features, such as
+ * drag-and-drop or dynamic Ajax loading, in a theme-independent way.
+ *
+ * @param $region
+ * The name of the page region (for example, 'page_top' or 'content').
+ *
+ * @return
+ * An HTML class that identifies the region (for example, 'region-page-top'
+ * or 'region-content').
+ *
+ * @see template_preprocess_region()
+ */
+function drupal_region_class($region) {
+ return drupal_html_class("region-$region");
+}
+
+/**
+ * Adds a JavaScript file, setting, or inline code to the page.
+ *
+ * The behavior of this function depends on the parameters it is called with.
+ * Generally, it handles the addition of JavaScript to the page, either as
+ * reference to an existing file or as inline code. The following actions can be
+ * performed using this function:
+ * - Add a file ('file'): Adds a reference to a JavaScript file to the page.
+ * - Add inline JavaScript code ('inline'): Executes a piece of JavaScript code
+ * on the current page by placing the code directly in the page (for example,
+ * to tell the user that a new message arrived, by opening a pop up, alert
+ * box, etc.). This should only be used for JavaScript that cannot be executed
+ * from a file. When adding inline code, make sure that you are not relying on
+ * $() being the jQuery function. Wrap your code in
+ * @code (function ($) {... })(jQuery); @endcode
+ * or use jQuery() instead of $().
+ * - Add external JavaScript ('external'): Allows the inclusion of external
+ * JavaScript files that are not hosted on the local server. Note that these
+ * external JavaScript references do not get aggregated when preprocessing is
+ * on.
+ * - Add settings ('setting'): Adds settings to Drupal's global storage of
+ * JavaScript settings. Per-page settings are required by some modules to
+ * function properly. All settings will be accessible at Drupal.settings.
+ *
+ * Examples:
+ * @code
+ * drupal_add_js('misc/collapse.js');
+ * drupal_add_js('misc/collapse.js', 'file');
+ * drupal_add_js('jQuery(document).ready(function () { alert("Hello!"); });', 'inline');
+ * drupal_add_js('jQuery(document).ready(function () { alert("Hello!"); });',
+ * array('type' => 'inline', 'scope' => 'footer', 'weight' => 5)
+ * );
+ * drupal_add_js('http://example.com/example.js', 'external');
+ * drupal_add_js(array('myModule' => array('key' => 'value')), 'setting');
+ * @endcode
+ *
+ * Calling drupal_static_reset('drupal_add_js') will clear all JavaScript added
+ * so far.
+ *
+ * If JavaScript aggregation is enabled, all JavaScript files added with
+ * $options['preprocess'] set to TRUE will be merged into one aggregate file.
+ * Preprocessed inline JavaScript will not be aggregated into this single file.
+ * Externally hosted JavaScripts are never aggregated.
+ *
+ * The reason for aggregating the files is outlined quite thoroughly here:
+ * http://www.die.net/musings/page_load_time/ "Load fewer external objects. Due
+ * to request overhead, one bigger file just loads faster than two smaller ones
+ * half its size."
+ *
+ * $options['preprocess'] should be only set to TRUE when a file is required for
+ * all typical visitors and most pages of a site. It is critical that all
+ * preprocessed files are added unconditionally on every page, even if the
+ * files are not needed on a page. This is normally done by calling
+ * drupal_add_js() in a hook_init() implementation.
+ *
+ * Non-preprocessed files should only be added to the page when they are
+ * actually needed.
+ *
+ * @param $data
+ * (optional) If given, the value depends on the $options parameter, or
+ * $options['type'] if $options is passed as an associative array:
+ * - 'file': Path to the file relative to base_path().
+ * - 'inline': The JavaScript code that should be placed in the given scope.
+ * - 'external': The absolute path to an external JavaScript file that is not
+ * hosted on the local server. These files will not be aggregated if
+ * JavaScript aggregation is enabled.
+ * - 'setting': An associative array with configuration options. The array is
+ * merged directly into Drupal.settings. All modules should wrap their
+ * actual configuration settings in another variable to prevent conflicts in
+ * the Drupal.settings namespace. Items added with a string key will replace
+ * existing settings with that key; items with numeric array keys will be
+ * added to the existing settings array.
+ * @param $options
+ * (optional) A string defining the type of JavaScript that is being added in
+ * the $data parameter ('file'/'setting'/'inline'/'external'), or an
+ * associative array. JavaScript settings should always pass the string
+ * 'setting' only. Other types can have the following elements in the array:
+ * - type: The type of JavaScript that is to be added to the page. Allowed
+ * values are 'file', 'inline', 'external' or 'setting'. Defaults
+ * to 'file'.
+ * - scope: The location in which you want to place the script. Possible
+ * values are 'header' or 'footer'. If your theme implements different
+ * regions, you can also use these. Defaults to 'header'.
+ * - group: A number identifying the group in which to add the JavaScript.
+ * Available constants are:
+ * - JS_LIBRARY: Any libraries, settings, or jQuery plugins.
+ * - JS_DEFAULT: Any module-layer JavaScript.
+ * - JS_THEME: Any theme-layer JavaScript.
+ * The group number serves as a weight: JavaScript within a lower weight
+ * group is presented on the page before JavaScript within a higher weight
+ * group.
+ * - every_page: For optimal front-end performance when aggregation is
+ * enabled, this should be set to TRUE if the JavaScript is present on every
+ * page of the website for users for whom it is present at all. This
+ * defaults to FALSE. It is set to TRUE for JavaScript files that are added
+ * via module and theme .info files. Modules that add JavaScript within
+ * hook_init() implementations, or from other code that ensures that the
+ * JavaScript is added to all website pages, should also set this flag to
+ * TRUE. All JavaScript files within the same group and that have the
+ * 'every_page' flag set to TRUE and do not have 'preprocess' set to FALSE
+ * are aggregated together into a single aggregate file, and that aggregate
+ * file can be reused across a user's entire site visit, leading to faster
+ * navigation between pages. However, JavaScript that is only needed on
+ * pages less frequently visited, can be added by code that only runs for
+ * those particular pages, and that code should not set the 'every_page'
+ * flag. This minimizes the size of the aggregate file that the user needs
+ * to download when first visiting the website. JavaScript without the
+ * 'every_page' flag is aggregated into a separate aggregate file. This
+ * other aggregate file is likely to change from page to page, and each new
+ * aggregate file needs to be downloaded when first encountered, so it
+ * should be kept relatively small by ensuring that most commonly needed
+ * JavaScript is added to every page.
+ * - weight: A number defining the order in which the JavaScript is added to
+ * the page relative to other JavaScript with the same 'scope', 'group',
+ * and 'every_page' value. In some cases, the order in which the JavaScript
+ * is presented on the page is very important. jQuery, for example, must be
+ * added to the page before any jQuery code is run, so jquery.js uses the
+ * JS_LIBRARY group and a weight of -20, jquery.once.js (a library drupal.js
+ * depends on) uses the JS_LIBRARY group and a weight of -19, drupal.js uses
+ * the JS_LIBRARY group and a weight of -1, other libraries use the
+ * JS_LIBRARY group and a weight of 0 or higher, and all other scripts use
+ * one of the other group constants. The exact ordering of JavaScript is as
+ * follows:
+ * - First by scope, with 'header' first, 'footer' last, and any other
+ * scopes provided by a custom theme coming in between, as determined by
+ * the theme.
+ * - Then by group.
+ * - Then by the 'every_page' flag, with TRUE coming before FALSE.
+ * - Then by weight.
+ * - Then by the order in which the JavaScript was added. For example, all
+ * else being the same, JavaScript added by a call to drupal_add_js() that
+ * happened later in the page request gets added to the page after one for
+ * which drupal_add_js() happened earlier in the page request.
+ * - requires_jquery: Set this to FALSE if the JavaScript you are adding does
+ * not have a dependency on jQuery. Defaults to TRUE, except for JavaScript
+ * settings where it defaults to FALSE. This is used on sites that have the
+ * 'javascript_always_use_jquery' variable set to FALSE; on those sites, if
+ * all the JavaScript added to the page by drupal_add_js() does not have a
+ * dependency on jQuery, then for improved front-end performance Drupal
+ * will not add jQuery and related libraries and settings to the page.
+ * - defer: If set to TRUE, the defer attribute is set on the ",rE:!0,sL:["actionscript","javascript","handlebars"]}},c,{cN:"pi",b:/<\?\w+/,e:/\?>/,r:10},{cN:"tag",b:"?",e:"/?>",c:[{cN:"title",b:/[^ \/><\n\t]+/,r:0},e]}]}});hljs.registerLanguage("css",function(e){var c="[a-zA-Z-][a-zA-Z0-9_-]*",a={cN:"function",b:c+"\\(",rB:!0,eE:!0,e:"\\("},r={cN:"rule",b:/[A-Z\_\.\-]+\s*:/,rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:/\S/,e:":",eE:!0,starts:{cN:"value",eW:!0,eE:!0,c:[a,e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"hexcolor",b:"#[0-9A-Fa-f]+"},{cN:"important",b:"!important"}]}}]};return{cI:!0,i:/[=\/|'\$]/,c:[e.CBCM,r,{cN:"id",b:/\#[A-Za-z0-9_-]+/},{cN:"class",b:/\.[A-Za-z0-9_-]+/},{cN:"attr_selector",b:/\[/,e:/\]/,i:"$"},{cN:"pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"']+/},{cN:"at_rule",b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{cN:"at_rule",b:"@",e:"[{;]",c:[{cN:"keyword",b:/\S+/},{b:/\s/,eW:!0,eE:!0,r:0,c:[a,e.ASM,e.QSM,e.CSSNM]}]},{cN:"tag",b:c,r:0},{cN:"rules",b:"{",e:"}",i:/\S/,c:[e.CBCM,r]}]}});hljs.registerLanguage("perl",function(e){var t="getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qqfileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmgetsub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedirioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when",r={cN:"subst",b:"[$@]\\{",e:"\\}",k:t},s={b:"->{",e:"}"},n={cN:"variable",v:[{b:/\$\d/},{b:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{b:/[\$%@][^\s\w{]/,r:0}]},o=[e.BE,r,n],i=[n,e.HCM,e.C("^\\=\\w","\\=cut",{eW:!0}),s,{cN:"string",c:o,v:[{b:"q[qwxr]?\\s*\\(",e:"\\)",r:5},{b:"q[qwxr]?\\s*\\[",e:"\\]",r:5},{b:"q[qwxr]?\\s*\\{",e:"\\}",r:5},{b:"q[qwxr]?\\s*\\|",e:"\\|",r:5},{b:"q[qwxr]?\\s*\\<",e:"\\>",r:5},{b:"qw\\s+q",e:"q",r:5},{b:"'",e:"'",c:[e.BE]},{b:'"',e:'"'},{b:"`",e:"`",c:[e.BE]},{b:"{\\w+}",c:[],r:0},{b:"-?\\w+\\s*\\=\\>",c:[],r:0}]},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\/\\/|"+e.RSR+"|\\b(split|return|print|reverse|grep)\\b)\\s*",k:"split return print reverse grep",r:0,c:[e.HCM,{cN:"regexp",b:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",r:10},{cN:"regexp",b:"(m|qr)?/",e:"/[a-z]*",c:[e.BE],r:0}]},{cN:"sub",bK:"sub",e:"(\\s*\\(.*?\\))?[;{]",r:5},{cN:"operator",b:"-\\w\\b",r:0},{b:"^__DATA__$",e:"^__END__$",sL:"mojolicious",c:[{b:"^@@.*",e:"$",cN:"comment"}]}];return r.c=i,s.c=i,{aliases:["pl"],k:t,c:i}});hljs.registerLanguage("cs",function(e){var r="abstract as base bool break byte case catch char checked const continue decimal dynamic default delegate do double else enum event explicit extern false finally fixed float for foreach goto if implicit in int interface internal is lock long null when object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this true try typeof uint ulong unchecked unsafe ushort using virtual volatile void while async protected public private internal ascending descending from get group into join let orderby partial select set value var where yield",t=e.IR+"(<"+e.IR+">)?";return{aliases:["csharp"],k:r,i:/::/,c:[e.C("///","$",{rB:!0,c:[{cN:"xmlDocTag",v:[{b:"///",r:0},{b:""},{b:"?",e:">"}]}]}),e.CLCM,e.CBCM,{cN:"preprocessor",b:"#",e:"$",k:"if else elif endif define undef warning error line region endregion pragma checksum"},{cN:"string",b:'@"',e:'"',c:[{b:'""'}]},e.ASM,e.QSM,e.CNM,{bK:"class interface",e:/[{;=]/,i:/[^\s:]/,c:[e.TM,e.CLCM,e.CBCM]},{bK:"namespace",e:/[{;=]/,i:/[^\s:]/,c:[{cN:"title",b:"[a-zA-Z](\\.?\\w)*",r:0},e.CLCM,e.CBCM]},{bK:"new return throw await",r:0},{cN:"function",b:"("+t+"\\s+)+"+e.IR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:r,c:[{b:e.IR+"\\s*\\(",rB:!0,c:[e.TM],r:0},{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:r,r:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]}]}});hljs.registerLanguage("apache",function(e){var r={cN:"number",b:"[\\$%]\\d+"};return{aliases:["apacheconf"],cI:!0,c:[e.HCM,{cN:"tag",b:"?",e:">"},{cN:"keyword",b:/\w+/,r:0,k:{common:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{e:/$/,r:0,k:{literal:"on off all"},c:[{cN:"sqbracket",b:"\\s\\[",e:"\\]$"},{cN:"cbracket",b:"[\\$%]\\{",e:"\\}",c:["self",r]},r,e.QSM]}}],i:/\S/}});hljs.registerLanguage("http",function(t){return{aliases:["https"],i:"\\S",c:[{cN:"status",b:"^HTTP/[0-9\\.]+",e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{cN:"request",b:"^[A-Z]+ (.*?) HTTP/[0-9\\.]+$",rB:!0,e:"$",c:[{cN:"string",b:" ",e:" ",eB:!0,eE:!0}]},{cN:"attribute",b:"^\\w",e:": ",eE:!0,i:"\\n|\\s|=",starts:{cN:"string",e:"$"}},{b:"\\n\\n",starts:{sL:[],eW:!0}}]}});hljs.registerLanguage("objectivec",function(e){var t={cN:"built_in",b:"(AV|CA|CF|CG|CI|MK|MP|NS|UI)\\w+"},i={keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},o=/[a-zA-Z@][a-zA-Z0-9_]*/,n="@interface @class @protocol @implementation";return{aliases:["mm","objc","obj-c"],k:i,l:o,i:"",c:[t,e.CLCM,e.CBCM,e.CNM,e.QSM,{cN:"string",v:[{b:'@"',e:'"',i:"\\n",c:[e.BE]},{b:"'",e:"[^\\\\]'",i:"[^\\\\][^']"}]},{cN:"preprocessor",b:"#",e:"$",c:[{cN:"title",v:[{b:'"',e:'"'},{b:"<",e:">"}]}]},{cN:"class",b:"("+n.split(" ").join("|")+")\\b",e:"({|$)",eE:!0,k:n,l:o,c:[e.UTM]},{cN:"variable",b:"\\."+e.UIR,r:0}]}});hljs.registerLanguage("python",function(e){var r={cN:"prompt",b:/^(>>>|\.\.\.) /},b={cN:"string",c:[e.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[r],r:10},{b:/(u|b)?r?"""/,e:/"""/,c:[r],r:10},{b:/(u|r|ur)'/,e:/'/,r:10},{b:/(u|r|ur)"/,e:/"/,r:10},{b:/(b|br)'/,e:/'/},{b:/(b|br)"/,e:/"/},e.ASM,e.QSM]},a={cN:"number",r:0,v:[{b:e.BNR+"[lLjJ]?"},{b:"\\b(0o[0-7]+)[lLjJ]?"},{b:e.CNR+"[lLjJ]?"}]},l={cN:"params",b:/\(/,e:/\)/,c:["self",r,a,b]};return{aliases:["py","gyp"],k:{keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10 None True False",built_in:"Ellipsis NotImplemented"},i:/(<\/|->|\?)/,c:[r,a,b,e.HCM,{v:[{cN:"function",bK:"def",r:10},{cN:"class",bK:"class"}],e:/:/,i:/[${=;\n,]/,c:[e.UTM,l]},{cN:"decorator",b:/^[\t ]*@/,e:/$/},{b:/\b(print|exec)\(/}]}});hljs.registerLanguage("java",function(e){var a=e.UIR+"(<"+e.UIR+">)?",t="false synchronized int abstract float private char boolean static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private",c="\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",r={cN:"number",b:c,r:0};return{aliases:["jsp"],k:t,i:/<\/|#/,c:[e.C("/\\*\\*","\\*/",{r:0,c:[{cN:"doctag",b:"@[A-Za-z]+"}]}),e.CLCM,e.CBCM,e.ASM,e.QSM,{cN:"class",bK:"class interface",e:/[{;=]/,eE:!0,k:"class interface",i:/[:"\[\]]/,c:[{bK:"extends implements"},e.UTM]},{bK:"new throw return else",r:0},{cN:"function",b:"("+a+"\\s+)+"+e.UIR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:t,c:[{b:e.UIR+"\\s*\\(",rB:!0,r:0,c:[e.UTM]},{cN:"params",b:/\(/,e:/\)/,k:t,r:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]},r,{cN:"annotation",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},s={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]},a={cN:"string",b:/'/,e:/'/};return{aliases:["sh","zsh"],l:/-?[a-z\.]+/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",operator:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"shebang",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],r:0},e.HCM,e.NM,s,a,t]}});hljs.registerLanguage("sql",function(e){var t=e.C("--","$");return{cI:!0,i:/[<>{}*]/,c:[{cN:"operator",bK:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke",e:/;/,eW:!0,k:{keyword:"abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias allocate allow alter always analyze ancillary and any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound buffer_cache buffer_pool build bulk by byte byteordermark bytes c cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle d data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration e each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain export export_set extended extent external external_1 external_2 externally extract f failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function g general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour http i id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists k keep keep_duplicates key keys kill l language large last last_day last_insert_id last_value lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim m main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex n name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding p package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second section securefile security seed segment select self sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime t table tables tablespace tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null",built_in:"array bigint binary bit blob boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text varchar varying void"},c:[{cN:"string",b:"'",e:"'",c:[e.BE,{b:"''"}]},{cN:"string",b:'"',e:'"',c:[e.BE,{b:'""'}]},{cN:"string",b:"`",e:"`",c:[e.BE]},e.CNM,e.CBCM,t]},e.CBCM,t]}});hljs.registerLanguage("nginx",function(e){var r={cN:"variable",v:[{b:/\$\d+/},{b:/\$\{/,e:/}/},{b:"[\\$\\@]"+e.UIR}]},b={eW:!0,l:"[a-z/_]+",k:{built_in:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},r:0,i:"=>",c:[e.HCM,{cN:"string",c:[e.BE,r],v:[{b:/"/,e:/"/},{b:/'/,e:/'/}]},{cN:"url",b:"([a-z]+):/",e:"\\s",eW:!0,eE:!0,c:[r]},{cN:"regexp",c:[e.BE,r],v:[{b:"\\s\\^",e:"\\s|{|;",rE:!0},{b:"~\\*?\\s+",e:"\\s|{|;",rE:!0},{b:"\\*(\\.[a-z\\-]+)+"},{b:"([a-z\\-]+\\.)+\\*"}]},{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{cN:"number",b:"\\b\\d+[kKmMgGdshdwy]*\\b",r:0},r]};return{aliases:["nginxconf"],c:[e.HCM,{b:e.UIR+"\\s",e:";|{",rB:!0,c:[{cN:"title",b:e.UIR,starts:b}],r:0}],i:"[^\\s\\}]"}});hljs.registerLanguage("cpp",function(t){var e={cN:"keyword",b:"\\b[a-z\\d_]*_t\\b"},r={cN:"string",v:[t.inherit(t.QSM,{b:'((u8?|U)|L)?"'}),{b:'(u8?|U)?R"',e:'"',c:[t.BE]},{b:"'\\\\?.",e:"'",i:"."}]},s={cN:"number",v:[{b:"\\b(\\d+(\\.\\d*)?|\\.\\d+)(u|U|l|L|ul|UL|f|F)"},{b:t.CNR}]},i={cN:"preprocessor",b:"#",e:"$",k:"if else elif endif define undef warning error line pragma ifdef ifndef",c:[{b:/\\\n/,r:0},{bK:"include",e:"$",c:[r,{cN:"string",b:"<",e:">",i:"\\n"}]},r,s,t.CLCM,t.CBCM]},a=t.IR+"\\s*\\(",c={keyword:"int float while private char catch export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const struct for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using class asm case typeid short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignof constexpr decltype noexcept static_assert thread_local restrict _Bool complex _Complex _Imaginary atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong",built_in:"std string cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf",literal:"true false nullptr NULL"};return{aliases:["c","cc","h","c++","h++","hpp"],k:c,i:"",c:[e,t.CLCM,t.CBCM,s,r,i,{b:"\\b(deque|list|queue|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",e:">",k:c,c:["self",e]},{b:t.IR+"::",k:c},{bK:"new throw return else",r:0},{cN:"function",b:"("+t.IR+"[\\*&\\s]+)+"+a,rB:!0,e:/[{;=]/,eE:!0,k:c,i:/[^\w\s\*&]/,c:[{b:a,rB:!0,c:[t.TM],r:0},{cN:"params",b:/\(/,e:/\)/,k:c,r:0,c:[t.CLCM,t.CBCM,r,s]},t.CLCM,t.CBCM,i]}]}});hljs.registerLanguage("php",function(e){var c={cN:"variable",b:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},a={cN:"preprocessor",b:/<\?(php)?|\?>/},i={cN:"string",c:[e.BE,a],v:[{b:'b"',e:'"'},{b:"b'",e:"'"},e.inherit(e.ASM,{i:null}),e.inherit(e.QSM,{i:null})]},t={v:[e.BNM,e.CNM]};return{aliases:["php3","php4","php5","php6"],cI:!0,k:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally",c:[e.CLCM,e.HCM,e.C("/\\*","\\*/",{c:[{cN:"doctag",b:"@[A-Za-z]+"},a]}),e.C("__halt_compiler.+?;",!1,{eW:!0,k:"__halt_compiler",l:e.UIR}),{cN:"string",b:/<<<['"]?\w+['"]?$/,e:/^\w+;?$/,c:[e.BE,{cN:"subst",v:[{b:/\$\w+/},{b:/\{\$/,e:/\}/}]}]},a,c,{b:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{cN:"function",bK:"function",e:/[;{]/,eE:!0,i:"\\$|\\[|%",c:[e.UTM,{cN:"params",b:"\\(",e:"\\)",c:["self",c,e.CBCM,i,t]}]},{cN:"class",bK:"class interface",e:"{",eE:!0,i:/[:\(\$"]/,c:[{bK:"extends implements"},e.UTM]},{bK:"namespace",e:";",i:/[\.']/,c:[e.UTM]},{bK:"use",e:";",c:[e.UTM]},{b:"=>"},i,t]}});hljs.registerLanguage("coffeescript",function(e){var c={keyword:"in if for while finally new do return else break catch instanceof throw try this switch continue typeof delete debugger super then unless until loop of by when and or is isnt not",literal:"true false null undefined yes no on off",built_in:"npm require console print module global window document"},n="[A-Za-z$_][0-9A-Za-z$_]*",r={cN:"subst",b:/#\{/,e:/}/,k:c},t=[e.BNM,e.inherit(e.CNM,{starts:{e:"(\\s*/)?",r:0}}),{cN:"string",v:[{b:/'''/,e:/'''/,c:[e.BE]},{b:/'/,e:/'/,c:[e.BE]},{b:/"""/,e:/"""/,c:[e.BE,r]},{b:/"/,e:/"/,c:[e.BE,r]}]},{cN:"regexp",v:[{b:"///",e:"///",c:[r,e.HCM]},{b:"//[gim]*",r:0},{b:/\/(?![ *])(\\\/|.)*?\/[gim]*(?=\W|$)/}]},{cN:"property",b:"@"+n},{b:"`",e:"`",eB:!0,eE:!0,sL:"javascript"}];r.c=t;var s=e.inherit(e.TM,{b:n}),i="(\\(.*\\))?\\s*\\B[-=]>",o={cN:"params",b:"\\([^\\(]",rB:!0,c:[{b:/\(/,e:/\)/,k:c,c:["self"].concat(t)}]};return{aliases:["coffee","cson","iced"],k:c,i:/\/\*/,c:t.concat([e.C("###","###"),e.HCM,{cN:"function",b:"^\\s*"+n+"\\s*=\\s*"+i,e:"[-=]>",rB:!0,c:[s,o]},{b:/[:\(,=]\s*/,r:0,c:[{cN:"function",b:i,e:"[-=]>",rB:!0,c:[o]}]},{cN:"class",bK:"class",e:"$",i:/[:="\[\]]/,c:[{bK:"extends",eW:!0,i:/[:="\[\]]/,c:[s]},s]},{cN:"attribute",b:n+":",e:":",rB:!0,rE:!0,r:0}])}});hljs.registerLanguage("javascript",function(e){return{aliases:["js"],k:{keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},c:[{cN:"pi",r:10,b:/^\s*['"]use (strict|asm)['"]/},e.ASM,e.QSM,{cN:"string",b:"`",e:"`",c:[e.BE,{cN:"subst",b:"\\$\\{",e:"\\}"}]},e.CLCM,e.CBCM,{cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{b:/,e:/>\s*[);\]]/,r:0,sL:"xml"}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:[e.CLCM,e.CBCM]}],i:/\[|%/},{b:/\$[(.]/},{b:"\\."+e.IR,r:0},{bK:"import",e:"[;$]",k:"import from as",c:[e.ASM,e.QSM]},{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]}],i:/#/}});hljs.registerLanguage("ini",function(e){var c={cN:"string",c:[e.BE],v:[{b:"'''",e:"'''",r:10},{b:'"""',e:'"""',r:10},{b:'"',e:'"'},{b:"'",e:"'"}]};return{aliases:["toml"],cI:!0,i:/\S/,c:[e.C(";","$"),e.HCM,{cN:"title",b:/^\s*\[+/,e:/\]+/},{cN:"setting",b:/^[a-z0-9\[\]_-]+\s*=\s*/,e:"$",c:[{cN:"value",eW:!0,k:"on off true false yes no",c:[{cN:"variable",v:[{b:/\$[\w\d"][\w\d_]*/},{b:/\$\{(.*?)}/}]},c,{cN:"number",b:/([\+\-]+)?[\d]+_[\d_]+/},e.NM],r:0}]}]}});hljs.registerLanguage("diff",function(e){return{aliases:["patch"],c:[{cN:"chunk",r:10,v:[{b:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{b:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{b:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{cN:"header",v:[{b:/Index: /,e:/$/},{b:/=====/,e:/=====$/},{b:/^\-\-\-/,e:/$/},{b:/^\*{3} /,e:/$/},{b:/^\+\+\+/,e:/$/},{b:/\*{5}/,e:/\*{5}$/}]},{cN:"addition",b:"^\\+",e:"$"},{cN:"deletion",b:"^\\-",e:"$"},{cN:"change",b:"^\\!",e:"$"}]}});
\ No newline at end of file
diff --git a/profiles/dkan/modules/contrib/recline/lib/highlight/styles/default.css b/profiles/dkan/modules/contrib/recline/lib/highlight/styles/default.css
deleted file mode 100644
index eb45fc6846a..00000000000
--- a/profiles/dkan/modules/contrib/recline/lib/highlight/styles/default.css
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
-
-Original style from softwaremaniacs.org (c) Ivan Sagalaev
-
-*/
-
-.hljs {
- display: block;
- overflow-x: auto;
- padding: 0.5em;
- background: #f0f0f0;
- -webkit-text-size-adjust: none;
-}
-
-.hljs,
-.hljs-subst,
-.hljs-tag .hljs-title,
-.nginx .hljs-title {
- color: black;
-}
-
-.hljs-string,
-.hljs-title,
-.hljs-constant,
-.hljs-parent,
-.hljs-tag .hljs-value,
-.hljs-rule .hljs-value,
-.hljs-preprocessor,
-.hljs-pragma,
-.hljs-name,
-.haml .hljs-symbol,
-.ruby .hljs-symbol,
-.ruby .hljs-symbol .hljs-string,
-.hljs-template_tag,
-.django .hljs-variable,
-.smalltalk .hljs-class,
-.hljs-addition,
-.hljs-flow,
-.hljs-stream,
-.bash .hljs-variable,
-.pf .hljs-variable,
-.apache .hljs-tag,
-.apache .hljs-cbracket,
-.tex .hljs-command,
-.tex .hljs-special,
-.erlang_repl .hljs-function_or_atom,
-.asciidoc .hljs-header,
-.markdown .hljs-header,
-.coffeescript .hljs-attribute,
-.tp .hljs-variable {
- color: #800;
-}
-
-.smartquote,
-.hljs-comment,
-.hljs-annotation,
-.diff .hljs-header,
-.hljs-chunk,
-.asciidoc .hljs-blockquote,
-.markdown .hljs-blockquote {
- color: #888;
-}
-
-.hljs-number,
-.hljs-date,
-.hljs-regexp,
-.hljs-literal,
-.hljs-hexcolor,
-.smalltalk .hljs-symbol,
-.smalltalk .hljs-char,
-.go .hljs-constant,
-.hljs-change,
-.lasso .hljs-variable,
-.makefile .hljs-variable,
-.asciidoc .hljs-bullet,
-.markdown .hljs-bullet,
-.asciidoc .hljs-link_url,
-.markdown .hljs-link_url {
- color: #080;
-}
-
-.hljs-label,
-.ruby .hljs-string,
-.hljs-decorator,
-.hljs-filter .hljs-argument,
-.hljs-localvars,
-.hljs-array,
-.hljs-attr_selector,
-.hljs-important,
-.hljs-pseudo,
-.hljs-pi,
-.haml .hljs-bullet,
-.hljs-doctype,
-.hljs-deletion,
-.hljs-envvar,
-.hljs-shebang,
-.apache .hljs-sqbracket,
-.nginx .hljs-built_in,
-.tex .hljs-formula,
-.erlang_repl .hljs-reserved,
-.hljs-prompt,
-.asciidoc .hljs-link_label,
-.markdown .hljs-link_label,
-.vhdl .hljs-attribute,
-.clojure .hljs-attribute,
-.asciidoc .hljs-attribute,
-.lasso .hljs-attribute,
-.coffeescript .hljs-property,
-.hljs-phony {
- color: #88f;
-}
-
-.hljs-keyword,
-.hljs-id,
-.hljs-title,
-.hljs-built_in,
-.css .hljs-tag,
-.hljs-doctag,
-.smalltalk .hljs-class,
-.hljs-winutils,
-.bash .hljs-variable,
-.pf .hljs-variable,
-.apache .hljs-tag,
-.hljs-type,
-.hljs-typename,
-.tex .hljs-command,
-.asciidoc .hljs-strong,
-.markdown .hljs-strong,
-.hljs-request,
-.hljs-status,
-.tp .hljs-data,
-.tp .hljs-io {
- font-weight: bold;
-}
-
-.asciidoc .hljs-emphasis,
-.markdown .hljs-emphasis,
-.tp .hljs-units {
- font-style: italic;
-}
-
-.nginx .hljs-built_in {
- font-weight: normal;
-}
-
-.coffeescript .javascript,
-.javascript .xml,
-.lasso .markup,
-.tex .hljs-formula,
-.xml .javascript,
-.xml .vbscript,
-.xml .css,
-.xml .hljs-cdata {
- opacity: 0.5;
-}
\ No newline at end of file
diff --git a/profiles/dkan/modules/contrib/recline/recline.theme.inc b/profiles/dkan/modules/contrib/recline/recline.theme.inc
index 28599ffaeed..d855ccbcb44 100644
--- a/profiles/dkan/modules/contrib/recline/recline.theme.inc
+++ b/profiles/dkan/modules/contrib/recline/recline.theme.inc
@@ -5,6 +5,10 @@
* Recline theme functions.
*/
+use Dkan\Datastore\Manager\Factory;
+use Dkan\Datastore\Resource;
+use Dkan\Datastore\Manager\ManagerInterface;
+
/**
* File size limit for remote zip files preview.
*
@@ -71,14 +75,24 @@ function theme_recline_default_formatter($vars) {
elseif (isset($vars['item']['fid'])) {
$url = file_create_url($file['uri']);
}
+
+ switch ($type) {
+ case 'html':
+ $link_text = t('Web Page');
+ break;
+ case 'api':
+ $link_text = t('API');
+ break;
+ default:
+ $link_text = '';
+ }
- $file['filename'] = isset($file['filename']) ? $file['filename'] : $url;
+ $file['filename'] = !empty($file['filename']) ? $file['filename'] : $link_text;
$description = isset($file['description']) ? $file['description'] : '';
$output = recline_build_icon($url, $type, $file['filename'], $file['filemime'], $file['filesize'], $description);
//If is a API then return without preview.
if ($api) {
- $output['preview'] = recline_format_link_api($url);
return drupal_render($output);
}
@@ -178,7 +192,6 @@ function theme_recline_default_formatter($vars) {
break;
case 'html':
- $output['preview'] = recline_format_link_api($url);
break;
case 'png':
@@ -197,7 +210,6 @@ function theme_recline_default_formatter($vars) {
break;
case 'pdf':
- $output['preview'] = recline_format_link_api($url);
break;
case 'wms':
@@ -301,25 +313,6 @@ function recline_format_image($url) {
);
}
-/**
- * Builds output for a link of an api.
- */
-function recline_format_link_api($url) {
- return array(
- 'iframe' => array(
- '#type' => 'html_tag',
- '#tag' => 'iframe',
- '#attributes' => array(
- 'src' => $url,
- 'height' => 600,
- 'width' => 900,
- 'style' => '',
- ),
- '#value' => '',
- ),
- );
-}
-
/**
* Provide unavaiable status message.
*/
@@ -548,13 +541,18 @@ function recline_preview_multiview($variables) {
$node = $item['entity'];
$embed_markup = theme('recline_embed_button', array('node' => $node));
// See if datastore is loaded, if so, prepare recline to view from it.
- if (module_exists('dkan_datastore_api') && module_exists('feeds_flatstore_processor') && function_exists('dkan_datastore_api_get_feeds_source')) {
- $source_id = dkan_datastore_api_get_feeds_source($node->nid);
- if ($table = feeds_flatstore_processor_table_name($source_id, $node->nid)) {
- if (db_table_exists($table)) {
- $datastoreStatus = $table;
+ if (module_exists('dkan_datastore_api')) {
+ try {
+ /* @var $manager ManagerInterface */
+ $manager = (new Factory(Resource::createFromDrupalNode($node)))->get();
+ $t = $manager->getTableName();
+ if (db_table_exists($t)) {
+ $datastoreStatus = $t;
}
}
+ catch (\Exception $e) {
+ $datastoreStatus = FALSE;
+ }
}
// Load all the required libraries.
diff --git a/profiles/dkan/modules/contrib/search_api/CHANGELOG.txt b/profiles/dkan/modules/contrib/search_api/CHANGELOG.txt
index 24c120fed14..1b61a9a1914 100644
--- a/profiles/dkan/modules/contrib/search_api/CHANGELOG.txt
+++ b/profiles/dkan/modules/contrib/search_api/CHANGELOG.txt
@@ -1,3 +1,25 @@
+Search API 1.25 (2018-09-17):
+-----------------------------
+- #2408727 by swim, drunken monkey: Added a batch operation for executing
+ pending tasks.
+- #2325917 by guillaumev, drunken monkey: Added a Views cache plugin based on
+ Views Content Cache.
+- #2989578 by KarlShea, drunken monkey: Fixed Views exposed form fields for
+ "not between" operator.
+- #2982167 by osopolar, drunken monkey: Added a Drush command for re-indexing
+ specific entities.
+- #1783746 by das-peter, sammys, SpadXIII, drunken monkey, ruloweb, KarlShea,
+ heshanlk, Anas_maw, pinkonomy, Damien Tournoud, rudiedirkx: Added support
+ for the "(not) between" operator.
+- #2408727 by drunken monkey, OliverColeman: Fixed out-of-memory errors when
+ executing pending tasks.
+- Issue #2948820 by capysara, drunken monkey: Added a link to the "need to
+ reindex" message on the Filters tab.
+- #2828883 by JorgenSandstrom, drunken monkey: Fixed property type for
+ string-typed aggregated fields.
+- #2949899 by drunken monkey, DamienMcKenna: Added a warning against using
+ particular processors with Solr servers to the "Workflow" tab.
+
Search API 1.24 (2018-04-05):
-----------------------------
- #2958201 by jcnventura, drunken monkey: Reverted issue #2566529: Added
diff --git a/profiles/dkan/modules/contrib/search_api/contrib/search_api_facetapi/search_api_facetapi.info b/profiles/dkan/modules/contrib/search_api/contrib/search_api_facetapi/search_api_facetapi.info
index 9c80f6e6ed3..2fd1c653bb3 100644
--- a/profiles/dkan/modules/contrib/search_api/contrib/search_api_facetapi/search_api_facetapi.info
+++ b/profiles/dkan/modules/contrib/search_api/contrib/search_api_facetapi/search_api_facetapi.info
@@ -9,8 +9,8 @@ files[] = plugins/facetapi/adapter.inc
files[] = plugins/facetapi/query_type_term.inc
files[] = plugins/facetapi/query_type_date.inc
-; Information added by Drupal.org packaging script on 2018-04-05
-version = "7.x-1.24"
+; Information added by Drupal.org packaging script on 2018-09-17
+version = "7.x-1.25"
core = "7.x"
project = "search_api"
-datestamp = "1522913891"
+datestamp = "1537171099"
diff --git a/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/README.txt b/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/README.txt
index b36a7b55b83..50cfce1c475 100644
--- a/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/README.txt
+++ b/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/README.txt
@@ -40,6 +40,21 @@ in that position. If the query is sorted in this way, then the
random sort, as an associative array with any of the following keys:
- seed: A numeric seed value to use for the random sort.
+"BETWEEN operator" feature
+--------------------------
+This module defines the "BETWEEN operator" feature (feature key:
+"search_api_between") that adds the "BETWEEN" and "NOT BETWEEN" filter
+operators to search queries. If your search server supports this feature, you
+can use the "Is between" and "Is not between" operators when adding Views
+filters for numeric, string or date types.
+
+For developers:
+A service class that wants to support this feature has to accept "BETWEEN" and
+"NOT BETWEEN" as additional $operator values in query conditions. The value in
+both cases is an array with the keys 0 and 1, with the value under key 0 being
+the lower and the value under key 1 being the upper bound for the range in which
+the field's value should ("BETWEEN") or should not ("NOT BETWEEN") be.
+
"Facets block" display
----------------------
Most features should be clear to users of Views. However, the module also
diff --git a/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/includes/handler_filter_date.inc b/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/includes/handler_filter_date.inc
index c7897245982..1259aa0bbbd 100644
--- a/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/includes/handler_filter_date.inc
+++ b/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/includes/handler_filter_date.inc
@@ -6,9 +6,9 @@
*/
/**
- * Views filter handler base class for handling all "normal" cases.
+ * Views filter handler base class for handling date fields.
*/
-class SearchApiViewsHandlerFilterDate extends SearchApiViewsHandlerFilter {
+class SearchApiViewsHandlerFilterDate extends SearchApiViewsHandlerFilterNumeric {
/**
* Add a "widget type" option.
@@ -88,9 +88,22 @@ class SearchApiViewsHandlerFilterDate extends SearchApiViewsHandlerFilter {
public function value_form(&$form, &$form_state) {
parent::value_form($form, $form_state);
+ $is_date_popup = ($this->options['widget_type'] == 'date_popup' && module_exists('date_popup'));
+
+ // If the operator is between
+ if ($this->operator == 'between') {
+ if ($is_date_popup) {
+ $form['value']['min']['#type'] = 'date_popup';
+ $form['value']['min']['#date_format'] = $this->options['date_popup_format'];
+ $form['value']['min']['#date_year_range'] = $this->options['year_range'];
+ $form['value']['max']['#type'] = 'date_popup';
+ $form['value']['max']['#date_format'] = $this->options['date_popup_format'];
+ $form['value']['max']['#date_year_range'] = $this->options['year_range'];
+ }
+ }
// If we are using the date popup widget, overwrite the settings of the form
// according to what date_popup expects.
- if ($this->options['widget_type'] == 'date_popup' && module_exists('date_popup')) {
+ elseif ($is_date_popup) {
$form['value']['#type'] = 'date_popup';
$form['value']['#date_format'] = $this->options['date_popup_format'];
$form['value']['#date_year_range'] = $this->options['year_range'];
@@ -115,11 +128,31 @@ class SearchApiViewsHandlerFilterDate extends SearchApiViewsHandlerFilter {
elseif ($this->operator === 'not empty') {
$this->query->condition($this->real_field, NULL, '<>', $this->options['group']);
}
- else {
- while (is_array($this->value)) {
- $this->value = $this->value ? reset($this->value) : NULL;
+ elseif (in_array($this->operator, array('between', 'not between'), TRUE)) {
+ $min = isset($this->value[0]['min']) ? $this->value[0]['min'] : '';
+ if ($min !== '') {
+ $min = is_numeric($min) ? $min : strtotime($min, REQUEST_TIME);
+ }
+ $max = isset($this->value[0]['max']) ? $this->value[0]['max'] : '';
+ if ($max !== '') {
+ $max = is_numeric($max) ? $max : strtotime($max, REQUEST_TIME);
}
- $v = is_numeric($this->value) ? $this->value : strtotime($this->value, REQUEST_TIME);
+
+ if (is_numeric($min) && is_numeric($max)) {
+ $this->query->condition($this->real_field, array($min, $max), strtoupper($this->operator), $this->options['group']);
+ }
+ elseif (is_numeric($min)) {
+ $operator = $this->operator === 'between' ? '>=' : '<';
+ $this->query->condition($this->real_field, $min, $operator, $this->options['group']);
+ }
+ elseif (is_numeric($max)) {
+ $operator = $this->operator === 'between' ? '<=' : '>';
+ $this->query->condition($this->real_field, $min, $operator, $this->options['group']);
+ }
+ }
+ else {
+ $value = isset($this->value[0]) ? $this->value[0]['value'] : $this->value['value'];
+ $v = is_numeric($value) ? $value : strtotime($value, REQUEST_TIME);
if ($v !== FALSE) {
$this->query->condition($this->real_field, $v, $this->operator, $this->options['group']);
}
diff --git a/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/includes/handler_filter_numeric.inc b/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/includes/handler_filter_numeric.inc
new file mode 100644
index 00000000000..f29a3e6e5ca
--- /dev/null
+++ b/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/includes/handler_filter_numeric.inc
@@ -0,0 +1,217 @@
+ array(
+ 'value' => array('default' => ''),
+ 'min' => array('default' => ''),
+ 'max' => array('default' => ''),
+ ),
+ );
+
+ return $options;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function operator_options() {
+ $operators = parent::operator_options();
+
+ $index = search_api_index_load(substr($this->table, 17));
+ $server = NULL;
+ try {
+ if ($index) {
+ $server = $index->server();
+ }
+ }
+ catch (SearchApiException $e) {
+ // Ignore.
+ }
+ if ($server && $server->supportsFeature('search_api_between')) {
+ $operators += array(
+ 'between' => t('Is between'),
+ 'not between' => t('Is not between'),
+ );
+ }
+
+ return $operators;
+ }
+
+ /**
+ * Provides a form for setting the filter value.
+ *
+ * Heavily borrowed from views_handler_filter_numeric.
+ *
+ * @see views_handler_filter_numeric::value_form()
+ */
+ public function value_form(&$form, &$form_state) {
+ $form['value']['#tree'] = TRUE;
+
+ $single_field_operators = $this->operator_options();
+ unset(
+ $single_field_operators['empty'],
+ $single_field_operators['not empty'],
+ $single_field_operators['between'],
+ $single_field_operators['not between']
+ );
+ $between_operators = array('between', 'not between');
+
+ // We have to make some choices when creating this as an exposed
+ // filter form. For example, if the operator is locked and thus
+ // not rendered, we can't render dependencies; instead we only
+ // render the form items we need.
+ $which = 'all';
+ $source = NULL;
+ if (!empty($form['operator'])) {
+ $source = ($form['operator']['#type'] == 'radios') ? 'radio:options[operator]' : 'edit-options-operator';
+ }
+
+ $identifier = NULL;
+ if (!empty($form_state['exposed'])) {
+ $identifier = $this->options['expose']['identifier'];
+ if (empty($this->options['expose']['use_operator']) || empty($this->options['expose']['operator_id'])) {
+ // Exposed and locked.
+ $which = in_array($this->operator, $between_operators) ? 'minmax' : 'value';
+ }
+ else {
+ $source = 'edit-' . drupal_html_id($this->options['expose']['operator_id']);
+ }
+ }
+
+ // Hide the value box if the operator is 'empty' or 'not empty'.
+ // Radios share the same selector so we have to add some dummy selector.
+ if ($which == 'all') {
+ $form['value']['value'] = array(
+ '#type' => 'textfield',
+ '#title' => empty($form_state['exposed']) ? t('Value') : '',
+ '#size' => 30,
+ '#default_value' => $this->value['value'],
+ '#dependency' => array($source => array_keys($single_field_operators)),
+ );
+ if ($identifier && !isset($form_state['input'][$identifier]['value'])) {
+ $form_state['input'][$identifier]['value'] = $this->value['value'];
+ }
+ }
+ elseif ($which == 'value') {
+ // When exposed we drop the value-value and just do value if
+ // the operator is locked.
+ $form['value'] = array(
+ '#type' => 'textfield',
+ '#title' => empty($form_state['exposed']) ? t('Value') : '',
+ '#size' => 30,
+ '#default_value' => isset($this->value['value']) ? $this->value['value'] : '',
+ );
+ if ($identifier && !isset($form_state['input'][$identifier])) {
+ $form_state['input'][$identifier] = isset($this->value['value']) ? $this->value['value'] : '';
+ }
+ }
+
+ if ($which == 'all' || $which == 'minmax') {
+ $form['value']['min'] = array(
+ '#type' => 'textfield',
+ '#title' => empty($form_state['exposed']) ? t('Min') : '',
+ '#size' => 30,
+ '#default_value' => $this->value['min'],
+ );
+ $form['value']['max'] = array(
+ '#type' => 'textfield',
+ '#title' => empty($form_state['exposed']) ? t('And max') : t('And'),
+ '#size' => 30,
+ '#default_value' => $this->value['max'],
+ );
+
+ if ($which == 'all') {
+ $form['value']['min']['#dependency'] = array($source => $between_operators);
+ $form['value']['max']['#dependency'] = array($source => $between_operators);
+ }
+
+ if (!empty($form_state['exposed']) && !isset($form_state['input'][$identifier]['min'])) {
+ $form_state['input'][$identifier]['min'] = $this->value['min'];
+ }
+ if (!empty($form_state['exposed']) && !isset($form_state['input'][$identifier]['max'])) {
+ $form_state['input'][$identifier]['max'] = $this->value['max'];
+ }
+
+ if (!isset($form['value']['value'])) {
+ // Ensure there is something in the 'value'.
+ $form['value']['value'] = array(
+ '#type' => 'value',
+ '#value' => NULL,
+ );
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function admin_summary() {
+ if (!empty($this->options['exposed'])) {
+ return t('exposed');
+ }
+
+ if ($this->operator === 'empty') {
+ return t('is empty');
+ }
+ if ($this->operator === 'not empty') {
+ return t('is not empty');
+ }
+
+ $value = isset($this->value[0]) ? $this->value[0] : $this->value;
+
+ if (in_array($this->operator, array('between', 'not between'), TRUE)) {
+ // This is of course wrong for translation purposes, but copied from
+ // views_handler_filter_numeric::admin_summary() so probably still better
+ // to re-use this than to do it correctly.
+ $operator = $this->operator === 'between' ? t('between') : t('not between');
+ $vars = array(
+ '@min' => (string) $value['min'],
+ '@max' => (string) $value['max'],
+ );
+ return $operator . ' ' . t('@min and @max', $vars);
+ }
+
+ return check_plain((string) $this->operator) . ' ' . check_plain((string) $value['value']);
+
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function query() {
+ if (in_array($this->operator, array('between', 'not between'), TRUE)) {
+ $min = isset($this->value[0]['min']) ? $this->value[0]['min'] : '';
+ $max = isset($this->value[0]['max']) ? $this->value[0]['max'] : '';
+ if ($min !== '' && $max !== '') {
+ $this->query->condition($this->real_field, array($min, $max), strtoupper($this->operator), $this->options['group']);
+ }
+ elseif ($min !== '') {
+ $operator = $this->operator === 'between' ? '>=' : '<';
+ $this->query->condition($this->real_field, $min, $operator, $this->options['group']);
+ }
+ elseif ($max !== '') {
+ $operator = $this->operator === 'between' ? '<=' : '>';
+ $this->query->condition($this->real_field, $min, $operator, $this->options['group']);
+ }
+ }
+ else {
+ parent::query();
+ }
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/includes/handler_filter_options.inc b/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/includes/handler_filter_options.inc
index 3040fb0fb59..2184fc855d6 100644
--- a/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/includes/handler_filter_options.inc
+++ b/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/includes/handler_filter_options.inc
@@ -121,6 +121,7 @@ class SearchApiViewsHandlerFilterOptions extends SearchApiViewsHandlerFilter {
*/
public function option_definition() {
$options = parent::option_definition();
+ $options['value'] = array('default' => '');
$options['expose']['contains']['reduce'] = array('default' => FALSE);
return $options;
}
diff --git a/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/includes/plugin_cache.inc b/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/includes/plugin_cache.inc
index c6bd41d4593..c63aed5e75b 100644
--- a/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/includes/plugin_cache.inc
+++ b/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/includes/plugin_cache.inc
@@ -35,11 +35,16 @@ class SearchApiViewsCache extends views_plugin_cache_time {
}
$cid = $this->get_results_key();
+ $results = NULL;
+ $query_plugin = $this->view->query;
+ if ($query_plugin instanceof SearchApiViewsQuery) {
+ $results = $query_plugin->getSearchApiResults();
+ }
$data = array(
'result' => $this->view->result,
'total_rows' => isset($this->view->total_rows) ? $this->view->total_rows : 0,
'current_page' => $this->view->get_current_page(),
- 'search_api results' => $this->view->query->getSearchApiResults(),
+ 'search_api results' => $results,
);
cache_set($cid, $data, $this->table, $this->cache_set_expire($type));
}
@@ -80,7 +85,7 @@ class SearchApiViewsCache extends views_plugin_cache_time {
* Overrides views_plugin_cache::get_cache_key().
*
* Use the Search API query as the main source for the key. Note that in
- * Views < 3.8, this function does not exist.
+ * Views < 3.8, this method does not exist.
*/
public function get_cache_key($key_data = array()) {
global $user;
@@ -121,7 +126,7 @@ class SearchApiViewsCache extends views_plugin_cache_time {
}
/**
- * Get the Search API query object associated with the current view.
+ * Retrieves the Search API query object associated with the current view.
*
* @return SearchApiQueryInterface|null
* The Search API query object associated with the current view; or NULL if
diff --git a/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/includes/plugin_content_cache.inc b/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/includes/plugin_content_cache.inc
new file mode 100644
index 00000000000..555fe89a133
--- /dev/null
+++ b/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/includes/plugin_content_cache.inc
@@ -0,0 +1,146 @@
+get_results_key();
+ $results = NULL;
+ $query_plugin = $this->view->query;
+ if ($query_plugin instanceof SearchApiViewsQuery) {
+ $results = $query_plugin->getSearchApiResults();
+ }
+ $data = array(
+ 'result' => $this->view->result,
+ 'total_rows' => isset($this->view->total_rows) ? $this->view->total_rows : 0,
+ 'current_page' => $this->view->get_current_page(),
+ 'search_api results' => $results,
+ );
+ cache_set($cid, $data, $this->table, $this->cache_set_expire($type));
+ }
+
+ /**
+ * Overrides views_plugin_cache::cache_get().
+ *
+ * Additionally stores successfully retrieved results with
+ * search_api_current_search().
+ */
+ public function cache_get($type) {
+ if ($type != 'results') {
+ return parent::cache_get($type);
+ }
+
+ // Values to set: $view->result, $view->total_rows, $view->execute_time,
+ // $view->current_page.
+ if ($cache = cache_get($this->get_results_key(), $this->table)) {
+ $cutoff = $this->cache_expire($type);
+ if (!$cutoff || $cache->created > $cutoff) {
+ $this->view->result = $cache->data['result'];
+ $this->view->total_rows = $cache->data['total_rows'];
+ $this->view->set_current_page($cache->data['current_page']);
+ $this->view->execute_time = 0;
+
+ // Trick Search API into believing a search happened, to make facetting
+ // et al. work.
+ $query = $this->getSearchApiQuery();
+ search_api_current_search($query->getOption('search id'), $query, $cache->data['search_api results']);
+
+ return TRUE;
+ }
+ }
+ return FALSE;
+ }
+
+ /**
+ * Overrides views_plugin_cache::get_cache_key().
+ *
+ * Use the Search API query as the main source for the key. Note that in
+ * Views < 3.8, this method does not exist.
+ */
+ public function get_cache_key($key_data = array()) {
+ global $user;
+
+ if (!isset($this->_results_key)) {
+ $query = $this->getSearchApiQuery();
+ $query->preExecute();
+ $key_data += array(
+ 'query' => $query,
+ 'roles' => array_keys($user->roles),
+ 'super-user' => $user->uid == 1, // special caching for super user.
+ 'language' => $GLOBALS['language']->language,
+ 'base_url' => $GLOBALS['base_url'],
+ 'offset' => $this->view->get_current_page() . '*' . $this->view->get_items_per_page() . '+' . $this->view->get_offset(),
+ );
+ // Not sure what gets passed in exposed_info, so better include it. All
+ // other parameters used in the parent method are already reflected in the
+ // Search API query object we use.
+ if (isset($_GET['exposed_info'])) {
+ $key_data['exposed_info'] = $_GET['exposed_info'];
+ }
+ }
+ $key = drupal_hash_base64(serialize($key_data));
+ return $key;
+ }
+
+ /**
+ * Overrides views_plugin_cache::get_results_key().
+ *
+ * This is unnecessary for Views >= 3.8.
+ */
+ public function get_results_key() {
+ if (!isset($this->_results_key)) {
+ $this->_results_key = $this->view->name . ':' . $this->display->id . ':results:' . $this->get_cache_key();
+ }
+
+ return $this->_results_key;
+ }
+
+ /**
+ * Retrieves the Search API query object associated with the current view.
+ *
+ * @return SearchApiQueryInterface|null
+ * The Search API query object associated with the current view; or NULL if
+ * there is none.
+ */
+ protected function getSearchApiQuery() {
+ if (!isset($this->search_api_query)) {
+ $this->search_api_query = FALSE;
+ if (isset($this->view->query) && $this->view->query instanceof SearchApiViewsQuery) {
+ $this->search_api_query = $this->view->query->getSearchApiQuery();
+ }
+ }
+
+ return $this->search_api_query ? $this->search_api_query : NULL;
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/search_api_views.info b/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/search_api_views.info
index 82c2f8f399a..ee66b2fb186 100644
--- a/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/search_api_views.info
+++ b/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/search_api_views.info
@@ -19,16 +19,18 @@ files[] = includes/handler_filter_date.inc
files[] = includes/handler_filter_entity.inc
files[] = includes/handler_filter_fulltext.inc
files[] = includes/handler_filter_language.inc
+files[] = includes/handler_filter_numeric.inc
files[] = includes/handler_filter_options.inc
files[] = includes/handler_filter_taxonomy_term.inc
files[] = includes/handler_filter_text.inc
files[] = includes/handler_filter_user.inc
files[] = includes/handler_sort.inc
files[] = includes/plugin_cache.inc
+files[] = includes/plugin_content_cache.inc
files[] = includes/query.inc
-; Information added by Drupal.org packaging script on 2018-04-05
-version = "7.x-1.24"
+; Information added by Drupal.org packaging script on 2018-09-17
+version = "7.x-1.25"
core = "7.x"
project = "search_api"
-datestamp = "1522913891"
+datestamp = "1537171099"
diff --git a/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/search_api_views.views.inc b/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/search_api_views.views.inc
index ff52d692fee..cc1cf4e52a7 100644
--- a/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/search_api_views.views.inc
+++ b/profiles/dkan/modules/contrib/search_api/contrib/search_api_views/search_api_views.views.inc
@@ -219,6 +219,9 @@ function _search_api_views_add_handlers($id, array $field, EntityMetadataWrapper
$table[$id]['filter']['vocabulary'] = $vocabulary;
}
}
+ elseif (in_array($inner_type, array('integer', 'decimal', 'duration', 'string'))) {
+ $table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilterNumeric';
+ }
else {
$table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilter';
}
@@ -285,6 +288,16 @@ function search_api_views_views_plugins() {
);
}
+ if (module_exists('views_content_cache')) {
+ $ret['cache']['search_api_views_content_cache'] = array(
+ 'title' => t('Search-specific content-based'),
+ 'help' => t("Cache Search API views based on content updates. (Requires Views Content Cache)"),
+ 'base' => $bases,
+ 'handler' => 'SearchApiViewsContentCache',
+ 'uses options' => TRUE,
+ );
+ }
+
return $ret;
}
diff --git a/profiles/dkan/modules/contrib/search_api/includes/callback_add_aggregation.inc b/profiles/dkan/modules/contrib/search_api/includes/callback_add_aggregation.inc
index 2e744f60f0a..55ed611c5cb 100644
--- a/profiles/dkan/modules/contrib/search_api/includes/callback_add_aggregation.inc
+++ b/profiles/dkan/modules/contrib/search_api/includes/callback_add_aggregation.inc
@@ -328,10 +328,10 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
'count' => 'integer',
'max' => 'integer',
'min' => 'integer',
- 'first' => 'string',
- 'first_char' => 'string',
- 'last' => 'string',
- 'list' => 'list',
+ 'first' => 'token',
+ 'first_char' => 'token',
+ 'last' => 'token',
+ 'list' => 'list',
);
case 'description':
return array(
diff --git a/profiles/dkan/modules/contrib/search_api/search_api.admin.inc b/profiles/dkan/modules/contrib/search_api/search_api.admin.inc
index 3afbaa7826f..89680dbf98c 100644
--- a/profiles/dkan/modules/contrib/search_api/search_api.admin.inc
+++ b/profiles/dkan/modules/contrib/search_api/search_api.admin.inc
@@ -528,7 +528,7 @@ function theme_search_api_server(array $variables) {
}
/**
- * Form constructor for completely clearing a server.
+ * Form constructor for server operations.
*
* @param SearchApiServer $server
* The server for which the form is displayed.
@@ -543,15 +543,39 @@ function search_api_server_status_form(array $form, array &$form_state, SearchAp
$form['clear'] = array(
'#type' => 'submit',
'#value' => t('Delete all indexed data on this server'),
+ '#submit' => array('search_api_server_status_form_clear_submit')
);
+ $count = $server->enabled ? search_api_server_tasks_count($server) : 0;
+ if ($count) {
+ $message = format_plural($count, '@count pending task must be executed before indexing.', '@count pending tasks must be executed before indexing.');
+ drupal_set_message($message, 'warning', FALSE);
+ $form['execute_pending_tasks'] = array(
+ '#type' => 'submit',
+ '#value' => t('Execute all pending tasks on this server'),
+ '#submit' => array('search_api_server_status_form_execute_pending_tasks_submit')
+ );
+ }
+
return $form;
}
/**
-* Form submission handler for search_api_server_status_form().
-*/
-function search_api_server_status_form_submit(array $form, array &$form_state) {
+ * Form submission handler for search_api_server_status_form().
+ *
+ * Used for the "Execute all pending tasks" button.
+ */
+function search_api_server_status_form_execute_pending_tasks_submit($form, &$form_state) {
+ $server_id = $form_state['server']->machine_name;
+ $form_state['redirect'] = "admin/config/search/search_api/server/$server_id/execute-tasks";
+}
+
+/**
+ * Form submission handler for search_api_server_status_form().
+ *
+ * Used for the "Delete all indexed data" button.
+ */
+function search_api_server_status_form_clear_submit(array $form, array &$form_state) {
$server_id = $form_state['server']->machine_name;
$form_state['redirect'] = "admin/config/search/search_api/server/$server_id/clear";
}
@@ -1566,10 +1590,13 @@ function search_api_admin_index_workflow(array $form, array &$form_state, Search
$form['processors'] = array(
'#type' => 'fieldset',
'#title' => t('Processors'),
- '#description' => t('Select processors which will pre- and post-process data at index and search time, and their order. ' .
- 'Most processors will only influence fulltext fields, but refer to their individual descriptions for details regarding their effect.'),
+ '#description' => '
' . t("Select processors which will pre- and post-process data at index and search time, and their order. Most processors will only influence fulltext fields, but refer to their individual descriptions for details regarding their effect. Also, some processors shouldn't be used with more advanced search engines (like Solr or Elasticsearch), since the search engine already provides this functionality.") . '
' . t("Check the server's service class description for details.",
+ array('@server-url' => url('admin/config/search/search_api/server/' . $index->server . '/edit'))) . '
';
+ }
// Processor status.
$form['processors']['status'] = array(
@@ -1696,6 +1723,7 @@ function search_api_admin_index_workflow_submit(array $form, array &$form_state)
unset($values['callbacks']['settings']);
unset($values['processors']['settings']);
$index = $form_state['index'];
+ $index_path = 'admin/config/search/search_api/index/' . $index->machine_name;
$options = empty($index->options) ? array() : $index->options;
@@ -1761,13 +1789,14 @@ function search_api_admin_index_workflow_submit(array $form, array &$form_state)
$index->save();
$index->reindex();
- drupal_set_message(t("The indexing workflow was successfully edited. All content was scheduled for re-indexing so the new settings can take effect."));
+ $vars = array('@url' => url($index_path));
+ drupal_set_message(t('The indexing workflow was successfully edited. All content was scheduled for re-indexing so the new settings can take effect.', $vars));
}
else {
drupal_set_message(t('No values were changed.'));
}
- $form_state['redirect'] = 'admin/config/search/search_api/index/' . $index->machine_name . '/workflow';
+ $form_state['redirect'] = $index_path . '/workflow';
}
/**
@@ -1822,8 +1851,8 @@ function search_api_admin_index_fields(array $form, array &$form_state, SearchAp
'In any case, fields of type "Fulltext" will always be fulltext-searchable.
'),
);
if ($index->server) {
- $form['description']['#description'] .= '
' . t('Check the ' . "server's service class description for details.",
- array('@server-url' => url('admin/config/search/search_api/server/' . $index->server))) . '
';
+ $form['description']['#description'] .= '
' . t("Check the server's service class description for details.",
+ array('@server-url' => url('admin/config/search/search_api/server/' . $index->server . '/edit'))) . '
';
}
foreach ($fields as $key => $info) {
$form['fields'][$key]['title']['#markup'] = check_plain($info['name']);
diff --git a/profiles/dkan/modules/contrib/search_api/search_api.drush.inc b/profiles/dkan/modules/contrib/search_api/search_api.drush.inc
index 61957b9ae44..8c995b17b86 100644
--- a/profiles/dkan/modules/contrib/search_api/search_api.drush.inc
+++ b/profiles/dkan/modules/contrib/search_api/search_api.drush.inc
@@ -95,6 +95,18 @@ function search_api_drush_command() {
'aliases' => array('sapi-r'),
);
+ $items['search-api-reindex-items'] = array(
+ 'description' => 'Force re-indexing of one or more specific items.',
+ 'examples' => array(
+ 'drush search-api-reindex-items node 12,34,56' => dt('Schedule the nodes with ID 12, 34 and 56 for re-indexing.'),
+ ),
+ 'arguments' => array(
+ 'entity_type' => dt('The entity type whose items should be re-indexed.'),
+ 'entities' => dt('The entities of the given entity type to be re-indexed.'),
+ ),
+ 'aliases' => array('sapi-ri'),
+ );
+
$items['search-api-clear'] = array(
'description' => 'Clear one or all search indexes and mark them for re-indexing.',
'examples' => array(
@@ -109,6 +121,19 @@ function search_api_drush_command() {
'aliases' => array('sapi-c'),
);
+ $items['search-api-execute-tasks'] = array(
+ 'description' => 'Execute all pending tasks or all for a given server.',
+ 'examples' => array(
+ 'drush search-api-execute-tasks my_solr_server' => dt('Execute all pending tasks on !server', array('!server' => 'my_solr_server')),
+ 'drush sapi-et my_solr_server' => dt('Execute all pending tasks on !server', array('!server' => 'my_solr_server')),
+ 'drush sapi-et' => dt('Execute all pending tasks on all servers.')
+ ),
+ 'arguments' => array(
+ 'server_id' => dt('The numeric ID or machine name of a server to execute tasks on.'),
+ ),
+ 'aliases' => array('sapi-et')
+ );
+
$items['search-api-set-index-server'] = array(
'description' => 'Set the search server used by a given index.',
'examples' => array(
@@ -448,6 +473,33 @@ function drush_search_api_reindex($index_id = NULL) {
}
}
+/**
+ * Marks the given entities as needing to be re-indexed.
+ */
+function drush_search_api_reindex_items($entity_type, $entities) {
+ if (search_api_drush_static(__FUNCTION__)) {
+ return;
+ }
+
+ // Validate list of entity ids.
+ if (!empty($entities) && !preg_match('#^[0-9]*(,[0-9]*)*$#', $entities)) {
+ drush_log(dt('Entities should be a single numeric entity ID or a list with the numeric entity IDs separated by comma.'), 'error');
+ return;
+ }
+
+ $ids = explode(',', $entities);
+
+ if (!empty($ids)) {
+ search_api_track_item_change($entity_type, $ids);
+
+ $combined_ids = array();
+ foreach ($ids as $id) {
+ $combined_ids[] = $entity_type . '/' . $id;
+ }
+ search_api_track_item_change('multiple', $combined_ids);
+ }
+}
+
/**
* Clear an index.
*/
@@ -466,6 +518,34 @@ function drush_search_api_clear($index_id = NULL) {
}
}
+/**
+ * Execute all pending tasks or all for a given server.
+ */
+function drush_search_api_execute_tasks($server_id = NULL) {
+ if (search_api_drush_static(__FUNCTION__)) {
+ return;
+ }
+
+ // Attempt to load the associated server.
+ $server = NULL;
+ if ($server_id) {
+ $servers = search_api_drush_get_server($server_id);
+ if (!$servers) {
+ return;
+ }
+ $server = reset($servers);
+ }
+
+ // Process batch op with drush.
+ try {
+ search_api_execute_pending_tasks($server);
+ drush_log(dt('!server tasks have been successfully executed.', array('!server' => $server->machine_name ? $server->machine_name : 'All')), 'ok');
+ }
+ catch (SearchApiException $e) {
+ drush_log($e->getMessage(), 'error');
+ }
+}
+
/**
* Set the server for a given index.
*/
diff --git a/profiles/dkan/modules/contrib/search_api/search_api.info b/profiles/dkan/modules/contrib/search_api/search_api.info
index 1c85bdadc12..201c3db4124 100644
--- a/profiles/dkan/modules/contrib/search_api/search_api.info
+++ b/profiles/dkan/modules/contrib/search_api/search_api.info
@@ -38,8 +38,8 @@ files[] = includes/service.inc
configure = admin/config/search/search_api
-; Information added by Drupal.org packaging script on 2018-04-05
-version = "7.x-1.24"
+; Information added by Drupal.org packaging script on 2018-09-17
+version = "7.x-1.25"
core = "7.x"
project = "search_api"
-datestamp = "1522913891"
+datestamp = "1537171099"
diff --git a/profiles/dkan/modules/contrib/search_api/search_api.install b/profiles/dkan/modules/contrib/search_api/search_api.install
index 5dc2689547a..61f686d7f19 100644
--- a/profiles/dkan/modules/contrib/search_api/search_api.install
+++ b/profiles/dkan/modules/contrib/search_api/search_api.install
@@ -264,6 +264,51 @@ function search_api_schema() {
return $schema;
}
+/**
+ * Implements hook_requirements().
+ */
+function search_api_requirements($phase) {
+ $requirements = array();
+
+ if ($phase == 'runtime') {
+ // Check whether at least one server has pending tasks.
+ if (search_api_server_tasks_count()) {
+ $items = array();
+
+ $conditions = array('enabled' => TRUE);
+ foreach (search_api_server_load_multiple(FALSE, $conditions) as $server) {
+ $count = search_api_server_tasks_count($server);
+ if ($count) {
+ $args = array(
+ '@name' => $server->name,
+ );
+ $text = format_plural($count, '@name has @count pending task.', '@name has @count pending tasks.', $args);
+ $items[] = l($text, "admin/config/search/search_api/server/{$server->machine_name}/execute-tasks");
+ }
+ }
+
+ if ($items) {
+ $text = t('There are pending tasks for the following servers:');
+ $text .= theme('item_list', array(
+ 'type' => 'ul',
+ 'items' => $items,
+ ));
+ if (count($items) > 1) {
+ $label = t('Execute pending tasks on all servers');
+ $text .= l($label, 'admin/config/search/search_api/execute-tasks');
+ }
+ $requirements['search_api_pending_tasks'] = array(
+ 'title' => t('Search API'),
+ 'value' => $text,
+ 'severity' => REQUIREMENT_WARNING,
+ );
+ }
+ }
+ }
+
+ return $requirements;
+}
+
/**
* Implements hook_install().
*
diff --git a/profiles/dkan/modules/contrib/search_api/search_api.module b/profiles/dkan/modules/contrib/search_api/search_api.module
index 8fe3e2a7cf2..a509302cda5 100644
--- a/profiles/dkan/modules/contrib/search_api/search_api.module
+++ b/profiles/dkan/modules/contrib/search_api/search_api.module
@@ -72,6 +72,15 @@ function search_api_menu() {
'type' => MENU_LOCAL_TASK,
'context' => MENU_CONTEXT_INLINE | MENU_CONTEXT_PAGE,
);
+ $items[$pre . '/server/%search_api_server/execute-tasks'] = array(
+ 'title' => 'Execute pending tasks',
+ 'description' => 'Attempt to process pending tasks for a given server.',
+ 'page callback' => 'search_api_execute_pending_tasks',
+ 'page arguments' => array(5),
+ 'access callback' => 'search_api_access_execute_tasks_batch',
+ 'access arguments' => array(5),
+ 'type' => MENU_CALLBACK,
+ );
$items[$pre . '/server/%search_api_server/disable'] = array(
'title' => 'Disable',
'description' => 'Disable index.',
@@ -98,6 +107,13 @@ function search_api_menu() {
'context' => MENU_CONTEXT_INLINE,
'weight' => 10,
);
+ $items[$pre . '/execute-tasks'] = array(
+ 'title' => 'Execute pending tasks',
+ 'description' => 'Attempt to process pending server tasks.',
+ 'page callback' => 'search_api_execute_pending_tasks',
+ 'access callback' => 'search_api_access_execute_tasks_batch',
+ 'type' => MENU_LOCAL_ACTION,
+ );
$items[$pre . '/index/%search_api_index'] = array(
'title' => 'View index',
'title callback' => 'search_api_admin_item_title',
@@ -1409,6 +1425,10 @@ function search_api_server_tasks_check(SearchApiServer $server = NULL) {
// Sometimes the order of tasks might be important, so make sure to order by
// the task ID (which should be in order of insertion).
$select->orderBy('t.id');
+ // Only retrieve and execute 100 tasks at once, to avoid running out of memory
+ // or time. We just can't do anything else until all tasks have been resolved,
+ // but at least we shouldn't crash sites, or keep piling up tasks, that way.
+ $select->range(0, 100);
$tasks = $select->execute();
$executed_tasks = array();
@@ -1465,11 +1485,116 @@ function search_api_server_tasks_check(SearchApiServer $server = NULL) {
if (!$executed_tasks) {
return TRUE;
}
- // Otherwise, delete the executed tasks and check if new tasks were created.
+ // Otherwise, delete the executed tasks and check if new tasks were created
+ // (or if we didn't even fetch all due to the 100 tasks limit).
search_api_server_tasks_delete($executed_tasks);
return $count_query->execute()->fetchField() === 0;
}
+/**
+ * Provides a batch wrapper for search_api_server_tasks_check().
+ *
+ * @param SearchApiServer|null $server
+ * (optional) The server whose tasks should be executed, or NULL to execute
+ * tasks for all servers.
+ */
+function search_api_execute_pending_tasks(SearchApiServer $server = NULL) {
+ batch_set(array(
+ 'title' => t('Processing pending tasks'),
+ 'operations' => array(
+ array(
+ 'search_api_execute_pending_tasks_batch',
+ array(
+ $server,
+ ),
+ ),
+ ),
+ 'finished' => 'search_api_execute_pending_tasks_finished'
+ ));
+ if ($server) {
+ $path = 'admin/config/search/search_api/server/' . $server->machine_name;
+ }
+ else {
+ $path = 'admin/config/search/search_api';
+ }
+
+ if (function_exists('drush_backend_batch_process')) {
+ drush_backend_batch_process();
+ }
+ else {
+ batch_process($path);
+ }
+}
+
+/**
+ * Executes pending server tasks as part of a batch operation.
+ */
+function search_api_execute_pending_tasks_batch(SearchApiServer $server = NULL, &$context) {
+ if (!isset($context['results']['total'])) {
+ $context['results']['total'] = search_api_server_tasks_count($server);
+ }
+ $total = $context['results']['total'];
+
+ search_api_server_tasks_check($server);
+
+ $remaining = search_api_server_tasks_count($server);
+ $executed = max($total - $remaining, 0);
+
+ $args['@remaining'] = $remaining;
+ $context['message'] = format_plural($executed, 'Successfully executed @count task, @remaining remaining.', 'Successfully executed @count tasks, @remaining remaining.', $args);
+ $context['finished'] = $executed / $total;
+}
+
+/**
+ * Batch finish callback for pending server tasks.
+ */
+function search_api_execute_pending_tasks_finished($success, $results, $operations) {
+ if ($success) {
+ // Clear the previous warning.
+ drupal_get_messages('warning');
+
+ // Alert user to the number of tasks executed.
+ drupal_set_message(format_plural($results['total'], 'Successfully executed @count task.', 'Successfully executed @count tasks.'));
+ }
+}
+
+/**
+ * Return the number of pending tasks.
+ *
+ * @param SearchApiServer|null $server
+ * (optional) The server for which tasks should be counted, or NULL to count
+ * for all enabled servers.
+ *
+ * @return int
+ * The number of pending tasks for the server, or in total.
+ */
+function search_api_server_tasks_count(SearchApiServer $server = NULL) {
+ $query = db_select('search_api_task', 't')
+ ->fields('t');
+
+ if ($server) {
+ $query->condition('server_id', $server->machine_name);
+ }
+ else {
+ $query->join('search_api_server', 's', 's.machine_name = t.server_id');
+ $query->condition('s.enabled', 1);
+ }
+
+ return $query->countQuery()->execute()->fetchField();
+}
+
+/**
+ * Access callback: Checks whether a user can execute pending tasks.
+ *
+ * @param SearchApiServer|null $server
+ * (optional) The server for which tasks would be executed.
+ */
+function search_api_access_execute_tasks_batch(SearchApiServer $server = NULL) {
+ return user_access('administer search_api')
+ && search_api_server_tasks_count($server)
+ && (!$server || $server->enabled);
+}
+
/**
* Adds an entry into a server's list of pending tasks.
*
diff --git a/profiles/dkan/modules/contrib/search_api/tests/search_api_test.info b/profiles/dkan/modules/contrib/search_api/tests/search_api_test.info
index 5286e5ebaab..e3a3695da5c 100644
--- a/profiles/dkan/modules/contrib/search_api/tests/search_api_test.info
+++ b/profiles/dkan/modules/contrib/search_api/tests/search_api_test.info
@@ -10,8 +10,8 @@ files[] = search_api_test.module
hidden = TRUE
-; Information added by Drupal.org packaging script on 2018-04-05
-version = "7.x-1.24"
+; Information added by Drupal.org packaging script on 2018-09-17
+version = "7.x-1.25"
core = "7.x"
project = "search_api"
-datestamp = "1522913891"
+datestamp = "1537171099"
diff --git a/profiles/dkan/modules/contrib/search_api/tests/search_api_test_2.info b/profiles/dkan/modules/contrib/search_api/tests/search_api_test_2.info
index 72d47e90ec8..e84483ed218 100644
--- a/profiles/dkan/modules/contrib/search_api/tests/search_api_test_2.info
+++ b/profiles/dkan/modules/contrib/search_api/tests/search_api_test_2.info
@@ -9,8 +9,8 @@ files[] = search_api_test_service_2.module
hidden = TRUE
-; Information added by Drupal.org packaging script on 2018-04-05
-version = "7.x-1.24"
+; Information added by Drupal.org packaging script on 2018-09-17
+version = "7.x-1.25"
core = "7.x"
project = "search_api"
-datestamp = "1522913891"
+datestamp = "1537171099"
diff --git a/profiles/dkan/modules/contrib/search_api_db/CHANGELOG.txt b/profiles/dkan/modules/contrib/search_api_db/CHANGELOG.txt
index 4b30f594b16..da676895cdd 100644
--- a/profiles/dkan/modules/contrib/search_api_db/CHANGELOG.txt
+++ b/profiles/dkan/modules/contrib/search_api_db/CHANGELOG.txt
@@ -1,3 +1,14 @@
+Search API Database Search 1.7 (2018-09-17):
+--------------------------------------------
+- #2982443 by KarlShea, drunken monkey: Added support for the "(not) between"
+ operators.
+- #2855634 by drunken monkey, SpadXIII, levmyshkin, chris.jichen: Fixed update
+ #7107.
+- #2940278 by james.williams, drunken monkey: Fixed indexing of decimal values
+ as boolean.
+- #2897548 by drunken monkey: Fixed sorting on MySQL 5.7.
+- #2879881 by drunken monkey: Fixed preprocessing of autocomplete keys.
+
Search API Database Search 1.6 (2017-02-23):
--------------------------------------------
- #2840261 by alan-ps, drunken monkey: Fixed usage of outdated hash functions.
diff --git a/profiles/dkan/modules/contrib/search_api_db/PATCHES.txt b/profiles/dkan/modules/contrib/search_api_db/PATCHES.txt
deleted file mode 100644
index 3a3c534fed5..00000000000
--- a/profiles/dkan/modules/contrib/search_api_db/PATCHES.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-The following patches have been applied to this project:
-- https://www.drupal.org/files/issues/2855634-23--fix_update_7107_for_different_db.patch
-
-This file was automatically generated by Drush Make (http://drupal.org/project/drush).
diff --git a/profiles/dkan/modules/contrib/search_api_db/search_api_db.info b/profiles/dkan/modules/contrib/search_api_db/search_api_db.info
index df593332c9b..3a2bd8481bc 100644
--- a/profiles/dkan/modules/contrib/search_api_db/search_api_db.info
+++ b/profiles/dkan/modules/contrib/search_api_db/search_api_db.info
@@ -6,9 +6,8 @@ package = Search
files[] = search_api_db.test
files[] = service.inc
-; Information added by Drupal.org packaging script on 2017-02-23
-version = "7.x-1.6"
+; Information added by Drupal.org packaging script on 2018-09-17
+version = "7.x-1.7"
core = "7.x"
project = "search_api_db"
-datestamp = "1487844786"
-
+datestamp = "1537173484"
diff --git a/profiles/dkan/modules/contrib/search_api_db/service.inc b/profiles/dkan/modules/contrib/search_api_db/service.inc
index 609e5d4f1b5..b98d5b793c2 100644
--- a/profiles/dkan/modules/contrib/search_api_db/service.inc
+++ b/profiles/dkan/modules/contrib/search_api_db/service.inc
@@ -174,6 +174,7 @@ class SearchApiDbService extends SearchApiAbstractService {
public function supportsFeature($feature) {
$supported = array(
'search_api_autocomplete' => TRUE,
+ 'search_api_between' => TRUE,
'search_api_facets' => TRUE,
'search_api_facets_operator_or' => TRUE,
'search_api_random_sort' => TRUE,
@@ -1112,6 +1113,11 @@ class SearchApiDbService extends SearchApiAbstractService {
return 0 + $value;
case 'boolean':
+ // Numeric strings need to be converted to a numeric type before
+ // converting to a boolean, as strings like '0.00' evaluate to TRUE.
+ if (is_string($value) && is_numeric($value)) {
+ $value = 0 + $value;
+ }
return $value ? 1 : 0;
case 'date':
@@ -1686,57 +1692,63 @@ class SearchApiDbService extends SearchApiAbstractService {
}
}
else {
- if (!isset($fields[$f[0]])) {
- throw new SearchApiException(t('Unknown field in filter clause: @field.', array('@field' => $f[0])));
- }
- $field = $fields[$f[0]];
- $not_equals = $f[2] == '<>' || $f[2] == '!=';
- $text_type = search_api_is_text_type($field['type']);
+ list ($field, $value, $operator) = $f;
+ if (!isset($fields[$field])) {
+ throw new SearchApiException(t('Unknown field in filter clause: @field.', array('@field' => $field)));
+ }
+ $field_info = $fields[$field];
+ $not_between = $operator === 'NOT BETWEEN';
+ $not_equals = $not_between || $operator === '<>' || $operator === '!=';
+ $text_type = search_api_is_text_type($field_info['type']);
// If the field is in its own table, we have to check for NULL values in
// a special way (i.e., check for missing entries in that table).
- if ($f[1] === NULL && ($field['column'] === 'value' || $text_type)) {
- $query = $this->connection->select($field['table'], 't')
+ if ($value === NULL && ($field_info['column'] === 'value' || $text_type)) {
+ $query = $this->connection->select($field_info['table'], 't')
->fields('t', array('item_id'));
if ($text_type) {
- $query->condition('t.field_name', $f[0]);
+ $query->condition('t.field_name', $field);
}
$cond->condition('t.item_id', $query, $not_equals ? 'IN' : 'NOT IN');
continue;
}
if ($text_type) {
- $keys = $this->prepareKeys($f[1]);
+ $keys = $this->prepareKeys($value);
if (!isset($keys)) {
continue;
}
- $query = $this->createKeysQuery($keys, array($f[0] => $field), $fields);
+ $query = $this->createKeysQuery($keys, array($field => $field_info), $fields);
// We only want the item IDs, so we use the keys query as a nested query.
$query = $this->connection->select($query, 't')->fields('t', array('item_id'));
$cond->condition('t.item_id', $query, $not_equals ? 'NOT IN' : 'IN');
}
else {
- $new_join = search_api_is_list_type($field['type'])
+ $new_join = search_api_is_list_type($field_info['type'])
&& ($filter->getConjunction() == 'AND'
- || empty($first_join[$f[0]]));
- if ($new_join || empty($tables[$f[0]])) {
- $tables[$f[0]] = $this->getTableAlias($field, $db_query, $new_join);
- $first_join[$f[0]] = TRUE;
+ || empty($first_join[$field]));
+ if ($new_join || empty($tables[$field])) {
+ $tables[$field] = $this->getTableAlias($field_info, $db_query, $new_join);
+ $first_join[$field] = TRUE;
}
- $column = $tables[$f[0]] . '.' . $field['column'];
- if ($f[1] === NULL) {
- $method = ($f[2] == '=') ? 'isNull' : 'isNotNull';
+ $column = $tables[$field] . '.' . $field_info['column'];
+ if ($value === NULL) {
+ $method = ($operator == '=') ? 'isNull' : 'isNotNull';
$cond->$method($column);
}
- elseif ($not_equals && search_api_is_list_type($field['type'])) {
+ elseif ($not_equals && search_api_is_list_type($field_info['type'])) {
// The situation is more complicated for multi-valued fields, since
// we must make sure that results are excluded if ANY of the field's
// values equals the one given in this condition.
- $query = $this->connection->select($field['table'], 't')
+ $sub_operator = ($not_between) ? 'BETWEEN' : '=';
+ $query = $this->connection->select($field_info['table'], 't')
->fields('t', array('item_id'))
- ->condition($field['column'], $f[1]);
+ ->condition($field_info['column'], $value, $sub_operator);
$cond->condition('t.item_id', $query, 'NOT IN');
}
+ elseif ($not_between) {
+ $cond->where("$column NOT BETWEEN {$value[0]} AND {$value[1]}");
+ }
else {
- $cond->condition($column, $f[1], $f[2]);
+ $cond->condition($column, $value, $operator);
}
}
}
@@ -1854,7 +1866,7 @@ class SearchApiDbService extends SearchApiAbstractService {
$alias = $this->getTableAlias($field, $db_query);
$db_query->orderBy($alias . '.' . $fields[$field_name]['column'], $order);
// PostgreSQL automatically adds a field to the SELECT list when sorting
- // on it. Therefore, if we have aggregrations present we also have to
+ // on it. Therefore, if we have aggregations present we also have to
// add the field to the GROUP BY (since Drupal won't do it for us).
// However, if no aggregations are present, a GROUP BY would lead to
// another error. Therefore, we only add it if there is already a GROUP
@@ -1862,6 +1874,13 @@ class SearchApiDbService extends SearchApiAbstractService {
if ($db_query->getGroupBy()) {
$db_query->groupBy($alias . '.' . $fields[$field_name]['column']);
}
+ // For SELECT DISTINCT queries in combination with an ORDER BY clause,
+ // MySQL 5.7 and higher require that the ORDER BY expressions are part
+ // of the field list. Ensure that all fields used for sorting are part
+ // of the select list.
+ if (empty($db_fields[$fields[$field_name]['column']])) {
+ $db_query->addField($alias, $fields[$field_name]['column']);
+ }
}
}
else {
@@ -2085,8 +2104,21 @@ class SearchApiDbService extends SearchApiAbstractService {
// Decide which methods we want to use.
if ($incomplete_key && $settings['suggest_suffix']) {
- $passes[] = 1;
- $incomplete_like = $this->connection->escapeLike($incomplete_key) . '%';
+ $processed_key = $this->splitKeys($incomplete_key);
+ if ($processed_key) {
+ // In case the $incomplete_key turned out to be more than one word, add
+ // all but the last one to the user input.
+ if (is_array($processed_key)) {
+ unset($processed_key['#conjunction']);
+ $incomplete_key = array_pop($processed_key);
+ if ($processed_key) {
+ $user_input .= ' ' . implode(' ', $processed_key);
+ }
+ $processed_key = $incomplete_key;
+ }
+ $passes[] = 1;
+ $incomplete_like = $this->connection->escapeLike($processed_key) . '%';
+ }
}
if ($settings['suggest_words']
&& (!$incomplete_key || strlen($incomplete_key) >= $this->options['min_chars'])) {
diff --git a/profiles/dkan/modules/contrib/tablefield/README.txt b/profiles/dkan/modules/contrib/tablefield/README.txt
index d9fcb2a090b..7c1fd7a08d4 100644
--- a/profiles/dkan/modules/contrib/tablefield/README.txt
+++ b/profiles/dkan/modules/contrib/tablefield/README.txt
@@ -86,11 +86,11 @@ stated otherwise.
This format is intended to provide table data as a service:
-- directly by enabling the submodule TableField Themeless. It provides
- themeless output of a node's tablefield on the path 'node/%/themeless' (HTML,
- JSON or XML).
+- directly by enabling the Themeless module
+ (https://www.drupal.org/project/themeless). It provides themeless output of a
+ node's tablefield on the path 'node/%/themeless' (HTML, JSON or XML).
- using a View (e.g. with https://www.drupal.org/project/views_datasource) that
- outputs JSON or XML. The Views field settings includes 'Formatter'.
+ outputs JSON or XML. The Views field settings includes 'Formatter'
- using a custom service (e.g. with https://www.drupal.org/project/services).
@@ -125,24 +125,19 @@ service.
### Themeless output ###
-Enabling the submodule TableField Themeless provides themeless output of a
-node's tablefield on the path 'node/%/themeless' (HTML, JSON or XML). This is
-useful to embed the table's HTML elsewhere (as an iFrame) or to provide the
-table data as a service (JSON or XML) directly without the need of Views or a
-Service.
+Themeless module https://www.drupal.org/project/themeless provides themeless
+output of a node's tablefield on the path 'node/%/themeless' (HTML, JSON or
+XML). This is useful to embed the table's HTML elsewhere (as an iFrame) or to
+provide the table data as a service (JSON or XML) directly without the need of
+Views or a Service.
-- Enable the submodule TableField Themeless.
+- Enable the module Themeless.
- Go to ../admin/structure/types/manage/[your-content-type]/display.
- Uncollapse the CUSTOM DISPLAY SETTINGS and select 'Themeless'.
- Save.
- Now a new display mode appears besides Default and Teaser. Go and configure.
- Save.
-Install and enable https://www.drupal.org/project/subpathauto to have the
-themeless output available under the alias path like 'some/alias/themeless'
-besides 'node/%/themeless'.
-
-
## CREDITS ##
- Original author: Kevin Hankens (http://www.kevinhankens.com)
diff --git a/profiles/dkan/modules/contrib/tablefield/tablefield.info b/profiles/dkan/modules/contrib/tablefield/tablefield.info
index 42bc3df664c..f6d33a4187d 100644
--- a/profiles/dkan/modules/contrib/tablefield/tablefield.info
+++ b/profiles/dkan/modules/contrib/tablefield/tablefield.info
@@ -7,9 +7,8 @@ package = Fields
dependencies[] = field
configure = admin/config/content/tablefield
-; Information added by Drupal.org packaging script on 2017-06-13
-version = "7.x-3.1"
+; Information added by Drupal.org packaging script on 2018-12-08
+version = "7.x-3.2"
core = "7.x"
project = "tablefield"
-datestamp = "1497359647"
-
+datestamp = "1544293992"
diff --git a/profiles/dkan/modules/contrib/tablefield/tablefield.install b/profiles/dkan/modules/contrib/tablefield/tablefield.install
index 4eb31864d4f..81de25839fa 100644
--- a/profiles/dkan/modules/contrib/tablefield/tablefield.install
+++ b/profiles/dkan/modules/contrib/tablefield/tablefield.install
@@ -401,3 +401,16 @@ function tablefield_update_7006() {
field_cache_clear();
drupal_set_message(t('All Table Field fields have their field settings converted to widget settings.'), 'warning');
}
+
+/**
+ * New Themeless module will subtitute submodule.
+ */
+function tablefield_update_7007() {
+ $themeless = l(t('Themeless'), 'https://www.drupal.org/project/themeless', array(
+ 'attributes' => array(
+ 'title' => t('Themeless module'),
+ 'target' => '_blank',
+ ),
+ ));
+ drupal_set_message(t('Module !themeless will substitute tablefield_themeless submodule. If you are using tablefield_themeless submodule then disable it and use !themeless', array('!themeless' => $themeless)), 'status');
+}
diff --git a/profiles/dkan/modules/contrib/tablefield/tablefield.module b/profiles/dkan/modules/contrib/tablefield/tablefield.module
index 1992b13e95c..75c42d7bdb0 100644
--- a/profiles/dkan/modules/contrib/tablefield/tablefield.module
+++ b/profiles/dkan/modules/contrib/tablefield/tablefield.module
@@ -183,17 +183,57 @@ function tablefield_item_property_info() {
'getter callback' => 'tablefield_get_table_value',
);
+ $properties['value'] = array(
+ 'type' => 'text',
+ 'label' => t('The value column of the table.'),
+ 'computed' => TRUE,
+ 'getter callback' => 'tablefield_value_array_get',
+ // @todo: can we support setting via an array submitted from REST?
+ // 'setter callback' => 'tablefield_value_array_set',
+ );
+
return $properties;
}
+/**
+ *
+ */
+function tablefield_tablefield_to_array($trat) {
+ // Translate tablefield to associative array.
+ $ttrans = array();
+ $rowkey = 0;
+ foreach ($trat as $rowix => $rowvals) {
+ foreach ($rowvals as $ix => $val) {
+ $ttrans[$rowkey][] = $val;
+ }
+ $rowkey++;
+ }
+ return $ttrans;
+}
+
+/**
+ * Get the value property in formatted array.
+ */
+function tablefield_value_array_get($data, array $options, $name, $type, $info) {
+
+ if (isset($data['tabledata']['tabledata'])) {
+ $data['value'] = tablefield_tablefield_to_array($data['tabledata']['tabledata']);
+ }
+ elseif (isset($data['tablefield']['tabledata'])) {
+ $data['value'] = tablefield_tablefield_to_array($data['tablefield']['tabledata']);
+ }
+ return $data['value'];
+}
+
/**
* Get the property just as it is set in the data. Search API indexing addition.
*/
function tablefield_get_table_value($data, array $options, $name, $type, $info) {
- if (isset($data['tabledata'])) {
+
+ if (isset($data['tabledata']['tabledata'])) {
$data['value'] = '';
- foreach ($data['tabledata'] as $rows) {
+ foreach ($data['tabledata']['tabledata'] as $rows) {
$data['value'] .= implode(" ", $rows) . " ";
}
}
@@ -601,7 +641,7 @@ function tablefield_field_formatter_settings_summary($field, $instance, $view_mo
'title' => t('Manage user permissions'),
),
));
- $summary[] = t('Show link to export table data as CSV depending on !permission: %tr', array('%tr' => ($settings['hide_cols_skip_head']) ? t('Yes') : t('No'), '!permission' => $permission));
+ $summary[] = t('Show link to export table data as CSV depending on !permission: %tr', array('%tr' => ($settings['export_csv']) ? t('Yes') : t('No'), '!permission' => $permission));
}
return implode(' ', $summary);;
}
@@ -756,6 +796,7 @@ function tablefield_field_formatter_settings_form($field, $instance, $view_mode,
);
$element['hide_cols_skip_head'] = array(
'#title' => t('Hide empty columns ignoring column header'),
+ '#description' => t('This will remove the table field completely if all columns are empty including the field label.'),
'#type' => 'checkbox',
'#default_value' => $settings['hide_cols_skip_head'],
);
@@ -779,11 +820,6 @@ function tablefield_field_formatter_settings_form($field, $instance, $view_mode,
'#type' => 'checkbox',
'#default_value' => $settings['hide_empty_cols'],
);
- $element['hide_cols_skip_head'] = array(
- '#title' => t('Hide empty columns ignoring column header'),
- '#type' => 'checkbox',
- '#default_value' => $settings['hide_cols_skip_head'],
- );
$permission = l(t('permission'), 'admin/people/permissions', array(
'fragment' => 'module-tablefield',
'attributes' => array(
@@ -1121,30 +1157,35 @@ function tablefield_field_formatter_view($entity_type, $entity, $field, $instanc
}
}
- $header = $noheader ? NULL : $header_data;
+ $header = $noheader ? [] : $header_data;
$entity_info = entity_get_info($entity_type);
$entity_id = !empty($entity_info['entity keys']['id']) ? $entity->{$entity_info['entity keys']['id']} : NULL;
// Remove the first row from the tabledata.
- array_shift($tabledata);
- // Theme the table for display.
- $element[$delta] = array(
- '#theme' => 'tablefield_view',
- '#caption' => $caption,
- '#header_orientation' => isset($settings['header_orientation']) ? $settings['header_orientation'] : 'Horizontal',
- '#sticky' => isset($settings['sticky_header']) ? $settings['sticky_header'] : NULL,
- '#striping' => isset($settings['striping']) ? $settings['striping'] : NULL,
- '#sortable' => isset($settings['sortable']) ? $settings['sortable'] : NULL,
- '#header' => $header,
- '#rows' => $tabledata,
- '#delta' => $delta,
- '#export' => isset($settings['export_csv']) ? $settings['export_csv'] : NULL,
- '#entity_type' => $entity_type,
- '#entity_id' => $entity_id,
- '#field_name' => $field['field_name'],
- '#langcode' => $langcode,
- '#formatter' => $formatter,
- );
+ if ((empty($settings['hide_header']) && $header_data) || $settings['hide_header']) {
+ array_shift($tabledata);
+ }
+ // Theme the table for display, but only if we have printable
+ // table data.
+ if (!$settings['hide_cols_skip_head'] || $tabledata || $header) {
+ $element[$delta] = array(
+ '#theme' => 'tablefield_view',
+ '#caption' => $caption,
+ '#header_orientation' => isset($settings['header_orientation']) ? $settings['header_orientation'] : 'Horizontal',
+ '#sticky' => isset($settings['sticky_header']) ? $settings['sticky_header'] : NULL,
+ '#striping' => isset($settings['striping']) ? $settings['striping'] : NULL,
+ '#sortable' => isset($settings['sortable']) ? $settings['sortable'] : NULL,
+ '#header' => $header,
+ '#rows' => $tabledata,
+ '#delta' => $delta,
+ '#export' => isset($settings['export_csv']) ? $settings['export_csv'] : NULL,
+ '#entity_type' => $entity_type,
+ '#entity_id' => $entity_id,
+ '#field_name' => $field['field_name'],
+ '#langcode' => $langcode,
+ '#formatter' => $formatter,
+ );
+ }
}
}
}
@@ -1438,13 +1479,22 @@ function tablefield_field_widget_form(&$form, &$form_state, $field, $instance, $
'#type' => 'textfield',
'#title' => t('Table description'),
'#default_value' => '',
- '#description' => t('This brief caption will be associated with the table and will help Assitive Technology, like screen readers, better describe the content within.'),
+ '#description' => t('This brief caption will be associated with the table and will help Assistive Technology, like screen readers, better describe the content within.'),
);
- if (isset($items[$delta]['value'])) {
+
+ // Could be the Paragraphs module.
+ if (isset($items[$delta]['tablefield'])) {
+ $raw = $items[$delta]['tablefield'];
+ }
+ elseif (isset($items[$delta]['value'])) {
$raw = unserialize($items[$delta]['value']);
- if (isset($raw['caption'])) {
- $element['tablefield']['caption']['#default_value'] = check_plain($raw['caption']);
- }
+ }
+ // If still nothing then get the instance default, but only for delta 0.
+ elseif ($delta === 0 && isset($instance['default_value'][0]['tablefield'])) {
+ $raw = $instance['default_value'][0]['tablefield'];
+ }
+ if (isset($raw['caption'])) {
+ $element['tablefield']['caption']['#default_value'] = check_plain($raw['caption']);
}
// If the user doesn't have rebuild perms, we pass along the data as a value.
@@ -1613,6 +1663,17 @@ function tablefield_field_widget_form(&$form, &$form_state, $field, $instance, $
return $element;
}
+/**
+ * Implements hook_custom_theme().
+ */
+function tablefield_custom_theme() {
+ // Ensure that if this is a valid POST request that we use the same theme
+ // used by the referring form.
+ if (isset($_POST['form_build_id']) && path_is_admin($_GET['q'])) {
+ return variable_get('admin_theme');
+ }
+}
+
/**
* Custom callback for a textarea to be processed for linebreaks.
*/
@@ -1855,10 +1916,12 @@ function tablefield_theme() {
* Theme function for table view.
*/
function theme_tablefield_view($variables) {
+ $id = $variables['entity_type'] . '-' . $variables['entity_id'] . '-' . $variables['field_name'] . '-' . $variables['delta'];
$sortable = $variables['sortable'] ? 'tablesorter' : NULL;
+ $cols_class = isset($variables['header']) ? 'tablefield-columns-' . count($variables['header']) : NULL;
$attributes = array(
- 'id' => 'tablefield-' . $variables['delta'],
- 'class' => array('tablefield', $sortable),
+ 'id' => 'tablefield-' . $id,
+ 'class' => array('tablefield', $sortable, $cols_class),
);
// Apply scope property to headers for accessibility.
@@ -1872,7 +1935,7 @@ function theme_tablefield_view($variables) {
$export = '';
if ($variables['export'] && user_access('export tablefield')) {
$url = sprintf('tablefield/export/%s/%s/%s/%s/%s', $variables['entity_type'], $variables['entity_id'], $variables['field_name'], $variables['langcode'], $variables['delta']);
- $export = '
' . l(t('Export Table Data'), $url) . '
';
+ $export = '
' . l(t('Export Table Data'), $url) . '
';
}
// Prepare variables for theme_tablefield().
@@ -1898,7 +1961,7 @@ function theme_tablefield_view($variables) {
if (isset($variables['sortable'])) {
$theme_variables['sortable'] = $variables['sortable'];
}
- return '
';
@@ -1984,7 +2047,7 @@ function tablefield_trim($tabledata, $ignore_head = FALSE) {
* Whether ignoring header or not.
*/
function tablefield_rtrim_cols($tabledata, $ignore_head = FALSE) {
- $row_num = count($tabledata);
+ $row_num = !empty($tabledata) ? count($tabledata) : 0;
if (!$row_num) {
return $tabledata;
@@ -2063,7 +2126,7 @@ function tablefield_hide_rows($tabledata, $ignore_head = FALSE) {
* Whether ignoring header or not.
*/
function tablefield_hide_cols($tabledata, $ignore_head = FALSE) {
- $row_num = count($tabledata);
+ $row_num = !empty($tabledata) ? count($tabledata) : 0;
if (!$row_num) {
return $tabledata;
@@ -2099,21 +2162,19 @@ function tablefield_multiple_field_remove_button_field_widgets_alter(&$fieldwidg
* Avoid empty tables on multivalue fields with default header values.
*/
function tablefield_form_alter(&$form, &$form_state, $form_id) {
- $instances = field_info_instances();
- $field_names = array();
- foreach ($instances as $entity_type => $entities) {
- foreach ($entities as $bundle => $fields) {
- foreach ($fields as $field_name => $instance) {
- if ($instance['widget']['type'] === 'tablefield') {
- $field_info = field_info_field($field_name);
- if (empty($field_info['field_name'])) {
- return;
- }
- if (isset($form[$field_info['field_name']]) && $field_info['cardinality'] != 1) {
- $field_language = $form[$field_info['field_name']]['#language'];
- $max_delta = $form[$field_info['field_name']][$field_language]['#max_delta'];
- unset($form[$field_name][$field_language][$max_delta]);
- }
+ if (empty($form_state['field'])) {
+ return;
+ }
+
+ foreach (element_children($form_state['field']) as $field_name) {
+ foreach ($form_state['field'][$field_name] as $lang => $value) {
+ if (isset($value['instance']) && $value['instance']['widget']['type'] === 'tablefield' && $value['field']['cardinality'] != 1) {
+ $key_exists = FALSE;
+ $max_delta = $form[$field_name][$lang]['#max_delta'];
+ $parents = array_merge($value['array_parents'], array($field_name, $lang));
+ $element = &drupal_array_get_nested_value($form, $parents, $key_exists);
+ if ($key_exists && isset($element[$max_delta])) {
+ unset($element[$max_delta]);
}
}
}
@@ -2226,7 +2287,7 @@ function theme_tablefield($variables) {
$header = array();
}
// Add sticky headers, if applicable.
- if (count($header) && $sticky) {
+ if (!empty($header) && $sticky) {
drupal_add_js('misc/tableheader.js');
// Add 'sticky-enabled' class to the table to identify it for JS.
// This is needed to target tables constructed by this function.
@@ -2240,7 +2301,7 @@ function theme_tablefield($variables) {
}
// Format the table columns:
- if (count($colgroups)) {
+ if (!empty($colgroups)) {
foreach ($colgroups as $number => $colgroup) {
$attributes = array();
@@ -2260,7 +2321,7 @@ function theme_tablefield($variables) {
}
// Build colgroup.
- if (is_array($cols) && count($cols)) {
+ if (is_array($cols) && !empty($cols)) {
$output .= '
';
$i = 0;
foreach ($cols as $col) {
@@ -2275,7 +2336,7 @@ function theme_tablefield($variables) {
}
// Add the 'empty' row message if available.
- if (!count($rows) && $empty) {
+ if (empty($rows) && $empty) {
$header_count = 0;
foreach ($header as $header_cell) {
if (is_array($header_cell)) {
@@ -2295,25 +2356,25 @@ function theme_tablefield($variables) {
}
// Format the table header:
- if (count($header)) {
+ if (!empty($header)) {
$ts = tablesort_init($header);
// HTML requires that the thead tag has tr tags in it followed by tbody
// tags. Using ternary operator to check and see if we have any rows.
- $output .= (count($rows) ? '
' : '
');
+ $output .= !empty($rows) ? '
' : '
';
foreach ($header as $cell) {
$cell = tablesort_header($cell, $header, $ts);
$output .= _theme_table_cell($cell, TRUE);
}
// Using ternary operator to close the tags based on whether or not there
// are rows.
- $output .= (count($rows) ? "
\n" : "
\n");
+ $output .= !empty($rows) ? " \n" : "\n";
}
else {
$ts = array();
}
// Format the table rows:
- if (count($rows)) {
+ if (!empty($rows)) {
$output .= "\n";
$flip = array('even' => 'odd', 'odd' => 'even');
$class = 'even';
@@ -2332,7 +2393,7 @@ function theme_tablefield($variables) {
$cells = $row;
$attributes = array();
}
- if (count($cells)) {
+ if (!empty($cells)) {
// Add odd/even class.
if (!$no_striping) {
$class = $flip[$class];
diff --git a/profiles/dkan/modules/contrib/tablefield/themeless/README.txt b/profiles/dkan/modules/contrib/tablefield/themeless/README.txt
deleted file mode 100644
index 4cd777056c9..00000000000
--- a/profiles/dkan/modules/contrib/tablefield/themeless/README.txt
+++ /dev/null
@@ -1,26 +0,0 @@
-# TableField Themeless #
-
-Provides themeless output of a node's tablefield on the path 'node/%/themeless'.
-
-
-## INSTALLATION ##
-
-- Enable the submodule at ../admin/modules.
-
-
-## GET STARTED ##
-
-- Go to ../admin/structure/types/manage/[your-content-type]/display/themeless
- and make sure it includes a TableField field.
-- Choose the desired format and format settings.
-- Update.
-- Save.
-- Visit a content page at ../node/%nid/themeless .
-
-
-## TO KEEP IN MIND ##
-
-- Only the first found TableField field will be included in the output (also
- multivalue).
-- Enable https://www.drupal.org/project/subpathauto to have URLs with aliases
- accessible for the themeless output, e.g. ../my/custom/alias/themeless.
diff --git a/profiles/dkan/modules/contrib/tablefield/themeless/tablefield_themeless.info b/profiles/dkan/modules/contrib/tablefield/themeless/tablefield_themeless.info
deleted file mode 100644
index 39d3661fb72..00000000000
--- a/profiles/dkan/modules/contrib/tablefield/themeless/tablefield_themeless.info
+++ /dev/null
@@ -1,12 +0,0 @@
-name = TableField Themeless
-description = Provides themeless output of a node's tablefield on the path 'node/%/themeless'.
-core = 7.x
-package = Fields
-dependencies[] = tablefield
-
-; Information added by Drupal.org packaging script on 2017-06-13
-version = "7.x-3.1"
-core = "7.x"
-project = "tablefield"
-datestamp = "1497359647"
-
diff --git a/profiles/dkan/modules/contrib/tablefield/themeless/tablefield_themeless.module b/profiles/dkan/modules/contrib/tablefield/themeless/tablefield_themeless.module
deleted file mode 100644
index 885dbd0f556..00000000000
--- a/profiles/dkan/modules/contrib/tablefield/themeless/tablefield_themeless.module
+++ /dev/null
@@ -1,108 +0,0 @@
- 'Themeless TableField',
- 'page callback' => 'tablefield_themeless_view',
- 'page arguments' => array(1),
- 'access arguments' => array('access content'),
- );
-
- return $items;
-}
-
-/**
- * Implements hook_menu_local_tasks_alter().
- */
-function tablefield_themeless_menu_local_tasks_alter(&$data, $router_item, $root_path) {
- $node = is_numeric(arg(1)) ? node_load(arg(1)) : NULL;
- // Get all fields of entity type.
- $fields = $node ? field_info_instances('node', $node->type) : array();
- // Get all table fields.
- $tablefield = array();
- foreach ($fields as $key => $value) {
- if ($value['widget']['type'] === 'tablefield') {
- $tablefield[] = $key;
- }
- }
- // Add a 'Themeless TableField' tab only if the content type has a TableField.
- if ($node && $root_path == 'node/%' && isset($tablefield[0]) && !empty($tablefield[0])) {
- $data['tabs'][0]['output'][] = array(
- '#theme' => 'menu_local_task',
- '#link' => array(
- 'title' => t('Themeless TableField'),
- 'href' => 'node/' . arg(1) . '/themeless',
- 'localized_options' => array(),
- ),
- );
- }
-}
-
-/**
- * Get a node by a menucallback and return the first table field as JSON.
- *
- * @param object $node
- * Fully loaded node object.
- */
-function tablefield_themeless_view($node) {
- // Get all fields of entity type.
- $fields = field_info_instances('node', $node->type);
- // Get all table fields.
- $tablefield = array();
- foreach ($fields as $key => $value) {
- if ($value['widget']['type'] === 'tablefield') {
- $tablefield[] = $key;
- }
- }
- // Populate $node->content with a render() array.
- node_build_content($node, 'themeless');
-
- $build = $node->content;
-
- // Get the field instance of the first found table field.
- $instance = isset($tablefield[0]) ? field_info_instance('node', $tablefield[0], $node->type) : NULL;
- $settings = isset($instance) ? field_get_display($instance, 'themeless', 'node') : NULL;
- // XML.
- if (isset($settings['settings']['xml']) && isset($build[$tablefield[0]][0]['#markup']) && $settings['settings']['xml']) {
- // We are returning XML, so tell the browser.
- drupal_add_http_header('Content-Type', 'application/xml');
- // Render the content of the first found table field.
- print $build[$tablefield[0]][0]['#markup'];
- }
- // JSON.
- elseif (isset($build[$tablefield[0]][0]['#markup'])) {
- // We are returning JSON, so tell the browser.
- drupal_add_http_header('Content-Type', 'application/json');
- // Render the content of the first found table field.
- print $build[$tablefield[0]][0]['#markup'];
- }
- // HTML.
- elseif ($tablefield[0] && $settings['type'] !== 'format_themeless') {
- $output = field_view_field('node', $node, $tablefield[0], $settings);
- print drupal_render($output);
- }
- else {
- $nodata['code'] = $instance ? 204 : 404;
- $nodata['message'] = $instance ? t('No Content: the tablefield is empty') : t('Not Found: no tablefield found');
- print drupal_json_output($nodata);
- }
-}
-
-/**
- * Implements hook_entity_info_alter().
- */
-function tablefield_entity_info_alter(&$entity_info) {
- $entity_info['node']['view modes']['themeless'] = array(
- 'label' => t('Themeless'),
- 'custom settings' => FALSE,
- );
-}
diff --git a/profiles/dkan/modules/contrib/taxonomy_menu/LICENSE.txt b/profiles/dkan/modules/contrib/taxonomy_menu/LICENSE.txt
old mode 100755
new mode 100644
diff --git a/profiles/dkan/modules/contrib/taxonomy_menu/README.txt b/profiles/dkan/modules/contrib/taxonomy_menu/README.txt
index cb404b39c41..83250ab19fe 100644
--- a/profiles/dkan/modules/contrib/taxonomy_menu/README.txt
+++ b/profiles/dkan/modules/contrib/taxonomy_menu/README.txt
@@ -187,7 +187,7 @@ MENU PATH TYPE: HIERARCHY
when clicking on the menu items. It produces nice breadcrumbs and page titles (remember to set
the titles for the arguments in views as described) - and it always displays descendants.
The only module that supports the saving of a whole term lineage when selecting a deep level
- item seems to be HIERACHICAL SELECT. See chapter INTEGRATION WITH OTHER MODULES
+ item seems to be HIERARCHICAL SELECT. See chapter INTEGRATION WITH OTHER MODULES
INTEGRATION WITH OTHER MODULES
==============================
@@ -205,7 +205,7 @@ CONTENT TAXONOMY
It is a nice and very helpful module to link taxonomy terms to nodes.
Taxonomy Menu does not interface with the content taxonomy tables, so be sure to enable the option
"Save values additionally to the core taxonomy system (into the 'term_node' table)"
-otherwise the related taxonomy terms will not be accessable for Taxonomy Menu.
+otherwise the related taxonomy terms will not be accessible for Taxonomy Menu.
(http://drupal.org/project/content_taxonomy)
HIERARCHICAL SELECT with submodule HS_TAXONOMY
@@ -241,7 +241,7 @@ ADDITIONAL NOTES
================
* Taxonomy Menu does not handle the menu call backs. It only creates the links to the menus.
This means that everythign that is displayed on the page (including title, content, breadcrumbs, etc)
- are not controled by Taxonony Menu.
+ are not controlled by Taxonony Menu.
* The router item must be created before Taxonomy Menu creates the links. Failure to so so
will cause the menu items to not be created.
* Router items can be created by either a view or another modules hook_menu.
diff --git a/profiles/dkan/modules/contrib/taxonomy_menu/UPGRADE.txt b/profiles/dkan/modules/contrib/taxonomy_menu/UPGRADE.txt
deleted file mode 100644
index 76391dae858..00000000000
--- a/profiles/dkan/modules/contrib/taxonomy_menu/UPGRADE.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-TAXONOMY MENU
-=============
-
-Upgrading from 6.1 => 6.2
-=========================
-1. Copy new version to sites/all/modules/taxonomy_menu
-2. Run update.php
-
-If that doesn't work then follow:
-1. Disable the module at admin/build/modules
-2. Uninstall the module at admin/build/modules/uninstall
-3. Copy new version to sites/all/modules/taxonomy_menu
-4. Enable the new version at admin/build/modules
-
-See README.TXT for configuration options.
-
-The best method is to treat this upgrade as a new module.
-The setting of the previous version will not be upgraded.
\ No newline at end of file
diff --git a/profiles/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.batch.inc b/profiles/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.batch.inc
index 1efec70e6f0..8d534c03250 100644
--- a/profiles/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.batch.inc
+++ b/profiles/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.batch.inc
@@ -6,61 +6,52 @@
*/
/**
- * The $batch can include the following values. Only 'operations'
- * and 'finished' are required, all others will be set to default values.
- *
- * @param operations
- * An array of callbacks and arguments for the callbacks.
- * There can be one callback called one time, one callback
- * called repeatedly with different arguments, different
- * callbacks with the same arguments, one callback with no
- * arguments, etc.
- *
- * @param finished
- * A callback to be used when the batch finishes.
- *
- * @param title
- * A title to be displayed to the end user when the batch starts.
- *
- * @param init_message
- * An initial message to be displayed to the end user when the batch starts.
- *
- * @param progress_message
- * A progress message for the end user. Placeholders are available.
- * Placeholders note the progression by operation, i.e. if there are
- * 2 operations, the message will look like:
- * 'Processed 1 out of 2.'
- * 'Processed 2 out of 2.'
- * Placeholders include:
- * @current, @remaining, @total and @percentage
- *
- * @param error_message
- * The error message that will be displayed to the end user if the batch
- * fails.
+ *Starts a batch operation.
*
+ * @param int $vid
+ * Vocabulary ID.
*/
function _taxonomy_menu_insert_link_items_batch($vid) {
- $terms = taxonomy_get_tree($vid, 0, NULL, TRUE);
+ $depth = variable_get(_taxonomy_menu_build_variable('max_depth', $vid), 0);
+ if ($depth == 0) {
+ $depth = NULL;
+ }
+ $terms = taxonomy_get_tree($vid, 0, $depth, TRUE);
$menu_name = variable_get(_taxonomy_menu_build_variable('vocab_menu', $vid), FALSE);
$batch = array(
+ // An array of callbacks and arguments for the callbacks.
'operations' => array(
array('_taxonomy_menu_insert_link_items_process', array($terms, $menu_name)),
),
+ // A callback to be used when the batch finishes.
'finished' => '_taxonomy_menu_insert_link_items_success',
+ // A title to be displayed to the end user when the batch starts.
'title' => t('Rebuilding Taxonomy Menu'),
+ // An initial message to be displayed to the end user when the batch starts.
'init_message' => t('The menu items have been deleted, and are about to be regenerated.'),
+ // A progress message for the end user. Placeholders are available.
+ // Placeholders include: @current, @remaining, @total and @percentage
'progress_message' => t('Import progress: Completed @current of @total stages.'),
- 'redirect' => 'admin/structure/taxonomy',
+ // The message that will be displayed to the end user if the batch fails.
'error_message' => t('The Taxonomy Menu rebuild process encountered an error.'),
+ 'redirect' => 'admin/structure/taxonomy',
);
batch_set($batch);
batch_process();
}
-
-/*
- * Insert 10 menu link items
+/**
+ * Batch operation callback function: inserts 10 menu link items.
+ *
+ * @param array $terms
+ * Taxonomy terms as from taxonomy_get_tree().
+ * @param string $menu_name
+ * Setting for the menu item name assigned to the vocabulary.
+ * @param array $context
+ * Batch context array.
+ *
+ * @see _taxonomy_menu_insert_link_items_batch().
*/
function _taxonomy_menu_insert_link_items_process($terms, $menu_name, &$context) {
_taxonomy_menu_batch_init_context($context, $start, $end, 10);
@@ -79,20 +70,27 @@ function _taxonomy_menu_insert_link_items_process($terms, $menu_name, &$context)
-/*
- * Set a message stating the menu has been updated
+/**
+ * Batch finished callback: sets a message stating the menu has been updated.
+ *
+ * @see _taxonomy_menu_insert_link_items_batch().
*/
function _taxonomy_menu_insert_link_items_success() {
// TODO state menu name here.
drupal_set_message(t('The Taxonomy Menu has been updated.'));
}
-/*
- * Initialise the batch context
- * @param array $context Batch context array.
- * @param int $start The item to start on in this pass
- * @param int $end The end item of this pass
- * @param int $items The number of items to process in this pass
+/**
+ * Initialise the batch context.
+ *
+ * @param array $context
+ * Batch context array.
+ * @param int $start
+ * The item to start on in this pass.
+ * @param int $end
+ * The end item of this pass.
+ * @param int $items
+ * The number of items to process in this pass.
*/
function _taxonomy_menu_batch_init_context(&$context, &$start, &$end, $items) {
// Initialize sandbox the first time through.
@@ -104,17 +102,20 @@ function _taxonomy_menu_batch_init_context(&$context, &$start, &$end, $items) {
$end = $start + $items;
}
-
-/*
+/**
* Update the batch context
*
- * @param array $context Batch context array.
- * @param int $end The end point of the most recent pass
- * @param int $total The total number of items to process in this batch
- * @param str $msg Message for the progress bar
+ * @param array $context
+ * Batch context array.
+ * @param int $end
+ * The end point of the most recent pass.
+ * @param int $total
+ * The total number of items to process in this batch.
+ * @param str $msg
+ * Message for the progress bar.
+ * @see _taxonomy_menu_insert_link_items_process().
*/
function _taxonomy_menu_batch_update_context(&$context, $end, $total, $msg) {
- //Update context array
if ($end > $total) {
$context['finished'] = 1;
return;
diff --git a/profiles/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.database.inc b/profiles/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.database.inc
index 2f127e35d66..aadf4632717 100644
--- a/profiles/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.database.inc
+++ b/profiles/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.database.inc
@@ -6,12 +6,15 @@
* Database functions
*/
- /**
- * helper function to insert a menu item
+/**
+ * Inserts a menu item.
*
- * @param $mlid
- * @param $tid
- * @param $vid
+ * @param int $mlid
+ * Menu link ID.
+ * @param int $tid
+ * Taxonomy term ID.
+ * @param int $vid
+ * Vocabulary ID.
*/
function _taxonomy_menu_insert_menu_item($mlid, $tid, $vid) {
$fields = array(
@@ -23,10 +26,14 @@ function _taxonomy_menu_insert_menu_item($mlid, $tid, $vid) {
}
/**
- * Return the corresponding menu link id.
+ * Returns the corresponding menu link id.
*
- * @param $tid
- * the term's id
+ * @param int $tid
+ * Taxonomy term ID.
+ * @param int $vid
+ * Vocabulary ID.
+ * @return int
+ * Menu link ID for that taxonomy term.
*/
function _taxonomy_menu_get_mlid($tid, $vid) {
$where = array(
@@ -37,28 +44,26 @@ function _taxonomy_menu_get_mlid($tid, $vid) {
}
/**
- * Retrieve the term / menu relations for a vocab.
+ * Retrieves the term / menu relations for a vocab.
*
* @param $vid
- * vocabulary's id
- * @return
- * array(tid => mlid)
+ * Vocabulary ID.
+ *
+ * @return array
+ * Relations to menu link ID as an array keyed by taxonomy term ID.
+ * array(tid => mlid)
*/
function _taxonomy_menu_get_menu_items($vid) {
-
- $result = db_query('SELECT tid, mlid FROM {taxonomy_menu} WHERE vid = :vid', array(':vid' => $vid));
- $menu_items = array();
- $menu_items = $result->fetchAllKeyed();
- return $menu_items;
+ return db_query('SELECT tid, mlid FROM {taxonomy_menu} WHERE vid = :vid', array(':vid' => $vid))->fetchAllKeyed();
}
- /**
- * Delete all links associated with this vocab from both the taxonomy_menu
- * table and the menu_link table.
- *
- * @param $vid
- * vocabulary's id
- */
+/**
+ * Deletes all links associated with this vocab from both the taxonomy_menu
+ * table and the menu_link table.
+ *
+ * @param int $vid
+ * Vocabulary ID.
+ */
function _taxonomy_menu_delete_all($vid) {
$menu_terms = _taxonomy_menu_get_menu_items($vid);
if (!empty($menu_terms)) {
@@ -74,10 +79,13 @@ function _taxonomy_menu_delete_all($vid) {
}
/**
- * Get an array of the tid's related to the node
+ * Gets an array of the tid's related to the node
+ *
+ * @param object $node
+ * Node object.
*
- * @param $node
- * @return array of tids
+ * @return array
+ * Array of taxonomy term IDs.
*/
function _taxonomy_menu_get_node_terms($node) {
// Get the taxonomy fields.
@@ -89,10 +97,8 @@ function _taxonomy_menu_get_node_terms($node) {
if (isset($node->$field_name)) {
$tid_field = $node->$field_name;
// Loop through all the languages.
-
foreach ($tid_field as $tid_field_languages) {
// Loop through all the tids
-
foreach ($tid_field_languages as $tid) {
$tids[] = $tid['tid'];
}
@@ -103,10 +109,13 @@ function _taxonomy_menu_get_node_terms($node) {
}
/**
- * Get an array of the tid's from the parent
+ * Gets the parent tids for a taxonomy term.
*
- * @param $tid
- * @return array of tid
+ * @param int $tid
+ * Taxonomy term ID.
+ *
+ * @return array
+ * Array of taxonomy term IDs.
*/
function _taxonomy_menu_get_parents($tid) {
$output = array();
@@ -118,21 +127,28 @@ function _taxonomy_menu_get_parents($tid) {
}
/**
- * Delete all rows from {taxomony_menu} associated with this tid
- *
- * @param $vid
- * @param $tid
- */
+ * Deletes all rows from {taxomony_menu} associated with this tid
+ *
+ * @param $vid
+ * @param $tid
+ * @param int $vid
+ * Vocabulary ID.
+ * @param int $tid
+ * Taxonomy term ID.
+ */
function _taxonomy_menu_delete_item($vid, $tid) {
$and = db_and()->condition('vid', $vid)->condition('tid', $tid);
db_delete('taxonomy_menu')->condition($and)->execute();
}
/**
- * Get all of the tid for a given vid
+ * Gets all taxonomy terms for a given vocabulary.
*
- * @param $vid
- * @return array of $tid
+ * @param int $vid
+ * Vocabulary ID.
+ *
+ * @return array
+ * Array of taxonomy term IDs.
*/
function _taxonomy_menu_get_terms($vid) {
$result = db_select('taxonomy_term_data', 'td')
@@ -143,11 +159,15 @@ function _taxonomy_menu_get_terms($vid) {
}
/**
- * @TODO Needs Updating since terms are related via node fields
+ * Gets the count of nodes for each term (without children).
*
- * used to get the count without children
+ * @param int $tid
+ * Taxonomy term ID.
*
- * @param $tid
+ * @return int
+ * Count of nodes that reference the term.
+ *
+ * @todo Needs updating since terms are related via fields now.
*/
function _taxonomy_menu_term_count($tid) {
$result = db_select('taxonomy_index', 'tn');
@@ -160,36 +180,44 @@ function _taxonomy_menu_term_count($tid) {
}
/**
- * Get tid for a given mlid
+ * Gets tid for a given mlid.
*
- * @param $mlid
- * @return $tid
+ * @param int $mlid
+ * Menu link ID.
+ *
+ * @return int $tid
+ * Taxonomy term ID.
*/
function _taxonomy_menu_get_tid($mlid) {
- $where = array(
- ':mlid' => $mlid,
- );
- return db_query('SELECT tid FROM {taxonomy_menu} WHERE mlid = :mlid', $where)->fetchField();
+ return db_query('SELECT tid FROM {taxonomy_menu} WHERE mlid = :mlid', array(':mlid' => $mlid))->fetchField();
}
/**
- * Get vid, tid for a given mlid
+ * Gets the vocabulary ID and taxonomy term ID for a given menu link ID.
+ *
+ * @param int $mlid
+ * Menu link ID.
*
- * @param $mlid
- * @return array of vid, tid
+ * @return array
+ * array(vid, tid)
*/
function _taxonomy_menu_get_item($mlid) {
$result = db_select('taxonomy_menu', 'tm')
->condition('mlid', $mlid, '=')
->fields('tm', array('tid', 'vid'))
->execute();
+
return $result->fetch();
}
/**
- * Get the vocabulary for a tid
- * @param $tid array of tids
+ * Gets the vocabulary for a taxonomy term ID.
+ *
+ * @param array $tids
+ * Taxonomy term IDs.
+ *
* @return $vid
+ * Vocabulary ID.
*/
function _taxonomy_menu_get_vid_by_tid($tids) {
if ($tids) {
@@ -198,11 +226,6 @@ function _taxonomy_menu_get_vid_by_tid($tids) {
->fields('term_data', array('vid'))
->distinct()
->execute();
- $vids = array();
return $result->fetchAllAssoc('vid');
}
}
-
-/**
- * Options functions
- */
diff --git a/profiles/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.info b/profiles/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.info
index 295734e2394..d0f6be973a0 100644
--- a/profiles/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.info
+++ b/profiles/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.info
@@ -1,18 +1,14 @@
-core = "7.x"
+name = Taxonomy menu
+description = Adds links to taxonomy terms into a menu.
+core = 7.x
+
dependencies[] = taxonomy
dependencies[] = menu
-description = "Adds links to taxonomy terms to a menu."
-name = "Taxonomy menu"
-package = Taxonomy menu
-files[] = taxonomy_menu.batch.inc
-files[] = taxonomy_menu.database.inc
-files[] = taxonomy_menu.module
+
files[] = taxonomy_menu.test
-files[] = taxonomy_menu.install
-; Information added by Drupal.org packaging script on 2014-04-01
-version = "7.x-1.5"
+; Information added by Drupal.org packaging script on 2018-12-22
+version = "7.x-1.6"
core = "7.x"
project = "taxonomy_menu"
-datestamp = "1396359247"
-
+datestamp = "1545492784"
diff --git a/profiles/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.install b/profiles/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.install
index 53ee938f898..83b76209ccc 100644
--- a/profiles/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.install
+++ b/profiles/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.install
@@ -9,34 +9,57 @@
* Implements hook_uninstall().
*/
function taxonomy_menu_uninstall() {
-
- //remove menu items
+ // Remove menu items.
db_delete('menu_links')->condition('module', 'taxonomy_menu', '=')->execute();
- //rebuild the menus
+ // Rebuild the menus.
variable_set('menu_rebuild_needed', TRUE);
- // Delete variables
- $query = db_select('variable', 'v')->fields('v')->execute();
+ // Gets array of more specific variables set by Taxonomy Menu module.
+ // This prevents known issue https://www.drupal.org/node/1966684
+ // where uninstalling Taxonomy Menu will delete variables related
+ // to the Taxonomy Menu Block module.
+ $variable_suffixes = array(
+ 'vocab_menu',
+ 'vocab_parent',
+ 'voc_item',
+ 'display_num',
+ 'hide_empty_terms',
+ 'expanded',
+ 'rebuild',
+ 'path',
+ 'menu_end_all',
+ 'display_descendants',
+ 'voc_name',
+ 'sync',
+ 'end_all',
+ 'flat',
+ 'max_depth',
+ 'term_item_description',
+ );
+
+ // Delete variables.
+ $query = db_select('variable', 'v')
+ ->fields('v', array('name', 'value'))
+ ->condition('name', '%' . db_like('taxonomy_menu') . '%', 'LIKE')
+ ->execute();
$variables = $query->fetchAll();
foreach ($variables as $variable) {
- if (strpos($variable->name, 'taxonomy_menu') !== FALSE) {
- variable_del($variable->name);
+ // Add check to make sure we only delete variables for Taxonomy Menu,
+ // not variables for Taxonomy Menu Block.
+ foreach ($variable_suffixes as $suffix) {
+ $taxonomy_menu_variable_name = 'taxonomy_menu_' . $suffix;
+ if (strpos($variable->name, $taxonomy_menu_variable_name) !== FALSE) {
+ variable_del($variable->name);
+ }
}
}
}
-/**
- * Implements hook_install().
- */
-function taxonomy_menu_install() {
-}
-
/**
* Implements hook_schema().
*/
function taxonomy_menu_schema() {
-
$schema['taxonomy_menu'] = array(
'description' => 'Links a taxonomy term to a menu item.',
'fields' => array(
diff --git a/profiles/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.module b/profiles/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.module
index 7bcea712467..7bec26f40a9 100644
--- a/profiles/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.module
+++ b/profiles/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.module
@@ -2,14 +2,27 @@
/**
* @file
- * It generates menu links for all selected taxonomy terms
+ * Adds links to taxonomy terms into a menu.
*/
-//include the database layer
-require_once(DRUPAL_ROOT . '/' . drupal_get_path('module', 'taxonomy_menu') . '/taxonomy_menu.database.inc');
+// Include the database layer.
+module_load_include('inc', 'taxonomy_menu', 'taxonomy_menu.database');
-//include the batch functions
-require_once(DRUPAL_ROOT . '/' . drupal_get_path('module', 'taxonomy_menu') . '/taxonomy_menu.batch.inc');
+// Include the batch functions.
+module_load_include('inc', 'taxonomy_menu', 'taxonomy_menu.batch');
+
+/**
+ * Implements hook_help().
+ */
+function taxonomy_menu_help($path, $arg) {
+ switch ($path) {
+ case 'admin/help#taxonomy_menu':
+ $output = '';
+ $output .= '
' . t('The Taxonomy Menu module transforms your taxonomy vocabularies into menus.') . '
';
+ $output .= '
' . t('For more information, please visit the official project page on Drupal.org.', array('@url' => 'https://www.drupal.org/project/taxonomy_menu')) . '
';
+ return $output;
+ }
+}
/**
* Implements hook_form_alter().
@@ -19,7 +32,7 @@ require_once(DRUPAL_ROOT . '/' . drupal_get_path('module', 'taxonomy_menu') . '/
*/
function taxonomy_menu_form_alter(&$form, &$form_state, $form_id) {
if ($form_id == 'taxonomy_form_vocabulary') {
- // do not alter on deletion
+ // Do not alter on deletion.
if (isset($form_state['confirm_delete']) && isset($form_state['values']['vid'])) {
return;
}
@@ -61,7 +74,7 @@ function taxonomy_menu_form_alter(&$form, &$form_state, $form_id) {
'#title' => t('Menu location'),
'#default_value' => $default,
'#options' => $menu_items,
- '#description' => t('The menu and parent under which to insert taxonomy menu items.'),
+ '#description' => t('Taxonomy menu items will be inserted below the item selected here.'),
'#attributes' => array('class' => array('menu-title-select')),
);
@@ -73,7 +86,7 @@ function taxonomy_menu_form_alter(&$form, &$form_state, $form_id) {
'#description' => t('The path will be taxonomy/term/tid if Default has been selected. The menu path will be passed through drupal_get_path_alias() function so all aliases will be applied.'),
);
- //get taxonomy menu form options
+ // Get taxonomy menu form options.
if (isset($form['vid']) && $form['vid']['#value']) {
$vid = $form['vid']['#value'];
}
@@ -82,26 +95,26 @@ function taxonomy_menu_form_alter(&$form, &$form_state, $form_id) {
}
$form['taxonomy_menu']['options'] = _taxonomy_menu_create_options($vid);
- //rebuild the menu
+ // Rebuild the menu.
$form['taxonomy_menu']['options']['rebuild'] = array(
'#type' => 'checkbox',
- '#title' => t('Select to rebuild the menu on submit.'),
+ '#title' => t('Rebuild the menu on submit'),
'#default_value' => 0,
'#weight' => 20,
- '#description' => t('Rebuild the menu on submit. Warning: This will delete then re-create all of the menu items. Only use this option if you are experiencing issues like missing menu items or other inconsistencies.'),
+ '#description' => t('Warning: This will delete then re-create all of the menu items. Only use this option if you are experiencing issues like missing menu items or other inconsistencies.'),
);
- // move the buttons to the bottom of the form
+ // Move the buttons to the bottom of the form.
$form['submit']['#weight'] = 49;
$form['delete']['#weight'] = 50;
- // add an extra submit handler to save these settings
+ // Add an extra submit handler to save these settings.
$form['#submit'][] = 'taxonomy_menu_vocab_submit';
}
elseif ($form_id == "taxonomy_overview_terms") {
- // add an extra submit handler to sync the rearranged terms with menu
+ // Add an extra submit handler to sync the rearranged terms with menu.
// @ TODO: using hook_taxonomy_vocabulary_update is nicer then callback,
- // but gives less info and does not always fire.
+ // But gives less info and does not always fire.
$form['#submit'][] = 'taxonomy_menu_overview_submit';
}
}
@@ -117,33 +130,33 @@ function taxonomy_menu_vocab_submit($form, &$form_state) {
$changed = FALSE;
if (is_numeric($form_state['values']['taxonomy_menu']['vocab_parent'])) {
- // Menu location has been set to disabled, don't want to throw notices
+ // Menu location has been set to disabled, don't want to throw notices.
$form_state['values']['taxonomy_menu']['vocab_parent'] = '0:0';
}
// Split the menu location into menu name and menu item id.
list($vocab_parent['vocab_menu'], $vocab_parent['vocab_parent']) = explode(':', $form_state['values']['taxonomy_menu']['vocab_parent']);
- // Init flag variables to avoid notices if changes haven't happened
+ // Init flag variables to avoid notices if changes haven't happened.
$changed_menu = FALSE;
$change_vocab_item = FALSE;
$changed_path = FALSE;
- // Set the menu name and check for changes
+ // Set the menu name and check for changes.
$variable_name = _taxonomy_menu_build_variable('vocab_menu', $vid);
if (_taxonomy_menu_check_variable($variable_name, $vocab_parent['vocab_menu'])) {
$changed_menu = TRUE;
}
variable_set($variable_name, $vocab_parent['vocab_menu']);
- // Set the menu parent item and check for changes
+ // Set the menu parent item and check for changes.
$variable_name = _taxonomy_menu_build_variable('vocab_parent', $vid);
if (_taxonomy_menu_check_variable($variable_name, $vocab_parent['vocab_parent'])) {
$changed_menu = TRUE;
}
variable_set($variable_name, $vocab_parent['vocab_parent']);
- // Set the path and check for changes
+ // Set the path and check for changes.
$variable_name = _taxonomy_menu_build_variable('path', $vid);
if (_taxonomy_menu_check_variable($variable_name, $form_state['values']['taxonomy_menu']['path'])) {
$changed_path = TRUE;
@@ -154,14 +167,14 @@ function taxonomy_menu_vocab_submit($form, &$form_state) {
// Create the variable name
$variable_name = _taxonomy_menu_build_variable($key, $vid);
- // Check to see if the vocab enable options has changed
+ // Check to see if the vocab enable options has changed.
if ($key == 'voc_item') {
if (_taxonomy_menu_check_variable($variable_name, $value)) {
$change_vocab_item = TRUE;
}
}
- // If $changed is alreayd set to true, then don't bother checking any others.
+ // If $changed is already set to true, then don't bother checking any others.
if (!$changed) {
// Check to see if the variable has changed.
if (_taxonomy_menu_check_variable($variable_name, $value)) {
@@ -190,14 +203,14 @@ function taxonomy_menu_vocab_submit($form, &$form_state) {
// Only send a message if one has been created.
if (isset($message) && $message) {
// $message is sanitized coming out of its source function,
- // no need to reclean it here
+ // No need to reclean it here.
drupal_set_message($message, 'status');
}
}
}
/**
- * Submit handler, reacting on form ID: taxonomy_overview_terms
+ * Submit handler, reacting on form ID: taxonomy_overview_terms.
*/
function taxonomy_menu_overview_submit(&$form, &$form_state) {
// Only sync if taxonomy_menu is enabled for this vocab and the 'sync'
@@ -215,7 +228,7 @@ function taxonomy_menu_overview_submit(&$form, &$form_state) {
$vid = $form['#vocabulary']->vid;
}
elseif ($form_state['rebuild'] == TRUE && isset($form['#vocabulary']->vid) ) {
- // Try to catch the 'Reset to alphabetical' button
+ // Try to catch the 'Reset to alphabetical' button.
$vid = NULL;
}
elseif ($form_state['rebuild'] == FALSE && isset($form['vid']['#value']) ) {
@@ -236,8 +249,8 @@ function taxonomy_menu_overview_submit(&$form, &$form_state) {
// Report status.
if (isset($message)) {
- // message is sanitized coming out of _taxonomy_menu_update_link_items
- // no need to reclean it here
+ // Message is sanitized coming out of _taxonomy_menu_update_link_items
+ // no need to reclean it here.
drupal_set_message($message, 'status');
}
@@ -248,17 +261,19 @@ function taxonomy_menu_overview_submit(&$form, &$form_state) {
}
/**
- * rebuilds a menu
+ * Rebuilds a menu.
*
- * @param $vid
- * @return $message
- * message that is displayed
+ * @param int $vid
+ * Vocabulary ID.
+ *
+ * @return string
+ * Message that is displayed.
*/
function _taxonomy_menu_rebuild($vid) {
- // Remove all of the menu items for this vocabulary
+ // Remove all of the menu items for this vocabulary.
_taxonomy_menu_delete_all($vid);
- // Only insert the links if a menu is set
+ // Only insert the links if a menu is set.
if (variable_get(_taxonomy_menu_build_variable('vocab_menu', $vid), FALSE)) {
_taxonomy_menu_insert_link_items($vid);
menu_rebuild();
@@ -272,10 +287,11 @@ function _taxonomy_menu_rebuild($vid) {
/**
* Checks to see if the variable has changed.
*
- * @param $variable
- * name of variable
- * @return Boolean
- * TRUE if it has changed
+ * @param string $variable
+ * Name of variable.
+ *
+ * @return bool
+ * TRUE if it has changed
*/
function _taxonomy_menu_check_variable($variable, $new_value) {
if ($new_value != variable_get($variable, FALSE)) {
@@ -285,19 +301,21 @@ function _taxonomy_menu_check_variable($variable, $new_value) {
}
/**
- * Update the menu items
+ * Updates the menu items.
*
* @param $vid
- * vocab id
+ * Vocabulary ID.
*/
function _taxonomy_menu_update_link_items($vid) {
$menu_name = variable_get(_taxonomy_menu_build_variable('vocab_menu', $vid), FALSE);
+ $depth = variable_get(_taxonomy_menu_build_variable('max_depth', $vid), 0);
- // Get a list of the current tid - menu_link combinations
+ // Get a list of the current tid - menu_link combinations.
$menu_links = _taxonomy_menu_get_menu_items($vid);
- // Cycle through the menu links
+ // Cycle through the menu links.
foreach ($menu_links as $tid => $mlid) {
+ if (!_taxonomy_menu_term_too_deep($tid, $depth))
// $args must be reset each time through.
$args = array(
'menu_name' => $menu_name,
@@ -311,7 +329,7 @@ function _taxonomy_menu_update_link_items($vid) {
$args['term'] = taxonomy_term_load($tid);
}
- //update the menu link
+ // Update the menu link.
taxonomy_menu_handler('update', $args);
}
@@ -319,13 +337,14 @@ function _taxonomy_menu_update_link_items($vid) {
}
/**
- * Creates new link items for the vocabulary
+ * Creates new link items for the vocabulary.
*
* @param $vid
+ * Vocabulary ID.
*/
function _taxonomy_menu_insert_link_items($vid) {
$menu_name = variable_get(_taxonomy_menu_build_variable('vocab_menu', $vid), FALSE);
- // Check to see if we should had a vocab item
+ // Check to see if we should had a vocab item.
if (variable_get(_taxonomy_menu_build_variable('voc_item', $vid), FALSE)) {
$args = array(
'vid' => $vid,
@@ -333,7 +352,7 @@ function _taxonomy_menu_insert_link_items($vid) {
);
taxonomy_menu_handler('insert', $args);
}
- // Let batch api take care of inserting the menu items
+ // Let batch api take care of inserting the menu items.
_taxonomy_menu_insert_link_items_batch($vid);
}
@@ -341,16 +360,34 @@ function _taxonomy_menu_insert_link_items($vid) {
* Implements hook_taxonomy_vocabulary_delete().
*/
function taxonomy_menu_taxonomy_vocabulary_delete($vocabulary) {
- //delete the menu items
+ // Delete the menu items for this vocab.
_taxonomy_menu_delete_all($vocabulary->vid);
$menu_name = variable_get(_taxonomy_menu_build_variable('vocab_menu', $vocabulary->vid), 0);
menu_cache_clear($menu_name);
+
+ // Delete all the variables for this vocab.
+ $variable_prefixes = array(
+ 'taxonomy_menu_vocab_menu_',
+ 'taxonomy_menu_vocab_parent_',
+ 'taxonomy_menu_voc_item_',
+ 'taxonomy_menu_display_num_',
+ 'taxonomy_menu_hide_empty_terms_',
+ 'taxonomy_menu_expanded_',
+ 'taxonomy_menu_rebuild_',
+ 'taxonomy_menu_path_',
+ 'taxonomy_menu_menu_end_all_',
+ 'taxonomy_menu_display_descendants_',
+ 'taxonomy_menu_voc_name_',
+ 'taxonomy_menu_sync_',
+ );
+ foreach ($variable_prefixes as $prefix) {
+ variable_del($prefix . $vocabulary->name);
+ }
}
/**
* Implements hook_taxonomy_term_insert($term).
*/
-
function taxonomy_menu_taxonomy_term_insert($term) {
_taxonomy_menu_taxonomy_termapi_helper($term, 'insert');
}
@@ -358,7 +395,6 @@ function taxonomy_menu_taxonomy_term_insert($term) {
/**
* Implements hook_taxonomy_term_update().
*/
-
function taxonomy_menu_taxonomy_term_update($term) {
_taxonomy_menu_taxonomy_termapi_helper($term, 'update');
}
@@ -366,7 +402,6 @@ function taxonomy_menu_taxonomy_term_update($term) {
/**
* Implements hook_taxonomy_term_delete().
*/
-
function taxonomy_menu_taxonomy_term_delete($term) {
_taxonomy_menu_taxonomy_termapi_helper($term, 'delete');
}
@@ -377,7 +412,7 @@ function taxonomy_menu_taxonomy_term_delete($term) {
function taxonomy_menu_node_insert($node) {
$terms_old = &drupal_static('taxonomy_menu_terms_old');
// We use this direct table pull to avoid the cache and because
- // free tags are not formated in a matter where extrating the
+ // free tags are not formatted in a matter where extrating the
// tid's is easy.
$terms_new = _taxonomy_menu_get_node_terms($node);
@@ -390,15 +425,17 @@ function taxonomy_menu_node_insert($node) {
* Implements hook_node_update().
*/
function taxonomy_menu_node_update($node) {
- $terms_old = &drupal_static('taxonomy_menu_terms_old');
- //we use this direct table pull to avoid the cache and because
- //free tags are not formated in a matter where extrating the
- //tid's is easy
- $terms_new = _taxonomy_menu_get_node_terms($node);
-
- //merge current terms and previous terms to update both menu items.
- $terms = array_unique(array_merge((array)$terms_new, (array)$terms_old));
- _taxonomy_menu_nodeapi_helper('update', $terms, $node);
+ if (isset($node->original->status) and $node->original->status != $node->status) {
+ $terms_old = &drupal_static('taxonomy_menu_terms_old');
+ // We use this direct table pull to avoid the cache and because
+ // free tags are not formatted in a matter where extracting the
+ // tid's is easy.
+ $terms_new = _taxonomy_menu_get_node_terms($node);
+
+ // Merge current terms and previous terms to update both menu items.
+ $terms = array_unique(array_merge((array)$terms_new, (array)$terms_old));
+ _taxonomy_menu_nodeapi_helper('update', $terms, $node);
+ }
}
/**
@@ -406,9 +443,9 @@ function taxonomy_menu_node_update($node) {
*/
function taxonomy_menu_node_presave($node) {
$terms_old = &drupal_static('taxonomy_menu_terms_old');
- //get the terms from the database before the changes are made.
- //these will be used to update the menu item's name if needed
- //we go directly to the db to bypass any caches
+ // Get the terms from the database before the changes are made. These will be
+ // used to update the menu item's name if needed we go directly to the db to
+ // bypass any caches.
if (isset($node->nid)) {
$node_old = node_load($node->nid);
$terms_old = _taxonomy_menu_get_node_terms($node_old);
@@ -422,8 +459,8 @@ function taxonomy_menu_node_presave($node) {
* Implements hook_node_delete().
*/
function taxonomy_menu_node_delete($node) {
- // since the delete operation is run after the data is deleted
- // pull the terms from the node object
+ // Since the delete operation is run after the data is deleted pull the terms
+ // from the node object.
$terms = _taxonomy_menu_get_node_terms($node);
_taxonomy_menu_nodeapi_helper('delete', $terms, $node);
}
@@ -436,8 +473,9 @@ function _taxonomy_menu_taxonomy_termapi_helper($term, $operation) {
// option has been checked.
$menu_name = variable_get(_taxonomy_menu_build_variable('vocab_menu', $term->vid), 0);
$sync = variable_get(_taxonomy_menu_build_variable('sync', $term->vid), 0);
+ $depth = variable_get(_taxonomy_menu_build_variable('max_depth', $term->vid), 0);
- if ($menu_name && $sync) {
+ if ($menu_name && $sync && !_taxonomy_menu_term_too_deep($term->tid, $depth)) {
$item = array(
'tid' => $term->tid,
'vid' => $term->vid,
@@ -459,13 +497,13 @@ function _taxonomy_menu_taxonomy_termapi_helper($term, $operation) {
}
$message = t($text, array('%term' => $term->name, '%menu_name' => $menu_name));
- // run function
+ // Run function.
taxonomy_menu_handler($operation, $item);
- // report status
+ // Report status.
drupal_set_message($message, 'status');
- // rebuild the menu
+ // Rebuild the menu.
menu_cache_clear($menu_name);
}
}
@@ -473,28 +511,29 @@ function _taxonomy_menu_taxonomy_termapi_helper($term, $operation) {
/**
* Builds argument arrays calls taxonomy_menu_handler.
*
- * @param $op
- * A string of the operation to be performed [update|insert|delete]
- * @param $terms
- * An array of tids.
+ * @param string $op
+ * The operation to be performed [update|insert|delete]
+ * @param array $terms
+ * The taxonomy terms.
* @param $node
+ * The node object.
*/
function _taxonomy_menu_nodeapi_helper($op, $terms = array(), $node) {
foreach ($terms as $key => $tid) {
-
- // taxonomy_term_load($tid) return FALSE if the term was not found
- // if taxonomy $term is false, then go to the next $term
+ // If taxonomy $term is false, then go to the next $term.
+ // taxonomy_term_load($tid) returns FALSE if the term was not found.
if (!$term = taxonomy_term_load($tid)) {
continue;
}
- // update the menu for each term if necessary
+ // Update the menu for each term if necessary.
$menu_name = variable_get(_taxonomy_menu_build_variable('vocab_menu', $term->vid), FALSE);
$vocb_sync = variable_get(_taxonomy_menu_build_variable('sync', $term->vid), TRUE);
$menu_num = variable_get(_taxonomy_menu_build_variable('display_num', $term->vid), TRUE);
+ $menu_num_inc_children = variable_get(_taxonomy_menu_build_variable('display_num_incl_children', $term->vid), TRUE);
$hide_empty = variable_get(_taxonomy_menu_build_variable('hide_empty_terms', $term->vid), FALSE);
if ($menu_name && $vocb_sync && ($menu_num || $hide_empty)) {
- //build argument array to save menu_item
+ // Build argument array to save menu_item.
$args = array(
'tid' => $term->tid,
'vid' => $term->vid,
@@ -512,7 +551,7 @@ function _taxonomy_menu_nodeapi_helper($op, $terms = array(), $node) {
}
taxonomy_menu_handler($op, $args, $node);
- if (variable_get(_taxonomy_menu_build_variable('hide_empty_terms', $term->vid), FALSE)) {
+ if ($hide_empty || $menu_num && $menu_num_inc_children) {
_taxonomy_menu_update_all_parents($term, $menu_name);
}
}
@@ -520,13 +559,12 @@ function _taxonomy_menu_nodeapi_helper($op, $terms = array(), $node) {
}
/**
- * Update all parent items
- *
- * @param $op
- * options are 'insert', 'update', 'delete' or path
+ * Updates all parent items.
*
+ * @param string $op
+ * options are 'insert', 'update', 'delete' or path.
* @param $node
- * The node object.
+ * The node object.
*/
function _taxonomy_menu_update_all_parents($term, $menu_name) {
$parents = taxonomy_get_parents($term->tid);
@@ -546,43 +584,43 @@ function _taxonomy_menu_update_all_parents($term, $menu_name) {
}
/**
- * HANDLING
+ * Taxonomy Menu Handler: Creates a menu item for each taxonomy term.
*
* @param $op
- * options are 'insert', 'update', 'delete' or path
- *
+ * options are 'insert', 'update', 'delete' or path.
+ * @param $args
+ * if $op == 'insert' then args is an array with the following key/value pairs:
+ * 'term': taxonomy term object,
+ * 'menu_name' : menu that the item is set to apply to
+ * if $op == 'delete' then then args is an array with the following key/value pairs:
+ * 'tid': TermID
+ * 'mlid': Menu ID
+ * if $op == 'update' then then args is an array with the following key/value pairs:
+ * 'term': term object,
+ * 'menu_name': menu that the item is set to apply to
+ * 'mlid': Menu ID
* @param $node
- * The node object.
+ * The node object.
+ * @param $item array
+ * Taxonomy menu item.
*
- * @param $args
- * if $op == 'insert' then
- * array with the following key/value pairs:
- * 'term' => term object,
- * 'menu_name' => menu that the item is set to apply to
- * if $op == 'delete' then
- * array(
- * 'tid' => TermID
- * 'mlid => Menu ID
- * )
- * if $op == 'update' then
- * 'term' => term object,
- * 'menu_name' => menu that the item is set to apply to
- * 'mlid' => Menu ID
+ * @return array
+ * Menu link ID for the taxonomy menu item.
*/
function taxonomy_menu_handler($op, $args = array(), $node = NULL, $item = array()) {
- // Get the initial $item
+ // Get the initial $item.
if (empty($item)) {
$item = _taxonomy_menu_create_item($args, $node);
}
- // Let other modules make edits
+ // Let other modules make edits.
$hook = 'taxonomy_menu_' . $op;
foreach (module_implements($hook) as $module) {
$function = $module . '_' . $hook;
$function($item);
}
- // Update the menu and return the mlid if the remove element is not true
+ // Update the menu and return the mlid if the remove element is not true.
if ($op != 'delete') {
return _taxonomy_menu_save($item);
}
@@ -609,19 +647,20 @@ function taxonomy_menu_handler($op, $args = array(), $node = NULL, $item = array
* 'mlid' => if this is filled in then the mlid will be updated
*/
function _taxonomy_menu_save($item) {
+ if (empty($item)) return;
+
$insert = TRUE;
- $flatten_menu = variable_get(_taxonomy_menu_build_variable('flat',
-$item['vid']));
+ $flatten_menu = variable_get(_taxonomy_menu_build_variable('flat', $item['vid']));
// Child items should appear around the parent/root, so set their weight
- // equal to the root term's
+ // equal to the root term's weight.
if ($flatten_menu) {
$item['weight'] = $item['root_term_weight'];
}
// create the path.
$path = taxonomy_menu_create_path($item['vid'], $item['tid']);
- // get the parent mlid: this is either:
+ // Get the parent mlid: this is either:
// - the parent tid's mlid
// - the vocab menu item's mlid
// - the menu parent setting for this vocab
@@ -630,7 +669,7 @@ $item['vid']));
$plid = variable_get(_taxonomy_menu_build_variable('vocab_parent', $item['vid']), NULL);
}
- // Make sure the path has less then 256 characters
+ // Make sure the path has less then 256 characters.
if (drupal_strlen($path) > 256) {
preg_match('/(.{256}.*?)\b/', $path, $matches);
$path = rtrim($matches[1]);
@@ -640,15 +679,25 @@ $item['vid']));
'link_title' => $item['name'],
'menu_name' => $item['menu_name'],
'plid' => $plid,
- 'options' => array('attributes' => array('title' => trim($item['description'])
- ? $item['description'] : $item['name'])),
'weight' => $item['weight'],
'module' => 'taxonomy_menu',
'expanded' => variable_get(_taxonomy_menu_build_variable('expanded', $item['vid']), TRUE),
'link_path' => $path,
);
- // Add setup the query paramater in the URL correctly
+ // Be sure to load the original menu link to preserve non-standard properties.
+ if (isset($item['mlid']) && !empty($item['mlid']) && $original_link = menu_link_load($item['mlid'])) {
+ $link = array_merge($original_link, $link);
+ }
+ else {
+ $link['options'] = array(
+ 'attributes' => array(
+ 'title' => trim($item['description']) ? $item['description'] : $item['name'],
+ ),
+ );
+ }
+
+ // Add setup the query paramater in the URL correctly.
if (strpos($path, '?') !== FALSE) {
$split = explode('?', $path);
if (strpos($split[1], '?') !== FALSE) {
@@ -661,22 +710,27 @@ $item['vid']));
}
}
- // If passed a mlid then add it
+ // If passed a mlid then add it.
if (isset($item['mlid']) && $item['mlid']) {
$link['mlid'] = $item['mlid'];
$insert = FALSE;
+ $menu_link = menu_link_load($item['mlid']);
+ $is_hidden = $menu_link['hidden'];
+ }
+ else {
+ $is_hidden = 0;
}
- // FIXME: i18nmenu need to be cleaned up to allow translation from other menu module
+ // @todo i18nmenu needs to be cleaned up to allow translation from other menu modules.
if (module_exists('i18n_menu')) {
$link['options']['alter'] = TRUE;
$link['language'] = $item['language'];
$link['customized'] = 1;
}
- // set the has_children property
- // if tid=0 then adding a vocab item and had children
- // if the term has any children then set it to true
+ // Set the has_children property.
+ // If tid=0 then adding a vocab item and had children.
+ // If the term has any children then set it to true.
if ($item['tid'] == 0) {
$link['has_children'] = 1;
}
@@ -687,12 +741,12 @@ $item['vid']));
}
}
- // If remove is true then set hidden to 1
- $link['hidden'] = (isset($item['remove']) && $item['remove']) ? 1 : 0;
+ // If remove is true then set hidden to 1.
+ $link['hidden'] = (isset($item['remove']) && $item['remove']) ? 1 : $is_hidden;
- // Save the menu item
+ // Save the menu item.
if ($mlid = menu_link_save($link)) {
- // if inserting a new menu item then insert a record into the table
+ // if inserting a new menu item then insert a record into the table.
if ($insert) {
_taxonomy_menu_insert_menu_item($mlid, $item['tid'], $item['vid']);
}
@@ -705,10 +759,10 @@ $item['vid']));
}
/**
- * Create the path to use in the menu item
+ * Create the path to use in the menu item.
*
* @return array
- * path selections
+ * Path selections.
*/
function _taxonomy_menu_get_paths() {
return module_invoke_all('taxonomy_menu_path');
@@ -718,25 +772,27 @@ function _taxonomy_menu_get_paths() {
* Creates the path for the vid/tid combination.
*
* @param $vid
+ * Vocablary ID.
* @param $tid
+ * Taxonomy term ID.
+ *
* @return string
- * path
+ * Path
*/
function taxonomy_menu_create_path($vid, $tid) {
- // get the path function for this vocabulary
+ // Get the path function for this vocabulary.
$function = variable_get(_taxonomy_menu_build_variable('path', $vid), 'taxonomy_menu_path_default');
- // run the function
+ // Run the function.
return $function($vid, $tid);
}
/**
* Implements hook_taxonomy_menu_path().
*
- * Invoked from _taxonomy_menu_get_paths.
- *
* @return array
- * function name => Display Title
- * a list of the path options.
+ * A list of the path options in the form: function_name => Display Title.
+ *
+ * @see _taxonomy_menu_get_paths().
*/
function taxonomy_menu_taxonomy_menu_path() {
$output = array(
@@ -747,33 +803,33 @@ function taxonomy_menu_taxonomy_menu_path() {
}
/**
- * Callback for hook_taxonomy_menu_path
+ * Callback for hook_taxonomy_menu_path.
*/
function taxonomy_menu_path_default($vid, $tid) {
- // if tid = 0 then we are creating the vocab menu item format will be taxonomy/term/$tid+$tid+$tid....
+ // If tid = 0 then we are creating the vocab menu item format will be taxonomy/term/$tid+$tid+$tid...
if ($tid == 0) {
- // get all of the terms for the vocab
+ // Get all of the terms for the vocab.
$vtids = _taxonomy_menu_get_terms($vid);
- $end = implode(' ', $vtids);
+ $end = implode('/', $vtids);
$path = "taxonomy/term/$end";
}
else {
$path = 'taxonomy/term/' . $tid;
if (variable_get(_taxonomy_menu_build_variable('display_descendants', $vid), FALSE)) {
- // Use 'all' at the end of the path
+ // Use 'all' at the end of the path.
if (variable_get(_taxonomy_menu_build_variable('end_all', $vid), FALSE)) {
$path .= '/all';
}
else {
- // we wait to run this instead of during the if above
- // because we only wan to run it once.
+ // We wait to run this instead of during the if above because we only
+ // want to run it once.
$terms = taxonomy_get_tree($vid, $tid);
foreach ($terms as $term) {
$tids[] = $term->tid;
}
- if ($tids) {
- $end = implode(' ', $tids);
- $path .= ' ' . $end;
+ if (isset($tids)) {
+ $end = implode('/', $tids);
+ $path .= '/ ' . $end;
}
}
}
@@ -783,39 +839,35 @@ function taxonomy_menu_path_default($vid, $tid) {
}
/**
- * hook_taxonomy_menu_delete
- *
- * @param $args
- * array(
- * 'vid' => Vocab ID
- * 'tid' => TermID
- * 'mlid' => Menu ID
- * )
+ * Implements hook_taxonomy_menu_delete().
*
+ * @param array $item
+ * Taxonomy menu item array, containing the following key/value pairs:
+ * 'vid': Vocabulary ID.
+ * 'tid': Taxonomy term ID.
+ * 'mlid': Menu link ID.
*/
function taxonomy_menu_taxonomy_menu_delete(&$item) {
menu_link_delete($item['mlid']);
_taxonomy_menu_delete_item($item['vid'], $item['tid']);
unset($item['mlid']);
-
}
/**
- * Create the initial $item array
+ * Creates the initial $item array.
*
* @param $args
- * array with the following key/value pairs:
- * 'term' => term object, if updating a term
- * 'menu_name' => menu that the item is set to apply to
- * 'vid' => vocab id. if editing vocab item
- * 'mlid' => menu id
+ * array with the following key/value pairs:
+ * 'term': Taxonomy term object, if updating a term.
+ * 'menu_name': menu that the item is set to apply to.
+ * 'vid': Vocabuary id, if editing vocab item.
+ * 'mlid': Menu link id.
*
* @param $node
- * The node object.
+ * The node object.
*/
function _taxonomy_menu_create_item($args = array(), $node) {
-
- // If tid = 0, then we are creating a vocab item
+ // If tid = 0, then we are creating a vocab item.
if (isset($args['tid']) && isset($args['vid']) && $args['tid'] == 0 && variable_get(_taxonomy_menu_build_variable('voc_item', $args['vid']), 0)) {
$vocab = taxonomy_vocabulary_load($args['vid']);
$item = array(
@@ -838,7 +890,7 @@ function _taxonomy_menu_create_item($args = array(), $node) {
if (empty($term->parents)) {
$term->parents = _taxonomy_menu_get_parents($term->tid);
if (empty($term->parents)) {
- // even without parents, create one with $ptid = 0
+ // Even without parents, create one with $ptid = 0.
$term->parents = array(0 => '0');
}
}
@@ -847,7 +899,7 @@ function _taxonomy_menu_create_item($args = array(), $node) {
// a flat taxonomy menu.
if (is_object($term)) {
$term_parents = taxonomy_get_parents_all($term->tid);
- $root_term_weight = $term_parents[count($term_parents) - 1]->weight;
+ $root_term_weight = ($term_parents) ? $term_parents[count($term_parents) - 1]->weight : 0;
}
else {
$root_term_weight = 0;
@@ -855,12 +907,13 @@ function _taxonomy_menu_create_item($args = array(), $node) {
foreach ($term->parents as $parent) {
$ptid = $parent;
- // turn the term into the correct $item array form
+ // Turn the term into the correct $item array form.
$item = array(
+ 'term' => $term,
'tid' => $term->tid,
'name' => $term->name,
'description' => variable_get(_taxonomy_menu_build_variable('term_item_description', $term->vid), 0) ? $term->description : '',
- 'weight' => $term->weight,
+ 'weight' => !empty($term->weight) ? $term->weight : 0,
'vid' => $term->vid,
'ptid' => $ptid,
'root_term_weight' => $root_term_weight,
@@ -870,8 +923,8 @@ function _taxonomy_menu_create_item($args = array(), $node) {
if (isset($args['mlid'])) {
$item['mlid'] = $args['mlid'];
}
- // Mutiple parents are not supported yet, hence this break.
- // without the break, the item is inserted multiple under one parent, instead once under each parent.
+ // Mutiple parents are not supported yet. Without the break, the item is
+ // inserted multiple under one parent, instead of once under each parent.
break;
}
}
@@ -880,10 +933,13 @@ function _taxonomy_menu_create_item($args = array(), $node) {
}
/**
- * Helper function to see if any of the children have any nodes
+ * Helper function: See if any of the children have any nodes.
*
* @param $tid
+ * Taxonomy term ID.
* @param $vid
+ * Vocabulary ID.
+ *
* @return boolean
*/
function _taxonomy_menu_children_has_nodes($tid, $vid, $return = FALSE) {
@@ -900,37 +956,42 @@ function _taxonomy_menu_children_has_nodes($tid, $vid, $return = FALSE) {
}
/**
- * Helper function for insert and update hooks
+ * Helper function: Inserts and updates menu along with taxonomy changes.
+ *
+ * @param array $item
+ * Taxonomy menu item.
*
- * @param $item
* @return array
+ * Taxonomy menu item.
*/
function _taxonomy_menu_item($item) {
- // if tid is 0 then do not change any settings
+ if (empty($item)) return;
+
+ // If tid is 0 then do not change any settings.
if ($item['tid'] > 0) {
- // get the number of node attached to this term
+ // Get the number of node attached to this term.
$num = _taxonomy_menu_term_count($item['tid']);
- // if hide menu is selected and the term count is 0 and the term has no children then do not create the menu item
+ // If hide menu is selected and the term count is 0 and the term has no
+ // children then do not create the menu item.
if ($num == 0 &&
- variable_get(_taxonomy_menu_build_variable('hide_empty_terms', $item['vid']), FALSE) &&
- !_taxonomy_menu_children_has_nodes($item['tid'], $item['vid'])) {
-
+ variable_get(_taxonomy_menu_build_variable('hide_empty_terms', $item['vid']), FALSE) &&
+ !_taxonomy_menu_children_has_nodes($item['tid'], $item['vid'])) {
$item['remove'] = TRUE;
return $item;
}
- // if display number is selected and $num > 0 then change the title
+ // If display number is selected and $num > 0 then change the title.
if (variable_get(_taxonomy_menu_build_variable('display_num', $item['vid']), FALSE)) {
- // if number > 0 and display decendants, then count all of the children
- if (variable_get(_taxonomy_menu_build_variable('display_descendants', $item['vid']), FALSE)) {
+ // If number > 0 and display include children num, then count all of the children.
+ if (variable_get(_taxonomy_menu_build_variable('display_num_incl_children', $item['vid']), TRUE)) {
$num = taxonomy_menu_term_count_nodes($item['tid'], $item['vid']);
}
$item['name'] .= " ($num)";
}
}
elseif ($item['tid'] == 0) {
- // if custom name is provided, use that name
+ // If custom name is provided, use that name.
$custom_name = variable_get(_taxonomy_menu_build_variable('voc_name', $item['vid']), '');
if (!empty($custom_name)) {
$item['name'] = $custom_name;
@@ -940,12 +1001,16 @@ function _taxonomy_menu_item($item) {
return $item;
}
-
/**
- * Calculates the number of nodes linked to the term and all children
- * @param $tid
- * @param $vid
- * @return integer
+ * Calculates the number of nodes linked to the term and all children.
+ *
+ * @param int $tid
+ * Taxonomy term ID.
+ * @param int $vid
+ * Vocabulary ID.
+ *
+ * @return int
+ * Count for how many nodes are in that taxonomy category.
*/
function taxonomy_menu_term_count_nodes($tid, $vid, $count = 0) {
$count += _taxonomy_menu_term_count($tid);
@@ -956,21 +1021,21 @@ function taxonomy_menu_term_count_nodes($tid, $vid, $count = 0) {
return $count;
}
-
/**
* Implements hook_taxonomy_menu_insert().
*
- * @param $item
- * array with the following key/value pairs:
- * 'tid' => the term id (if 0 then updating the vocab as an item)
- * 'name' => new menu name
- * 'description' => new menu description, used as to build the title attribute
- * 'weight' => new menu weight
- * 'vid' => the new vocabulary's id
- * 'ptid' => the new parent tid
- * 'remove' => if this is set to TRUE then the $item is not added as a menu
+ * @param array $item
+ * Taxonomy menu item array with the following key/value pairs:
+ * 'tid': The term id (if 0 then updating the vocab as an item).
+ * 'name': New menu name.
+ * 'description': New menu description, used for the title attribute.
+ * 'weight': New menu weight.
+ * 'vid': The new vocabulary's id.
+ * 'ptid': The new parent tid.
+ * 'remove': If this is set to TRUE then the $item is not added as a menu.
*
- * @return $item
+ * @return array $item
+ * Taxonomy menu item.
*/
function taxonomy_menu_taxonomy_menu_insert(&$item) {
$item = _taxonomy_menu_item($item);
@@ -979,52 +1044,53 @@ function taxonomy_menu_taxonomy_menu_insert(&$item) {
/**
* Implements hook_taxonomy_menu_update().
*
- * @param $item
- * array with the following key/value pairs:
- * 'tid' => the term id (if 0 then updating the vocab as an item)
- * 'name' => new menu name
- * 'description' => new menu description, used as to build the title attribute
- * 'weight' => new menu weight
- * 'vid' => the new vocabulary's id
- * 'ptid' => the new parent tid
- * 'mlid' => mlid that needs to be edited
- * 'remove' => if this is set to TRUE then the $item is not added as a menu
- *
+ * @param array $item
+ * Taxonomy menu item array with the following key/value pairs:
+ * 'tid': The term id (if 0 then updating the vocab as an item).
+ * 'name': New menu name.
+ * 'description': New menu description, used for the title attribute.
+ * 'weight': New menu weight.
+ * 'vid': The new vocabulary's id.
+ * 'ptid': The new parent tid.
+ * 'remove': If this is set to TRUE then the $item is not added as a menu.
*/
function taxonomy_menu_taxonomy_menu_update(&$item) {
$item = _taxonomy_menu_item($item);
}
/**
- * Used to create a form array of taxonomy menu options
- * invokes hook_taxonomy_menu_options().
+ *Creates a form array of taxonomy menu options.
+ * Invokes hook_taxonomy_menu_options().
+ *
+ * @param $vid
+ * Vocabulary ID.
*
- * @return $form array
+ * @return array
+ * Form array.
*/
function _taxonomy_menu_create_options($vid) {
$options = module_invoke_all('taxonomy_menu_options');
- // cycle through field
+ // Cycle through field.
foreach ($options as $field_name => $field_elements) {
- // cycle through each value of the field
+ // Cycle through each value of the field.
$variable_name = _taxonomy_menu_build_variable($field_name, $vid);
- // if the variable is set then use that, if the default key is set then use that, otherwise use false
+ // If the variable is set then use that, if the default key is set then use that,
+ // otherwise use FALSE.
$options[$field_name]['#default_value'] =
variable_get($variable_name,
!empty($options[$field_name]['default']) ? $options[$field_name]['default'] : FALSE);
- // set the type to checkbox if it is empty
+ // Set the type to checkbox if it is empty.
if (empty($options[$field_name]['#type'])) {
$options[$field_name]['#type'] = 'checkbox';
}
- // set the option feildset values
- $options['#type'] = 'fieldset';
- $options['#title'] = t('Options');
- $options['#collapsible'] = TRUE;
+ // Set the options.
+ $options['#type'] = 'container';
- // remove the default value from the array so we don't pass it to the form
+ // Remove the default value from the array so we don't pass it to the form.
unset($options[$field_name]['default']);
}
@@ -1034,8 +1100,8 @@ function _taxonomy_menu_create_options($vid) {
/**
* Builds a variable from the supplied name and machine name of the vocabulary.
*
- * @param $name
- * String to be added to the returned variable.
+ * @param string $name
+ * Name to be added to the returned variable.
* @param $vid
* VID of the vocabulary from which the machine name will be taken.
*
@@ -1049,67 +1115,73 @@ function _taxonomy_menu_build_variable($name, $vid) {
else {
return FALSE;
}
-
}
/**
* Implements hook_taxonomy_menu_options().
- *
- * @return array
- * Uses the value to set the variable taxonomy_menu__
- * $options[value]
- * default - optional. this is what will be used if the varialbe is not set. if empty then FALSE is used
- * #title - required.
- * any other form element
*/
function taxonomy_menu_taxonomy_menu_options() {
-
$options['sync'] = array(
'#title' => t('Synchronise changes to this vocabulary'),
'#description' => t('Every time a term is added/deleted/modified, the corresponding menu link will be altered too.'),
'default' => TRUE,
);
+ $options['max_depth'] = array(
+ '#type' => 'select',
+ '#title' => t('Max depth'),
+ '#description' => t('Limit how many levels of the taxonomy tree to process. Useful if you have a very large tree of taxonomy terms, and only want to provide a menu for the first several levels.'),
+ '#options' => array(0 => t('All'), 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 6 => 6, 7 => 7, 8 => 8, 9 => 9),
+ 'default' => 0,
+ );
+
$options['display_num'] = array(
- '#title' => t('Display number of items'),
- '#description' => t('Display the number of items per taxonomy terms. Will not show up for vocabulary menu items.'),
+ '#title' => t('Display the number of items in each taxonomy term.'),
'default' => FALSE,
);
+ $options['display_num_incl_children'] = array(
+ '#title' => t('Include items belonging to child terms in number counts'),
+ '#description' => t('Items belonging to the child terms will be counted when performing item count calculations.'),
+ '#states' => array(
+ 'visible' => array(
+ ':input[name="taxonomy_menu[options][display_num]"]' => array(
+ 'checked' => TRUE,
+ ),
+ ),
+ ),
+ 'default' => TRUE,
+ );
+
$options['hide_empty_terms'] = array(
- '#title' => t('Hide empty terms'),
- '#description' => t('Hide terms with no items attached to them.'),
+ '#title' => t('Do not add a menu link for taxonomy terms with no items.'),
'default' => FALSE,
);
$options['voc_item'] = array(
- '#title' => t('Add item for vocabulary'),
- '#description' => t('Shows the vocabulary name as the top level menu item of the taxonomy menu.'),
+ '#title' => t('Include the vocabulary name as the top level menu item of the taxonomy menu.'),
'default' => FALSE,
'#disabled' => TRUE,
);
$options['voc_item_description'] = array(
- '#title' => t('Add description for vocabulary'),
- '#description' => t('Add the vocabulary description to the vocabulary menu item.'),
+ '#title' => t('Add the vocabulary description to the vocabulary menu item.'),
'default' => FALSE,
+ '#access' => FALSE,
);
$options['term_item_description'] = array(
- '#title' => t('Add description for terms'),
- '#description' => t('Add the term description to the term menu item.'),
+ '#title' => t('Add the taxonomy term description to the term menu item.'),
'default' => FALSE,
);
$options['expanded'] = array(
- '#title' => t('Auto expand menu items'),
- '#description' => t('Automatically show all menu items as expanded.'),
+ '#title' => t('Automatically show all menu items as expanded.'),
'default' => TRUE,
);
$options['flat'] = array(
- '#title' => t('Flatten the taxonomy\'s hierarchy in the menu'),
- '#description' => t('Add all menu items to the same level rather than hierarchically.'),
+ '#title' => t('Add all menu items to the same level rather than retaining term hierarchy.'),
'default' => FALSE,
);
@@ -1122,8 +1194,7 @@ function taxonomy_menu_taxonomy_menu_options() {
);
$options['display_descendants'] = array(
- '#title' => t('Display descendants'),
- '#description' => t('Changes the default path to taxonomy/term/tid+tid+tid for all terms thave have child terms.'),
+ '#title' => t('Display descendants: change the path to taxonomy/term/tid+tid+tid for all terms thave have child terms.'),
'default' => FALSE,
);
@@ -1137,63 +1208,95 @@ function taxonomy_menu_taxonomy_menu_options() {
return $options;
}
-
/**
* Implements hook_translated_menu_link_alter().
*
* Translate menu links on the fly by using term translations.
- *
*/
function taxonomy_menu_translated_menu_link_alter(&$item, $map) {
if (module_exists('i18n_taxonomy')) {
// In case of localized terms, use term translation for menu title.
if ($item['module'] == 'taxonomy_menu') {
$t = _taxonomy_menu_get_item($item['mlid']);
- // Only translate when term exist (may per example occur with stray menu item)
+ // Only translate when term exist (may occur with stray menu item).
if ($t) {
- // Only translate when translation mode is set to localize
+ // Only translate when translation mode is set to localize.
if (i18n_taxonomy_vocabulary_mode($t->vid, I18N_MODE_LOCALIZE)) {
- // this is a term
+ // This is a term.
if ($t->tid > 0) {
$term = taxonomy_term_load($t->tid);
$display_num = '';
- $num = _taxonomy_menu_term_count($t->tid);
- // If hide menu is selected and the term count is 0 and the term has no children then do not create the menu item
+ $menu_num_inc_children = variable_get(_taxonomy_menu_build_variable('display_num_incl_children', $term->vid), TRUE);
+ $menu_display_descendants = variable_get(_taxonomy_menu_build_variable('display_descendants', $t->vid), FALSE);
+ if ($menu_num_inc_children || $menu_display_descendants) {
+ $num = taxonomy_menu_term_count_nodes($t->tid, $t->vid);
+ }
+ else {
+ $num = _taxonomy_menu_term_count($t->tid);
+ }
+
+ // If hide menu is selected and the term count is 0 and the term
+ // has no children then do not create the menu item.
if ($num == 0 && variable_get(_taxonomy_menu_build_variable('hide_empty_terms', $t->vid), FALSE) && !_taxonomy_menu_children_has_nodes($t->tid, $t->vid)) {
$display_num = '';
}
- // if display number is selected and $num > 0 then change the title
+ // If display number is selected and $num > 0 then change the title.
elseif (variable_get(_taxonomy_menu_build_variable('display_num', $t->vid), FALSE)) {
- // if number > 0 and display decendants, then count all of the children
- if (variable_get(_taxonomy_menu_build_variable('display_descendants', $t->vid), FALSE)) {
- $num = taxonomy_menu_term_count_nodes($t->tid, $t->vid);
- }
$display_num = " ($num)";
}
if ($item['title'] != ($term->name . $display_num)) {
- // Should not happen
+ // Should not happen.
watchdog('error', 'Menu and taxonomy name mismatch: @title != @name', array('@title' => $item['title'], '@name' => $term->name . $display_num));
}
$term = i18n_taxonomy_localize_terms($term);
$item['title'] = $item['link_title'] = $term->name . $display_num;
- if ($term->description) {
+ if (variable_get(_taxonomy_menu_build_variable('term_item_description', $t->vid), FALSE)) {
$item['options']['attributes']['title'] = $term->description;
}
}
- // is a vocabulary
+ // Is a vocabulary.
else {
$vocab = taxonomy_vocabulary_load($t->vid);
$item['title'] = i18n_string('taxonomy:vocabulary:' . $vocab->vid . ':name', $vocab->name);
}
}
}
- // no term, add a watchdog entry to help
+ // No term, add a watchdog entry to help.
else {
watchdog('taxonomy_menu', 'Error with menu entry "%me" in menu "%mt"', array('%me' => $item['title'], '%mt' => $item['menu_name']));
}
}
}
}
+
+/**
+ * Gets term depth from a tid.
+ *
+ * @param $tid
+ * Taxonomy term ID.
+ * @param $max_depth
+ * Maximum depth.
+ *
+ * @return bool
+ * Whether or not the term is too deep to include.
+ */
+function _taxonomy_menu_term_too_deep($tid, $max_depth) {
+ if ($max_depth) {
+ $depth = 0;
+ while ($parent = db_select('taxonomy_term_hierarchy', 't')
+ ->condition('tid', $tid, '=')
+ ->fields('t')
+ ->execute()
+ ->fetchAssoc()) {
+ $depth++;
+ $tid = $parent['parent'];
+ if ($depth > $max_depth) {
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
diff --git a/profiles/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.test b/profiles/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.test
index 89ec8327cf1..9713b475cb9 100644
--- a/profiles/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.test
+++ b/profiles/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.test
@@ -1,5 +1,4 @@
assertResponse(200);
- // By default, auto expand is on : we must find the whole hierarchy
+ // By default, auto expand is on : we must find the whole hierarchy.
foreach ($this->terms as $term) {
$this->assertLink($term->name); // 1st level
foreach ($term->children as $child) {
@@ -197,15 +196,15 @@ class TaxonomyMenuHierarchyTest extends TaxonomyMenuWebTestCase {
}
}
- // Set auto expand to off
+ // Set auto expand to off.
$edit = array();
$edit['taxonomy_menu[options][expanded]'] = FALSE;
- //$edit['taxonomy_menu[options][rebuild]'] = '1'; // Rebuild menu on submit
+ // $edit['taxonomy_menu[options][rebuild]'] = '1'; // Rebuild menu on submit.
$this->drupalPost('admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/edit', $edit, 'Save');
$this->assertResponse(200);
- //$this->drupalGet('admin/structure/taxonomy/'. $this->vocabulary->machine_name . '/edit');
+ // $this->drupalGet('admin/structure/taxonomy/'. $this->vocabulary->machine_name . '/edit');
- // We should have links to the first level of the hierarchy only
+ // We should have links to the first level of the hierarchy only.
$this->drupalGet('');
foreach ($this->terms as $term) {
$this->assertLink($term->name);
@@ -229,7 +228,7 @@ class TaxonomyMenuHierarchyTest extends TaxonomyMenuWebTestCase {
$this->assertNoLink($child->name);
}
else {
- // We must have a link AND the children
+ // We must have a link AND the children.
$this->assertLink($child->name);
if ($child->name == "term1_2") {
foreach ($child->children as $grandchild) {
@@ -243,7 +242,7 @@ class TaxonomyMenuHierarchyTest extends TaxonomyMenuWebTestCase {
}
/**
- * Helper class to build the tree and keep data on hand
+ * Helper class to build the tree and keep data on hand.
*/
class TaxonomyMenuTreeNode {
function __construct(&$testcase, $parent, $name, $children) {
@@ -267,4 +266,3 @@ class TaxonomyMenuTreeNode {
}
}
}
-
diff --git a/profiles/dkan/modules/contrib/uuid/uuid.features.inc b/profiles/dkan/modules/contrib/uuid/uuid.features.inc
index 03b7d55b220..93b418ef0f5 100644
--- a/profiles/dkan/modules/contrib/uuid/uuid.features.inc
+++ b/profiles/dkan/modules/contrib/uuid/uuid.features.inc
@@ -168,6 +168,7 @@ function uuid_entities_rebuild($module_name = '', $op = 'rebuild') {
foreach ($entities as $plan_name => $entities) {
// Let other modules do things before default entities are created.
module_invoke_all("uuid_entities_pre_$op", $plan_name);
+ drupal_alter("uuid_entities_pre_$op", $entities, $plan_name);
foreach ($entities as $entity) {
entity_uuid_save($entity->__metadata['type'], $entity);
}
diff --git a/profiles/dkan/modules/contrib/uuid/uuid.info b/profiles/dkan/modules/contrib/uuid/uuid.info
index bd1e8735985..cefaf29132f 100644
--- a/profiles/dkan/modules/contrib/uuid/uuid.info
+++ b/profiles/dkan/modules/contrib/uuid/uuid.info
@@ -7,8 +7,8 @@ files[] = uuid.test
dependencies[] = node
dependencies[] = user
-; Information added by Drupal.org packaging script on 2018-07-03
-version = "7.x-1.1"
+; Information added by Drupal.org packaging script on 2018-07-19
+version = "7.x-1.2"
core = "7.x"
project = "uuid"
-datestamp = "1530614937"
+datestamp = "1531990689"
diff --git a/profiles/dkan/modules/contrib/uuid/uuid_path/uuid_path.info b/profiles/dkan/modules/contrib/uuid/uuid_path/uuid_path.info
index 0f4fa2ba4a2..77ad15c31ce 100644
--- a/profiles/dkan/modules/contrib/uuid/uuid_path/uuid_path.info
+++ b/profiles/dkan/modules/contrib/uuid/uuid_path/uuid_path.info
@@ -5,8 +5,8 @@ package = UUID
dependencies[] = uuid
-; Information added by Drupal.org packaging script on 2018-07-03
-version = "7.x-1.1"
+; Information added by Drupal.org packaging script on 2018-07-19
+version = "7.x-1.2"
core = "7.x"
project = "uuid"
-datestamp = "1530614937"
+datestamp = "1531990689"
diff --git a/profiles/dkan/modules/contrib/uuid/uuid_services/uuid_services.admin.inc b/profiles/dkan/modules/contrib/uuid/uuid_services/uuid_services.admin.inc
index d9afe29b1e8..28e447cf26c 100644
--- a/profiles/dkan/modules/contrib/uuid/uuid_services/uuid_services.admin.inc
+++ b/profiles/dkan/modules/contrib/uuid/uuid_services/uuid_services.admin.inc
@@ -18,5 +18,13 @@ function uuid_services_settings() {
'#description' => t('Check this box to automatically provide Services integration for all entity types with UUID support.'),
'#default_value' => variable_get('uuid_services_support_all_entity_types', FALSE),
);
+ $form['uuid_services_allowed_media_mimes'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Allowed Media Mime type'),
+ '#default_value' => variable_get('uuid_services_allowed_media_mimes', UUID_SERVICES_DEFAULT_ALLOWED_MEDIA_MIMES),
+ '#cols' => 40,
+ '#rows' => 5,
+ '#description' => t("Enter one mime type per line you wish to allow in the system without extension. Example mime type 'video/brightcove'."),
+ );
return system_settings_form($form);
}
diff --git a/profiles/dkan/modules/contrib/uuid/uuid_services/uuid_services.info b/profiles/dkan/modules/contrib/uuid/uuid_services/uuid_services.info
index 911f2965412..dd1ae423bf2 100644
--- a/profiles/dkan/modules/contrib/uuid/uuid_services/uuid_services.info
+++ b/profiles/dkan/modules/contrib/uuid/uuid_services/uuid_services.info
@@ -7,8 +7,14 @@ dependencies[] = services
dependencies[] = uuid
dependencies[] = entity
-; Information added by Drupal.org packaging script on 2018-07-03
-version = "7.x-1.1"
+test_dependencies[] = services
+test_dependencies[] = entity
+test_dependencies[] = file
+test_dependencies[] = field
+test_dependencies[] = file_entity
+
+; Information added by Drupal.org packaging script on 2018-07-19
+version = "7.x-1.2"
core = "7.x"
project = "uuid"
-datestamp = "1530614937"
+datestamp = "1531990689"
diff --git a/profiles/dkan/modules/contrib/uuid/uuid_services/uuid_services.install b/profiles/dkan/modules/contrib/uuid/uuid_services/uuid_services.install
new file mode 100644
index 00000000000..1dfeb6ccfb6
--- /dev/null
+++ b/profiles/dkan/modules/contrib/uuid/uuid_services/uuid_services.install
@@ -0,0 +1,14 @@
+uuid_services = TRUE;
+ // Check that the mime type is whitelisted.
+ $valid_media_mimes = variable_get('uuid_services_allowed_media_mimes', UUID_SERVICES_DEFAULT_ALLOWED_MEDIA_MIMES);
+
// Sanitize file user input.
if ($entity_type == 'file') {
- $entity->filename = _services_file_check_name_extension($entity->filename);
- $entity->uri = _services_file_check_destination_uri($entity->uri);
- if (!empty($entity->filepath)) {
- $entity->filepath = _services_file_check_destination($entity->filepath);
+ // We have to make sure to whitelist mime types, to avoid the video files
+ // getting converted into text files, when deployed from one env to other.
+ if (!in_array($entity->filemime, preg_split('/\r?\n/', $valid_media_mimes))) {
+ $entity->filename = _services_file_check_name_extension($entity->filename);
+ $entity->uri = _services_file_check_destination_uri($entity->uri);
+ if (!empty($entity->filepath)) {
+ $entity->filepath = _services_file_check_destination($entity->filepath);
+ }
}
}
entity_uuid_save($entity_type, $entity);
diff --git a/profiles/dkan/modules/contrib/uuid/uuid_services_example/uuid_services_example.info b/profiles/dkan/modules/contrib/uuid/uuid_services_example/uuid_services_example.info
index 65cd2df1b42..5874051f152 100644
--- a/profiles/dkan/modules/contrib/uuid/uuid_services_example/uuid_services_example.info
+++ b/profiles/dkan/modules/contrib/uuid/uuid_services_example/uuid_services_example.info
@@ -11,8 +11,8 @@ features[ctools][] = services:services:3
features[features_api][] = api:2
features[services_endpoint][] = uuid_services_example
-; Information added by Drupal.org packaging script on 2018-07-03
-version = "7.x-1.1"
+; Information added by Drupal.org packaging script on 2018-07-19
+version = "7.x-1.2"
core = "7.x"
project = "uuid"
-datestamp = "1530614937"
+datestamp = "1531990689"
diff --git a/profiles/dkan/modules/contrib/workbench_moderation/PATCHES.txt b/profiles/dkan/modules/contrib/workbench_moderation/PATCHES.txt
new file mode 100644
index 00000000000..4870ef67c47
--- /dev/null
+++ b/profiles/dkan/modules/contrib/workbench_moderation/PATCHES.txt
@@ -0,0 +1,5 @@
+The following patches have been applied to this project:
+- https://www.drupal.org/files/issues/workbench_moderation-install-warnings-2360973-3.patch
+- https://www.drupal.org/files/issues/1512442-20-workbench_moderation-fix_access_check.patch
+
+This file was automatically generated by Drush Make (http://drupal.org/project/drush).
diff --git a/profiles/dkan/modules/contrib/workbench_moderation/workbench_moderation.features.inc b/profiles/dkan/modules/contrib/workbench_moderation/workbench_moderation.features.inc
index 21090f11c62..418fa795c8c 100644
--- a/profiles/dkan/modules/contrib/workbench_moderation/workbench_moderation.features.inc
+++ b/profiles/dkan/modules/contrib/workbench_moderation/workbench_moderation.features.inc
@@ -72,11 +72,12 @@ function workbench_moderation_states_features_enable_feature($module) {
* Store each exported transition in the database.
*/
function workbench_moderation_states_features_rebuild($module) {
- $defaults = features_get_default('workbench_moderation_states', $module);
- foreach ($defaults as $state) {
- workbench_moderation_state_save((object) $state);
+ if ($defaults = features_get_default('workbench_moderation_states', $module)) {
+ foreach ($defaults as $state) {
+ workbench_moderation_state_save((object) $state);
+ }
+ drupal_static_reset('workbench_moderation_states');
}
- drupal_static_reset('workbench_moderation_states');
}
/**
@@ -157,9 +158,10 @@ function workbench_moderation_transitions_features_enable_feature($module) {
* Store each exported transition in the database.
*/
function workbench_moderation_transitions_features_rebuild($module) {
- $defaults = features_get_default('workbench_moderation_transitions', $module);
- foreach ($defaults as $machine_name => $transition) {
- workbench_moderation_transition_save((object) $transition);
+ if ($defaults = features_get_default('workbench_moderation_transitions', $module)) {
+ foreach ($defaults as $machine_name => $transition) {
+ workbench_moderation_transition_save((object) $transition);
+ }
+ drupal_static_reset('workbench_moderation_transitions');
}
- drupal_static_reset('workbench_moderation_transitions');
}
diff --git a/profiles/dkan/modules/contrib/workbench_moderation/workbench_moderation.module b/profiles/dkan/modules/contrib/workbench_moderation/workbench_moderation.module
index c28261b6d0f..15a81064cbf 100644
--- a/profiles/dkan/modules/contrib/workbench_moderation/workbench_moderation.module
+++ b/profiles/dkan/modules/contrib/workbench_moderation/workbench_moderation.module
@@ -450,38 +450,50 @@ function workbench_moderation_node_access($node, $op, $account) {
/**
* Custom access handler for node operations.
*
- * @param $op
- * The operation being requested.
- * @param $node
+ * @param string $op
+ * The operation being requested. Must be one of 'view', 'update', 'view
+ * revisions', 'view history' or 'unpublish'.
+ * @param object $node
* The node being acted upon.
+ * @param object $account
+ * Optional user account to check. If omitted will default to the currently
+ * logged in user.
*
- * @return
- * Boolean TRUE or FALSE.
+ * @return bool
+ * TRUE if the user has access, FALSE otherwise.
*/
-function _workbench_moderation_access($op, $node) {
+function _workbench_moderation_access($op, $node, $account = NULL) {
global $user;
+ // Default to the logged in user.
+ $account = empty($account) ? $user : $account;
+
// If we do not control this node type, deny access.
if (workbench_moderation_node_type_moderated($node->type) === FALSE) {
return FALSE;
}
- $access = TRUE;
-
// The user must be able to view the moderation history.
- $access &= user_access('view moderation history');
+ $access = user_access('view moderation history', $account);
- // The user must be able to edit this node.
- $access &= node_access('update', $node);
+ if ($op == 'update' || $op == 'unpublish') {
+ // The user must be able to edit this node.
+ $access &= node_access('update', $node, $account);
+ }
+
+ if ($op == 'view revisions' || $op == 'view history') {
+ // The user must be able to see revisions.
+ _node_revision_access($node, 'view', $account);
+ }
if ($op == 'unpublish') {
// workbench_moderation_states_next() checks transition permissions.
- $next_states = workbench_moderation_states_next(workbench_moderation_state_published(), $user, $node);
+ $next_states = workbench_moderation_states_next(workbench_moderation_state_published(), $account, $node);
$access &= !empty($next_states);
}
// Allow other modules to change our rule set.
- drupal_alter('workbench_moderation_access', $access, $op, $node);
+ drupal_alter('workbench_moderation_access', $access, $op, $node, $account);
return $access;
}
diff --git a/profiles/dkan/modules/contrib/workbench_moderation/workbench_moderation.node.inc b/profiles/dkan/modules/contrib/workbench_moderation/workbench_moderation.node.inc
index 9afba9845fd..1cdfa699e64 100644
--- a/profiles/dkan/modules/contrib/workbench_moderation/workbench_moderation.node.inc
+++ b/profiles/dkan/modules/contrib/workbench_moderation/workbench_moderation.node.inc
@@ -170,7 +170,7 @@ function workbench_moderation_node_history_view($node) {
}
// Provide a courtesy edit operation if this is the current revision.
- if ($revision->vid == $node->workbench_moderation['current']->vid) {
+ if ($revision->vid == $node->workbench_moderation['current']->vid && _workbench_moderation_access('update', $node)) {
// The edit operation's default link title, "Edit draft", matches
// the logic tree in workbench_moderation_edit_tab_title().
$edit_operation_title = t('Edit draft');
diff --git a/profiles/dkan/modules/contrib/xautoload/.travis.yml b/profiles/dkan/modules/contrib/xautoload/.travis.yml
new file mode 100644
index 00000000000..23902b4d69e
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/.travis.yml
@@ -0,0 +1,5 @@
+language: php
+php:
+ - 5.5
+ - 5.4
+ - 5.3
diff --git a/profiles/dkan/modules/contrib/date/LICENSE.txt b/profiles/dkan/modules/contrib/xautoload/LICENSE.txt
similarity index 100%
rename from profiles/dkan/modules/contrib/date/LICENSE.txt
rename to profiles/dkan/modules/contrib/xautoload/LICENSE.txt
diff --git a/profiles/dkan/modules/contrib/xautoload/README.md b/profiles/dkan/modules/contrib/xautoload/README.md
new file mode 100644
index 00000000000..732a04d2aa1
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/README.md
@@ -0,0 +1,3 @@
+
+
+[![Build Status](https://travis-ci.org/donquixote/drupal-xautoload.png)](https://travis-ci.org/donquixote/drupal-xautoload)
diff --git a/profiles/dkan/modules/contrib/xautoload/legacy/lib/FinderPlugin/CheckIncludePath.php b/profiles/dkan/modules/contrib/xautoload/legacy/lib/FinderPlugin/CheckIncludePath.php
new file mode 100644
index 00000000000..8b3e1dcb8e8
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/legacy/lib/FinderPlugin/CheckIncludePath.php
@@ -0,0 +1,17 @@
+suggestFile_checkIncludePath($path);
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/legacy/lib/FinderPlugin/Interface.php b/profiles/dkan/modules/contrib/xautoload/legacy/lib/FinderPlugin/Interface.php
new file mode 100644
index 00000000000..6890b97d202
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/legacy/lib/FinderPlugin/Interface.php
@@ -0,0 +1,51 @@
+ The class finder will transform the class name to
+ * "Some/Namespace/Some/Class.php"
+ * - The plugin was registered for the namespace "Some\Namespace". This is
+ * because all those exotic classes all begin with Some\Namespace\
+ * -> The arguments will be:
+ * ($api = the API object, see below)
+ * $logical_base_path = "Some/Namespace/"
+ * $relative_path = "Some/Class.php"
+ * $api->getClass() gives the original class name, if we still need it.
+ * -> We are supposed to:
+ * if ($api->suggestFile('exotic/location.php')) {
+ * return TRUE;
+ * }
+ *
+ * @param InjectedApiInterface $api
+ * An object with a suggestFile() method.
+ * We are supposed to suggest files until suggestFile() returns TRUE, or we
+ * have no more suggestions.
+ * @param string $logical_base_path
+ * The key that this plugin was registered with.
+ * With trailing '/'.
+ * @param string $relative_path
+ * Second part of the canonical path, ending with '.php'.
+ *
+ * @return bool|null
+ * TRUE, if the file was found.
+ * FALSE or NULL, otherwise.
+ */
+ function findFile($api, $logical_base_path, $relative_path);
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/legacy/lib/InjectedAPI/hookXautoload.php b/profiles/dkan/modules/contrib/xautoload/legacy/lib/InjectedAPI/hookXautoload.php
new file mode 100644
index 00000000000..eb958641655
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/legacy/lib/InjectedAPI/hookXautoload.php
@@ -0,0 +1,248 @@
+finder = $adapter->getFinder();
+ }
+
+ // Prefix stuff
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Register an additional prefix for this module.
+ * Note: Drupal\\ is already registered for /lib.
+ *
+ * @deprecated
+ *
+ * @param string $prefix
+ * The prefix.
+ * @param string $prefix_root_dir
+ * Prefix root dir.
+ * If $relative is TRUE, this is relative to the extension module dir.
+ * If $relative is FALSE, this is an absolute path.
+ * @param boolean $relative
+ * Whether or not the path is relative to the current extension dir.
+ */
+ function prefixRoot($prefix, $prefix_root_dir = NULL, $relative = TRUE) {
+ $prefix_root_dir = $this->processDir($prefix_root_dir, $relative);
+ $this->finder->registerPrefixRoot($prefix, $prefix_root_dir);
+ }
+
+ /**
+ * Register an additional namespace for this module.
+ * Note: Drupal\\ is already registered for /lib.
+ *
+ * @deprecated
+ *
+ * @param string $prefix
+ * The namespace
+ * @param string $prefix_deep_dir
+ * PSR-0 root dir.
+ * If $relative is TRUE, this is relative to the current extension dir.
+ * If $relative is FALSE, this is an absolute path.
+ * @param boolean $relative
+ * Whether or not the path is relative to the current extension dir.
+ */
+ function prefixDeep($prefix, $prefix_deep_dir = NULL, $relative = TRUE) {
+ $prefix_deep_dir = $this->processDir($prefix_deep_dir, $relative);
+ $this->finder->registerPrefixDeep($prefix, $prefix_deep_dir);
+ }
+
+ /**
+ * Legacy: Plugins were called "Handler" before.
+ *
+ * @deprecated
+ *
+ * @param string $prefix
+ * @param xautoload_FinderPlugin_Interface $plugin
+ *
+ * @return string
+ * The key under which the plugin was registered. This can later be used to
+ * unregister the plugin again.
+ */
+ function prefixHandler($prefix, $plugin) {
+ $key = Util::randomString();
+ $this->finder->registerPrefixDeep($prefix, $key, $plugin);
+
+ return $key;
+ }
+
+ /**
+ * Register a prefix plugin object
+ *
+ * @deprecated
+ *
+ * @param string $prefix
+ * @param xautoload_FinderPlugin_Interface $plugin
+ *
+ * @return string
+ * The key under which the plugin was registered. This can later be used to
+ * unregister the plugin again.
+ */
+ function prefixPlugin($prefix, $plugin) {
+ $key = Util::randomString();
+ $this->finder->registerPrefixDeep($prefix, $key, $plugin);
+
+ return $key;
+ }
+
+ // Namespace stuff
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Register an additional namespace for this module.
+ * Note: Drupal\\ is already registered for /lib.
+ *
+ * @deprecated
+ *
+ * @param string $namespace
+ * The namespace
+ * @param string $psr_0_root_dir
+ * PSR-0 root dir.
+ * If $relative is TRUE, this is relative to the current module dir.
+ * If $relative is FALSE, this is an absolute path.
+ * @param boolean $relative
+ * Whether or not the path is relative to the current extension dir.
+ */
+ function namespaceRoot($namespace, $psr_0_root_dir = NULL, $relative = TRUE) {
+ $psr_0_root_dir = $this->processDir($psr_0_root_dir, $relative);
+ $this->finder->registerNamespaceRoot($namespace, $psr_0_root_dir);
+ }
+
+ /**
+ * Register an additional namespace for this module.
+ * Note: Drupal\\ is already registered for /lib.
+ *
+ * @deprecated
+ *
+ * @param string $namespace
+ * The namespace
+ * @param string $namespace_deep_dir
+ * PSR-0 root dir.
+ * If $relative is TRUE, this is relative to the current extension dir.
+ * If $relative is FALSE, this is an absolute path.
+ * @param boolean $relative
+ * Whether or not the path is relative to the current extension dir.
+ */
+ function namespaceDeep($namespace, $namespace_deep_dir = NULL, $relative = TRUE) {
+ $namespace_deep_dir = $this->processDir($namespace_deep_dir, $relative);
+ $this->finder->registerNamespaceDeep($namespace, $namespace_deep_dir);
+ }
+
+ /**
+ * Register a namespace plugin object
+ *
+ * @deprecated
+ *
+ * @param string $namespace
+ * @param xautoload_FinderPlugin_Interface $plugin
+ *
+ * @return string
+ * The key under which the plugin was registered. This can later be used to
+ * unregister the plugin again.
+ */
+ function namespacePlugin($namespace, $plugin) {
+ $key = Util::randomString();
+ $this->finder->registerNamespaceDeep($namespace, $key, $plugin);
+
+ return $key;
+ }
+
+ /**
+ * Legacy: Plugins were called "Handler" before.
+ *
+ * @deprecated
+ *
+ * @param string $namespace
+ * @param xautoload_FinderPlugin_Interface $plugin
+ *
+ * @return string
+ * The key under which the plugin was registered. This can later be used to
+ * unregister the plugin again.
+ */
+ function namespaceHandler($namespace, $plugin) {
+ $key = Util::randomString();
+ $this->finder->registerNamespaceDeep($namespace, $key, $plugin);
+
+ return $key;
+ }
+
+ /**
+ * Process a given directory to make it relative to Drupal root,
+ * instead of relative to the current extension dir.
+ *
+ * @deprecated
+ *
+ * @param string $dir
+ * The directory path that we want to make absolute.
+ * @param boolean $relative
+ * If TRUE, the $dir will be transformed from relative to absolute.
+ * If FALSE, the $dir is assumed to already be absolute, and remain unchanged.
+ *
+ * @return string
+ * The modified (absolute) directory path.
+ */
+ protected function processDir($dir, $relative) {
+ if (!isset($dir)) {
+ return $this->localDirectory . 'lib/';
+ }
+ $dir = strlen($dir)
+ ? rtrim($dir, '/') . '/'
+ : '';
+
+ return $relative
+ ? $this->localDirectory . $dir
+ : $dir;
+ }
+
+ /**
+ * Explicitly set the base for relative paths.
+ *
+ * Alias for LocalDirectoryAdapter::setLocalDirectory()
+ *
+ * @param string $dir
+ * New relative base path.
+ */
+ function setExtensionDir($dir) {
+ $this->localDirectory = strlen($dir)
+ ? rtrim($dir, '/') . '/'
+ : '';
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/lib/Drupal/xautoload/Tests/EnvironmentSnapshotMaker.php b/profiles/dkan/modules/contrib/xautoload/lib/Drupal/xautoload/Tests/EnvironmentSnapshotMaker.php
new file mode 100644
index 00000000000..4355028e501
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/lib/Drupal/xautoload/Tests/EnvironmentSnapshotMaker.php
@@ -0,0 +1,62 @@
+ 'X Autoload unit test',
+ 'description' => 'Test the xautoload class finder.',
+ 'group' => 'X Autoload',
+ );
+ }
+
+ function setUp() {
+
+ // drupal_load('module', 'xautoload') would register namespaces for all
+ // enabled modules, which is not intended for this unit test.
+ // Instead, we just include xautoload.early.inc.
+ require_once __DIR__ . '/../../../../xautoload.early.inc';
+
+ // Make sure we use the regular loader, not the APC one.
+ // Also make sure to prepend this one. Otherwise, the core class loader will
+ // try to load xautoload-related stuff, e.g. xautoload_Mock_* stuff, and
+ // will fail due to the database.
+ foreach (spl_autoload_functions() as $callback) {
+ if (is_array($callback)
+ && ($loader = $callback[0])
+ && $loader instanceof ClassLoaderInterface
+ ) {
+ $loader->unregister();
+ }
+ }
+ xautoload()->finder->register(TRUE);
+
+ // Do the regular setUp().
+ parent::setUp();
+ }
+
+ function testAutoloadStackOrder() {
+ $expected = array(
+ 'Drupal\\xautoload\\ClassFinder\\ClassFinder->loadClass()',
+ /* @see _drupal_bootstrap_database() */
+ 'drupal_autoload_class',
+ 'drupal_autoload_interface',
+ /* @see simpletest_classloader_register() */
+ '_simpletest_autoload_psr4_psr0',
+ );
+
+ $actual = array();
+ foreach (spl_autoload_functions() as $callback) {
+ $actual[] = Util::callbackToString($callback);
+ }
+
+ $this->assertEqualBlock($expected, $actual, "SPL autoload stack:");
+ }
+
+ function testNamespaces() {
+
+ // Prepare the class finder.
+ $finder = new ClassFinder();
+ $finder->add('Drupal\\ex_ample', 'sites/all/modules/contrib/ex_ample/lib-psr0');
+ $finder->addPsr4('Drupal\\ex_ample', 'sites/all/modules/contrib/ex_ample/lib-psr4');
+
+ // Test class finding for 'Drupal\\ex_ample\\Abc_Def'.
+ $this->assertFinderSuggestions($finder, 'Drupal\\ex_ample\\Abc_Def', array(
+ // Class finder is expected to suggest these files, in the exact order,
+ // until one of them is accepted.
+ array('suggestFile', 'sites/all/modules/contrib/ex_ample/lib-psr0/Drupal/ex_ample/Abc/Def.php'),
+ array('suggestFile', 'sites/all/modules/contrib/ex_ample/lib-psr4/Abc_Def.php'),
+ ));
+ }
+
+ function testPrefixes() {
+
+ // Prepare the class finder.
+ $finder = new ClassFinder();
+ $finder->registerPrefixDeep('ex_ample', 'sites/all/modules/contrib/ex_ample/lib');
+ $finder->registerPrefixRoot('ex_ample', 'sites/all/modules/contrib/ex_ample/vendor');
+
+ // Test class finding for 'ex_ample_Abc_Def'.
+ $this->assertFinderSuggestions($finder, 'ex_ample_Abc_Def', array(
+ // Class finder is expected to suggest these files, in the exact order,
+ // until one of them is accepted.
+ array('suggestFile', 'sites/all/modules/contrib/ex_ample/lib/Abc/Def.php'),
+ array('suggestFile', 'sites/all/modules/contrib/ex_ample/vendor/ex/ample/Abc/Def.php'),
+ ));
+ }
+
+ /**
+ * @param ClassFinder $finder
+ * @param string $class
+ * @param array $expectedSuggestions
+ *
+ * @return bool
+ * Result of the assertion
+ */
+ protected function assertFinderSuggestions($finder, $class, array $expectedSuggestions) {
+ $success = TRUE;
+ for ($iAccept = 0; $iAccept < count($expectedSuggestions); ++$iAccept) {
+ list($method_name, $file) = $expectedSuggestions[$iAccept];
+ $api = new CollectFilesInjectedApi($class, $method_name, $file);
+ $finder->apiFindFile($api, $class);
+ $suggestions = $api->getSuggestions();
+ $expected = array_slice($expectedSuggestions, 0, $iAccept + 1);
+ $success = $success && $this->assertEqualBlock($expected, $suggestions, "Finder suggestions for class $class:");
+ }
+ return $success;
+ }
+
+ /**
+ * @param mixed $expected
+ * @param mixed $actual
+ * @param string $label
+ *
+ * @return bool
+ * Result of the assertion
+ */
+ protected function assertEqualBlock($expected, $actual, $label) {
+ $label .= ' ' .
+ 'Expected:
' . var_export($expected, TRUE) . '
' .
+ 'Actual:
' . var_export($actual, TRUE) . '
';
+ return $this->assertEqual($expected, $actual, $label);
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/lib/Drupal/xautoload/Tests/XAutoloadWebTestCase.php b/profiles/dkan/modules/contrib/xautoload/lib/Drupal/xautoload/Tests/XAutoloadWebTestCase.php
new file mode 100644
index 00000000000..3d80f4bd481
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/lib/Drupal/xautoload/Tests/XAutoloadWebTestCase.php
@@ -0,0 +1,264 @@
+ 'X Autoload web test',
+ 'description' => 'Test xautoload class loading for an example module.',
+ 'group' => 'X Autoload',
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function setUp() {
+ parent::setUp();
+ }
+
+ /**
+ *
+ */
+ function testNoCache() {
+ $this->xautoloadTestWithCacheTypes(array(), TRUE);
+ }
+
+ /**
+ *
+ */
+ function testApcCache() {
+ $cache_types = array(
+ 'apc' => 'apc',
+ 'xcache' => 'xcache',
+ 'wincache' => 'wincache',
+ );
+ $this->xautoloadTestWithCacheTypes($cache_types, TRUE);
+ }
+
+ /**
+ * @param array $cache_types
+ * The autoloader modes that are enabled, e.g.
+ * array('apc' => 'apc', 'xcache' => 'xcache')
+ * @param bool $cache_lazy
+ * Whether the "lazy" mode is enabled.
+ */
+ protected function xautoloadTestWithCacheTypes($cache_types, $cache_lazy) {
+
+ variable_set(XAUTOLOAD_VARNAME_CACHE_TYPES, $cache_types);
+ $this->pass("Set cache types: " . var_export($cache_types, TRUE));
+
+ variable_set(XAUTOLOAD_VARNAME_CACHE_LAZY, $cache_lazy);
+ $this->pass("Set cache lazy mode: " . var_export($cache_lazy, TRUE));
+
+ // Enable xautoload.
+ module_enable(array('xautoload'), FALSE);
+
+ // At this time the xautoload_cache_mode setting is not in effect yet,
+ // so we have to clear old cached values from APC cache.
+ xautoload()->cacheManager->renewCachePrefix();
+
+ module_enable(array(
+ 'xautoload_test_1',
+ 'xautoload_test_2',
+ 'xautoload_test_3',
+ 'xautoload_test_4',
+ 'xautoload_test_5',
+ ), FALSE);
+ menu_rebuild();
+
+ foreach (array(
+ 'xautoload_test_1' => array('Drupal\xautoload_test_1\ExampleClass'),
+ 'xautoload_test_2' => array('xautoload_test_2_ExampleClass'),
+ 'xautoload_test_3' => array('Drupal\xautoload_test_3\ExampleClass'),
+ ) as $module => $classes) {
+ $classes_on_include = in_array($module, array('xautoload_test_2', 'xautoload_test_3'));
+ $this->xautoloadModuleEnabled($module, $classes, $classes_on_include);
+ $this->xautoloadModuleCheckJson($module, $cache_types, $cache_lazy, $classes);
+ }
+ }
+
+ /**
+ * @param string $module
+ * @param string[] $classes
+ * @param bool $classes_on_include
+ */
+ protected function xautoloadModuleEnabled($module, $classes, $classes_on_include) {
+
+ EnvironmentSnapshotMaker::takeSnapshot($module, 'later', $classes);
+
+ $all = EnvironmentSnapshotMaker::getSnapshots($module);
+
+ foreach ($all as $phase => $observations) {
+ $when = ($phase === 'early')
+ ? 'on drupal_load() during module_enable()'
+ : (($phase === 'later')
+ ? 'after hook_modules_enabled()'
+ : 'at an undefined time'
+ );
+
+ // Test the classes of the example module.
+ foreach ($classes as $class) {
+ // Test that the class was already found in $phase.
+ $this->assertTrue(isset($observations['class_exists'][$class]), "Class $class was checked $when.");
+ if ($classes_on_include || $phase !== 'early') {
+ $this->assertTrue($observations['class_exists'][$class], "Class $class was found $when.");
+ }
+ else {
+ $this->assertFalse($observations['class_exists'][$class], "Class $class cannot be found $when.");
+ }
+ }
+ }
+ }
+
+ /**
+ * @param string $module
+ * @param array $cache_types
+ * The autoloader modes that are enabled, e.g.
+ * array('apc' => 'apc', 'xcache' => 'xcache')
+ * @param bool $cache_lazy
+ * Whether the "lazy" mode is enabled.
+ * @param string[] $classes
+ */
+ protected function xautoloadModuleCheckJson($module, $cache_types, $cache_lazy, $classes) {
+
+ $path = "$module.json";
+ $json = $this->drupalGet($path);
+ $all = json_decode($json, TRUE);
+
+ if (!is_array($all) || empty($all)) {
+ $this->fail("$path must return a non-empty json array.");
+ return;
+ }
+
+ foreach ($all as $phase => $observations) {
+
+ $when = ($phase === 'early')
+ ? 'on early bootstrap'
+ : (($phase === 'boot')
+ ? 'during hook_boot()'
+ : 'at an undefined time'
+ );
+
+ $this->xautoloadCheckTestEnvironment($observations, $cache_types, $cache_lazy, $when);
+
+ // Test the classes of the example module.
+ foreach ($classes as $class) {
+ // Test that the class was already found in $phase.
+ $this->assertTrue($observations['class_exists'][$class], "Class $class was found $when.");
+ }
+ }
+ }
+
+ /**
+ * @param array $observations
+ * @param array $cache_types
+ * The autoloader modes that are enabled, e.g.
+ * array('apc' => 'apc', 'xcache' => 'xcache')
+ * @param bool $lazy
+ * Whether the "lazy" mode is enabled.
+ * @param $when
+ */
+ protected function xautoloadCheckTestEnvironment($observations, $cache_types, $lazy, $when) {
+
+ // Check early-bootstrap variables.
+ $label = "$when: xautoload_cache_types:";
+ $this->assertEqualBlock($cache_types, $observations[XAUTOLOAD_VARNAME_CACHE_TYPES], $label);
+
+ $label = "$when: xautoload_cache_lazy:";
+ $this->assertEqualInline($lazy, $observations[XAUTOLOAD_VARNAME_CACHE_LAZY], $label);
+
+ // Check registered class loaders.
+ $expected = $this->expectedAutoloadStackOrder($cache_types);
+ $actual = $observations['spl_autoload_functions'];
+ $label = "$when: spl autoload stack:";
+ $this->assertEqualBlock($expected, $actual, $label);
+ }
+
+ /**
+ * @param string $cache_types
+ * The autoloader modes that are enabled, e.g.
+ * array('apc' => 'apc', 'xcache' => 'xcache')
+ *
+ * @return string[]
+ * Expected order of class loaders on the spl autoload stack for the given
+ * autoloader mode. Each represented by a string.
+ */
+ protected function expectedAutoloadStackOrder($cache_types) {
+
+ if (!empty($cache_types['apc']) && extension_loaded('apc') && function_exists('apc_store')) {
+ $loader = 'Drupal\xautoload\ClassLoader\ApcClassLoader->loadClass()';
+ }
+ elseif (!empty($cache_types['wincache']) && extension_loaded('wincache') && function_exists('wincache_ucache_get')) {
+ $loader = 'Drupal\xautoload\ClassLoader\WinCacheClassLoader->loadClass()';
+ }
+ elseif (!empty($cache_types['xcache']) && extension_loaded('Xcache') && function_exists('xcache_get')) {
+ $loader = 'Drupal\xautoload\ClassLoader\XCacheClassLoader->loadClass()';
+ }
+ else {
+ $loader = 'Drupal\xautoload\ClassFinder\ClassFinder->loadClass()';
+ }
+
+ return array(
+ 'drupal_autoload_class',
+ 'drupal_autoload_interface',
+ $loader,
+ );
+ }
+
+ /**
+ * Assert that a module is disabled.
+ *
+ * @param string $module
+ */
+ protected function assertModuleDisabled($module) {
+ $this->assertFalse(module_exists($module), "Module $module is disabled.");
+ }
+
+ /**
+ * Assert that a module is enabled.
+ *
+ * @param string $module
+ */
+ protected function assertModuleEnabled($module) {
+ $this->assertTrue(module_exists($module), "Module $module is enabled.");
+ }
+
+ /**
+ * Assert that a class is defined.
+ *
+ * @param string $class
+ */
+ protected function assertClassExists($class) {
+ $this->assertTrue(class_exists($class), "Class '$class' must exist.");
+ }
+
+ /**
+ * @param mixed $expected
+ * @param mixed $actual
+ * @param string $label
+ */
+ protected function assertEqualBlock($expected, $actual, $label) {
+ $label .=
+ 'Expected:
' . var_export($expected, TRUE) . '
' .
+ 'Actual:
' . var_export($actual, TRUE) . '
';
+ $this->assertEqual($expected, $actual, $label);
+ }
+
+ /**
+ * @param mixed $expected
+ * @param mixed $actual
+ * @param string $label
+ */
+ protected function assertEqualInline($expected, $actual, $label) {
+ $label .= ' ' .
+ 'Expected: ' . var_export($expected, TRUE) . ' ' .
+ 'Actual: ' . var_export($actual, TRUE) . '';
+ $this->assertEqual($expected, $actual, $label);
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/phpunit.xml.dist b/profiles/dkan/modules/contrib/xautoload/phpunit.xml.dist
new file mode 100644
index 00000000000..4acf8cc6d5c
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/phpunit.xml.dist
@@ -0,0 +1,19 @@
+
+
+
+
+
+ ./tests/src
+
+
+
diff --git a/profiles/dkan/modules/contrib/xautoload/src/Adapter/ClassFinderAdapter.php b/profiles/dkan/modules/contrib/xautoload/src/Adapter/ClassFinderAdapter.php
new file mode 100644
index 00000000000..e2904337d47
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/Adapter/ClassFinderAdapter.php
@@ -0,0 +1,287 @@
+finder = $finder;
+ $this->prefixMap = $finder->getPrefixMap();
+ $this->namespaceMap = $finder->getNamespaceMap();
+ $this->defaultBehavior = new DefaultDirectoryBehavior();
+ $this->psr0Behavior = new Psr0DirectoryBehavior();
+ $this->classMapGenerator = $classmap_generator;
+ }
+
+ /**
+ * @return \Drupal\xautoload\ClassFinder\GenericPrefixMap
+ */
+ function getNamespaceMap() {
+ return $this->namespaceMap;
+ }
+
+ /**
+ * @return GenericPrefixMap
+ */
+ function getPrefixMap() {
+ return $this->prefixMap;
+ }
+
+ /**
+ * @return ClassMapGeneratorInterface
+ */
+ function getClassmapGenerator() {
+ return $this->classMapGenerator;
+ }
+
+ /**
+ * @return ClassMapGeneratorInterface
+ */
+ function getFinder() {
+ return $this->finder;
+ }
+
+ // Discovery
+ // ---------------------------------------------------------------------------
+
+ /**
+ * {@inheritdoc}
+ */
+ function addClassmapSources($paths) {
+ $map = $this->classMapGenerator->wildcardPathsToClassmap($paths);
+ $this->addClassMap($map);
+ }
+
+ // Composer tools
+ // ---------------------------------------------------------------------------
+
+ /**
+ * {@inheritdoc}
+ */
+ function composerJson($file) {
+ $json = ComposerJson::createFromFile($file);
+ $json->writeToAdapter($this);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function composerDir($dir) {
+ $dir = ComposerDir::create($dir);
+ $dir->writeToAdapter($this);
+ }
+
+ // multiple PSR-0 / PSR-4
+ // ---------------------------------------------------------------------------
+
+ /**
+ * {@inheritdoc}
+ */
+ function addMultiplePsr0(array $prefixes) {
+ $namespace_map = array();
+ $prefix_map = array();
+ foreach ($prefixes as $prefix => $paths) {
+ if (FALSE === strpos($prefix, '\\')) {
+ $logical_base_path = Util::prefixLogicalPath($prefix);
+ foreach ((array) $paths as $root_path) {
+ $deep_path = strlen($root_path)
+ ? rtrim($root_path, '/') . '/' . $logical_base_path
+ : $logical_base_path;
+ $prefix_map[$logical_base_path][$deep_path] = $this->defaultBehavior;
+ }
+ }
+ $logical_base_path = Util::namespaceLogicalPath($prefix);
+ foreach ((array) $paths as $root_path) {
+ $deep_path = strlen($root_path)
+ ? rtrim($root_path, '/') . '/' . $logical_base_path
+ : $logical_base_path;
+ $namespace_map[$logical_base_path][$deep_path] = $this->psr0Behavior;
+ }
+ }
+ if (!empty($prefix_map)) {
+ $this->prefixMap->registerDeepPaths($prefix_map);
+ }
+ $this->namespaceMap->registerDeepPaths($namespace_map);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function addMultiplePsr4(array $map) {
+ $namespace_map = array();
+ foreach ($map as $namespace => $paths) {
+ $logical_base_path = Util::namespaceLogicalPath($namespace);
+ foreach ($paths as $root_path) {
+ $deep_path = strlen($root_path)
+ ? rtrim($root_path, '/') . '/'
+ : '';
+ $namespace_map[$logical_base_path][$deep_path] = $this->defaultBehavior;
+ }
+ }
+ $this->namespaceMap->registerDeepPaths($namespace_map);
+ }
+
+ // Composer ClassLoader
+ // ---------------------------------------------------------------------------
+
+ /**
+ * {@inheritdoc}
+ */
+ function addClassMap(array $classMap) {
+ $this->finder->registerClasses($classMap);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function add($prefix, $paths) {
+ if (FALSE === strpos($prefix, '\\')) {
+ // Due to the ambiguity of PSR-0, this could be either PEAR-like or namespaced.
+ $logical_base_path = Util::prefixLogicalPath($prefix);
+ foreach ((array) $paths as $root_path) {
+ $deep_path = strlen($root_path)
+ ? rtrim($root_path, '/') . '/' . $logical_base_path
+ : $logical_base_path;
+ $this->prefixMap->registerDeepPath(
+ $logical_base_path,
+ $deep_path,
+ $this->defaultBehavior);
+ }
+ }
+ // Namespaced PSR-0
+ $logical_base_path = Util::namespaceLogicalPath($prefix);
+ foreach ((array) $paths as $root_path) {
+ $deep_path = strlen($root_path)
+ ? rtrim($root_path, '/') . '/' . $logical_base_path
+ : $logical_base_path;
+ $this->namespaceMap->registerDeepPath(
+ $logical_base_path,
+ $deep_path,
+ $this->psr0Behavior);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function addPsr0($prefix, $paths) {
+ $this->add($prefix, $paths);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function addPsr4($prefix, $paths) {
+ // Namespaced PSR-4
+ $logical_base_path = Util::namespaceLogicalPath($prefix);
+ foreach ((array) $paths as $deep_path) {
+ $deep_path = strlen($deep_path)
+ ? rtrim($deep_path, '/') . '/'
+ : '';
+ $this->namespaceMap->registerDeepPath(
+ $logical_base_path,
+ $deep_path,
+ $this->defaultBehavior);
+ }
+ }
+
+ // More convenience stuff
+ // ---------------------------------------------------------------------------
+
+ /**
+ * {@inheritdoc}
+ */
+ function addNamespacePsr0($prefix, $paths) {
+ $logical_base_path = Util::namespaceLogicalPath($prefix);
+ foreach ((array) $paths as $root_path) {
+ $deep_path = strlen($root_path)
+ ? rtrim($root_path, '/') . '/' . $logical_base_path
+ : $logical_base_path;
+ $this->namespaceMap->registerDeepPath(
+ $logical_base_path,
+ $deep_path,
+ $this->psr0Behavior);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function addPear($prefix, $paths) {
+ $logical_base_path = Util::prefixLogicalPath($prefix);
+ foreach ((array) $paths as $root_path) {
+ $deep_path = strlen($root_path)
+ ? rtrim($root_path, '/') . '/' . $logical_base_path
+ : $logical_base_path;
+ $this->prefixMap->registerDeepPath(
+ $logical_base_path,
+ $deep_path,
+ $this->defaultBehavior);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function addPearFlat($prefix, $paths) {
+ $logical_base_path = Util::prefixLogicalPath($prefix);
+ foreach ((array) $paths as $deep_path) {
+ $deep_path = strlen($deep_path) ? (rtrim($deep_path, '/') . '/') : '';
+ $this->prefixMap->registerDeepPath(
+ $logical_base_path,
+ $deep_path,
+ $this->defaultBehavior
+ );
+ }
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/Adapter/ClassFinderAdapterInterface.php b/profiles/dkan/modules/contrib/xautoload/src/Adapter/ClassFinderAdapterInterface.php
new file mode 100644
index 00000000000..0fdd0d02526
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/Adapter/ClassFinderAdapterInterface.php
@@ -0,0 +1,56 @@
+system = $system;
+ $this->finder = $finder;
+ $this->namespaceMap = $finder->getNamespaceMap();
+ $this->prefixMap = $finder->getPrefixMap();
+ foreach (array('module', 'theme') as $extension_type) {
+ $this->namespaceBehaviors[$extension_type] = new DrupalExtensionNamespaceFinderPlugin(
+ $extension_type,
+ $this->namespaceMap,
+ $this->prefixMap,
+ $this->system);
+ $this->prefixBehaviors[$extension_type] = new DrupalExtensionUnderscoreFinderPlugin(
+ $extension_type,
+ $this->namespaceMap,
+ $this->prefixMap,
+ $this->system);
+ }
+ $this->defaultBehavior = new DefaultDirectoryBehavior();
+ }
+
+ /**
+ * Register lazy plugins for enabled Drupal modules and themes, assuming that
+ * we don't know yet whether they use PSR-0, PEAR-Flat, or none of these.
+ *
+ * @param string[] $extensions
+ * An array where the keys are extension names, and the values are extension
+ * types like 'module' or 'theme'.
+ */
+ function registerExtensions(array $extensions) {
+
+ $prefix_map = array();
+ $namespace_map = array();
+ foreach ($extensions as $name => $type) {
+ if (empty($this->namespaceBehaviors[$type])) {
+ // Unsupported extension type, e.g. "theme_engine".
+ // This can happen if a site was upgraded from Drupal 6.
+ // See https://drupal.org/comment/8503979#comment-8503979
+ continue;
+ }
+ if (!empty($this->registered[$name])) {
+ // The extension has already been processed.
+ continue;
+ }
+ $namespace_map['Drupal/' . $name . '/'][$name] = $this->namespaceBehaviors[$type];
+ $prefix_map[str_replace('_', '/', $name) . '/'][$name] = $this->prefixBehaviors[$type];
+ $this->registered[$name] = TRUE;
+ }
+ $this->namespaceMap->registerDeepPaths($namespace_map);
+ $this->prefixMap->registerDeepPaths($prefix_map);
+ }
+
+ /**
+ * Register lazy plugins for a given extension, assuming that we don't know
+ * yet whether it uses PSR-0, PEAR-Flat, or none of these.
+ *
+ * @param string $name
+ * @param string $type
+ */
+ function registerExtension($name, $type) {
+ if (!empty($this->registered[$name])) {
+ // The extension has already been processed.
+ return;
+ }
+ $this->namespaceMap->registerDeepPath('Drupal/' . $name . '/', $name, $this->namespaceBehaviors[$type]);
+ $this->prefixMap->registerDeepPath(str_replace('_', '/', $name) . '/', $name, $this->prefixBehaviors[$type]);
+ $this->registered[$name] = TRUE;
+ }
+
+ /**
+ * Register PSR-4 directory for an extension.
+ * Override previous settings for this extension.
+ *
+ * @param string $name
+ * The extension name.
+ * @param string $extension_dir
+ * The directory of the extension.
+ * @param string $subdir
+ * The PSR-4 base directory, relative to the extension directory.
+ * E.g. 'lib' or 'src'.
+ */
+ function registerExtensionPsr4($name, $extension_dir, $subdir) {
+ if (!empty($this->registered[$name])) {
+ if ('psr-4' === $this->registered[$name]) {
+ // It already happened.
+ return;
+ }
+ // Unregister the lazy plugins.
+ $this->namespaceMap->unregisterDeepPath('Drupal/' . $name . '/', $name);
+ $this->prefixMap->unregisterDeepPath(str_replace('_', '/', $name) . '/', $name);
+ }
+
+ $dir = strlen($subdir)
+ ? $extension_dir . '/' . trim($subdir, '/') . '/'
+ : $extension_dir . '/';
+ $this->namespaceMap->registerDeepPath('Drupal/' . $name . '/', $dir, $this->defaultBehavior);
+
+ // Re-add the PSR-0 test directory, for consistency's sake.
+ if (is_dir($psr0_tests_dir = $extension_dir . '/lib/Drupal/' . $name . '/Tests')) {
+ $this->namespaceMap->registerDeepPath('Drupal/' . $name . '/Tests/', $psr0_tests_dir, $this->defaultBehavior);
+ }
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/Adapter/LocalDirectoryAdapter.php b/profiles/dkan/modules/contrib/xautoload/src/Adapter/LocalDirectoryAdapter.php
new file mode 100644
index 00000000000..6949214c40f
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/Adapter/LocalDirectoryAdapter.php
@@ -0,0 +1,278 @@
+finder, $adapter->getClassmapGenerator());
+ $this->master = $adapter;
+ $this->localDirectory = strlen($localDirectory)
+ ? rtrim($localDirectory, '/') . '/'
+ : '';
+ }
+
+ /**
+ * Returns an adapter object that is not relative to a local directory.
+ *
+ * @return ClassFinderAdapter
+ */
+ function absolute() {
+ return $this->master;
+ }
+
+ // Discovery
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Adds source paths for classmap discovery.
+ *
+ * The classmap for each source will be cached between requests.
+ * A "clear all caches" will trigger a rescan.
+ *
+ * @param string[] $paths
+ * File paths or wildcard paths for class discovery.
+ * @param bool $relative
+ * If TRUE, the paths will be relative to $this->localDirectory.
+ */
+ function addClassmapSources($paths, $relative = TRUE) {
+ $relative && $this->prependToPaths($paths);
+ $this->master->addClassmapSources($paths);
+ }
+
+ // Composer tools
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Scans a composer.json file provided by a Composer package.
+ *
+ * @param string $file
+ * @param bool $relative
+ * If TRUE, the paths will be relative to $this->localDirectory.
+ *
+ * @throws \Exception
+ */
+ function composerJson($file, $relative = TRUE) {
+ $relative && $file = $this->localDirectory . $file;
+ $json = ComposerJson::createFromFile($file);
+ $json->writeToAdapter($this->master);
+ }
+
+ /**
+ * Scans a directory containing Composer-generated autoload files.
+ *
+ * @param string $dir
+ * Directory to look for Composer-generated files. Typically this is the
+ * ../vendor/composer dir.
+ * @param bool $relative
+ * If TRUE, the paths will be relative to $this->localDirectory.
+ */
+ function composerDir($dir, $relative = TRUE) {
+ $relative && $dir = $this->localDirectory . $dir;
+ $dir = ComposerDir::create($dir);
+ $dir->writeToAdapter($this->master);
+ }
+
+ // multiple PSR-0 / PSR-4
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Adds multiple PSR-0 prefixes.
+ *
+ * @param array $prefixes
+ * Each array key is a PSR-0 prefix, e.g. "Acme\\FooPackage\\".
+ * Each array value is either a PSR-0 base directory or an array of PSR-0
+ * base directories.
+ * @param bool $relative
+ * If TRUE, the paths will be relative to $this->localDirectory.
+ */
+ function addMultiplePsr0(array $prefixes, $relative = TRUE) {
+ $relative && $this->prependMultiple($prefixes);
+ $this->master->addMultiplePsr0($prefixes);
+ }
+
+ /**
+ * Adds multiple PSR-4 namespaces.
+ *
+ * @param array $map
+ * Each array key is a namespace, e.g. "Acme\\FooPackage\\".
+ * Each array value is either a PSR-4 base directory or an array of PSR-4
+ * base directories.
+ * @param bool $relative
+ * If TRUE, the paths will be relative to $this->localDirectory.
+ */
+ function addMultiplePsr4(array $map, $relative = TRUE) {
+ $relative && $this->prependMultiple($map);
+ $this->master->addMultiplePsr4($map);
+ }
+
+ // Composer ClassLoader
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Registers an array ("map") of classes to file paths.
+ *
+ * @param array $classMap
+ * The map of classes to file paths.
+ * @param bool $relative
+ * If TRUE, the paths will be relative to $this->localDirectory.
+ */
+ function addClassMap(array $classMap, $relative = TRUE) {
+ $relative && $this->prependToPaths($classMap);
+ $this->master->addClassMap($classMap);
+ }
+
+ /**
+ * Adds a PSR-0 style prefix. Alias for ->addPsr0().
+ *
+ * @param string $prefix
+ * @param string|\string[] $paths
+ * @param bool $relative
+ * If TRUE, the paths will be relative to $this->localDirectory.
+ */
+ function add($prefix, $paths, $relative = TRUE) {
+ $relative && $this->prependToPaths($paths);
+ $this->master->add($prefix, $paths);
+ }
+
+ /**
+ * Adds a PSR-0 style prefix. Alias for ->add().
+ *
+ * @param string $prefix
+ * @param string|\string[] $paths
+ * @param bool $relative
+ * If TRUE, the paths will be relative to $this->localDirectory.
+ */
+ function addPsr0($prefix, $paths, $relative = TRUE) {
+ $relative && $this->prependToPaths($paths);
+ $this->master->add($prefix, $paths);
+ }
+
+ /**
+ * Adds a PSR-4 style namespace.
+ *
+ * @param string $prefix
+ * @param string|\string[] $paths
+ * @param bool $relative
+ * If TRUE, the paths will be relative to $this->localDirectory.
+ */
+ function addPsr4($prefix, $paths, $relative = TRUE) {
+ $relative && $this->prependToPaths($paths);
+ $this->master->addPsr4($prefix, $paths);
+ }
+
+ // More convenience stuff
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Adds a PSR-0 style namespace.
+ *
+ * This will assume that we are really dealing with a namespace, even if it
+ * has no '\\' included.
+ *
+ * @param string $prefix
+ * @param string|\string[] $paths
+ * @param bool $relative
+ * If TRUE, the paths will be relative to $this->localDirectory.
+ */
+ function addNamespacePsr0($prefix, $paths, $relative = TRUE) {
+ $relative && $this->prependToPaths($paths);
+ $this->master->addNamespacePsr0($prefix, $paths);
+ }
+
+ /**
+ * Adds a PEAR-like prefix.
+ *
+ * This will assume with no further checks that $prefix contains no namespace
+ * separator.
+ *
+ * @param string $prefix
+ * The prefix, e.g. 'Acme_FooPackage_'
+ * @param string|string[] $paths
+ * An array of paths, or one specific path.
+ * E.g. 'lib' for $relative = TRUE,
+ * or 'sites/all/libraries/AcmeFooPackage/lib' for $relative = FALSE.
+ * @param bool $relative
+ * If TRUE, the paths will be relative to $this->localDirectory.
+ */
+ function addPear($prefix, $paths, $relative = TRUE) {
+ $relative && $this->prependToPaths($paths);
+ $this->master->addPear($prefix, $paths);
+ }
+
+ /**
+ * Adds a prefix similar to PEAR, but with flat directories.
+ *
+ * This will assume with no further checks that $prefix contains no namespace
+ * separator.
+ *
+ * @param string $prefix
+ * The prefix, e.g. 'Acme_FooPackage_'
+ * @param string|string[] $paths
+ * An array of paths, or one specific path.
+ * E.g. 'lib' for $relative = TRUE,
+ * or 'sites/all/libraries/AcmeFooPackage/lib' for $relative = FALSE.
+ * @param bool $relative
+ * If TRUE, the paths will be relative to $this->localDirectory.
+ */
+ function addPearFlat($prefix, $paths, $relative = TRUE) {
+ $relative && $this->prependToPaths($paths);
+ $this->master->addPearFlat($prefix, $paths);
+ }
+
+ // Relative path handling
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Prepends $this->localDirectory to a number of paths.
+ *
+ * @param array $map
+ */
+ protected function prependMultiple(array &$map) {
+ foreach ($map as &$paths) {
+ $paths = (array) $paths;
+ foreach ($paths as &$path) {
+ $path = $this->localDirectory . $path;
+ }
+ }
+ }
+
+ /**
+ * Prepends $this->localDirectory to a number of paths.
+ *
+ * @param string|string[] &$paths
+ */
+ protected function prependToPaths(&$paths) {
+ if (!is_array($paths)) {
+ $paths = $this->localDirectory . $paths;
+ }
+ else {
+ foreach ($paths as &$path) {
+ $path = $this->localDirectory . $path;
+ }
+ }
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/CacheManager/CacheManager.php b/profiles/dkan/modules/contrib/xautoload/src/CacheManager/CacheManager.php
new file mode 100644
index 00000000000..633adb8275d
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/CacheManager/CacheManager.php
@@ -0,0 +1,68 @@
+prefix = $prefix;
+ $this->system = $system;
+ }
+
+ /**
+ * This method has side effects, so it is not the constructor.
+ *
+ * @param \Drupal\xautoload\DrupalSystem\DrupalSystemInterface $system
+ *
+ * @return CacheManager
+ */
+ static function create(DrupalSystemInterface $system) {
+ $prefix = $system->variableGet(XAUTOLOAD_VARNAME_CACHE_PREFIX, NULL);
+ $manager = new self($prefix, $system);
+ if (empty($prefix)) {
+ $manager->renewCachePrefix();
+ }
+ return $manager;
+ }
+
+ /**
+ * @param CacheManagerObserverInterface $observer
+ */
+ function observeCachePrefix($observer) {
+ $observer->setCachePrefix($this->prefix);
+ $this->observers[] = $observer;
+ }
+
+ /**
+ * Renew the cache prefix, save it, and notify all observers.
+ */
+ function renewCachePrefix() {
+ $this->prefix = Util::randomString();
+ $this->system->variableSet(XAUTOLOAD_VARNAME_CACHE_PREFIX, $this->prefix);
+ foreach ($this->observers as $observer) {
+ $observer->setCachePrefix($this->prefix);
+ }
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/CacheManager/CacheManagerObserverInterface.php b/profiles/dkan/modules/contrib/xautoload/src/CacheManager/CacheManagerObserverInterface.php
new file mode 100644
index 00000000000..4d13554e0dc
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/CacheManager/CacheManagerObserverInterface.php
@@ -0,0 +1,14 @@
+loader = $loader;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function cacheMiss($finder) {
+ $this->loader->setFinder($finder);
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/CacheMissObserver/CacheMissObserverInterface.php b/profiles/dkan/modules/contrib/xautoload/src/CacheMissObserver/CacheMissObserverInterface.php
new file mode 100644
index 00000000000..bd1d21973ac
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/CacheMissObserver/CacheMissObserverInterface.php
@@ -0,0 +1,29 @@
+prefixMap = new GenericPrefixMap('_');
+ $this->namespaceMap = new GenericPrefixMap('\\');
+ $this->defaultBehavior = new DefaultDirectoryBehavior();
+ $this->psr0Behavior = new Psr0DirectoryBehavior();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function getPrefixMap() {
+ return $this->prefixMap;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function getNamespaceMap() {
+ return $this->namespaceMap;
+ }
+
+ // Composer compatibility
+ // ---------------------------------------------------------------------------
+
+ /**
+ * {@inheritdoc}
+ */
+ function addClassMap(array $classMap) {
+ $this->registerClasses($classMap);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function add($prefix, $paths) {
+ if (FALSE === strpos($prefix, '\\')) {
+ // Due to the ambiguity of PSR-0, this could be either PEAR-like or namespaced.
+ $logical_base_path = Util::prefixLogicalPath($prefix);
+ foreach ((array) $paths as $root_path) {
+ $deep_path = strlen($root_path)
+ ? rtrim($root_path, '/') . '/' . $logical_base_path
+ : $logical_base_path;
+ $this->prefixMap->registerDeepPath(
+ $logical_base_path,
+ $deep_path,
+ $this->defaultBehavior);
+ }
+ }
+ // Namespaced PSR-0
+ $logical_base_path = Util::namespaceLogicalPath($prefix);
+ foreach ((array) $paths as $root_path) {
+ $deep_path = strlen($root_path)
+ ? rtrim($root_path, '/') . '/' . $logical_base_path
+ : $logical_base_path;
+ $this->namespaceMap->registerDeepPath(
+ $logical_base_path,
+ $deep_path,
+ $this->psr0Behavior);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function addPsr0($prefix, $paths) {
+ $this->add($prefix, $paths);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function addPsr4($prefix, $paths) {
+ // Namespaced PSR-4
+ $logical_base_path = Util::namespaceLogicalPath($prefix);
+ foreach ((array) $paths as $deep_path) {
+ $deep_path = strlen($deep_path)
+ ? rtrim($deep_path, '/') . '/'
+ : '';
+ $this->namespaceMap->registerDeepPath(
+ $logical_base_path,
+ $deep_path,
+ $this->defaultBehavior);
+ }
+ }
+
+ // More convenience stuff
+ // ---------------------------------------------------------------------------
+
+ /**
+ * {@inheritdoc}
+ */
+ function addNamespacePsr0($prefix, $paths) {
+ $logical_base_path = Util::namespaceLogicalPath($prefix);
+ foreach ((array) $paths as $root_path) {
+ $deep_path = strlen($root_path)
+ ? rtrim($root_path, '/') . '/' . $logical_base_path
+ : $logical_base_path;
+ $this->namespaceMap->registerDeepPath(
+ $logical_base_path,
+ $deep_path,
+ $this->psr0Behavior);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function addPear($prefix, $paths) {
+ $logical_base_path = Util::prefixLogicalPath($prefix);
+ foreach ((array) $paths as $root_path) {
+ $deep_path = strlen($root_path)
+ ? rtrim($root_path, '/') . '/' . $logical_base_path
+ : $logical_base_path;
+ $this->prefixMap->registerDeepPath(
+ $logical_base_path,
+ $deep_path,
+ $this->defaultBehavior);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function addPearFlat($prefix, $paths) {
+ $logical_base_path = Util::prefixLogicalPath($prefix);
+ foreach ((array) $paths as $deep_path) {
+ $deep_path = strlen($deep_path)
+ ? (rtrim($deep_path, '/') . '/')
+ : '';
+ $this->prefixMap->registerDeepPath(
+ $logical_base_path,
+ $deep_path,
+ $this->defaultBehavior
+ );
+ }
+ }
+
+ // Class map stuff
+ // ---------------------------------------------------------------------------
+
+ /**
+ * {@inheritdoc}
+ */
+ function registerClass($class, $file_path) {
+ $this->classes[$class][$file_path] = TRUE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function registerClasses($classes) {
+ foreach ($classes as $class => $file_path) {
+ $this->classes[$class][$file_path] = TRUE;
+ }
+ }
+
+ // Prefix stuff
+ // ---------------------------------------------------------------------------
+
+ /**
+ * {@inheritdoc}
+ */
+ function registerPrefixRoot($prefix, $root_path, $behavior = NULL) {
+ if (!isset($behavior)) {
+ $behavior = $this->defaultBehavior;
+ }
+ $logical_base_path = Util::prefixLogicalPath($prefix);
+ $deep_path = strlen($root_path)
+ ? rtrim($root_path, '/') . '/' . $logical_base_path
+ : $logical_base_path;
+ $this->prefixMap->registerDeepPath(
+ $logical_base_path,
+ $deep_path,
+ $behavior);
+
+ if (strlen($prefix)) {
+ // We assume that the class named $prefix is also found at this path.
+ $filepath = substr($deep_path, 0, -1) . '.php';
+ $this->registerClass($prefix, $filepath);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function registerPrefixesRoot($map, $behavior = NULL) {
+ if (!isset($behavior)) {
+ $behavior = $this->defaultBehavior;
+ }
+ $deep_map = array();
+ foreach ($map as $prefix => $root_path) {
+ $logical_base_path = Util::prefixLogicalPath($prefix);
+ $deep_path = strlen($root_path)
+ ? rtrim($root_path, '/') . '/' . $logical_base_path
+ : $logical_base_path;
+ $deep_map[$logical_base_path][$deep_path] = $behavior;
+
+ // Register the class with name $prefix.
+ if (strlen($prefix)) {
+ $filepath = substr($deep_path, 0, -1) . '.php';
+ $this->classes[$prefix][$filepath] = TRUE;
+ }
+ }
+ $this->prefixMap->registerDeepPaths($deep_map);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function registerPrefixDeep($prefix, $deep_path, $behavior = NULL) {
+ if (!isset($behavior)) {
+ $behavior = $this->defaultBehavior;
+ }
+ $this->registerPrefixDeepLocation($prefix, $deep_path, $behavior);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function registerPrefixesDeep($map, $behavior = NULL) {
+ if (!isset($behavior)) {
+ $behavior = $this->defaultBehavior;
+ }
+ $deep_map = array();
+ foreach ($map as $prefix => $deep_path) {
+ $logical_base_path = Util::prefixLogicalPath($prefix);
+ $deep_path = strlen($deep_path)
+ ? rtrim($deep_path, '/') . '/'
+ : '';
+ $deep_map[$logical_base_path][$deep_path] = $behavior;
+ }
+ $this->prefixMap->registerDeepPaths($deep_map);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function registerPrefixDeepLocation($prefix, $deep_path, $behavior = NULL) {
+ if (!isset($behavior)) {
+ $behavior = $this->defaultBehavior;
+ }
+ $logical_base_path = Util::prefixLogicalPath($prefix);
+ $deep_path = strlen($deep_path)
+ ? rtrim($deep_path, '/') . '/'
+ : '';
+ $this->prefixMap->registerDeepPath(
+ $logical_base_path,
+ $deep_path,
+ $behavior);
+ }
+
+ // Namespace stuff
+ // ---------------------------------------------------------------------------
+
+ /**
+ * {@inheritdoc}
+ */
+ function registerNamespaceRoot($namespace, $root_path, $behavior = NULL) {
+ if (!isset($behavior)) {
+ $behavior = $this->defaultBehavior;
+ }
+ $logical_base_path = Util::namespaceLogicalPath($namespace);
+ $deep_path = strlen($root_path)
+ ? rtrim($root_path, '/') . '/' . $logical_base_path
+ : $logical_base_path;
+ $this->namespaceMap->registerDeepPath(
+ $logical_base_path,
+ $deep_path,
+ $behavior);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function registerNamespacesRoot($map, $behavior = NULL) {
+ if (!isset($behavior)) {
+ $behavior = $this->defaultBehavior;
+ }
+ $deep_map = array();
+ foreach ($map as $namespace => $root_path) {
+ $logical_base_path = Util::namespaceLogicalPath($namespace);
+ $deep_path = strlen($root_path)
+ ? rtrim($root_path, '/') . '/' . $logical_base_path
+ : $logical_base_path;
+ $deep_map[$logical_base_path][$deep_path] = $behavior;
+ }
+ $this->namespaceMap->registerDeepPaths($deep_map);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function registerNamespaceDeep($namespace, $path, $behavior = NULL) {
+ if (!isset($behavior)) {
+ $behavior = $this->defaultBehavior;
+ }
+ $logical_base_path = Util::namespaceLogicalPath($namespace);
+ $deep_path = strlen($path)
+ ? $path . '/'
+ : '';
+ $this->namespaceMap->registerDeepPath(
+ $logical_base_path,
+ $deep_path,
+ $behavior);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function registerNamespacesDeep($map, $behavior = NULL) {
+ if (!isset($behavior)) {
+ $behavior = $this->defaultBehavior;
+ }
+ $deep_map = array();
+ foreach ($map as $namespace => $deep_path) {
+ $logical_base_path = Util::namespaceLogicalPath($namespace);
+ $deep_path = strlen($deep_path)
+ ? rtrim($deep_path, '/') . '/'
+ : '';
+ $deep_map[$logical_base_path][$deep_path] = $behavior;
+ }
+ $this->namespaceMap->registerDeepPaths($deep_map);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function registerNamespaceDeepLocation($namespace, $path, $behavior = NULL) {
+ if (!isset($behavior)) {
+ $behavior = $this->defaultBehavior;
+ }
+ $namespace_path_fragment = Util::namespaceLogicalPath($namespace);
+ $deep_path = strlen($path)
+ ? $path . '/'
+ : '';
+ $this->namespaceMap->registerDeepPath(
+ $namespace_path_fragment,
+ $deep_path,
+ $behavior);
+ }
+
+ // ---------------------------------------------------------------------------
+
+ /**
+ * {@inheritdoc}
+ */
+ function loadClass($class) {
+
+ // Fix the behavior of some PHP versions that prepend '\\' to the class name.
+ if ('\\' === $class[0]) {
+ $class = substr($class, 1);
+ }
+
+ // First check if the literal class name is registered.
+ if (!empty($this->classes[$class])) {
+ foreach ($this->classes[$class] as $filepath => $true) {
+ if (file_exists($filepath)) {
+ require $filepath;
+
+ return TRUE;
+ }
+ }
+ }
+
+ // Check if the class has a namespace.
+ if (FALSE !== $pos = strrpos($class, '\\')) {
+
+ // Build the "logical path" based on PSR-4 replacement rules.
+ $logical_path = str_replace('\\', '/', $class) . '.php';
+
+ return $this->namespaceMap->loadClass($class, $logical_path, $pos);
+ }
+
+ // Build the "logical path" based on PEAR replacement rules.
+ $pear_logical_path = str_replace('_', '/', $class) . '.php';
+
+ // Clean up surplus '/' resulting from duplicate underscores, or an
+ // underscore at the beginning of the class.
+ while (FALSE !== $pos = strrpos('/' . $pear_logical_path, '//')) {
+ $pear_logical_path{$pos} = '_';
+ }
+
+ // Check if the class has any underscore.
+ $pos = strrpos($pear_logical_path, '/');
+
+ return $this->prefixMap->loadClass($class, $pear_logical_path, $pos);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function apiFindFile($api, $class) {
+
+ // Fix the behavior of some PHP versions that prepend '\\' to the class name.
+ if ('\\' === $class[0]) {
+ $class = substr($class, 1);
+ }
+
+ // First check if the literal class name is registered.
+ if (!empty($this->classes[$class])) {
+ foreach ($this->classes[$class] as $filepath => $true) {
+ if ($api->suggestFile($filepath)) {
+ return TRUE;
+ }
+ }
+ }
+
+ // Check if the class has a namespace.
+ if (FALSE !== $pos = strrpos($class, '\\')) {
+
+ // Build the "logical path" based on PSR-4 replacement rules.
+ $logical_path = str_replace('\\', '/', $class) . '.php';
+
+ return $this->namespaceMap->apiFindFile($api, $logical_path, $pos);
+ }
+
+ // Build the "logical path" based on PEAR replacement rules.
+ $pear_logical_path = str_replace('_', '/', $class) . '.php';
+
+ // Clean up surplus '/' resulting from duplicate underscores, or an
+ // underscore at the beginning of the class.
+ while (FALSE !== $pos = strrpos('/' . $pear_logical_path, '//')) {
+ $pear_logical_path{$pos} = '_';
+ }
+
+ // Check if the class has any underscore.
+ $pos = strrpos($pear_logical_path, '/');
+
+ return $this->prefixMap->apiFindFile($api, $pear_logical_path, $pos);
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/ClassFinderInterface.php b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/ClassFinderInterface.php
new file mode 100644
index 00000000000..bb421d3bb7c
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/ClassFinderInterface.php
@@ -0,0 +1,30 @@
+suggestFile($file) with all suggestions we
+ * can find, until it returns TRUE. Once suggestFile() returns TRUE, we stop
+ * and return TRUE as well. The $file will be in the $api object, so we
+ * don't need to return it.
+ * @param string $class
+ * The name of the class, with all namespaces prepended.
+ * E.g. Some\Namespace\Some\Class
+ *
+ * @return TRUE|NULL
+ * TRUE, if we found the file for the class.
+ * That is, if the $api->suggestFile($file) method returned TRUE one time.
+ * NULL, if we have no more suggestions.
+ */
+ function apiFindFile($api, $class);
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/CommonRegistrationInterface.php b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/CommonRegistrationInterface.php
new file mode 100644
index 00000000000..6582738dd8f
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/CommonRegistrationInterface.php
@@ -0,0 +1,81 @@
+addPsr0().
+ *
+ * @param string $prefix
+ * @param string[]|string $paths
+ */
+ function add($prefix, $paths);
+
+ /**
+ * Adds a PSR-0 style prefix. Alias for ->add().
+ *
+ * @param string $prefix
+ * @param string[]|string $paths
+ */
+ function addPsr0($prefix, $paths);
+
+ /**
+ * Adds a PSR-4 style namespace.
+ *
+ * @param string $prefix
+ * @param string[]|string $paths
+ */
+ function addPsr4($prefix, $paths);
+
+ // More convenience stuff
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Adds a PSR-0 style namespace.
+ *
+ * This will assume that we are really dealing with a namespace, even if it
+ * has no '\\' included.
+ *
+ * @param string $prefix
+ * @param string[]|string $paths
+ */
+ function addNamespacePsr0($prefix, $paths);
+
+ /**
+ * Adds a PEAR-like prefix.
+ *
+ * This will assume with no further checks that $prefix contains no namespace
+ * separator.
+ *
+ * @param string $prefix
+ * @param string[]|string $paths
+ */
+ function addPear($prefix, $paths);
+
+ /**
+ * Adds a prefix similar to PEAR, but with flat directories.
+ *
+ * This will assume with no further checks that $prefix contains no namespace
+ * separator.
+ *
+ * @param string $prefix
+ * @param string[]|string $paths
+ */
+ function addPearFlat($prefix, $paths);
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/ExtendedClassFinderInterface.php b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/ExtendedClassFinderInterface.php
new file mode 100644
index 00000000000..c5a8b491344
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/ExtendedClassFinderInterface.php
@@ -0,0 +1,215 @@
+ ../lib/My/Prefix/SomeClass.php
+ * My_Prefix -> ../lib/My/Prefix.php
+ * @param DirectoryBehaviorInterface $behavior
+ * If TRUE, then we are not sure if the directory at $path actually exists.
+ * If during the process we find the directory to be nonexistent, we
+ * unregister the path.
+ */
+ function registerPrefixRoot($prefix, $root_path, $behavior = NULL);
+
+ /**
+ * Register an array of PEAR-style deep paths for given class prefixes.
+ *
+ * Note:
+ * This actually goes beyond PEAR style, because it also allows "shallow"
+ * PEAR-like structures like
+ * my_library_Some_Class -> (library dir)/src/Some/Class.php
+ * instead of
+ * my_library_Some_Class -> (library dir)/src/my/library/Some/Class.php
+ * via
+ * $finder->registerPrefixDeep('my_library', "$library_dir/src");
+ *
+ * @param string[] $map
+ * Associative array, the keys are the prefixes, the values are the
+ * directories.
+ * This does NOT cover the class named $prefix itself.
+ * @param DirectoryBehaviorInterface $behavior
+ * If TRUE, then we are not sure if the directory at $path actually exists.
+ * If during the process we find the directory to be nonexistent, we
+ * unregister the path.
+ */
+ function registerPrefixesRoot($map, $behavior = NULL);
+
+ /**
+ * Register a PEAR-style deep path for a given class prefix.
+ *
+ * Note:
+ * This actually goes beyond PEAR style, because it also allows things like
+ * my_library_Some_Class -> (library dir)/src/Some/Class.php
+ * instead of
+ * my_library_Some_Class -> (library dir)/src/my/library/Some/Class.php
+ * via
+ * $finder->registerPrefixDeep('my_library', "$library_dir/src");
+ *
+ * @param string $prefix
+ * Prefix, e.g. "My_Prefix", for classes like "My_Prefix_SomeClass".
+ * This does NOT cover the class named "My_Prefix" itself.
+ * @param string $deep_path
+ * The deep path, e.g. "../lib/My/Prefix", for classes placed in
+ * My_Prefix_SomeClass -> ../lib/My/Prefix/SomeClass.php
+ * @param DirectoryBehaviorInterface $behavior
+ * If TRUE, then we are not sure if the directory at $path actually exists.
+ * If during the process we find the directory to be nonexistent, we
+ * unregister the path.
+ */
+ function registerPrefixDeep($prefix, $deep_path, $behavior = NULL);
+
+ /**
+ * Register an array of PEAR-style deep paths for given class prefixes.
+ *
+ * Note:
+ * This actually goes beyond PEAR style, because it also allows "shallow"
+ * PEAR-like structures like
+ * my_library_Some_Class -> (library dir)/src/Some/Class.php
+ * instead of
+ * my_library_Some_Class -> (library dir)/src/my/library/Some/Class.php
+ * via
+ * $finder->registerPrefixDeep('my_library', "$library_dir/src");
+ *
+ * @param string[] $map
+ * Associative array, the keys are the prefixes, the values are the
+ * directories.
+ * This does NOT cover the class named $prefix itself.
+ * @param \Drupal\xautoload\DirectoryBehavior\DirectoryBehaviorInterface $behavior
+ * If TRUE, then we are not sure if the directory at $path actually exists.
+ * If during the process we find the directory to be nonexistent, we
+ * unregister the path.
+ */
+ function registerPrefixesDeep($map, $behavior = NULL);
+
+ /**
+ * Register a filesystem location for a given class prefix.
+ *
+ * @param string $prefix
+ * The prefix, e.g. "My_Prefix"
+ * @param string $deep_path
+ * The deep filesystem location, e.g. "../lib/My/Prefix".
+ * @param DirectoryBehaviorInterface $behavior
+ * If TRUE, then we are not sure if the directory at $path actually exists.
+ * If during the process we find the directory to be nonexistent, we
+ * unregister the path.
+ */
+ function registerPrefixDeepLocation($prefix, $deep_path, $behavior = NULL);
+
+ // Namespace stuff
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Register a PSR-0 root folder for a given namespace.
+ *
+ * @param string $namespace
+ * The namespace, e.g. "My\Namespace", to cover all classes within that,
+ * e.g. My\Namespace\SomeClass, or My\Namespace\Xyz\SomeClass. This does not
+ * cover the root-level class, e.g. My\Namespace
+ * @param string $root_path
+ * The deep path, e.g. "../lib", if classes reside in e.g.
+ * My\Namespace\SomeClass -> ../lib/My/Namespace/SomeClass.php
+ * @param \Drupal\xautoload\DirectoryBehavior\DirectoryBehaviorInterface $behavior
+ * If TRUE, then we are not sure if the directory at $path actually exists.
+ * If during the process we find the directory to be nonexistent, we
+ * unregister the path.
+ */
+ function registerNamespaceRoot($namespace, $root_path, $behavior = NULL);
+
+ /**
+ * Register PSR-0 root folders for given namespaces.
+ *
+ * @param string[] $map
+ * Associative array, the keys are the namespaces, the values are the
+ * directories.
+ * @param \Drupal\xautoload\DirectoryBehavior\DirectoryBehaviorInterface $behavior
+ * If TRUE, then we are not sure if the directory at $path actually exists.
+ * If during the process we find the directory to be nonexistent, we
+ * unregister the path.
+ */
+ function registerNamespacesRoot($map, $behavior = NULL);
+
+ /**
+ * Alias for registerNamespaceDeepLocation()
+ *
+ * @param string $namespace
+ * The namespace, e.g. "My\Namespace"
+ * @param string $path
+ * The deep path, e.g. "../lib/My/Namespace"
+ * @param \Drupal\xautoload\DirectoryBehavior\DirectoryBehaviorInterface $behavior
+ * If TRUE, then we are not sure if the directory at $path actually exists.
+ * If during the process we find the directory to be nonexistent, we
+ * unregister the path.
+ */
+ function registerNamespaceDeep($namespace, $path, $behavior = NULL);
+
+ /**
+ * Register a number of "deep" namespace directories at once.
+ *
+ * @param string[] $map
+ * @param DirectoryBehaviorInterface $behavior
+ */
+ function registerNamespacesDeep($map, $behavior = NULL);
+
+ /**
+ * Register a deep filesystem location for a given namespace.
+ *
+ * @param string $namespace
+ * The namespace, e.g. "My\Namespace"
+ * @param string $path
+ * The deep path, e.g. "../lib/My/Namespace"
+ * @param DirectoryBehaviorInterface $behavior
+ * If TRUE, then we are not sure if the directory at $path actually exists.
+ * If during the process we find the directory to be nonexistent, we
+ * unregister the path.
+ */
+ function registerNamespaceDeepLocation($namespace, $path, $behavior = NULL);
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/GenericPrefixMap.php b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/GenericPrefixMap.php
new file mode 100644
index 00000000000..9f2e5f604a0
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/GenericPrefixMap.php
@@ -0,0 +1,242 @@
+separator = $separator;
+ }
+
+ /**
+ * If a class file would be in
+ * $psr0_root . '/' . $path_fragment . $path_suffix
+ * then instead, we look in
+ * $deep_path . $path_suffix
+ *
+ * @param string $logical_base_path
+ * The would-be namespace path relative to PSR-0 root.
+ * That is, the namespace with '\\' replaced by '/'.
+ * @param string $deep_path
+ * The filesystem location of the (PSR-0) subfolder for the given namespace.
+ * @param DirectoryBehaviorInterface $behavior
+ * Behavior in this directory.
+ */
+ function registerDeepPath($logical_base_path, $deep_path, $behavior) {
+ $this->paths[$logical_base_path][$deep_path] = $behavior;
+ }
+
+ /**
+ * @param string $logical_base_path
+ * The would-be namespace path relative to PSR-0 root.
+ * That is, the namespace with '\\' replaced by '/'.
+ * @param string $deep_path
+ * The filesystem location of the (PSR-0) subfolder for the given namespace.
+ * @param DirectoryBehaviorInterface $behavior
+ * Behavior in this directory.
+ */
+ function prependDeepPath($logical_base_path, $deep_path, $behavior) {
+ $this->paths[$logical_base_path]
+ = isset($this->paths[$logical_base_path])
+ ? array($deep_path => $behavior) + $this->paths[$logical_base_path]
+ : array($deep_path => $behavior);
+ }
+
+ /**
+ * Register a bunch of those paths ..
+ *
+ * @param array[] $map
+ *
+ * @throws \Exception
+ */
+ function registerDeepPaths(array $map) {
+ foreach ($map as $key => $paths) {
+ if (isset($this->paths[$key])) {
+ $paths += $this->paths[$key];
+ }
+ $this->paths[$key] = $paths;
+ }
+ }
+
+ /**
+ * Delete a registered path mapping.
+ *
+ * @param string $logical_base_path
+ * @param string $deep_path
+ */
+ function unregisterDeepPath($logical_base_path, $deep_path) {
+ unset($this->paths[$logical_base_path][$deep_path]);
+ }
+
+
+ /**
+ * @param string $class
+ * @param string $logical_path
+ * Class name translated into a logical path, either with PSR-4 or with PEAR
+ * translation rules.
+ * @param int|bool $lastpos
+ * Position of the last directory separator in $logical_path.
+ * FALSE, if there is no directory separator in $logical_path.
+ *
+ * @return bool|NULL
+ * TRUE, if the class was found.
+ */
+ function loadClass($class, $logical_path, $lastpos) {
+ $pos = $lastpos;
+ while (TRUE) {
+ $logical_base_path = (FALSE === $pos)
+ ? ''
+ : substr($logical_path, 0, $pos + 1);
+
+ if (isset($this->paths[$logical_base_path])) {
+ foreach ($this->paths[$logical_base_path] as $dir => $behavior) {
+ if ($behavior instanceof DefaultDirectoryBehavior) {
+ // PSR-4 and PEAR
+ if (file_exists($file = $dir . substr($logical_path, $pos + 1))) {
+ require $file;
+
+ return TRUE;
+ }
+ }
+ elseif ($behavior instanceof Psr0DirectoryBehavior) {
+ // PSR-0
+ if (file_exists(
+ $file = $dir
+ . substr($logical_path, $pos + 1, $lastpos - $pos)
+ . str_replace('_', '/', substr($logical_path, $lastpos + 1))
+ )) {
+ require $file;
+
+ return TRUE;
+ }
+ }
+ elseif ($behavior instanceof xautoload_FinderPlugin_Interface) {
+ // Legacy "FinderPlugin".
+ $api = new LoadClassInjectedAPI($class);
+ if ($behavior->findFile($api, $logical_base_path, substr($logical_path, $pos + 1), $dir)) {
+ return TRUE;
+ }
+ }
+ }
+ }
+
+ // Continue with parent fragment.
+ if (FALSE === $pos) {
+ return NULL;
+ }
+
+ $pos = strrpos($logical_base_path, '/', -2);
+ }
+
+ return NULL;
+ }
+
+ /**
+ * Find the file for a class that in PSR-0 or PEAR would be in
+ * $psr_0_root . '/' . $path_fragment . $path_suffix
+ *
+ * @param InjectedApiInterface $api
+ * @param string $logical_path
+ * Class name translated into a logical path, either with PSR-4 or with PEAR
+ * translation rules.
+ * @param int|bool $lastpos
+ * Position of the last directory separator in $logical_path.
+ * FALSE, if there is no directory separator in $logical_path.
+ *
+ * @return bool|NULL
+ * TRUE, if the class was found.
+ */
+ function apiFindFile($api, $logical_path, $lastpos) {
+ $pos = $lastpos;
+ while (TRUE) {
+ $logical_base_path = (FALSE === $pos)
+ ? ''
+ : substr($logical_path, 0, $pos + 1);
+
+ if (isset($this->paths[$logical_base_path])) {
+ foreach ($this->paths[$logical_base_path] as $dir => $behavior) {
+ if ($behavior instanceof DefaultDirectoryBehavior) {
+ // PSR-4 and PEAR
+ if ($api->suggestFile($dir . substr($logical_path, $pos + 1))) {
+ return TRUE;
+ }
+ }
+ elseif ($behavior instanceof Psr0DirectoryBehavior) {
+ // PSR-0
+ if ($api->suggestFile(
+ $dir
+ . substr($logical_path, $pos + 1, $lastpos - $pos)
+ . str_replace('_', '/', substr($logical_path, $lastpos + 1))
+ )) {
+ return TRUE;
+ }
+ }
+ elseif ($behavior instanceof xautoload_FinderPlugin_Interface) {
+ // Legacy "FinderPlugin".
+ if ($behavior->findFile($api, $logical_base_path, substr($logical_path, $pos + 1), $dir)) {
+ return TRUE;
+ }
+ }
+ }
+ }
+
+ // Continue with parent fragment.
+ if (FALSE === $pos) {
+ return NULL;
+ }
+
+ $pos = strrpos($logical_base_path, '/', -2);
+ }
+
+ return NULL;
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/AbstractInjectedApi.php b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/AbstractInjectedApi.php
new file mode 100644
index 00000000000..f4aba964344
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/AbstractInjectedApi.php
@@ -0,0 +1,50 @@
+className = $class_name;
+ }
+
+ /**
+ * This is done in the injected api object, so we can easily provide a mock
+ * implementation.
+ */
+ function is_dir($dir) {
+ return is_dir($dir);
+ }
+
+ /**
+ * Get the name of the class we are looking for.
+ *
+ * @return string
+ * The class we are looking for.
+ */
+ function getClass() {
+ return $this->className;
+ }
+
+ /**
+ * Dummy method to force autoloading this class (or an ancestor).
+ */
+ static function forceAutoload() {
+ // Do nothing.
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/CollectFilesInjectedApi.php b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/CollectFilesInjectedApi.php
new file mode 100644
index 00000000000..b62ef29b10d
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/CollectFilesInjectedApi.php
@@ -0,0 +1,147 @@
+file, will return TRUE.
+ */
+ protected $methodName;
+
+ /**
+ * @var string
+ * The file where $this->$method($this->file) will return TRUE.
+ */
+ protected $file;
+
+ /**
+ * @var array[]
+ * All files that were suggested.
+ */
+ protected $suggestions;
+
+ /**
+ * @param string $class_name
+ * @var string $method
+ * The method that, if called with $this->file, will return TRUE.
+ * @param string $file
+ * The file where $this->$method($this->file) will return TRUE.
+ */
+ function __construct($class_name, $method_name, $file) {
+ $this->methodName = $method_name;
+ $this->file = $file;
+ parent::__construct($class_name);
+ }
+
+ /**
+ * When the process has finished, use this to return the result.
+ *
+ * @return string
+ * The file that is supposed to declare the class.
+ */
+ function getSuggestions() {
+ return $this->suggestions;
+ }
+
+ /**
+ * Suggest a file that, if the file exists,
+ * has to declare the class we are looking for.
+ * Only keep the class on success.
+ *
+ * @param string $file
+ * The file that is supposed to declare the class.
+ *
+ * @return bool
+ * TRUE, if the file exists.
+ * FALSE, otherwise.
+ */
+ function suggestFile($file) {
+ $this->suggestions[] = array(__FUNCTION__, $file);
+ return __FUNCTION__ === $this->methodName && $file === $this->file;
+ }
+
+ /**
+ * Same as suggestFile(), but skip the file_exists(),
+ * assuming that we already know the file exists.
+ *
+ * This could make sense if a plugin already did the file_exists() check.
+ *
+ * @param string $file
+ * The file that is supposed to declare the class.
+ *
+ * @return bool
+ * TRUE, if the file was found - which is always.
+ */
+ function suggestFile_skipFileExists($file) {
+ $this->suggestions[] = array(__FUNCTION__, $file);
+ return TRUE;
+ }
+
+ /**
+ * Same as suggestFile(), but assume that file_exists() returns TRUE.
+ *
+ * @param string $file
+ * The file that is supposed to declare the class.
+ *
+ * @return bool
+ * TRUE, if the file was found - which is always.
+ */
+ function suggestFile_checkNothing($file) {
+ $this->suggestions[] = array(__FUNCTION__, $file);
+ return TRUE;
+ }
+
+ /**
+ * Same as suggestFile(), but check the full PHP include path.
+ *
+ * @param string $file
+ * The file that is supposed to declare the class.
+ *
+ * @return bool
+ * TRUE, if the file exists.
+ * FALSE, otherwise.
+ */
+ function suggestFile_checkIncludePath($file) {
+ $this->suggestions[] = array(__FUNCTION__, $file);
+ return __FUNCTION__ == $this->methodName && $file === $this->file;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function guessFile($file) {
+ $this->suggestions[] = array(__FUNCTION__, $file);
+ return __FUNCTION__ == $this->methodName && $file === $this->file;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function guessPath($file) {
+ $this->suggestions[] = array(__FUNCTION__, $file);
+ return __FUNCTION__ == $this->methodName && $file === $this->file;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function claimFile($file) {
+ $this->suggestions[] = array(__FUNCTION__, $file);
+ return TRUE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function claimPath($file) {
+ $this->suggestions[] = array(__FUNCTION__, $file);
+ return __FUNCTION__ == $this->methodName && $file === $this->file;
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/FindFileInjectedApi.php b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/FindFileInjectedApi.php
new file mode 100644
index 00000000000..67107cd3e91
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/FindFileInjectedApi.php
@@ -0,0 +1,145 @@
+file;
+ }
+
+ /**
+ * Suggest a file that, if the file exists,
+ * has to declare the class we are looking for.
+ * Only keep the class on success.
+ *
+ * @param string $file
+ * The file that is supposed to declare the class.
+ *
+ * @return bool
+ * TRUE, if the file exists.
+ * FALSE, otherwise.
+ */
+ function suggestFile($file) {
+ if (file_exists($file)) {
+ $this->file = $file;
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ /**
+ * Same as suggestFile(), but skip the file_exists(),
+ * assuming that we already know the file exists.
+ *
+ * This could make sense if a plugin already did the file_exists() check.
+ *
+ * @param string $file
+ * The file that is supposed to declare the class.
+ *
+ * @return bool
+ * TRUE, if the file was found - which is always.
+ */
+ function suggestFile_skipFileExists($file) {
+ $this->file = $file;
+ return TRUE;
+ }
+
+ /**
+ * Same as suggestFile(), but assume that file_exists() returns TRUE.
+ *
+ * @param string $file
+ * The file that is supposed to declare the class.
+ *
+ * @return bool
+ * TRUE, if the file was found - which is always.
+ */
+ function suggestFile_checkNothing($file) {
+ $this->file = $file;
+ return TRUE;
+ }
+
+ /**
+ * Same as suggestFile(), but check the full PHP include path.
+ *
+ * @param string $file
+ * The file that is supposed to declare the class.
+ *
+ * @return bool
+ * TRUE, if the file exists.
+ * FALSE, otherwise.
+ */
+ function suggestFile_checkIncludePath($file) {
+ if (FALSE !== $file = Util::findFileInIncludePath($file)) {
+ $this->file = $file;
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function guessFile($file) {
+ // The file must be included, or else we can't know if it defines the class.
+ require_once $file;
+ if (Util::classIsDefined($this->className)) {
+ $this->file = $file;
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function guessPath($file) {
+ if (file_exists($file)) {
+ // The file must be included, or else we can't know if it defines the class.
+ require_once $file;
+ if (Util::classIsDefined($this->className)) {
+ $this->file = $file;
+ return TRUE;
+ }
+ }
+ return FALSE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function claimFile($file) {
+ $this->file = $file;
+ return TRUE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function claimPath($file) {
+ if (file_exists($file)) {
+ $this->file = $file;
+ return TRUE;
+ }
+ return FALSE;
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/InjectedApiInterface.php b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/InjectedApiInterface.php
new file mode 100644
index 00000000000..6e836e90e15
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/InjectedApiInterface.php
@@ -0,0 +1,138 @@
+file = $file;
+ require $file;
+ return TRUE;
+ }
+ else {
+ return FALSE;
+ }
+ }
+
+ /**
+ * Same as suggestFile(), but skip the file_exists(),
+ * assuming that we already know the file exists.
+ *
+ * This could make sense if a plugin already did the file_exists() check.
+ *
+ * @param string $file
+ * The file that is supposed to declare the class.
+ *
+ * @return bool
+ * TRUE, if the file was found - which is always.
+ */
+ function suggestFile_skipFileExists($file) {
+ $this->file = $file;
+ require $file;
+ return TRUE;
+ }
+
+ /**
+ * Same as suggestFile(), but assume that file_exists() returns TRUE.
+ *
+ * @param string $file
+ * The file that is supposed to declare the class.
+ *
+ * @return bool
+ * TRUE, if the file was found - which is always.
+ */
+ function suggestFile_checkNothing($file) {
+ $this->file = $file;
+ require $file;
+ return TRUE;
+ }
+
+ /**
+ * Same as suggestFile(), but check the full PHP include path.
+ *
+ * @param string $file
+ * The file that is supposed to declare the class.
+ *
+ * @return bool
+ * TRUE, if the file exists.
+ * FALSE, otherwise.
+ */
+ function suggestFile_checkIncludePath($file) {
+ if (FALSE !== $file = Util::findFileInIncludePath($file)) {
+ $this->file = $file;
+ require $file;
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function guessFile($file) {
+ require_once $file;
+ if (Util::classIsDefined($this->className)) {
+ $this->file = $file;
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function guessPath($file) {
+ if (file_exists($file)) {
+ require_once $file;
+ if (Util::classIsDefined($this->className)) {
+ $this->file = $file;
+ return TRUE;
+ }
+ }
+ return FALSE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function claimFile($file) {
+ require $file;
+ $this->file = $file;
+ return TRUE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function claimPath($file) {
+ if (file_exists($file)) {
+ require $file;
+ $this->file = $file;
+ return TRUE;
+ }
+ return FALSE;
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/LoadClassInjectedAPI.php b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/LoadClassInjectedAPI.php
new file mode 100644
index 00000000000..a233628cb5c
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/LoadClassInjectedAPI.php
@@ -0,0 +1,105 @@
+className);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function guessPath($file) {
+ if (file_exists($file)) {
+ require_once $file;
+
+ return Util::classIsDefined($this->className);
+ }
+ else {
+ return FALSE;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function claimFile($file) {
+ require $file;
+
+ return TRUE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function claimPath($file) {
+ if (file_exists($file)) {
+ require $file;
+
+ return TRUE;
+ }
+ else {
+ return FALSE;
+ }
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/DrupalCoreRegistryPlugin.php b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/DrupalCoreRegistryPlugin.php
new file mode 100644
index 00000000000..50b45f03306
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/DrupalCoreRegistryPlugin.php
@@ -0,0 +1,79 @@
+baseDir = $baseDir;
+ }
+
+ /**
+ * Find the file for a class that in PSR-0 or PEAR would be in
+ * $psr_0_root . '/' . $path_fragment . $path_suffix
+ *
+ * E.g.:
+ * - The class we look for is Some\Namespace\Some\Class
+ * - The file is actually in "exotic/location.php". This is not following
+ * PSR-0 or PEAR standard, so we need a plugin.
+ * -> The class finder will transform the class name to
+ * "Some/Namespace/Some/Class.php"
+ * - The plugin was registered for the namespace "Some\Namespace". This is
+ * because all those exotic classes all begin with Some\Namespace\
+ * -> The arguments will be:
+ * ($api = the API object, see below)
+ * $logical_base_path = "Some/Namespace/"
+ * $relative_path = "Some/Class.php"
+ * $api->getClass() gives the original class name, if we still need it.
+ * -> We are supposed to:
+ * if ($api->suggestFile('exotic/location.php')) {
+ * return TRUE;
+ * }
+ *
+ * @param InjectedApiInterface $api
+ * An object with a suggestFile() method.
+ * We are supposed to suggest files until suggestFile() returns TRUE, or we
+ * have no more suggestions.
+ * @param string $logical_base_path_empty
+ * The key that this plugin was registered with.
+ * With trailing '/'.
+ * @param string $relative_path_irrelevant
+ * Second part of the canonical path, ending with '.php'.
+ *
+ * @return bool|null
+ * TRUE, if the file was found.
+ * FALSE or NULL, otherwise.
+ */
+ function findFile($api, $logical_base_path_empty, $relative_path_irrelevant) {
+ $q = db_select('registry');
+ // Use LIKE here to make the query case-insensitive.
+ $q->condition('name', db_like($api->getClass()), 'LIKE');
+ $q->addField('registry', 'filename');
+ $stmt = $q->execute();
+ while ($relative_path = $stmt->fetchField()) {
+ $file = $this->baseDir . $relative_path;
+ // Attention: The db_select() above can trigger the class loader for
+ // classes and interfaces of the database layer. This can cause some files
+ // to be included twice, if the file defines more than one class.
+ // So we need to use require_once here, instead of require. That is, use
+ // guessFile() instead of claimFile().
+ if ($api->guessFile($file)) {
+ return TRUE;
+ }
+ }
+ return FALSE;
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/DrupalExtensionNamespaceFinderPlugin.php b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/DrupalExtensionNamespaceFinderPlugin.php
new file mode 100644
index 00000000000..5e23d46984a
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/DrupalExtensionNamespaceFinderPlugin.php
@@ -0,0 +1,219 @@
+type = $type;
+ $this->prefixMap = $prefix_map;
+ $this->namespaceMap = $namespace_map;
+ $this->defaultBehavior = new DefaultDirectoryBehavior();
+ $this->psr0Behavior = new Psr0DirectoryBehavior();
+ $this->system = $system;
+ }
+
+ /**
+ * Looks up a class starting with "Drupal\$extension_name\\".
+ *
+ * This plugin method will be called for every class beginning with
+ * "Drupal\\$extension_name\\", as long as the plugin is registered for
+ * $logical_base_path = 'Drupal/$extension_name/'.
+ *
+ * A similar plugin will is registered along with this one for the PEAR-FLAT
+ * pattern, called for every class beginning with $modulename . '_'.
+ *
+ * The plugin will eventually unregister itself and its cousin, once it has
+ * - determined the correct path for the module, and
+ * - determined that the module is using either PSR-0 or PSR-4.
+ * It does that by including the file candidate for PSR-0 and/or PSR-4 and
+ * checking whether the class is now defined.
+ *
+ * The plugin will instead register a direct
+ *
+ * @param \Drupal\xautoload\ClassFinder\InjectedApi\InjectedApiInterface $api
+ * An object with methods like suggestFile() and guessFile().
+ * @param string $logical_base_path
+ * The logical base path determined from the registered namespace.
+ * E.g. 'Drupal/menupoly/'.
+ * @param string $relative_path
+ * Remaining part of the logical path following $logical_base_path.
+ * E.g. 'FooNamespace/BarClass.php'.
+ * @param string|null $extension_name
+ * Second key that the plugin was registered with. Usually this would be the
+ * physical base directory where we prepend the relative path to get the
+ * file path. But in this case it is simply the extensions name.
+ * E.g. 'menupoly'.
+ *
+ * @return bool|null
+ * TRUE, if the file was found.
+ * FALSE or NULL, otherwise.
+ */
+ function findFile($api, $logical_base_path, $relative_path, $extension_name = NULL) {
+
+ $extension_file = $this->system->drupalGetFilename($this->type, $extension_name);
+ if (empty($extension_file)) {
+ // Extension does not exist, or is not installed.
+ return FALSE;
+ }
+
+ $nspath = 'Drupal/' . $extension_name . '/';
+ $testpath = $nspath . 'Tests/';
+ $uspath = $extension_name . '/';
+ $extension_dir = dirname($extension_file);
+ $src = $extension_dir . '/src/';
+ $lib_psr0 = $extension_dir . '/lib/Drupal/' . $extension_name . '/';
+ $is_test_class = (0 === strpos($relative_path, 'Tests/'));
+
+ // Try PSR-4.
+ if ($api->guessPath($src . $relative_path)) {
+ if ($is_test_class) {
+ // Register PSR-0 directory for "Drupal\\$modulename\\Tests\\"
+ // This generally happens only once per module, because for subsequent
+ // test classes the class will be found before this plugin is triggered.
+ // However, for class_exists() with nonexistent test files, this line
+ // will occur more than once.
+ $this->namespaceMap->registerDeepPath($testpath, $src . 'Tests/', $this->defaultBehavior);
+ // We found the class, but it is a test class, so it does not tell us
+ // anything about whether non-test classes are in PSR-0 or PSR-4.
+ return TRUE;
+ }
+
+ // Register PSR-4 directory for "Drupal\\$modulename\\".
+ $this->namespaceMap->registerDeepPath($nspath, $src, $this->defaultBehavior);
+
+ // Unregister the lazy plugins, including this one, for
+ // "Drupal\\$modulename\\" and for $modulename . '_'.
+ $this->namespaceMap->unregisterDeepPath($nspath, $extension_name);
+ $this->prefixMap->unregisterDeepPath($uspath, $extension_name);
+
+ // Test classes in PSR-4 are already covered by the PSR-4 plugin we just
+ // registered. But test classes in PSR-0 would slip through. So we check
+ // if a separate behavior needs to be registered for those.
+ if (is_dir($lib_psr0 . 'Tests/')) {
+ $this->namespaceMap->registerDeepPath($testpath, $lib_psr0 . 'Tests/', $this->psr0Behavior);
+ }
+
+ // The class was found, so return TRUE.
+ return TRUE;
+ }
+
+ // Build PSR-0 relative path.
+ if (FALSE === $nspos = strrpos($relative_path, '/')) {
+ // No namespace separators in $relative_path, so all underscores must be
+ // replaced.
+ $relative_path = str_replace('_', '/', $relative_path);
+ }
+ else {
+ // Replace only those underscores in $relative_path after the last
+ // namespace separator, from right to left. On average there is no or very
+ // few of them, so this loop rarely iterates even once.
+ while ($nspos < $uspos = strrpos($relative_path, '_')) {
+ $relative_path{$uspos} = '/';
+ }
+ }
+
+ // Try PSR-0
+ if ($api->guessPath($lib_psr0 . $relative_path)) {
+ if ($is_test_class) {
+ // We know now that there are test classes using PSR-0.
+ $this->namespaceMap->registerDeepPath($testpath, $lib_psr0 . 'Tests/', $this->psr0Behavior);
+ // We found the class, but it is a test class, so it does not tell us
+ // anything about whether non-test classes are in PSR-0 or PSR-4.
+ return TRUE;
+ }
+
+ // Unregister the lazy plugins, including this one.
+ $this->namespaceMap->unregisterDeepPath($nspath, $extension_name);
+ $this->prefixMap->unregisterDeepPath($uspath, $extension_name);
+
+ // Register PSR-0 for regular namespaced classes.
+ $this->namespaceMap->registerDeepPath($nspath, $lib_psr0, $this->psr0Behavior);
+
+ // Test classes in PSR-0 are already covered by the PSR-0 plugin we just
+ // registered. But test classes in PSR-4 would slip through. So we check
+ // if a separate behavior needs to be registered for those.
+ # if (is_dir($src . 'Tests/')) {
+ # $this->namespaceMap->registerDeepPath($testpath, $src . 'Tests/', $this->psr0Behavior);
+ # }
+
+ // The class was found, so return TRUE.
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/DrupalExtensionUnderscoreFinderPlugin.php b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/DrupalExtensionUnderscoreFinderPlugin.php
new file mode 100644
index 00000000000..79465365823
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/DrupalExtensionUnderscoreFinderPlugin.php
@@ -0,0 +1,53 @@
+system->drupalGetFilename($this->type, $extension_name);
+
+ if (empty($extension_file)) {
+ // Extension does not exist, or is not installed.
+ return FALSE;
+ }
+
+ $nspath = 'Drupal/' . $extension_name . '/';
+ $testpath = $nspath . 'Tests/';
+ $uspath = $extension_name . '/';
+ $lib = dirname($extension_file) . '/lib/';
+ $lib_psr0 = $lib . 'Drupal/' . $extension_name . '/';
+
+ // Try PEAR-Flat.
+ if ($api->guessPath($lib . $relative_path)) {
+ // Register PEAR-Flat.
+ $this->prefixMap->registerDeepPath($uspath, $lib, $this->defaultBehavior);
+ // Unregister the lazy plugins.
+ $this->namespaceMap->unregisterDeepPath($nspath, $extension_name);
+ $this->prefixMap->unregisterDeepPath($uspath, $extension_name);
+ // See if there are PSR-0 or PSR-4 test classes.
+ if (is_dir($lib_psr0 . 'Tests/')) {
+ $this->namespaceMap->registerDeepPath(
+ $testpath,
+ $lib_psr0 . 'Tests/',
+ $this->psr0Behavior);
+ }
+ if (is_dir($lib . 'Tests/')) {
+ $this->namespaceMap->registerDeepPath(
+ $testpath,
+ $lib . 'Tests/',
+ $this->defaultBehavior);
+ }
+
+ // The class was found, so return TRUE.
+ return TRUE;
+ }
+
+ // The class was not found, so return FALSE.
+ return FALSE;
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/FinderPluginInterface.php b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/FinderPluginInterface.php
new file mode 100644
index 00000000000..4803d0997b2
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/FinderPluginInterface.php
@@ -0,0 +1,65 @@
+ The class finder will transform the class name to
+ * "Some/Namespace/Some/Class.php"
+ * - The plugin was registered for the namespace "Some\Namespace". This is
+ * because all those exotic classes all begin with Some\Namespace\
+ * -> The arguments will be:
+ * ($api = the API object, see below)
+ * $path_fragment = "Some/Namespace/"
+ * $path_suffix = "Some/Class.php"
+ * $api->getClass() gives the original class name, if we still need it.
+ * -> We are supposed to:
+ * if ($api->suggestFile('exotic/location.php')) {
+ * return TRUE;
+ * }
+ *
+ * @param InjectedApiInterface $api
+ * An object with a suggestFile() method.
+ * We are supposed to suggest files until suggestFile() returns TRUE, or we
+ * have no more suggestions.
+ * @param string $path_fragment
+ * The key that this plugin was registered with.
+ * With trailing '/'.
+ * @param string $path_suffix
+ * Second part of the canonical path, ending with '.php'.
+ * @param int|string $id
+ * Id under which the plugin was registered.
+ * This may be a numeric id, or a string key.
+ *
+ * @return bool|null
+ * TRUE, if the file was found.
+ * FALSE, otherwise.
+ *
+ * NOTE:
+ * The signature of this method has changed since the legacy base interface,
+ * with a new optional parameter being added.
+ * Due to a bug in PHP 5.3.0 - 5.3.8, redeclaring the method with the
+ * modified signature would result in a fatal error in these PHP versions.
+ * This is why the method is commented out.
+ * The additional optional parameter can still be added in implementations.
+ */
+ # function findFile($api, $path_fragment, $path_suffix, $id = NULL);
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/Psr4FinderPlugin.php b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/Psr4FinderPlugin.php
new file mode 100644
index 00000000000..8ecd8b7ea29
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/Psr4FinderPlugin.php
@@ -0,0 +1,34 @@
+getClass(), strlen($logical_base_path));
+ $relative_path = str_replace('\\', '/', $relative_classname) . '.php';
+ return $api->suggestFile($base_dir . '/' . $relative_path);
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/ProxyClassFinder.php b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/ProxyClassFinder.php
new file mode 100644
index 00000000000..97ce4068108
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/ClassFinder/ProxyClassFinder.php
@@ -0,0 +1,90 @@
+finder = $finder;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function loadClass($class) {
+ $this->initFinder();
+ $this->finder->loadClass($class);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function apiFindFile($api, $class) {
+ $this->initFinder();
+
+ return $this->finder->apiFindFile($api, $class);
+ }
+
+ /**
+ * @param CacheMissObserverInterface $observer
+ */
+ function observeFirstCacheMiss($observer) {
+ if (!$this->initialized) {
+ $this->cacheMissObservers[] = $observer;
+ }
+ else {
+ $observer->cacheMiss($this->finder);
+ }
+ }
+
+ /**
+ * @return ClassFinderInterface
+ */
+ function getFinder() {
+ $this->initFinder();
+
+ return $this->finder;
+ }
+
+ /**
+ * Initialize the finder and notify cache miss observers.
+ */
+ protected function initFinder() {
+ if (!$this->initialized) {
+ $this->initialized = TRUE;
+ foreach ($this->cacheMissObservers as $operation) {
+ $operation->cacheMiss($this->finder);
+ }
+ }
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/AbstractCachedClassLoader.php b/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/AbstractCachedClassLoader.php
new file mode 100644
index 00000000000..a8cd44a439f
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/AbstractCachedClassLoader.php
@@ -0,0 +1,51 @@
+checkRequirements()) {
+ $class = get_class($loader);
+ throw new CacheNotSupportedException("Unable to use $class, because the respetive PHP extension is not enabled.");
+ }
+ $cacheManager->observeCachePrefix($loader);
+
+ return $loader;
+ }
+
+ /**
+ * @return bool
+ */
+ protected abstract function checkRequirements();
+
+ /**
+ * {@inheritdoc}
+ */
+ function setCachePrefix($prefix) {
+ $this->prefix = $prefix;
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/AbstractClassLoader.php b/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/AbstractClassLoader.php
new file mode 100644
index 00000000000..f5b7ca828e5
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/AbstractClassLoader.php
@@ -0,0 +1,41 @@
+= 0) {
+ spl_autoload_register(array($this, 'loadClass'), TRUE, $prepend);
+ }
+ elseif ($prepend) {
+ $loaders = spl_autoload_functions();
+ spl_autoload_register(array($this, 'loadClass'));
+ foreach ($loaders as $loader) {
+ spl_autoload_unregister($loader);
+ spl_autoload_register($loader);
+ }
+ }
+ else {
+ spl_autoload_register(array($this, 'loadClass'));
+ }
+ }
+
+ /**
+ * Unregister from the spl autoload stack.
+ */
+ function unregister() {
+ spl_autoload_unregister(array($this, 'loadClass'));
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/AbstractClassLoaderDecorator.php b/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/AbstractClassLoaderDecorator.php
new file mode 100644
index 00000000000..f3774122576
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/AbstractClassLoaderDecorator.php
@@ -0,0 +1,41 @@
+finder = $finder;
+ }
+
+ /**
+ * Replace the finder with another one.
+ *
+ * @param ClassFinderInterface $finder
+ * The object that does the actual class finding.
+ */
+ function setFinder($finder) {
+ $this->finder = $finder;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function loadClass($class) {
+ $this->finder->loadClass($class);
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/AbstractQueuedCachedClassLoader.php b/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/AbstractQueuedCachedClassLoader.php
new file mode 100644
index 00000000000..004c0a331f1
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/AbstractQueuedCachedClassLoader.php
@@ -0,0 +1,137 @@
+observeCachePrefix($loader);
+
+ return $loader;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function loadClass($class) {
+
+ // Look if the cache has anything for this class.
+ if (isset($this->classFiles[$class])) {
+ $file = $this->classFiles[$class];
+ // The is_file() check may cost around 0.0045 ms per class file, but this
+ // depends on your system of course.
+ if (is_file($file)) {
+ require $file;
+
+ return;
+ }
+ $this->toBeDeleted[$class] = $file;
+ unset($this->classFiles[$class]);
+ ++$this->n;
+ }
+
+ // Resolve cache miss.
+ $api = new LoadClassGetFileInjectedApi($class);
+ if ($this->finder->apiFindFile($api, $class)) {
+ // Queue the result for the cache.
+ $this->toBeAdded[$class]
+ = $this->classFiles[$class]
+ = $api->getFile();
+ ++$this->n;
+ }
+
+ // Save the cache if enough has been queued up.
+ if ($this->n >= $this->nMax) {
+ $this->classFiles = $this->updateClassFiles($this->toBeAdded, $this->toBeDeleted);
+ $this->toBeDeleted = array();
+ $this->toBeAdded = array();
+ $this->nMax *= 2;
+ $this->n = 0;
+ }
+ }
+
+ /**
+ * Set the new cache prefix after a flush cache.
+ *
+ * @param string $prefix
+ * A prefix for the storage key in APC.
+ */
+ function setCachePrefix($prefix) {
+ $this->classFiles = $this->loadClassFiles($prefix);
+ }
+
+ /**
+ * @param string $prefix
+ *
+ * @return string[]
+ */
+ abstract protected function loadClassFiles($prefix);
+
+ /**
+ * @param string[] $toBeAdded
+ * @param string[] $toBeRemoved
+ *
+ * @return string[]
+ */
+ abstract protected function updateClassFiles($toBeAdded, $toBeRemoved);
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/ApcClassLoader.php b/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/ApcClassLoader.php
new file mode 100644
index 00000000000..ebf57b6c452
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/ApcClassLoader.php
@@ -0,0 +1,39 @@
+prefix . $class)) {
+ if (is_file($file)) {
+ require $file;
+
+ return;
+ }
+ apc_delete($this->prefix . $class);
+ }
+
+ // Resolve cache miss.
+ $api = new LoadClassGetFileInjectedApi($class);
+ if ($this->finder->apiFindFile($api, $class)) {
+ apc_store($this->prefix . $class, $api->getFile());
+ }
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/ApcuClassLoader.php b/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/ApcuClassLoader.php
new file mode 100644
index 00000000000..53753e6f51b
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/ApcuClassLoader.php
@@ -0,0 +1,38 @@
+prefix . $class)) {
+ if (is_file($file)) {
+ require $file;
+
+ return;
+ }
+ \apcu_delete($this->prefix . $class);
+ }
+
+ // Resolve cache miss.
+ $api = new LoadClassGetFileInjectedApi($class);
+ if ($this->finder->apiFindFile($api, $class)) {
+ \apcu_store($this->prefix . $class, $api->getFile());
+ }
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/ApcuQueuedCachedClassLoader.php b/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/ApcuQueuedCachedClassLoader.php
new file mode 100644
index 00000000000..dc7cf71458d
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/ApcuQueuedCachedClassLoader.php
@@ -0,0 +1,52 @@
+prefix = $prefix;
+ $cached = \apcu_fetch($this->prefix);
+
+ return !empty($cached)
+ ? $cached
+ : array();
+ }
+
+ /**
+ * @param string[] $toBeAdded
+ * @param string[] $toBeRemoved
+ *
+ * @return string[]
+ */
+ protected function updateClassFiles($toBeAdded, $toBeRemoved) {
+
+ $class_files = $toBeAdded;
+ // Other requests may have already written to the cache, so we get an up to
+ // date version.
+ $cached = \apcu_fetch($this->prefix);
+ if (!empty($cached)) {
+ $class_files += $cached;
+ foreach ($toBeRemoved as $class => $file) {
+ if (isset($class_files[$class]) && $class_files[$class] === $file) {
+ unset($class_files[$class]);
+ }
+ }
+ }
+
+ \apcu_store($this->prefix, $class_files);
+
+ return $class_files;
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/CacheNotSupportedException.php b/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/CacheNotSupportedException.php
new file mode 100644
index 00000000000..b7623bd42d3
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/CacheNotSupportedException.php
@@ -0,0 +1,5 @@
+cacheName = 'xautoload_db_cache:' . $prefix;
+ $cached = cache_get($this->cacheName);
+ return isset($cached->data)
+ ? $cached->data
+ : array();
+ }
+
+ /**
+ * @param string[] $toBeAdded
+ * @param string[] $toBeRemoved
+ *
+ * @return string[]
+ */
+ protected function updateClassFiles($toBeAdded, $toBeRemoved) {
+
+ $class_files = $toBeAdded;
+ // Other requests may have already written to the cache, so we get an up to
+ // date version.
+ $cached = cache_get($this->cacheName);
+ if (isset($cached->data)) {
+ $class_files += $cached->data;
+ foreach ($toBeRemoved as $class => $file) {
+ if (isset($class_files[$class]) && $class_files[$class] === $file) {
+ unset($class_files[$class]);
+ }
+ }
+ }
+
+ cache_set($this->cacheName, $class_files);
+
+ return $class_files;
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/WinCacheClassLoader.php b/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/WinCacheClassLoader.php
new file mode 100644
index 00000000000..ee8376d5dc8
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/WinCacheClassLoader.php
@@ -0,0 +1,39 @@
+prefix . $class)) {
+ if (is_file($file)) {
+ require $file;
+
+ return;
+ }
+ wincache_ucache_delete($this->prefix . $class);
+ }
+
+ // Resolve cache miss.
+ $api = new LoadClassGetFileInjectedApi($class);
+ if ($this->finder->apiFindFile($api, $class)) {
+ wincache_ucache_set($this->prefix . $class, $api->getFile());
+ }
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/XCacheClassLoader.php b/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/XCacheClassLoader.php
new file mode 100644
index 00000000000..a61cb9170f9
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/ClassLoader/XCacheClassLoader.php
@@ -0,0 +1,40 @@
+prefix . $class)
+ && $file = xcache_get($this->prefix . $class)
+ ) {
+ if (is_file($file)) {
+ require $file;
+
+ return;
+ }
+ xcache_unset($this->prefix . $class);
+ }
+
+ // Resolve cache miss.
+ $api = new LoadClassGetFileInjectedApi($class);
+ if ($this->finder->apiFindFile($api, $class)) {
+ xcache_set($this->prefix . $class, $api->getFile());
+ }
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/DIC/ServiceContainer.php b/profiles/dkan/modules/contrib/xautoload/src/DIC/ServiceContainer.php
new file mode 100644
index 00000000000..83fd0ebee8d
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/DIC/ServiceContainer.php
@@ -0,0 +1,76 @@
+services[$key])
+ ? $this->services[$key]
+ : $this->services[$key] = $this->factory->$key($this) ? : FALSE;
+ }
+
+ /**
+ * Unset the service for a specific key.
+ *
+ * @param string $key
+ */
+ function reset($key) {
+ $this->services[$key] = NULL;
+ }
+
+ /**
+ * Register a new service under the given key.
+ *
+ * @param string $key
+ * @param mixed $service
+ */
+ function set($key, $service) {
+ $this->services[$key] = $service;
+ }
+
+ /**
+ * Magic getter for a service.
+ *
+ * @param string $key
+ *
+ * @return mixed
+ *
+ * @throws \Exception
+ */
+ function __get($key) {
+ if (isset($this->services[$key])) {
+ return $this->services[$key];
+ }
+ if (!method_exists($this->factory, $key)) {
+ throw new \Exception("Unsupported key '$key' for service factory.");
+ }
+
+ return $this->services[$key] = $this->factory->$key($this) ? : FALSE;
+ }
+
+ /**
+ * @param ServiceFactory $factory
+ */
+ function __construct($factory) {
+ $this->factory = $factory;
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/DIC/ServiceContainerInterface.php b/profiles/dkan/modules/contrib/xautoload/src/DIC/ServiceContainerInterface.php
new file mode 100644
index 00000000000..312c4f12429
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/DIC/ServiceContainerInterface.php
@@ -0,0 +1,47 @@
+classFinder
+ * @property DrupalSystemInterface $system
+ * @property DrupalPhaseControl $phaseControl
+ * @property DrupalExtensionAdapter $extensionRegistrationService
+ * @property ExtensionNamespaces extensionNamespaces
+ * @property LibrariesInfoAlter librariesInfoAlter
+ *
+ * @see \Drupal\xautoload\DIC\ServiceContainer
+ * @see \Drupal\xautoload\DIC\ServiceFactory
+ */
+interface ServiceContainerInterface {
+
+ /**
+ * Retrieves a lazy-instantiated service.
+ *
+ * @param string $key
+ * A key to specify a service.
+ * @return mixed
+ * The service for the given key. Usually an object.
+ */
+ function __get($key);
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/DIC/ServiceFactory.php b/profiles/dkan/modules/contrib/xautoload/src/DIC/ServiceFactory.php
new file mode 100644
index 00000000000..8c21899b698
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/DIC/ServiceFactory.php
@@ -0,0 +1,174 @@
+finder, $services->classMapGenerator);
+ }
+
+ /**
+ * @param ServiceContainer $services
+ *
+ * @return ClassMapGenerator
+ */
+ function classMapGenerator($services) {
+ return new CachedClassMapGenerator($services->classMapGeneratorRaw, $services->system);
+ }
+
+ /**
+ * @param ServiceContainer $services
+ *
+ * @return ClassMapGenerator
+ */
+ function classMapGeneratorRaw($services) {
+ return new ClassMapGenerator();
+ }
+
+ /**
+ * @param ServiceContainer $services
+ *
+ * @return DrupalExtensionAdapter
+ */
+ function extensionRegistrationService($services) {
+ return new DrupalExtensionAdapter($services->system, $services->finder);
+ }
+
+ /**
+ * @param ServiceContainer $services
+ *
+ * @return CacheManager
+ */
+ function cacheManager($services) {
+ return CacheManager::create($services->system);
+ }
+
+ /**
+ * Proxy class finder.
+ *
+ * @param ServiceContainer $services
+ *
+ * @return ClassFinderInterface
+ * Proxy object wrapping the class finder.
+ * This is used to delay namespace registration until the first time the
+ * finder is actually used.
+ * (which might never happen thanks to the APC cache)
+ */
+ function proxyFinder($services) {
+ // The class finder is cheap to create, so it can use an identity proxy.
+ return new ProxyClassFinder($services->finder);
+ }
+
+ /**
+ * The class finder (alias for 'finder').
+ *
+ * @param ServiceContainer $services
+ *
+ * @return ClassFinderInterface
+ * Object that can find classes,
+ * and provides methods to register namespaces and prefixes.
+ * Note: The findClass() method expects an InjectedAPI argument.
+ */
+ function classFinder($services) {
+ return $services->finder;
+ }
+
+ /**
+ * The class finder (alias for 'classFinder').
+ *
+ * @param ServiceContainer $services
+ *
+ * @return ClassFinderInterface
+ * Object that can find classes,
+ * and provides methods to register namespaces and prefixes.
+ * Notes:
+ * - The findClass() method expects an InjectedAPI argument.
+ * - namespaces are only supported since PHP 5.3
+ */
+ function finder($services) {
+ return new ClassFinder();
+ }
+
+ /**
+ * @param ServiceContainer $services
+ *
+ * @return DrupalSystemInterface
+ */
+ function system($services) {
+ return new DrupalSystem();
+ }
+
+ /**
+ * @param ServiceContainer $services
+ *
+ * @return DrupalPhaseControl
+ */
+ function phaseControl($services) {
+ $observers = array(
+ $services->extensionNamespaces,
+ new HookXautoload($services->system),
+ new LibrariesOnInit($services->system),
+ );
+ if ($services->system->variableGet(XAUTOLOAD_VARNAME_REPLACE_CORE, FALSE)) {
+ $observers[] = new DrupalCoreRegistryRegistrator();
+ }
+ return new DrupalPhaseControl($services->system, $observers);
+ }
+
+ /**
+ * @param ServiceContainer $services
+ *
+ * @return ExtensionNamespaces
+ */
+ function extensionNamespaces($services) {
+ return new ExtensionNamespaces($services->system);
+ }
+
+ /**
+ * @param ServiceContainer $services
+ *
+ * @return LibrariesInfoAlter
+ */
+ function librariesInfoAlter($services) {
+ return new LibrariesInfoAlter();
+ }
+
+}
+
diff --git a/profiles/dkan/modules/contrib/xautoload/src/DirectoryBehavior/DefaultDirectoryBehavior.php b/profiles/dkan/modules/contrib/xautoload/src/DirectoryBehavior/DefaultDirectoryBehavior.php
new file mode 100644
index 00000000000..4d089b82e02
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/DirectoryBehavior/DefaultDirectoryBehavior.php
@@ -0,0 +1,13 @@
+decorated = $decorated;
+ $this->system = $system;
+ }
+
+ /**
+ * @param string[] $paths
+ *
+ * @return string[]
+ */
+ function wildcardPathsToClassmap($paths) {
+ // Attempt to load from cache.
+ $cid = 'xautoload:wildcardPathsToClassmap:' . md5(serialize($paths));
+ $cache = $this->system->cacheGet($cid);
+ if ($cache && isset($cache->data)) {
+ return $cache->data;
+ }
+ // Resolve cache miss and save.
+ $map = $this->decorated->wildcardPathsToClassmap($paths);
+ $this->system->cacheSet($cid, $map);
+
+ return $map;
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/Discovery/ClassMapGenerator.php b/profiles/dkan/modules/contrib/xautoload/src/Discovery/ClassMapGenerator.php
new file mode 100644
index 00000000000..213b3e96543
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/Discovery/ClassMapGenerator.php
@@ -0,0 +1,47 @@
+wildcardPathsToFiles($paths);
+
+ return $this->filesToClassmap($files);
+ }
+
+ /**
+ * @param string[] $files
+ *
+ * @return string[]
+ */
+ protected function filesToClassmap($files) {
+ $map = array();
+ foreach ($files as $file) {
+ $classes = FileInspector::inspectPhpFile($file);
+ foreach ($classes as $class) {
+ $map[$class] = $file;
+ }
+ }
+
+ return $map;
+ }
+
+ /**
+ * @param string[] $paths
+ *
+ * @return string[]
+ */
+ protected function wildcardPathsToFiles($paths) {
+ $wildcardFinder = new WildcardFileFinder();
+ $wildcardFinder->addPaths($paths);
+
+ return $wildcardFinder->getFiles();
+ }
+}
\ No newline at end of file
diff --git a/profiles/dkan/modules/contrib/xautoload/src/Discovery/ClassMapGeneratorInterface.php b/profiles/dkan/modules/contrib/xautoload/src/Discovery/ClassMapGeneratorInterface.php
new file mode 100644
index 00000000000..d3547a3996a
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/Discovery/ClassMapGeneratorInterface.php
@@ -0,0 +1,14 @@
+dir = $dir;
+ }
+
+ /**
+ * @param ClassFinderAdapter $adapter
+ */
+ function writeToAdapter($adapter) {
+
+ // PSR-0 namespaces / prefixes
+ if (is_file($this->dir . '/autoload_namespaces.php')) {
+ $prefixes = require $this->dir . '/autoload_namespaces.php';
+ if (!empty($prefixes)) {
+ $adapter->addMultiplePsr0($prefixes);
+ }
+ }
+
+ // PSR-4 namespaces
+ if (is_file($this->dir . '/autoload_psr4.php')) {
+ $map = require $this->dir . '/autoload_psr4.php';
+ if (!empty($map)) {
+ $adapter->addMultiplePsr4($map);
+ }
+ }
+
+ // Class map
+ if (is_file($this->dir . '/autoload_classmap.php')) {
+ $class_map = require $this->dir . '/autoload_classmap.php';
+ if (!empty($class_map)) {
+ $adapter->addClassMap($class_map);
+ }
+ }
+
+ // Include path
+ if (is_file($this->dir . '/include_paths.php')) {
+ $include_paths = require $this->dir . '/include_paths.php';
+ if (!empty($include_paths)) {
+ array_push($include_paths, get_include_path());
+ set_include_path(join(PATH_SEPARATOR, $include_paths));
+ }
+ }
+
+ // Include files
+ if (is_file($this->dir . '/autoload_files.php')) {
+ $include_files = require $this->dir . '/autoload_files.php';
+ foreach ($include_files as $file) {
+ require $file;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/profiles/dkan/modules/contrib/xautoload/src/Discovery/ComposerJson.php b/profiles/dkan/modules/contrib/xautoload/src/Discovery/ComposerJson.php
new file mode 100644
index 00000000000..6e4e85a7073
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/Discovery/ComposerJson.php
@@ -0,0 +1,132 @@
+data = $data;
+ $this->pathPrefix = $path_prefix;
+ }
+
+ /**
+ * @param ClassFinderAdapter $adapter
+ */
+ function writeToAdapter(ClassFinderAdapter $adapter) {
+
+ $data = $this->data;
+
+ if (!empty($data['include-path'])) {
+ $this->addIncludePaths((array)$data['include-path']);
+ }
+
+ if (!empty($data['autoload']['psr-0'])) {
+ $map = $this->transformMultiple($data['autoload']['psr-0']);
+ $adapter->addMultiplePsr0($map);
+ }
+
+ if (!empty($data['autoload']['psr-4'])) {
+ $map = $this->transformMultiple($data['autoload']['psr-4']);
+ $adapter->addMultiplePsr4($map);
+ }
+
+ if (!empty($data['autoload']['classmap'])) {
+ $this->addClassmapSources($adapter, (array)$data['autoload']['classmap']);
+ }
+
+ if (!empty($data['autoload']['files'])) {
+ foreach ($data['autoload']['files'] as $file) {
+ require $this->pathPrefix . $file;
+ }
+ }
+ }
+
+ /**
+ * @param array $multiple
+ *
+ * @return array[]
+ */
+ protected function transformMultiple(array $multiple) {
+ foreach ($multiple as &$paths) {
+ $paths = (array)$paths;
+ foreach ($paths as &$path) {
+ if ('' === $path || '/' !== $path{0}) {
+ $path = $this->pathPrefix . $path;
+ }
+ }
+ }
+ return $multiple;
+ }
+
+ /**
+ * @param string[] $include_paths
+ */
+ protected function addIncludePaths(array $include_paths) {
+ foreach ($include_paths as &$path) {
+ $path = $this->pathPrefix . $path;
+ }
+ array_push($include_paths, get_include_path());
+ set_include_path(join(PATH_SEPARATOR, $include_paths));
+ }
+
+ /**
+ * @param ClassFinderAdapter $adapter
+ * @param string[] $sources_raw
+ * Array of files and folders to scan for class implementations.
+ */
+ protected function addClassmapSources($adapter, array $sources_raw) {
+ foreach ($sources_raw as &$path) {
+ $path = $this->pathPrefix . $path;
+ }
+ $adapter->addClassmapSources($sources_raw);
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/Discovery/ComposerJsonTargetDir.php b/profiles/dkan/modules/contrib/xautoload/src/Discovery/ComposerJsonTargetDir.php
new file mode 100644
index 00000000000..26a45280784
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/Discovery/ComposerJsonTargetDir.php
@@ -0,0 +1,118 @@
+targetDir = rtrim($data['target-dir'], '/') . '/';
+ }
+
+ /**
+ * @param ClassFinderAdapter $adapter
+ *
+ * @throws \Exception
+ */
+ function writeToAdapter(ClassFinderAdapter $adapter) {
+
+ $data = $this->data;
+
+ if (!empty($data['include-path'])) {
+ $paths = $this->pathsResolveTargetDir((array) $data['include-path']);
+ $this->addIncludePaths($paths, $this->pathPrefix);
+ }
+
+ if (!empty($data['autoload']['psr-0'])) {
+ $this->addMultipleWithTargetDir($adapter, $data['autoload']['psr-0']);
+ }
+
+ if (!empty($data['autoload']['psr-4'])) {
+ throw new \Exception("PSR-4 is incompatible with target-dir.");
+ }
+
+ if (!empty($data['autoload']['classmap'])) {
+ $paths = $this->pathsResolveTargetDir($data['autoload']['classmap']);
+ $this->addClassmapSources($adapter, $paths);
+ }
+
+ if (!empty($data['autoload']['files'])) {
+ $paths = $this->pathsResolveTargetDir($data['autoload']['files']);
+ foreach ($paths as $file) {
+ require $this->pathPrefix . $file;
+ }
+ }
+ }
+
+ /**
+ * @param string[] $paths
+ *
+ * @return string[]
+ */
+ protected function pathsResolveTargetDir(array $paths) {
+ $strlen = strlen($this->targetDir);
+ foreach ($paths as &$path) {
+ if (0 === strpos($path, $this->targetDir)) {
+ $path = substr($path, $strlen);
+ }
+ }
+
+ return $paths;
+ }
+
+ /**
+ * @param ClassFinderAdapter $adapter
+ * @param array $prefixes
+ */
+ protected function addMultipleWithTargetDir(ClassFinderAdapter $adapter, array $prefixes) {
+ $default_behavior = new DefaultDirectoryBehavior();
+ $psr0_behavior = new Psr0DirectoryBehavior();
+ $namespace_map = array();
+ $prefix_map = array();
+ $target_dir_strlen = strlen($this->targetDir);
+ foreach ($prefixes as $prefix => $paths) {
+ if (FALSE === strpos($prefix, '\\')) {
+ $logical_base_path = Util::prefixLogicalPath($prefix);
+ foreach ((array) $paths as $root_path) {
+ $deep_path = strlen($root_path)
+ ? rtrim($root_path, '/') . '/' . $logical_base_path
+ : $logical_base_path;
+ if (0 !== strpos($deep_path, $this->targetDir)) {
+ continue;
+ }
+ $deep_path = $this->pathPrefix . substr($deep_path, $target_dir_strlen);
+ $prefix_map[$logical_base_path][$deep_path] = $default_behavior;
+ }
+ }
+ $logical_base_path = Util::namespaceLogicalPath($prefix);
+ foreach ((array) $paths as $root_path) {
+ $deep_path = strlen($root_path)
+ ? rtrim($root_path, '/') . '/' . $logical_base_path
+ : $logical_base_path;
+ if (0 !== strpos($deep_path, $this->targetDir)) {
+ continue;
+ }
+ $deep_path = $this->pathPrefix . substr($deep_path, $target_dir_strlen);
+ $namespace_map[$logical_base_path][$deep_path] = $psr0_behavior;
+ }
+ }
+ if (!empty($prefix_map)) {
+ $adapter->getPrefixMap()->registerDeepPaths($prefix_map);
+ }
+ $adapter->getNamespaceMap()->registerDeepPaths($namespace_map);
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/Discovery/FileInspector.php b/profiles/dkan/modules/contrib/xautoload/src/Discovery/FileInspector.php
new file mode 100644
index 00000000000..f2df0258979
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/Discovery/FileInspector.php
@@ -0,0 +1,98 @@
+getMessage(),
+ // The Exception code. Defaults to 0.
+ 0,
+ // The previous exception used for exception chaining.
+ $e);
+ }
+
+ return self::inspectFileContents($contents);
+ }
+
+ /**
+ * @param string $contents
+ * The PHP file contents obtained with php_strip_whitespace($path).
+ *
+ * @return string[]
+ * Classes discovered in the file.
+ */
+ protected static function inspectFileContents($contents) {
+ $traits = version_compare(PHP_VERSION, '5.4', '<')
+ ? ''
+ : '|trait';
+
+ // return early if there is no chance of matching anything in this file
+ if (!preg_match('{\b(?:class|interface' . $traits . ')\s}i', $contents)) {
+ return array();
+ }
+
+ // strip heredocs/nowdocs
+ $contents = preg_replace(
+ '{<<<\'?(\w+)\'?(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\1(?=\r\n|\n|\r|;)}s',
+ 'null',
+ $contents);
+
+ // strip strings
+ $contents = preg_replace(
+ '{"[^"\\\\]*(\\\\.[^"\\\\]*)*"|\'[^\'\\\\]*(\\\\.[^\'\\\\]*)*\'}s',
+ 'null',
+ $contents);
+
+ // strip leading non-php code if needed
+ if (substr($contents, 0, 2) !== '') {
+ $contents = preg_replace('{^.+?<\?}s', '', $contents);
+ }
+
+ // strip non-php blocks in the file
+ $contents = preg_replace('{\?>.+<\?}s', '?>', $contents);
+
+ // strip trailing non-php code if needed
+ $pos = strrpos($contents, '?>');
+ if (FALSE !== $pos && FALSE === strpos(substr($contents, $pos), '')) {
+ $contents = substr($contents, 0, $pos);
+ }
+
+ preg_match_all(
+ '{
+ (?:
+ \b(?])(?Pclass|interface' . $traits . ') \s+ (?P[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)
+ | \b(?])(?Pnamespace) (?P\s+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:\s*\\\\\s*[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*)? \s*[\{;]
+ )
+ }ix',
+ $contents,
+ $matches
+ );
+
+ $classes = array();
+ $namespace = '';
+
+ for ($i = 0, $len = count($matches['type']); $i < $len; $i++) {
+ if (!empty($matches['ns'][$i])) {
+ $namespace = str_replace(array(' ', "\t", "\r", "\n"), '', $matches['nsname'][$i])
+ . '\\';
+ }
+ else {
+ $classes[] = ltrim($namespace . $matches['name'][$i], '\\');
+ }
+ }
+
+ return $classes;
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/Discovery/WildcardFileFinder.php b/profiles/dkan/modules/contrib/xautoload/src/Discovery/WildcardFileFinder.php
new file mode 100644
index 00000000000..43638682450
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/Discovery/WildcardFileFinder.php
@@ -0,0 +1,211 @@
+ $value) {
+ if (1
+ && FALSE !== strpos($path, '*')
+ && preg_match('#^([^\*]*)/(.*\*.*)$#', $path, $m)
+ ) {
+ // Resolve wildcards.
+ $this->value = $value;
+ list(, $base, $wildcard) = $m;
+ $this->scanDirectory($base, $wildcard);
+ }
+ else {
+ // Register the file directly.
+ $this->files[$path] = $value;
+ }
+ }
+ }
+
+ /**
+ * @param string[] $paths
+ * Array keys are file paths or wildcard file paths.
+ * @param mixed $value
+ */
+ function addPaths(array $paths, $value = TRUE) {
+ foreach ($paths as $path) {
+ if (1
+ && FALSE !== strpos($path, '*')
+ && preg_match('#^([^\*]*)/(.*\*.*)$#', $path, $m)
+ ) {
+ // Resolve wildcards.
+ $this->value = $value;
+ list(, $base, $wildcard) = $m;
+ $this->scanDirectory($base, $wildcard);
+ }
+ elseif (is_dir($path)) {
+ // Resolve wildcards.
+ $this->value = $value;
+ $this->scanDirectory($path . '/', '**/*.inc');
+ $this->scanDirectory($path . '/', '**/*.php');
+ }
+ elseif (is_file($path)) {
+ // Register the file directly.
+ $this->files[$path] = $value;
+ }
+ }
+ }
+
+ /**
+ * @return string[]
+ */
+ function getFiles() {
+ return array_keys($this->files);
+ }
+
+ /**
+ * @return mixed[]
+ */
+ function getDrupalFiles() {
+ return $this->files;
+ }
+
+ /**
+ * @param string $dir
+ * Base folder, e.g. "sites/all/modules/foo/includes", which does NOT
+ * contain any asterisk ("*").
+ * @param string $wildcard
+ * Suffix which may contain asterisks.
+ */
+ protected function scanDirectory($dir, $wildcard) {
+ if (!is_dir($dir)) {
+ return;
+ }
+ if (FALSE === strpos($wildcard, '*')) {
+ // $wildcard is a fixed string, not a wildcard.
+ $this->suggestFile($dir . '/' . $wildcard);
+ }
+ elseif ('**' === $wildcard) {
+ // Trick: "$a/**" == union of "$a/*" and "$a/*/**"
+ $this->scanDirectoryLevel($dir, '*');
+ $this->scanDirectoryLevel($dir, '*', '**');
+ }
+ elseif ('**/' === substr($wildcard, 0, 3)) {
+ // Trick: "$a/**/$b" == union of "$a/$b" and "$a/*/**/$b"
+ $remaining = substr($wildcard, 3);
+ $this->scanDirectory($dir, $remaining);
+ $this->scanDirectoryLevel($dir, '*', $wildcard);
+ }
+ elseif (FALSE !== ($slashpos = strpos($wildcard, '/'))) {
+ // $wildcard consists of more than one fragment.
+ $fragment = substr($wildcard, 0, $slashpos);
+ $remaining = substr($wildcard, $slashpos + 1);
+ if (FALSE === strpos($fragment, '*')) {
+ $this->scanDirectory($dir . '/' . $fragment, $remaining);
+ }
+ else {
+ $this->scanDirectoryLevel($dir, $fragment, $remaining);
+ }
+ }
+ else {
+ // $wildcard represents a file name.
+ $this->scanDirectoryLevel($dir, $wildcard);
+ }
+ }
+
+ /**
+ * @param string $dir
+ * Base directory, not containing any wildcard.
+ * @param string $fragment
+ * Wildcard path fragment to be processed now. This is never '**', but it
+ * always contains at least one asterisk.
+ * @param null $remaining
+ * Optional rest of the wildcard string, that may contain path fragments to
+ * be processed later.
+ *
+ * @throws \Exception
+ */
+ protected function scanDirectoryLevel($dir, $fragment, $remaining = NULL) {
+
+ if (!is_dir($dir)) {
+ return;
+ }
+
+ if ('**' === $fragment) {
+ throw new \Exception("Fragment must not be '**'.");
+ }
+
+ foreach (scandir($dir) as $candidate) {
+ if (!$this->validateCandidate($candidate, $fragment)) {
+ continue;
+ }
+
+ if (!isset($remaining)) {
+ $this->suggestFile($dir . '/' . $candidate);
+ }
+ else {
+ $this->scanDirectory($dir . '/' . $candidate, $remaining);
+ }
+ }
+ }
+
+ /**
+ * @param $candidate
+ * String to be checked against the wildcard.
+ * @param $wildcard
+ * Wildcard string like '*', '*.*' or '*.inc'.
+ *
+ * @return bool
+ * TRUE, if $candidate matches $wildcard.
+ */
+ protected function validateCandidate($candidate, $wildcard) {
+
+ if ($candidate == '.' || $candidate == '..') {
+ return FALSE;
+ }
+ if (strpos($candidate, '*') !== FALSE) {
+ return FALSE;
+ }
+ if ($wildcard == '*' || $wildcard == '**') {
+ return TRUE;
+ }
+
+ // More complex wildcard string.
+ $fragments = array();
+ foreach (explode('*', $wildcard) as $fragment) {
+ $fragments[] = preg_quote($fragment);
+ }
+ $regex = implode('.*', $fragments);
+
+ return preg_match("/^$regex$/", $candidate);
+ }
+
+ /**
+ * @param string $path
+ * Add a new file path to $this->filesInRegistry().
+ */
+ protected function suggestFile($path) {
+ if (is_file($path)) {
+ $this->files[$path] = $this->value;
+ }
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/DrupalSystem/DrupalSystem.php b/profiles/dkan/modules/contrib/xautoload/src/DrupalSystem/DrupalSystem.php
new file mode 100644
index 00000000000..9b8ea60a82d
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/DrupalSystem/DrupalSystem.php
@@ -0,0 +1,178 @@
+condition('name', $extension_names);
+ $q->fields('system', array('name', 'type'));
+
+ return $q->execute()->fetchAllKeyed();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function getActiveExtensions() {
+ try {
+ // Doing this directly tends to be a lot faster than system_list().
+ return db_query("SELECT name, type from {system} WHERE status = 1")
+ ->fetchAllKeyed();
+ }
+ catch (\DatabaseConnectionNotDefinedException $e) {
+ // During install, the database is not available.
+ // At this time only the system module is "installed".
+ /** See https://www.drupal.org/node/2393205 */
+ return array('system' => 'module');
+ }
+ catch (\PDOException $e) {
+ // Some time later during install, there is a database but not yet a system table.
+ // At this time only the system module is "installed".
+ // @todo Check if this is really a "Table 'system' doesn't exist'" exception.
+ return array('system' => 'module');
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function moduleImplements($hook) {
+ return module_implements($hook);
+ }
+
+ /**
+ * Wrapper for module_list()
+ *
+ * @return array
+ */
+ function moduleList() {
+ return module_list();
+ }
+
+ /**
+ * @see libraries_info()
+ *
+ * @throws \Exception
+ * @return mixed
+ */
+ function getLibrariesInfo() {
+ if (!function_exists('libraries_info')) {
+ // Libraries is at a lower version, which does not have this function.
+ return array();
+ }
+ # drupal_static_reset('libraries_info');
+ return libraries_info();
+ }
+
+ /**
+ * @see libraries_get_path()
+ *
+ * @param string $name
+ * Name of the library.
+ *
+ * @throws \Exception
+ * @return string|false
+ */
+ function librariesGetPath($name) {
+ if (!function_exists('libraries_get_path')) {
+ throw new \Exception('Function libraries_get_path() does not exist.');
+ }
+ return libraries_get_path($name);
+ }
+
+ /**
+ * Called from xautoload_install() to set the module weight.
+ *
+ * @param int $weight
+ * New module weight for xautoload.
+ */
+ public function installSetModuleWeight($weight) {
+ db_update('system')
+ ->fields(array('weight' => $weight))
+ ->condition('name', 'xautoload')
+ ->condition('type', 'module')
+ ->execute();
+ system_list_reset();
+ }
+
+ /**
+ * @param string $cid
+ * @param string $bin
+ *
+ * @return mixed
+ *
+ * @see cache_get()
+ */
+ public function cacheGet($cid, $bin = 'cache') {
+ return cache_get($cid, $bin);
+ }
+
+ /**
+ * @param string $cid
+ * @param mixed $data
+ * @param string $bin
+ *
+ * @return mixed
+ *
+ * @see cache_set()
+ */
+ public function cacheSet($cid, $data, $bin = 'cache') {
+ cache_set($cid, $data, $bin);
+ }
+
+ /**
+ * @param string|null $cid
+ * @param string|null $bin
+ *
+ * @see cache_clear_all()
+ */
+ public function cacheClearAll($cid = NULL, $bin = NULL) {
+ cache_clear_all($cid, $bin);
+ }
+
+ /**
+ * @param string $key
+ */
+ public function drupalStaticReset($key) {
+ \drupal_static_reset($key);
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/DrupalSystem/DrupalSystemInterface.php b/profiles/dkan/modules/contrib/xautoload/src/DrupalSystem/DrupalSystemInterface.php
new file mode 100644
index 00000000000..6a76143a323
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/DrupalSystem/DrupalSystemInterface.php
@@ -0,0 +1,145 @@
+finder = $finder;
+ $this->system = $system;
+ }
+
+ /**
+ * Find the file for a class that in PSR-0 or PEAR would be in
+ * $psr_0_root . '/' . $path_fragment . $path_suffix
+ *
+ * @param InjectedApiInterface $api
+ * @param string $logical_base_path
+ * @param string $relative_path
+ *
+ * @return bool|null
+ * TRUE, if the file was found.
+ * FALSE or NULL, otherwise.
+ */
+ function findFile($api, $logical_base_path, $relative_path) {
+
+ // Prevent recursion if this is called from libraries_info().
+ // @todo Find a better way to do this?
+ $backtrace = defined('DEBUG_BACKTRACE_IGNORE_ARGS')
+ ? debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)
+ : debug_backtrace(FALSE);
+ foreach ($backtrace as $call) {
+ if ('libraries_info' === $call['function']) {
+ return FALSE;
+ }
+ }
+
+ $this->finder->getNamespaceMap()->unregisterDeepPath('', '');
+ $this->finder->getPrefixMap()->unregisterDeepPath('', '');
+ $this->registerAllLibraries();
+ return $this->finder->apiFindFile($api, $api->getClass());
+ }
+
+ /**
+ * Registers all libraries that have an "xautoload" setting.
+ */
+ private function registerAllLibraries() {
+ $adapter = \xautoload_InjectedAPI_hookXautoload::create($this->finder, '');
+ foreach ($info = $this->getLibrariesXautoloadInfo() as $name => $pathAndCallback) {
+ list($path, $callback) = $pathAndCallback;
+ if (!is_callable($callback)) {
+ continue;
+ }
+ if (!is_dir($path)) {
+ continue;
+ }
+ $adapter->setExtensionDir($path);
+ call_user_func($callback, $adapter, $path);
+ }
+ }
+
+ /**
+ * @return array[]
+ */
+ private function getLibrariesXautoloadInfo() {
+ $cached = $this->system->cacheGet(XAUTOLOAD_CACHENAME_LIBRARIES_INFO);
+ if (FALSE !== $cached) {
+ return $cached->data;
+ }
+ $info = $this->buildLibrariesXautoloadInfo();
+ $this->system->cacheSet(XAUTOLOAD_CACHENAME_LIBRARIES_INFO, $info);
+ return $info;
+ }
+
+ /**
+ * @return array[]
+ */
+ private function buildLibrariesXautoloadInfo() {
+ // @todo Reset drupal_static('libraries') ?
+ $all = array();
+ foreach ($this->system->getLibrariesInfo() as $name => $info) {
+ if (!isset($info['xautoload'])) {
+ continue;
+ }
+ $callback = $info['xautoload'];
+ if (!is_callable($callback)) {
+ continue;
+ }
+ /** See https://www.drupal.org/node/2473901 */
+ $path = isset($info['library path'])
+ ? $info['library path']
+ : $this->system->librariesGetPath($name);
+ if (FALSE === $path) {
+ continue;
+ }
+ $all[$name] = array($path, $callback);
+ }
+ return $all;
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/Libraries/LibrariesInfoAlter.php b/profiles/dkan/modules/contrib/xautoload/src/Libraries/LibrariesInfoAlter.php
new file mode 100644
index 00000000000..dc3340adc03
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/Libraries/LibrariesInfoAlter.php
@@ -0,0 +1,55 @@
+ &$library_info) {
+ if (1
+ && isset($library_info['xautoload'])
+ && is_callable($library_info['xautoload'])
+ ) {
+ $this->alterLibraryInfo($library_info, $library_name);
+ }
+ }
+ }
+
+ /**
+ * @param array $library_info
+ * @param string $library_name
+ */
+ private function alterLibraryInfo(&$library_info, $library_name) {
+ $callable = $library_info['xautoload'];
+ if ($callable instanceof \Closure) {
+ // Wrap the closure so it can be serialized.
+ $callable = new SerializableClosureWrapper(
+ $library_info['xautoload'],
+ // The module name and library name allow the closure to be recovered on
+ // unserialize.
+ $library_info['module'],
+ $library_name);
+ $library_info['xautoload'] = $callable;
+ }
+ # $library_info['callbacks']['pre-load'][] = new LibrariesPreLoadCallback($callable);
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/Libraries/LibrariesOnInit.php b/profiles/dkan/modules/contrib/xautoload/src/Libraries/LibrariesOnInit.php
new file mode 100644
index 00000000000..a2c3dce1b13
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/Libraries/LibrariesOnInit.php
@@ -0,0 +1,91 @@
+system = $system;
+ }
+
+ /**
+ * Wake up after a cache fail.
+ *
+ * @param ExtendedClassFinderInterface $finder
+ * @param string[] $extensions
+ * Extension type by extension name.
+ */
+ public function wakeUp(ExtendedClassFinderInterface $finder, array $extensions) {
+ $this->finder = $finder;
+ }
+
+ /**
+ * Enter the boot phase of the request, where all bootstrap module files are included.
+ */
+ public function enterBootPhase() {
+ // Nothing.
+ }
+
+ /**
+ * Enter the main phase of the request, where all module files are included.
+ */
+ public function enterMainPhase() {
+ $this->registerLibrariesFinderPlugin();
+ }
+
+ /**
+ * React to new extensions that were just enabled.
+ *
+ * @param string $name
+ * @param string $type
+ */
+ public function welcomeNewExtension($name, $type) {
+ // Nothing.
+ }
+
+ /**
+ * React to xautoload_modules_enabled()
+ *
+ * @param string[] $modules
+ * New module names.
+ */
+ public function modulesEnabled($modules) {
+ $this->system->drupalStaticReset('libraries_info');
+ $this->system->cacheClearAll(XAUTOLOAD_CACHENAME_LIBRARIES_INFO, 'cache');
+ $this->registerLibrariesFinderPlugin();
+ }
+
+ /**
+ * Registers all libraries that have an "xautoload" setting.
+ */
+ private function registerLibrariesFinderPlugin() {
+ $plugin = new LibrariesFinderPlugin($this->finder, $this->system);
+ $this->finder->getPrefixMap()->registerDeepPath('', '', $plugin);
+ $this->finder->getNamespaceMap()->registerDeepPath('', '', $plugin);
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/Libraries/LibrariesPreLoadCallback.php b/profiles/dkan/modules/contrib/xautoload/src/Libraries/LibrariesPreLoadCallback.php
new file mode 100644
index 00000000000..8048bfd60c6
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/Libraries/LibrariesPreLoadCallback.php
@@ -0,0 +1,51 @@
+callable = $callable;
+ }
+
+ /**
+ * Callback that is applied directly before the library is loaded. At this
+ * point the library contains variant-specific information, if specified. Note
+ * that in this group the 'variants' property is no longer available.
+ *
+ * @param array $library
+ * An array of library information belonging to the top-level library, a
+ * specific version, a specific variant or a specific variant of a specific
+ * version. Because library information such as the 'files' property (see
+ * above) can be declared in all these different locations of the library
+ * array, but a callback may have to act on all these different parts of the
+ * library, it is called recursively for each library with a certain part of
+ * the libraries array passed as $library each time.
+ * @param string|null $version
+ * If the $library array belongs to a certain version (see above), a string
+ * containing the version. This argument may be empty, so NULL should be
+ * specified as the default value.
+ * @param string|null $variant
+ * If the $library array belongs to a certain variant (see above), a string
+ * containing the variant name. This argument may be empty, so NULL should
+ * be specified as the default value.
+ */
+ function __invoke($library, $version, $variant) {
+ if (!empty($library['installed'])) {
+ xautoload()->proxyFinder->observeFirstCacheMiss(
+ new LibraryCacheMissObserver($this->callable, $library['library path']));
+ }
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/Libraries/LibraryCacheMissObserver.php b/profiles/dkan/modules/contrib/xautoload/src/Libraries/LibraryCacheMissObserver.php
new file mode 100644
index 00000000000..bda0df71986
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/Libraries/LibraryCacheMissObserver.php
@@ -0,0 +1,45 @@
+callable = $callable;
+ $this->path = $path;
+ }
+
+ /**
+ * Executes the operation.
+ *
+ * This method will only be called if and when the "real" class finder is
+ * initialized.
+ *
+ * @param ExtendedClassFinderInterface $finder
+ * The class finder.
+ */
+ function cacheMiss($finder) {
+ $adapter = \xautoload_InjectedAPI_hookXautoload::create($finder, $this->path);
+ call_user_func($this->callable, $adapter, $this->path);
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/Libraries/SerializableClosureWrapper.php b/profiles/dkan/modules/contrib/xautoload/src/Libraries/SerializableClosureWrapper.php
new file mode 100644
index 00000000000..90f8982b95d
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/Libraries/SerializableClosureWrapper.php
@@ -0,0 +1,85 @@
+closure = $closure;
+ $this->moduleName = $moduleName;
+ $this->libraryName = $libraryName;
+ }
+
+ public function __sleep() {
+ return array('moduleName', 'libraryName');
+ }
+
+ /**
+ * @param \Drupal\xautoload\Adapter\LocalDirectoryAdapter $adapter
+ */
+ public function __invoke($adapter) {
+ $closure = $this->lazyGetClosure();
+ if ($closure instanceof \Closure) {
+ $closure($adapter);
+ }
+ }
+
+ /**
+ * @return \Closure|FALSE
+ */
+ private function lazyGetClosure() {
+ return isset($this->closure)
+ ? $this->closure
+ : $this->closure = $this->loadClosure();
+ }
+
+ /**
+ * @return \Closure|FALSE
+ */
+ private function loadClosure() {
+ $source_function = $this->moduleName . '_libraries_info';
+ if (!function_exists($source_function)) {
+ return FALSE;
+ }
+ $module_libraries = $source_function();
+ if (!isset($module_libraries[$this->libraryName]['xautoload'])) {
+ return FALSE;
+ }
+ $closure_candidate = $module_libraries[$this->libraryName]['xautoload'];
+ if (!$closure_candidate instanceof \Closure) {
+ return FALSE;
+ }
+ return $closure_candidate;
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/Main.php b/profiles/dkan/modules/contrib/xautoload/src/Main.php
new file mode 100644
index 00000000000..7d9204a08a5
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/Main.php
@@ -0,0 +1,110 @@
+services = $services;
+ }
+
+ /**
+ * @return ServiceContainer
+ */
+ function getServiceContainer() {
+ return $this->services;
+ }
+
+ /**
+ * Invalidate all values stored in APC.
+ */
+ function flushCache() {
+ $this->services->cacheManager->renewCachePrefix();
+ }
+
+ /**
+ * Register a module in early bootstrap, or from modulename.install.
+ *
+ * This is only needed for modules that need autoloading very early in the
+ * request, or e.g. during uninstall, or any situation that xautoload cannot
+ * catch up with.
+ *
+ * The method will register all autoloading schemes for this module that are
+ * supported by default:
+ * - PSR-0: "Drupal\\$module\\Foo" => "$module_dir/lib/Drupal/$module/Foo.php"
+ * - PEAR-FLAT: $module . "_Foo_Bar" => "$module_dir/lib/Foo/Bar.php"
+ *
+ * It will not register anything for PSR-4, since it is not clear whether this
+ * will be in "/lib/" or "/src/" or elsewhere.
+ *
+ * Suggested usage: (in your $modulename.module, or $modulename.install):
+ *
+ * xautoload()->registerModule(__FILE__);
+ *
+ * @param string $__FILE__
+ * File path to a *.module or *.install file.
+ * The basename of the file MUST be the module name.
+ * It is recommended to call this from the respective file itself, using the
+ * __FILE__ constant for this argument.
+ */
+ function registerModule($__FILE__) {
+ $this->services->extensionNamespaces->registerExtension($__FILE__);
+ }
+
+ /**
+ * Register a module as PSR-4, in early bootstrap or from modulename.install
+ *
+ * This can be used while Drupal 8 is still undecided whether PSR-4 class
+ * files should live in "lib" or in "src" by default.
+ *
+ * Suggested usage: (in your $modulename.module, or $modulename.install):
+ *
+ * // E.g. "Drupal\\$module\\Foo" => "$module_dir/lib/Foo.php"
+ * xautoload()->registerModulePsr4(__FILE__, 'lib');
+ *
+ * or
+ *
+ * // E.g. "Drupal\\$module\\Foo" => "$module_dir/src/Foo.php"
+ * xautoload()->registerModulePsr4(__FILE__, 'src');
+ *
+ * or
+ *
+ * // E.g. "Drupal\\$module\\Foo" => "$module_dir/psr4/Foo.php"
+ * xautoload()->registerModulePsr4(__FILE__, 'psr4');
+ *
+ * @param string $__FILE__
+ * File path to a *.module or *.install file.
+ * The basename of the file MUST be the module name.
+ * It is recommended to call this from the respective file itself, using the
+ * __FILE__ constant for this argument.
+ * @param string $subdir
+ * The PSR-4 base directory for the module namespace, relative to the module
+ * directory. E.g. "src" or "lib".
+ */
+ function registerModulePsr4($__FILE__, $subdir) {
+ $this->services->extensionNamespaces->registerExtensionPsr4($__FILE__, $subdir);
+ }
+
+ /**
+ * Magic getter for service objects. This lets this class act as a proxy for
+ * the service container.
+ *
+ * @param string $key
+ * @return mixed
+ */
+ function __get($key) {
+ return $this->services->__get($key);
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/Phases/DrupalCoreRegistryRegistrator.php b/profiles/dkan/modules/contrib/xautoload/src/Phases/DrupalCoreRegistryRegistrator.php
new file mode 100644
index 00000000000..73200f5a1da
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/Phases/DrupalCoreRegistryRegistrator.php
@@ -0,0 +1,57 @@
+getNamespaceMap()->registerDeepPath('', 'registry', $plugin);
+ $finder->getPrefixMap()->registerDeepPath('', 'registry', $plugin);
+ }
+
+ /**
+ * Enter the boot phase of the request, where all bootstrap module files are included.
+ */
+ public function enterBootPhase() {
+ // Nothing.
+ }
+
+ /**
+ * Enter the main phase of the request, where all module files are included.
+ */
+ public function enterMainPhase() {
+ // Nothing.
+ }
+
+ /**
+ * React to new extensions that were just enabled.
+ *
+ * @param string $name
+ * @param string $type
+ */
+ public function welcomeNewExtension($name, $type) {
+ // Nothing.
+ }
+
+ /**
+ * React to xautoload_modules_enabled()
+ *
+ * @param string[] $modules
+ * New module names.
+ */
+ public function modulesEnabled($modules) {
+ // Nothing.
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/Phases/DrupalPhaseControl.php b/profiles/dkan/modules/contrib/xautoload/src/Phases/DrupalPhaseControl.php
new file mode 100644
index 00000000000..40bc209fa43
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/Phases/DrupalPhaseControl.php
@@ -0,0 +1,171 @@
+system = $system;
+ $this->observers = $observers;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function cacheMiss($finder) {
+ $this->extensions = $this->system->getActiveExtensions();
+ foreach ($this->observers as $observer) {
+ $observer->wakeUp($finder, $this->extensions);
+ }
+ $this->awake = TRUE;
+ if ($this->bootPhase) {
+ // We slipped into boot phase while asleep. Need to catch up.
+ foreach ($this->observers as $observer) {
+ $observer->enterBootPhase();
+ }
+ }
+ if ($this->mainPhase) {
+ // We slipped into main phase while asleep. Need to catch up.
+ foreach ($this->observers as $observer) {
+ $observer->enterMainPhase();
+ }
+ }
+ }
+
+ public function enterBootPhase() {
+ if ($this->bootPhase) {
+ // We are already in the main phase. Nothing changes.
+ return;
+ }
+ $this->bootPhase = TRUE;
+ if (!$this->awake) {
+ // The entire thing is not initialized yet.
+ // Postpone until operateOnFinder()
+ return;
+ }
+ foreach ($this->observers as $observer) {
+ $observer->enterBootPhase();
+ }
+ }
+
+ /**
+ * Initiate the main phase.
+ *
+ * Called from
+ * @see xautoload_custom_theme()
+ * @see xautolaod_init()
+ */
+ public function enterMainPhase() {
+ // Main phase implies boot phase.
+ $this->enterBootPhase();
+ if ($this->mainPhase) {
+ // We are already in the main phase. Nothing changes.
+ return;
+ }
+ $this->mainPhase = TRUE;
+ if (!$this->awake) {
+ // The entire thing is not initialized yet.
+ // Postpone until operateOnFinder()
+ return;
+ }
+ foreach ($this->observers as $observer) {
+ $observer->enterMainPhase();
+ }
+ }
+
+ /**
+ * Checks if new extensions have been enabled, and registers them.
+ *
+ * This is called from xautoload_module_implements_alter(), which is called
+ * whenever a new module is enabled, but also some calls we need to ignore.
+ */
+ public function checkNewExtensions() {
+ if (!$this->awake) {
+ // The entire thing is not initialized yet.
+ // Postpone until operateOnFinder()
+ return;
+ }
+ $activeExtensions = $this->system->getActiveExtensions();
+ if ($activeExtensions === $this->extensions) {
+ // Nothing actually changed. False alarm.
+ return;
+ }
+ // Now check all extensions to find out if any of them is new.
+ foreach ($activeExtensions as $name => $type) {
+ if (!isset($this->extensions[$name])) {
+ // This extension was freshly enabled.
+ if ('xautoload' === $name) {
+ // If xautoload is enabled in this request, then boot phase and main
+ // phase are not properly initialized yet.
+ $this->enterMainPhase();
+ }
+ // Notify observers about the new extension.
+ foreach ($this->observers as $observer) {
+ $observer->welcomeNewExtension($name, $type);
+ }
+ }
+ }
+ }
+
+ /**
+ * Called from @see xautoload_modules_enabled()
+ *
+ * @param $modules
+ */
+ public function modulesEnabled($modules) {
+ if (!$this->awake) {
+ // No need to postpone.
+ // initMainPhase() will have these modules included.
+ return;
+ }
+ foreach ($this->observers as $observer) {
+ $observer->modulesEnabled($modules);
+ }
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/Phases/ExtensionNamespaces.php b/profiles/dkan/modules/contrib/xautoload/src/Phases/ExtensionNamespaces.php
new file mode 100644
index 00000000000..a9a4a1894d2
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/Phases/ExtensionNamespaces.php
@@ -0,0 +1,253 @@
+system = $system;
+ }
+
+ /**
+ * Registers the namespaces for a module even though it might be currently
+ * disabled, or even though it might be early in the request.
+ *
+ * @param string $__FILE__
+ */
+ public function registerExtension($__FILE__) {
+ if (NULL === $this->finder) {
+ // Sleeping..
+ $this->queue[$__FILE__] = FALSE;
+
+ return;
+ }
+
+ $info = pathinfo($__FILE__);
+ $name = $info['filename'];
+
+ // Check if something was registered before.
+ if (isset($this->registered[$name])) {
+ // Already registered.
+ return;
+ }
+
+ $this->_registerExtension($name, $info['dirname']);
+ }
+
+ /**
+ * Registers the namespace with PSR-4 for a module even though it might be
+ * currently disabled, or even though it might be early in the request.
+ *
+ * @param string $__FILE__
+ * @param string $subdir
+ */
+ public function registerExtensionPsr4($__FILE__, $subdir) {
+ if (NULL === $this->finder) {
+ // Sleeping..
+ $this->queue[$__FILE__] = $subdir;
+
+ return;
+ }
+
+ $info = pathinfo($__FILE__);
+ $name = $info['filename'];
+
+ // Check if something was registered before.
+ if (isset($this->registered[$name])) {
+ if ('psr-4' === $this->registered[$name]) {
+ // It already happened.
+ return;
+ }
+ // Unregister the lazy plugins.
+ $this->finder->getNamespaceMap()->unregisterDeepPath(
+ 'Drupal/' . $name . '/',
+ $name
+ );
+ $this->finder->getPrefixMap()->unregisterDeepPath(
+ str_replace('_', '/', $name) . '/',
+ $name
+ );
+ }
+ $this->_registerExtensionPsr4($name, $info['dirname'], $subdir);
+ }
+
+ /**
+ * Wake up after a cache fail.
+ *
+ * @param ExtendedClassFinderInterface $finder
+ * @param string[] $extensions
+ * Extension type by extension name.
+ */
+ public function wakeUp(ExtendedClassFinderInterface $finder, array $extensions) {
+ $this->finder = $finder;
+
+ // Register queued extensions.
+ foreach ($this->queue as $__FILE__ => $subdir) {
+ $info = pathinfo($__FILE__);
+ $name = $info['filename'];
+ $dir = $info['dirname'];
+ if (FALSE === $subdir) {
+ // This is not PSR-4.
+ $this->_registerExtension($name, $dir);
+ }
+ else {
+ // This is PSR-4.
+ $this->_registerExtensionPsr4($name, $dir, $subdir);
+ }
+ }
+
+ $extensions = array_diff_key($extensions, $this->registered);
+
+ // Register remaining extensions, using the lazy plugins.
+ $this->_registerLazyExtensionPlugins($extensions);
+ }
+
+ /**
+ * Enter the boot phase of the request, where all bootstrap module files are included.
+ */
+ public function enterBootPhase() {
+ // Nothing.
+ }
+
+ /**
+ * Enter the main phase of the request, where all module files are included.
+ */
+ public function enterMainPhase() {
+ // Nothing.
+ }
+
+ /**
+ * React to new extensions that were just enabled.
+ *
+ * @param string $name
+ * @param string $type
+ */
+ public function welcomeNewExtension($name, $type) {
+ if (isset($this->registered[$name])) {
+ // Already registered.
+ return;
+ }
+ $dir = $this->system->drupalGetPath($type, $name);
+ $this->_registerExtension($name, $dir);
+ }
+
+ /**
+ * React to xautoload_modules_enabled()
+ *
+ * @param string[] $modules
+ * New module names.
+ */
+ public function modulesEnabled($modules) {
+ // Nothing.
+ }
+
+ /**
+ * @param string $name
+ * @param string $dir
+ */
+ private function _registerExtension($name, $dir) {
+ if (is_dir($lib = $dir . '/lib')) {
+ $this->finder->addPsr0('Drupal\\' . $name . '\\', $lib);
+ $this->finder->addPearFlat($name . '_', $lib);
+ }
+ if (is_dir($src = $dir . '/src')) {
+ $this->finder->addPsr4('Drupal\\' . $name . '\\', $src);
+ }
+
+ $this->registered[$name] = TRUE;
+ }
+
+ /**
+ * @param string $name
+ * @param string $dir
+ * @param string $subdir
+ */
+ private function _registerExtensionPsr4($name, $dir, $subdir) {
+ $this->finder->addPsr4('Drupal\\' . $name . '\\', $dir . '/' . $subdir);
+
+ // Re-add the PSR-0 test directory, for consistency's sake.
+ if (is_dir($lib_tests = $dir . '/lib/Drupal/' . $name . '/Tests')) {
+ $this->finder->addPsr0('Drupal\\' . $name . '\\Tests\\', $lib_tests);
+ }
+ $this->registered[$name] = 'psr-4';
+ }
+
+ /**
+ * Register lazy plugins for enabled Drupal modules and themes, assuming that
+ * we don't know yet whether they use PSR-0, PEAR-Flat, or none of these.
+ *
+ * @param string[] $extensions
+ * An array where the keys are extension names, and the values are extension
+ * types like 'module' or 'theme'.
+ */
+ private function _registerLazyExtensionPlugins(array $extensions) {
+
+ $namespaceBehaviors = array();
+ $prefixBehaviors = array();
+ foreach (array('module', 'theme') as $extension_type) {
+ $namespaceBehaviors[$extension_type] = new DrupalExtensionNamespaceFinderPlugin(
+ $extension_type,
+ $this->finder->getNamespaceMap(),
+ $this->finder->getPrefixMap(),
+ $this->system);
+ $prefixBehaviors[$extension_type] = new DrupalExtensionUnderscoreFinderPlugin(
+ $extension_type,
+ $this->finder->getNamespaceMap(),
+ $this->finder->getPrefixMap(),
+ $this->system);
+ }
+
+ $prefix_map = array();
+ $namespace_map = array();
+ foreach ($extensions as $name => $type) {
+ if (empty($namespaceBehaviors[$type])) {
+ // Unsupported extension type, e.g. "theme_engine".
+ // This can happen if a site was upgraded from Drupal 6.
+ // See https://drupal.org/comment/8503979#comment-8503979
+ continue;
+ }
+ if (!empty($this->registered[$name])) {
+ // The extension has already been processed.
+ continue;
+ }
+ $namespace_map['Drupal/' . $name . '/'][$name] = $namespaceBehaviors[$type];
+ $prefix_map[str_replace('_', '/', $name) . '/'][$name] = $prefixBehaviors[$type];
+ $this->registered[$name] = TRUE;
+ }
+
+ $this->finder->getNamespaceMap()->registerDeepPaths($namespace_map);
+ $this->finder->getPrefixMap()->registerDeepPaths($prefix_map);
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/Phases/HookXautoload.php b/profiles/dkan/modules/contrib/xautoload/src/Phases/HookXautoload.php
new file mode 100644
index 00000000000..2f621564587
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/Phases/HookXautoload.php
@@ -0,0 +1,111 @@
+system = $system;
+ }
+
+ /**
+ * Wake up after a cache fail.
+ *
+ * @param ExtendedClassFinderInterface $finder
+ * @param string[] $extensions
+ * Extension type by extension name.
+ */
+ public function wakeUp(ExtendedClassFinderInterface $finder, array $extensions) {
+ $this->finder = $finder;
+ $this->extensions = $extensions;
+ }
+
+ /**
+ * Enter the boot phase of the request, where all bootstrap module files are included.
+ */
+ public function enterBootPhase() {
+ // Nothing.
+ }
+
+ /**
+ * Enter the main phase of the request, where all module files are included.
+ */
+ public function enterMainPhase() {
+ $modules = $this->system->moduleImplements('xautoload');
+ $this->runHookXautoload($modules);
+ }
+
+ /**
+ * New extensions were enabled/installed.
+ *
+ * @param string $name
+ * Extension type by name.
+ * @param string $type
+ */
+ public function welcomeNewExtension($name, $type) {
+ // Nothing.
+ }
+
+ /**
+ * React to xautoload_modules_enabled()
+ *
+ * @param string[] $modules
+ * New module names.
+ */
+ public function modulesEnabled($modules) {
+ $modules = $this->system->moduleImplements('xautoload');
+ $this->runHookXautoload($modules);
+ }
+
+ /**
+ * Runs hook_xautoload() on all enabled modules.
+ *
+ * This may occur multiple times in a request, if new modules are enabled.
+ *
+ * @param array $modules
+ */
+ private function runHookXautoload(array $modules) {
+ // Let other modules register stuff to the finder via hook_xautoload().
+ $adapter = \xautoload_InjectedAPI_hookXautoload::create($this->finder, '');
+ foreach ($modules as $module) {
+ $adapter->setExtensionDir($dir = $this->system->drupalGetPath('module', $module));
+ $function = $module . '_xautoload';
+ $function($adapter, $dir);
+ }
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/Phases/HookXautoloadEarly.php b/profiles/dkan/modules/contrib/xautoload/src/Phases/HookXautoloadEarly.php
new file mode 100644
index 00000000000..1306cfbf48f
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/Phases/HookXautoloadEarly.php
@@ -0,0 +1,110 @@
+system = $system;
+ }
+
+ /**
+ * Wake up after a cache fail.
+ *
+ * @param ExtendedClassFinderInterface $finder
+ * @param string[] $extensions
+ * Extension type by extension name.
+ */
+ public function wakeUp(ExtendedClassFinderInterface $finder, array $extensions) {
+ $this->finder = $finder;
+ $this->extensions = $extensions;
+ }
+
+ /**
+ * Enter the boot phase of the request, where all bootstrap module files are included.
+ */
+ public function enterBootPhase() {
+ // @todo Call hook_xautoload() on bootstrap modules, if in bootstrap phase.
+ }
+
+ /**
+ * Enter the main phase of the request, where all module files are included.
+ */
+ public function enterMainPhase() {
+ // @todo Don't use moduleImplements(), to prevent hook_module_implements_alter()
+ $modules = $this->system->moduleImplements('xautoload');
+ // @todo Remove boot modules from the list.
+ $this->runHookXautoload($modules);
+ }
+
+ /**
+ * New extensions were enabled/installed.
+ *
+ * @param string $name
+ * Extension type by name.
+ * @param string $type
+ */
+ public function welcomeNewExtension($name, $type) {
+ $function = $name . '_xautoload';
+ if (!function_exists($function)) {
+ return;
+ }
+ $dir = $this->system->drupalGetPath($type, $name);
+ $adapter = \xautoload_InjectedAPI_hookXautoload::create($this->finder, $dir);
+ $function($adapter, $dir);
+ }
+
+ /**
+ * React to xautoload_modules_enabled()
+ *
+ * @param string[] $modules
+ * New module names.
+ */
+ public function modulesEnabled($modules) {
+ // Nothing.
+ }
+
+ /**
+ * Runs hook_xautoload() on all enabled modules.
+ *
+ * This may occur multiple times in a request, if new modules are enabled.
+ *
+ * @param array $modules
+ */
+ private function runHookXautoload(array $modules) {
+ // Let other modules register stuff to the finder via hook_xautoload().
+ $adapter = \xautoload_InjectedAPI_hookXautoload::create($this->finder, '');
+ foreach ($modules as $module) {
+ $adapter->setExtensionDir($dir = $this->system->drupalGetPath('module', $module));
+ $function = $module . '_xautoload';
+ $function($adapter, $dir);
+ }
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/src/Phases/PhaseObserverInterface.php b/profiles/dkan/modules/contrib/xautoload/src/Phases/PhaseObserverInterface.php
new file mode 100644
index 00000000000..976fc5b59c1
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/src/Phases/PhaseObserverInterface.php
@@ -0,0 +1,63 @@
+bar()"
+ */
+ static function callbackToString($callback) {
+ if (is_array($callback)) {
+ list($obj, $method) = $callback;
+ if (is_object($obj)) {
+ $str = get_class($obj) . '->' . $method . '()';
+ }
+ else {
+ $str = $obj . '::';
+ $str .= $method . '()';
+ }
+ }
+ else {
+ $str = $callback;
+ }
+
+ return $str;
+ }
+
+ /**
+ * Convert the underscores of a prefix into directory separators.
+ *
+ * @param string $prefix
+ * Prefix, without trailing underscore.
+ *
+ * @return string
+ * Path fragment representing the prefix, with trailing '/'.
+ */
+ static function prefixLogicalPath($prefix) {
+ if (!strlen($prefix)) {
+ return '';
+ }
+ $pear_logical_path = str_replace('_', '/', rtrim($prefix, '_') . '_');
+ // Clean up surplus '/' resulting from duplicate underscores, or an
+ // underscore at the beginning of the class.
+ while (FALSE !== $pos = strrpos('/' . $pear_logical_path, '//')) {
+ $pear_logical_path{$pos} = '_';
+ }
+
+ return $pear_logical_path;
+ }
+
+ /**
+ * Replace the namespace separator with directory separator.
+ *
+ * @param string $namespace
+ * Namespace without trailing namespace separator.
+ *
+ * @return string
+ * Path fragment representing the namespace, with trailing '/'.
+ */
+ static function namespaceLogicalPath($namespace) {
+ return
+ strlen($namespace)
+ ? str_replace('\\', '/', rtrim($namespace, '\\') . '\\')
+ : '';
+ }
+
+ /**
+ * Check if a file exists, considering the full include path.
+ * Return the resolved path to that file.
+ *
+ * @param string $file
+ * The filepath
+ * @return boolean|string
+ * The resolved file path, if the file exists in the include path.
+ * FALSE, otherwise.
+ */
+ static function findFileInIncludePath($file) {
+ if (function_exists('stream_resolve_include_path')) {
+ // Use the PHP 5.3.1+ way of doing this.
+ return stream_resolve_include_path($file);
+ }
+ elseif ($file{0} === '/') {
+ // That's an absolute path already.
+ return file_exists($file)
+ ? $file
+ : FALSE;
+ }
+ else {
+ // Manually loop all candidate paths.
+ foreach (explode(PATH_SEPARATOR, get_include_path()) as $base_dir) {
+ if (file_exists($base_dir . '/' . $file)) {
+ return $base_dir . '/' . $file;
+ }
+ }
+
+ return FALSE;
+ }
+ }
+
+ /**
+ * Checks whether an identifier is defined as either a class, interface or
+ * trait. Does not trigger autoloading.
+ *
+ * @param string $class
+ * @return bool
+ */
+ static function classIsDefined($class) {
+ return class_exists($class, FALSE)
+ || interface_exists($class, FALSE)
+ || (PHP_VERSION_ID >= 50400 && trait_exists($class, FALSE));
+ }
+
+ /**
+ * Dummy method to force autoloading this class (or an ancestor).
+ */
+ static function forceAutoload() {
+ // Do nothing.
+ }
+}
+
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/bootstrap.php b/profiles/dkan/modules/contrib/xautoload/tests/bootstrap.php
new file mode 100644
index 00000000000..7f47702602a
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/bootstrap.php
@@ -0,0 +1,37 @@
+finder->addPsr4('Drupal\xautoload\Tests\\', __DIR__ . '/src/');
+
+// Use a non-cached class map generator.
+xautoload()->getServiceContainer()->set('classMapGenerator', new ClassMapGenerator());
+
+// Register a one-off class loader for the test PSR-4 classes.
+/*
+call_user_func(
+ function() {
+ $addPsr4 = function($namespace, $src) {
+ $strlen = strlen($namespace);
+ spl_autoload_register(
+ function ($class) use ($src, $namespace, $strlen) {
+ if (0 === strpos($class, $namespace)) {
+ $file = $src . '/' . str_replace('\\', '/', substr($class, $strlen)) . '.php';
+ if (file_exists($file)) {
+ require_once $file;
+ return TRUE;
+ }
+ }
+ return FALSE;
+ }
+ );
+ };
+ $addPsr4('Drupal\xautoload\Tests\\', __DIR__ . '/src');
+ $addPsr4('Drupal\xautoload\\', dirname(__DIR__) . '/src');
+ }
+);
+*/
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/fixtures/.libraries/ComposerTargetDirTestLib/Foo.php b/profiles/dkan/modules/contrib/xautoload/tests/fixtures/.libraries/ComposerTargetDirTestLib/Foo.php
new file mode 100644
index 00000000000..2b8fedacfe9
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/fixtures/.libraries/ComposerTargetDirTestLib/Foo.php
@@ -0,0 +1,8 @@
+librariesLoad($name);
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/fixtures/.modules/system/system.module b/profiles/dkan/modules/contrib/xautoload/tests/fixtures/.modules/system/system.module
new file mode 100644
index 00000000000..b3d9bbc7f37
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/fixtures/.modules/system/system.module
@@ -0,0 +1 @@
+addPsr4('Drupal\testmod\\', 'psr4');
+ new \Drupal\testmod\Foo();
+}
+
+/**
+ * Implements hook_libraries_info()
+ */
+function testmod_libraries_info() {
+ StaticCallLog::addCall();
+ new \Drupal\testmod\Foo();
+ return array(
+ 'testlib' => array(
+ 'name' => 'Test library',
+ 'xautoload' => '_testmod_libraries_testlib_xautoload',
+ ),
+ 'ComposerTestLib' => array(
+ 'xautoload' => '_testmod_libraries_ComposerTestLib_xautoload',
+ ),
+ 'ComposerTargetDirTestLib' => array(
+ 'xautoload' => '_testmod_libraries_ComposerTargetDirTestLib_xautoload',
+ ),
+ );
+}
+
+/**
+ * @param LocalDirectoryAdapter $adapter
+ */
+function _testmod_libraries_testlib_xautoload($adapter) {
+ StaticCallLog::addCall();
+ $adapter->addPsr4('Acme\TestLib\\', 'src');
+}
+
+/**
+ * @param LocalDirectoryAdapter $adapter
+ */
+function _testmod_libraries_ComposerTestLib_xautoload($adapter) {
+ $adapter->composerJson('composer.json');
+}
+
+/**
+ * @param LocalDirectoryAdapter $adapter
+ */
+function _testmod_libraries_ComposerTargetDirTestLib_xautoload($adapter) {
+ $adapter->composerJson('composer.json');
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_pearflat/lib/Foo.php b/profiles/dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_pearflat/lib/Foo.php
new file mode 100644
index 00000000000..9890a23ef74
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_pearflat/lib/Foo.php
@@ -0,0 +1,3 @@
+main->registerModulePsr4(__FILE__, 'psr4');
+
+function testmod_psr4_custom_init() {
+ StaticCallLog::addCall();
+ new Foo;
+}
+
+function testmod_psr4_custom_modules_enabled() {
+ StaticCallLog::addCall();
+ new Foo;
+}
+
+function testmod_psr4_custom_watchdog() {
+ StaticCallLog::addCall();
+ new Foo;
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_psr4_src/src/Foo.php b/profiles/dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_psr4_src/src/Foo.php
new file mode 100644
index 00000000000..35e1e6a815a
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_psr4_src/src/Foo.php
@@ -0,0 +1,5 @@
+includeAllRecursivePsr4($lib, 'Drupal\xautoload', $skip);
+ }
+
+ /**
+ * @param string $dir
+ * @param string $namespace
+ * @param array $skip
+ *
+ * @throws \Exception
+ */
+ private function includeAllRecursivePsr4($dir, $namespace, array $skip) {
+ foreach (scandir($dir) as $candidate) {
+ if ('.' === $candidate || '..' === $candidate) {
+ continue;
+ }
+ $path = $dir . '/' . $candidate;
+ if (in_array($path, $skip)) {
+ continue;
+ }
+ if (is_dir($path)) {
+ $this->includeAllRecursivePsr4($dir . '/' . $candidate, $namespace . '\\' . $candidate, $skip);
+ }
+ elseif (is_file($path)) {
+ if ('.php' === substr($candidate, -4)) {
+ $class = $namespace . '\\' . substr($candidate, 0, -4);
+ if (class_exists($class)) {
+ continue;
+ }
+ if (interface_exists($class)) {
+ continue;
+ }
+ if (function_exists('trait_exists') && trait_exists($class)) {
+ continue;
+ }
+ throw new \Exception("Non-existing class, trait or interface '$class'.");
+ }
+ }
+ }
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/ClassFinderAdapterTest.php b/profiles/dkan/modules/contrib/xautoload/tests/src/ClassFinderAdapterTest.php
new file mode 100644
index 00000000000..cbb2c1ee369
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/ClassFinderAdapterTest.php
@@ -0,0 +1,35 @@
+filesystem = StreamWrapper::register('test');
+ }
+
+ function tearDown() {
+ stream_wrapper_unregister('test');
+ parent::tearDown();
+ }
+
+ /**
+ * Test hook_registry_files_alter() wildcard replacement.
+ */
+ public function testWildcardClassmap() {
+ $this->filesystem->addClass('test://lib/xy/z.php', 'Foo\Bar');
+
+ $this->assertFalse(class_exists('Foo\Bar', FALSE), 'Class Foo\Bar must not exist yet.');
+ xautoload()->adapter->addClassmapSources(array('test://lib/**/*.php'));
+ $this->assertTrue(class_exists('Foo\Bar'), 'Class Foo\Bar must exist.');
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/ClassLoaderTest.php b/profiles/dkan/modules/contrib/xautoload/tests/src/ClassLoaderTest.php
new file mode 100644
index 00000000000..b1b88c2115d
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/ClassLoaderTest.php
@@ -0,0 +1,210 @@
+filesystem = StreamWrapper::register('test');
+ }
+
+ function tearDown() {
+ stream_wrapper_unregister('test');
+ parent::tearDown();
+ }
+
+ // Test methods
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Test PSR-4-like namespaces.
+ */
+ function testPsr4() {
+
+ // Prepare the class finder.
+ $finder = new ClassFinder();
+
+ $finder->addPsr4('Drupal\ex_ample\\', 'test://base/lib/');
+
+ $this->assertCandidateOrder(
+ $finder,
+ 'Drupal\ex_ample\Psr4_%\Foo_Bar',
+ array('test://base/lib/Psr4_%/Foo_Bar.php'));
+ }
+
+ /**
+ * Test PSR-0-like namespaces.
+ */
+ function testNamespaces() {
+
+ // Prepare the class finder.
+ $finder = new ClassFinder();
+ $psr0 = new Psr0DirectoryBehavior();
+
+ $finder->registerNamespaceDeep('Drupal\\ex_ample', 'test://base/lib', $psr0);
+ $finder->registerNamespaceRoot('Drupal\\ex_ample', 'test://base/vendor', $psr0);
+
+ $this->assertCandidateOrder(
+ $finder,
+ 'Drupal\ex_ample\Sub_%\Foo_Bar',
+ array(
+ 'test://base/lib/Sub_%/Foo/Bar.php',
+ 'test://base/vendor/Drupal/ex_ample/Sub_%/Foo/Bar.php',
+ ));
+ }
+
+ /**
+ * Test PEAR-like prefixes.
+ */
+ function testPrefixes() {
+
+ // Prepare the class finder.
+ $finder = new ClassFinder();
+
+ $finder->registerPrefixDeep('ex_ample', 'test://base/lib');
+ $finder->registerPrefixRoot('ex_ample', 'test://base/vendor');
+
+ $this->assertCandidateOrder(
+ $finder,
+ 'ex_ample_Sub%_Foo',
+ array(
+ 'test://base/lib/Sub%/Foo.php',
+ 'test://base/vendor/ex/ample/Sub%/Foo.php',
+ ));
+ }
+
+ /**
+ * Tests PEAR-like class names beginning with underscore, or with a double
+ * underscore in between.
+ */
+ function testSpecialUnderscores() {
+
+ // Prepare the class finder.
+ $finder = new ClassFinder();
+
+ $finder->registerPrefixDeep('_ex_ample', 'test://lib');
+ $finder->registerPrefixRoot('_ex_ample', 'test://vendor');
+
+ // Verify that underscores are not a problem..
+ $this->assertCandidateOrder(
+ $finder,
+ '_ex_ample_Abc%_Def', array(
+ 'test://lib/Abc%/Def.php',
+ 'test://vendor/_ex/ample/Abc%/Def.php',
+ ));
+ $this->assertCandidateOrder($finder, '_abc_Foo%', array());
+ $this->assertCandidateOrder($finder, 'abc__Foo%', array());
+ }
+
+ // Assertion helpers
+ // ---------------------------------------------------------------------------
+
+ /**
+ * @param \Drupal\xautoload\ClassLoader\ClassLoaderInterface $loader
+ * @param string $class
+ * @param string $file
+ */
+ protected function assertLoadClass($loader, $class, $file) {
+
+ // Register the class file in the virtual filesystem.
+ $this->filesystem->addClass($file, $class);
+
+ // Check that the class is not already defined.
+ $this->assertFalse(class_exists($class, FALSE));
+
+ // Trigger the class loader.
+ $loader->loadClass($class);
+
+ // Check that the class is defined after the class loader has done its job.
+ $this->assertTrue(class_exists($class, FALSE));
+ }
+
+ /**
+ * @param \Drupal\xautoload\ClassLoader\ClassLoaderInterface $loader
+ * @param string $classTemplate
+ * @param string[] $expectedCandidateTemplates
+ */
+ protected function assertCandidateOrder($loader, $classTemplate, array $expectedCandidateTemplates) {
+ for ($i = 0; $i < count($expectedCandidateTemplates); ++$i) {
+ $class = $this->replaceWildcard($classTemplate, "n$i");
+ // If str_replace() is called with an array as 3rd parameter, it will do
+ // the replacement on all array elements.
+ $expectedCandidates = $this->replaceWildcardMultiple(array_slice($expectedCandidateTemplates, 0, $i + 1), "n$i");
+ $this->assertFileInclusions($loader, $class, $expectedCandidates);
+ }
+ }
+
+ /**
+ * Assert that inclusions are done in the expected order.
+ *
+ * @param \Drupal\xautoload\ClassLoader\ClassLoaderInterface $loader
+ * @param string $class
+ * @param string[] $expectedCandidates
+ */
+ protected function assertFileInclusions($loader, $class, array $expectedCandidates) {
+
+ // Register the class file in the virtual filesystem.
+ $this->filesystem->addClass(end($expectedCandidates), $class);
+
+ $this->filesystem->resetReportedOperations();
+
+ // Check that the class is not already defined.
+ $this->assertFalse(class_exists($class, FALSE), "Class '$class' is not defined before loadClass().");
+
+ // Trigger the class loader.
+ $loader->loadClass($class);
+
+ $expectedOperations = array();
+ foreach ($expectedCandidates as $file) {
+ $expectedOperations[] = $file . ' - stat';
+ }
+ $expectedOperations[] = end($expectedCandidates) . ' - include';
+ $this->assertSame($expectedOperations, $this->filesystem->getReportedOperations());
+
+ // Check that the class is defined after the class loader has done its job.
+ $this->assertTrue(class_exists($class, FALSE), "Class is defined after loadClass().");
+ }
+
+ /**
+ * @param string[] $strings
+ * @param string $replacement
+ *
+ * @return string[]
+ */
+ protected function replaceWildcardMultiple(array $strings, $replacement) {
+ foreach ($strings as &$str) {
+ $str = $this->replaceWildcard($str, $replacement);
+ }
+ return $strings;
+ }
+
+ /**
+ * @param string $str
+ * @param string $replacement
+ *
+ * @return string
+ *
+ * @throws \Exception
+ */
+ protected function replaceWildcard($str, $replacement) {
+ $fragments = explode('%', $str);
+ if (count($fragments) < 2) {
+ throw new \Exception("String '$str' does not contain a '%' wildcard.");
+ }
+ if (count($fragments) > 2) {
+ throw new \Exception("String '$str' has more than one '%' wildcard.");
+ }
+ return str_replace('%', $replacement, $str);
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/ComposerJsonTest.php b/profiles/dkan/modules/contrib/xautoload/tests/src/ComposerJsonTest.php
new file mode 100644
index 00000000000..688a0496423
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/ComposerJsonTest.php
@@ -0,0 +1,35 @@
+ array(
+ 'ComposerTestLib\Foo',
+ 'ComposerTestLib\Other\Foo',
+ ),
+ dirname(__DIR__) . '/fixtures/.libraries/ComposerTargetDirTestLib' => array(
+ 'Acme\ComposerTargetDirTestLib\Foo',
+ ),
+ ) as $dir => $classes) {
+ $localDirectoryAdapter = new LocalDirectoryAdapter($masterAdapter, $dir);
+ $localDirectoryAdapter->composerJson('composer.json');
+ foreach ($classes as $class) {
+ $this->assertFalse(class_exists($class, FALSE), "Class $class not defined yet.");
+ $finder->loadClass($class);
+ $this->assertTrue(class_exists($class, FALSE), "Class $class is defined.");
+ }
+ }
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/DiscoveryTest.php b/profiles/dkan/modules/contrib/xautoload/tests/src/DiscoveryTest.php
new file mode 100644
index 00000000000..9ea0e08efc7
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/DiscoveryTest.php
@@ -0,0 +1,63 @@
+ 'views', 'weight' => 0);
+ }
+
+ // The class file is loaded using the regular uncached xautoload autoload.
+ $file_finder = new WildcardFileFinder();
+ $file_finder->addDrupalPaths($files, TRUE);
+ $files = $file_finder->getDrupalFiles();
+
+ // The order of scandir() cannot be predicted, therefore only the sorted
+ // list of files is being compared here.
+ ksort($files);
+
+ $expected = array (
+ dirname(__DIR__) . '/fixtures/WildcardFileFinder/foo/bar.inc',
+ dirname(__DIR__) . '/fixtures/WildcardFileFinder/handlers/bar.inc',
+ dirname(__DIR__) . '/fixtures/WildcardFileFinder/handlers/foo.inc',
+ dirname(__DIR__) . '/fixtures/WildcardFileFinder/misc/abc',
+ dirname(__DIR__) . '/fixtures/WildcardFileFinder/misc/foo.bar',
+ dirname(__DIR__) . '/fixtures/WildcardFileFinder/misc/sub/xyz',
+ dirname(__DIR__) . '/fixtures/WildcardFileFinder/modules/sub/foo.inc',
+ dirname(__DIR__) . '/fixtures/WildcardFileFinder/modules/sub/sub/foo.inc',
+ dirname(__DIR__) . '/fixtures/WildcardFileFinder/modules/sub/sub/sub/foo.inc',
+ dirname(__DIR__) . '/fixtures/WildcardFileFinder/tests/foo.test',
+ dirname(__DIR__) . '/fixtures/WildcardFileFinder/tests/sub/foo.test',
+ dirname(__DIR__) . '/fixtures/WildcardFileFinder/tests/sub/sub/foo.test',
+ dirname(__DIR__) . '/fixtures/WildcardFileFinder/tests/sub/sub/sub/foo.test',
+ );
+
+ $expected = array_fill_keys(
+ $expected,
+ array (
+ 'module' => 'views',
+ 'weight' => 0,
+ ));
+
+ $this->assertEquals($expected, $files);
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/DrupalBootTest/AbstractDrupalBootTest.php b/profiles/dkan/modules/contrib/xautoload/tests/src/DrupalBootTest/AbstractDrupalBootTest.php
new file mode 100644
index 00000000000..e3552e92cfe
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/DrupalBootTest/AbstractDrupalBootTest.php
@@ -0,0 +1,221 @@
+prepare();
+
+ $this->prepareAllEnabled();
+
+ $this->exampleDrupal->boot();
+
+ $expectedCalls = $this->getExpectedCallsForNormalRequest();
+
+ $this->callLog->assertCalls($this, $expectedCalls);
+
+ // Now we want all classes to be available.
+ foreach ($this->exampleModules->getExampleClasses() as $classes) {
+ foreach ((array)$classes as $class) {
+ $this->assertClassExists($class);
+ }
+ }
+
+ $this->unprepare();
+ }
+
+ /**
+ * Tests a request where modules are enabled, but xautoload is already
+ * enabled.
+ *
+ * @dataProvider providerModuleEnable
+ *
+ * @param mixed[] $initialModules
+ * Initial modules being installed / enabled.
+ * @param array $expectedCalls
+ *
+ * @throws \Exception
+ */
+ function testModuleEnable(array $initialModules, array $expectedCalls) {
+
+ $this->prepare();
+
+ $this->prepareInitialModules($initialModules);
+
+ foreach ($this->exampleModules->getExampleClasses() as $classes) {
+ foreach ((array)$classes as $class) {
+ $this->assertClassIsUndefined($class);
+ }
+ }
+
+ $this->exampleDrupal->boot();
+
+ $new_modules = array_keys($this->exampleModules->getExampleClasses());
+ $this->exampleDrupal->moduleEnable($new_modules);
+
+ # HackyLog::log($this->callLog->getCalls());
+
+ $this->callLog->assertCalls($this, $expectedCalls);
+
+ // Now we want all classes to be available.
+ foreach ($this->exampleModules->getExampleClasses() as $classes) {
+ foreach ((array)$classes as $class) {
+ $this->assertClassExists($class);
+ }
+ }
+
+ $this->unprepare();
+ }
+
+ /**
+ * @return array[]
+ */
+ abstract public function providerModuleEnable();
+
+ /**
+ * Start with all available modules enabled.
+ */
+ private function prepareAllEnabled() {
+ foreach (array('system', 'xautoload', 'libraries') as $name) {
+ $this->exampleDrupal->getSystemTable()->moduleSetEnabled($name);
+ }
+ foreach ($this->exampleModules->getExampleClasses() as $name => $classes) {
+ $this->exampleDrupal->getSystemTable()->moduleSetEnabled($name);
+ }
+ $this->exampleDrupal->getSystemTable()->moduleSetWeight('xautoload', -90);
+ }
+
+ /**
+ * @param mixed[] $initialModules
+ * Initial modules being installed / enabled.
+ *
+ * @throws \Exception
+ */
+ private function prepareInitialModules($initialModules) {
+ foreach ($initialModules as $name => $state) {
+ if (TRUE === $state) {
+ // Module is installed and enabled.
+ $this->exampleDrupal->getSystemTable()->moduleSetEnabled($name);
+ $this->exampleDrupal->getSystemTable()->moduleSetSchemaVersion($name, 7000);
+ }
+ elseif (FALSE === $state) {
+ // Module is installed, but disabled.
+ $this->exampleDrupal->getSystemTable()->moduleSetSchemaVersion($name, 7000);
+ }
+ elseif (NULL === $state) {
+ // Module is neither installed nor enabled.
+ }
+ else {
+ throw new \Exception("Unexpected state.");
+ }
+ }
+ if (isset($initialModules['xautoload'])) {
+ // xautoload is installed or enabled, so the module weight must be in the database.
+ $this->exampleDrupal->getSystemTable()->moduleSetWeight('xautoload', -90);
+ }
+ }
+
+ /**
+ * setUp() does not help us because of the process sharing problem.
+ * So we use this instead.
+ *
+ * @throws \Exception
+ */
+ abstract protected function prepare();
+
+ /**
+ * Runs after a test is finished.
+ */
+ private function unprepare() {
+ stream_wrapper_unregister('test');
+ StaticCallLog::unsetCallLog();
+ }
+
+ /**
+ * @param string $class
+ */
+ public function assertLoadClass($class) {
+ $this->assertFalse(class_exists($class, FALSE), "Class '$class' is not defined yet.");
+ $this->assertTrue(class_exists($class), "Class '$class' successfully loaded.");
+ }
+
+ /**
+ * @param string $class
+ */
+ public function assertClassExists($class) {
+ $this->assertTrue(class_exists($class), "Class '$class' exists.");
+ }
+
+ /**
+ * @param string $class
+ */
+ public function assertClassIsDefined($class) {
+ $this->assertTrue(class_exists($class, FALSE), "Class '$class' is defined.");
+ }
+
+ /**
+ * @param string $class
+ */
+ public function assertClassIsUndefined($class) {
+ $this->assertFalse(class_exists($class, FALSE), "Class '$class' is undefined.");
+ }
+
+ /**
+ * @return array[]
+ */
+ abstract protected function getExpectedCallsForNormalRequest();
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/DrupalBootTest/DrupalBootHookTest.php b/profiles/dkan/modules/contrib/xautoload/tests/src/DrupalBootTest/DrupalBootHookTest.php
new file mode 100644
index 00000000000..5b57b696b86
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/DrupalBootTest/DrupalBootHookTest.php
@@ -0,0 +1,172 @@
+ TRUE));
+ foreach (array(
+ 'xautoload' => array(FALSE, TRUE),
+ 'libraries' => array(FALSE, TRUE),
+ 'testmod' => array(FALSE, NULL),
+ ) as $module => $states) {
+ $initialModuleVariations = $this->providerArrayKeyVariations($initialModuleVariations, $module, $states);
+ }
+ $variations = array();
+ foreach ($initialModuleVariations as $initialModuleVariation) {
+ $expectedCalls = array();
+
+ if ($hookXautoloadEarly) {
+ $expectedCalls[] = array(
+ 'function' => 'testmod_xautoload',
+ 'args' => array(
+ '(xautoload_InjectedAPI_hookXautoload)',
+ dirname(dirname(__DIR__)) . '/fixtures/.modules/testmod',
+ ),
+ );
+ }
+
+ if (NULL === $initialModuleVariation['testmod']) {
+ $expectedCalls[] = array(
+ 'function' => 'testmod_schema',
+ 'args' => array(),
+ );
+ $expectedCalls[] = array(
+ 'function' => 'testmod_install',
+ 'args' => array(),
+ );
+ $expectedCalls[] = array(
+ 'function' => 'testmod_watchdog',
+ 'args' => array(),
+ );
+ }
+
+ $expectedCalls[] = array(
+ 'function' => 'testmod_enable',
+ 'args' => array(),
+ );
+ $expectedCalls[] = array(
+ 'function' => 'testmod_watchdog',
+ 'args' => array(),
+ );
+
+ if ($hookXautoloadLate) {
+ $expectedCalls[] = array(
+ 'function' => 'testmod_xautoload',
+ 'args' => array(
+ '(xautoload_InjectedAPI_hookXautoload)',
+ dirname(dirname(__DIR__)) . '/fixtures/.modules/testmod',
+ ),
+ );
+ }
+ $expectedCalls[] = array(
+ 'function' => 'testmod_modules_enabled',
+ 'args' => array(
+ '(array)'
+ ),
+ );
+ $expectedCalls[] = array(
+ 'function' => 'testmod_libraries_info',
+ 'args' => array(),
+ );
+ $expectedCalls[] = array(
+ 'function' => '_testmod_libraries_testlib_xautoload',
+ 'args' => array(
+ '(xautoload_InjectedAPI_hookXautoload)',
+ dirname(dirname(__DIR__)) . '/fixtures/.libraries/testlib',
+ ),
+ );
+
+ $variations[] = array($initialModuleVariation, $expectedCalls);
+ }
+ return $variations;
+ }
+
+ function initOnce() {
+ if (isset($this->exampleDrupal)) {
+ return;
+ }
+ $this->exampleModules = new HookTestExampleModules();
+ $this->exampleDrupal = new DrupalEnvironment($this->exampleModules);
+ $this->exampleDrupal->setStaticInstance();
+ }
+
+ /**
+ * setUp() does not help us because of the process sharing problem.
+ * So we use this instead.
+ *
+ * @throws \Exception
+ */
+ protected function prepare() {
+ $this->initOnce();
+ $filesystem = StreamWrapper::register('test');
+ foreach ($this->exampleModules->discoverModuleFilenames('module') as $name => $filename) {
+ $this->exampleDrupal->getSystemTable()->addModuleWithFilename($name, $filename);
+ }
+ $this->exampleDrupal->getSystemTable()->moduleSetEnabled('system');
+ $this->exampleDrupal->initBootstrapStatus();
+ # $this->exampleDrupal->getCache()->cacheSet('module_implements', $data, 'cache_bootstrap');
+ xautoload()->getServiceContainer()->set('system', $this->exampleDrupal->getMockDrupalSystem());
+ $this->callLog = new CallLog();
+ StaticCallLog::setCallLog($this->callLog);
+ }
+
+ /**
+ * @return array[]
+ */
+ protected function getExpectedCallsForNormalRequest() {
+ $expectedCalls = array(
+ array(
+ 'function' => 'testmod_xautoload',
+ 'args' => array(
+ '(xautoload_InjectedAPI_hookXautoload)',
+ dirname(dirname(__DIR__)) . '/fixtures/.modules/testmod',
+ # 'test://modules/testmod',
+ ),
+ ),
+ array(
+ 'function' => 'testmod_init',
+ 'args' => array(),
+ ),
+ array(
+ 'function' => 'testmod_libraries_info',
+ 'args' => array(),
+ ),
+ array(
+ 'function' => '_testmod_libraries_testlib_xautoload',
+ 'args' => array(
+ '(xautoload_InjectedAPI_hookXautoload)',
+ dirname(dirname(__DIR__)) . '/fixtures/.libraries/testlib',
+ # 'test://libraries/testlib',
+ ),
+ ),
+ );
+ return $expectedCalls;
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/DrupalBootTest/DrupalBootTest.php b/profiles/dkan/modules/contrib/xautoload/tests/src/DrupalBootTest/DrupalBootTest.php
new file mode 100644
index 00000000000..80b5fe6ed62
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/DrupalBootTest/DrupalBootTest.php
@@ -0,0 +1,157 @@
+ TRUE,
+ * 'xautoload' => FALSE,
+ * 'libraries' => NULL), ...)
+ */
+ private function initialModulesVariations($install) {
+ $variations = array();
+ $state = $install ? NULL : FALSE;
+ $variation = array('system' => TRUE);
+ $variation += array_fill_keys(array_keys($this->exampleModules->getExampleClasses()), $state);
+ $variations[] = $variation;
+ foreach (array('xautoload') as $module) {
+ $variations = $this->providerArrayKeyVariations($variations, $module, array(TRUE, FALSE, NULL));
+ }
+ return $variations;
+ }
+
+ /**
+ * @return array[]
+ */
+ public function providerModuleEnable() {
+ $this->initOnce();
+ $variations = array();
+ foreach (array(TRUE, FALSE) as $install) {
+ $expectedCalls = array();
+ $enabledModulesSoFar = array();
+ foreach ($this->exampleModules->getExampleClasses() as $module => $classes) {
+ $enabledModulesSoFar[] = $module;
+ if ($install) {
+ $expectedCalls[] = array(
+ 'function' => $module . '_schema',
+ 'args' => array(),
+ );
+ $expectedCalls[] = array(
+ 'function' => $module . '_install',
+ 'args' => array(),
+ );
+ foreach ($enabledModulesSoFar as $module) {
+ $expectedCalls[] = array(
+ 'function' => $module . '_watchdog',
+ 'args' => array(),
+ );
+ }
+ }
+ $expectedCalls[] = array(
+ 'function' => $module . '_enable',
+ 'args' => array(),
+ );
+ foreach ($enabledModulesSoFar as $module) {
+ $expectedCalls[] = array(
+ 'function' => $module . '_watchdog',
+ 'args' => array(),
+ );
+ }
+ }
+ foreach ($this->initialModulesVariations($install) as $moduleStates) {
+ /*
+ $enabledModules = array();
+ foreach ($moduleStates as $module => $state) {
+ if (TRUE !== $state) {
+ $enabledModules[$module] = TRUE;
+ }
+ }
+ foreach ($enabledModulesSoFar as $module) {
+ if (isset($enabledModules[$module])) {
+ unset($enabledModules[$module]);
+ $enabledModules[$module] = TRUE;
+ }
+ }
+ $enabledModules = array_keys($enabledModules);
+ */
+ $variationExpectedCalls = $expectedCalls;
+ foreach (array_keys($this->exampleModules->getExampleClasses()) as $module) {
+ $variationExpectedCalls[] = array(
+ 'function' => $module . '_modules_enabled',
+ 'args' => array('(array)'),
+ );
+ }
+ $variations[] = array($moduleStates, $variationExpectedCalls);
+ }
+ }
+
+ return $variations;
+ }
+
+ function initOnce() {
+ if (isset($this->exampleDrupal)) {
+ return;
+ }
+ $this->exampleModules = new ExampleModules();
+ $this->exampleDrupal = new DrupalEnvironment($this->exampleModules);
+ $this->exampleDrupal->setStaticInstance();
+ }
+
+ /**
+ * setUp() does not help us because of the process sharing problem.
+ * So we use this instead.
+ *
+ * @throws \Exception
+ */
+ protected function prepare() {
+ $this->initOnce();
+ $filesystem = StreamWrapper::register('test');
+ foreach ($this->exampleModules->discoverModuleFilenames('module') as $name => $filename) {
+ $this->exampleDrupal->getSystemTable()->addModuleWithFilename($name, $filename);
+ }
+ $this->exampleDrupal->getSystemTable()->moduleSetEnabled('system');
+ $this->exampleDrupal->initBootstrapStatus();
+ # $this->exampleDrupal->getCache()->cacheSet('module_implements', $data, 'cache_bootstrap');
+ xautoload()->getServiceContainer()->set('system', $this->exampleDrupal->getMockDrupalSystem());
+ $this->callLog = new CallLog();
+ StaticCallLog::setCallLog($this->callLog);
+ }
+
+ /**
+ * @return array[]
+ */
+ protected function getExpectedCallsForNormalRequest() {
+ $expectedCalls = array();
+ foreach ($this->exampleModules->getExampleClasses() as $module => $classes) {
+ $expectedCalls[] = array(
+ 'function' => $module . '_init',
+ 'args' => array(),
+ );
+ }
+ return $expectedCalls;
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/Example/AbstractExampleModules.php b/profiles/dkan/modules/contrib/xautoload/tests/src/Example/AbstractExampleModules.php
new file mode 100644
index 00000000000..172a740cd4c
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/Example/AbstractExampleModules.php
@@ -0,0 +1,106 @@
+getAvailableExtensions() as $name => $type) {
+ if ('module' !== $type) {
+ continue;
+ }
+ $modules[$name] = (object)array(
+ 'uri' => $this->getExtensionFilename($type, $name),
+ 'filename' => $name . '.module',
+ 'name' => $name,
+ );
+ }
+ return $modules;
+ }
+
+ /**
+ * @return string[]
+ * Extension types by name.
+ */
+ abstract protected function getAvailableExtensions();
+
+ /**
+ * @return true[]
+ */
+ public function getBootstrapModules() {
+ $bootstrap_modules = array();
+ foreach ($this->discoverModuleFilenames('module') as $name => $filename) {
+ $php = file_get_contents($filename);
+ foreach (PureFunctions::bootstrapHooks() as $hook) {
+ if (FALSE !== strpos($php, 'function ' . $name . '_' . $hook)) {
+ $bootstrap_modules[$name] = TRUE;
+ break;
+ }
+ }
+ }
+ return $bootstrap_modules;
+ }
+
+ /**
+ * @param \Drupal\xautoload\Tests\DrupalBootTest\AbstractDrupalBootTest $testCase
+ */
+ public function assertLoadExampleClasses(AbstractDrupalBootTest $testCase) {
+ foreach ($this->getExampleClasses() as $class) {
+ $testCase->assertLoadClass($class);
+ }
+ }
+
+ /**
+ * @return array[]
+ */
+ abstract public function getExampleClasses();
+
+ /**
+ * @param string $type
+ * E.g. 'module'
+ *
+ * @return string[]
+ */
+ function discoverModuleFilenames($type) {
+ $filenames = array();
+ foreach ($this->getAvailableExtensions() as $name => $itemType) {
+ if ($type !== $itemType) {
+ continue;
+ }
+ $filenames[$name] = $this->getExtensionFilename($type, $name);
+ }
+ return $filenames;
+ }
+
+ /**
+ * @param string $type
+ * @param string $name
+ *
+ * @return string
+ */
+ public function getExtensionFilename($type, $name) {
+ if ('xautoload' === $name) {
+ return dirname(dirname(dirname(__DIR__))) . '/xautoload.module';
+ }
+ $file = dirname(dirname(__DIR__)) . '/fixtures/.modules/' . $name . '/' . $name . '.module';
+ if (is_file($file)) {
+ return $file;
+ }
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/Example/ExampleModules.php b/profiles/dkan/modules/contrib/xautoload/tests/src/Example/ExampleModules.php
new file mode 100644
index 00000000000..647a19bc419
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/Example/ExampleModules.php
@@ -0,0 +1,52 @@
+ 'testmod_pearflat_Foo',
+ 'testmod_psr0_lib' => 'Drupal\testmod_psr0_lib\Foo',
+ 'testmod_psr4_custom' => 'Drupal\testmod_psr4_custom\Foo',
+ 'testmod_psr4_src' => 'Drupal\testmod_psr4_src\Foo',
+ );
+ }
+
+ /**
+ * Replicates drupal_parse_info_file(dirname($module->uri) . '/' . $module->name . '.info')
+ *
+ * @see drupal_parse_info_file()
+ *
+ * @param string $name
+ *
+ * @return array
+ * Parsed info file contents.
+ */
+ public function drupalParseInfoFile($name) {
+ $info = array('core' => '7.x');
+ if (0 === strpos($name, 'testmod')) {
+ $info['dependencies'][] = 'xautoload';
+ }
+ return $info;
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/Example/HookTestExampleModules.php b/profiles/dkan/modules/contrib/xautoload/tests/src/Example/HookTestExampleModules.php
new file mode 100644
index 00000000000..59871d244ef
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/Example/HookTestExampleModules.php
@@ -0,0 +1,56 @@
+ array(
+ 'Drupal\\testmod\\Foo',
+ 'Acme\\TestLib\\Foo',
+ ),
+ );
+ }
+
+ /**
+ * Replicates drupal_parse_info_file(dirname($module->uri) . '/' . $module->name . '.info')
+ *
+ * @see drupal_parse_info_file()
+ *
+ * @param string $name
+ *
+ * @return array
+ * Parsed info file contents.
+ */
+ public function drupalParseInfoFile($name) {
+ $info = array('core' => '7.x');
+ if ('testmod' === $name) {
+ $info['dependencies'][] = 'xautoload';
+ $info['dependencies'][] = 'libraries';
+ }
+ return $info;
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/Filesystem/StreamWrapper.php b/profiles/dkan/modules/contrib/xautoload/tests/src/Filesystem/StreamWrapper.php
new file mode 100644
index 00000000000..6eb23e80289
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/Filesystem/StreamWrapper.php
@@ -0,0 +1,159 @@
+getStat($path);
+ }
+
+ /**
+ * @param $path
+ * @param $mode
+ * @param $options
+ * @param $opened_path
+ *
+ * @return bool
+ *
+ * @throws \Exception
+ */
+ function stream_open($path, $mode, $options, &$opened_path) {
+
+ $this->contents = self::$filesystem->getFileContents($path);
+
+ $this->path = $path;
+ $this->position = 0;
+
+ return TRUE;
+ }
+
+ /**
+ * @return array
+ * Stat for the currently open stream.
+ * @throws \Exception
+ */
+ function stream_stat() {
+ if (!isset($this->path)) {
+ throw new \Exception("No file currently open.");
+ }
+ return self::$filesystem->getStat($this->path, FALSE);
+ }
+
+ /**
+ * @param int $count
+ * Number of characters to read.
+ *
+ * @return string
+ * Snippet read from the file.
+ */
+ function stream_read($count) {
+ $ret = substr($this->contents, $this->position, $count);
+ $this->position += strlen($ret);
+
+ return $ret;
+ }
+
+ /**
+ * @return bool
+ */
+ function stream_eof() {
+ return $this->position >= strlen($this->contents);
+ }
+
+ /**
+ * @param string $path
+ * @param int $options
+ *
+ * @return bool
+ */
+ function dir_opendir($path, $options) {
+ $contents = self::$filesystem->getDirContents($path);
+ if (FALSE === $contents) {
+ return FALSE;
+ }
+ $this->path = $path;
+ $this->dirContents = $contents;
+ return TRUE;
+ }
+
+ /**
+ * @return string
+ */
+ function dir_readdir() {
+ $name = current($this->dirContents);
+ next($this->dirContents);
+ return $name;
+ }
+}
\ No newline at end of file
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/Filesystem/VirtualFilesystem.php b/profiles/dkan/modules/contrib/xautoload/tests/src/Filesystem/VirtualFilesystem.php
new file mode 100644
index 00000000000..ca8787dc1d3
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/Filesystem/VirtualFilesystem.php
@@ -0,0 +1,273 @@
+reportedOperations[] = $file . ' - include';
+ }
+
+ /**
+ * @var string
+ */
+ protected $instanceKey;
+
+ /**
+ * @var string[]
+ */
+ protected $knownPaths = array();
+
+ /**
+ * @var string[]
+ */
+ protected $reportedOperations = array();
+
+ const NOTHING = FALSE;
+ const DIR = '(dir)';
+ const FILE = '(file)';
+
+ function __construct() {
+ $this->instanceKey = Util::randomString();
+ self::$instances[$this->instanceKey] = $this;
+ }
+
+ /**
+ * @return array[]
+ */
+ function getReportedOperations() {
+ return $this->reportedOperations;
+ }
+
+ /**
+ * Delete all reported operations and start fresh.
+ */
+ function resetReportedOperations() {
+ $this->reportedOperations = array();
+ }
+
+ /**
+ * @param string $file
+ * @param string $class
+ * @throws \Exception
+ */
+ function addClass($file, $class) {
+ $this->addKnownFile($file);
+ if (self::FILE !== ($existing = $this->knownPaths[$file])) {
+ throw new \Exception("A non-empty file already exists at '$file'. Cannot overwrite with class '$class'.");
+ }
+ $this->knownPaths[$file] = $class;
+ }
+
+ /**
+ * @param string $file
+ * @param string $php
+ * File contents starting with 'addKnownFile($file);
+ if (!$overwrite && self::FILE !== ($existing = $this->knownPaths[$file])) {
+ throw new \Exception("A non-empty file already exists at '$file'. Cannot overwrite with PHP code.");
+ }
+ if (0 !== strpos($php, 'knownPaths[$file] = $php;
+ }
+
+ /**
+ * @param string[] $files
+ */
+ function addKnownFiles(array $files) {
+ foreach ($files as $file) {
+ $this->addKnownFile($file);
+ }
+ }
+
+ /**
+ * @param string $file
+ *
+ * @throws \Exception
+ */
+ function addKnownFile($file) {
+ if (!isset($this->knownPaths[$file])) {
+ $this->knownPaths[$file] = self::FILE;
+ $this->addKnownDir(dirname($file));
+ }
+ elseif (self::DIR === $this->knownPaths[$file]) {
+ throw new \Exception("A directory already exists at '$file', cannot overwrite with a file.");
+ }
+ }
+
+ /**
+ * @param string $dir
+ */
+ function addKnownDir($dir) {
+ if (FALSE === strpos($dir, '://')) {
+ return;
+ }
+ if (!isset($this->knownPaths[$dir])) {
+ // Need to set parents first.
+ $this->addKnownDir(dirname($dir));
+ }
+ $this->knownPaths[$dir] = self::DIR;
+ }
+
+ /**
+ * @param string $path
+ * @return string|bool
+ * One of self::NOTHING, self::DIR, self::FILE, or a class name for a class
+ * that is supposed to be defined in the file.
+ */
+ function resolvePath($path) {
+ if (isset($this->knownPaths[$path])) {
+ return $this->knownPaths[$path];
+ }
+ else {
+ return self::NOTHING;
+ }
+ }
+
+ /**
+ * @param string $dir
+ * @return array|bool
+ */
+ function getDirContents($dir) {
+ if (empty($this->knownPaths[$dir]) || self::DIR !== $this->knownPaths[$dir]) {
+ return FALSE;
+ }
+ $pos = strlen($dir . '/');
+ $contents = array('.', '..');
+ foreach ($this->knownPaths as $path => $type) {
+ if ($dir . '/' !== substr($path, 0, $pos)) {
+ continue;
+ }
+ $name = substr($path, $pos);
+ if (FALSE !== strpos($name, '/')) {
+ // This is a deeper subdirectory.
+ continue;
+ }
+ if ('' === $name) {
+ continue;
+ }
+ $contents[] = $name;
+ }
+ return $contents;
+ }
+
+ /**
+ * @param string $path
+ * @param bool $report
+ *
+ * @return array
+ */
+ function getStat($path, $report = TRUE) {
+ if ($report) {
+ $this->reportedOperations[] = $path . ' - stat';
+ }
+ if (!isset($this->knownPaths[$path])) {
+ // File does not exist.
+ return FALSE;
+ }
+ elseif (self::DIR === $this->knownPaths[$path]) {
+ return stat(__DIR__);
+ }
+ else {
+ // Create a tmp file with the contents and get its stats.
+ $contents = $this->getFileContents($path);
+ $resource = tmpfile();
+ fwrite($resource, $contents);
+ $stat = fstat($resource);
+ fclose($resource);
+ return $stat;
+ }
+ }
+
+ /**
+ * @param $path
+ * The file path.
+ *
+ * @return string
+ * The file contents.
+ *
+ * @throws \Exception
+ * Exception thrown if there is no file at $path.
+ */
+ function getFileContents($path) {
+ if (!isset($this->knownPaths[$path])) {
+ // File does not exist.
+ throw new \Exception("Assumed file '$path' does not exist.");
+ }
+ elseif (self::DIR === $this->knownPaths[$path]) {
+ throw new \Exception("Assumed file '$path' is a directory.");
+ }
+
+ $instance_key_export = var_export($this->instanceKey, TRUE);
+ $path_export = var_export($path, TRUE);
+ if (self::FILE === $this->knownPaths[$path]) {
+ // Empty PHP file..
+ return <<knownPaths[$path], 'knownPaths[$path], 5);
+ return <<knownPaths[$path])) {
+ // File with arbitrary contents.
+ return $this->knownPaths[$path];
+ }
+
+ // PHP file with class definition.
+ $class = $this->knownPaths[$path];
+
+ if (FALSE === ($pos = strrpos($class, '\\'))) {
+ // Class without namespace.
+ return <<components = $components;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function variableSet($name, $value) {
+ $this->variables[$name] = $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function variableGet($name, $default = NULL) {
+ return isset($this->variables[$name])
+ ? $this->variables[$name]
+ : $default;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function drupalGetFilename($type, $name) {
+ return $this->components->DrupalGetFilename->drupalGetFilename($type, $name);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function drupalGetPath($type, $name) {
+ return $this->components->DrupalGetFilename->drupalGetPath($type, $name);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function getExtensionTypes($extension_names) {
+ // Simply assume that everything is a module.
+ return array_fill_keys($extension_names, 'module');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ function getActiveExtensions() {
+ return $this->components->SystemTable->getActiveExtensions();
+ }
+
+ /**
+ * Replicates module_list()
+ *
+ * @param bool $refresh
+ * @param bool $bootstrap_refresh
+ * @param bool $sort
+ *
+ * @return string[]
+ */
+ function moduleList($refresh = FALSE, $bootstrap_refresh = FALSE, $sort = FALSE) {
+ return $this->components->ModuleList->moduleList($refresh, $bootstrap_refresh, $sort);
+ }
+
+ /**
+ * @see module_invoke()
+ *
+ * @param string $module
+ * @param string $hook
+ *
+ * @return mixed
+ *
+ * @throws \Exception
+ */
+ function moduleInvoke($module, $hook) {
+ $args = func_get_args();
+ switch (count($args)) {
+ case 2:
+ return PureFunctions::moduleInvoke($module, $hook);
+ case 3:
+ return PureFunctions::moduleInvoke($module, $hook, $args[2]);
+ case 4:
+ return PureFunctions::moduleInvoke($module, $hook, $args[2], $args[3]);
+ default:
+ throw new \Exception("More arguments than expected.");
+ }
+ }
+
+ /**
+ * @param string $hook
+ */
+ function moduleInvokeAll($hook) {
+ $args = func_get_args();
+ call_user_func_array(array($this->components->HookSystem, 'moduleInvokeAll'), $args);
+ }
+
+ /**
+ * @param string $hook
+ *
+ * @throws \Exception
+ * @return array
+ */
+ function moduleImplements($hook) {
+ return $this->components->HookSystem->moduleImplements($hook);
+ }
+
+ /**
+ * @param string $hook
+ * @param mixed $data
+ */
+ function drupalAlter($hook, &$data) {
+ $args = func_get_args();
+ assert($hook === array_shift($args));
+ assert($data === array_shift($args));
+ while (count($args) < 3) {
+ $args[] = NULL;
+ }
+ $this->components->HookSystem->drupalAlter($hook, $data, $args[0], $args[1], $args[2]);
+ }
+
+ /**
+ * Replicates module_load_include()
+ *
+ * @param string $type
+ * @param string $module
+ * @param string|null $name
+ *
+ * @return bool|string
+ */
+ function moduleLoadInclude($type, $module, $name = NULL) {
+ if (!isset($name)) {
+ $name = $module;
+ }
+ $file = $this->drupalGetPath('module', $module) . "/$name.$type";
+ if (is_file($file)) {
+ require_once $file;
+ return $file;
+ }
+ return FALSE;
+ }
+
+ /**
+ * Resets the module_implements() cache.
+ */
+ public function resetModuleImplementsCache() {
+ $this->components->HookSystem->moduleImplementsReset();
+ }
+
+ /**
+ * @see libraries_info()
+ *
+ * @return mixed
+ */
+ function getLibrariesInfo() {
+ $this->components->LibrariesInfo->resetLibrariesInfo();
+ return $this->components->LibrariesInfo->getLibrariesInfo();
+ }
+
+ /**
+ * @see libraries_get_path()
+ *
+ * @param string $name
+ * Name of the library.
+ *
+ * @return string|false
+ */
+ function librariesGetPath($name) {
+ return $this->components->LibrariesInfo->librariesGetPath($name);
+ }
+
+ /**
+ * Called from xautoload_install() to set the module weight.
+ *
+ * @param int $weight
+ * New module weight for xautoload.
+ */
+ public function installSetModuleWeight($weight) {
+ $this->components->SystemTable->moduleSetWeight('xautoload', $weight);
+ $this->components->SystemListReset->systemListReset();
+ }
+
+ /**
+ * @param string $cid
+ * @param string $bin
+ *
+ * @return object|false
+ * The cache or FALSE on failure.
+ *
+ * @see cache_get()
+ */
+ public function cacheGet($cid, $bin = 'cache') {
+ return $this->components->Cache->cacheGet($cid, $bin);
+ }
+
+ /**
+ * @param string $cid
+ * @param mixed $data
+ * @param string $bin
+ *
+ * @return mixed
+ *
+ * @see cache_set()
+ */
+ public function cacheSet($cid, $data, $bin = 'cache') {
+ $this->components->Cache->cacheSet($cid, $data, $bin);
+ }
+
+ /**
+ * @param string|null $cid
+ * @param string|null $bin
+ *
+ * @see cache_clear_all()
+ */
+ public function cacheClearAll($cid = NULL, $bin = NULL) {
+ $this->components->Cache->cacheClearAll($cid, $bin);
+ }
+
+ /**
+ * @param string $key
+ */
+ public function drupalStaticReset($key) {
+ $this->components->DrupalStatic->resetKey($key);
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/Util/CallLog.php b/profiles/dkan/modules/contrib/xautoload/tests/src/Util/CallLog.php
new file mode 100644
index 00000000000..72911a7e071
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/Util/CallLog.php
@@ -0,0 +1,75 @@
+calls[] = $call;
+ }
+
+ /**
+ * @return array[]
+ */
+ function getCalls() {
+ return $this->calls;
+ }
+
+ /**
+ * @param \PHPUnit_Framework_TestCase $testCase
+ * @param array[] $expectedCalls
+ */
+ function assertCalls(\PHPUnit_Framework_TestCase $testCase, array $expectedCalls) {
+ if (array_values($expectedCalls) !== $expectedCalls) {
+ throw new \InvalidArgumentException('$expectedCalls must be a numeric array with no keys missing.');
+ }
+ $extractFunction = array($this, 'callGetFunction');
+ $testCase->assertEquals(
+ "\n" . implode("\n", array_map($extractFunction, $expectedCalls)) . "\n",
+ "\n" . implode("\n", array_map($extractFunction, $this->calls)) . "\n");
+ $testCase->assertEquals($expectedCalls, $this->calls);
+ for ($i = 0; TRUE; ++$i) {
+ $actualCall = isset($this->calls[$i]) ? $this->calls[$i] : NULL;
+ $expectedCall = isset($expectedCalls[$i]) ? $expectedCalls[$i] : NULL;
+ if (NULL === $actualCall && NULL === $expectedCall) {
+ break;
+ }
+ if (NULL === $actualCall) {
+ $testCase->fail("Call $i missing.\nExpected: " . var_export($expectedCall, TRUE));
+ break;
+ }
+ if (NULL === $expectedCall) {
+ $testCase->fail("Call $i was not expected.\nActual: " . var_export($actualCall, TRUE));
+ break;
+ }
+ if ($actualCall !== $expectedCall) {
+ $testCase->fail("Call $i mismatch.\nExpected: " . var_export($expectedCall, TRUE) . "\nActual: " . var_export($this->calls[$i], TRUE));
+ break;
+ }
+ }
+ $testCase->assertEquals($expectedCalls, $this->calls);
+ }
+
+ function callGetFunction($call) {
+ if (!isset($call['function'])) {
+ return NULL;
+ }
+ if (!isset($call['class'])) {
+ return $call['function'];
+ }
+ return $call['class'] . '::' . $call['function'];
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/Util/HackyLog.php b/profiles/dkan/modules/contrib/xautoload/tests/src/Util/HackyLog.php
new file mode 100644
index 00000000000..63452fbfe7f
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/Util/HackyLog.php
@@ -0,0 +1,23 @@
+=')
+ ? debug_backtrace(0, 2)
+ // Second parameter not supported in PHP < 5.4.0. It would cause a
+ // "Warning: debug_backtrace() expects at most 1 parameter, 2 given".
+ : debug_backtrace(0);
+
+ $call = $trace[1];
+ $callFiltered = array();
+ foreach (array('function', 'class', 'type') as $key) {
+ if (isset($call[$key])) {
+ $callFiltered[$key] = $call[$key];
+ }
+ }
+ $callFiltered['args'] = array();
+ foreach ($call['args'] as $arg) {
+ if (is_array($arg)) {
+ $arg = '(array)';
+ }
+ elseif (is_object($arg)) {
+ $arg = '(' . get_class($arg) . ')';
+ }
+ $callFiltered['args'][] = $arg;
+ }
+ self::$callLog->addCall($callFiltered);
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/Cache.php b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/Cache.php
new file mode 100644
index 00000000000..5254d7eb796
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/Cache.php
@@ -0,0 +1,68 @@
+cache[$bin][$cid])) {
+ return FALSE;
+ }
+ return $this->cache[$bin][$cid];
+ }
+
+ /**
+ * @param string $cid
+ * @param mixed $data
+ * @param string $bin
+ *
+ * @see cache_set()
+ */
+ function cacheSet($cid, $data, $bin = 'cache') {
+ $this->cache[$bin][$cid] = (object)array(
+ 'data' => $data,
+ );
+ }
+
+ /**
+ * @param null $cid
+ * @param null $bin
+ *
+ * @return mixed
+ *
+ * @see cache_clear_all()
+ */
+ function cacheClearAll($cid = NULL, $bin = NULL) {
+ if (!isset($cid) && !isset($bin)) {
+ $this->cacheClearAll(NULL, 'cache_page');
+ return NULL;
+ }
+ elseif (!isset($cid)) {
+ unset($this->cache[$bin]);
+ }
+ elseif (!isset($bin)) {
+ throw new \InvalidArgumentException("No cache \$bin argument given.");
+ }
+ else {
+ unset($this->cache[$bin][$cid]);
+ }
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalBootstrap.php b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalBootstrap.php
new file mode 100644
index 00000000000..38932f86f42
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalBootstrap.php
@@ -0,0 +1,120 @@
+drupalLoad = $drupalLoad;
+ $this->hookSystem = $hookSystem;
+ $this->moduleList = $moduleList;
+ }
+
+ /**
+ * @see drupal_bootstrap()
+ */
+ function boot() {
+ $this->drupalBootstrapVariables();
+ $this->drupalBootstrapPageHeader();
+ $this->drupalBootstrapFull();
+ }
+
+ /**
+ * @see _drupal_bootstrap_variables()
+ */
+ private function drupalBootstrapVariables() {
+ $this->moduleLoadAll(TRUE);
+ }
+
+ /**
+ * @see _drupal_bootstrap_page_header()
+ */
+ private function drupalBootstrapPageHeader() {
+ $this->bootstrapInvokeAll('boot');
+ }
+
+ /**
+ * @see _drupal_bootstrap_full()
+ */
+ private function drupalBootstrapFull() {
+ $this->moduleLoadAll();
+ $this->menuSetCustomTheme();
+ $this->hookSystem->moduleInvokeAll('init');
+ }
+
+ /**
+ * @see menu_set_custom_theme()
+ */
+ private function menuSetCustomTheme() {
+ $this->hookSystem->moduleInvokeAll('custom_theme');
+ }
+
+ /**
+ * Replicates module_load_all()
+ *
+ * @see module_load_all()
+ *
+ * @param bool|null $bootstrap
+ *
+ * @return bool
+ */
+ private function moduleLoadAll($bootstrap = FALSE) {
+ if (isset($bootstrap)) {
+ foreach ($this->moduleList->moduleList(TRUE, $bootstrap) as $module) {
+ $this->drupalLoad->drupalLoad('module', $module);
+ }
+ // $has_run will be TRUE if $bootstrap is FALSE.
+ $this->moduleLoadAllHasRun = !$bootstrap;
+ }
+ return $this->moduleLoadAllHasRun;
+ }
+
+ /**
+ * @see bootstrap_invoke_all()
+ *
+ * @param string $hook
+ */
+ private function bootstrapInvokeAll($hook) {
+ // Bootstrap modules should have been loaded when this function is called, so
+ // we don't need to tell module_list() to reset its internal list (and we
+ // therefore leave the first parameter at its default value of FALSE). We
+ // still pass in TRUE for the second parameter, though; in case this is the
+ // first time during the bootstrap that module_list() is called, we want to
+ // make sure that its internal cache is primed with the bootstrap modules
+ // only.
+ foreach ($this->moduleList->moduleList(FALSE, TRUE) as $module) {
+ $this->drupalLoad->drupalLoad('module', $module);
+ PureFunctions::moduleInvoke($module, $hook);
+ }
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalComponentContainer.php b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalComponentContainer.php
new file mode 100644
index 00000000000..e0d7f6d7aa0
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalComponentContainer.php
@@ -0,0 +1,268 @@
+exampleModules = $exampleModules;
+ }
+
+ /**
+ * Magic getter for a Drupal component.
+ *
+ * @param string $key
+ *
+ * @return object
+ *
+ * @throws \Exception
+ */
+ function __get($key) {
+ if (array_key_exists($key, $this->components)) {
+ return $this->components[$key];
+ }
+ $method = 'get' . $key;
+ if (!method_exists($this, $method)) {
+ throw new \Exception("Unsupported key '$key' for DrupalComponentContainer.");
+ }
+ return $this->components[$key] = $this->$method($this);
+ }
+
+ /**
+ * @return SystemTable
+ *
+ * @see DrupalComponentContainer::SystemTable
+ */
+ protected function getSystemTable() {
+ return new SystemTable();
+ }
+
+ /**
+ * @return Cache
+ *
+ * @see DrupalComponentContainer::Cache
+ */
+ protected function getCache() {
+ return new Cache();
+ }
+
+ /**
+ * @return DrupalStatic
+ *
+ * @see DrupalComponentContainer::DrupalStatic
+ */
+ protected function getDrupalStatic() {
+ return new DrupalStatic();
+ }
+
+ /**
+ * @return DrupalGetFilename
+ *
+ * @see DrupalComponentContainer::DrupalGetFilename
+ */
+ protected function getDrupalGetFilename() {
+ return new DrupalGetFilename($this->SystemTable, $this->exampleModules);
+ }
+
+ /**
+ * @return HookSystem
+ *
+ * @see DrupalComponentContainer::HookSystem
+ */
+ protected function getHookSystem() {
+ return new HookSystem(
+ $this->DrupalStatic,
+ $this->Cache,
+ $this->ModuleList);
+ }
+
+ /**
+ * @return ModuleEnable
+ *
+ * @see DrupalComponentContainer::ModuleEnable
+ */
+ protected function getModuleEnable() {
+ return new ModuleEnable(
+ $this->DrupalGetFilename,
+ $this->HookSystem,
+ $this->ModuleList,
+ $this->SystemTable,
+ $this->SystemListReset,
+ $this->SystemRebuildModuleData,
+ $this->SystemUpdateBootstrapStatus);
+ }
+
+ /**
+ * @return ModuleList
+ *
+ * @see DrupalComponentContainer::ModuleList
+ */
+ protected function getModuleList() {
+ return new ModuleList(
+ $this->DrupalGetFilename,
+ $this->SystemList,
+ $this->DrupalStatic);
+ }
+
+ /**
+ * @return SystemListReset
+ *
+ * @see DrupalComponentContainer::SystemListReset
+ */
+ protected function getSystemListReset() {
+ return new SystemListReset(
+ $this->Cache,
+ $this->DrupalStatic);
+ }
+
+ /**
+ * @return ModuleBuildDependencies
+ *
+ * @see DrupalComponentContainer::ModuleBuildDependencies
+ */
+ protected function getModuleBuildDependencies() {
+ return new ModuleBuildDependencies();
+ }
+
+ /**
+ * @return SystemBuildModuleData
+ *
+ * @see DrupalComponentContainer::SystemBuildModuleData
+ */
+ protected function getSystemBuildModuleData() {
+ return new SystemBuildModuleData(
+ $this->exampleModules,
+ $this->HookSystem);
+ }
+
+ /**
+ * @return SystemRebuildModuleData
+ *
+ * @see DrupalComponentContainer::SystemRebuildModuleData
+ */
+ protected function getSystemRebuildModuleData() {
+ return new SystemRebuildModuleData(
+ $this->DrupalStatic,
+ $this->ModuleBuildDependencies,
+ $this->SystemTable,
+ $this->SystemBuildModuleData,
+ $this->SystemListReset);
+ }
+
+ /**
+ * @return SystemUpdateBootstrapStatus
+ *
+ * @see DrupalComponentContainer::SystemUpdateBootstrapStatus
+ */
+ protected function getSystemUpdateBootstrapStatus() {
+ return new SystemUpdateBootstrapStatus(
+ $this->HookSystem,
+ $this->SystemTable,
+ $this->SystemListReset);
+ }
+
+ /**
+ * @return SystemList
+ *
+ * @see DrupalComponentContainer::SystemList
+ */
+ protected function getSystemList() {
+ return new SystemList(
+ $this->Cache,
+ $this->SystemTable,
+ $this->DrupalGetFilename,
+ $this->DrupalStatic);
+ }
+
+ /**
+ * @return LibrariesInfo
+ *
+ * @see DrupalComponentContainer::LibrariesInfo
+ */
+ protected function getLibrariesInfo() {
+ return new LibrariesInfo(
+ $this->DrupalStatic,
+ $this->HookSystem);
+ }
+
+ /**
+ * @return LibrariesLoad
+ *
+ * @see DrupalComponentContainer::LibrariesLoad
+ */
+ protected function getLibrariesLoad() {
+ return new LibrariesLoad(
+ $this->DrupalStatic,
+ $this->Cache,
+ $this->LibrariesInfo);
+ }
+
+ /**
+ * @return DrupalBootstrap
+ *
+ * @see DrupalComponentContainer::DrupalBoot
+ */
+ protected function getDrupalBoot() {
+ return new DrupalBootstrap(
+ $this->DrupalLoad,
+ $this->HookSystem,
+ $this->ModuleList);
+ }
+
+ /**
+ * @return MockDrupalSystem
+ *
+ * @see DrupalComponentContainer::MockDrupalSystem
+ */
+ protected function getMockDrupalSystem() {
+ return new MockDrupalSystem($this);
+ }
+
+ /**
+ * @return DrupalLoad
+ *
+ * @see DrupalComponentContainer::DrupalLoad
+ */
+ protected function getDrupalLoad() {
+ return new DrupalLoad(
+ $this->DrupalGetFilename);
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalEnvironment.php b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalEnvironment.php
new file mode 100644
index 00000000000..bafef37a279
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalEnvironment.php
@@ -0,0 +1,106 @@
+components = new DrupalComponentContainer($exampleModules);
+ $this->exampleModules = $exampleModules;
+ }
+
+ function setStaticInstance() {
+ self::$staticInstance = $this;
+ }
+
+ /**
+ * @return DrupalEnvironment
+ */
+ static function getInstance() {
+ return self::$staticInstance;
+ }
+
+ /**
+ * @return MockDrupalSystem
+ */
+ function getMockDrupalSystem() {
+ return $this->components->MockDrupalSystem;
+ }
+
+ /**
+ * @return Cache
+ */
+ function getCache() {
+ return $this->components->Cache;
+ }
+
+ /**
+ * @return SystemTable
+ */
+ function getSystemTable() {
+ return $this->components->SystemTable;
+ }
+
+ /**
+ * Simulates Drupal's \module_enable()
+ *
+ * @param string[] $module_list
+ * Array of module names.
+ * @param bool $enable_dependencies
+ * TRUE, if dependencies should be enabled too.
+ *
+ * @return bool
+ */
+ function moduleEnable(array $module_list, $enable_dependencies = TRUE) {
+ $this->components->ModuleEnable->moduleEnable($module_list, $enable_dependencies);
+ }
+
+ /**
+ * Replicates the Drupal bootstrap.
+ */
+ public function boot() {
+ $this->components->DrupalBoot->boot();
+ }
+
+ /**
+ * Version of systemUpdateBootstrapStatus() with no side effects.
+ *
+ * @see _system_update_bootstrap_status()
+ */
+ public function initBootstrapStatus() {
+ $bootstrap_modules = $this->exampleModules->getBootstrapModules();
+ $this->components->SystemTable->setBootstrapModules($bootstrap_modules);
+ }
+
+ /**
+ * @param string $name
+ *
+ * @return mixed
+ */
+ public function librariesLoad($name) {
+ return $this->components->LibrariesLoad->librariesLoad($name);
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalGetFilename.php b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalGetFilename.php
new file mode 100644
index 00000000000..d50039a2754
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalGetFilename.php
@@ -0,0 +1,97 @@
+systemTable = $systemTable;
+ $this->exampleModules = $exampleModules;
+ }
+
+ /**
+ * Replicates drupal_get_filename(*, *, $filename)
+ *
+ * @param string $type
+ * @param string $name
+ * @param string $filename
+ */
+ function drupalSetFilename($type, $name, $filename) {
+ if (file_exists($filename)) {
+ $this->files[$type][$name] = $filename;
+ }
+ }
+
+ /**
+ * Replicates drupal_get_filename(*, *, NULL)
+ *
+ * @param string $type
+ * @param string $name
+ *
+ * @return string|null
+ */
+ function drupalGetFilename($type, $name) {
+
+ // Profiles are a special case: they have a fixed location and naming.
+ if ($type == 'profile') {
+ $profile_filename = "profiles/$name/$name.profile";
+ $this->files[$type][$name] = file_exists($profile_filename)
+ ? $profile_filename
+ : FALSE;
+ }
+
+ // Look in runtime cache.
+ if (isset($this->files[$type][$name])) {
+ return $this->files[$type][$name];
+ }
+
+ // Load from the database.
+ $file = $this->systemTable->moduleGetFilename($name);
+ if (isset($file) && file_exists($file)) {
+ $this->files[$type][$name] = $file;
+ return $file;
+ }
+
+ // Fallback: Search the filesystem.
+ $this->files[$type] = $this->exampleModules->discoverModuleFilenames($type);
+
+ if (isset($this->files[$type][$name])) {
+ return $this->files[$type][$name];
+ }
+
+ return NULL;
+ }
+
+ /**
+ * @see drupal_get_path()
+ *
+ * @param string $type
+ * @param string $name
+ *
+ * @return string|null
+ */
+ function drupalGetPath($type, $name) {
+ return dirname($this->drupalGetFilename($type, $name));
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalLoad.php b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalLoad.php
new file mode 100644
index 00000000000..f6e857bc2cd
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalLoad.php
@@ -0,0 +1,46 @@
+drupalGetFilename = $drupalGetFilename;
+ }
+
+ /**
+ * @see drupal_load()
+ */
+ function drupalLoad($type, $name) {
+
+ if (isset($this->files[$type][$name])) {
+ return TRUE;
+ }
+
+ $filename = $this->drupalGetFilename->drupalGetFilename($type, $name);
+
+ if ($filename) {
+ include_once $filename;
+ $this->files[$type][$name] = TRUE;
+
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalStatic.php b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalStatic.php
new file mode 100644
index 00000000000..1747d7a0f02
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalStatic.php
@@ -0,0 +1,88 @@
+data[$name]) || array_key_exists($name, $this->data)) {
+ // Non-NULL $name and both $this->data[$name] and $this->default[$name] statics exist.
+ return $this->data[$name];
+ }
+ // Neither $this->data[$name] nor $this->default[$name] static variables exist.
+ // First call with new non-NULL $name. Initialize a new static variable.
+ $this->default[$name] = $this->data[$name] = $default_value;
+ return $this->data[$name];
+ }
+
+ /**
+ * Replicates drupal_static($name, NULL, TRUE).
+ *
+ * @see drupal_static()
+ *
+ * @param string $name
+ *
+ * @return array
+ */
+ public function &resetKey($name) {
+ if (!isset($name)) {
+ throw new \InvalidArgumentException('$name cannot be NULL.');
+ }
+ // First check if dealing with a previously defined static variable.
+ if (isset($this->data[$name]) || array_key_exists($name, $this->data)) {
+ // Non-NULL $name and both $this->data[$name] and $this->default[$name] statics exist.
+ // Reset pre-existing static variable to its default value.
+ $this->data[$name] = $this->default[$name];
+ return $this->data[$name];
+ }
+ // Neither $this->data[$name] nor $this->default[$name] static variables exist.
+ // Reset was called before a default is set and yet a variable must be
+ // returned.
+ return $this->data;
+ }
+
+ /**
+ * Replicates drupal_static(NULL, NULL, TRUE).
+ *
+ * @see drupal_static()
+ *
+ * @return array
+ */
+ public function &resetAll() {
+ // Reset all: ($name == NULL). This needs to be done one at a time so that
+ // references returned by earlier invocations of drupal_static() also get
+ // reset.
+ foreach ($this->default as $name => $value) {
+ $this->data[$name] = $value;
+ }
+ // As the function returns a reference, the return should always be a
+ // variable.
+ return $this->data;
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ExampleModulesInterface.php b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ExampleModulesInterface.php
new file mode 100644
index 00000000000..21b2b9e67a1
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ExampleModulesInterface.php
@@ -0,0 +1,48 @@
+ (object)array(
+ * 'uri' => 'sites/all/modules/contrib/devel/devel.module',
+ * 'filename' => 'devel.module',
+ * 'name' => 'devel',
+ * ));
+ */
+ public function drupalSystemListingModules();
+
+ /**
+ * Replicates drupal_parse_info_file(dirname($module->uri) . '/' . $module->name . '.info')
+ *
+ * @see drupal_parse_info_file()
+ *
+ * @param string $name
+ *
+ * @return array
+ * Parsed info file contents.
+ */
+ public function drupalParseInfoFile($name);
+
+ /**
+ * @return true[]
+ */
+ public function getBootstrapModules();
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/HookSystem.php b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/HookSystem.php
new file mode 100644
index 00000000000..7a0d74097e4
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/HookSystem.php
@@ -0,0 +1,71 @@
+moduleImplements = new ModuleImplements($drupalStatic, $cache, $moduleList, $this);
+ }
+
+ /**
+ * @param string $hook
+ */
+ function moduleInvokeAll($hook) {
+ $args = func_get_args();
+ assert($hook === array_shift($args));
+ foreach ($this->moduleImplements($hook) as $extension) {
+ $function = $extension . '_' . $hook;
+ if (function_exists($function)) {
+ call_user_func_array($function, $args);
+ }
+ }
+ }
+
+ /**
+ * @param string $hook
+ * @param mixed $data
+ */
+ function drupalAlter($hook, &$data) {
+ $args = func_get_args();
+ assert($hook === array_shift($args));
+ assert($data === array_shift($args));
+ while (count($args) < 3) {
+ $args[] = NULL;
+ }
+ foreach ($this->moduleImplements($hook . '_alter') as $extension) {
+ $function = $extension . '_' . $hook . '_alter';
+ $function($data, $args[0], $args[1], $args[2]);
+ }
+ }
+
+ /**
+ * @param string $hook
+ *
+ * @throws \Exception
+ * @return array
+ */
+ function moduleImplements($hook) {
+ return $this->moduleImplements->moduleImplements($hook);
+ }
+
+ /**
+ * Resets the module_implements() cache.
+ */
+ public function moduleImplementsReset() {
+ $this->moduleImplements->reset();
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/LibrariesInfo.php b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/LibrariesInfo.php
new file mode 100644
index 00000000000..9aa0d610091
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/LibrariesInfo.php
@@ -0,0 +1,198 @@
+drupalStatic = $drupalStatic;
+ $this->hookSystem = $hookSystem;
+ }
+
+ /**
+ * @see libraries_info()
+ *
+ * @param string|null $name
+ *
+ * @return mixed
+ */
+ function &getLibrariesInfo($name = NULL) {
+ // This static cache is re-used by libraries_detect() to save memory.
+ $libraries = &$this->drupalStatic->get('libraries_info');
+
+ if (!isset($libraries)) {
+ $libraries = array();
+ // Gather information from hook_libraries_info().
+ foreach ($this->hookSystem->moduleImplements('libraries_info') as $module) {
+ foreach (PureFunctions::moduleInvoke($module, 'libraries_info') as $machine_name => $properties) {
+ $properties['module'] = $module;
+ $libraries[$machine_name] = $properties;
+ }
+ }
+
+ // Gather information from hook_libraries_info() in enabled themes.
+ // @see drupal_alter()
+ // SKIPPED
+
+ // Gather information from .info files.
+ // .info files override module definitions.
+ // SKIPPED
+
+ // Provide defaults.
+ foreach ($libraries as $machine_name => &$properties) {
+ $this->librariesInfoDefaults($properties, $machine_name);
+ }
+
+ // Allow modules to alter the registered libraries.
+ $this->hookSystem->drupalAlter('libraries_info', $libraries);
+
+ // Invoke callbacks in the 'info' group.
+ // SKIPPED
+ }
+
+ if (isset($name)) {
+ if (!empty($libraries[$name])) {
+ return $libraries[$name];
+ }
+ else {
+ $false = FALSE;
+ return $false;
+ }
+ }
+
+ return $libraries;
+ }
+
+ /**
+ * @see libraries_info_defaults()
+ *
+ * @param array $library
+ * @param string $name
+ *
+ * @return array
+ */
+ private function librariesInfoDefaults(&$library, $name) {
+ $library += array(
+ 'machine name' => $name,
+ 'name' => $name,
+ 'vendor url' => '',
+ 'download url' => '',
+ 'path' => '',
+ 'library path' => NULL,
+ 'version callback' => 'libraries_get_version',
+ 'version arguments' => array(),
+ 'files' => array(),
+ 'dependencies' => array(),
+ 'variants' => array(),
+ 'versions' => array(),
+ 'integration files' => array(),
+ 'callbacks' => array(),
+ );
+ $library['callbacks'] += array(
+ 'info' => array(),
+ 'pre-detect' => array(),
+ 'post-detect' => array(),
+ 'pre-dependencies-load' => array(),
+ 'pre-load' => array(),
+ 'post-load' => array(),
+ );
+
+ // Add our own callbacks before any others.
+ array_unshift($library['callbacks']['info'], 'libraries_prepare_files');
+ array_unshift($library['callbacks']['post-detect'], 'libraries_detect_dependencies');
+
+ return $library;
+ }
+
+ /**
+ * @see libraries_get_path()
+ *
+ * @param string $name
+ * @param string|bool $base_path
+ *
+ * @return string|bool
+ */
+ public function librariesGetPath($name, $base_path = FALSE) {
+ $libraries = &$this->drupalStatic->get('libraries_get_path');
+
+ if (!isset($libraries)) {
+ $libraries = $this->librariesGetLibraries();
+ }
+
+ $path = ($base_path ? base_path() : '');
+ if (!isset($libraries[$name])) {
+ return FALSE;
+ }
+ else {
+ $path .= $libraries[$name];
+ }
+
+ return $path;
+ }
+
+ /**
+ * @see libraries_get_libraries()
+ */
+ private function librariesGetLibraries() {
+ $searchdir = array();
+ # $profile = drupal_get_path('profile', drupal_get_profile());
+ # $config = conf_path();
+
+ // Similar to 'modules' and 'themes' directories in the root directory,
+ // certain distributions may want to place libraries into a 'libraries'
+ // directory in Drupal's root directory.
+ # $searchdir[] = 'libraries';
+
+ // Similar to 'modules' and 'themes' directories inside an installation
+ // profile, installation profiles may want to place libraries into a
+ // 'libraries' directory.
+ # $searchdir[] = "$profile/libraries";
+
+ // Always search sites/all/libraries.
+ # $searchdir[] = 'sites/all/libraries';
+
+ // Also search sites//*.
+ # $searchdir[] = "$config/libraries";
+
+ // Custom location to search
+ $searchdir[] = dirname(dirname(__DIR__)) . '/fixtures/.libraries';
+
+ // Retrieve list of directories.
+ $directories = array();
+ $nomask = array('CVS');
+ foreach ($searchdir as $dir) {
+ if (is_dir($dir) && $handle = opendir($dir)) {
+ while (FALSE !== ($file = readdir($handle))) {
+ if (!in_array($file, $nomask) && $file[0] != '.') {
+ if (is_dir("$dir/$file")) {
+ $directories[$file] = "$dir/$file";
+ }
+ }
+ }
+ closedir($handle);
+ }
+ }
+
+ return $directories;
+ }
+
+ public function resetLibrariesInfo() {
+ $this->drupalStatic->resetKey('libraries_info');
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/LibrariesLoad.php b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/LibrariesLoad.php
new file mode 100644
index 00000000000..d4a5ee891f6
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/LibrariesLoad.php
@@ -0,0 +1,337 @@
+drupalStatic = $drupalStatic;
+ $this->cache = $cache;
+ $this->librariesInfo = $librariesInfo;
+ }
+
+ /**
+ * @param string $name
+ *
+ * @see libraries_load()
+ */
+ function librariesLoad($name) {
+ $loaded = &$this->drupalStatic->get('libraries_load', array());
+
+ if (!isset($loaded[$name])) {
+ $library = $this->cache->cacheGet($name, 'cache_libraries');
+ if ($library) {
+ $library = $library->data;
+ }
+ else {
+ $library = $this->librariesDetect($name);
+ $this->cache->cacheSet($name, $library, 'cache_libraries');
+ }
+
+ // If a variant was specified, override the top-level properties with the
+ // variant properties.
+ if (isset($variant)) {
+ // Ensure that the $variant key exists, and if it does not, set its
+ // 'installed' property to FALSE by default. This will prevent the loading
+ // of the library files below.
+ $library['variants'] += array($variant => array('installed' => FALSE));
+ $library = array_merge($library, $library['variants'][$variant]);
+ }
+ // Regardless of whether a specific variant was requested or not, there can
+ // only be one variant of a library within a single request.
+ unset($library['variants']);
+
+ // Invoke callbacks in the 'pre-dependencies-load' group.
+ $this->librariesInvoke('pre-dependencies-load', $library);
+
+ // If the library (variant) is installed, load it.
+ $library['loaded'] = FALSE;
+ if ($library['installed']) {
+ // Load library dependencies.
+ if (isset($library['dependencies'])) {
+ foreach ($library['dependencies'] as $dependency) {
+ $this->librariesLoad($dependency);
+ }
+ }
+
+ // Invoke callbacks in the 'pre-load' group.
+ $this->librariesInvoke('pre-load', $library);
+
+ // Load all the files associated with the library.
+ $library['loaded'] = $this->librariesLoadFiles($library);
+
+ // Invoke callbacks in the 'post-load' group.
+ $this->librariesInvoke('post-load', $library);
+ }
+ $loaded[$name] = $library;
+ }
+
+ return $loaded[$name];
+ }
+
+ /**
+ * Tries to detect a library and its installed version.
+ *
+ * @param $name
+ * The machine name of a library to return registered information for.
+ *
+ * @return array|false
+ * An associative array containing registered information for the library
+ * specified by $name, or FALSE if the library $name is not registered.
+ * In addition to the keys returned by libraries_info(), the following keys
+ * are contained:
+ * - installed: A boolean indicating whether the library is installed. Note
+ * that not only the top-level library, but also each variant contains this
+ * key.
+ * - version: If the version could be detected, the full version string.
+ * - error: If an error occurred during library detection, one of the
+ * following error statuses: "not found", "not detected", "not supported".
+ * - error message: If an error occurred during library detection, a detailed
+ * error message.
+ *
+ * @see libraries_info()
+ * @see libraries_detect()
+ */
+ private function librariesDetect($name) {
+ // Re-use the statically cached value of libraries_info() to save memory.
+ $library = & $this->librariesInfo->getLibrariesInfo($name);
+
+ if ($library === FALSE) {
+ return $library;
+ }
+ // If 'installed' is set, library detection ran already.
+ if (isset($library['installed'])) {
+ return $library;
+ }
+
+ $library['installed'] = FALSE;
+
+ // Check whether the library exists.
+ if (!isset($library['library path'])) {
+ $library['library path'] = $this->librariesInfo->librariesGetPath($library['machine name']);
+ }
+ if ($library['library path'] === FALSE || !file_exists($library['library path'])) {
+ $library['error'] = 'not found';
+ $library['error message'] = t(
+ 'The %library library could not be found.', array(
+ '%library' => $library['name'],
+ )
+ );
+
+ return $library;
+ }
+
+ // Invoke callbacks in the 'pre-detect' group.
+ $this->librariesInvoke('pre-detect', $library);
+
+ // Detect library version, if not hardcoded.
+ if (!isset($library['version'])) {
+ // We support both a single parameter, which is an associative array, and an
+ // indexed array of multiple parameters.
+ if (isset($library['version arguments'][0])) {
+ // Add the library as the first argument.
+ $library['version'] = call_user_func_array($library['version callback'], array_merge(array($library), $library['version arguments']));
+ }
+ elseif ('libraries_get_version' === $library['version callback']) {
+ $library['version'] = $this->librariesGetVersion($library, $library['version arguments']);
+ }
+ else {
+ $library['version'] = $library['version callback']($library, $library['version arguments']);
+ }
+ if (empty($library['version'])) {
+ $library['error'] = 'not detected';
+ $library['error message'] = t(
+ 'The version of the %library library could not be detected.', array(
+ '%library' => $library['name'],
+ )
+ );
+
+ return $library;
+ }
+ }
+
+ // Determine to which supported version the installed version maps.
+ if (!empty($library['versions'])) {
+ ksort($library['versions']);
+ $version = 0;
+ foreach ($library['versions'] as $supported_version => $version_properties) {
+ if (version_compare($library['version'], $supported_version, '>=')) {
+ $version = $supported_version;
+ }
+ }
+ if (!$version) {
+ $library['error'] = 'not supported';
+ $library['error message'] = t(
+ 'The installed version %version of the %library library is not supported.', array(
+ '%version' => $library['version'],
+ '%library' => $library['name'],
+ )
+ );
+
+ return $library;
+ }
+
+ // Apply version specific definitions and overrides.
+ $library = array_merge($library, $library['versions'][$version]);
+ unset($library['versions']);
+ }
+
+ // Check each variant if it is installed.
+ if (!empty($library['variants'])) {
+ foreach ($library['variants'] as $variant_name => &$variant) {
+ // If no variant callback has been set, assume the variant to be
+ // installed.
+ if (!isset($variant['variant callback'])) {
+ $variant['installed'] = TRUE;
+ }
+ else {
+ // We support both a single parameter, which is an associative array,
+ // and an indexed array of multiple parameters.
+ if (isset($variant['variant arguments'][0])) {
+ // Add the library as the first argument, and the variant name as the second.
+ $variant['installed'] = call_user_func_array(
+ $variant['variant callback'], array_merge(
+ array(
+ $library,
+ $variant_name
+ ), $variant['variant arguments']
+ )
+ );
+ }
+ else {
+ $variant['installed'] = $variant['variant callback']($library, $variant_name, $variant['variant arguments']);
+ }
+ if (!$variant['installed']) {
+ $variant['error'] = 'not found';
+ $variant['error message'] = t(
+ 'The %variant variant of the %library library could not be found.', array(
+ '%variant' => $variant_name,
+ '%library' => $library['name'],
+ )
+ );
+ }
+ }
+ }
+ }
+
+ // If we end up here, the library should be usable.
+ $library['installed'] = TRUE;
+
+ // Invoke callbacks in the 'post-detect' group.
+ $this->librariesInvoke('post-detect', $library);
+
+ return $library;
+ }
+
+ /**
+ * Invokes library callbacks.
+ *
+ * @param string $group
+ * A string containing the group of callbacks that is to be applied. Should be
+ * either 'info', 'pre-detect', 'post-detect', or 'load'.
+ * @param array $library
+ * An array of library information, passed by reference.
+ *
+ * @see libraries_invoke()
+ */
+ private function librariesInvoke($group, &$library) {
+ foreach ($library['callbacks'][$group] as $callback) {
+ if ('libraries_detect_dependencies' === $callback) {
+ continue;
+ }
+ $this->librariesTraverseLibrary($library, $callback);
+ }
+ }
+
+ /**
+ * Helper function to apply a callback to all parts of a library.
+ *
+ * Because library declarations can include variants and versions, and those
+ * version declarations can in turn include variants, modifying e.g. the 'files'
+ * property everywhere it is declared can be quite cumbersome, in which case
+ * this helper function is useful.
+ *
+ * @param array $library
+ * An array of library information, passed by reference.
+ * @param callback $callback
+ * A string containing the callback to apply to all parts of a library.
+ *
+ * @see libraries_traverse_library()
+ */
+ private function librariesTraverseLibrary(&$library, $callback) {
+ // Always apply the callback to the top-level library.
+ $callback($library, NULL, NULL);
+
+ // Apply the callback to versions.
+ if (isset($library['versions'])) {
+ foreach ($library['versions'] as $version_string => &$version) {
+ $callback($version, $version_string, NULL);
+ // Versions can include variants as well.
+ if (isset($version['variants'])) {
+ foreach ($version['variants'] as $version_variant_name => &$version_variant) {
+ $callback($version_variant, $version_string, $version_variant_name);
+ }
+ }
+ }
+ }
+
+ // Apply the callback to variants.
+ if (isset($library['variants'])) {
+ foreach ($library['variants'] as $variant_name => &$variant) {
+ $callback($variant, NULL, $variant_name);
+ }
+ }
+ }
+
+ /**
+ * Loads a library's files.
+ *
+ * @param array $library
+ * An array of library information as returned by libraries_info().
+ *
+ * @return int
+ * The number of loaded files.
+ *
+ * @see libraries_load_files()
+ */
+ private function librariesLoadFiles($library) {
+ // Not doing anything here, since library files are not relevant for
+ // xautoload.
+ return 0;
+ }
+
+ /**
+ * @param $library
+ * @param $options
+ *
+ * @return string
+ *
+ * @see libraries_get_version()
+ */
+ private function librariesGetVersion($library, $options) {
+ return '1.0.0';
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleBuildDependencies.php b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleBuildDependencies.php
new file mode 100644
index 00000000000..9510d3ea25b
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleBuildDependencies.php
@@ -0,0 +1,190 @@
+name]['edges'] = array();
+ if (isset($file->info['dependencies']) && is_array($file->info['dependencies'])) {
+ foreach ($file->info['dependencies'] as $dependency) {
+ $dependency_data = $this->drupalParseDependency($dependency);
+ $graph[$file->name]['edges'][$dependency_data['name']] = $dependency_data;
+ }
+ }
+ }
+ $this->drupalDepthFirstSearch($graph);
+ foreach ($graph as $module => $data) {
+ $files[$module]->required_by = isset($data['reverse_paths'])
+ ? $data['reverse_paths']
+ : array();
+ $files[$module]->requires = isset($data['paths'])
+ ? $data['paths']
+ : array();
+ $files[$module]->sort = $data['weight'];
+ }
+ return $files;
+ }
+
+ /**
+ * @see drupal_depth_first_search()
+ *
+ * @param $graph
+ */
+ private function drupalDepthFirstSearch(&$graph) {
+ $state = array(
+ // The order of last visit of the depth first search. This is the reverse
+ // of the topological order if the graph is acyclic.
+ 'last_visit_order' => array(),
+ // The components of the graph.
+ 'components' => array(),
+ );
+ // Perform the actual search.
+ foreach ($graph as $start => $data) {
+ $this->drupalDepthFirstSearchRec($graph, $state, $start);
+ }
+
+ // We do such a numbering that every component starts with 0. This is useful
+ // for module installs as we can install every 0 weighted module in one
+ // request, and then every 1 weighted etc.
+ $component_weights = array();
+
+ foreach ($state['last_visit_order'] as $vertex) {
+ $component = $graph[$vertex]['component'];
+ if (!isset($component_weights[$component])) {
+ $component_weights[$component] = 0;
+ }
+ $graph[$vertex]['weight'] = $component_weights[$component]--;
+ }
+ }
+
+ /**
+ * Performs a depth-first search on a graph.
+ *
+ * @see _drupal_depth_first_search()
+ *
+ * @param array $graph
+ * A three dimensional associated graph array.
+ * @param array $state
+ * An associative array. The key 'last_visit_order' stores a list of the
+ * vertices visited. The key components stores list of vertices belonging
+ * to the same the component.
+ * @param string $start
+ * An arbitrary vertex where we started traversing the graph.
+ * @param $component
+ * The component of the last vertex.
+ */
+ function drupalDepthFirstSearchRec(&$graph, &$state, $start, &$component = NULL) {
+ // Assign new component for each new vertex, i.e. when not called recursively.
+ if (!isset($component)) {
+ $component = $start;
+ }
+ // Nothing to do, if we already visited this vertex.
+ if (isset($graph[$start]['paths'])) {
+ return;
+ }
+ // Mark $start as visited.
+ $graph[$start]['paths'] = array();
+
+ // Assign $start to the current component.
+ $graph[$start]['component'] = $component;
+ $state['components'][$component][] = $start;
+
+ // Visit edges of $start.
+ if (isset($graph[$start]['edges'])) {
+ foreach ($graph[$start]['edges'] as $end => $v) {
+ // Mark that $start can reach $end.
+ $graph[$start]['paths'][$end] = $v;
+
+ if (isset($graph[$end]['component']) && $component != $graph[$end]['component']) {
+ // This vertex already has a component, use that from now on and
+ // reassign all the previously explored vertices.
+ $new_component = $graph[$end]['component'];
+ foreach ($state['components'][$component] as $vertex) {
+ $graph[$vertex]['component'] = $new_component;
+ $state['components'][$new_component][] = $vertex;
+ }
+ unset($state['components'][$component]);
+ $component = $new_component;
+ }
+ // Only visit existing vertices.
+ if (isset($graph[$end])) {
+ // Visit the connected vertex.
+ $this->drupalDepthFirstSearchRec($graph, $state, $end, $component);
+
+ // All vertices reachable by $end are also reachable by $start.
+ $graph[$start]['paths'] += $graph[$end]['paths'];
+ }
+ }
+ }
+
+ // Now that any other subgraph has been explored, add $start to all reverse
+ // paths.
+ foreach ($graph[$start]['paths'] as $end => $v) {
+ if (isset($graph[$end])) {
+ $graph[$end]['reverse_paths'][$start] = $v;
+ }
+ }
+
+ // Record the order of the last visit. This is the reverse of the
+ // topological order if the graph is acyclic.
+ $state['last_visit_order'][] = $start;
+ }
+
+ /**
+ * @see drupal_parse_dependency()
+ *
+ * @param $dependency
+ *
+ * @return array
+ */
+ private function drupalParseDependency($dependency) {
+ // We use named subpatterns and support every op that version_compare
+ // supports. Also, op is optional and defaults to equals.
+ $p_op = '(?P!=|==|=|<|<=|>|>=|<>)?';
+ // Core version is always optional: 7.x-2.x and 2.x is treated the same.
+ $p_core = '(?:' . preg_quote('7.x') . '-)?';
+ $p_major = '(?P\d+)';
+ // By setting the minor version to x, branches can be matched.
+ $p_minor = '(?P(?:\d+|x)(?:-[A-Za-z]+\d+)?)';
+ $value = array();
+ $parts = explode('(', $dependency, 2);
+ $value['name'] = trim($parts[0]);
+ if (isset($parts[1])) {
+ $value['original_version'] = ' (' . $parts[1];
+ foreach (explode(',', $parts[1]) as $version) {
+ if (preg_match("/^\s*$p_op\s*$p_core$p_major\.$p_minor/", $version, $matches)) {
+ $op = !empty($matches['operation']) ? $matches['operation'] : '=';
+ if ($matches['minor'] == 'x') {
+ // Drupal considers "2.x" to mean any version that begins with
+ // "2" (e.g. 2.0, 2.9 are all "2.x"). PHP's version_compare(),
+ // on the other hand, treats "x" as a string; so to
+ // version_compare(), "2.x" is considered less than 2.0. This
+ // means that >=2.x and <2.x are handled by version_compare()
+ // as we need, but > and <= are not.
+ if ($op == '>' || $op == '<=') {
+ $matches['major']++;
+ }
+ // Equivalence can be checked by adding two restrictions.
+ if ($op == '=' || $op == '==') {
+ $value['versions'][] = array('op' => '<', 'version' => ($matches['major'] + 1) . '.x');
+ $op = '>=';
+ }
+ }
+ $value['versions'][] = array('op' => $op, 'version' => $matches['major'] . '.' . $matches['minor']);
+ }
+ }
+ }
+ return $value;
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleEnable.php b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleEnable.php
new file mode 100644
index 00000000000..0338e2ab263
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleEnable.php
@@ -0,0 +1,221 @@
+drupalGetFilename = $drupalGetFilename;
+ $this->hookSystem = $hookSystem;
+ $this->moduleList = $moduleList;
+ $this->systemTable = $systemTable;
+ $this->systemListReset = $systemListReset;
+ $this->systemRebuildModuleData = $systemRebuildModuleData;
+ $this->systemUpdateBootstrapStatus = $systemUpdateBootstrapStatus;
+ }
+
+ /**
+ * Simulates Drupal's module_enable()
+ *
+ * @see module_enable()
+ *
+ * @param string[] $module_list
+ * Array of module names.
+ * @param bool $enable_dependencies
+ * TRUE, if dependencies should be enabled too.
+ *
+ * @return bool
+ */
+ function moduleEnable(array $module_list, $enable_dependencies = TRUE) {
+
+ if ($enable_dependencies) {
+ $module_list = $this->moduleEnableCheckDependencies($module_list);
+ }
+
+ if (empty($module_list)) {
+ // Nothing to do. All modules already enabled.
+ return TRUE;
+ }
+
+ $modules_installed = array();
+ $modules_enabled = array();
+ foreach ($module_list as $module) {
+ if (1 == $this->systemTable->moduleGetStatus($module)) {
+ // Already installed + enabled, do nothing.
+ continue;
+ }
+ if (-1 == $this->systemTable->moduleGetSchemaVersion($module)) {
+ // Install this module.
+ $this->enableModule($module, TRUE);
+ $modules_installed[] = $module;
+ $modules_enabled[] = $module;
+ }
+ else {
+ // Enable the module.
+ $this->enableModule($module, FALSE);
+ $modules_enabled[] = $module;
+ }
+ }
+
+ // If any modules were newly installed, invoke hook_modules_installed().
+ if (!empty($modules_installed)) {
+ $this->hookSystem->moduleInvokeAll('modules_installed', $modules_installed);
+ }
+
+ // If any modules were newly enabled, invoke hook_modules_enabled().
+ if (!empty($modules_enabled)) {
+ $this->hookSystem->moduleInvokeAll('modules_enabled', $modules_enabled);
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * @param string[] $module_list
+ *
+ * @return string[]
+ * Module list with added dependencies, sorted by dependency.
+ *
+ * @throws \Exception
+ */
+ protected function moduleEnableCheckDependencies(array $module_list) {
+ // Get all module data so we can find dependencies and sort.
+ $module_data = $this->systemRebuildModuleData->systemRebuildModuleData();
+ // Create an associative array with weights as values.
+ $module_list = array_flip(array_values($module_list));
+
+ while (list($module) = each($module_list)) {
+ if (!isset($module_data[$module])) {
+ // This module is not found in the filesystem, abort.
+ throw new \Exception("Module '$module' not found.");
+ }
+ if ($module_data[$module]->status) {
+ // Skip already enabled modules.
+ unset($module_list[$module]);
+ continue;
+ }
+ $module_list[$module] = $module_data[$module]->sort;
+
+ // Add dependencies to the list, with a placeholder weight.
+ // The new modules will be processed as the while loop continues.
+ foreach (array_keys($module_data[$module]->requires) as $dependency) {
+ if (!isset($module_list[$dependency])) {
+ $module_list[$dependency] = 0;
+ }
+ }
+ }
+
+ if (!$module_list) {
+ // Nothing to do. All modules already enabled.
+ return array();
+ }
+
+ // Sort the module list by pre-calculated weights.
+ arsort($module_list);
+ return array_keys($module_list);
+ }
+
+ /**
+ * @param string $extension
+ * @param bool $install
+ *
+ * @see module_enable()
+ */
+ private function enableModule($extension, $install) {
+
+ $filename = $this->drupalGetFilename->drupalGetFilename('module', $extension);
+
+ // Include module files.
+ require_once $filename;
+ if (file_exists($install_file = dirname($filename) . '/' . $extension . '.install')) {
+ require_once $install_file;
+ }
+
+ // Update status in system table
+ $this->systemTable->moduleSetEnabled($extension);
+
+ // Clear various caches, especially hook_module_implements()
+ $this->systemListReset->systemListReset();
+ $this->moduleList->moduleList(TRUE);
+ $this->hookSystem->moduleImplementsReset();
+ $this->systemUpdateBootstrapStatus->systemUpdateBootstrapStatus();
+
+ // Update the registry to include it.
+ # registry_update();
+ // Refresh the schema to include it.
+ # drupal_get_schema(NULL, TRUE);
+ // Update the theme registry to include it.
+ # drupal_theme_rebuild();
+ // Clear entity cache.
+ # entity_info_cache_clear();
+
+ if ($install) {
+ PureFunctions::moduleInvoke($extension, 'schema');
+ $this->systemTable->moduleSetSchemaVersion($extension, 7000);
+ PureFunctions::moduleInvoke($extension, 'update_last_removed');
+ // Optional hook_install()..
+ PureFunctions::moduleInvoke($extension, 'install');
+ // Optional watchdog()
+ $this->hookSystem->moduleInvokeAll('watchdog');
+ }
+ // hook_enable()
+ PureFunctions::moduleInvoke($extension, 'enable');
+ // watchdog()
+ $this->hookSystem->moduleInvokeAll('watchdog');
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleImplements.php b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleImplements.php
new file mode 100644
index 00000000000..861614f25fd
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleImplements.php
@@ -0,0 +1,197 @@
+drupalStatic = $drupalStatic;
+ $this->cache = $cache;
+ $this->moduleList = $moduleList;
+ $this->hookSystem = $hookSystem;
+ }
+
+ /**
+ * Replicates module_implements(*, *, TRUE)
+ *
+ * @see module_implements()
+ *
+ * @return null
+ */
+ function reset() {
+
+ // Use the advanced drupal_static() pattern, since this is called very often.
+ if (!isset($this->drupalStaticFast)) {
+ $this->drupalStaticFast['implementations'] = &$this->drupalStatic->get('module_implements');
+ }
+ $implementations = &$this->drupalStaticFast['implementations'];
+
+ $implementations = array();
+ $this->cache->cacheSet('module_implements', array(), 'cache_bootstrap');
+ $this->drupalStatic->resetKey('module_hook_info');
+ $this->drupalStatic->resetKey('drupal_alter');
+ $this->cache->cacheClearAll('hook_info', 'cache_bootstrap');
+ return NULL;
+ }
+
+ /**
+ * @see module_implements()
+ *
+ * @param string $hook
+ * @param bool $sort
+ *
+ * @return array
+ */
+ function moduleImplements($hook, $sort = FALSE) {
+
+ // Use the advanced drupal_static() pattern, since this is called very often.
+ if (!isset($this->drupalStaticFast)) {
+ $this->drupalStaticFast['implementations'] = &$this->drupalStatic->get('module_implements');
+ }
+ $implementations = &$this->drupalStaticFast['implementations'];
+
+ // Fetch implementations from cache.
+ if (empty($implementations)) {
+ $cache = $this->cache->cacheGet('module_implements', 'cache_bootstrap');
+ if (FALSE === $cache) {
+ $implementations = array();
+ }
+ else {
+ $implementations = $cache->data;
+ }
+ }
+
+ if (!isset($implementations[$hook])) {
+ $implementations[$hook] = $this->discoverImplementations($hook, $sort);
+ }
+ else {
+ // @todo Change this when https://drupal.org/node/2263365 has landed in Drupal core.
+ $this->filterImplementations($implementations[$hook], $hook);
+ }
+
+ return array_keys($implementations[$hook]);
+ }
+
+ /**
+ * @param string $hook
+ * @param bool $sort
+ *
+ * @return array
+ */
+ private function discoverImplementations($hook, $sort) {
+
+ # StaticCallLog::addCall();
+
+ // The hook is not cached, so ensure that whether or not it has
+ // implementations, that the cache is updated at the end of the request.
+ $this->writeCache = TRUE;
+ $hook_info = $this->moduleHookInfo();
+ $implementations = array();
+ $list = $this->moduleList->moduleList(FALSE, FALSE, $sort);
+
+ if ('modules_enabled' === $hook) {
+ # HackyLog::logx($list);
+ }
+
+ foreach ($list as $module) {
+ $include_file = isset($hook_info[$hook]['group'])
+ && module_load_include('inc', $module, $module . '.' . $hook_info[$hook]['group']);
+ // Since module_hook() may needlessly try to load the include file again,
+ // function_exists() is used directly here.
+ if (function_exists($module . '_' . $hook)) {
+ $implementations[$module] = $include_file
+ ? $hook_info[$hook]['group']
+ : FALSE;
+ }
+ }
+
+ // Allow modules to change the weight of specific implementations but avoid
+ // an infinite loop.
+ if ($hook != 'module_implements_alter') {
+ $this->hookSystem->drupalAlter('module_implements', $implementations, $hook);
+ }
+
+ return $implementations;
+ }
+
+ /**
+ * @param array &$implementations
+ * @param string $hook
+ */
+ private function filterImplementations(&$implementations, $hook) {
+ foreach ($implementations as $module => $group) {
+ // If this hook implementation is stored in a lazy-loaded file, so include
+ // that file first.
+ if ($group) {
+ module_load_include('inc', $module, "$module.$group");
+ }
+ // It is possible that a module removed a hook implementation without the
+ // implementations cache being rebuilt yet, so we check whether the
+ // function exists on each request to avoid undefined function errors.
+ // Since module_hook() may needlessly try to load the include file again,
+ // function_exists() is used directly here.
+ if (!function_exists($module . '_' . $hook)) {
+ // Clear out the stale implementation from the cache and force a cache
+ // refresh to forget about no longer existing hook implementations.
+ unset($implementations[$module]);
+ $this->writeCache = TRUE;
+ }
+ }
+ }
+
+
+ /**
+ * Replicates module_hook_info() for some known hooks.
+ *
+ * @return array
+ * An associative array whose keys are hook names and whose values are an
+ * associative array containing a group name. The structure of the array
+ * is the same as the return value of hook_hook_info().
+ *
+ * @see hook_hook_info()
+ */
+ private function moduleHookInfo() {
+ // No core modules implement hook_hook_info().
+ return array();
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleList.php b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleList.php
new file mode 100644
index 00000000000..b00e89af175
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleList.php
@@ -0,0 +1,124 @@
+drupalGetFilename = $drupalGetFilename;
+ $this->systemList = $systemList;
+ $this->drupalStatic = $drupalStatic;
+ }
+
+ /**
+ * Replicates module_list(FALSE, FALSE, $sort, $fixed_list)
+ *
+ * @param array $fixed_list
+ * @param bool $sort
+ *
+ * @return string[]
+ */
+ function setModuleList($fixed_list, $sort = FALSE) {
+
+ foreach ($fixed_list as $name => $module) {
+ $this->drupalGetFilename->drupalSetFilename('module', $name, $module['filename']);
+ $this->list[$name] = $name;
+ }
+
+ if ($sort) {
+ return $this->moduleListSorted();
+ }
+
+ return $this->list;
+ }
+
+ /**
+ * Replicates module_list($refresh, $bootstrap_refresh, $sort, NULL)
+ *
+ * @see module_list()
+ *
+ * @param bool $refresh
+ * @param bool $bootstrap_refresh
+ * @param bool $sort
+ *
+ * @return string[]
+ */
+ function moduleList($refresh = FALSE, $bootstrap_refresh = FALSE, $sort = FALSE) {
+
+ if (empty($this->list) || $refresh) {
+ $this->list = array();
+ $sorted_list = NULL;
+ if ($refresh) {
+ // For the $refresh case, make sure that system_list() returns fresh
+ // data.
+ $this->drupalStatic->resetKey('system_list');
+ }
+ if ($bootstrap_refresh) {
+ $this->list = $this->systemList->systemListBootstrap();
+ }
+ else {
+ // Not using drupal_map_assoc() here as that requires common.inc.
+ $this->list = array_keys($this->systemList->systemListModuleEnabled());
+ $this->list = !empty($this->list)
+ ? array_combine($this->list, $this->list)
+ : array();
+ }
+ }
+
+ if ($sort) {
+ return $this->moduleListSorted();
+ }
+
+ if (count($this->list)) {
+ # HackyLog::log($this->list);
+ }
+
+ return $this->list;
+ }
+
+ /**
+ * @return string[]
+ */
+ private function moduleListSorted() {
+ if (!isset($this->moduleListSorted)) {
+ $this->moduleListSorted = $this->list;
+ ksort($this->moduleListSorted);
+ }
+ return $this->moduleListSorted;
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/PureFunctions.php b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/PureFunctions.php
new file mode 100644
index 00000000000..bb07c8587b0
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/PureFunctions.php
@@ -0,0 +1,39 @@
+exampleModules = $exampleModules;
+ $this->hookSystem = $hookSystem;
+ }
+
+ /**
+ * Scans and collects module .info data.
+ *
+ * @see _system_rebuild_module_data()
+ *
+ * @return object[]
+ */
+ public function systemBuildModuleData() {
+ // Find modules
+ $modules = $this->exampleModules->drupalSystemListingModules();
+
+ if (FALSE) {
+ // Include the installation profile in modules that are loaded.
+ $profile = 'minimal';
+ $modules[$profile] = new \stdClass();
+ $modules[$profile]->name = $profile;
+ $modules[$profile]->uri = 'profiles/' . $profile . '/' . $profile . '.profile';
+ $modules[$profile]->filename = $profile . '.profile';
+
+ // Installation profile hooks are always executed last.
+ $modules[$profile]->weight = 1000;
+ }
+ else {
+ $profile = 'NO_PROFILE';
+ }
+
+ // Set defaults for module info.
+ $defaults = array(
+ 'dependencies' => array(),
+ 'description' => '',
+ 'package' => 'Other',
+ 'version' => NULL,
+ # 'php' => DRUPAL_MINIMUM_PHP,
+ 'files' => array(),
+ 'bootstrap' => 0,
+ );
+
+ // Read info files for each module.
+ foreach ($modules as $key => $module) {
+ // The module system uses the key 'filename' instead of 'uri' so copy the
+ // value so it will be used by the modules system.
+ $modules[$key]->filename = $module->uri;
+
+ // Look for the info file.
+ $module->info = $this->exampleModules->drupalParseInfoFile($module->name);
+
+ // Skip modules that don't provide info.
+ if (empty($module->info)) {
+ unset($modules[$key]);
+ continue;
+ }
+
+ // Merge in defaults and save.
+ $modules[$key]->info = $module->info + $defaults;
+
+ // Installation profiles are hidden by default, unless explicitly specified
+ // otherwise in the .info file.
+ if ($key == $profile && !isset($modules[$key]->info['hidden'])) {
+ $modules[$key]->info['hidden'] = TRUE;
+ }
+
+ // Invoke hook_system_info_alter() to give installed modules a chance to
+ // modify the data in the .info files if necessary.
+ $type = 'module';
+ $this->hookSystem->drupalAlter('system_info', $modules[$key]->info, $modules[$key], $type);
+ }
+
+ if (isset($modules[$profile])) {
+ // The installation profile is required, if it's a valid module.
+ $modules[$profile]->info['required'] = TRUE;
+ // Add a default distribution name if the profile did not provide one. This
+ // matches the default value used in install_profile_info().
+ if (!isset($modules[$profile]->info['distribution_name'])) {
+ $modules[$profile]->info['distribution_name'] = 'Drupal';
+ }
+ }
+
+ unset($modules['NO_PROFILE']);
+
+ return $modules;
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemList.php b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemList.php
new file mode 100644
index 00000000000..1814f8eafbc
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemList.php
@@ -0,0 +1,123 @@
+cache = $cache;
+ $this->drupalGetFilename = $drupalGetFilename;
+ $this->systemListLoader = new SystemListLoader($systemTable);
+ $this->drupalStatic = $drupalStatic;
+ }
+
+ /**
+ * Replicates system_list('module_enabled').
+ *
+ * @return object[]
+ */
+ public function systemListModuleEnabled() {
+ return $this->systemList('module_enabled');
+ }
+
+ /**
+ * Replicates system_list($type), with $type !== 'bootstrap'.
+ *
+ * @see system_list()
+ *
+ * @param string $type
+ * Either 'module_enabled', 'theme' or 'filepaths'.
+ *
+ * @return object[]|array[]
+ */
+ private function systemList($type) {
+ $lists = &$this->drupalStatic->get('system_list');
+
+ if (isset($lists['module_enabled'])) {
+ return $lists[$type];
+ }
+
+ // Otherwise build the list for enabled modules and themes.
+ if ($cached = $this->cache->cacheGet('system_list', 'cache_bootstrap')) {
+ $lists = $cached->data;
+ }
+ else {
+ $lists = $this->systemListLoader->loadSystemLists();
+ $this->cache->cacheSet('system_list', $lists, 'cache_bootstrap');
+ }
+ // To avoid a separate database lookup for the filepath, prime the
+ // drupal_get_filename() static cache with all enabled modules and themes.
+ foreach ($lists['filepaths'] as $item) {
+ $this->drupalGetFilename->drupalSetFilename($item['type'], $item['name'], $item['filepath']);
+ }
+
+ return $lists[$type];
+ }
+
+ /**
+ * Replicates system_list('bootstrap')
+ *
+ * @see system_list()
+ *
+ * @return array|null
+ */
+ function systemListBootstrap() {
+ $lists = &$this->drupalStatic->get('system_list');
+
+ // For bootstrap modules, attempt to fetch the list from cache if possible.
+ // if not fetch only the required information to fire bootstrap hooks
+ // in case we are going to serve the page from cache.
+ if (isset($lists['bootstrap'])) {
+ return $lists['bootstrap'];
+ }
+
+ if ($cached = $this->cache->cacheGet('bootstrap_modules', 'cache_bootstrap')) {
+ $bootstrap_list = $cached->data;
+ }
+ else {
+ $bootstrap_list = $this->systemListLoader->fetchBootstrapSystemList();
+ $this->cache->cacheSet('bootstrap_modules', $bootstrap_list, 'cache_bootstrap');
+ }
+
+ // To avoid a separate database lookup for the filepath, prime the
+ // drupal_get_filename() static cache for bootstrap modules only.
+ // The rest is stored separately to keep the bootstrap module cache small.
+ foreach ($bootstrap_list as $module) {
+ $this->drupalGetFilename->drupalSetFilename('module', $module->name, $module->filename);
+ }
+
+ // We only return the module names here since module_list() doesn't need
+ // the filename itself.
+ return $lists['bootstrap'] = array_keys($bootstrap_list);
+ }
+
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemListLoader.php b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemListLoader.php
new file mode 100644
index 00000000000..21dead08e77
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemListLoader.php
@@ -0,0 +1,156 @@
+systemTable = $systemTable;
+ }
+
+ /**
+ * @return object[]
+ */
+ public function fetchBootstrapSystemList() {
+ $bootstrapList = array();
+ foreach ($this->systemTable->systemTableSortedObjects(NULL, 'module') as $name => $record) {
+ if (1 == $record->status && 1 == $record->bootstrap) {
+ $bootstrapList[$name] = (object)array(
+ 'name' => $record->name,
+ 'filename' => $record->filename,
+ );
+ }
+ }
+ return $bootstrapList;
+ }
+
+ /**
+ * @see system_list()
+ *
+ * @return array[]
+ */
+ public function loadSystemLists() {
+
+ $lists = array(
+ 'module_enabled' => array(),
+ 'theme' => array(),
+ 'filepaths' => array(),
+ );
+
+ // The module name (rather than the filename) is used as the fallback
+ // weighting in order to guarantee consistent behavior across different
+ // Drupal installations, which might have modules installed in different
+ // locations in the file system. The ordering here must also be
+ // consistent with the one used in module_implements().
+ foreach ($this->systemTable->systemTableSortedObjects() as $record) {
+ // Build a list of all enabled modules.
+ if ($record->type == 'module') {
+ if (1 != $record->status) {
+ continue;
+ }
+ $lists['module_enabled'][$record->name] = $record;
+ }
+ // Build a list of themes.
+ elseif ($record->type == 'theme') {
+ $lists['theme'][$record->name] = $record;
+ }
+ else {
+ continue;
+ }
+ // Build a list of filenames so drupal_get_filename can use it.
+ if ($record->status) {
+ $lists['filepaths'][] = array(
+ 'type' => $record->type,
+ 'name' => $record->name,
+ 'filepath' => $record->filename
+ );
+ }
+ }
+
+ $this->themesAddHierarchy($lists['theme']);
+
+ return $lists;
+ }
+
+ /**
+ * @param array $themes
+ */
+ private function themesAddHierarchy(array $themes) {
+ foreach ($themes as $key => $theme) {
+ if (!empty($theme->info['base theme'])) {
+ // Make a list of the theme's base themes.
+ $theme->base_themes = $this->drupalFindBaseThemes($themes, $key);
+ // Don't proceed if there was a problem with the root base theme.
+ if (!current($theme->base_themes)) {
+ continue;
+ }
+ // Determine the root base theme.
+ $base_key = key($theme->base_themes);
+ // Add to the list of sub-themes for each of the theme's base themes.
+ foreach (array_keys($theme->base_themes) as $base_theme) {
+ $themes[$base_theme]->sub_themes[$key] = $theme->info['name'];
+ }
+ // Add the base theme's theme engine info.
+ $theme->info['engine'] = isset($themes[$base_key]->info['engine'])
+ ? $themes[$base_key]->info['engine']
+ : 'theme';
+ }
+ else {
+ // A plain theme is its own engine.
+ $base_key = $key;
+ if (!isset($theme->info['engine'])) {
+ $theme->info['engine'] = 'theme';
+ }
+ }
+ // Set the theme engine prefix.
+ $theme->prefix = ($theme->info['engine'] == 'theme')
+ ? $base_key
+ : $theme->info['engine'];
+ }
+ }
+
+ /**
+ * Replicates drupal_find_base_themes()
+ *
+ * @param $themes
+ * @param $key
+ * @param array $used_keys
+ *
+ * @return array
+ */
+ private function drupalFindBaseThemes($themes, $key, $used_keys = array()) {
+ $base_key = $themes[$key]->info['base theme'];
+ // Does the base theme exist?
+ if (!isset($themes[$base_key])) {
+ return array($base_key => NULL);
+ }
+
+ $current_base_theme = array($base_key => $themes[$base_key]->info['name']);
+
+ // Is the base theme itself a child of another theme?
+ if (isset($themes[$base_key]->info['base theme'])) {
+ // Do we already know the base themes of this theme?
+ if (isset($themes[$base_key]->base_themes)) {
+ return $themes[$base_key]->base_themes + $current_base_theme;
+ }
+ // Prevent loops.
+ if (!empty($used_keys[$base_key])) {
+ return array($base_key => NULL);
+ }
+ $used_keys[$base_key] = TRUE;
+ return $this->drupalFindBaseThemes($themes, $base_key, $used_keys) + $current_base_theme;
+ }
+ // If we get here, then this is our parent theme.
+ return $current_base_theme;
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemListReset.php b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemListReset.php
new file mode 100644
index 00000000000..e844974758f
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemListReset.php
@@ -0,0 +1,39 @@
+cache = $cache;
+ $this->drupalStatic = $drupalStatic;
+ }
+
+ /**
+ * @see system_list_reset()
+ */
+ function systemListReset() {
+ $this->drupalStatic->resetKey('system_list');
+ $this->drupalStatic->resetKey('system_rebuild_module_data');
+ $this->drupalStatic->resetKey('list_themes');
+ $this->cache->cacheClearAll('bootstrap_modules', 'cache_bootstrap');
+ $this->cache->cacheClearAll('system_list', 'cache_bootstrap');
+
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemRebuildModuleData.php b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemRebuildModuleData.php
new file mode 100644
index 00000000000..b4633e64f0c
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemRebuildModuleData.php
@@ -0,0 +1,92 @@
+drupalStatic = $drupalStatic;
+ $this->moduleBuildDependencies = $moduleBuildDependencies;
+ $this->systemTable = $systemTable;
+ $this->systemBuildModuleData = $systemBuildModuleData;
+ $this->systemListReset = $systemListReset;
+ }
+
+ /**
+ * Rebuild, save, and return data about all currently available modules.
+ *
+ * @see system_rebuild_module_data()
+ *
+ * @return array[]
+ */
+ public function systemRebuildModuleData() {
+ $modules_cache = &$this->drupalStatic->get('system_rebuild_module_data');
+ // Only rebuild once per request. $modules and $modules_cache cannot be
+ // combined into one variable, because the $modules_cache variable is reset by
+ // reference from system_list_reset() during the rebuild.
+ if (!isset($modules_cache)) {
+ $modules = $this->systemBuildModuleData->systemBuildModuleData();
+ ksort($modules);
+ $this->systemTable->systemGetFilesDatabase($modules, 'module');
+ $this->systemUpdateFilesDatabase($modules, 'module');
+ $modules = $this->moduleBuildDependencies->moduleBuildDependencies($modules);
+ $modules_cache = $modules;
+ }
+ return $modules_cache;
+ }
+
+ /**
+ * @see system_update_files_database()
+ *
+ * @param object[] $files
+ * @param string $type
+ */
+ private function systemUpdateFilesDatabase($files, $type) {
+ $this->systemTable->systemUpdateFilesDatabase($files, $type);
+
+ // If any module or theme was moved to a new location, we need to reset the
+ // system_list() cache or we will continue to load the old copy, look for
+ // schema updates in the wrong place, etc.
+ $this->systemListReset->systemListReset();
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemTable.php b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemTable.php
new file mode 100644
index 00000000000..713a0272a12
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemTable.php
@@ -0,0 +1,330 @@
+addModule($module, array('filename' => $filename));
+ }
+
+ /**
+ * @param string $module
+ * @param mixed[] $data
+ *
+ * @throws \Exception
+ */
+ function addModule($module, $data) {
+ if (!isset($data['filename'])) {
+ throw new \Exception("Missing filename in module data.");
+ }
+ if ($data['filename'] !== dirname($data['filename']) . '/' . $module . '.module') {
+ throw new \Exception("Unexpected filename for module.");
+ }
+ $this->systemTableData[$module] = $data + array(
+ 'status' => 0,
+ 'bootstrap' => 0,
+ 'schema_version' => -1,
+ 'weight' => 0,
+ 'info' => NULL,
+ 'type' => 'module',
+ 'name' => $module,
+ );
+ }
+
+ /**
+ * @param string $module
+ * @param string $dir
+ *
+ * @throws \Exception
+ */
+ function moduleSetDir($module, $dir) {
+ if (!isset($this->systemTableData[$module])) {
+ throw new \Exception("Unknown module '$module'.");
+ }
+ $filename = $dir . '/' . $module . '.module';
+ $this->systemTableData[$module]['filename'] = $filename;
+ }
+
+ /**
+ * @param string $module
+ * @param string $filename
+ *
+ * @throws \Exception
+ */
+ function moduleSetFilename($module, $filename) {
+ if (!isset($this->systemTableData[$module])) {
+ throw new \Exception("Unknown module '$module'.");
+ }
+ $this->systemTableData[$module]['filename'] = $filename;
+ }
+
+ /**
+ * @param string $module
+ *
+ * @throws \Exception
+ */
+ function moduleSetEnabled($module) {
+ if (!isset($this->systemTableData[$module])) {
+ throw new \Exception("Unknown module '$module'.");
+ }
+ $this->systemTableData[$module]['status'] = 1;
+ }
+
+ /**
+ * @param string $module
+ * @param int $version
+ *
+ * @throws \Exception
+ */
+ public function moduleSetSchemaVersion($module, $version) {
+ if (!isset($this->systemTableData[$module])) {
+ throw new \Exception("Unknown module '$module'.");
+ }
+ $this->systemTableData[$module]['schema_version'] = $version;
+ }
+
+ /**
+ * @return string[]
+ * Extension type by extension name.
+ */
+ function getActiveExtensions() {
+ $activeExtensions = array();
+ foreach ($this->systemTableData as $module => $data) {
+ if (1 === $data['status']) {
+ $activeExtensions[$module] = $data['type'];
+ }
+ }
+ return $activeExtensions;
+ }
+
+ /**
+ * Load module data/status from the system table.
+ *
+ * @param $module
+ *
+ * @return array|null
+ */
+ function moduleGetData($module) {
+ if (!isset($this->systemTableData[$module])) {
+ return NULL;
+ }
+ return $this->systemTableData[$module];
+ }
+
+ /**
+ * @param string $module
+ *
+ * @return string|null
+ */
+ function moduleGetFilename($module) {
+ if (!isset($this->systemTableData[$module])) {
+ return NULL;
+ }
+ return $this->systemTableData[$module]['filename'];
+ }
+
+ /**
+ * @param string $module
+ *
+ * @return int
+ * @throws \Exception
+ */
+ function moduleGetStatus($module) {
+ if (!isset($this->systemTableData[$module])) {
+ throw new \Exception("Unknown module '$module'.");
+ }
+ return $this->systemTableData[$module]['status'];
+ }
+
+ /**
+ * @param string $module
+ *
+ * @return int
+ * @throws \Exception
+ */
+ function moduleGetSchemaVersion($module) {
+ if (!isset($this->systemTableData[$module])) {
+ throw new \Exception("Unknown module '$module'.");
+ }
+ return $this->systemTableData[$module]['schema_version'];
+ }
+
+ /**
+ * @param string[]|null $fields
+ * @param string|null $type
+ *
+ * @return array
+ */
+ public function systemTableObjects(array $fields = NULL, $type = NULL) {
+ $objects = array();
+ foreach ($this->systemTableData as $name => $record) {
+ if (NULL !== $type && $type !== $record['type']) {
+ continue;
+ }
+ $objects[$name] = $this->makeObject($record, $fields);
+ }
+ return $objects;
+ }
+
+ /**
+ * @param string[] $fields
+ * @param string|null $type
+ *
+ * @return object[]
+ */
+ public function systemTableSortedObjects(array $fields = NULL, $type = NULL) {
+ $byWeight = array();
+ foreach ($this->systemTableData as $name => $record) {
+ if (NULL !== $type && $type !== $record['type']) {
+ continue;
+ }
+ $byWeight[$record['weight']][$name] = $this->makeObject($record, $fields);
+ }
+ ksort($byWeight);
+ $sorted = array();
+ foreach ($byWeight as $records) {
+ ksort($records);
+ $sorted += $records;
+ }
+ return $sorted;
+ }
+
+ /**
+ * @param $array
+ * @param array $fields
+ *
+ * @return \stdClass
+ */
+ private function makeObject($array, array $fields = NULL) {
+ if (!isset($fields)) {
+ return (object)$array;
+ }
+ $object = new \stdClass;
+ foreach ($fields as $field) {
+ $object->$field = $array[$field];
+ }
+ return $object;
+ }
+
+ /**
+ * Retrieves the current status of an array of files in the system table.
+ *
+ * @see system_get_files_database()
+ *
+ * @param object[] $files
+ * @param string $type
+ * E.g. 'module'
+ */
+ public function systemGetFilesDatabase($files, $type) {
+ $fields = array('filename', 'name', 'type', 'status', 'schema_version', 'weight');
+ foreach ($this->systemTableObjects($fields) as $file) {
+ if ($type !== $file->type) {
+ continue;
+ }
+ if (!isset($files[$file->name]) || !is_object($files[$file->name])) {
+ continue;
+ }
+ $file->uri = $file->filename;
+ foreach ($file as $key => $value) {
+ if (!isset($files[$file->name]->$key)) {
+ $files[$file->name]->$key = $value;
+ }
+ }
+ }
+ }
+
+ /**
+ * @see system_update_files_database()
+ *
+ * @param object[] $files
+ * @param string $type
+ */
+ public function systemUpdateFilesDatabase(&$files, $type) {
+
+ // Add all files that need to be deleted to a DatabaseCondition.
+ foreach ($this->systemTableObjects(NULL, $type) as $record) {
+ if (isset($files[$record->name]) && is_object($files[$record->name])) {
+ $file = $files[$record->name];
+ // Scan remaining fields to find only the updated values.
+ foreach ($record as $key => $oldvalue) {
+ if (isset($file->$key)) {
+ $this->systemTableData[$record->name][$key] = $file->$key;
+ }
+ }
+ // Indicate that the file exists already.
+ $file->exists = TRUE;
+ }
+ else {
+ // File is not found in file system, so delete record from the system table.
+ unset($this->systemTableData[$record->name]);
+ }
+ }
+
+ // All remaining files are not in the system table, so we need to add them.
+ foreach ($files as $name => $file) {
+ if (isset($file->exists)) {
+ unset($file->exists);
+ }
+ else {
+ $this->systemTableData[$name] = array(
+ 'filename' => $file->uri,
+ 'name' => $file->name,
+ 'type' => $type,
+ 'owner' => isset($file->owner) ? $file->owner : '',
+ 'info' => $file->info,
+ 'status' => 0,
+ 'bootstrap' => 0,
+ 'schema_version' => -1,
+ 'weight' => 0,
+ );
+ $file->type = $type;
+ $file->status = 0;
+ $file->schema_version = -1;
+ }
+ }
+ }
+
+ /**
+ * @param true[] $bootstrap_modules
+ */
+ public function setBootstrapModules($bootstrap_modules) {
+ foreach ($this->systemTableData as $name => $record) {
+ $record['bootstrap'] = empty($bootstrap_modules[$name]) ? 0 : 1;
+ }
+ }
+
+ /**
+ * @param string $name
+ * @param int $weight
+ *
+ * @throws \Exception
+ */
+ public function moduleSetWeight($name, $weight) {
+ if (!isset($this->systemTableData[$name])) {
+ throw new \Exception("Unknown module '$name'.");
+ }
+ $this->systemTableData[$name]['weight'] = $weight;
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemUpdateBootstrapStatus.php b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemUpdateBootstrapStatus.php
new file mode 100644
index 00000000000..84dc6d067ba
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemUpdateBootstrapStatus.php
@@ -0,0 +1,49 @@
+hookSystem = $hookSystem;
+ $this->systemTable = $systemTable;
+ $this->systemListReset = $systemListReset;
+ }
+
+ /**
+ * @see _system_update_bootstrap_status()
+ */
+ function systemUpdateBootstrapStatus() {
+ $bootstrap_modules = array();
+ foreach (PureFunctions::bootstrapHooks() as $hook) {
+ foreach ($this->hookSystem->moduleImplements($hook) as $module) {
+ $bootstrap_modules[$module] = TRUE;
+ }
+ }
+ $this->systemTable->setBootstrapModules($bootstrap_modules);
+ // Reset the cached list of bootstrap modules.
+ $this->systemListReset->systemListReset();
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/test_1/lib/Drupal/xautoload_test_1/ExampleClass.php b/profiles/dkan/modules/contrib/xautoload/tests/test_1/lib/Drupal/xautoload_test_1/ExampleClass.php
new file mode 100644
index 00000000000..3ff9602fdfd
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/test_1/lib/Drupal/xautoload_test_1/ExampleClass.php
@@ -0,0 +1,5 @@
+ array(
+ 'page callback' => '_xautoload_test_1_json',
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ ),
+ );
+}
+
+/**
+ * Page callback for "xautoload-example/json"
+ */
+function _xautoload_test_1_json() {
+ $all = EnvironmentSnapshotMaker::getSnapshots('xautoload_test_1');
+ drupal_json_output($all);
+ exit();
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/test_2/lib/ExampleClass.php b/profiles/dkan/modules/contrib/xautoload/tests/test_2/lib/ExampleClass.php
new file mode 100644
index 00000000000..2f9ff893bb7
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/test_2/lib/ExampleClass.php
@@ -0,0 +1,3 @@
+registerModule(__FILE__);
+
+/**
+ * Implements hook_boot()
+ *
+ * This turns xautoload_test_2 into a boot module.
+ */
+function xautoload_test_2_boot() {
+ _xautoload_test_2_early_boot_observations('boot');
+}
+
+_xautoload_test_2_early_boot_observations('early');
+
+/**
+ * Test the current state, and remember it.
+ */
+function _xautoload_test_2_early_boot_observations($phase = NULL) {
+ EnvironmentSnapshotMaker::takeSnapshot('xautoload_test_2', $phase, array('xautoload_test_2_ExampleClass'));
+}
+
+/**
+ * Implements hook_menu()
+ */
+function xautoload_test_2_menu() {
+ return array(
+ 'xautoload_test_2.json' => array(
+ 'page callback' => '_xautoload_test_2_json',
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ ),
+ );
+}
+
+/**
+ * Page callback for "xautoload-example/json"
+ */
+function _xautoload_test_2_json() {
+ $all = EnvironmentSnapshotMaker::getSnapshots('xautoload_test_2');
+ drupal_json_output($all);
+ exit();
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/test_3/lib/ExampleClass.php b/profiles/dkan/modules/contrib/xautoload/tests/test_3/lib/ExampleClass.php
new file mode 100644
index 00000000000..d36ed25b3ea
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/test_3/lib/ExampleClass.php
@@ -0,0 +1,5 @@
+registerModulePsr4(__FILE__, 'lib');
+
+/**
+ * Implements hook_boot()
+ *
+ * This turns xautoload_test_2 into a boot module.
+ */
+function xautoload_test_3_boot() {
+ _xautoload_test_3_early_boot_observations('boot');
+}
+
+_xautoload_test_3_early_boot_observations('early');
+
+/**
+ * Test the current state, and remember it.
+ */
+function _xautoload_test_3_early_boot_observations($phase = NULL) {
+ EnvironmentSnapshotMaker::takeSnapshot(
+ 'xautoload_test_3',
+ $phase,
+ array('Drupal\xautoload_test_3\ExampleClass'));
+}
+
+/**
+ * Implements hook_menu()
+ */
+function xautoload_test_3_menu() {
+ return array(
+ 'xautoload_test_3.json' => array(
+ 'page callback' => '_xautoload_test_3_json',
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ ),
+ );
+}
+
+/**
+ * Page callback for "xautoload-example/json"
+ */
+function _xautoload_test_3_json() {
+ $all = EnvironmentSnapshotMaker::getSnapshots('xautoload_test_3');
+ drupal_json_output($all);
+ exit();
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/test_4/testlib/src/TestClass.php b/profiles/dkan/modules/contrib/xautoload/tests/test_4/testlib/src/TestClass.php
new file mode 100644
index 00000000000..9acaeec8a5e
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/test_4/testlib/src/TestClass.php
@@ -0,0 +1,7 @@
+addPsr4('Drupal\xautoload_test_4\testlib\\', 'testlib/src');
+}
+
+/**
+ * Implements hook_menu()
+ */
+function xautoload_test_4_menu() {
+ // Let's see if this breaks.
+ new \Drupal\xautoload_test_4\testlib\TestClass();
+}
+
+/**
+ * Implements hook_theme()
+ */
+function xautoload_test_4_theme() {
+ new \Drupal\xautoload_test_4\testlib\TestClass();
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/tests/test_5/src/FooNamespace/Foo_Class.php b/profiles/dkan/modules/contrib/xautoload/tests/test_5/src/FooNamespace/Foo_Class.php
new file mode 100644
index 00000000000..6c639400538
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/tests/test_5/src/FooNamespace/Foo_Class.php
@@ -0,0 +1,9 @@
+finder->addPsr4('Aaa\Bbb\\', 'sites/all/libraries/aaa-bbb/src');
+
+ // Or use an adapter with more powerful methods.
+ xautoload()->adapter->composerDir('sites/all/vendor/composer');
+}
+
+/**
+ * Implements hook_xautoload()
+ *
+ * Register additional classes, namespaces, autoload patterns, that are not
+ * already registered by default.
+ *
+ * @param \Drupal\xautoload\Adapter\LocalDirectoryAdapter $adapter
+ * An adapter object that can register stuff into the class loader.
+ */
+function hook_xautoload($adapter) {
+
+ // Register a namespace with PSR-0.
+ $adapter->add(
+ // Namespace of a 3rd party package included in the module directory.
+ 'Acme\GardenKit\\',
+ // Path to the 3rd party package, relative to the module directory.
+ 'shrubbery/lib');
+
+ // Register a namespace with PSR-4.
+ $adapter->absolute()->addPsr4(
+ // The namespace.
+ 'Acme\ShrubGardens\\',
+ // Absolute path to the PSR-4 base directory.
+ '/home/karnouffle/php/shrub-gardens/src');
+
+ // Scan sites/all/vendor/composer for Composer-generated autoload files, e.g.
+ // 'sites/all/vendor/composer/autoload_namespaces.php', etc.
+ $adapter->absolute()->composerDir('sites/all/vendor/composer');
+}
+
+
+/**
+ * Implements hook_libraries_info()
+ *
+ * Allows to register PSR-0 (or other) class folders for your libraries.
+ * (those things living in sites/all/libraries)
+ *
+ * The original documentation for this hook is at libraries module,
+ * libraries.api.php
+ *
+ * X Autoload extends the capabilities of this hook, by adding an "xautoload"
+ * key. This key takes a callback or closure function, which has the same
+ * signature as hook_xautoload($adapter).
+ * This means, you can use the same methods on the $api object.
+ *
+ * @return array[]
+ * Same as explained in libraries module, but with added key 'xautoload'.
+ */
+function mymodule_libraries_info() {
+
+ return array(
+ 'ruebenkraut' => array(
+ 'name' => 'Rübenkraut library',
+ 'vendor url' => 'http://www.example.com',
+ 'download url' => 'http://github.com/example/ruebenkraut',
+ 'version' => '1.0',
+ 'xautoload' => function($adapter) {
+ /**
+ * @var \Drupal\xautoload\Adapter\LocalDirectoryAdapter $adapter
+ * An adapter object that can register stuff into the class loader.
+ */
+ // Register a namespace with PSR-0 root in
+ // 'sites/all/libraries/ruebenkraut/src'.
+ $adapter->add('Rueben\Kraut\\', 'src');
+ },
+ ),
+ 'gurkentraum' => array(
+ 'name' => 'Gurkentraum library',
+ 'xautoload' => function($adapter) {
+ /** @var \Drupal\xautoload\Adapter\LocalDirectoryAdapter $adapter */
+ // Scan sites/all/libraries/ruebenkraut/composer.json to look for
+ // autoload information.
+ $adapter->composerJson('composer.json');
+ }
+ )
+ );
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/xautoload.early.inc b/profiles/dkan/modules/contrib/xautoload/xautoload.early.inc
new file mode 100644
index 00000000000..4882e8b20c7
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/xautoload.early.inc
@@ -0,0 +1,14 @@
+classFinder;
+}
+
+/**
+ * Get a service object from the registry.
+ * Services are lazy-created first time you need them.
+ *
+ * @param string $key
+ * Identifier of the service within the registry.
+ * The xautoload_ServiceFactory should have a method with the same name.
+ * The recommended way (esp if you ask your IDE) is to omit this parameter and
+ * use xautoload()->$key instead.
+ *
+ * @return Main|object
+ */
+function xautoload($key = 'main') {
+ static $service_registry;
+ static $main;
+ if (!isset($service_registry)) {
+ $service_factory = new ServiceFactory();
+ $service_registry = new ServiceContainer($service_factory);
+ $main = $service_registry->main;
+ }
+ switch ($key) {
+ case 'main':
+ return $main;
+ default:
+ // Legacy..
+ return $service_registry->get($key);
+ }
+}
+
+
+// "Private" functions.
+// -----------------------------------------------------------------------------
+
+/**
+ * Creates and registers the xautoload class loader.
+ *
+ * Registers the xautoload_ prefix and the Drupal\xautoload\\ namespace, but
+ * does not register any other Drupal-specific stuff yet. This is because this
+ * might be called from settings.php, while some parts of Drupal are not fully
+ * initialized yet.
+ */
+function _xautoload_register() {
+
+ // Check that this runs only once.
+ static $_first_run = TRUE;
+ if (!$_first_run) {
+ return;
+ }
+ $_first_run = FALSE;
+
+ // Register a temporary loader.
+ spl_autoload_register('_xautoload_autoload_temp', TRUE, TRUE);
+
+ // Some classes need to be loaded manually. Believe it!
+ LoadClassInjectedAPI::forceAutoload();
+ LoadClassGetFileInjectedApi::forceAutoload();
+ \Drupal\xautoload\Util::forceAutoload();
+
+ $finder = xautoload()->finder;
+ $finder->register();
+
+ // Register the 'Drupal\xautoload\\' namespace.
+ $finder->addPsr4('Drupal\xautoload\\', XAUTOLOAD_SRC_DIR . '/');
+
+ // Register the "xautoload_" prefix for legacy classes.
+ $finder->addPearFlat('xautoload_', __DIR__ . '/legacy/lib/');
+
+ // Unregister the temporary loader.
+ spl_autoload_unregister('_xautoload_autoload_temp');
+}
+
+/**
+ * Temporary loader callback, to avoid any module_load_include()
+ * while building the real autoloader.
+ *
+ * @param string $name
+ * Name of the class or interface we want to load.
+ *
+ * @throws \Exception
+ */
+function _xautoload_autoload_temp($name) {
+
+ if ('Drupal\xautoload\\' === substr($name, 0, 17)) {
+ // PSR-4 case
+ $file = XAUTOLOAD_SRC_DIR . '/' . str_replace('\\', '/', substr($name, 17)) . '.php';
+ require_once $file;
+ }
+ elseif ('xautoload_' === substr($name, 0, 10) && FALSE === strpos($name, '\\')) {
+ // Legacy case
+ $file = XAUTOLOAD_LIB_DIR . '/' . str_replace('_', '/', substr($name, 10)) . '.php';
+ require_once $file;
+ }
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/xautoload.emulate.inc b/profiles/dkan/modules/contrib/xautoload/xautoload.emulate.inc
new file mode 100644
index 00000000000..4843968b237
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/xautoload.emulate.inc
@@ -0,0 +1,71 @@
+system->installSetModuleWeight(-90);
+}
+
+/**
+ * Implements hook_uninstall()
+ */
+function xautoload_uninstall() {
+ variable_del(XAUTOLOAD_VARNAME_CACHE_TYPES);
+ variable_del(XAUTOLOAD_VARNAME_CACHE_LAZY);
+ variable_del(XAUTOLOAD_VARNAME_REPLACE_CORE);
+ variable_del(XAUTOLOAD_VARNAME_CACHE_PREFIX);
+
+ // The following variable is a leftover from previous versions.
+ variable_del('xautoload_cache_mode');
+}
+
+/**
+ * Implements hook_update_N()
+ */
+function xautoload_update_7000() {
+ // Set module weight for xautoload to run before other modules.
+ db_query("UPDATE {system} SET weight = -90 WHERE name = 'xautoload' AND type = 'module'");
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/xautoload.libraries.inc b/profiles/dkan/modules/contrib/xautoload/xautoload.libraries.inc
new file mode 100644
index 00000000000..f6a97fb6864
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/xautoload.libraries.inc
@@ -0,0 +1,15 @@
+librariesInfoAlter->librariesInfoAlter($info);
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/xautoload.module b/profiles/dkan/modules/contrib/xautoload/xautoload.module
new file mode 100644
index 00000000000..c20584408f3
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/xautoload.module
@@ -0,0 +1,198 @@
+phaseControl->enterMainPhase();
+}
+
+/**
+ * Implements hook_init()
+ *
+ * Note:
+ * This is a first step to allow modules to register foreign namespaces.
+ * We will probably change this, to allow bootstrap modules to register their
+ * namespaces earlier in the request.
+ * We might also find a solution to cache the result of this hook between
+ * requests. This would require a different implementation of the InjectedAPI,
+ * which would no longer have a direct reference to the finder object.
+ */
+function xautoload_init() {
+ xautoload()->phaseControl->enterMainPhase();
+}
+
+/**
+ * Implements hook_system_theme_info().
+ *
+ * This is the first hook to fire on update.php.
+ *
+ * Unfortunately, hook_custom_theme() and hook_init() are not called on
+ * update.php in _drupal_bootstrap_full().
+ *
+ * But in list_themes(), _system_rebuild_theme_data() is always called in
+ * maintenance mode. And from there, hook_system_theme_info().
+ *
+ * @see _drupal_bootstrap_full()
+ * @see list_themes()
+ */
+function xautoload_system_theme_info() {
+ xautoload()->phaseControl->enterMainPhase();
+}
+
+/**
+ * Implements hook_module_implements_alter()
+ *
+ * @param array &$implementations
+ * @param string $hook
+ */
+function xautoload_module_implements_alter(&$implementations, $hook) {
+
+ // Check if new modules have been enabled.
+ if ('boot' === $hook) {
+
+ # \Drupal\xautoload\Tests\HackyLog::log($hook);
+ // hook_module_implements_alter('boot') gets called (indirectly) from
+ // _system_update_bootstrap_status(), which happens each time a new module
+ // is enabled.
+ xautoload()->phaseControl->checkNewExtensions();
+ }
+
+ // Most hook implementations are in dedicated files.
+ switch ($hook) {
+ case 'init':
+ case 'custom_theme':
+ case 'system_theme_info':
+ // Move xautoload_$hook() to the start.
+ $implementations = array('xautoload' => FALSE) + $implementations;
+ break;
+ case 'form_system_performance_settings_alter':
+ // Specify that the implementation lives in xautoload.ui.inc.
+ $implementations['xautoload'] = 'ui';
+ require_once __DIR__ . '/xautoload.ui.inc';
+ break;
+ case 'modules_enabled':
+ case 'registry_files_alter':
+ // Move xautoload_$hook() to the start, and specify that the
+ // implementation lives in xautoload.system.inc.
+ $implementations = array('xautoload' => 'system') + $implementations;
+ require_once __DIR__ . '/xautoload.system.inc';
+ break;
+ case 'libraries_info_alter':
+ $implementations['xautoload'] = 'libraries';
+ require_once __DIR__ . '/xautoload.libraries.inc';
+ break;
+ default:
+ return;
+ }
+}
+
+
+// "Private" functions.
+// -----------------------------------------------------------------------------
+
+/**
+ * Registers Drupal-related namespaces and prefixes in the xautoload loader, and
+ * activates the APC (or similar) cache, if enabled.
+ */
+function _xautoload_register_drupal() {
+
+ // Check that this runs only once.
+ static $_first_run = TRUE;
+ if (!$_first_run) {
+ return;
+ }
+ $_first_run = FALSE;
+
+ // Register the class loader itself.
+ require_once __DIR__ . '/xautoload.early.lib.inc';
+ _xautoload_register();
+
+ $services = xautoload()->getServiceContainer();
+
+ if ($services->system->variableGet(XAUTOLOAD_VARNAME_REPLACE_CORE)) {
+ /**
+ * Completely take over.
+ *
+ * @see _drupal_bootstrap_database()
+ * @see drupal_autoload_class()
+ * @see drupal_autoload_interface()
+ */
+ spl_autoload_unregister('drupal_autoload_class');
+ spl_autoload_unregister('drupal_autoload_interface');
+ }
+
+ $lazy = $services->system->variableGet(XAUTOLOAD_VARNAME_CACHE_LAZY, FALSE);
+ $decorated = $lazy
+ ? $services->proxyFinder
+ : $services->proxyFinder->getFinder()
+ ;
+
+ // Activate a cache, if available and enabled.
+ $cache_types = $services->system->variableGet(XAUTOLOAD_VARNAME_CACHE_TYPES, array());
+ if (!empty($cache_types['apcu_q']) && extension_loaded('apcu') && function_exists('apcu_store')) {
+ $cached_loader = ApcuQueuedCachedClassLoader::create($decorated, $services->cacheManager);
+ }
+ elseif (!empty($cache_types['apc']) && extension_loaded('apc') && function_exists('apc_store')) {
+ $cached_loader = ApcClassLoader::create($decorated, $services->cacheManager);
+ }
+ /** @noinspection NotOptimalIfConditionsInspection */
+ elseif (!empty($cache_types['apcu']) && extension_loaded('apcu') && function_exists('apcu_store')) {
+ $cached_loader = ApcuClassLoader::create($decorated, $services->cacheManager);
+ }
+ elseif (!empty($cache_types['wincache']) && extension_loaded('wincache') && function_exists('wincache_ucache_get')) {
+ $cached_loader = WinCacheClassLoader::create($decorated, $services->cacheManager);
+ }
+ elseif (!empty($cache_types['xcache']) && extension_loaded('Xcache') && function_exists('xcache_get')) {
+ $cached_loader = XCacheClassLoader::create($decorated, $services->cacheManager);
+ }
+ elseif (!empty($cache_types['dbcache'])) {
+ $cached_loader = DbCacheClassLoader::create($decorated, $services->cacheManager);
+ }
+
+ if (isset($cached_loader)) {
+ if ($lazy) {
+ $decorated->observeFirstCacheMiss(new CacheMissLoaderSetFinder($cached_loader));
+ }
+ $cached_loader->register();
+ $services->finder->unregister();
+ }
+ else {
+ // No cache is active.
+ // Initialize the finder, to fire scheduled operations.
+ $services->proxyFinder->getFinder();
+ }
+
+ // Register prefixes and namespaces for enabled extensions.
+ $services->proxyFinder->observeFirstCacheMiss($services->phaseControl);
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/xautoload.system.inc b/profiles/dkan/modules/contrib/xautoload/xautoload.system.inc
new file mode 100644
index 00000000000..b37df33c56f
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/xautoload.system.inc
@@ -0,0 +1,41 @@
+phaseControl->modulesEnabled($modules);
+}
+
+/**
+ * Implements hook_registry_files_alter()
+ *
+ * Support wildcard syntax in the files[] setting in your module's info file.
+ * See https://drupal.org/node/1976198
+ *
+ * This function will remove entries like foo/inc/**, and instead add all the
+ * individual class files found in the foo/inc/ folder.
+ *
+ * @param array[] &$files
+ * List of files specified in files[] array in module info files.
+ * Format:
+ *
+ * $files['modules/field/field.attach.inc'] = array(
+ * 'module' => 'field',
+ * 'weight' => 0,
+ * );
+ * // Wildcard syntax.
+ * $files['sites/all/modules/foo/inc/**'] = array(
+ * 'module' => 'foo',
+ * 'weight' => 0,
+ * );
+ */
+function xautoload_registry_files_alter(&$files) {
+ $file_finder = new WildcardFileFinder();
+ $file_finder->addDrupalPaths($files);
+ $files = $file_finder->getDrupalFiles();
+}
diff --git a/profiles/dkan/modules/contrib/xautoload/xautoload.ui.inc b/profiles/dkan/modules/contrib/xautoload/xautoload.ui.inc
new file mode 100644
index 00000000000..5f9aca83320
--- /dev/null
+++ b/profiles/dkan/modules/contrib/xautoload/xautoload.ui.inc
@@ -0,0 +1,89 @@
+ 'fieldset',
+ '#title' => t('X Autoload'),
+ );
+
+ $cache_default_value = variable_get(XAUTOLOAD_VARNAME_CACHE_TYPES, array());
+
+ $cache_status = array(
+ 'apcu_q' => $apcu_status = (extension_loaded('apcu') && function_exists('apcu_store')),
+ 'apc' => (extension_loaded('apc') && function_exists('apc_store')),
+ 'apcu' => $apcu_status,
+ 'wincache' => (extension_loaded('WinCache') && function_exists('wincache_ucache_get')),
+ 'xcache' => (extension_loaded('Xcache') && function_exists('xcache_get')),
+ 'dbcache' => TRUE,
+ );
+ $cache_names = array(
+ 'apcu_q' => t('Self-updating APCu classmap (*)'),
+ 'apc' => 'APC',
+ 'apcu' => 'APCu',
+ 'wincache' => 'WinCache',
+ 'xcache' => 'XCache',
+ 'dbcache' => t('Self-updating database classmap (*)'),
+ );
+ $options = $cache_names;
+ $options_descriptions = array();
+ $options['dbcache'] = l($options['dbcache'], 'https://www.drupal.org/node/2451261');
+ $active_cache_key = NULL;
+ $active_cache_name = t('No cache.');
+ foreach ($cache_names as $key => $title) {
+ $status = $cache_status[$key];
+ if (!isset($active_cache_key) && $status && !empty($cache_default_value[$key])) {
+ $active_cache_key = $key;
+ $active_cache_name = $title;
+ }
+ }
+ foreach ($options as $key => $title) {
+ if ($cache_status[$key]) {
+ $options[$key] .= ' (' . t('Running and available') . ')';
+ }
+ else {
+ $options[$key] .= ' (' . t('Not currently available') . ')';
+ $options[$key] = '' . $options[$key] . '';
+ }
+ }
+
+ $form['xautoload'][XAUTOLOAD_VARNAME_CACHE_TYPES] = array(
+ /* @see system_element_info() */
+ '#type' => 'checkboxes',
+ '#title' => t('Cache mode'),
+ '#default_value' => $cache_default_value,
+ '#options' => $options,
+ '#options_descriptions' => $options_descriptions,
+ '#description' => ''
+ . '
' . t('X Autoload will pick the first cache mode that is available and enabled.')
+ . ' ' . t('Currently, this is:') . ' ' . $active_cache_name . '.'
+ . '
' . t('It is usually safe to enable all these checkboxes, so xautoload can always use the best cache mode available on your system.')
+ . ' ' . t('This also makes it easier to sync these settings between environments, where different PHP extensions might be installed.')
+ . '
'
+ . '
(*) ' . t('The "Self-updating [_] classmap" cache types require more than one request until they are "hot", but may bring higher performance.')
+ . '
',
+ );
+
+ $form['xautoload'][XAUTOLOAD_VARNAME_CACHE_LAZY] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Postpone registration of module namespaces until the first cache miss (recommended).'),
+ '#default_value' => variable_get(XAUTOLOAD_VARNAME_CACHE_LAZY, FALSE),
+ '#description' => t('This should speed up the bootstrap of xautoload.'),
+ );
+
+ $form['xautoload'][XAUTOLOAD_VARNAME_REPLACE_CORE] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Replace core class loader.'),
+ '#default_value' => variable_get(XAUTOLOAD_VARNAME_REPLACE_CORE, FALSE),
+ '#description' => t('Lets xautoload replace Drupal\'s drupal_autoload_class() and drupal_autoload_interface().')
+ . ' ' . t('Improves performance, if at least one of the cache options is active and enabled.')
+ . ' ' . t('This features is quite new. Please report any problems in the xautoload issue queue on drupal.org.'),
+ );
+}
diff --git a/profiles/dkan/modules/dkan/dkan_data_dashboard/dkan_data_dashboard.info b/profiles/dkan/modules/dkan/dkan_data_dashboard/dkan_data_dashboard.info
index 7be2731f128..290cf4342ac 100644
--- a/profiles/dkan/modules/dkan/dkan_data_dashboard/dkan_data_dashboard.info
+++ b/profiles/dkan/modules/dkan/dkan_data_dashboard/dkan_data_dashboard.info
@@ -30,4 +30,4 @@ features[variable][] = panelizer_node:data_dashboard_default
features[views_view][] = data_dashboards
features[views_view][] = front_page_dashboards_list
features_exclude[dependencies][dkan_topics] = dkan_topics
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_data_story/dkan_data_story.info b/profiles/dkan/modules/dkan/dkan_data_story/dkan_data_story.info
index 3d9ec22cee6..f7e822b4c92 100644
--- a/profiles/dkan/modules/dkan/dkan_data_story/dkan_data_story.info
+++ b/profiles/dkan/modules/dkan/dkan_data_story/dkan_data_story.info
@@ -53,4 +53,4 @@ features_exclude[dependencies][image] = image
features_exclude[dependencies][strongarm] = strongarm
features_exclude[dependencies][taxonomy] = taxonomy
no autodetect = 1
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_dataset/dkan_dataset.info b/profiles/dkan/modules/dkan/dkan_dataset/dkan_dataset.info
index b4996305719..8d083a4a961 100644
--- a/profiles/dkan/modules/dkan/dkan_dataset/dkan_dataset.info
+++ b/profiles/dkan/modules/dkan/dkan_dataset/dkan_dataset.info
@@ -33,4 +33,4 @@ features[ctools][] = views:views_default:3.0
features[features_api][] = api:2
features[variable][] = pathauto_node_dataset_pattern
features[variable][] = pathauto_node_resource_pattern
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_dataset/dkan_dataset.module b/profiles/dkan/modules/dkan/dkan_dataset/dkan_dataset.module
index d9cd5c7a2c5..90a4d7ec87e 100644
--- a/profiles/dkan/modules/dkan/dkan_dataset/dkan_dataset.module
+++ b/profiles/dkan/modules/dkan/dkan_dataset/dkan_dataset.module
@@ -135,7 +135,7 @@ function dkan_dataset_node_view($node, $view_mode, $langcode) {
'#theme' => 'dkan_dataset_modified_date_view',
'#node' => $node,
'#enabled' => TRUE,
- '#title' => t('Modified Date'),
+ '#title' => t('Modified'),
'#label_display' => 'above',
'#items' => array(),
'#field_name' => '',
@@ -199,7 +199,7 @@ function dkan_dataset_field_extra_fields() {
'weight' => 0,
),
'modified_date' => array(
- 'label' => t('Modified Date'),
+ 'label' => t('Modified'),
'description' => t('Node changed date available as a field'),
'weight' => 0,
),
@@ -221,7 +221,7 @@ function dkan_dataset_field_extra_fields() {
'weight' => 0,
),
'modified_date' => array(
- 'label' => t('Modified Date'),
+ 'label' => t('Modified'),
'description' => t('Node changed date available as a field'),
'weight' => 0,
),
@@ -1063,7 +1063,7 @@ function dkan_dataset_preview_url_cartodb($node) {
$resource_file_url = file_create_url($resource_wrapper->field_upload->value()->uri);
}
elseif ($resource_wrapper->field_link_remote_file->value()) {
- $resource_file_url = $resource_wrapper->field_link_remote_file->value()->uri;
+ $resource_file_url = $resource_wrapper->field_link_remote_file->value()['uri'];
}
if ($resource_file_url) {
return sprintf($pattern, $resource_file_url);
@@ -1268,7 +1268,7 @@ function _dkan_dataset_format($text) {
}
/**
- * Return node modified date.
+ * Return node created date.
*
* For harvested nodes use the value from 'field_harvest_source_issued' as
* filled by the dkan_harvest. Fallback to the 'changed' node property
@@ -1292,11 +1292,10 @@ function dkan_dataset_release_date($node) {
}
/**
- * Return node modified date with ISO 8601 format.
+ * Return node modified value with ISO 8601 format.
*
* For harvested nodes use the value from 'field_harvest_source_modified' as
- * filled by the dkan_harvest. Fallback to the 'changed' node property
- * otherwise.
+ * filled by the dkan_harvest. Otherwise fallback to the node 'changed' property.
*/
function dkan_dataset_modified_date($node) {
$node = ($node instanceof EntityMetadataWrapper) ? $node :
@@ -1304,7 +1303,23 @@ function dkan_dataset_modified_date($node) {
if (isset($node->field_harvest_source_modified) &&
!empty($node->field_harvest_source_modified->value())) {
- $date = format_date($node->field_harvest_source_modified->value(), 'iso_8601_date');
+ $d = dkan_dataset_validate_date($node->field_harvest_source_modified->value()) ? true : false;
+ // If the harvest source modified value is a date, display it.
+ if ($d) {
+ $date = $node->field_harvest_source_modified->value();
+ }
+ // If the harvest source modified value is a repeating interval, decode it.
+ else {
+ try {
+ $r = substr($node->field_harvest_source_modified->value(), 2);
+ $date = dkan_dataset_modified_date_interval($r);
+ }
+ catch (Exception $e) {
+ $date = format_date($node->changed->value(), 'iso_8601_date');
+ $message = t('Invalid harvest modified value in %nid', array('%nid' => $node->label()));
+ watchdog('dkan_dataset', $message);
+ }
+ }
}
else {
$date = format_date($node->changed->value(), 'iso_8601_date');
@@ -1358,3 +1373,69 @@ function dkan_dataset_field_formatter_view($entity_type, $entity, $field, $insta
return $element;
}
+
+/**
+ * Helper function to validate dates.
+ */
+function dkan_dataset_validate_date($date) {
+ // Check for partial date elements.
+ $parts = explode("-",$date);
+ $count = count($parts);
+ switch ($count) {
+ case '1':
+ $format = 'Y';
+ break;
+
+ case '2':
+ $format = 'Y-m';
+ break;
+
+ default:
+ $format = 'Y-m-d';
+ break;
+ }
+ $d = DateTime::createFromFormat($format, $date);
+ return $d && $d->format($format) === $date;
+}
+
+/**
+ * Helper function to convert ISO 8601 repeating interval values into plain text.
+ */
+function dkan_dataset_modified_date_interval($int) {
+ $interval = new DateInterval($int);
+ $doPlural = function($nb,$str){return $nb>1?$str.'s':$str;};
+
+ $format = array();
+ if($interval->y !== 0) {
+ $format[] = "%y ".$doPlural($interval->y, "year");
+ }
+ if($interval->m !== 0) {
+ $format[] = "%m ".$doPlural($interval->m, "month");
+ }
+ if($interval->d !== 0) {
+ $format[] = "%d ".$doPlural($interval->d, "day");
+ }
+ if($interval->h !== 0) {
+ $format[] = "%h ".$doPlural($interval->h, "hour");
+ }
+ if($interval->i !== 0) {
+ $format[] = "%i ".$doPlural($interval->i, "minute");
+ }
+ if($interval->s !== 0) {
+ if(!count($format)) {
+ return "less than a minute ago";
+ } else {
+ $format[] = "%s ".$doPlural($interval->s, "second");
+ }
+ }
+
+ // We use the two biggest parts
+ if(count($format) > 1) {
+ $format = array_shift($format)." and ".array_shift($format);
+ } else {
+ $format = array_pop($format);
+ }
+
+ return 'Every ' . $interval->format($format);
+
+}
diff --git a/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.features.field_base.inc b/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.features.field_base.inc
index d4f5dba5fa2..23f39b2e6e8 100644
--- a/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.features.field_base.inc
+++ b/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.features.field_base.inc
@@ -273,31 +273,11 @@ function dkan_dataset_content_types_field_default_field_bases() {
'locked' => 0,
'module' => 'list',
'settings' => array(
- 'allowed_values' => array(
- 0 => 'Daily',
- 1 => 'Weekly',
- 2 => 'Monthly',
- 3 => 'Annually',
- 4 => 'Continuously',
- 5 => 'Irregularly',
- 6 => 'Decennial',
- 7 => 'Quadrennial',
- 8 => 'Bimonthly',
- 9 => 'Semiweekly',
- 10 => 'Biweekly',
- 11 => 'Semiannual',
- 12 => 'Biennial',
- 13 => 'Triennial',
- 14 => 'Three times a week',
- 15 => 'Three times a month',
- 16 => 'Quarterly',
- 17 => 'Three times a year',
- 18 => 'Semimonthly',
- ),
- 'allowed_values_function' => '',
+ 'allowed_values' => array(),
+ 'allowed_values_function' => 'dkan_dataset_content_types_iso_frecuency_map',
),
'translatable' => 0,
- 'type' => 'list_integer',
+ 'type' => 'list_text',
);
// Exported field_base: 'field_granularity'.
@@ -328,26 +308,18 @@ function dkan_dataset_content_types_field_default_field_bases() {
'deleted' => 0,
'entity_types' => array(),
'field_name' => 'field_harvest_source_modified',
- 'indexes' => array(),
+ 'indexes' => array(
+ 'format' => array(
+ 0 => 'format',
+ ),
+ ),
'locked' => 0,
- 'module' => 'date',
+ 'module' => 'text',
'settings' => array(
- 'cache_count' => 4,
- 'cache_enabled' => 0,
- 'granularity' => array(
- 'day' => 'day',
- 'hour' => 'hour',
- 'minute' => 'minute',
- 'month' => 'month',
- 'second' => 0,
- 'year' => 'year',
- ),
- 'timezone_db' => '',
- 'todate' => '',
- 'tz_handling' => 'none',
+ 'max_length' => 255,
),
'translatable' => 0,
- 'type' => 'datetime',
+ 'type' => 'text',
);
// Exported field_base: 'field_harvest_source_issued'.
diff --git a/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.features.field_instance.inc b/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.features.field_instance.inc
index cfa7abb0aad..1e4dcdebed6 100644
--- a/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.features.field_instance.inc
+++ b/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.features.field_instance.inc
@@ -621,14 +621,16 @@ function dkan_dataset_content_types_field_default_field_instances() {
// Exported field_instance: 'node-dataset-field_harvest_source_modified'.
$field_instances['node-dataset-field_harvest_source_modified'] = array(
'bundle' => 'dataset',
+ 'default_value' => NULL,
'deleted' => 0,
'description' => '',
'display' => array(
'default' => array(
'label' => 'hidden',
+ 'module' => 'text',
'settings' => array(),
'type' => 'hidden',
- 'weight' => 9,
+ 'weight' => 15,
),
'search_result' => array(
'label' => 'above',
@@ -648,25 +650,16 @@ function dkan_dataset_content_types_field_default_field_instances() {
'label' => 'Harvest Source Modified',
'required' => 0,
'settings' => array(
- 'default_value' => 'blank',
- 'default_value2' => 'same',
- 'default_value_code' => '',
- 'default_value_code2' => '',
+ 'text_processing' => 0,
'user_register_form' => FALSE,
),
'widget' => array(
'active' => 1,
- 'module' => 'date',
+ 'module' => 'text',
'settings' => array(
- 'increment' => 15,
- 'input_format' => 'm/d/Y - H:i:s',
- 'input_format_custom' => '',
- 'label_position' => 'above',
- 'no_fieldset' => 0,
- 'text_parts' => array(),
- 'year_range' => '-3:+3',
+ 'size' => 60,
),
- 'type' => 'date_text',
+ 'type' => 'text_textfield',
'weight' => 32,
),
);
diff --git a/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.info b/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.info
index 5a8947c433f..a24ebe6bc0e 100644
--- a/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.info
+++ b/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.info
@@ -125,4 +125,4 @@ features_exclude[dependencies][og] = og
features_exclude[dependencies][dkan_featured_topics] = dkan_featured_topics
features_exclude[field_base][og_group_ref] = og_group_ref
features_exclude[field_instance][node-dataset-og_group_ref] = node-dataset-og_group_ref
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.license_field.inc b/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.license_field.inc
index befca24cfe0..034b5663739 100644
--- a/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.license_field.inc
+++ b/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.license_field.inc
@@ -65,19 +65,19 @@ function dkan_dataset_content_types_license_subscribe() {
return array(
"cc-by" => array(
"label" => t("Creative Commons Attribution"),
- "uri" => "http://opendefinition.org/licenses/cc-by/",
+ "uri" => "https://creativecommons.org/licenses/by/4.0/",
),
"cc-by-sa" => array(
"label" => t("Creative Commons Attribution Share-Alike"),
- "uri" => "http://opendefinition.org/licenses/cc-by-sa/",
+ "uri" => "https://creativecommons.org/licenses/by-sa/4.0/",
),
"cc-zero" => array(
"label" => t("Creative Commons CCZero"),
- "uri" => "http://opendefinition.org/licenses/cc-zero/",
+ "uri" => "https://creativecommons.org/publicdomain/zero/1.0/",
),
"cc-nc" => array(
- "label" => t("Creative Commons Non-Commercial (Any)"),
- "uri" => "http://opendefinition.org/licenses/cc-nc/",
+ "label" => t("Creative Commons Non-Commercial (2.5)"),
+ "uri" => "https://creativecommons.org/licenses/by-nc/2.5/",
),
"cc-by-nc-nd" => array(
"label" => t("Attribution NonCommercial NoDerivatives 4.0 International"),
@@ -85,15 +85,15 @@ function dkan_dataset_content_types_license_subscribe() {
),
"gfdl" => array(
"label" => t("GNU Free Documentation License"),
- "uri" => "http://opendefinition.org/licenses/gfdl/",
+ "uri" => "https://www.gnu.org/licenses/fdl.html",
),
"odc-by" => array(
"label" => t("Open Data Commons Attribution License"),
- "uri" => "http://opendefinition.org/licenses/odc-by/",
+ "uri" => "https://opendatacommons.org/licenses/by/1.0/",
),
"odc-odbl" => array(
"label" => t("Open Data Commons Open Database License (ODbL)"),
- "uri" => "http://opendefinition.org/licenses/odc-odbl/",
+ "uri" => "https://opendatacommons.org/licenses/odbl/1.0/",
),
"odc-pddl" => array(
"label" => t("Open Data Commons Public Domain Dedication and Licence (PDDL)"),
diff --git a/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.module b/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.module
index 1fb6693ff53..8f710a9ff8c 100644
--- a/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.module
+++ b/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.module
@@ -210,30 +210,31 @@ function dkan_dataset_content_types_preprocess_field(&$vars) {
/**
* Map of iso frequency ranges to field frequency allowed values.
+ * https://project-open-data.cio.gov/iso8601_guidance/#accrualperiodicity.
*/
function dkan_dataset_content_types_iso_frecuency_map() {
return array(
- 'R/P10Y' => 6,
- 'R/P4Y' => 7,
- 'R/P1Y' => 3,
- 'R/P2M' => 8,
- 'R/P0.5M' => 8,
- 'R/P3.5D' => 9,
- 'R/P1D' => 0,
- 'R/P2W' => 10,
- 'R/P0.5W' => 10,
- 'R/P6M' => 11,
- 'R/P2Y' => 12,
- 'R/P3Y' => 13,
- 'R/P0.33W' => 14,
- 'R/P0.33M' => 15,
- 'R/PT1S' => 4,
- 'R/P1M' => 2,
- 'R/P3M' => 16,
- 'R/P0.5M' => 18,
- 'R/P4M' => 17,
- 'R/P1W' => 1,
- 'irregular' => 5,
+ 'R/PT1H' => t('Hourly'),
+ 'R/P1D' => t('Daily'),
+ 'R/P1W' => t('Weekly'),
+ 'R/P1M' => t('Monthly'),
+ 'R/P1Y' => t('Annually'),
+ 'R/PT1S' => t('Continuously'),
+ 'irregular' => t('Irregularly'),
+ 'R/P10Y' => t('Decennial'),
+ 'R/P4Y' => t('Quadrennial'),
+ 'R/P2M' => t('Bimonthly'),
+ 'R/P0.5M' => t('Semimonthly'),
+ 'R/P3.5D' => t('Semiweekly'),
+ 'R/P2W' => t('Biweekly'),
+ 'R/P0.5W' => t('Biweekly'),
+ 'R/P6M' => t('Semiannual'),
+ 'R/P2Y' => t('Biennial'),
+ 'R/P3Y' => t('Triennial'),
+ 'R/P0.33W' => t('Three times a week'),
+ 'R/P0.33M' => t('Three times a month'),
+ 'R/P4M' => t('Three times a year'),
+ 'R/P3M' => t('Quarterly'),
);
}
diff --git a/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_groups/dkan_dataset_groups.info b/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_groups/dkan_dataset_groups.info
index 67baa099a4c..7acea7de874 100644
--- a/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_groups/dkan_dataset_groups.info
+++ b/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_groups/dkan_dataset_groups.info
@@ -74,4 +74,4 @@ features[views_view][] = front_page_group_grid
features[views_view][] = front_page_group_list
features[views_view][] = group_block
features[views_view][] = groups_page
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_groups/modules/dkan_dataset_groups_perms/dkan_dataset_groups_perms.info b/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_groups/modules/dkan_dataset_groups_perms/dkan_dataset_groups_perms.info
index 1fb247adb3c..73a144f61e8 100644
--- a/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_groups/modules/dkan_dataset_groups_perms/dkan_dataset_groups_perms.info
+++ b/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_groups/modules/dkan_dataset_groups_perms/dkan_dataset_groups_perms.info
@@ -32,4 +32,4 @@ features[og_features_permission][] = node:group:update own dataset content
features[og_features_permission][] = node:group:update own resource content
features[og_features_permission][] = node:group:view any unpublished dataset content
features[og_features_permission][] = node:group:view any unpublished resource content
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_rest_api/dkan_dataset_rest_api.info b/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_rest_api/dkan_dataset_rest_api.info
index bf76cf87c96..f2a8c62a3d2 100644
--- a/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_rest_api/dkan_dataset_rest_api.info
+++ b/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_rest_api/dkan_dataset_rest_api.info
@@ -1,13 +1,13 @@
name = DKAN Dataset REST API
-description = "REST APIs for DKAN Datasets"
+description = REST APIs for DKAN Datasets
core = 7.x
package = DKAN
dependencies[] = ctools
dependencies[] = dkan_dataset
+dependencies[] = dkan_datastore
dependencies[] = rest_server
dependencies[] = services
features[ctools][] = services:services:3
features[features_api][] = api:2
features[services_endpoint][] = dkan_dataset_api
-mtime = 1417998121
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_rest_api/dkan_dataset_rest_api.module b/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_rest_api/dkan_dataset_rest_api.module
index e6e64c6255f..fb68fb6dc04 100644
--- a/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_rest_api/dkan_dataset_rest_api.module
+++ b/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_rest_api/dkan_dataset_rest_api.module
@@ -24,16 +24,6 @@ function dkan_dataset_rest_api_services_request_postprocess_alter($controller, $
$wrapper->og_group_ref->set(array($gids));
$wrapper->save();
}
-
- // Alter service attach file for add automatically the resource to datastore.
- if ($controller['callback'] == '_node_resource_attach_file') {
- if (isset($result[0]['uri'])) {
- $nid = $args[0];
- $uuid = current(entity_get_uuid_by_id('node', array($nid)));
-
- dkan_datastore_queue_import($uuid);
- }
- }
}
/**
diff --git a/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_rest_api/dkan_dataset_rest_api.services.inc b/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_rest_api/dkan_dataset_rest_api.services.inc
index 23c2f6927e6..867fb19318d 100644
--- a/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_rest_api/dkan_dataset_rest_api.services.inc
+++ b/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_rest_api/dkan_dataset_rest_api.services.inc
@@ -40,6 +40,30 @@ function dkan_dataset_rest_api_default_services_endpoint() {
),
);
$endpoint->resources = array(
+ 'datastore' => array(
+ 'operations' => array(
+ 'retrieve' => array(
+ 'enabled' => '1',
+ ),
+ 'create' => array(
+ 'enabled' => '1',
+ ),
+ 'update' => array(
+ 'enabled' => '1',
+ ),
+ 'delete' => array(
+ 'enabled' => '1',
+ ),
+ ),
+ 'targeted_actions' => array(
+ 'drop' => array(
+ 'enabled' => '1',
+ ),
+ 'import' => array(
+ 'enabled' => '1',
+ ),
+ ),
+ ),
'node' => array(
'alias' => 'node',
'operations' => array(
@@ -79,9 +103,19 @@ function dkan_dataset_rest_api_default_services_endpoint() {
'actions' => array(
'login' => array(
'enabled' => '1',
+ 'settings' => array(
+ 'services' => array(
+ 'resource_api_version' => '1.0',
+ ),
+ ),
),
'logout' => array(
'enabled' => '1',
+ 'settings' => array(
+ 'services' => array(
+ 'resource_api_version' => '1.0',
+ ),
+ ),
),
'token' => array(
'enabled' => '1',
diff --git a/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_voting/dkan_dataset_voting.info b/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_voting/dkan_dataset_voting.info
index cb4992ad583..345e4ae31dc 100644
--- a/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_voting/dkan_dataset_voting.info
+++ b/profiles/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_voting/dkan_dataset_voting.info
@@ -17,4 +17,4 @@ features[field_instance][] = comment-comment_node_dataset-field_rating
features[field_instance][] = node-dataset-field_rating
features[variable][] = ajax_comments_node_types
features[variable][] = ajax_comments_notify
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_dataset/tests/dkan_dataset_test.info b/profiles/dkan/modules/dkan/dkan_dataset/tests/dkan_dataset_test.info
index 017fde308f4..58e880c09d7 100644
--- a/profiles/dkan/modules/dkan/dkan_dataset/tests/dkan_dataset_test.info
+++ b/profiles/dkan/modules/dkan/dkan_dataset/tests/dkan_dataset_test.info
@@ -4,4 +4,4 @@ core = 7.x
dependencies[] = dkan_dataset
dependencies[] = dkan_dataset_rest_api
hidden = TRUE
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/README.md b/profiles/dkan/modules/dkan/dkan_datastore/README.md
index affbd7bc74d..fab1c84d698 100644
--- a/profiles/dkan/modules/dkan/dkan_datastore/README.md
+++ b/profiles/dkan/modules/dkan/dkan_datastore/README.md
@@ -1,69 +1 @@
# DKAN Datastore
-
-DKAN Datastore bundles a number of modules and configuration to allow users to upload CSV files, parse them and save them into the native database as flat tables, and query them through a public API.
-
-## Basic Architecture
-
-CSV and XML files uploaded to DKAN through the "Add Resources" form or through the API are parsed and inserted into unique tables in the DKAN database. Because Drupal has a pluggable database layer DKAN's database can MySQL, PostgreSQL, or MS SQL Server.
-
-### Manual Processing
-
-Files are parsed and inserted in batches. The user has the option of parsing them upon form submission. If the user chooses to parse the file manually they are able to see the progress of the processing through a batch operations screen similar to the one below.
-
-![Drupal batch operation](https://drupal.org/files/images/computed_field_tools_drupal7_batch.png)
-
-### Cron Processing
-
-Files that are not processed manually are processed in pieces during cron.
-
-### Datastore API
-
-Once processed, Datastore information is available via the Datastore API. For more information, see the Datastore API page.
-
-### User Interface
-
-DKAN provides UI for managing the Datastore. (PICTURE SHOULD GO HERE) Management activities include:
-
-* Importing items
-* Deleting items
-* Editing the schema (see below)
-* Edit Views integration
-
-(PICTURE OF EDITING SCHEMA BELONGS HERE)
-
-### Drupal Architecture
-
-The DKAN Datastore is managed by the Feeds module. Custom plugins were created for the Feed fetcher and processor to make the file uploaded to the resource form a feed item.
-
-## Geocoder
-
-DKAN's native Datastore can use the Drupal [geocoder](https://www.drupal.org/project/geocoder) module to add latitude/longitude coordinates to resources that have plain-text address information. This means that datasets containing plain-text addresses can be viewed on a map using the [Data Preview](dkan-documentation/dkan-features/data-preview-features) or more easily used to build map-based data visualizations.
-
-## Managing datastores with Drush
-
-The DKAN Datastore API module provides the functionality needed to manage the
-datastores using Drush. The available commands are:
-
-### To create a datastore from a local file:
-
-```bash
-drush dsc (path-to-local-file)
-```
-
-### To update a datastore from a local file:
-
-```bash
-drush dsu (datastore-id) (path-to-local-file)
-```
-
-### To delete a datastore file (imported items will be deleted as well):
-
-```bash
-drush dsfd (datastore-id)
-```
-
-### To get the URI of the datastore file:
-
-```bash
-drush dsfuri (datastore-id)
-```Z
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/dkan_datastore.drush.inc b/profiles/dkan/modules/dkan/dkan_datastore/dkan_datastore.drush.inc
new file mode 100644
index 00000000000..31f75e9c539
--- /dev/null
+++ b/profiles/dkan/modules/dkan/dkan_datastore/dkan_datastore.drush.inc
@@ -0,0 +1,35 @@
+ 'Remove datastore store configuration for a resource. (Only use if configuration have been orphaned - The configuration exists but the resource does not). For any other scenario use the datasotre UI.',
+ 'callback' => 'dkan_datastore_drush_delete_config',
+ 'arguments' => array(
+ 'resource_nid' => "Resource NID"
+ ),
+ );
+
+ return $items;
+}
+
+function dkan_datastore_drush_delete_config($resource_id) {
+ $node = node_load($resource_id);
+ if ($node === FALSE) {
+ $state_storage = new LockableDrupalVariables("dkan_datastore");
+ $state_storage->delete($resource_id);
+ }
+ else {
+ print_r("Can not delete the configuration for an existing node.");
+ }
+}
\ No newline at end of file
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/dkan_datastore.features.inc b/profiles/dkan/modules/dkan/dkan_datastore/dkan_datastore.features.inc
index b5df1eaf7aa..5377b837b10 100644
--- a/profiles/dkan/modules/dkan/dkan_datastore/dkan_datastore.features.inc
+++ b/profiles/dkan/modules/dkan/dkan_datastore/dkan_datastore.features.inc
@@ -5,15 +5,6 @@
* dkan_datastore.features.inc
*/
-/**
- * Implements hook_ctools_plugin_api().
- */
-function dkan_datastore_ctools_plugin_api($module = NULL, $api = NULL) {
- if ($module == "feeds" && $api == "feeds_importer_default") {
- return array("version" => "1");
- }
-}
-
/**
* Implements hook_views_api().
*/
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/dkan_datastore.feeds_importer_default.inc b/profiles/dkan/modules/dkan/dkan_datastore/dkan_datastore.feeds_importer_default.inc
deleted file mode 100644
index f1b06acf7d3..00000000000
--- a/profiles/dkan/modules/dkan/dkan_datastore/dkan_datastore.feeds_importer_default.inc
+++ /dev/null
@@ -1,95 +0,0 @@
-disabled = FALSE; /* Edit this to true to make a default feeds_importer disabled initially */
- $feeds_importer->api_version = 1;
- $feeds_importer->id = 'dkan_file';
- $feeds_importer->config = array(
- 'name' => 'DKAN Datastore File',
- 'description' => 'Feeds importer for files uploaded to DKAN',
- 'fetcher' => array(
- 'plugin_key' => 'FeedsFileFieldFetcher',
- 'config' => array(
- 'file_field' => 'field_upload',
- ),
- ),
- 'parser' => array(
- 'plugin_key' => 'FeedsCSVParser',
- 'config' => array(
- 'delimiter' => ',',
- 'no_headers' => 0,
- ),
- ),
- 'processor' => array(
- 'plugin_key' => 'FeedsFlatstoreProcessor',
- 'config' => array(
- 'update_existing' => 0,
- 'expire' => -1,
- 'mappings' => array(),
- 'delete_with_source' => FALSE,
- 'truncate' => 1,
- ),
- ),
- 'content_type' => 'resource',
- 'weight' => '0',
- 'update' => 0,
- 'import_period' => '-1',
- 'expire_period' => 3600,
- 'import_on_create' => 0,
- 'process_in_background' => 0,
- );
- $export['dkan_file'] = $feeds_importer;
-
- $feeds_importer = new stdClass();
- $feeds_importer->disabled = FALSE; /* Edit this to true to make a default feeds_importer disabled initially */
- $feeds_importer->api_version = 1;
- $feeds_importer->id = 'dkan_link';
- $feeds_importer->config = array(
- 'name' => 'DKAN Datastore Link Importer',
- 'description' => '',
- 'fetcher' => array(
- 'plugin_key' => 'FeedsFileFieldFetcher',
- 'config' => array(
- 'file_field' => 'field_link_remote_file',
- ),
- ),
- 'parser' => array(
- 'plugin_key' => 'FeedsCSVParser',
- 'config' => array(
- 'delimiter' => ',',
- 'no_headers' => 0,
- ),
- ),
- 'processor' => array(
- 'plugin_key' => 'FeedsFlatstoreProcessor',
- 'config' => array(
- 'update_existing' => 0,
- 'expire' => -1,
- 'mappings' => array(),
- 'delete_with_source' => FALSE,
- 'truncate' => 1,
- ),
- ),
- 'content_type' => 'resource',
- 'weight' => '0',
- 'update' => 0,
- 'import_period' => '-1',
- 'expire_period' => 3600,
- 'import_on_create' => 0,
- 'process_in_background' => 0,
- );
- $export['dkan_link'] = $feeds_importer;
-
- return $export;
-}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/dkan_datastore.info b/profiles/dkan/modules/dkan/dkan_datastore/dkan_datastore.info
index 3bae3f321a0..3d8c69f7fc3 100644
--- a/profiles/dkan/modules/dkan/dkan_datastore/dkan_datastore.info
+++ b/profiles/dkan/modules/dkan/dkan_datastore/dkan_datastore.info
@@ -2,24 +2,14 @@ name = DKAN Datastore
description = Creates a datastore designed to make it easy to share open data.
core = 7.x
package = DKAN
-dependencies[] = dkan_datastore_api
dependencies[] = entity
-dependencies[] = feeds
-dependencies[] = feeds_field_fetcher
-dependencies[] = feeds_flatstore_processor
dependencies[] = field_hidden
-dependencies[] = rest_server
dependencies[] = uuid
dependencies[] = views
-features[ctools][] = feeds:feeds_importer_default:1
+dependencies[] = xautoload
features[ctools][] = views:views_default:3.0
features[features_api][] = api:2
-features[feeds_importer][] = dkan_file
-features[feeds_importer][] = dkan_link
features[field_base][] = field_datastore_status
features[field_instance][] = node-resource-field_datastore_status
features[views_view][] = datasets
-files[] = includes/Datastore.inc
-files[] = includes/DkanDatastore.inc
-files[] = includes/DkanDatastoreFastImport.inc
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/dkan_datastore.module b/profiles/dkan/modules/dkan/dkan_datastore/dkan_datastore.module
index 3edd79be785..e0d31738754 100644
--- a/profiles/dkan/modules/dkan/dkan_datastore/dkan_datastore.module
+++ b/profiles/dkan/modules/dkan/dkan_datastore/dkan_datastore.module
@@ -5,105 +5,81 @@
* Creates DKAN Datastore.
*/
-// Datastore is created for a given resource.
-define('DKAN_DATASTORE_EMPTY', 0);
-// Datastore is created for a given resource.
-define('DKAN_DATASTORE_EXISTS', 1);
-// File is attached to a resource, but not added to the datastore.
-define('DKAN_DATASTORE_FILE_EXISTS', 2);
-// File is attached to a resource, but cannot be added to the datastore.
-define('DKAN_DATASTORE_WRONG_TYPE', 3);
-
include_once "dkan_datastore.features.inc";
+use Dkan\Datastore\Manager\ManagerInterface;
+use Dkan\Datastore\Manager\Factory;
+use Dkan\Datastore\Resource;
+use Dkan\Datastore\LockableDrupalVariables;
+
+/**
+ * Implements hook_xautoload().
+ */
+function dkan_datastore_xautoload($adapter) {
+ $adapter->absolute()->addPsr4(
+ 'Dkan\Datastore\\',
+ drupal_get_path("module", "dkan_datastore") . '/src'
+ );
+}
+
+/**
+ * Implements hook_services_resources().
+ */
+function dkan_datastore_services_resources() {
+ module_load_include('inc', 'dkan_datastore', 'resources/dkan_datastore_resource');
+ $resources = array();
+ $resources += _dkan_datastore_resource_definition();
+ return $resources;
+}
+
/**
* Implements hook_menu().
*/
function dkan_datastore_menu() {
+
+ // @todo Move somewhere else.
$items['node/%dkan_datastore_resource/download'] = array(
'title callback' => 'dkan_datastore_download_title',
'title arguments' => array(1),
'page callback' => 'dkan_datastore_download',
'page arguments' => array(1),
- 'access callback' => 'dkan_datastore_download_access',
+ 'access callback' => 'dkan_datastore_access',
'access arguments' => array('view', 1),
'file' => 'dkan_datastore.pages.inc',
'weight' => '20',
'type' => MENU_LOCAL_TASK,
);
+ // @todo Move somewhere else.
$items['node/%dkan_datastore_resource/data'] = array(
'page callback' => 'dkan_datastore_proxy',
'page arguments' => array(1),
- 'access callback' => 'dkan_datastore_download_access',
+ 'access callback' => 'dkan_datastore_access',
'access arguments' => array('view', 1),
'file' => 'dkan_datastore.pages.inc',
'type' => MENU_CALLBACK,
);
- $items['node/%node/api'] = array(
- 'title' => 'Data API',
- 'page callback' => 'dkan_datastore_datastore_api',
- 'page arguments' => array(1),
- 'access callback' => 'dkan_datastore_datastore_api_access',
- 'access arguments' => array(1),
- 'file' => 'dkan_datastore.pages.inc',
- 'weight' => '25',
- 'type' => MENU_LOCAL_TASK,
- );
$items['node/%node/datastore'] = array(
'title' => 'Manage Datastore',
'page callback' => 'drupal_get_form',
- 'page arguments' => array('dkan_datastore_import_tab_form', 1),
- 'access callback' => 'dkan_datastore_feeds_access',
- 'access arguments' => array('import', 1),
+ 'page arguments' => array('dkan_datastore_pages', 1),
+ 'access callback' => 'dkan_datastore_access',
+ 'access arguments' => array('manage', 1),
'file' => 'dkan_datastore.pages.inc',
'weight' => '15',
'type' => MENU_LOCAL_TASK,
);
- $items['node/%node/datastore/import'] = array(
- 'title' => 'Import',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('dkan_datastore_import_tab_form', 1),
- 'access callback' => 'dkan_datastore_feeds_access',
- 'access arguments' => array('import', 1),
- 'file' => 'dkan_datastore.pages.inc',
- 'weight' => 10,
- 'type' => MENU_DEFAULT_LOCAL_TASK,
- );
- $items['node/%node/datastore/delete-items'] = array(
- 'title' => 'Delete items',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('dkan_datastore_delete_tab_form', NULL, 1),
- 'access callback' => 'dkan_datastore_feeds_access',
- 'access arguments' => array('clear', 1),
- 'file' => 'dkan_datastore.pages.inc',
- 'weight' => 11,
- 'type' => MENU_LOCAL_TASK,
- );
- $items['node/%node/datastore/unlock'] = array(
- 'title' => 'Unlock',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('dkan_datastore_unlock_tab_form', NULL, 1),
- 'access callback' => 'dkan_datastore_feeds_access',
- 'access arguments' => array('unlock', 1),
- 'file' => 'dkan_datastore.pages.inc',
- 'weight' => 11,
- 'type' => MENU_LOCAL_TASK,
- );
- $items['node/%node/datastore/drop'] = array(
- 'title' => 'Drop Datastore',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('dkan_datastore_drop_tab_form', 1),
- 'access callback' => 'dkan_datastore_feeds_access',
- 'access arguments' => array('drop', 1),
- 'file' => 'dkan_datastore.pages.inc',
- 'weight' => 12,
- 'type' => MENU_LOCAL_TASK,
- );
return $items;
}
+/**
+ * Get managers info.
+ */
+function dkan_datastore_managers_info() {
+ return module_invoke_all("dkan_datastore_manager");
+}
+
/**
* Retrieves the url and type to the physical resource.
*/
@@ -112,7 +88,7 @@ function dkan_datastore_resource_link_type($node) {
$file_field = dkan_datastore_file_upload_field();
$remote_file_field = dkan_datastore_file_link_field();
$api_field = dkan_datastore_api_link_field();
- $type = dkan_datastore_node_type();
+ $type = 'resource';
if ($node->type == $type && $file = $node_wrapper->{$file_field}->value()) {
$uri = isset($file->uri) ? $file->uri : $file['uri'];
@@ -155,64 +131,33 @@ function dkan_datastore_menu_alter(&$items) {
/**
* Access callback for DKAN Datastore operations.
*/
-function dkan_datastore_feeds_access($action, $node) {
-
- // If $action is not one of the supported actions, return access denied.
- if (!in_array($action, array('import', 'clear', 'unlock', 'drop'))) {
+function dkan_datastore_access($op, $node, $account = NULL) {
+ try {
+ Resource::createFromDrupalNode($node);
+ }
+ catch (\Exception $e) {
return FALSE;
}
- // All available operations requires the 'manage datastore' permission.
- if (user_access('manage datastore') && node_access('update', $node) && $node->type == 'resource') {
- return TRUE;
+ global $user;
+ if (!isset($account)) {
+ $account = $user;
}
- return FALSE;
-}
+ switch ($op) {
+ case 'view':
+ return node_access('view', $node, $account);
-/**
- * Access callback for Download.
- */
-function dkan_datastore_download_access($action, $resource) {
- $type = dkan_datastore_node_type();
- $node = is_object($resource) ? $resource : node_load($resource);
-
- if ($node->type != $type) {
- return FALSE;
- }
-
- return node_access('view', $node);
-}
-
-/**
- * Access callback for Data API instructions.
- */
-function dkan_datastore_datastore_api_access($resource) {
- $status = dkan_datastore_status($resource);
- if ($status == DKAN_DATASTORE_FILE_EXISTS && !user_access('manage datastore')) {
- return FALSE;
- }
- if ($status != DKAN_DATASTORE_WRONG_TYPE) {
- $file_field = dkan_datastore_file_upload_field();
- $link_field = dkan_datastore_file_link_field();
- $type = dkan_datastore_node_type();
- $node = is_object($resource) ? $resource : node_load($resource);
- $upload = '';
- $link = '';
- if (isset($node->$file_field) && $node->$file_field) {
- $upload = isset($node->{$file_field}[$node->language]) ? $node->{$file_field}[$node->language] : $node->{$file_field}['und'];
- }
- if (isset($node->{$link_field}) && $node->{$link_field}) {
- $link = isset($node->{$link_field}[$node->language]) ? $node->{$link_field}[$node->language] : $node->{$link_field}['und'];
- }
- if ($node->type == $type && ($upload || $link)) {
- return node_access('view', $node);
- }
- else {
- return FALSE;
- }
+ case 'drop':
+ case 'delete':
+ case 'import':
+ case 'manage':
+ // All available operations require the 'manage datastore' permission.
+ if (user_access('manage datastore', $account) && node_access('update', $node, $account)) {
+ return TRUE;
+ }
+ break;
}
- return FALSE;
}
/**
@@ -229,7 +174,6 @@ function dkan_datastore_resource_load($id) {
$node = entity_uuid_load('node', array($id));
return ($node) ? reset($node) : FALSE;
}
-
}
catch (Exception $e) {
return FALSE;
@@ -238,18 +182,6 @@ function dkan_datastore_resource_load($id) {
return FALSE;
}
-/**
- * Access callback for back link.
- */
-function dkan_datastore_back_access($node) {
- if ($node->type != 'resource') {
- return FALSE;
- }
- else {
- return node_access('view', $node);
- }
-}
-
/**
* Access callback for 'Add Resource' tab.
*/
@@ -268,11 +200,13 @@ function dkan_add_resource($node) {
function dkan_datastore_node_insert($node) {
$type = dkan_datastore_node_type();
if ($node->type == $type) {
- if (!isset($node->feeds)) {
- // Feeds only saves if there is a form present. We want this to work
- // programmatically as well.
- feeds_node_prepare($node);
- feeds_node_update($node);
+ try {
+ $resource = Resource::createFromDrupalNode($node);
+ $manager = (new Factory($resource))->get();
+ $manager->saveState();
+ }
+ catch (\Exception $e) {
+ drupal_set_message($e->getMessage());
}
}
}
@@ -314,6 +248,22 @@ function dkan_datastore_node_update($node) {
}
}
+/**
+ * Implements hook_node_delete().
+ */
+function dkan_datastore_node_delete($node) {
+ if ($node->type == "resource") {
+ try {
+ /* @var $manager ManagerInterface */
+ $manager = (new Factory(Resource::createFromDrupalNode($node)))->get();
+ $manager->drop();
+ }
+ catch (\Exception $e) {
+
+ }
+ }
+}
+
/**
* Implements hook_permission().
*/
@@ -328,48 +278,6 @@ function dkan_datastore_permission() {
);
}
-/**
- * Implements hook_node_view().
- */
-function dkan_datastore_node_view($node, $view_mode, $langcode) {
-
- $type = dkan_datastore_node_type();
- if ($node->type == $type) {
- $status = dkan_datastore_status($node);
- if (user_access('manage datastore') && $status && $view_mode == 'full') {
- if ($status == DKAN_DATASTORE_FILE_EXISTS) {
- drupal_set_message(t('Your file for this resource is not added to the datastore. Click "Manage Datastore" to import file into the datastore.'));
- }
- elseif ($status == DKAN_DATASTORE_EXISTS) {
- drupal_set_message(t('Your file for this resource has been added to the datastore.'));
- }
- }
- }
-}
-
-/**
- * Determines status of datastore attached to resource node.
- */
-function dkan_datastore_status($node) {
- if ($file = dkan_datastore_file_field($node)) {
- if (dkan_datastore_is_importable($file)) {
- $node = entity_metadata_wrapper('node', $node);
- if ($status = $node->field_datastore_status->value()) {
- return $status;
- }
- else {
- return DKAN_DATASTORE_FILE_EXISTS;
- }
- }
- else {
- return DKAN_DATASTORE_WRONG_TYPE;
- }
- }
- else {
- return DKAN_DATASTORE_EMPTY;
- }
-}
-
/**
* Determines if a resource can be imported.
*/
@@ -377,30 +285,6 @@ function dkan_datastore_is_importable($file) {
return (is_object($file) && $file->filemime == 'text/csv') || (is_array($file) && array_key_exists('filemime', $file) && $file['filemime'] == 'text/csv');
}
-/**
- * Determines if records exist in a datastore.
- *
- * @param string $nid
- * Node id for resource node.
- */
-function dkan_datastore_records($nid) {
- if (function_exists('dkan_datastore_api_get_feeds_source')) {
- $source_id = dkan_datastore_api_get_feeds_source($nid);
- if ($table = feeds_flatstore_processor_table_name($source_id, $nid)) {
- if (db_table_exists($table)) {
- $query = db_select($table, 't')
- ->fields('t', array('timestamp'))
- ->range(0, 1);
- $result = $query->execute();
- if (!empty($result->rowCount())) {
- return TRUE;
- }
- }
- }
- }
- return FALSE;
-}
-
/**
* Retrieves loaded file from resource node.
*/
@@ -474,35 +358,19 @@ function dkan_datastore_theme() {
* @ingroup themeable
*/
function theme_dkan_datastore_status_formatter(array $variables) {
- $status = dkan_datastore_status($variables['item']['entity']);
- if ($status === DKAN_DATASTORE_FILE_EXISTS) {
- $title = t('A file has been uploaded but not added to the datastore');
- return '' . t('Data ready to be added') . '';
- }
- elseif ($status === DKAN_DATASTORE_EXISTS) {
- return '' . t('Datastore enabled') . '';
- }
- return '' . t('Correct file type not attached to resource') . '';
-}
+ $node = $variables['item']['entity'];
+ try {
+ $resource = \Dkan\Datastore\Resource::createFromDrupalNode($node);
-/**
- * Gets a datastore instance.
- *
- * @param string $id
- * The unique id of the importer object.
- *
- * @return Datastore
- * A Datastore object or an object of a class defined by the Drupal
- * variable 'dkan_datastore_class'. There is only one importer object
- * per $id system-wide.
- */
-function dkan_datastore_go($id = NULL, $class = NULL) {
- if (!$class) {
- $class = variable_get('dkan_datastore_class', 'DkanDatastore');
+ /* @var $manager ManagerInterface */
+ $manager = (new Factory($resource))->get();
+ $status = $manager->getStatus();
+ $string = \Dkan\Datastore\Page\Component\Status::datastoreStateToString($status['data_import']);
+ return '' . t($string) . '';
+ }
+ catch (\Exception $e) {
+ return '' . t('Datastore disabled') . '';
}
-
- return class_exists($class) ? Datastore::instance($class, $id) :
- NULL;
}
/**
@@ -543,28 +411,12 @@ function dkan_datastore_file_link_field() {
/**
* Returns name of node type that the Datastore is attached to.
+ *
+ * @deprecated
+ * Use Resource::resourceContentType.
*/
function dkan_datastore_node_type() {
- static $node_type;
- if (!$node_type) {
- $node_type = 'resource';
- drupal_alter('dkan_datastore_node_type', $node_type);
- }
- return $node_type;
-}
-
-/**
- * Implements hook_feeds_after_import().
- */
-function dkan_datastore_feeds_after_import(FeedsSource $source) {
- $importer_id = $source->importer->id;
- $importers = array('dkan_file', 'dkan_link');
- if (empty($source->exception) && in_array($importer_id, $importers)) {
- $node = node_load($source->feed_nid);
- $node = entity_metadata_wrapper('node', $node);
- $node->field_datastore_status->set(DKAN_DATASTORE_EXISTS);
- $node->save();
- }
+ return 'resource';
}
/**
@@ -572,64 +424,334 @@ function dkan_datastore_feeds_after_import(FeedsSource $source) {
*/
function dkan_datastore_node_presave($node) {
if (is_object($node) && $node->type == 'resource') {
- $wrap = entity_metadata_wrapper('node', $node);
- $wrap->field_datastore_status->set(dkan_datastore_status($node));
+ /*$wrap = entity_metadata_wrapper('node', $node);
+ $wrap->field_datastore_status->set(dkan_datastore_status($node));*/
}
}
/**
- * Implements hook_cron_queue_info().
+ * Implements hook_cron().
*/
-function dkan_datastore_cron_queue_info() {
- $queues['dkan_datastore_queue'] = array(
- 'worker callback' => 'dkan_datastore_queue_import_worker',
- 'time' => 120,
- 'skip on cron' => FALSE,
- );
+function dkan_datastore_cron() {
+ if (drupal_is_cli()) {
+ $storage = new LockableDrupalVariables("dkan_datastore");
- return $queues;
-}
+ $bin = $storage->borrowBin();
-/**
- * Utility function for add a new resource to datastore queue.
- */
-function dkan_datastore_queue_import($uuid) {
- $item = array(
- 'uuid' => $uuid,
- );
- DrupalQueue::get('dkan_datastore_queue')->createItem($item);
- watchdog('dkan_datastore', 'Added resource %uuid to queue for import into datastore', array('%uuid' => $uuid));
-}
+ $already_importing = FALSE;
-/**
- * Callback used with queue for index content into datastore.
- */
-function dkan_datastore_queue_import_worker($item) {
- $datastore = dkan_datastore_go($item['uuid']);
+ $current_nid = NULL;
+ foreach ($bin as $nid => $state) {
+ if ($state['data_import'] == ManagerInterface::DATA_IMPORT_IN_PROGRESS) {
+ $already_importing = TRUE;
+ break;
+ }
+ elseif ($state['data_import'] == ManagerInterface::DATA_IMPORT_READY || $state['data_import'] == ManagerInterface::DATA_IMPORT_PAUSED) {
+ $current_nid = $nid;
+ break;
+ }
+ }
- if (empty($datastore)) {
- watchdog('dkan_datastore', "Failed to instantiate the Datastore for the resource id @uuid",
- array('@uuid' => $item['uuid']),
- 'error');
- return FALSE;
- }
+ $storage->returnBin($bin);
+
+ if ($current_nid) {
+ print_r(PHP_EOL . "DKAN DATASTORE: Starting Import of Resource {$nid}" . PHP_EOL);
- return $datastore->importByCli();
+ $resource = Resource::createFromDrupalNodeNid($current_nid);
+
+ /* @var $manager ManagerInterface */
+ $manager = (new Factory($resource))->get();
+
+ $finished = $manager->import();
+
+ if ($finished) {
+ print_r(PHP_EOL . "DKAN DATASTORE: Done Importing Resource {$current_nid}" . PHP_EOL);
+ }
+ else {
+ $general = "DKAN DATASTORE: There was a problem while importing Resource {$current_nid}";
+ $errors = $manager->getErrors();
+ $error_string = implode(" | ", $errors);
+ $final_error_string = "{$general} - {$error_string}";
+ print_r(PHP_EOL . $final_error_string . PHP_EOL);
+ watchdog("error", $final_error_string);
+ }
+ }
+ else {
+ if ($already_importing) {
+ print_r(PHP_EOL . "DKAN DATASTORE: Already Importing {$nid}" . PHP_EOL);
+ }
+ else {
+ print_r(PHP_EOL . "DKAN DATASTORE: No Resources to Import" . PHP_EOL);
+ }
+ }
+ }
}
/**
- * Implements hook_form_alter().
+ * Determines safe name using reserved words. Should move to data.
*/
-function dkan_datastore_form_alter(&$form, &$form_state, $form_id) {
- if ($form['#form_id'] == 'views_exposed_form' && $form['#id'] == 'views-exposed-form-dkan-datasets-panel-pane-1') {
- // Redirect reset button to main search page.
- $form['reset']['#submit'] = array('_dkan_datastore_search_redirect');
+function dkan_datastore_safe_name($name) {
+ $map = array(
+ '.' => '_',
+ ':' => '',
+ '/' => '',
+ '-' => '_',
+ ' ' => '_',
+ ',' => '_',
+ );
+ $simple = trim(strtolower(strip_tags($name)));
+ // Limit length to 64 http://dev.mysql.com/doc/refman/5.0/en/identifiers.html
+ $simple = substr(strtr($simple, $map), 0, 64);
+
+ if (is_numeric($simple)) {
+ // We need to escape numerics because Drupal's drupal_write_record()
+ // does not properly escape token MYSQL names.
+ $simple = '__num_' . $simple;
}
+
+ $reserved = dkan_datastore_reserved_words();
+ if (in_array($simple, $reserved)) {
+ $simple = $simple . '_';
+ }
+
+ return db_escape_table($simple);
}
/**
- * Callback to redirect to search url.
+ * Creates list of reserved words for MySQL.
*/
-function _dkan_datastore_search_redirect() {
- drupal_goto('/search');
+function dkan_datastore_reserved_words() {
+ return array(
+ 'accessible',
+ 'add',
+ 'all',
+ 'alter',
+ 'analyze',
+ 'and',
+ 'as',
+ 'asc',
+ 'asensitive',
+ 'before',
+ 'between',
+ 'bigint',
+ 'binary',
+ 'blob',
+ 'both',
+ 'by',
+ 'call',
+ 'cascade',
+ 'case',
+ 'change',
+ 'char',
+ 'character',
+ 'check',
+ 'collate',
+ 'column',
+ 'condition',
+ 'constraint',
+ 'continue',
+ 'convert',
+ 'create',
+ 'cross',
+ 'current_date',
+ 'current_time',
+ 'current_timestamp',
+ 'current_user',
+ 'cursor',
+ 'database',
+ 'databases',
+ 'day_hour',
+ 'day_microsecond',
+ 'day_minute',
+ 'day_second',
+ 'dec',
+ 'decimal',
+ 'declare',
+ 'default',
+ 'delayed',
+ 'delete',
+ 'desc',
+ 'describe',
+ 'deterministic',
+ 'distinct',
+ 'distinctrow',
+ 'div',
+ 'double',
+ 'drop',
+ 'dual',
+ 'each',
+ 'else',
+ 'elseif',
+ 'enclosed',
+ 'escaped',
+ 'exists',
+ 'exit',
+ 'explain',
+ 'false',
+ 'fetch',
+ 'float',
+ 'float4',
+ 'float8',
+ 'for',
+ 'force',
+ 'foreign',
+ 'from',
+ 'fulltext',
+ 'get',
+ 'grant',
+ 'group',
+ 'having',
+ 'high_priority',
+ 'hour_microsecond',
+ 'hour_minute',
+ 'hour_second',
+ 'if',
+ 'ignore',
+ 'in',
+ 'index',
+ 'infile',
+ 'inner',
+ 'inout',
+ 'insensitive',
+ 'insert',
+ 'int',
+ 'int1',
+ 'int2',
+ 'int3',
+ 'int4',
+ 'int8',
+ 'integer',
+ 'interval',
+ 'into',
+ 'io_after_gtids',
+ 'io_before_gtids',
+ 'is',
+ 'iterate',
+ 'join',
+ 'key',
+ 'keys',
+ 'kill',
+ 'leading',
+ 'leave',
+ 'left',
+ 'like',
+ 'limit',
+ 'linear',
+ 'lines',
+ 'load',
+ 'localtime',
+ 'localtimestamp',
+ 'lock',
+ 'long',
+ 'longblob',
+ 'longtext',
+ 'loop',
+ 'low_priority',
+ 'master_bind',
+ 'master_ssl_verify_server_cert',
+ 'match',
+ 'maxvalue',
+ 'mediumblob',
+ 'mediumint',
+ 'mediumtext',
+ 'middleint',
+ 'minute_microsecond',
+ 'minute_second',
+ 'mod',
+ 'modifies',
+ 'natural',
+ 'not',
+ 'no_write_to_binlog',
+ 'null',
+ 'numeric',
+ 'on',
+ 'optimize',
+ 'option',
+ 'optionally',
+ 'or',
+ 'order',
+ 'out',
+ 'outer',
+ 'outfile',
+ 'partition',
+ 'precision',
+ 'primary',
+ 'procedure',
+ 'purge',
+ 'range',
+ 'read',
+ 'reads',
+ 'read_write',
+ 'real',
+ 'references',
+ 'regexp',
+ 'release',
+ 'rename',
+ 'repeat',
+ 'replace',
+ 'require',
+ 'resignal',
+ 'restrict',
+ 'return',
+ 'revoke',
+ 'right',
+ 'rlike',
+ 'schema',
+ 'schemas',
+ 'second_microsecond',
+ 'select',
+ 'sensitive',
+ 'separator',
+ 'set',
+ 'show',
+ 'signal',
+ 'smallint',
+ 'spatial',
+ 'specific',
+ 'sql',
+ 'sqlexception',
+ 'sqlstate',
+ 'sqlwarning',
+ 'sql_big_result',
+ 'sql_calc_found_rows',
+ 'sql_small_result',
+ 'ssl',
+ 'starting',
+ 'straight_join',
+ 'table',
+ 'terminated',
+ 'then',
+ 'tinyblob',
+ 'tinyint',
+ 'tinytext',
+ 'to',
+ 'trailing',
+ 'trigger',
+ 'true',
+ 'undo',
+ 'union',
+ 'unique',
+ 'unlock',
+ 'unsigned',
+ 'update',
+ 'usage',
+ 'use',
+ 'using',
+ 'utc_date',
+ 'utc_time',
+ 'utc_timestamp',
+ 'values',
+ 'varbinary',
+ 'varchar',
+ 'varcharacter',
+ 'varying',
+ 'when',
+ 'where',
+ 'while',
+ 'with',
+ 'write',
+ 'xor',
+ 'year_month',
+ 'zerofill',
+ );
}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/dkan_datastore.pages.inc b/profiles/dkan/modules/dkan/dkan_datastore/dkan_datastore.pages.inc
index 4e1ce69cff8..ae73bcec959 100644
--- a/profiles/dkan/modules/dkan/dkan_datastore/dkan_datastore.pages.inc
+++ b/profiles/dkan/modules/dkan/dkan_datastore/dkan_datastore.pages.inc
@@ -5,31 +5,46 @@
* Callbacks for datastore pages.
*/
+use Dkan\Datastore\Resource;
+use Dkan\Datastore\Manager\Factory;
+use Dkan\Datastore\Page\Page;
+
// Default to 50MB.
define('MAX_FILE_REMOTE_PROXY_DEFAULT', 1024 * 1024 * 50);
/**
- * Callback for Data API instructions.
+ * Proxy remote resources.
*/
-function dkan_datastore_datastore_api($node) {
- $status = dkan_datastore_status($node);
- switch ($status) {
- case DKAN_DATASTORE_EXISTS:
- $datastore = dkan_datastore_go($node->uuid);
- $output = $datastore->apiForm();
- return $output;
+function dkan_datastore_proxy($node) {
+ $allowed_types = array('csv');
+ $node_wrapper = entity_metadata_wrapper('node', $node);
+ $remote_file = $node_wrapper->field_link_remote_file->value();
+ $uri = $remote_file['uri'];
+ $filename = $remote_file['filename'];
+ $mime = $remote_file['filemime'];
+ $type = recline_get_data_type($remote_file['filemime']);
- case DKAN_DATASTORE_FILE_EXISTS:
- drupal_set_message(t('You need to add your file to the datastore in order to use the DATA API'));
- $redirect_path = 'node/' . $node->nid . '/datastore';
- break;
+ if (in_array($type, $allowed_types)) {
+ drupal_add_http_header('Content-Type', $mime);
+ drupal_add_http_header('Content-Disposition', 'attachment; filename=' . $filename);
+ print file_get_contents($uri);
+ }
+ else {
+ // Not in allowed types, treat the file URI as a normal url.
+ return drupal_goto($uri);
+ }
+}
- default:
- drupal_set_message(t('This resource can not be added to the datastore'));
- $redirect_path = 'node/' . $node->nid;
- break;
+/**
+ * Callback for back link.
+ */
+function dkan_datastore_back($node) {
+ $node_wrapper = entity_metadata_wrapper('node', $node);
+ $dataset = $node_wrapper->field_dataset_ref->value();
+ if ($dataset) {
+ drupal_goto('node/' . $dataset->nid);
}
- drupal_goto($redirect_path);
+ return '';
}
/**
@@ -66,41 +81,6 @@ function dkan_datastore_download($node) {
}
}
-/**
- * Proxy remote resources.
- */
-function dkan_datastore_proxy($node) {
- $allowed_types = array('csv');
- $node_wrapper = entity_metadata_wrapper('node', $node);
- $remote_file = $node_wrapper->field_link_remote_file->value();
- $uri = $remote_file['uri'];
- $filename = $remote_file['filename'];
- $mime = $remote_file['filemime'];
- $type = recline_get_data_type($remote_file['filemime']);
-
- if (in_array($type, $allowed_types)) {
- drupal_add_http_header('Content-Type', $mime);
- drupal_add_http_header('Content-Disposition', 'attachment; filename=' . $filename);
- print file_get_contents($uri);
- }
- else {
- // Not in allowed types, treat the file URI as a normal url.
- return drupal_goto($uri);
- }
-}
-
-/**
- * Callback for back link.
- */
-function dkan_datastore_back($node) {
- $node_wrapper = entity_metadata_wrapper('node', $node);
- $dataset = $node_wrapper->field_dataset_ref->value();
- if ($dataset) {
- drupal_goto('node/' . $dataset->nid);
- }
- return '';
-}
-
/**
* Callback for 'Add Resouce' tab.
*/
@@ -109,96 +89,50 @@ function dkan_datastore_add_resource($node) {
}
/**
- * Render a datastore import form on node/id/import pages.
+ * Datastore forms menu callback.
*/
-function dkan_datastore_import_tab_form($form, &$form_state, $node) {
- $datastore = dkan_datastore_go($node->uuid);
- $form = $datastore->manageForm($form_state);
- $form['#datastore'] = $datastore;
- return $form;
+function dkan_datastore_pages($form, &$form_state, $node) {
+ $form['#node'] = $node;
+ $page = new Page($node, $form, $form_state);
+ return $page->get();
}
/**
- * Submit function for import tab.
+ * Submit handler.
*/
-function dkan_datastore_import_tab_form_submit($form, &$form_state) {
- $datastore = $form['#datastore'];
- $datastore->manageFormSubmit($form_state);
+function dkan_datastore_pages_submit($form, &$form_state) {
+ $page = new Page($form['#node'], $form, $form_state);
+ $page->submit();
}
/**
- * Render a datastore delete form.
- *
- * Used on both node pages and configuration pages.
- * Therefore $node may be missing.
+ * Submit handler.
*/
-function dkan_datastore_delete_tab_form($form, &$form_state, $importer_id, $node = NULL) {
- $datastore = dkan_datastore_go($node->uuid);
- $form = $datastore->deleteForm($form_state);
- $form['#submit'] = array('feeds_delete_tab_form_submit');
- return $form;
+function dkan_datastore_drop_submit($form, &$form_state) {
+ $form_state['storage']['drop'] = TRUE;
+ $form_state['storage']['original_form'] = $form_state['values'];
+ $form_state['rebuild'] = TRUE;
}
/**
- * Render a datastore unlock form.
- *
- * Used on both node pages and configuration pages.
- * Therefore $node may be missing.
+ * Submit handler.
*/
-function dkan_datastore_unlock_tab_form($form, &$form_state, $importer_id, $node = NULL) {
- module_load_include('inc', 'feeds', 'feeds.pages');
- if (empty($node)) {
- $source = feeds_source($importer_id);
- $form['#redirect'] = 'import/' . $source->id;
- }
- else {
- $importer_id = feeds_get_importer_id($node->type);
- $source = feeds_source($importer_id, $node->nid);
- $form['#redirect'] = 'node/' . $source->feed_nid;
- }
- // Form cannot pass on source object.
- $form['#importer_id'] = $source->id;
- $form['#feed_nid'] = $source->feed_nid;
- $form['source_status'] = array(
- '#type' => 'fieldset',
- '#title' => t('Status'),
- '#tree' => TRUE,
- '#value' => feeds_source_status($source),
- );
- $form = confirm_form($form, t('Unlock this importer?'), $form['#redirect'], '', t('Delete'), t('Cancel'), 'confirm feeds update');
- if ($source->progressImporting() == FEEDS_BATCH_COMPLETE && $source->progressClearing() == FEEDS_BATCH_COMPLETE) {
- $form['source_locked'] = array(
- '#type' => 'markup',
- '#title' => t('Not Locked'),
- '#tree' => TRUE,
- '#markup' => t('This importer is not locked, therefore it cannot be unlocked.'),
- );
- $form['actions']['submit']['#disabled'] = TRUE;
- $form['actions']['submit']['#value'] = t('Unlock (disabled)');
- }
- else {
- $form['actions']['submit']['#value'] = t('Unlock');
- }
- $form['#submit'] = array('feeds_unlock_tab_form_submit');
- return $form;
+function dkan_datastore_stop_submit($form, &$form_state) {
+ drupal_set_message(t("Importing will be stopped shortly."));
+ variable_set('dkan_datastore_interrupt', 1);
}
/**
- * Render a datastore drop form on node/id/drop pages.
+ * Submit handler.
*/
-function dkan_datastore_drop_tab_form($form, &$form_state, $node) {
- $datastore = dkan_datastore_go($node->uuid);
- $form = $datastore->dropForm($form_state);
- $form['#datastore'] = $datastore;
- return $form;
-}
-
-/**
- * Submit function for drop tab.
- */
-function dkan_datastore_drop_tab_form_submit($form, &$form_state) {
- $datastore = $form['#datastore'];
- $form_state['#tables_to_drop'] = $form['#tables_to_drop'];
- $datastore->dropFormSubmit($form_state);
- $form_state['redirect'] = $form['#redirect'];
+function dkan_datastore_go_to_paused_state_submit($form, &$form_state) {
+ $node = $form['#node'];
+ try {
+ /* @var $manager \Dkan\Datastore\Manager\ManagerInterface */
+ $manager = (new Factory(Resource::createFromDrupalNode($node)))->get();
+ $manager->goToPausedState();
+ }
+ catch (\Exception $e) {
+ drupal_set_message($e->getMessage());
+ }
}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/includes/Datastore.inc b/profiles/dkan/modules/dkan/dkan_datastore/includes/Datastore.inc
deleted file mode 100644
index 1a75d19e82c..00000000000
--- a/profiles/dkan/modules/dkan/dkan_datastore/includes/Datastore.inc
+++ /dev/null
@@ -1,247 +0,0 @@
-uuid = $uuid;
- // Make sure configuration is populated.
- $this->config = $this->configDefaults();
- $this->node = $this->node($uuid);
- }
-
- /**
- * Config defaults.
- */
- public function configDefaults() {
- }
-
- /**
- * Loads node from uuuid.
- */
- protected function node($uuid) {
- static $nodes = array();
- if (!isset($node[$uuid])) {
- if ($nid = $this->getNid($uuid)) {
- $nodes[$uuid] = node_load($nid);
- }
- else {
- watchdog('dkan_datastore', 'Could not load, uuid: %uuid does not exist', array('%uuid' => $uuid), WATCHDOG_ERROR);
- }
- }
- return $nodes[$uuid];
- }
-
- /**
- * Gets nid using uuid.
- */
- public function getNid($uuid) {
- $nid = db_query('SELECT nid FROM {node} WHERE uuid = :uuid', array(':uuid' => $uuid))->fetchField();
- if ($nid) {
- return $nid;
- }
- else {
- return FALSE;
- }
- }
-
- /**
- * Instantiate a Datastore object with some baseline caching.
- *
- * Don't use directly, use dkan_datastore_go() instead.
- */
- public static function instance($class, $id) {
- // This is useful at least as long as we're developing.
- if (empty($id)) {
- // Create an empty resource.
- $resource = new stdClass();
- $resource->title = t('Automatically created by datastore');
- $resource->type = dkan_datastore_node_type();
- $resource->language = LANGUAGE_NONE;
- $resource->status = '1';
- $resource->promote = '0';
- $resource->sticky = '0';
- $resource->comment = '0';
- $resource->translate = '0';
- node_save($resource);
-
- $id = $resource->uuid;
- }
-
- static $instances = array();
- if (!isset($instances[$class][$id])) {
- $instances[$class][$id] = new $class($id);
- }
- return $instances[$class][$id];
- }
-
- /**
- * Imports file contents into the datastore.
- */
- public function import() {
- }
-
- /**
- * Drops datastore.
- */
- public function drop($form) {
- }
-
- /**
- * Retrieves headers.
- */
- public function headers() {
- }
-
- /**
- * Provides number of rows in datastore.
- */
- public function rows() {
- }
-
- /**
- * Provides API endpoint.
- */
- public function apiUri() {
- }
-
- /**
- * Deletes rows from the Datastore.
- */
- public function deleteRows($rows = 'all') {
- }
-
- /**
- * Unlocks locked datastore for updates.
- */
- public function unlock() {
- }
-
- /**
- * Retrieves file object from datastore.
- */
- public function file() {
- if ($file = dkan_datastore_file_field($this->node)) {
- return $file;
- }
- }
-
- /**
- * Updates the datastore with file.
- */
- public function updateFromFileUri($uri = '', $copy_file = TRUE) {
-
- }
-
- /**
- * Creates a datastore with file.
- */
- public function createFromFileUri($uri = '', $copy_file = TRUE) {
-
- }
-
- /**
- * Deletes the file.
- */
- public function deleteFile() {
-
- }
-
- /**
- * Retrieves file URI.
- */
- public function fileUri() {
- $file = $this->file();
- return $file->uri;
- }
-
- /**
- * Retrieves file URL.
- */
- public function fileUrl() {
- $file = $this->file();
- return file_create_url($file->uri);
- }
-
- /**
- * Retrieves timestamp for last import.
- */
- public function lastImport() {
- }
-
- /**
- * Retrieves progress of datastore import.
- */
- public function progress() {
- }
-
- /**
- * Retrieves status of the datastore.
- *
- * TODO: Document status levels.
- */
- public function status() {
- }
-
-}
-
-/**
- * Declares interface for forms.
- *
- * Declares interface for forms required for interaction with Datastores through
- * the DKAN user interface.
- */
-interface DatastoreFormInterface {
-
- /**
- * Manage form.
- */
- public function manageForm(&$form_state);
-
- /**
- * Manage form submit.
- */
- public function manageFormSubmit(&$form_state);
-
- /**
- * Delete form.
- */
- public function deleteForm(&$form_state);
-
- /**
- * Delete form submit.
- */
- public function deleteFormSubmit(&$form_state);
-
- /**
- * Drop form.
- */
- public function dropForm(&$form_state);
-
- /**
- * Drop form submit.
- */
- public function dropFormSubmit(&$form_state);
-
- /**
- * Api form.
- */
- public function apiForm();
-
-}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/includes/DkanDatastore.inc b/profiles/dkan/modules/dkan/dkan_datastore/includes/DkanDatastore.inc
deleted file mode 100644
index a90594a68e8..00000000000
--- a/profiles/dkan/modules/dkan/dkan_datastore/includes/DkanDatastore.inc
+++ /dev/null
@@ -1,689 +0,0 @@
-uuid = $uuid;
- $this->node = $this->node($uuid);
- $this->importerId = $this->importerId();
- $this->source = $this->source();
- $this->importer = $this->importer();
- $this->sourceId = dkan_datastore_api_get_feeds_source($this->node->nid);
- $this->tableName = feeds_flatstore_processor_table_name($this->sourceId, $this->node->nid);
- $this->status = $this->status();
- }
-
- /**
- * Retrieves importer id.
- */
- public function importerId() {
- $id = db_query("SELECT id FROM {feeds_source} WHERE feed_nid = :nid", array(':nid' => $this->node->nid))->fetchField();
- return $id;
- }
-
- /**
- * Revrieves source from $node if it exists.
- *
- * @return FeedsSource
- * Instance of FeedsSource class fetched. Or FALSE if failed.
- */
- public function source() {
- $source = FALSE;
-
- if ($this->importerId) {
- $source = feeds_source($this->importerId, $this->node->nid);
- }
-
- return $source instanceof FeedsSource ?
- $source :
- FALSE;
- }
-
- /**
- * Revrieves importer from $node if it exists.
- */
- public function importer() {
- if ($this->importerId) {
- $importer = feeds_importer($this->importerId, $this->node->nid);
- if ($importer) {
- return $importer;
- }
- }
- return FALSE;
- }
-
- /**
- * Returns Importer ID.
- */
-
- /**
- * Retrieves importers from node and instantiates FeedsSource.
- */
- public function feed($node) {
- $importer_ids = feeds_get_importer_ids($node->type, $node->nid);
- foreach ($importer_ids as $importer_id) {
- $importer = feeds_importer($importer_id, $node->nid);
- $source = feeds_source($importer_id, $node->nid);
- $source_input = isset($source->config[$source->importer->config['fetcher']['plugin_key']]['source']) ? $source->config[$source->importer->config['fetcher']['plugin_key']]['source'] : '';
- if ($source_input) {
- return array('source' => $source, 'importer' => $importer);
- }
- }
- return FALSE;
- }
-
- /**
- * Returns number of records in the datastore.
- */
- public function records() {
- if ($records = dkan_datastore_records($this->node->nid)) {
- return $records;
- }
- else {
- return FALSE;
- }
- }
-
- /**
- * Retrieves current Datastore status.
- */
- public function status() {
- module_load_include('module', 'dkan_datastore', 'dkan_datastore');
- return dkan_datastore_status($this->node);
- }
-
- /**
- * Construcs api endpoint.
- */
- public function apiUri() {
- return url($this->endpoint, array(
- 'query' => array(
- 'resource_id' => $this->uuid,
- 'limit' => 5,
- ),
- 'absolute' => TRUE,
- ));
- }
-
- /**
- * Retrievs number of rows from the Datastore.
- */
- public function rows() {
- if ($this->status == DKAN_DATASTORE_EXISTS) {
- $result = db_select($this->tableName, 't')->addExpression('count(t.timestamp)')->execute();
- return $result->fetchField();
- }
- return FALSE;
- }
-
- /**
- * Retrieves headers from Datastore.
- */
- public function headers() {
- if ($this->status == DKAN_DATASTORE_EXISTS) {
- $table = data_get_table($this->tableName);
- if (isset($table->meta['fields'])) {
- $headers = array();
- foreach ($table->meta['fields'] as $safe_name => $label) {
- $headers[$safe_name] = $label['label'];
- }
- return $headers;
- }
- }
- return FALSE;
- }
-
- /**
- * Creates manage form for Datastore.
- */
- public function manageForm(&$form_state) {
- $node = $this->node;
- module_load_include('inc', 'feeds', 'feeds.pages');
- $total_progress = 0;
- $importer_id = $this->importerId;
- $form = array();
- if ($importer_id) {
- $source = $this->source;
- $importer = $this->importer;
- $plugin = $source->importer->config['fetcher']['plugin_key'];
- $form[$importer_id]['source_status'] = array(
- '#type' => 'item',
- '#title' => t('@source_name: Status', array('@source_name' => $source->importer->config['name'])),
- '#markup' => $this->statusMessage(),
- );
- $form['#feed_nid'] = $node->nid;
- $form['#redirect'] = 'node/' . $node->nid;
- $source_input = isset($source->config[$plugin]['source']) ? $source->config[$plugin]['source'] : '';
- $default_values = $source->config[$plugin];
- if ($source_input) {
- $form[$importer_id]['settings'] = array(
- '#type' => 'fieldset',
- '#title' => t('Settings'),
- );
- $form[$importer_id]['settings'] = $source->configForm($form_state);
- $form[$importer_id]['settings']['#submit'] = array('feeds_form_submit');
- $form[$importer_id]['#count'] = $source->itemCount();
- $progress = $source->progressImporting();
- $form = confirm_form($form, t('Import all content from source?'), 'node/' . $node->nid, '', t('Import'), t('Cancel'), 'confirm feeds update');
- $form[$importer_id]['description']['#weight'] = '-10';
- $form[$importer_id]['source_status']['#weight'] = '-9';
- if (isset($form[$importer_id]['settings']['FeedsCSVParser']['help'])) {
- unset($form[$importer_id]['settings']['FeedsCSVParser']['help']);
- }
- $form['importer_id'] = array(
- '#type' => 'value',
- '#value' => $importer_id,
- );
- if ($progress !== FEEDS_BATCH_COMPLETE) {
- $form['actions']['submit']['#disabled'] = TRUE;
- $form['actions']['submit']['#value'] = t('Importing (@progress %)', array('@progress' => number_format(100 * $progress, 0)));
- }
- }
- else {
- $form['no_source'] = array(
- '#markup' => t('The file type for this resource is not supported by the datastore.'),
- );
- }
- }
- else {
- $form['no_source'] = array(
- '#markup' => t('There is nothing to manage! You need to upload or link to a file in order to use the datastore.'),
- );
- }
- return $form;
-
- }
-
- /**
- * Takes values from form submit, saves updated config and starts import.
- */
- public function manageFormSubmit(&$form_state) {
- $importer_id = $form_state['values']['importer_id'];
- $source = feeds_source($importer_id, $this->node->nid);
- $parser = $source->importer->config['parser']['plugin_key'];
- $processor = $source->importer->config['processor']['plugin_key'];
- $this->addConfig(array($parser => $form_state['values'][$parser]));
- $this->addConfig(array($processor => $form_state['values'][$processor]));
- $this->save();
- $this->import();
- }
-
- /**
- * Adds configuration to feeds source.
- */
- public function addConfig($config) {
- $this->source->addConfig($config);
- }
-
- /**
- * Saves feeds source.
- */
- public function save() {
- $this->source->save();
- }
-
- /**
- * Provides themed status message for import.
- */
- public function statusMessage() {
- return feeds_source_status($this->source);
- }
-
- /**
- * Provides delete form.
- */
- public function deleteForm(&$form_state) {
- module_load_include('inc', 'feeds', 'feeds.pages');
- $total_progress = 0;
- $node = $this->node;
-
- $form = array();
- $importer_id = $this->importerId;
- $form['#redirect'] = 'node/' . $node->nid;
-
- if ($importer_id) {
- // Form cannot pass on source object.
- $form['#feed_nid'] = $node->nid;
- $source = feeds_source($importer_id, $node->nid);
- $form[$importer_id]['source_status'] = array(
- '#type' => 'item',
- '#title' => t('@source_name: Status', array('@source_name' => $source->importer->config['name'])),
- '#tree' => TRUE,
- '#markup' => $this->statusMessage(),
- );
- $progress = $source->progressClearing();
- $form['importer_ids'] = array(
- '#type' => 'value',
- '#value' => array($importer_id),
- );
-
- $form = confirm_form($form, t('Delete all items from source?'), $form['#redirect'], '', t('Delete'), t('Cancel'), 'confirm feeds update');
-
- if ($progress !== FEEDS_BATCH_COMPLETE) {
- $form['actions']['submit']['#disabled'] = TRUE;
- $form['actions']['submit']['#value'] = t('Deleting (@progress %)', array('@progress' => number_format(100 * $progress, 0)));
- }
- }
- else {
- $form['no_source'] = array(
- '#markup' => t('No feeds sources added to node.'),
- );
- }
- return $form;
- }
-
- /**
- * Feeds does the magic on this.
- *
- * TODO: undo feeds magic.
- */
- public function deleteFormSubmit(&$form_submit) {}
-
- /**
- * Provides form to drop datastore.
- */
- public function dropForm(&$form_state) {
- module_load_include('module', 'data', 'data');
- module_load_include('inc', 'feeds', 'feeds.pages');
- $node = $this->node;
- $form = array();
- $form['#redirect'] = 'node/' . $node->nid;
- $form = confirm_form($form, t('Drop this datastore?'), $form['#redirect'], '', t('Drop'), t('Cancel'), 'confirm drop');
-
- $importer_ids = feeds_get_importer_ids($node->type);
- $tables_to_drop = array();
-
- foreach ($importer_ids as $importer_id) {
- $source = feeds_source($importer_id, $node->nid);
- $table_name = feeds_flatstore_processor_table_name($source->id, $source->feed_nid);
- $table = data_get_table($table_name) ? data_get_table($table_name) : db_table_exists($table_name);
- if ($table) {
- array_push($tables_to_drop, $table_name);
- }
- }
- if (!count($tables_to_drop)) {
- $form['tables_absent'] = array(
- '#type' => 'markup',
- '#title' => t("Can't drop table"),
- '#tree' => TRUE,
- '#markup' => t('You need to have a file or link imported to the datastore in order to drop it.'),
- );
- $form['actions']['submit']['#disabled'] = TRUE;
- $form['actions']['submit']['#value'] = t('Drop (disabled)');
- }
- else {
- $form['tables_present'] = array(
- '#type' => 'markup',
- '#title' => t('Drop tables'),
- '#tree' => TRUE,
- '#markup' => t('Are you sure you want to drop the datastore?'),
- );
- $form['#tables_to_drop'] = $tables_to_drop;
- $form['actions']['submit']['#value'] = t('Drop');
- }
- return $form;
- }
-
- /**
- * Drop form submision.
- */
- public function dropFormSubmit(&$form_state) {
- module_load_include('module', 'data', 'data');
- module_load_include('module', 'dkan_datastore', 'dkan_datastore');
- foreach ($form_state['#tables_to_drop'] as $table_name) {
- $node = node_load($form_state['build_info']['args']['0']->nid);
- $has_file = dkan_datastore_file_field($node);
- $node = entity_metadata_wrapper('node', $node);
- $status = ($has_file) ? DKAN_DATASTORE_FILE_EXISTS : DKAN_DATASTORE_EMPTY;
- $node->field_datastore_status->set($status);
- $node->save();
- $this->dropTable($table_name);
- }
- drupal_set_message(t('Datastore dropped!'));
- }
-
- /**
- * Drop a datastore table.
- */
- private function dropTable($table_name) {
- $table = data_get_table($table_name);
- if ($table) {
- $table->drop();
- }
- elseif (db_table_exists($table_name)) {
- db_drop_table($table_name);
- }
- }
-
- /**
- * Provides API form.
- */
- public function apiForm() {
- // TODO: Make a theme function.
- $output = '
' . t('DKAN Datastore API') . '
';
- if ($importer_id = $this->importerId) {
- $source_config = feeds_source($importer_id, $this->node->nid);
- $fetcher = get_class($source_config->importer->fetcher);
- $source = isset($source_config->config[$fetcher]['source']) ? $source_config->config[$fetcher]['source'] : '';
- if ($source) {
- $table_name = feeds_flatstore_processor_table_name($source_config->id, $source_config->feed_nid);
- if (!db_table_exists($table_name)) {
- $output .= t('This resources has a file that has not been added to the datastore.');
- }
- else {
- $progress = $source_config->progressImporting();
- if ($progress != FEEDS_BATCH_COMPLETE) {
- $output .= t('This data source has not yet completed importing. Import is at @progress%. The API for this data source will be available upon the completion of the import process.', array('@ progress' => number_format(100 * $progress, 0)));
- }
- else {
- $output .= t('Access resource data via a web API with powerful query support.');
- $output .= '
' . t('Resource ID') . '
';
- $output .= t("The Resource ID for this resource is %id", array('%id' => $this->node->uuid));
- $output .= '
' . t('Example Query') . '
';
- $url = $this->apiUri();
- $output .= '
' . l($url, $url) . '
';
- $output .= '
' . t('Query this datastore and return first five results') . '
';
- $output .= '
' . t('Documentation') . '
';
- $output .= '
' . t('See DKAN API documentation for more details: DKAN Datastore API') . '
';
- }
- }
- return $output;
- }
- }
- else {
- $output .= t('No files have been added to the datastore.');
- }
- return $output;
-
- }
-
- /**
- * Starts import.
- */
- public function import() {
- $feed = isset($this->feed) ? $this->feed : $this->feed($this->node);
- $feed['source']->startImport();
- }
-
- /**
- * Starts import without invoking batch UI process.
- *
- * @return bool
- * Status of the import operation. TRUE if successful, FALSE if failed.
- */
- public function importByCli() {
- $source = $this->source();
-
- if (is_object($source)) {
- // Prepare for background process.
- $source_prepared = $this->setupSourceBackground($source);
- }
-
- // Check we got the source up and ready.
- if (!$source_prepared) {
- watchdog('dkan_datastore', "Failed to load or prepare the source.",
- array(), WATCHDOG_ERROR);
- return FALSE;
- }
-
- // Import new items.
- try {
- while (FEEDS_BATCH_COMPLETE != $source->import()) {
- continue;
- };
- }
- catch (Exception $exception) {
- watchdog('dkan_datastore', "Source import crashed with exception: \"@exception_message\".",
- array('@exception_message' => $exception->getMessage()), WATCHDOG_ERROR);
- return FALSE;
- }
-
- return TRUE;
- }
-
- /**
- * Updates the resource file using the URI.
- *
- * @param string $uri
- * The URI, ie public://myfile.png, for the file.
- * @param bool $copy_file
- * A boolean indicating whether to copy the file locally.
- * If set to TRUE the file is copied locally.
- */
- public function updateFromFileUri($uri = '', $copy_file = TRUE) {
- $file = FALSE;
-
- // Sanity check: make sure the file exits.
- if (!file_exists($uri)) {
- return FALSE;
- }
-
- if ($copy_file) {
- // Generate destination based on the URI.
- $destination = 'public://' . drupal_basename($uri);
-
- $data = file_get_contents($uri);
- // Using the same name can break the update process when the current file
- // is cleaned.
- $file = file_save_data($data, $destination, FILE_EXISTS_RENAME);
- }
- else {
- $file = file_uri_to_object($uri);
- }
-
- // At this point we should have a valid file object.
- if (!$file) {
- return FALSE;
- }
-
- return $this->updateFromFile($file);
- }
-
- /**
- * Update resource with file.
- *
- * @param object $file
- * Drupal file object.
- * @param bool $use_current_setttings
- * If set to TRUE use the current file visibility settings.
- */
- public function updateFromFile($file, $use_current_setttings = TRUE) {
- // Get current file.
- $current_file = $this->file();
-
- if ($current_file && $use_current_setttings) {
- // Copy view settings from current file.
- if (isset($current_file->map)) {
- $file->map = $current_file->map;
- }
- if (isset($current_file->grid)) {
- $file->grid = $current_file->grid;
- }
- if (isset($current_file->graph)) {
- $file->graph = $current_file->graph;
- }
-
- // Delete current file.
- $this->deleteFile();
- }
-
- // Save the file if new.
- if (empty($file->fid)) {
- file_save($file);
- }
-
- $file_field = dkan_datastore_file_upload_field();
- $this->node->{$file_field} = array(LANGUAGE_NONE => array((array) $file));
- node_save($this->node);
-
- return TRUE;
- }
-
- /**
- * Delete file from datastore.
- */
- public function deleteFile() {
-
- // Get current file.
- $current_file = $this->file();
-
- if ($current_file) {
- // If the datastore has items, delete them.
- $this->deleteItems();
- // Delete file.
- file_delete($current_file, TRUE);
- return TRUE;
- }
- else {
- return FALSE;
- }
- }
-
- /**
- * Delete file from datastore.
- */
- public function createFromFileUri($uri = '', $copy_file = TRUE) {
-
- if ($uri) {
- return $this->updateFromFileUri($uri, $copy_file);
- }
- else {
- return FALSE;
- }
- }
-
- /**
- * Set up of source and importer for background operations.
- *
- * @param FeedsSource $source
- * The FeedsSource object to work on.
- *
- * @return bool
- * TRUE if the new configuration is applyed to the source. FALSE if not.
- */
- protected function setupSourceBackground(FeedsSource &$source) {
- if ($source instanceof FeedsSource) {
- // IMPROVEMENT: Allow configuration of parser.
- // IMPROVEMENT: Allow configuration of processor.
- // Code added to fix this issue: https://www.drupal.org/node/2087091
- // The feeds source does not get cleaned up properly.
- // Ask it for its states so they are all present.
- $source->state(FEEDS_FETCH);
- $source->state(FEEDS_PROCESS);
- $source->state(FEEDS_PROCESS_CLEAR);
-
- $config = array(
- 'process_in_background' => TRUE,
- );
- $source->importer->addConfig($config);
-
- $source->save();
- return TRUE;
- }
-
- // Something wrong happened. Fail.
- watchdog('dkan_datastore',
- "Failed to setup the source for background operations.",
- array(), WATCHDOG_ERROR);
- return FALSE;
- }
-
- /**
- * Delete items in datastore.
- *
- * @return bool
- * Status of the operation. TRUE if successful, FALSE if failed.
- */
- protected function deleteItems() {
- // If the datastore has items, delete them.
- if ($this->status() == DKAN_DATASTORE_EXISTS) {
- // Set up source and importer.
- $source = $this->source();
- $source_ready = $this->setupSourceBackground($source);
-
- if (!$source_ready) {
- watchdog('dkan_datastore',
- "Delete failed. Cannot instantiate the source or source not ready.",
- array(), WATCHDOG_ERROR);
- return FALSE;
- }
-
- // Delete items.
- try {
- while (FEEDS_BATCH_COMPLETE != $source->clear()) {
- continue;
- };
- }
- catch (Exception $exception) {
- // Log error and fail.
- watchdog('dkan_datastore',
- "Delete failed. Operation crashed with exception: \"@exception_message\".",
- array('@exception_message' => $exception->getMessage()),
- WATCHDOG_ERROR);
- return FALSE;
- }
- }
-
- return TRUE;
- }
-
- /**
- * Adds row to table.
- *
- * @param array $row
- * Key/value array of table fields to data.
- *
- * @return string
- * Autoincrement id number for row if successful.
- */
- public function addRow(array $row = array()) {
- if (is_array($row) && $row) {
- $defaults = array(
- 'timestamp' => time(),
- 'feeds_entity_id' => $this->node->nid,
- );
- $row = array_merge($defaults, $row);
- $insert = db_insert($this->tableName)
- ->fields($row)
- ->execute();
-
- return $insert;
- }
- else {
- return FALSE;
- }
- }
-
- /**
- * Deletes row from table.
- *
- * @param string $field
- * Database field to use for condition value.
- * @param string $value
- * Condition value from which to delete.
- *
- * @return string
- * Number of rows deleted.
- */
- public function deleteRow($field, $value) {
- $num_deleted = db_delete($this->tableName)
- ->condition($field, $value)
- ->execute();
-
- return $num_deleted;
- }
-
-}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/includes/DkanDatastoreFastImport.inc b/profiles/dkan/modules/dkan/dkan_datastore/includes/DkanDatastoreFastImport.inc
deleted file mode 100644
index 7e595b502fc..00000000000
--- a/profiles/dkan/modules/dkan/dkan_datastore/includes/DkanDatastoreFastImport.inc
+++ /dev/null
@@ -1,78 +0,0 @@
-node->nid);
- $parser = $source->importer->config['parser']['plugin_key'];
- $processor = $source->importer->config['processor']['plugin_key'];
- $this->addConfig(array($parser => $form_state['values'][$parser]));
- $this->addConfig(array($processor => $form_state['values'][$processor]));
- $this->save();
-
- // Bypass feeds to improve the import performance.
- $table = feeds_flatstore_processor_table($source, array());
- $node = entity_metadata_wrapper('node', $form_state['build_info']['args'][0]);
- $file = $node->field_upload->value();
- $file_remote = $node->field_link_remote_file->value();
- $filesize = filesize(drupal_realpath($file->uri));
- $use_fast_import = $form_state['values']['use_fast_import'];
- $queue_filesize_threshold = parse_size(variable_get('queue_filesize_threshold', self::QUEUE_FILESIZE_THRESHOLD_DEFAULT));
- variable_set('quote_delimiters', $form_state['values']['quote_delimiters']);
- variable_set('lines_terminated_by', $form_state['values']['lines_terminated_by']);
- variable_set('fields_escaped_by', $form_state['values']['fields_escaped_by']);
- variable_set('dkan_datastore_fast_import_load_empty_cells_as_null', $form_state['values']['dkan_datastore_fast_import_load_empty_cells_as_null']);
-
- // If a remote file is provided we fallback to feeds importer
- // else we perform a fast import.
- if (!empty($file_remote) || !$use_fast_import) {
- $this->import();
- }
- else {
- // Avoid the queueing the resource if it's small.
- if ($filesize > $queue_filesize_threshold) {
- $item = array(
- 'source' => $source,
- 'node' => $node,
- 'table' => $table,
- 'config' => $form_state['values'][$parser],
- );
- DrupalQueue::get(dkan_datastore_fast_import_queue_name())->createItem($item);
- drupal_set_message(t('File was succesfully enqueued to be imported and will be available in the datastore in a few minutes'), 'status');
- }
- else {
- try {
- dkan_datastore_fast_import_import($source, $node, $table, $form_state['values'][$parser]);
- drupal_set_message(t('File was succesfully imported into the datastore'), 'status');
- }
- catch (Exception $e) {
- drupal_set_message(t('An error occurred trying to import this file: @error', array('@error' => $e->getMessage())), 'error');
- }
- }
- }
- }
-
-}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_api/dkan_datastore_api.drush.inc b/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_api/dkan_datastore_api.drush.inc
deleted file mode 100644
index 5b172b99fb1..00000000000
--- a/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_api/dkan_datastore_api.drush.inc
+++ /dev/null
@@ -1,245 +0,0 @@
- array('dkan_datastore_api'),
- 'aliases' => array('dsd'),
- 'description' => 'Drops the datastore.',
- 'callback' => 'dkan_datastore_api_datastore_drop_command',
- 'arguments' => array(
- 'id' => 'Datastore (or resource) id.',
- ),
- );
-
- // Get the number of rows in the datastore.
- $items['datastore-rows'] = array(
- 'drupal dependencies' => array('dkan_datastore_api'),
- 'aliases' => array('dsr'),
- 'description' => 'Get the number of rows in the datastore.',
- 'callback' => 'dkan_datastore_api_datastore_rows_command',
- 'arguments' => array(
- 'id' => 'Datastore (or resource) id.',
- ),
- );
-
- // Update datastore by file.
- $items['datastore-update'] = array(
- 'drupal dependencies' => array('dkan_datastore_api'),
- 'aliases' => array('dsu'),
- 'description' => 'Updates the datastore with the file.',
- 'callback' => 'dkan_datastore_api_datastore_update_command',
- 'arguments' => array(
- 'id' => 'Datastore (or resource) id.',
- 'file_path' => 'The path to the file.',
- ),
- );
-
- // Create datastore by file.
- $items['datastore-create'] = array(
- 'drupal dependencies' => array('dkan_datastore_api'),
- 'aliases' => array('dsc'),
- 'description' => 'Creates a datastore with the file.',
- 'callback' => 'dkan_datastore_api_datastore_create_command',
- 'arguments' => array(
- 'file' => 'The path to the file.',
- ),
- );
-
- // Delete datastore file.
- $items['datastore-file-delete'] = array(
- 'drupal dependencies' => array('dkan_datastore_api'),
- 'aliases' => array('dsfd'),
- 'description' => 'Deletes the file on a datastore.',
- 'callback' => 'dkan_datastore_api_datastore_file_delete_command',
- 'arguments' => array(
- 'id' => 'Datastore (or resource) id.',
- ),
- );
-
- // Show the URI of the datastore file.
- $items['datastore-file-uri'] = array(
- 'drupal dependencies' => array('dkan_datastore_api'),
- 'aliases' => array('dsfuri'),
- 'description' => 'Shows the URI of the datastore file.',
- 'callback' => 'dkan_datastore_api_datastore_file_uri_command',
- 'arguments' => array(
- 'id' => 'Datastore (or resource) id.',
- ),
- );
-
- // Show the URL of the datastore file.
- $items['datastore-file-url'] = array(
- 'drupal dependencies' => array('dkan_datastore_api'),
- 'aliases' => array('dsfurl'),
- 'description' => 'Shows the URL of the datastore file.',
- 'callback' => 'dkan_datastore_api_datastore_file_url_command',
- 'arguments' => array(
- 'id' => 'Datastore (or resource) id.',
- ),
- );
-
- return $items;
-}
-
-/**
- * Callback for the datastore-drop command.
- */
-function dkan_datastore_api_datastore_drop_command($id = NULL) {
-
- drush_print('The command is not implemented yet.');
-}
-
-/**
- * Callback for the datastore-rows command.
- */
-function dkan_datastore_api_datastore_rows_command($id = NULL) {
-
- drush_print('The command is not implemented yet.');
-}
-
-/**
- * Callback for the datastore-update command.
- */
-function dkan_datastore_api_datastore_update_command($id = NULL, $file_path = NULL) {
- // Sanity check the arguments.
- foreach (array('id', 'file_path') as $argument) {
- if (!$$argument) {
- drush_print(t('%argument is required.', array('%argument' => $argument)));
- return FALSE;
- }
- }
-
- $datastore = get_datastore($id);
-
- if (!$datastore) {
- $message = t('Failed to load Datastore with id "%id"', array('%id' => $id));
- drush_print($message);
- return FALSE;
- }
- else {
- $message = t('Datastore loaded.');
- drush_print($message);
- }
-
- if (!$datastore->updateFromFileUri($file_path, TRUE)) {
- drush_print(t('Failed to upload the file.'));
- return FALSE;
- }
- else {
- drush_print(t('File copied and uploaded.'));
- }
-
- if (!$datastore->importByCli()) {
- $message = t('Failed to import the file to the datastore.');
- drush_print($message);
- return FALSE;
- }
- else {
- $message = t('File imported to the datastore.');
- drush_print($message);
- }
-}
-
-/**
- * Callback for the datastore-create command.
- */
-function dkan_datastore_api_datastore_create_command($file = NULL) {
-
- if ($datastore = dkan_datastore_go()) {
-
- $result = $datastore->createFromFileUri($file->uri, TRUE);
-
- if ($result) {
- drush_print('The new file has been saved and the items were imported.');
- drush_print('A new datastore was created with ID: ' . $datastore->node->uuid);
- }
- else {
- drush_print('The file could not be loaded.');
- }
- }
-}
-
-/**
- * Callback for the datastore-file-delete command.
- */
-function dkan_datastore_api_datastore_file_delete_command($id = NULL) {
-
- if ($datastore = get_datastore($id)) {
- $result = $datastore->deleteFile();
-
- if ($result) {
- drush_print('The file has been deleted and the items were removed.');
- }
- else {
- drush_print('The file could not be deleted.');
- }
- }
-}
-
-/**
- * Callback for the datastore-file-uri command.
- */
-function dkan_datastore_api_datastore_file_uri_command($id = NULL) {
-
- if ($datastore = get_datastore($id)) {
-
- if ($datastore->file()) {
- drush_print('The URI of the file in the datastore is: ' . $datastore->fileUri());
- }
- else {
- drush_print('The datastore has no file.');
- }
-
- }
-
-}
-
-/**
- * Callback for the datastore-file-url command.
- */
-function dkan_datastore_api_datastore_file_url_command($id = NULL) {
-
- if ($datastore = get_datastore($id)) {
-
- if ($datastore->file()) {
- drush_print('The URL of the file in the datastore is: ' . $datastore->fileUrl());
- }
- else {
- drush_print('The datastore has no file.');
- }
- }
-}
-
-/**
- * Get datastore based on id.
- */
-function get_datastore($id) {
-
- // Show an error if the resource_id is not present.
- if (!$id) {
- drush_print('No datastore id was provided.');
- return FALSE;
- }
-
- // Search for a datastore associated with the resource.
- try {
- $datastore = dkan_datastore_go($id);
-
- }
- catch (Exception $e) {
- drush_print('There are no datastores with the specified id.');
- return FALSE;
- }
-
- return $datastore;
-}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_api/dkan_datastore_api.info b/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_api/dkan_datastore_api.info
index a1aa377a1b5..567aa3da6b4 100644
--- a/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_api/dkan_datastore_api.info
+++ b/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_api/dkan_datastore_api.info
@@ -3,4 +3,5 @@ description = Access datastore info over json.
package = DKAN API
core = 7.x
dependencies[] = services
-version = 7.x-1.15.5
+dependencies[] = dkan_datastore
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_api/dkan_datastore_api.module b/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_api/dkan_datastore_api.module
index 7dc0d355dc9..9e6e6f038df 100644
--- a/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_api/dkan_datastore_api.module
+++ b/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_api/dkan_datastore_api.module
@@ -7,8 +7,30 @@
* TODO: Add search element, investigate caching, add uuids.
*/
+use Dkan\Datastore\Manager\Factory;
+use Dkan\Datastore\Resource;
+
define('DKAN_DATASTORE_API_DEFAULT_TABLE_ALIAS', 't');
+/**
+ * Implements hook_menu().
+ */
+function dkan_datastore_api_menu() {
+ $items = [];
+
+ $items['node/%node/api'] = array(
+ 'title' => 'Data API',
+ 'page callback' => 'dkan_datastore_api_api_page',
+ 'page arguments' => array(1),
+ 'access callback' => 'dkan_datastore_api_api_page_access',
+ 'access arguments' => array(1),
+ 'weight' => '25',
+ 'type' => MENU_LOCAL_TASK,
+ );
+
+ return $items;
+}
+
/**
* Given all the possible fields input returns a normalized version.
*
@@ -32,9 +54,7 @@ function normalize_fields($fields, $resources) {
if ((is_string($fields) && $fields == '*') || in_array($alias, array_keys($fields))) {
// Fields comes with table prefix and fields are not specified.
if (is_assoc($fields) && is_string($fields[$alias]) && $fields[$alias] == '*') {
-
- $table = dkan_datastore_api_tablename($id);
- $schema = drupal_get_schema($table);
+ $schema = dkan_datastore_api_get_schema($id);
$new_fields = build_qualified_fields(array_keys($schema['fields']), $alias);
// Fields comes with table prefix either using an
@@ -56,8 +76,7 @@ function normalize_fields($fields, $resources) {
// from the table schema.
}
elseif (is_string($fields) && $fields == '*') {
- $table = dkan_datastore_api_tablename($id);
- $schema = drupal_get_schema($table);
+ $schema = dkan_datastore_api_get_schema($id);
$new_fields = build_qualified_fields(array_keys($schema['fields']), $alias);
// Fields comes without table prefix and split by commas.
@@ -81,7 +100,7 @@ function normalize_fields($fields, $resources) {
* For example: table_name.field_name.
*/
function build_qualified_fields($fields, $alias) {
- return array_reduce($fields, function ($memo, $field) use ($alias, $excludes) {
+ return array_reduce($fields, function ($memo, $field) use ($alias) {
if (!dkan_datastore_api_field_excluded($field)) {
$memo[] = $alias . '.' . normalize_field_name($field);
}
@@ -139,21 +158,15 @@ function schema_fields($resource_ids) {
$fields = array();
foreach ((array) $resource_ids as $id) {
- $table = dkan_datastore_api_tablename($id);
- if ($data_table = data_get_table($table)) {
- $schema = $data_table->table_schema;
- $meta = $data_table->meta;
- }
- else {
- $schema = drupal_get_schema($table);
- $meta = array('fields' => array());
- }
+ $schema = dkan_datastore_api_get_schema($id);
+ $meta = array('fields' => array());
$meta_fields = array_merge_recursive($meta['fields'], $schema['fields']);
$fields = array_merge_recursive((array) $fields, (array) $meta_fields);
}
// Remove flatstore processor fields.
+ // This is feeds specific, It should not be here.
unset($fields['timestamp']);
unset($fields['feeds_entity_id']);
unset($fields['feeds_flatstore_entry_id']);
@@ -177,10 +190,9 @@ function dkan_datastore_api_init() {
// If the request doesn't have datastore_search, we need to redirect 'q' to
// 'query' if it does not have /api/action/datastore/search in it.
if (isset($_GET['resource_id']) && !strpos($_SERVER['REQUEST_URI'], 'datastore_search')) {
- $request = explode('/', $_SERVER['REQUEST_URI']);
$query = explode('/', $_GET['q']);
$query_val = array();
- foreach ($query as $key => $val) {
+ foreach ($query as $val) {
$query_val[$val] = 1;
}
if (!isset($query_val['api']) && !isset($query_val['action']) && !isset($query_val['datastore'])) {
@@ -235,8 +247,6 @@ function dkan_datastore_api_ctools_plugin_api($owner, $api) {
*/
function dkan_datastore_api_services_resources() {
- $datastore_node_type = dkan_datastore_node_type();
-
return array(
'dkan_datastore_search' => array(
'index' => array(
@@ -266,109 +276,6 @@ function dkan_datastore_api_services_resources() {
'access arguments append' => FALSE,
),
),
- 'dkan_datastore' => array(
- 'operations' => array(
- 'create' => array(
- 'file' => array(
- 'type' => 'module',
- 'module' => 'dkan_datastore_api',
- 'name' => 'dkan_datastore_api',
- ),
- 'help' => 'Create a store with a file with base64 encoded data',
- 'callback' => 'dkan_datastore_api_datastore_create',
- 'access arguments' => array('create ' . $datastore_node_type . ' content'),
- 'args' => array(
- array(
- 'name' => 'file',
- 'type' => 'array',
- 'description' => t('An array representing a file.'),
- 'source' => 'data',
- 'optional' => FALSE,
- ),
- ),
- ),
- 'update' => array(
- 'file' => array(
- 'type' => 'module',
- 'module' => 'dkan_datastore_api',
- 'name' => 'dkan_datastore_api',
- ),
- 'help' => 'Update a store with a file with base64 encoded data',
- 'callback' => 'dkan_datastore_api_datastore_update',
- 'access arguments' => array('edit any ' . $datastore_node_type . ' content'),
- 'args' => array(
- array(
- 'name' => 'id',
- 'optional' => FALSE,
- 'type' => 'string',
- 'description' => 'Datastore ID',
- 'default value' => '',
- 'source' => array('path' => 0),
- ),
- array(
- 'name' => 'file',
- 'type' => 'array',
- 'description' => t('An array representing a file.'),
- 'source' => 'data',
- 'optional' => FALSE,
- ),
- ),
- ),
- ),
- 'actions' => array(
- 'create' => array(
- 'help' => 'Create store with a file with raw data.',
- 'file' => array(
- 'type' => 'module',
- 'module' => 'dkan_datastore_api',
- 'name' => 'dkan_datastore_api',
- ),
- 'callback' => 'dkan_datastore_api_datastore_create_raw',
- 'access arguments' => array('create ' . $datastore_node_type . ' content'),
- ),
- 'update' => array(
- 'help' => 'Update a store with a file with raw data.',
- 'file' => array(
- 'type' => 'module',
- 'module' => 'dkan_datastore_api',
- 'name' => 'dkan_datastore_api',
- ),
- 'callback' => 'dkan_datastore_api_datastore_update_raw',
- 'access arguments' => array('edit any ' . $datastore_node_type . ' content'),
- 'args' => array(
- array(
- 'name' => 'id',
- 'optional' => FALSE,
- 'type' => 'string',
- 'description' => 'Datastore ID',
- 'default value' => '',
- 'source' => array('path' => 1),
- ),
- ),
- ),
- ),
- ),
- 'dkan_datastore_file' => array(
- 'delete' => array(
- 'file' => array(
- 'type' => 'module',
- 'module' => 'dkan_datastore_api',
- 'name' => 'dkan_datastore_api',
- ),
- 'callback' => 'dkan_datastore_api_datastore_file_delete',
- 'access arguments' => array('edit any ' . $datastore_node_type . ' content'),
- 'args' => array(
- array(
- 'name' => 'id',
- 'optional' => FALSE,
- 'type' => 'string',
- 'description' => 'Datastore ID',
- 'default value' => '',
- 'source' => array('path' => 0),
- ),
- ),
- ),
- ),
);
}
@@ -380,14 +287,7 @@ function dkan_datastore_api_services_resources() {
function dkan_datastore_api_datastore_index() {
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$params = dkan_datastore_api_get_params();
- // Add anonoymous user to hash so refresh after login relaxes query limits.
- $cache_hash = md5(json_encode($params)) . user_is_anonymous();
-
- if ($cache = cache_get($cache_hash)) {
- return $cache->data;
- }
$result = dkan_datastore_api_query($params);
- cache_set($cache_hash, $result, 'cache', CACHE_TEMPORARY);
}
elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
$result = dkan_datastore_api_multiple_query();
@@ -441,46 +341,52 @@ function dkan_datastore_api_get_params($params = array()) {
* I decided to wrap it to make easy to decouple it.
*/
function normalize_field_name($field) {
- return feeds_flatstore_processor_safe_name($field);
+ return $field;
}
/**
* Performs a single query against the datastore.
*/
function dkan_datastore_api_query($params) {
- extract($params);
+
$data_select = NULL;
- $aggregations = array(
- 'sum' => $sum,
- 'avg' => $avg,
- 'max' => $max,
- 'min' => $min,
- 'count' => $count,
- );
+
try {
- $resource_ids = (array) $resource_ids;
- list($alias, $resource_id) = each($resource_ids);
+ $resource_ids = (array) $params['resource_ids'];
+
+ if (empty($resource_ids)) {
+ throw new \Exception("The resource_id is a required parameter.");
+ }
+ $keys = array_keys($resource_ids);
+ $values = array_values($resource_ids);
+ $alias = array_shift($keys);
+ $resource_id = array_shift($values);
+
$alias = is_string($alias) ? $alias : DKAN_DATASTORE_API_DEFAULT_TABLE_ALIAS;
$table = dkan_datastore_api_tablename($resource_id);
$data_select = db_select($table, $alias);
- dkan_datastore_api_limit($data_select, $offset, $limit);
- dkan_datastore_api_group_by($data_select, $group_by);
- dkan_datastore_api_aggregations($data_select, $aggregations);
- dkan_datastore_api_sort($data_select, $sort, $alias);
- dkan_datastore_api_where($data_select, $filters);
+ dkan_datastore_api_limit($data_select, $params['offset'], $params['limit']);
+ dkan_datastore_api_group_by($data_select, $params['group_by']);
+ dkan_datastore_api_aggregations($data_select, $params['aggregations']);
+ dkan_datastore_api_sort($data_select, $params['sort'], $alias);
+ dkan_datastore_api_where($data_select, $params['filters']);
// Query for a single resource.
if (count($resource_ids) > 1) {
// We can't have multiple resources if we can't join them.
- if (!$join) {
+ if (!$params['join']) {
throw new Exception('Mulitple resources require joins.');
}
- while (list($alias, $resource_id) = each($resource_ids)) {
- $table = dkan_datastore_api_tablename($resource_id);
- dkan_datastore_api_join($data_select, $table, $join);
+ $counter = 0;
+ foreach ($resource_ids as $alias => $resource_id) {
+ if ($counter >= 1) {
+ $table = dkan_datastore_api_tablename($resource_id);
+ dkan_datastore_api_join($data_select, $table, $params['join']);
+ }
+ $counter++;
}
}
@@ -489,18 +395,23 @@ function dkan_datastore_api_query($params) {
// avoid alias renaming we need to move fields below the
// join process otherwise join alias will be renamed using
// incremental counters.
- dkan_datastore_api_select_fields($data_select, $fields, $resource_ids);
+ dkan_datastore_api_select_fields($data_select, $params['fields'], $resource_ids);
- if ($query && $query != 'api/3/action/datastore_search') {
- dkan_datastore_api_use_indexes($data_select, $table, $query);
+ if (!empty($params['query']) && $params['query'] != 'api/3/action/datastore_search') {
+ dkan_datastore_api_use_indexes($data_select, $table, $params['query']);
}
$count = dkan_datastore_api_count($data_select);
$results = $data_select->execute();
- return dkan_datastore_api_output($data_select, $results, $table, $fields, $resource_ids, $count, $limit);
+
+ return dkan_datastore_api_output($data_select, $results, $table, $params['fields'], $resource_ids, $count, $params['limit']);
}
- catch (Exception $ex) {
- return array('sql' => dkan_datastore_api_debug($data_select), 'error' => array('message' => 'Caught exception: ' . $ex->getMessage()));
+ catch (Exception $e) {
+ $info = "";
+ if ($data_select) {
+ $info = dkan_datastore_api_debug($data_select);
+ }
+ return array('sql' => $info, 'error' => array('message' => 'Caught exception: ' . $e->getMessage()));
}
}
@@ -672,7 +583,7 @@ function dkan_datastore_api_aggregations(&$data_select, $aggregations, $alias =
*
* @todo Add SQL server support
*/
-function dkan_datastore_api_use_indexes(&$data_select, $table, $query) {
+function dkan_datastore_api_use_indexes(SelectQuery &$data_select, $table, $query) {
if (db_driver() == 'mysql') {
// Get fulltext fields.
$ft_result = db_query("SHOW INDEX IN {$table} WHERE Index_type = 'FULLTEXT'");
@@ -688,9 +599,20 @@ function dkan_datastore_api_use_indexes(&$data_select, $table, $query) {
}
// If no FULLTEXT indexes, try concat method if on mysql or postgres.
- if ($field_list = feeds_flatstore_fulltext_fields(drupal_get_schema($table))) {
+ /* @var $result DatabaseStatementBase */
+ $result = db_query("DESCRIBE {$table}");
+ $field_list = [];
+ foreach ($result->fetchAll() as $field_info) {
+ $field_list[] = $field_info->Field;
+ }
+
+ if (!empty($field_list)) {
if (db_driver() == 'mysql' || db_driver() == 'pgsql') {
- $data_select->where("CONCAT_WS(' ', $field_list) LIKE :query", array(':query' => "%$query%"));
+ $or = db_or();
+ foreach ($field_list as $field_name) {
+ $or->condition($field_name, "%{$query}%", "LIKE");
+ }
+ $data_select->condition($or);
}
// For MS SQL Server, use alternate concat syntax.
elseif (db_driver() == 'sqlsrv') {
@@ -742,30 +664,22 @@ function dkan_datastore_api_sort_clean($sort) {
* Returns table name or exeption.
*/
function dkan_datastore_api_tablename($resource_id) {
- static $tables;
- if (!isset($tables[$resource_id])) {
- $nid = db_query('SELECT nid FROM {node} WHERE uuid = :uuid', array(':uuid' => $resource_id))->fetchField();
- $source_id = dkan_datastore_api_get_feeds_source($nid);
- $table = feeds_flatstore_processor_table_name($source_id, $nid);
- if (db_table_exists($table) && dkan_datastore_datastore_api_access($nid)) {
- $tables[$resource_id] = $table;
- return $table;
- }
- else {
- throw new Exception('Resource ' . $resource_id . ' does not exist.');
- }
- }
- else {
- return $tables[$resource_id];
- }
+ $resource = Resource::createFromDrupalNodeUuid($resource_id);
+ /* @var $manager \Dkan\Datastore\Manager\ManagerInterface */
+ $manager = (new Factory($resource))->get();
+ $table = $manager->getTableName();
+ return $table;
}
/**
- * Retrieves source_id from feeds_source.
+ * Get the datastore table schema for a resource.
*/
-function dkan_datastore_api_get_feeds_source($nid) {
- $source_id = db_query("SELECT id FROM {feeds_source} WHERE feed_nid = :nid AND source != ''", array(':nid' => $nid))->fetchField();
- return $source_id ? $source_id : NULL;
+function dkan_datastore_api_get_schema($resource_id) {
+ $resource = Resource::createFromDrupalNodeUuid($resource_id);
+ /* @var $manager \Dkan\Datastore\Manager\ManagerInterface */
+ $manager = (new Factory($resource))->get();
+ $schema = $manager->getTableSchema();
+ return $schema;
}
/**
@@ -789,22 +703,19 @@ function dkan_datastore_api_build_query($table, $fields, $sort, $alias = DKAN_DA
function dkan_datastore_api_sort(&$data_select, $sort, $alias) {
if (!is_array($sort)) {
$sort = explode(' ', $sort);
- $order = $sort[0];
$columns = empty($sort) ? $sort : explode(',', $sort[0]);
}
else {
$columns = $sort;
}
-
- if (count($columns) == 1 && array_values($columns)[0] == '') {
- $data_select->orderBy($alias . '.feeds_flatstore_entry_id', 'ASC');
+ $vs = array_values($sort);
+ $ks = array_keys($sort);
+ $key = end($vs);
+ if (!in_array($key, ['desc', 'asc'])) {
+ $columns = $key;
+ $alias = end($ks);
}
- else {
- $key = end(array_values($sort));
- if (!in_array($key, ['desc', 'asc'])) {
- $columns = $key;
- $alias = end(array_keys($sort));
- }
+ if (!empty($columns)) {
foreach ($columns as $field => $order) {
$data_select->orderBy($alias . '.' . $field, $order);
}
@@ -814,7 +725,7 @@ function dkan_datastore_api_sort(&$data_select, $sort, $alias) {
/**
* Same as services version but not adding conditions.
*/
-function dkan_datastore_api_select_fields(&$data_select, $fields, $resource_ids) {
+function dkan_datastore_api_select_fields(SelectQuery &$data_select, $fields, $resource_ids) {
$resource_ids = is_assoc($resource_ids)
? $resource_ids
: array(DKAN_DATASTORE_API_DEFAULT_TABLE_ALIAS => $resource_ids[0]);
@@ -876,7 +787,12 @@ function dkan_datastore_api_output($data_select, $results, $table, $fields, $res
if (!dkan_datastore_api_field_excluded($name)) {
if (!empty($schema_fields[$name])) {
$new_name = $schema_fields[$name]['label'];
- $new_item->$new_name = $data;
+ if ($new_name) {
+ $new_item->$new_name = $data;
+ }
+ else {
+ $new_item->$name = $data;
+ }
}
else {
$new_item->$name = $data;
@@ -889,7 +805,7 @@ function dkan_datastore_api_output($data_select, $results, $table, $fields, $res
$return = new stdClass();
// Prepare schema.
- foreach (prepare_fields($fields) as $table_alias => $table_fields) {
+ foreach (prepare_fields($fields) as $table_fields) {
foreach ($table_fields as $table_field) {
$field = $schema_fields[$table_field];
$name = $field['label'];
@@ -914,211 +830,109 @@ function dkan_datastore_api_resource_help() {
}
/**
- * For arguments, see: dkan_datastore_api_services_resources().
- */
-function dkan_datastore_api_datastore_create($file) {
-
- $file = _services_arg_value($file, 'file');
-
- // If the file data or filename is empty then bail.
- if (!isset($file['file']) || empty($file['filename'])) {
- return services_error(t("Missing data the file upload can not be completed"), 500);
- }
-
- $file_saved = process_file_argument($file);
-
- if ($file_saved) {
- $datastore = dkan_datastore_go();
- if ($datastore) {
- if ($datastore->createFromFileUri($file_saved->uri, FALSE)) {
-
- $result = new stdClass();
- $result->result = 'success';
- $result->datastore_id = $datastore->node->uuid;
- $result->node_id = $datastore->node->nid;
- return $result;
- }
- }
- }
- else {
- return services_error(t('The store could not be created.'));
- }
-}
-
-/**
- * For arguments, see: dkan_datastore_api_services_resources().
+ * Returns sql statement for debug purposes.
*/
-function dkan_datastore_api_datastore_create_raw() {
-
- $validators = array(
- 'file_validate_extensions' => array(),
- 'file_validate_size' => array(),
- );
-
- foreach ($_FILES['files']['name'] as $field_name => $file_name) {
- $file = file_save_upload($field_name, $validators, file_default_scheme() . "://");
-
- if (!empty($file->fid)) {
- $file->status = FILE_STATUS_PERMANENT;
- file_save($file);
-
- // Required to be able to reference this file.
- file_usage_add($file, 'dkan_datastore_api', 'files', $file->fid);
-
- $datastore = dkan_datastore_go();
- if ($datastore) {
- if ($datastore->createFromFileUri($file->uri, FALSE)) {
-
- $result = new stdClass();
- $result->result = 'success';
- $result->datastore_id = $datastore->node->uuid;
- $result->node_id = $datastore->node->nid;
- return $result;
- }
- }
+function dkan_datastore_api_debug($data_select) {
+ if (is_object($data_select) && $data_select instanceof SelectQueryInterface) {
+ if (method_exists($data_select, 'preExecute')) {
+ $data_select->preExecute();
}
- else {
- return services_error(t('An unknown error occured'), 500);
+ $sql = (string)$data_select;
+ $quoted = array();
+ $connection = Database::getConnection();
+ foreach ((array)$data_select->arguments() as $key => $val) {
+ $quoted[$key] = $connection->quote($val);
}
+ $sql = strtr($sql, $quoted);
+ $sql = str_replace('\n', ' ', $sql);
+ return (string)$sql;
}
-
- return services_error(t('An unknown error occured'), 500);
+ return "";
}
/**
- * For arguments, see: dkan_datastore_api_services_resources().
+ * Callback for Data API instructions.
*/
-function dkan_datastore_api_datastore_update($id, $file) {
-
- $file = _services_arg_value($file, 'file');
-
- if (!isset($file['file']) || empty($file['filename'])) {
- return services_error(t("The file is missing or empty."), 500);
- }
+function dkan_datastore_api_api_page($node) {
+ if ($resource = Resource::createFromDrupalNode($node)) {
+ /* @var $manager \Dkan\Datastore\Manager\ManagerInterface */
+ $manager = (new Factory($resource))->get();
+ $status = $manager->getStatus();
+ switch ($status['data_import']) {
+ case $manager::DATA_IMPORT_DONE:
+ $output = dkan_datastore_api_instructions_output($node);
+ return $output;
- $file_saved = process_file_argument($file);
+ case $manager::DATA_IMPORT_UNINITIALIZED:
+ case $manager::DATA_IMPORT_IN_PROGRESS:
+ drupal_set_message(t('You need to add your file to the datastore in order to use the DATA API'));
+ $redirect_path = 'node/' . $node->nid . '/datastore';
+ break;
- if ($file_saved) {
- $datastore = dkan_datastore_go($id);
- if ($datastore) {
- if ($datastore->updateFromFileUri($file_saved->uri, FALSE)) {
-
- $result = new stdClass();
- $result->result = 'success';
- $result->datastore_id = $datastore->node->uuid;
- $result->node_id = $datastore->node->nid;
- return $result;
- }
+ default:
+ drupal_set_message(t('This resource can not be added to the datastore'));
+ $redirect_path = 'node/' . $node->nid;
+ break;
}
- }
- else {
- return services_error(t('The store could not be updated.'));
+ drupal_goto($redirect_path);
}
}
/**
- * For arguments, see: dkan_datastore_api_services_resources().
+ * Output for datastore API instructions.
*/
-function dkan_datastore_api_datastore_update_raw($id) {
-
- $validators = array(
- 'file_validate_extensions' => array(),
- 'file_validate_size' => array(),
- );
-
- foreach ($_FILES['files']['name'] as $field_name => $file_name) {
- $file = file_save_upload($field_name, $validators, file_default_scheme() . "://");
-
- if (!empty($file->fid)) {
- $file->status = FILE_STATUS_PERMANENT;
- file_save($file);
-
- file_usage_add($file, 'dkan_datastore_api', 'files', $file->fid);
-
- $datastore = dkan_datastore_go($id);
- if ($datastore) {
- if ($datastore->updateFromFileUri($file->uri, FALSE)) {
-
- $result = new stdClass();
- $result->result = 'success';
- $result->datastore_id = $datastore->node->uuid;
- $result->node_id = $datastore->node->nid;
- return $result;
- }
- }
- }
- else {
- return services_error(t('The store could not be updated.'));
- }
- }
-
- return services_error(t('An unknown error occured'), 500);
-}
+function dkan_datastore_api_instructions_output($node) {
+ // TODO: Make a theme function.
+ $output = '
' . t('DKAN Datastore API') . '
';
-/**
- * For arguments, see: dkan_datastore_api_services_resources().
- */
-function dkan_datastore_api_datastore_file_delete($id) {
+ $url = url('api/action/datastore/search.json', array(
+ 'query' => array(
+ 'resource_id' => $node->uuid,
+ 'limit' => 5,
+ ),
+ 'absolute' => TRUE,
+ ));
- $datastore = dkan_datastore_go($id);
- if ($datastore) {
- if ($datastore->deleteFile()) {
+ $output .= t('Access resource data via a web API with powerful query support.');
+ $output .= '
' . t('Resource ID') . '
';
+ $output .= t("The Resource ID for this resource is %id", array('%id' => $node->uuid));
+ $output .= '
' . t('Example Query') . '
';
+ $output .= '
' . l($url, $url) . '
';
+ $output .= '
' . t('Query this datastore and return first five results') . '
';
+ $output .= '
' . t('Documentation') . '
';
+ $output .= '
' . t('See DKAN API documentation for more details: DKAN Datastore API') . '
';
- $result = new stdClass();
- $result->result = 'success';
- $result->datastore_id = $datastore->node->uuid;
- $result->node_id = $datastore->node->nid;
- return $result;
- }
- }
+ return $output;
}
/**
- * Saves the received file.
+ * Access callback for Data API instructions.
*/
-function process_file_argument($file) {
-
- if (!isset($file['file']) || empty($file['filename'])) {
- return FALSE;
- }
-
- if (empty($file['filepath'])) {
- $file['filepath'] = file_default_scheme() . '://' . $file['filename'];
- }
- $dir = drupal_dirname($file['filepath']);
-
- if (!file_prepare_directory($dir, FILE_CREATE_DIRECTORY)) {
- return FALSE;
+function dkan_datastore_api_api_page_access($node) {
+ try {
+ $resource = Resource::createFromDrupalNode($node);
}
-
- if (!$file_saved = file_save_data(base64_decode($file['file']), $file['filepath'], FILE_EXISTS_RENAME)) {
+ catch (\Exception $e) {
return FALSE;
}
-
- if (isset($file['status']) && $file['status'] == 0) {
- $file_saved->status = FILE_STATUS_PERMANENT;
- file_save($file_saved);
- }
-
- file_usage_add($file_saved, 'dkan_datastore_api', 'files', $file_saved->fid);
-
- return $file_saved;
-}
-
-/**
- * Returns sql statement for debug purposes.
- */
-function dkan_datastore_api_debug($data_select) {
- if (method_exists($data_select, 'preExecute')) {
- $data_select->preExecute();
- }
- $sql = (string) $data_select;
- $quoted = array();
- $connection = Database::getConnection();
- foreach ((array) $data_select->arguments() as $key => $val) {
- $quoted[$key] = $connection->quote($val);
+ /* @var $manager \Dkan\Datastore\Manager\ManagerInterface */
+ if ($manager = (new Factory($resource))->get()) {
+ $status = $manager->getStatus();
+ if ($status['data_import'] == $manager::DATA_IMPORT_DONE) {
+ $type = 'resource';
+ $file_field = dkan_datastore_file_upload_field();
+ $link_field = dkan_datastore_file_link_field();
+ $upload = '';
+ $link = '';
+ if (isset($node->$file_field) && $node->$file_field) {
+ $upload = isset($node->{$file_field}[$node->language]) ? $node->{$file_field}[$node->language] : $node->{$file_field}[LANGUAGE_NONE];
+ }
+ if (isset($node->{$link_field}) && $node->{$link_field}) {
+ $link = isset($node->{$link_field}[$node->language]) ? $node->{$link_field}[$node->language] : $node->{$link_field}[LANGUAGE_NONE];
+ }
+ if ($node->type == $type && ($upload || $link)) {
+ return node_access('view', $node);
+ }
+ }
}
- $sql = strtr($sql, $quoted);
- $sql = str_replace('\n', ' ', $sql);
- return (string) $sql;
}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/dkan_datastore_fast_import.info b/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/dkan_datastore_fast_import.info
index 02175e8e4c1..90cfeb99013 100644
--- a/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/dkan_datastore_fast_import.info
+++ b/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/dkan_datastore_fast_import.info
@@ -3,4 +3,4 @@ description = Enable fast import for resources
core = 7.x
package = DKAN
dependencies[] = dkan_datastore
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/dkan_datastore_fast_import.install b/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/dkan_datastore_fast_import.install
index 63a1eb9230e..4e0ae7deaf8 100644
--- a/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/dkan_datastore_fast_import.install
+++ b/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/dkan_datastore_fast_import.install
@@ -9,5 +9,32 @@
* Implements hook_enable().
*/
function dkan_datastore_fast_import_enable() {
- dkan_datastore_check_database();
+ dkan_datastore_fast_import_check_database();
+}
+
+/**
+ * Check database is set with pdo flags.
+ */
+function dkan_datastore_fast_import_check_database() {
+ global $databases;
+ $target = Database::getConnection()->getTarget();
+ $key = Database::getConnection()->getKey();
+ $database = $databases[$target][$key];
+
+ if (!isset($database['pdo'])) {
+ drupal_set_message(t('Required PDO flags for dkan_datastore_fast_import were not found. This module requires PDO::MYSQL_ATTR_LOCAL_INFILE and PDO::MYSQL_ATTR_USE_BUFFERED_QUERY'), 'error', FALSE);
+ }
+ else {
+
+ $infile_enabled = array_key_exists(PDO::MYSQL_ATTR_LOCAL_INFILE, $database['pdo']) && $database['pdo'][PDO::MYSQL_ATTR_LOCAL_INFILE];
+ if (!$infile_enabled) {
+ drupal_set_message(t('Required PDO flag for dkan_datastore_fast_import were not found. This module requires PDO::MYSQL_ATTR_LOCAL_INFILE'), 'error', FALSE);
+ }
+
+ $buffered_query_enabled = array_key_exists(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, $database['pdo']) && $database['pdo'][PDO::MYSQL_ATTR_USE_BUFFERED_QUERY];
+ if (!$buffered_query_enabled) {
+ drupal_set_message(t('Required PDO flag for dkan_datastore_fast_import were not found. This module requires PDO::MYSQL_ATTR_USE_BUFFERED_QUERY'), 'error', FALSE);
+ }
+
+ }
}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/dkan_datastore_fast_import.js b/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/dkan_datastore_fast_import.js
deleted file mode 100644
index c2388aa1217..00000000000
--- a/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/dkan_datastore_fast_import.js
+++ /dev/null
@@ -1,9 +0,0 @@
-(function ($) {
- Drupal.behaviors.dkanDatastoreFastImport = {
- attach: function (context, settings) {
- $('#edit-feedsflatstoreprocessor-geolocate', context).on('change', function (e) {
- $('#edit-use-fast-import').attr('checked', false);
- });
- }
- };
-}(jQuery));
\ No newline at end of file
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/dkan_datastore_fast_import.module b/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/dkan_datastore_fast_import.module
index 32561ae1d41..6df750870af 100644
--- a/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/dkan_datastore_fast_import.module
+++ b/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/dkan_datastore_fast_import.module
@@ -2,324 +2,30 @@
/**
* @file
- * Enables database csv imports.
+ * DKAN Datastore - Fast Import.
*/
-define('DKAN_DATASTORE_FAST_IMPORT_DKAN_DATASTORE_BATCH_IMPORT', 0);
-define('DKAN_DATASTORE_FAST_IMPORT_DKAN_DATASTORE_FAST_IMPORT', 1);
-define('DKAN_DATASTORE_FAST_IMPORT_DKAN_DATASTORE_DYNAMIC_IMPORT', 2);
+use Dkan\Datastore\Manager\Info;
/**
- * Implements hook_menu().
+ * Implements hook_xautoload().
*/
-function dkan_datastore_fast_import_menu() {
- $items['admin/dkan/datastore'] = array(
- 'title' => 'DKAN Datastore',
- 'description' => 'Settings for DKAN Datastore.',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('dkan_datastore_fast_import_settings'),
- 'access arguments' => array('manage datastore settings'),
+function dkan_datastore_fast_import_xautoload($adapter) {
+ $adapter->absolute()->addPsr4(
+ 'Dkan\Datastore\Manager\FastImport\\',
+ drupal_get_path("module", "dkan_datastore_fast_import") . '/src'
);
-
- return $items;
-}
-
-/**
- * Check database is set with pdo flags.
- */
-function dkan_datastore_check_database() {
- global $databases;
- $target = Database::getConnection()->getTarget();
- $key = Database::getConnection()->getKey();
- $database = $databases[$target][$key];
-
- if (!isset($database['pdo'])) {
- drupal_set_message(t('Required PDO flags for dkan_datastore_fast_import were not found. This module requires PDO::MYSQL_ATTR_LOCAL_INFILE and PDO::MYSQL_ATTR_USE_BUFFERED_QUERY'), 'error', FALSE);
- }
- else {
-
- $infile_enabled = array_key_exists(PDO::MYSQL_ATTR_LOCAL_INFILE, $database['pdo']) && $database['pdo'][PDO::MYSQL_ATTR_LOCAL_INFILE];
- if (!$infile_enabled) {
- drupal_set_message(t('Required PDO flag for dkan_datastore_fast_import were not found. This module requires PDO::MYSQL_ATTR_LOCAL_INFILE'), 'error', FALSE);
- }
-
- $buffered_query_enabled = array_key_exists(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, $database['pdo']) && $database['pdo'][PDO::MYSQL_ATTR_USE_BUFFERED_QUERY];
- if (!$buffered_query_enabled) {
- drupal_set_message(t('Required PDO flag for dkan_datastore_fast_import were not found. This module requires PDO::MYSQL_ATTR_USE_BUFFERED_QUERY'), 'error', FALSE);
- }
-
- }
-}
-
-/**
- * Settings form.
- */
-function dkan_datastore_fast_import_settings() {
- dkan_datastore_check_database();
- $form = array();
- $options = array(
- DKAN_DATASTORE_FAST_IMPORT_DKAN_DATASTORE_BATCH_IMPORT => t('Use regular import as default (BATCH)'),
- DKAN_DATASTORE_FAST_IMPORT_DKAN_DATASTORE_FAST_IMPORT => t('Use fast import as default (LOAD DATA)'),
- DKAN_DATASTORE_FAST_IMPORT_DKAN_DATASTORE_DYNAMIC_IMPORT => t('Use fast import for files with a weight over:'),
- );
-
- $form['dkan_datastore_fast_import_selection'] = array(
- '#type' => 'radios',
- '#title' => t('Fast Import Selection'),
- '#options' => $options,
- '#default_value' => variable_get('dkan_datastore_fast_import_selection', 0),
- );
-
- $form['dkan_datastore_fast_import_selection_threshold'] = array(
- '#type' => 'textfield',
- '#title' => t('File size threshold'),
- '#size' => 50,
- '#default_value' => variable_get('dkan_datastore_fast_import_selection_threshold', DkanDatastoreFastImport::FAST_IMPORT_THRESHOLD_DEFAULT),
- '#description' => '',
- '#states' => array(
- 'visible' => array(
- ':input[name="dkan_datastore_fast_import_selection"]' => array('value' => DKAN_DATASTORE_FAST_IMPORT_DKAN_DATASTORE_DYNAMIC_IMPORT),
- ),
- ),
- );
-
- $options = array(
- 'load_data_local_infile' => t('LOAD DATA LOCAL INFILE'),
- 'load_data_infile' => t('LOAD DATA INFILE'),
- );
- $form['dkan_datastore_load_data_type'] = array(
- '#type' => 'radios',
- '#title' => t('Load Data Statement'),
- '#options' => $options,
- '#description' => t('Choose the version of load data you want to use. This depends on your hosting configuration.'),
- '#default_value' => variable_get('dkan_datastore_load_data_type', 'load_data_local_infile'),
- '#states' => array(
- 'invisible' => array(
- ':input[name="dkan_datastore_fast_import_selection"]' => array('value' => DKAN_DATASTORE_FAST_IMPORT_DKAN_DATASTORE_BATCH_IMPORT),
- ),
- ),
- );
-
- $form['queue_filesize_threshold'] = array(
- '#type' => 'textfield',
- '#title' => t('Queue Filesize Threshold'),
- '#size' => 50,
- '#default_value' => variable_get('queue_filesize_threshold', DkanDatastoreFastImport::QUEUE_FILESIZE_THRESHOLD_DEFAULT),
- '#description' => 'You need to setup a cron to run periodically "drush queue-run dkan_datastore_queue". If not, files will not be imported into the datastore.',
- '#states' => array(
- 'invisible' => array(
- ':input[name="dkan_datastore_fast_import_selection"]' => array('value' => DKAN_DATASTORE_FAST_IMPORT_DKAN_DATASTORE_BATCH_IMPORT),
- ),
- ),
- );
- $form['#submit'][] = 'dkan_datastore_fast_import_settings_submit';
-
- return system_settings_form($form);
-}
-
-/**
- * Settings submit callback.
- */
-function dkan_datastore_fast_import_settings_submit($form, &$form_state) {
- $fast_import_selection = $form_state['values']['dkan_datastore_fast_import_selection'];
- $class = ($fast_import_selection != DKAN_DATASTORE_FAST_IMPORT_DKAN_DATASTORE_BATCH_IMPORT) ? 'DkanDatastoreFastImport' : 'DkanDatastore';
- variable_set('dkan_datastore_class', $class);
}
/**
- * Get the datastore queue name.
+ * Implements hook_dkan_datastore_manager().
*/
-function dkan_datastore_fast_import_queue_name() {
- return 'dkan_datastore_fast_import_queue';
-}
-
-/**
- * Implements hook_cron_queue_info().
- */
-function dkan_datastore_fast_import_cron_queue_info() {
- return array(
- dkan_datastore_fast_import_queue_name() => array(
- 'worker callback' => 'dkan_datastore_fast_import_import_queue_worker',
- 'skip on cron' => TRUE,
- 'time' => 600,
- ),
+function dkan_datastore_fast_import_dkan_datastore_manager() {
+ $info = new Info(
+ '\Dkan\Datastore\Manager\FastImport\FastImport',
+ "fast",
+ "Fast Import"
);
-}
-
-/**
- * Import csv using the most performant way.
- */
-function dkan_datastore_fast_import_import($source, $node, $table, $config) {
- $load_data_type = variable_get('dkan_datastore_load_data_type', 'load_data_local_infile');
- $file = $node->field_upload->value();
- $file_path = drupal_realpath($file->uri);
- $feeds_entity_id = $source->feed_nid;
- $headers = array_keys($table->meta['fields']);
- $fields = implode(',', $headers);
- $delim = $config['delimiter'];
- $has_headers = ($config['no_headers']) ? '' : 'IGNORE 1 LINES';
- $quote_delimiters = variable_get('quote_delimiters', '"');
- $lines_terminated_by = variable_get('lines_terminated_by', '\n');
- $fields_escaped_by = variable_get('fields_escaped_by', '');
- $empty_as_null = variable_get('dkan_datastore_fast_import_load_empty_cells_as_null', 0);
- $set_null_values = '';
- $params = array();
-
- // If importing empty values as null, create a local var for each column.
- // See https://stackoverflow.com/questions/2675323/mysql-load-null-values-from-csv-data
- if ($empty_as_null) {
- $vars = dkan_datastore_fast_import_get_fields_as_vars($headers);
- $fields = implode(',', $vars);
- $headers_to_vars = array_combine($headers, $vars);
- foreach ($headers_to_vars as $header => $var) {
- $set_null_values = $set_null_values . ", $header = nullif($var,'')";
- }
- }
-
- $load_data_statement = ($load_data_type === 'load_data_local_infile') ? 'LOAD DATA LOCAL' : 'LOAD DATA';
-
- $sql = "$load_data_statement INFILE :file_path IGNORE
- INTO TABLE {$table->name}
- FIELDS TERMINATED BY :delim
- ENCLOSED BY :quote_delimiters";
- $params[':file_path'] = $file_path;
- $params[':delim'] = $delim;
- $params[':quote_delimiters'] = $quote_delimiters;
-
- if ($fields_escaped_by) {
- $sql = $sql . " ESCAPED BY :fields_escaped_by";
- $params[':fields_escaped_by'] = $fields_escaped_by;
- }
- $sql = $sql . " LINES TERMINATED BY '$lines_terminated_by' $has_headers ($fields)
- SET timestamp=UNIX_TIMESTAMP(), feeds_entity_id=$feeds_entity_id $set_null_values;";
-
- try {
- $result = db_query($sql, $params);
- if ($result) {
- if ($result->rowCount() == 0) {
- drupal_set_message(t('There were no items imported. It may be due to a misconfiguration related to characters set as quote delimiters, lines terminators or escape characters.'), 'warning', FALSE);
- }
- }
- $node->field_datastore_status->set(DKAN_DATASTORE_EXISTS);
- $node->save();
- return array('total_imported_items' => $result->rowCount());
- }
- catch (Exception $e) {
- drupal_set_message(t('There was an error trying to import this file: @error', array('@error' => $e->getMessage())), 'error', FALSE);
- return array('error', $e);
- }
-}
-
-/**
- * Get header names as sql variables.
- */
-function dkan_datastore_fast_import_get_fields_as_vars($headers) {
- $vars = array();
- foreach ($headers as $header) {
- $vars[] = '@v' . $header;
- }
- return $vars;
-}
-
-/**
- * Queue worker for dkan fast imports.
- */
-function dkan_datastore_fast_import_import_queue_worker($item) {
- dkan_datastore_fast_import_import($item['source'], $item['node'], $item['table'], $item['config']);
-}
-
-/**
- * Implements hook_form_alter().
- */
-function dkan_datastore_fast_import_form_alter(&$form, &$form_state, $form_id) {
- if ($form_id == 'dkan_datastore_import_tab_form') {
- $node = entity_metadata_wrapper('node', $form_state['build_info']['args'][0]);
- $file = $node->field_upload->value();
- $selection = variable_get('dkan_datastore_fast_import_selection', DKAN_DATASTORE_FAST_IMPORT_DKAN_DATASTORE_BATCH_IMPORT);
- $use_fast_import = FALSE;
- variable_set('quote_delimiters', '"');
- variable_set('lines_terminated_by', '\n');
- variable_set('fields_escaped_by', '');
- variable_set('dkan_datastore_fast_import_load_empty_cells_as_null', 0);
-
- if ($selection == DKAN_DATASTORE_FAST_IMPORT_DKAN_DATASTORE_DYNAMIC_IMPORT) {
- $threshold = variable_get('dkan_datastore_fast_import_selection_threshold', DkanDatastoreFastImport::FAST_IMPORT_THRESHOLD_DEFAULT);
- if ($file->filesize > parse_size($threshold)) {
- $use_fast_import = TRUE;
- }
- }
- elseif ($selection == DKAN_DATASTORE_FAST_IMPORT_DKAN_DATASTORE_FAST_IMPORT) {
- $use_fast_import = TRUE;
- }
- else {
- $use_fast_import = FALSE;
- }
- $form['use_fast_import'] = array(
- '#type' => 'checkbox',
- '#title' => t('Use Fast Import'),
- '#default_value' => $use_fast_import,
- '#states' => array(
- 'disabled' => array(
- ':input[name="FeedsFlatstoreProcessor[geolocate]"]' => array('checked' => TRUE),
- ),
- ),
- );
- $form['#attached']['js'][] = array(
- 'data' => drupal_get_path('module', 'dkan_datastore_fast_import') . '/dkan_datastore_fast_import.js',
- 'type' => 'file',
- );
- $form['quote_delimiters'] = array(
- '#type' => 'select',
- '#title' => t('Quote delimiters'),
- '#options' => array(
- '\'' => '\'',
- '"' => '"',
- '~' => '~',
- ),
- '#default_value' => variable_get('quote_delimiters', '"'),
- '#description' => t('The character that will be used to enclose fields in this CSV file.'),
- '#states' => array(
- 'invisible' => array(
- ':input[name="use_fast_import"]' => array('checked' => FALSE),
- ),
- ),
- );
- $form['lines_terminated_by'] = array(
- '#type' => 'select',
- '#title' => t('Lines terminated by:'),
- '#options' => array(
- '\n' => '\n (Unix)',
- '\r\n' => '\r\n (Windows)',
- '\r' => '\r (Legacy Mac)',
- ),
- '#default_value' => variable_get('lines_terminated_by', '\n'),
- '#states' => array(
- 'invisible' => array(
- ':input[name="use_fast_import"]' => array('checked' => FALSE),
- ),
- ),
- );
- $form['fields_escaped_by'] = array(
- '#type' => 'textfield',
- '#title' => t('Fields escaped by:'),
- '#default_value' => variable_get('fields_escaped_by', ''),
- '#description' => t('The character used to escape other characters on each fields. Leave empty if it isn\'t needed.'),
- '#states' => array(
- 'invisible' => array(
- ':input[name="use_fast_import"]' => array('checked' => FALSE),
- ),
- ),
- );
- $form['dkan_datastore_fast_import_load_empty_cells_as_null'] = array(
- '#type' => 'checkbox',
- '#title' => t('Read empty cells as NULL (if unchecked, empty cells will be read as zeros or empty strings).'),
- '#default_value' => variable_get('dkan_datastore_fast_import_load_empty_cells_as_null'),
- '#states' => array(
- 'invisible' => array(
- ':input[name="use_fast_import"]' => array('checked' => FALSE),
- ),
- ),
- );
- }
+ return $info;
}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/src/FastImport.php b/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/src/FastImport.php
new file mode 100644
index 00000000000..7f4cd2833d8
--- /dev/null
+++ b/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/src/FastImport.php
@@ -0,0 +1,67 @@
+getConfigurableProperties();
+
+ $file_path = $this->getResource()->getFilePath();
+
+ $headers = $this->getTableHeaders();
+ $fields = implode(", ", $headers);
+
+ $delim = $properties['delimiter'];
+
+ // @todo Add support for no headers.
+ $has_headers = '';
+
+ $quote_delimiters = $properties["quote"];
+
+ $lines_terminated_by = "\n";
+
+ $fields_escaped_by = $properties["escape"];
+
+ $load_data_statement = 'LOAD DATA';
+
+ $sql = "$load_data_statement INFILE :file_path IGNORE
+ INTO TABLE {$this->getTableName()}
+ FIELDS TERMINATED BY :delim
+ ENCLOSED BY :quote_delimiters";
+ $params[':file_path'] = $file_path;
+ $params[':delim'] = $delim;
+ $params[':quote_delimiters'] = $quote_delimiters;
+
+ if ($fields_escaped_by) {
+ $sql = $sql . " ESCAPED BY :fields_escaped_by";
+ $params[':fields_escaped_by'] = $fields_escaped_by;
+ }
+ $sql = $sql . " LINES TERMINATED BY '$lines_terminated_by' $has_headers ($fields);";
+
+ try {
+ db_query($sql, $params);
+ }
+ catch (\Exception $e) {
+ $this->setError($e->getMessage());
+ return self::DATA_IMPORT_ERROR;
+ }
+
+ return self::DATA_IMPORT_DONE;
+ }
+
+}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_simple_import/dkan_datastore_simple_import.info b/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_simple_import/dkan_datastore_simple_import.info
new file mode 100644
index 00000000000..9de6fb0d3f3
--- /dev/null
+++ b/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_simple_import/dkan_datastore_simple_import.info
@@ -0,0 +1,6 @@
+name = DKAN Datastore Simple Import
+description = A datastore importer that uses MySQL insert statements, and a custom csv parser.
+core = 7.x
+package = DKAN
+dependencies[] = dkan_datastore
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_simple_import/dkan_datastore_simple_import.module b/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_simple_import/dkan_datastore_simple_import.module
new file mode 100644
index 00000000000..943fb73ae54
--- /dev/null
+++ b/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_simple_import/dkan_datastore_simple_import.module
@@ -0,0 +1,31 @@
+absolute()->addPsr4(
+ 'Dkan\Datastore\Manager\SimpleImport\\',
+ drupal_get_path("module", "dkan_datastore_simple_import") . '/src'
+ );
+}
+
+/**
+ * Implements hook_dkan_datastore_manager().
+ */
+function dkan_datastore_simple_import_dkan_datastore_manager() {
+ $info = new Info(
+ '\Dkan\Datastore\Manager\SimpleImport\SimpleImport',
+ "simple",
+ "Simple Import"
+ );
+
+ return $info;
+}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_simple_import/src/SimpleImport.php b/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_simple_import/src/SimpleImport.php
new file mode 100644
index 00000000000..d724b1c571a
--- /dev/null
+++ b/profiles/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_simple_import/src/SimpleImport.php
@@ -0,0 +1,163 @@
+ 0) {
+ $end = time() + $time_limit;
+ }
+
+ $number_of_items_imported = $this->numberOfRecordsImported();
+ $start = ($number_of_items_imported > 0) ? $number_of_items_imported + 1 : 1;
+
+ $query = db_insert($this->getTableName());
+ $header = $this->getTableHeaders();
+ $query->fields($header);
+
+ $counter = 0;
+
+ $parser = $this->getParser();
+
+ $h = fopen($this->getResource()->getFilePath(), 'r');
+
+ $finished = TRUE;
+ $interrupt = $this->getInterrupt();
+ while ($chunk = fread($h, 32)) {
+ if ($interrupt) {
+ $finished = FALSE;
+ break;
+ }
+ if (time() < $end) {
+ $parser->feed($chunk);
+ $counter = $this->getAndStore($parser, $query, $header, $counter, $start);
+
+ if ($counter === FALSE) {
+ return ManagerInterface::DATA_IMPORT_ERROR;
+ }
+ }
+ else {
+ $finished = FALSE;
+ break;
+ }
+ if ($counter % 1000 == 0) {
+ $interrupt = $this->getInterrupt();
+ }
+ }
+
+ fclose($h);
+
+ // Flush the parser.
+ $parser->finish();
+ $this->getAndStore($parser, $query, $header, $counter, $start);
+
+ try {
+ $query->execute();
+ }
+ catch (\Exception $e) {
+ $this->setError($e->getMessage());
+ return ManagerInterface::DATA_IMPORT_ERROR;
+ }
+
+ if ($interrupt) {
+ variable_set('dkan_datastore_interrupt', 0);
+ }
+
+ if ($finished) {
+ return ManagerInterface::DATA_IMPORT_DONE;
+ }
+ else {
+ return ManagerInterface::DATA_IMPORT_PAUSED;
+ }
+ }
+
+ /**
+ * Get Interrupt.
+ */
+ private function getInterrupt() {
+ $query = db_select("variable", 'v');
+ $query->fields('v', ['value']);
+ $query->condition('name', "dkan_datastore_interrupt");
+ $results = $query->execute();
+ foreach ($results as $result) {
+ $value = unserialize($result->value);
+ return $value;
+ }
+ return 0;
+ }
+
+ /**
+ * Private method.
+ */
+ private function getAndStore(Csv $parser, \InsertQuery $query, $header, $counter, $start) {
+ while ($record = $parser->getRecord()) {
+ if ($counter >= $start) {
+ $values = $record;
+
+ if ($this->valuesAreValid($values, $header)) {
+ $query->values($values);
+ }
+ else {
+ $header_count = count($header);
+ $values_count = count($values);
+ $json_header = json_encode($header);
+ $json_values = json_encode($values);
+
+ $message = "";
+ if ($header_count != $values_count) {
+ $message = "The number of values ($values_count) does not match the number of columns ($header_count). ";
+ }
+ $this->setError($message . "Invalid line {$counter} in {$this->getResource()->getFilePath()}; header({$header_count}): {$json_header} values({$values_count}): {$json_values}");
+ return FALSE;
+ }
+
+ if ($counter % 1000 == 0) {
+ try {
+ $query->execute();
+ }
+ catch (\Exception $e) {
+ $this->setError($e->getMessage());
+ return FALSE;
+ }
+ $query = db_insert($this->getTableName());
+ $query->fields($header);
+ }
+ }
+
+ $counter++;
+ }
+
+ return $counter;
+ }
+
+ /**
+ * Private method.
+ */
+ private function valuesAreValid($values, $header) {
+ $number_of_fields = count($header);
+ $number_of_values = count($values);
+ if ($number_of_fields == $number_of_values) {
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/resources/dkan_datastore_resource.inc b/profiles/dkan/modules/dkan/dkan_datastore/resources/dkan_datastore_resource.inc
new file mode 100644
index 00000000000..298fa3a0db9
--- /dev/null
+++ b/profiles/dkan/modules/dkan/dkan_datastore/resources/dkan_datastore_resource.inc
@@ -0,0 +1,374 @@
+ 'inc',
+ 'module' => 'dkan_datastore',
+ 'name' => 'resources/dkan_datastore_resource',
+ );
+
+ return array(
+ 'datastore' => array(
+ 'operations' => array(
+ 'retrieve' => array(
+ 'help' => 'Retrieve information about a datastore',
+ 'file' => $file,
+ 'callback' => '_dkan_datastore_resource_retrieve',
+ 'args' => array(
+ array(
+ 'name' => 'resource_nid',
+ 'optional' => FALSE,
+ 'source' => array('path' => 0),
+ 'type' => 'int',
+ 'description' => 'The nid of the resource the datastore is connected to.',
+ ),
+ ),
+ 'access callback' => '_dkan_datastore_resource_access',
+ 'access arguments' => array('manage'),
+ 'access arguments append' => TRUE,
+ ),
+ 'create' => array(
+ 'help' => 'Create a datastore for the given resource.',
+ 'file' => $file,
+ 'callback' => '_dkan_datastore_resource_create',
+ 'args' => array(
+ array(
+ 'name' => 'nid',
+ 'optional' => FALSE,
+ 'source' => array('path' => 0),
+ 'type' => 'int',
+ 'description' => 'The nid of the resource to configure a datastore for.',
+ ),
+ array(
+ 'name' => 'data',
+ 'optional' => FALSE,
+ 'type' => 'string',
+ 'source' => 'data',
+ 'description' => 'The machine name of the datastore manager that should be used.',
+ ),
+ ),
+ 'access callback' => '_dkan_datastore_resource_access',
+ 'access arguments' => array('manage'),
+ 'access arguments append' => TRUE,
+ ),
+ 'update' => array(
+ 'help' => 'Update/modify the datastore for a given resource.',
+ 'file' => $file,
+ 'callback' => '_dkan_datastore_resource_update',
+ 'access arguments' => array('manage'),
+ 'access arguments append' => TRUE,
+ 'args' => array(
+ array(
+ 'name' => 'nid',
+ 'optional' => FALSE,
+ 'source' => array('path' => 0),
+ 'type' => 'int',
+ 'description' => 'The nid of the resource to configure a datastore for.',
+ ),
+ array(
+ 'name' => 'data',
+ 'optional' => FALSE,
+ 'type' => 'string',
+ 'source' => 'data',
+ 'description' => 'Configuration data for datastore.',
+ ),
+ ),
+ 'access callback' => '_dkan_datastore_resource_access',
+ ),
+ ),
+ 'delete' => array(
+ 'help' => 'Remove the datastore for a given resource.',
+ 'file' => $file,
+ 'callback' => '_dkan_datastore_resource_delete',
+ 'args' => array(
+ array(
+ 'name' => 'nid',
+ 'optional' => FALSE,
+ 'source' => array('path' => 0),
+ 'type' => 'int',
+ 'description' => 'The nid of the resource for which to drop datastore.',
+ ),
+ ),
+ 'access callback' => '_dkan_datastore_resource_access',
+ 'access arguments' => array('delete'),
+ 'access arguments append' => TRUE,
+ ),
+ 'targeted_actions' => array(
+ 'drop' => array(
+ 'help' => 'Drop a datastore table but keep configuration.',
+ 'file' => $file,
+ 'callback' => '_dkan_datastore_resource_drop',
+ 'access callback' => '_dkan_datastore_resource_access',
+ 'access arguments' => array('drop'),
+ 'access arguments append' => TRUE,
+ 'args' => array(
+ array(
+ 'name' => 'nid',
+ 'optional' => FALSE,
+ 'source' => array('path' => 0),
+ 'type' => 'int',
+ 'description' => 'The nid of the node to attach a file to',
+ ),
+ ),
+ ),
+ 'import' => array(
+ 'help' => 'Queue a datastore for import.',
+ 'file' => $file,
+ 'callback' => '_dkan_datastore_resource_import',
+ 'access callback' => '_dkan_datastore_resource_access',
+ 'access arguments' => array('import'),
+ 'access arguments append' => TRUE,
+ 'args' => array(
+ array(
+ 'name' => 'nid',
+ 'optional' => FALSE,
+ 'source' => array('path' => 0),
+ 'type' => 'int',
+ 'description' => 'The nid of the node to attach a file to',
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+}
+
+/**
+ * Get datastore.
+ */
+function _dkan_datastore_resource_get_datastore_manager($resource_nid) {
+ try {
+ $resource = Resource::createFromDrupalNodeNid($resource_nid);
+ }
+ catch (\Exception $e) {
+ services_error("Resource {$resource_nid} does not exist | {$e->getMessage()}");
+ die();
+ }
+
+ /* @var $manger \Dkan\Datastore\Manager\ManagerInterface */
+ $manager = (new Factory($resource))->get();
+
+ return $manager;
+}
+
+/**
+ * Datastore retrieve.
+ */
+function _dkan_datastore_resource_retrieve($resource_nid) {
+ /* @var $manager \Dkan\Datastore\Manager\ManagerInterface */
+ $manager = _dkan_datastore_resource_get_datastore_manager($resource_nid);
+
+ if (!$manager) {
+ services_error("The datastore for Resource {$resource_nid} has not been configured.");
+ }
+
+ return $manager->getStatus();
+}
+
+/**
+ * Datastore create.
+ */
+function _dkan_datastore_resource_create($resource_nid, $data) {
+ $data = _services_arg_value($data, 'data');
+ $manager_name = $data['manager'];
+ $configuration = $data['configuration'];
+ try {
+ if (_dkan_datastore_resource_get_datastore_manager($resource_nid)) {
+ services_error("Configuration for this resource's datastore already exists.");
+ die();
+ }
+ }
+ catch (\Exception $e) {
+ return $e->getMessage();
+ }
+
+ $class = _dkan_datastore_resource_get_class($manager_name);
+ if (!$class) {
+ services_error("The manager {$manager_name} does not exist or is not active.");
+ die();
+ }
+ else {
+ $resource = Resource::createFromDrupalNodeNid($resource_nid);
+
+ $factory = new Factory($resource);
+ $factory->setClass($class);
+ /* @var $manager \Dkan\Datastore\Manager\ManagerInterface */
+ $manager = $factory->get();
+ $properties = $manager->getConfigurableProperties();
+ foreach ($properties as $property_name => $default_value) {
+ if (!isset($configuration[$property_name])) {
+ services_error("The configuration property {$property_name} was not set.");
+ }
+ $manager->setConfigurableProperties($configuration);
+ }
+ // Now get the stored object and return.
+ unset($manager);
+ $manager = _dkan_datastore_resource_get_datastore_manager($resource_nid);
+
+ if (!$manager) {
+ services_error("The datastore for Resource {$resource_nid} was not properly configured.");
+ }
+ return $manager->getStatus();
+ }
+}
+
+/**
+ * Datastore update.
+ */
+function _dkan_datastore_resource_update($resource_nid, $data) {
+ $data = _services_arg_value($data, 'data');
+ $manager_name = $data['manager'];
+ $configuration = $data['configuration'];
+ try {
+ $manager = _dkan_datastore_resource_get_datastore_manager($resource_nid);
+ if (empty($manager)) {
+ services_error("This resource's datastore is not configured or does not exist.");
+ }
+ }
+ catch (\Exception $e) {
+ return $e->getMessage();
+ }
+
+ if ($manager_name) {
+ $class = _dkan_datastore_resource_get_class($manager_name);
+ $status = $manager->getStatus();
+ // If the manager class has changed, drop and create a new one.
+ if ($status['class'] != $class) {
+ services_error("You may not change the manager of an existing datastore. Drop the datastore and create a new one to use a different manager.");
+ }
+ }
+
+ $properties = $manager->getConfigurableProperties();
+ foreach ($properties as $property_name => $default_value) {
+ if (isset($configuration[$property_name])) {
+ $manager->setConfigurableProperties($configuration);
+ }
+ }
+}
+
+/**
+ * Datastore delete.
+ */
+function _dkan_datastore_resource_delete($resource_nid) {
+ try {
+ $manager = _dkan_datastore_resource_get_datastore_manager($resource_nid);
+ if (empty($manager)) {
+ services_error("This resource's datastore is not configured or does not exist.");
+ }
+ }
+ catch (\Exception $e) {
+ return $e->getMessage();
+ }
+
+ $manager->drop();
+ $manager->dropState();
+
+ return TRUE;
+}
+
+/**
+ * Datastore import.
+ */
+function _dkan_datastore_resource_import($nid) {
+ /* @var $manager \Dkan\Datastore\Manager\ManagerInterface */
+ $manager = _dkan_datastore_resource_get_datastore_manager($nid);
+
+ if (!$manager) {
+ services_error("The datastore for Resource {$nid} has not been configured.");
+ }
+
+ $finished = $manager->import();
+ if ($finished) {
+ return TRUE;
+ }
+ else {
+ return $manager->getErrors();
+ }
+}
+
+/**
+ * Datastore drop.
+ */
+function _dkan_datastore_resource_drop($nid) {
+ $manager = _dkan_datastore_resource_get_datastore_manager($nid);
+
+ if (!$manager) {
+ services_error("The datastore for Resource {$nid} has not been configured.");
+ }
+
+ $finished = $manager->drop();
+ if ($finished) {
+ return TRUE;
+ }
+ else {
+ return $manager->getErrors();
+ }
+}
+
+/**
+ * Get manager class from machine name.
+ */
+function _dkan_datastore_resource_get_class($manager_machine_name) {
+ $info = dkan_datastore_managers_info();
+ /* @var $i \Dkan\Datastore\Manager\Info */
+ foreach ($info as $i) {
+ if ($i->getMachineName() == $manager_machine_name) {
+ return $i->getClass();
+ }
+ }
+ return NULL;
+}
+
+/**
+ * Resource info.
+ */
+function _dkan_datastore_resource_info() {
+ $info = dkan_datastore_managers_info();
+ return $info;
+}
+
+/**
+ * Resource access.
+ */
+function _dkan_datastore_resource_access($op = 'view', $args = array()) {
+ // Make sure we have an object or this all fails, some servers can
+ // mess up the types.
+ if (is_array($args[0])) {
+ $args[0] = (object) $args[0];
+ }
+ // This is to determine if it is just a string happens on node/%NID.
+ elseif (!is_array($args[0]) && !is_object($args[0])) {
+ $args[0] = (object) ['nid' => $args[0]];
+ }
+
+ if ($op != 'create' && !empty($args)) {
+ $node = node_load($args[0]->nid);
+ }
+ elseif ($op == 'create') {
+ if (isset($args[0]->type)) {
+ $node = $args[0]->type;
+ return node_access($op, $node);
+ }
+ else {
+ return services_error(t('Node type is required'), 406);
+ }
+ }
+ if (isset($node->nid) && $node->nid) {
+ return dkan_datastore_access($op, $node);
+ }
+ else {
+ return services_error(t('Node @nid could not be found', array('@nid' => $args[0]->nid)), 404);
+ }
+
+}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/src/LockableDrupalVariables.php b/profiles/dkan/modules/dkan/dkan_datastore/src/LockableDrupalVariables.php
new file mode 100644
index 00000000000..eb41fbf950a
--- /dev/null
+++ b/profiles/dkan/modules/dkan/dkan_datastore/src/LockableDrupalVariables.php
@@ -0,0 +1,158 @@
+binName = $bin_name;
+ }
+
+ /**
+ * Get all the variables in this bin.
+ *
+ * This operation will lock other bin operataions until the
+ * return bin method is called.
+ *
+ * @return array
+ * All of the variables in this bin.
+ */
+ public function borrowBin() {
+ $this->getLock();
+
+ $bin = variable_get($this->binName, []);
+
+ return $bin;
+ }
+
+ /**
+ * Get bin.
+ */
+ public function getBin() {
+ $bin = variable_get($this->binName, []);
+ return $bin;
+ }
+
+ /**
+ * Return bin.
+ *
+ * Sets the whole bin and all of its variables.
+ * Releases the lock set if borrowBin() was called.
+ *
+ * @param array $bin
+ * The full bin.
+ */
+ public function returnBin(array $bin) {
+ variable_set($this->binName, $bin);
+
+ $this->releaseLock();
+ }
+
+ /**
+ * Set a variable.
+ *
+ * @param string $id
+ * The variable's id.
+ * @param mixed $data
+ * The variable's value.
+ */
+ public function set($id, $data) {
+ $this->getLock();
+
+ $bin = variable_get($this->binName, []);
+ $bin[$id] = $data;
+ variable_set($this->binName, $bin);
+
+ $this->releaseLock();
+ }
+
+ /**
+ * Get the variable with the given id.
+ *
+ * @param string $id
+ * The variable's id.
+ *
+ * @return mixed
+ * The variable's value.
+ */
+ public function get($id) {
+ $this->getLock();
+
+ $bin = variable_get($this->binName, []);
+
+ $this->releaseLock();
+
+ return isset($bin[$id]) ? $bin[$id] : NULL;
+ }
+
+ /**
+ * Delete the variable with the given id.
+ *
+ * @param string $id
+ * The id of the variable.
+ */
+ public function delete($id) {
+ $this->getLock();
+
+ $bin = variable_get($this->binName, []);
+ unset($bin[$id]);
+ variable_set($this->binName, $bin);
+
+ $this->releaseLock();
+ }
+
+ /**
+ * Private method.
+ */
+ private function getLock() {
+ $counter = 0;
+ $success = 0;
+ do {
+ if ($counter >= 1) {
+ sleep(1);
+ }
+
+ // We have to query the variable table directly instead of
+ // using variable_get, b/c this is a global lock that can/will
+ // be set or released in different processes. variable_get
+ // simply check a global variable set earlier in the request.
+ // This global variable does not get updated during the
+ // request even if another process changes the value in
+ // the database.
+ $query = db_select("variable", 'v');
+ $query->fields('v', ['value']);
+ $query->condition('name', "dkan_datastore_lock");
+ $results = $query->execute();
+
+ $exist = FALSE;
+ foreach ($results as $result) {
+ $exist = TRUE;
+ $value = unserialize($result->value);
+ break;
+ }
+
+ if (!$exist || $value == 0) {
+ variable_set('dkan_datastore_lock', 1);
+ $success = 1;
+ }
+
+ $counter++;
+ } while (!$success);
+ }
+
+ /**
+ * Private method.
+ */
+ private function releaseLock() {
+ variable_set("dkan_datastore_lock", 0);
+ }
+
+}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/src/Manager/Factory.php b/profiles/dkan/modules/dkan/dkan_datastore/src/Manager/Factory.php
new file mode 100644
index 00000000000..ee17f260991
--- /dev/null
+++ b/profiles/dkan/modules/dkan/dkan_datastore/src/Manager/Factory.php
@@ -0,0 +1,107 @@
+resource = $resource;
+ $this->setupClassHierarchy();
+ }
+
+ /**
+ * Set a class.
+ *
+ * This class will be given priority when trying to
+ * create a datastore manager for the given resource.
+ */
+ public function setClass($class) {
+ array_unshift($this->classes, $class);
+ }
+
+ /**
+ * Get the datastore manager.
+ */
+ public function get() {
+ foreach ($this->classes as $class) {
+ try {
+ return $this->getManager($class);
+ }
+ catch (\Exception $e) {
+ }
+ }
+ throw new \Exception("Datastore could not be loaded");
+
+ }
+
+ /**
+ * Get manager.
+ */
+ private function getManager($class) {
+ $exists = class_exists($class);
+ if (!$exists) {
+ throw new \Exception("The class {$class} does not exist.");
+ }
+
+ $interfaces = class_implements($class);
+ $interface = "Dkan\Datastore\Manager\ManagerInterface";
+ if (!in_array($interface, $interfaces)) {
+ throw new \Exception("The class {$class} does not implement the interface {$interface}.");
+ }
+
+ return new $class($this->resource);
+ }
+
+ /**
+ * Create an array of datastore manager classes.
+ *
+ * It picks a "random" manager class, and, if available,
+ * the class from the datastore state.
+ *
+ * This is useful as a hierarcy to eliminate failures
+ * if our datastore state becomes corrupt, and the official
+ * manager is no longer valid or available.
+ */
+ private function setupClassHierarchy() {
+ array_unshift($this->classes, $this->getClass());
+
+ $state_storage = new LockableDrupalVariables("dkan_datastore");
+ $state = $state_storage->get($this->resource->getId());
+
+ if ($state) {
+ $class = $state['class'];
+ array_unshift($this->classes, $class);
+ }
+ }
+
+ /**
+ * Choose the first class from the manager's info array.
+ */
+ private function getClass() {
+ $info = dkan_datastore_managers_info();
+
+ if (empty($info)) {
+ throw new \Exception("No Datastore Managers are active");
+ }
+
+ /* @var $i \Dkan\Datastore\Manager\Info */
+ $i = array_shift($info);
+ return $i->getClass();
+ }
+
+}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/src/Manager/Info.php b/profiles/dkan/modules/dkan/dkan_datastore/src/Manager/Info.php
new file mode 100644
index 00000000000..50ca0ff8d94
--- /dev/null
+++ b/profiles/dkan/modules/dkan/dkan_datastore/src/Manager/Info.php
@@ -0,0 +1,56 @@
+class = $class;
+ $this->label = $label;
+ $this->machineName = $machine_name;
+ }
+
+ /**
+ * Getter.
+ */
+ public function getClass() {
+ return $this->class;
+ }
+
+ /**
+ * Getter.
+ */
+ public function getLabel() {
+ return $this->label;
+ }
+
+ /**
+ * Getter.
+ */
+ public function getMachineName() {
+ return $this->machineName;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function jsonSerialize() {
+ return [
+ 'class' => $this->class,
+ 'machine_name' => $this->machineName,
+ 'label' => $this->label,
+ ];
+ }
+
+}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/src/Manager/Manager.php b/profiles/dkan/modules/dkan/dkan_datastore/src/Manager/Manager.php
new file mode 100644
index 00000000000..b245015b100
--- /dev/null
+++ b/profiles/dkan/modules/dkan/dkan_datastore/src/Manager/Manager.php
@@ -0,0 +1,363 @@
+timeLimit = 0;
+
+ $this->resource = $resource;
+
+ $this->stateDataImport = self::DATA_IMPORT_UNINITIALIZED;
+ $this->stateStorage = self::STORAGE_UNINITIALIZED;
+
+ $this->configurableProperties = [];
+
+ if (!$this->loadState()) {
+ $this->setConfigurablePropertiesHelper([
+ 'delimiter' => ',',
+ 'quote' => '"',
+ 'escape' => '\\',
+ 'trailing_delimiter' => FALSE,
+ ]);
+ $this->initialization($resource);
+ }
+ }
+
+ /**
+ * Set the time limit.
+ *
+ * The import process will stop if it hits the time limit.
+ *
+ * @param int $seconds
+ * Number of seconds.
+ */
+ public function setImportTimelimit($seconds) {
+ if ($seconds > 0) {
+ $this->timeLimit = $seconds;
+ }
+ else {
+ $this->timeLimit = 0;
+ }
+ }
+
+ /**
+ * Get resource.
+ *
+ * @return \Dkan\Datastore\Resource
+ * The resource associated with this datastore.
+ */
+ protected function getResource() {
+ return $this->resource;
+ }
+
+ /**
+ * Get parser.
+ *
+ * @return \Dkan\Datastore\Parser\Csv
+ * Parser object.
+ */
+ protected function getParser() {
+ if (!$this->parser) {
+ $this->parser = new Csv(
+ $this->configurableProperties['delimiter'],
+ $this->configurableProperties['quote'],
+ $this->configurableProperties['escape'],
+ ["\r", "\n"]
+ );
+
+ if (isset($this->configurableProperties['trailing_delimiter']) && $this->configurableProperties['trailing_delimiter'] == TRUE) {
+ $this->parser->activateTrailingDelimiter();
+ }
+ }
+ return $this->parser;
+ }
+
+ /**
+ * Initialization.
+ *
+ * This method is called the first time an instance of a
+ * Manager is created.
+ *
+ * This gives specific classes to affect what happens
+ * during construction.
+ *
+ * @param \Dkan\Datastore\Resource $resource
+ * Resource.
+ */
+ abstract protected function initialization(Resource $resource);
+
+ /**
+ * {@inheritdoc}
+ */
+ private function initializeStorage() {
+ $table_name = $this->getTableName();
+
+ if (!db_table_exists($table_name)) {
+ $schema = $this->getTableSchema();
+
+ db_create_table($table_name, $schema);
+
+ $this->stateStorage = self::STORAGE_INITIALIZED;
+ $this->saveState();
+ }
+ elseif ($this->stateStorage == self::STORAGE_UNINITIALIZED) {
+ $this->stateStorage = self::STORAGE_INITIALIZED;
+ $this->saveState();
+ }
+ }
+
+ /**
+ * Get table schema.
+ */
+ public function getTableSchema() {
+ $schema = [];
+ $header = $this->getTableHeaders();
+ foreach ($header as $field) {
+ $schema['fields'][$field] = [
+ 'label' => $field,
+ 'type' => "text",
+ ];
+ }
+ return $schema;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTableHeaders() {
+ $parser = $this->getParser();
+
+ $h = fopen($this->resource->getFilePath(), 'r');
+
+ $headers = [];
+ // @todo If csv cofiguration is incorrect we could end up getting the whole file.
+ while ($chunk = fread($h, 32)) {
+ $parser->feed($chunk);
+ if ($record = $parser->getRecord()) {
+ $headers = $record;
+ break;
+ }
+ }
+ fclose($h);
+ $parser->reset();
+
+ foreach ($headers as $key => $field) {
+ $new = preg_replace("/[^A-Za-z0-9_ ]/", '', $field);
+ $new = dkan_datastore_safe_name($new);
+ $header[$key] = $new;
+ }
+
+ if (empty($header)) {
+ throw new \Exception("Unable to get headers from {$this->resource->getFilePath()}");
+ }
+
+ return $header;
+ }
+
+ /**
+ * Private method.
+ */
+ private function loadState() {
+ $state_storage = new LockableDrupalVariables("dkan_datastore");
+ $state = $state_storage->get($this->resource->getId());
+
+ if ($state) {
+ if ($state['storage']) {
+ $this->stateStorage = $state['storage'];
+ }
+ if ($state['data_import']) {
+ $this->stateDataImport = $state['data_import'];
+ }
+ if ($state['configurable_properties']) {
+ $this->setConfigurablePropertiesHelper($state['configurable_properties']);
+ }
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function saveState() {
+ $state_storage = new LockableDrupalVariables("dkan_datastore");
+ $state_storage->set($this->resource->getId(), $this->getStatus());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropState() {
+ $state_storage = new LockableDrupalVariables("dkan_datastore");
+ $state_storage->delete($this->resource->getId());
+ $this->stateStorage = self::STORAGE_UNINITIALIZED;
+ $this->stateDataImport = self::DATA_IMPORT_UNINITIALIZED;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function import() {
+ $status = $this->getStatus();
+ if ($status['storage'] == self::STORAGE_UNINITIALIZED) {
+ $this->initializeStorage();
+ }
+
+ $this->stateDataImport = self::DATA_IMPORT_IN_PROGRESS;
+ $this->saveState();
+
+ $import_state = $this->storeRecords($this->timeLimit);
+ if ($import_state === self::DATA_IMPORT_DONE) {
+ $this->stateDataImport = self::DATA_IMPORT_DONE;
+ $this->saveState();
+ }
+ elseif ($import_state === self::DATA_IMPORT_PAUSED) {
+ $this->stateDataImport = self::DATA_IMPORT_PAUSED;
+ $this->saveState();
+ }
+ elseif ($import_state === self::DATA_IMPORT_ERROR) {
+ $this->stateDataImport = self::DATA_IMPORT_ERROR;
+ watchdog("dkan_datastore", $this->errors);
+ $this->saveState();
+ }
+ else {
+ throw new \Exception("An incorrect state was returnd by storeRecords().");
+ }
+
+ return $import_state;
+ }
+
+ /**
+ * Store records.
+ *
+ * Move records from the resource to the datastore.
+ *
+ * @return bool
+ * Whether the storing process was successful.
+ */
+ abstract protected function storeRecords($time_limit = 0);
+
+ /**
+ * {@inheritdoc}
+ */
+ public function drop() {
+ $this->dropTable();
+ $this->dropState();
+ }
+
+ /**
+ * Drop table.
+ */
+ protected function dropTable() {
+ db_drop_table($this->getTableName());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStatus() {
+ $state = [];
+
+ $state['class'] = static::class;
+ $state['storage'] = $this->stateStorage;
+ $state['data_import'] = $this->stateDataImport;
+ $state['configurable_properties'] = $this->getConfigurableProperties();
+
+ return $state;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTableName() {
+ return "dkan_datastore_{$this->resource->getId()}";
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getConfigurableProperties() {
+ return $this->configurableProperties;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setConfigurableProperties($properties) {
+ $this->setConfigurablePropertiesHelper($properties);
+ $this->stateDataImport = self::DATA_IMPORT_READY;
+ $this->saveState();
+ }
+
+ /**
+ * Helper.
+ */
+ private function setConfigurablePropertiesHelper($properties) {
+ $this->configurableProperties = $properties;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function numberOfRecordsImported() {
+ $table_name = $this->getTableName();
+ $query = db_select($table_name, "t");
+
+ try {
+ return $query->countQuery()->execute()->fetchField();
+ }
+ catch (\Exception $exception) {
+ return 0;
+ }
+ }
+
+ /**
+ * Set error.
+ *
+ * Adds an error message to the errors array.
+ */
+ protected function setError($error) {
+ $this->errors[] = $error;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getErrors() {
+ return $this->errors;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function goToPausedState() {
+ $this->stateDataImport = $this::DATA_IMPORT_PAUSED;
+ $this->saveState();
+ }
+
+}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/src/Manager/ManagerInterface.php b/profiles/dkan/modules/dkan/dkan_datastore/src/Manager/ManagerInterface.php
new file mode 100644
index 00000000000..3350d6e7dd6
--- /dev/null
+++ b/profiles/dkan/modules/dkan/dkan_datastore/src/Manager/ManagerInterface.php
@@ -0,0 +1,120 @@
+datastoreManager = $manager;
+ }
+
+ /**
+ * Get form.
+ */
+ public function getForm() {
+ $form = [];
+ $form['import_options'] = [
+ '#type' => 'fieldset',
+ '#title' => t('Import options'),
+ '#collapsible' => FALSE,
+ ];
+ foreach ($this->datastoreManager->getConfigurableProperties() as $property => $default_value) {
+ $propety_label = ucfirst(str_replace("_", " ", $property));
+
+ if ($property == "delimiter") {
+ $form['import_options']["datastore_manager_config_{$property}"] = array(
+ '#type' => 'select',
+ // @codingStandardsIgnoreStart
+ '#title' => t($propety_label),
+ // @codingStandardsIgnoreEnd
+ '#options' => array(
+ "," => ",",
+ ";" => ";",
+ "|" => "|",
+ "\t" => "TAB",
+ ),
+ '#default_value' => $default_value,
+ );
+ }
+ elseif ($property == "trailing_delimiter") {
+ $form['import_options']["datastore_manager_config_{$property}"] = array(
+ '#type' => 'checkbox',
+ // @codingStandardsIgnoreStart
+ '#title' => t($propety_label),
+ // @codingStandardsIgnoreEnd
+ '#default_value' => $default_value,
+ );
+ }
+ else {
+ $form['import_options']["datastore_manager_config_{$property}"] = [
+ '#type' => 'textfield',
+ // @codingStandardsIgnoreStart
+ '#title' => t($propety_label),
+ // @codingStandardsIgnoreEnd
+ '#default_value' => $default_value,
+ ];
+ }
+ }
+
+ return $form;
+ }
+
+ /**
+ * Submit.
+ */
+ public function submit($value) {
+ $configurable_properties = [];
+ foreach ($value as $property_name => $v) {
+ if (!empty($v)) {
+ $pname = str_replace("datastore_manager_config_", "", $property_name);
+ if ($pname == "trailing_delimiter") {
+ $configurable_properties[$pname] = ($v == 1) ? TRUE : FALSE;
+ }
+ else {
+ $configurable_properties[$pname] = $v;
+ }
+ }
+ }
+
+ $this->datastoreManager->setConfigurableProperties($configurable_properties);
+ $this->datastoreManager->saveState();
+ }
+
+}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/src/Page/Component/ManagerSelection.php b/profiles/dkan/modules/dkan/dkan_datastore/src/Page/Component/ManagerSelection.php
new file mode 100644
index 00000000000..f406458b26b
--- /dev/null
+++ b/profiles/dkan/modules/dkan/dkan_datastore/src/Page/Component/ManagerSelection.php
@@ -0,0 +1,69 @@
+resource = $resource;
+ $this->datastoreManager = $manager;
+ }
+
+ /**
+ * Get form.
+ */
+ public function getForm() {
+ $managers_info = dkan_datastore_managers_info();
+ $form = array();
+
+ // We only show this if there are multiple managers.
+ if (count($managers_info) > 1) {
+ $class = get_class($this->datastoreManager);
+
+ $options = [];
+
+ /* @var $manager_info \Dkan\Datastore\Manager\Info */
+ foreach ($managers_info as $manager_info) {
+ $options[$manager_info->getClass()] = $manager_info->getLabel();
+ }
+ $form['datastore_managers_selection'] = array(
+ '#type' => 'select',
+ '#title' => t('Change datastore importer:'),
+ '#options' => $options,
+ '#default_value' => "\\$class",
+ );
+ }
+
+ return $form;
+ }
+
+ /**
+ * Submit.
+ */
+ public function submit($values) {
+ $class = $values;
+
+ $factory = new Factory($this->resource);
+ $factory->setClass($class);
+
+ /* @var $manager \Dkan\Datastore\Manager\ManagerInterface */
+ $manager = $factory->get();
+ $manager->saveState();
+ }
+
+}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/src/Page/Component/Status.php b/profiles/dkan/modules/dkan/dkan_datastore/src/Page/Component/Status.php
new file mode 100644
index 00000000000..752b54a6fbd
--- /dev/null
+++ b/profiles/dkan/modules/dkan/dkan_datastore/src/Page/Component/Status.php
@@ -0,0 +1,88 @@
+datastoreManager = $manager;
+ }
+
+ /**
+ * Get HTML.
+ */
+ public function getHtml() {
+ $state = $this->datastoreManager->getStatus();
+ $stringSubs = [
+ 'class' => $this->formatClassName(get_class($this->datastoreManager)),
+ 'records' => $this->datastoreManager->numberOfRecordsImported(),
+ 'import' => self::datastoreStateToString($state['data_import']),
+ ];
+
+ $statusInfo = "
" . t("Importer") . "
{$stringSubs['class']}
";
+ $statusInfo .= "
" . t("Records Imported") . "
{$stringSubs['records']}
";
+ $statusInfo .= "
" . t("Data Importing") . "
{$stringSubs['import']}
";
+
+ return "
{$statusInfo}
";
+ }
+
+ /**
+ * Format the class name to something prettier.
+ */
+ private function formatClassName($classname) {
+ /* @var $info \Dkan\Datastore\Manager\Info */
+ foreach (dkan_datastore_managers_info() as $info) {
+ if ('\\' . $classname == $info->getClass()) {
+ return $info->getLabel();
+ }
+ }
+ // Fallback if this fails for some reason.
+ $nameBits = explode('\\', $classname);
+ return end($nameBits);
+ }
+
+ /**
+ * Private method.
+ */
+ public static function datastoreStateToString($state) {
+ switch ($state) {
+ case ManagerInterface::STORAGE_UNINITIALIZED:
+ return t("Uninitialized");
+
+ case ManagerInterface::STORAGE_INITIALIZED:
+ return t("Initialized");
+
+ case ManagerInterface::DATA_IMPORT_UNINITIALIZED:
+ return t("Ready");
+
+ case ManagerInterface::DATA_IMPORT_READY:
+ return t("Ready");
+
+ case ManagerInterface::DATA_IMPORT_IN_PROGRESS:
+ return t("In Progress");
+
+ case ManagerInterface::DATA_IMPORT_PAUSED:
+ drupal_set_message(t("The datastore importer is currently paused. It will resume in the background the next time cron runs from drush."));
+ return t("Paused");
+
+ case ManagerInterface::DATA_IMPORT_DONE:
+ return t("Done");
+
+ case ManagerInterface::DATA_IMPORT_ERROR:
+ return t("Error");
+ }
+ }
+
+}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/src/Page/Page.php b/profiles/dkan/modules/dkan/dkan_datastore/src/Page/Page.php
new file mode 100644
index 00000000000..496f588f9a1
--- /dev/null
+++ b/profiles/dkan/modules/dkan/dkan_datastore/src/Page/Page.php
@@ -0,0 +1,258 @@
+node = $node;
+ $this->form = $form;
+ $this->formState = $form_state;
+ }
+
+ /**
+ * Get the page/form.
+ */
+ public function get() {
+ try {
+ /* @var $resource \Dkan\Datastore\Resource */
+ $resource = Resource::createFromDrupalNode($this->node);
+
+ /* @var $manager ManagerInterface */
+ $manager = (new Factory($resource))->get();
+
+ // The drop button was pressed. Lets confirmed.
+ if (isset($this->formState['storage']) && isset($this->formState['storage']['drop'])) {
+ return $this->dropForm();
+ }
+
+ $html = '
Import the data from a CSV or TSV file into a database table to make it accessible through an API.
",
+ ];
+
+ $status = $manager->getStatus();
+ if (in_array($status['data_import'], [ManagerInterface::DATA_IMPORT_READY, ManagerInterface::DATA_IMPORT_UNINITIALIZED])) {
+
+ $this->form += (new ManagerSelection($resource, $manager))->getForm();
+
+ $this->form += (new ManagerConfiguration($manager))->getForm();
+
+ $this->form['actions'] = array('#type' => 'actions');
+ $this->form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t("Import"),
+ );
+ }
+ elseif (in_array($status['data_import'],
+ [
+ ManagerInterface::DATA_IMPORT_DONE,
+ ManagerInterface::DATA_IMPORT_PAUSED,
+ ManagerInterface::DATA_IMPORT_ERROR,
+ ])) {
+ $this->form['actions']['drop'] = array(
+ '#type' => 'submit',
+ '#value' => t("Drop"),
+ '#submit' => array('dkan_datastore_drop_submit'),
+ );
+ }
+ elseif (in_array($status['data_import'], [ManagerInterface::DATA_IMPORT_IN_PROGRESS])) {
+ $this->form['actions']['stop'] = array(
+ '#type' => 'submit',
+ '#value' => t("Stop"),
+ '#submit' => array('dkan_datastore_stop_submit'),
+ );
+
+ $this->form['actions']['advanced'] = [
+ '#type' => 'fieldset',
+ '#title' => t('Advanced'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ ];
+
+ $this->form['actions']['advanced']['help'] = [
+ '#type' => 'item',
+ '#markup' => 'When a datastore import shows an "in-progress" state but is stalled (no active cron job is importing new records into the datastore), the "Go to Paused State" button will return the datastore import to the paused state. It will then continue to be processed in the next cron run. Use this option with caution, as it will cause problems with the datastore if used in any scenario other than the one described above.',
+ ];
+
+ $this->form['actions']['advanced']['pause'] = array(
+ '#type' => 'submit',
+ '#value' => t("Go to Paused State"),
+ '#submit' => array('dkan_datastore_go_to_paused_state_submit'),
+ );
+ }
+
+ return $this->form;
+ }
+ catch (\Exception $e) {
+ drupal_set_message("The datastore does not support node {$this->node->nid}: {$e->getMessage()}");
+ drupal_goto("/node/{$this->node->nid}");
+ }
+ return [];
+ }
+
+ /**
+ * Form Submit.
+ */
+ public function submit() {
+ $resource = Resource::createFromDrupalNode($this->node);
+
+ /* @var $manager ManagerInterface */
+ $manager = (new Factory($resource))->get();
+
+ $values = $this->formState['values'];
+
+ try {
+ $value = isset($values['datastore_managers_selection']) ? $values['datastore_managers_selection'] : NULL;
+ if (isset($value)) {
+ (new ManagerSelection($resource, $manager))->submit($value);
+
+ // The manager got configured we have to reload it.
+ $manager = (new Factory($resource))->get();
+ }
+
+ $manager_values = [];
+ foreach ($values as $property_name => $v) {
+ if (substr_count($property_name, "datastore_manager_config") > 0) {
+ $manager_values[$property_name] = $v;
+ }
+ }
+ if (!empty($manager_values)) {
+ (new ManagerConfiguration($manager))->submit($manager_values);
+ }
+
+ if ($values['submit'] == "Import") {
+ $this->batchConfiguration($manager);
+ }
+ elseif ($values['submit'] == "Drop") {
+ $this->dropFormSubmit($manager);
+ }
+ }
+ catch (\Exception $e) {
+ drupal_set_message($e->getMessage());
+ }
+ }
+
+ /**
+ * Batch event handler.
+ */
+ public function batchProcess($manager, &$context) {
+ stream_wrapper_restore("https");
+ stream_wrapper_restore("http");
+ if (!isset($context['sandbox']['progress'])) {
+ $context['sandbox']['progress'] = 0;
+ $context['sandbox']['max'] = 1;
+ }
+
+ try {
+ /* @var $manager ManagerInterface */
+ $finished = $manager->import();
+ if ($finished == ManagerInterface::DATA_IMPORT_ERROR) {
+ $general = "DKAN DATASTORE: There was a problem while importing the Resource";
+ $errors = $manager->getErrors();
+ $error_string = implode(" | ", $errors);
+ $final_error_string = "{$general} - {$error_string}";
+ drupal_set_message($final_error_string, 'error');
+ }
+ }
+ catch (\Exception $e) {
+ $context['sandbox']['progress'] = 1;
+ drupal_set_message($e->getMessage(), 'error');
+ }
+
+ if ($finished == ManagerInterface::DATA_IMPORT_PAUSED) {
+ return FALSE;
+ }
+
+ $context['sandbox']['progress']++;
+
+ if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
+ $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+ }
+ }
+
+ /**
+ * Batch event handler.
+ */
+ public function batchFinished($success, $results, $operations) {
+ }
+
+ /**
+ * Setting up the batch process for importing a file.
+ */
+ private function batchConfiguration(ManagerInterface $manager) {
+ $manager->setImportTimelimit(self::BATCH_TIME_LIMIT);
+
+ $batch = array(
+ 'operations' => [],
+ 'finished' => [$this, 'batchFinished'],
+ 'title' => t('Importing.'),
+ 'init_message' => t('Starting Import.'),
+ 'progress_message' => t('Processed @current out of @total.'),
+ 'error_message' => t('An error occurred during import.'),
+ );
+
+ for ($i = 0; $i < self::BATCH_ITERATIONS; $i++) {
+ $batch['operations'][] = [[$this, 'batchProcess'], [$manager]];
+ }
+
+ batch_set($batch);
+ }
+
+ /**
+ * Drop form.
+ */
+ private function dropForm() {
+ $node = $this->form['#node'];
+
+ $question = t('Are you sure you want to drop this datastore?');
+ $path = 'node/' . $node->nid . '/datastore';
+ $description = t('This operation will destroy the db table and all the data previously imported.');
+ $yes = t('Drop');
+ $no = t('Cancel');
+ $name = 'drop';
+
+ return confirm_form($this->form, $question, $path, $description, $yes, $no, $name);
+ }
+
+ /**
+ * Form Submit.
+ */
+ private function dropFormSubmit(ManagerInterface $manager) {
+ $manager->drop();
+ $this->formState['redirect'] = "node/{$this->node->nid}/datastore";
+ drupal_set_message(t("The datastore for %title has been successfully dropped.", ['%title' => $this->node->title]));
+ }
+
+}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/src/Parser/Base.php b/profiles/dkan/modules/dkan/dkan_datastore/src/Parser/Base.php
new file mode 100644
index 00000000000..0b507943f5b
--- /dev/null
+++ b/profiles/dkan/modules/dkan/dkan_datastore/src/Parser/Base.php
@@ -0,0 +1,77 @@
+reset();
+ }
+
+ /**
+ * Feeds the parser a chunck of string to be parsed.
+ *
+ * @param string $chunk
+ * Part of what we are parsing.
+ */
+ public function feed($chunk) {
+ $this->chunck = $chunk;
+ $chars = str_split($chunk);
+
+ for ($i = 0; $i < count($chars); $i++) {
+ $char = $chars[$i];
+ $this->stateMachineInput($char);
+ }
+ }
+
+ /**
+ * It sets the parser's state to its initial state.
+ */
+ public function reset() {
+ $this->setupStateMachine();
+ }
+
+ /**
+ * Get a proper state machine input and feed it to state machine.
+ */
+ protected function stateMachineInput($char) {
+ $this->currentChar = $char;
+ $input = $this->getStateMachineInput($char);
+ $this->feedStateMachine($input);
+ }
+
+}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/src/Parser/Csv.php b/profiles/dkan/modules/dkan/dkan_datastore/src/Parser/Csv.php
new file mode 100644
index 00000000000..a79c4c73f8a
--- /dev/null
+++ b/profiles/dkan/modules/dkan/dkan_datastore/src/Parser/Csv.php
@@ -0,0 +1,216 @@
+trailingDelimiter = TRUE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function feed($chunk) {
+ $this->chunck = $chunk;
+ $chars = str_split($chunk);
+
+ for ($i = 0; $i < count($chars); $i++) {
+ $char = $chars[$i];
+
+ // Ignore consecutive endline chars.
+ if (!(in_array($this->currentChar, $this->recordEnd) && in_array($char, $this->recordEnd))) {
+ $this->stateMachineInput($char);
+ }
+ }
+ }
+
+ /**
+ * Get a record.
+ */
+ public function getRecord() {
+ return array_shift($this->records);
+ }
+
+ /**
+ * Informs the parser that we are done.
+ */
+ public function finish() {
+ if (!in_array($this->currentChar, $this->recordEnd)) {
+ $this->feed($this->recordEnd[0]);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reset() {
+ parent::reset();
+ $this->field = "";
+ $this->fields = [];
+ $this->records = [];
+ $this->fieldParser = NULL;
+ }
+
+ /**
+ * Create a new record from the current state.
+ */
+ public function createNewRecord() {
+ $this->createNewField();
+
+ // A CSV with trailing delimiter characters in each record will create an
+ // empty field at the end of each record. Here we check to see if that last
+ // field is empty when we are in "trailingDelimiter" mode and remove that
+ // last field from the record. The reason we need to check for emptiness is
+ // that it is possible for a record to not have a trailing delimiter and we
+ // do not want to remove a valid field.
+ if ($this->trailingDelimiter && empty($this->fields[count($this->fields) - 1])) {
+ array_pop($this->fields);
+ }
+
+ $this->records[] = $this->fields;
+ $this->fields = [];
+ }
+
+ /**
+ * Create a new field from the current state.
+ */
+ public function createNewField() {
+ $this->fields[] = $this->field;
+ $this->field = "";
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setupStateMachine() {
+ $states = [
+ "NEUTRAL",
+ ];
+
+ $inputs = [
+ "BLANK",
+ "DELIMITER",
+ "END",
+ ];
+
+ $this->stateMachine = new StateMachine($states, $inputs);
+ $this->stateMachine->addInitialState("NEUTRAL");
+
+ $this->stateMachine->addTransition(
+ "NEUTRAL",
+ "BLANK",
+ "NEUTRAL",
+ TRUE);
+
+ $this->stateMachine->addTransition(
+ "NEUTRAL",
+ "DELIMITER",
+ "NEUTRAL",
+ [$this, "createNewField"]);
+
+ $this->stateMachine->addTransition(
+ "NEUTRAL",
+ "END",
+ "NEUTRAL",
+ [$this, "createNewRecord"]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function feedStateMachine($input) {
+
+ // No field parser is active, we will handle things.
+ if (!$this->fieldParser) {
+ try {
+ $this->stateMachine->processInput($input);
+ }
+ catch (\Exception $e) {
+ // We couldn't handle it, lets try an appropriate field parser.
+ if ($input == "QUOTE") {
+ $this->fieldParser = new QuotedField($this->delimiter, $this->quote, $this->escape, $this->recordEnd);
+ }
+ else {
+ $this->fieldParser = new Field($this->delimiter, $this->quote, $this->escape, $this->recordEnd);
+ }
+ }
+ }
+
+ // A field parser is active, let them do their thing.
+ if ($this->fieldParser) {
+ try {
+ $this->fieldParser->feed($this->currentChar);
+ }
+ // The field parser could not handle it.
+ catch (\Exception $e) {
+ // Lets get their work,.
+ $this->field = $this->fieldParser->getField();
+ $this->fieldParser = NULL;
+
+ // And lets try ourselves again.
+ try {
+ $this->stateMachine->processInput($input);
+ }
+ // If we can not handle it, we have a parsing issue.
+ catch (\Exception $e) {
+ $debug_info = [];
+ $debug_info['Message'] = $e->getMessage();
+ $debug_info['Chunk'] = $this->chunck;
+ $debug_info['Records'] = $this->records;
+ $debug_info['Fields'] = $this->fields;
+ $debug_info['Field'] = $this->field;
+
+ $json = json_encode($debug_info, JSON_PRETTY_PRINT);
+
+ throw new \Exception("Error parsing CSV: {$json}");
+ }
+ }
+ }
+ }
+
+}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/src/Parser/CsvBase.php b/profiles/dkan/modules/dkan/dkan_datastore/src/Parser/CsvBase.php
new file mode 100644
index 00000000000..94e0f643715
--- /dev/null
+++ b/profiles/dkan/modules/dkan/dkan_datastore/src/Parser/CsvBase.php
@@ -0,0 +1,60 @@
+recordEnd = $record_end;
+ $this->delimiter = $delimiter;
+ $this->quote = $quote;
+ $this->escape = $escape;
+
+ parent::__construct();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getStateMachineInput($char) {
+ if (in_array($char, $this->recordEnd)) {
+ return "END";
+ }
+ if ($char == $this->delimiter) {
+ return "DELIMITER";
+ }
+ if ($char == $this->quote) {
+ return "QUOTE";
+ }
+ if ($char == $this->escape) {
+ return "ESCAPE";
+ }
+ if (ctype_space($char)) {
+ return "BLANK";
+ }
+ return "OTHER";
+ }
+
+}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/src/Parser/Field.php b/profiles/dkan/modules/dkan/dkan_datastore/src/Parser/Field.php
new file mode 100644
index 00000000000..4d998ae2617
--- /dev/null
+++ b/profiles/dkan/modules/dkan/dkan_datastore/src/Parser/Field.php
@@ -0,0 +1,93 @@
+field, " ");
+ }
+
+ /**
+ * Add the current char to the field.
+ */
+ public function addCharToField() {
+ $this->field .= $this->currentChar;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setupStateMachine() {
+ $states = [
+ "CAPTURE",
+ "ESCAPE",
+ ];
+
+ $inputs = [
+ "ESCAPE",
+ "BLANK",
+ "OTHER",
+ "END",
+ "DELIMITER",
+ ];
+
+ $this->stateMachine = new StateMachine($states, $inputs);
+ $this->stateMachine->addInitialState("CAPTURE");
+
+ $this->stateMachine->addTransition(
+ "CAPTURE",
+ "BLANK",
+ "CAPTURE",
+ [$this, "addCharToField"]);
+
+ $this->stateMachine->addTransition(
+ "CAPTURE",
+ "OTHER",
+ "CAPTURE",
+ [$this, "addCharToField"]);
+
+ $this->stateMachine->addTransition(
+ "CAPTURE",
+ "ESCAPE",
+ "ESCAPE",
+ TRUE);
+
+ $this->stateMachine->addTransition(
+ "ESCAPE",
+ ["END", "DELIMITER", "ESCAPE", "BLANK", "OTHER"],
+ "CAPTURE",
+ [$this, "addCharToField"]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function feedStateMachine($input) {
+ $this->stateMachine->processInput($input);
+ }
+
+}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/src/Parser/QuotedField.php b/profiles/dkan/modules/dkan/dkan_datastore/src/Parser/QuotedField.php
new file mode 100644
index 00000000000..537f51bc792
--- /dev/null
+++ b/profiles/dkan/modules/dkan/dkan_datastore/src/Parser/QuotedField.php
@@ -0,0 +1,175 @@
+field;
+ }
+
+ /**
+ * Add the current char to the field.
+ */
+ public function addCharToField($input) {
+ // If we get a mismatch between input and the current char,
+ // it is likely that our current char is out of sync due
+ // to double quote escape handling. In that case, lets try
+ // to translate our input into the proper character.
+ if ($this->getStateMachineInput($this->currentChar) != $input) {
+ $this->field .= $this->inputToChar($input);
+ }
+ else {
+ $this->field .= $this->currentChar;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setupStateMachine() {
+ $states = [
+ "INITIAL",
+ "CAPTURE",
+ "ESCAPE",
+ "END",
+ ];
+
+ $inputs = [
+ "QUOTE",
+ "ESCAPE",
+ "BLANK",
+ "OTHER",
+ "END",
+ "DELIMITER",
+ ];
+
+ $this->stateMachine = new StateMachine($states, $inputs);
+
+ $this->stateMachine->setAmbiguousInputHandler([$this, "disambiguateInput"]);
+ $this->stateMachine->addAmbiguousInput("QUOTE");
+ $this->stateMachine->addInitialState("INITIAL");
+ $this->stateMachine->addEndState("END");
+
+ $this->stateMachine->addTransition(
+ "INITIAL",
+ "QUOTE",
+ "CAPTURE",
+ TRUE);
+
+ $this->stateMachine->addTransition(
+ "CAPTURE",
+ ["BLANK", "OTHER", "END", "DELIMITER"],
+ "CAPTURE",
+ [$this, "addCharToField"]);
+
+ $this->stateMachine->addTransition(
+ "CAPTURE",
+ "ESCAPE",
+ "ESCAPE",
+ TRUE);
+
+ $this->stateMachine->addTransition(
+ "ESCAPE",
+ ["QUOTE", "END", "DELIMITER", "ESCAPE", "BLANK", "OTHER"],
+ "CAPTURE",
+ [$this, "addCharToField"]);
+
+ $this->stateMachine->addTransition(
+ "CAPTURE",
+ "QUOTE",
+ "END",
+ TRUE);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function feedStateMachine($input) {
+ $this->stateMachine->processInput($input);
+ }
+
+ /**
+ * Diambiguation handler for the state machine.
+ *
+ * Once we are capturing characters for the field,
+ * when we get a string-quoting-character, we do not know
+ * whether it should be the end of the quoted string or if
+ * it is being used as a means to escape a string-quoting-character
+ * inside the string.
+ *
+ * We disambiguate by letting the state machine know that any
+ * string-quoting-character follow by another should be treated
+ * as an escape character.
+ */
+ public function disambiguateInput($inputs, $current_state) {
+ if ($current_state == "CAPTURE") {
+ if ($inputs[0] == "QUOTE" && $inputs[1] == "QUOTE") {
+ return "ESCAPE";
+ }
+ else {
+ return "QUOTE";
+ }
+ }
+ else {
+ return "QUOTE";
+ }
+ }
+
+ /**
+ * Translate a state machine input into a character.
+ */
+ private function inputToChar($input) {
+ if ($input == "QUOTE") {
+ return $this->quote;
+ }
+ else {
+ return "?";
+ }
+ }
+
+}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/src/Resource.php b/profiles/dkan/modules/dkan/dkan_datastore/src/Resource.php
new file mode 100644
index 00000000000..92b040f3d0e
--- /dev/null
+++ b/profiles/dkan/modules/dkan/dkan_datastore/src/Resource.php
@@ -0,0 +1,128 @@
+id = $id;
+ $this->filePath = $file_path;
+ }
+
+ /**
+ * Getter.
+ */
+ public function getId() {
+ return $this->id;
+ }
+
+ /**
+ * Getter.
+ */
+ public function getFilePath() {
+ return $this->filePath;
+ }
+
+ /**
+ * Create a resource from a Resource Node's uuid.
+ *
+ * @param string $uuid
+ * A node's uuid.
+ *
+ * @return Resource
+ * Resource.
+ */
+ public static function createFromDrupalNodeUuid($uuid) {
+ $nid = self::getNidFromUuid($uuid);
+ return self::createFromDrupalNodeNid($nid);
+ }
+
+ /**
+ * Create a resource from a Resource Node's nid.
+ *
+ * @param string $nid
+ * A node's nid.
+ *
+ * @return Resource
+ * Resource.
+ */
+ public static function createFromDrupalNodeNid($nid) {
+ if ($node = node_load($nid)) {
+ return self::createFromDrupalNode($node);
+ }
+ throw new \Exception(t('Failed to load resource node.'));
+ }
+
+ /**
+ * Create a resource from a Resource Node.
+ *
+ * @param object $node
+ * A node.
+ *
+ * @return Resource
+ * Resource.
+ */
+ public static function createFromDrupalNode($node) {
+ if ($node->type != 'resource') {
+ throw new \Exception(t('Invalid node type.'));
+ }
+ $id = $node->nid;
+ $file_path = self::filePath($node);
+ return new self($id, $file_path);
+ }
+
+ /**
+ * Gets nid using uuid.
+ */
+ private static function getNidFromUuid($uuid) {
+ $nid = db_query('SELECT nid FROM {node} WHERE uuid = :uuid', array(':uuid' => $uuid))->fetchField();
+ if ($nid) {
+ return $nid;
+ }
+ else {
+ throw new \Exception(t("uuid !uuid not found.", array('!uuid' => $uuid)));
+ }
+ }
+
+ /**
+ * Get the full path to a resource's file.
+ *
+ * Regardless of whether it was uploaded or a remote file.
+ */
+ private static function filePath($node) {
+ if (isset($node->field_upload[LANGUAGE_NONE][0]['fid'])) {
+ // We can't trust that the field_upload array will contain a real uri, so
+ // we need to load the full file object.
+ $file = file_load($node->field_upload[LANGUAGE_NONE][0]['fid']);
+ $filemime = $file->filemime;
+ if (!in_array($filemime, ["text/csv", "text/tsv"])) {
+ throw new \Exception("This filemime type ({$filemime}) can be added as a resource, but cannot be imported to our datastore.");
+ }
+
+ $drupal_uri = $file->uri;
+ return drupal_realpath($drupal_uri);
+ }
+ if (isset($node->field_link_remote_file[LANGUAGE_NONE][0]['fid'])) {
+ $file = file_load($node->field_link_remote_file[LANGUAGE_NONE][0]['fid']);
+ if ($filemime = $file->filemime) {
+ if (!in_array($filemime, ["text/csv", "text/tsv", "text/psv"])) {
+ throw new \Exception("This filemime type ({$filemime}) can be added as a resource, but cannot be imported to our datastore.");
+ }
+ }
+ stream_wrapper_restore("https");
+ stream_wrapper_restore("http");
+ return $file->uri;
+ }
+ throw new \Exception(t("Node !nid doesn't have a proper file path.", array('!nid' => $node->nid)));
+ }
+
+}
diff --git a/profiles/dkan/modules/dkan/dkan_datastore/src/StateMachine.php b/profiles/dkan/modules/dkan/dkan_datastore/src/StateMachine.php
new file mode 100644
index 00000000000..b958f0ef945
--- /dev/null
+++ b/profiles/dkan/modules/dkan/dkan_datastore/src/StateMachine.php
@@ -0,0 +1,223 @@
+states = $states;
+ $this->inputs = $inputs;
+ }
+
+ /**
+ * Set an ambiguous input handler.
+ */
+ public function setAmbiguousInputHandler($callable) {
+ $this->ambiguousInputHandler = $callable;
+ }
+
+ /**
+ * Mark inputs as ambiguous in the machine.
+ */
+ public function addAmbiguousInput($input) {
+ if (isset($this->ambiguousInputHandler)) {
+ if (in_array($input, $this->inputs)) {
+ $this->ambiguousInputs[] = $input;
+ }
+ else {
+ throw new \Exception("Invalid input: {$input}");
+ }
+ }
+ else {
+ throw new \Exception("Declare and ambiguous input handler before adding ambiguous inputs");
+ }
+ }
+
+ /**
+ * Set the initial state.
+ */
+ public function addInitialState($state) {
+ if ($this->stateIsValid($state)) {
+ $this->initialState = $state;
+ }
+ else {
+ throw new \Exception("Invalid initial state {$state}");
+ }
+ }
+
+ /**
+ * Set an end state.
+ */
+ public function addEndState($state) {
+ if ($this->stateIsValid($state)) {
+ $this->endState = $state;
+ }
+ else {
+ throw new \Exception("Invalid end state {$state}");
+ }
+ }
+
+ /**
+ * Note transitions, and an action if relevant.
+ */
+ public function addTransition($current_state, $inputs, $next_state, $callable = TRUE) {
+
+ if (!is_array($inputs)) {
+ $inputs = [$inputs];
+ }
+
+ foreach ($inputs as $input) {
+ if ($this->stateIsValid($current_state) && in_array($input, $this->inputs) &&
+ $this->stateIsValid($next_state)) {
+ $this->transitions[$current_state][$input][$next_state] = $callable;
+ }
+ else {
+ throw new \Exception("Invalid transition: {$current_state}->{$input}->{$next_state}");
+ }
+ }
+ }
+
+ /**
+ * Give the state machine an input for it to work.
+ */
+ public function processInput($input) {
+
+ $this->setCurrentStateIfNotSet();
+
+ if ($this->processPreviouslyFoundAmbiguousInputs($input)) {
+ return;
+ }
+
+ if ($this->checkAndCaptureAmbiguousInputs($input)) {
+ return;
+ }
+
+ // Handle input after the end state.
+ if ($this->currentState == $this->endState) {
+ throw new \Exception("Already at end state {$this->endState}, no more processing can be done.");
+ }
+
+ $this->letTheStateMachineWork($input);
+ }
+
+ /**
+ * Private.
+ */
+ private function setCurrentStateIfNotSet() {
+ if (!isset($this->currentState)) {
+ if (!isset($this->initialState)) {
+ throw new \Exception("An initial state must be provided to start processing");
+ }
+ $this->currentState = $this->initialState;
+ }
+ }
+
+ /**
+ * Private.
+ */
+ private function processPreviouslyFoundAmbiguousInputs($input) {
+ if (!empty($this->buffer)) {
+ $this->buffer[] = $input;
+
+ $buffer = $this->buffer;
+ $this->buffer = [];
+ $final_input = call_user_func($this->ambiguousInputHandler, $buffer, $this->currentState);
+ $buffer[0] = $final_input;
+
+ $this->disambiguated = TRUE;
+ $counter = 0;
+ foreach ($buffer as $input) {
+ $this->processInput($input);
+ if ($counter == 0) {
+ $this->disambiguated = FALSE;
+ }
+ $counter++;
+ }
+
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ /**
+ * Private.
+ */
+ private function checkAndCaptureAmbiguousInputs($input) {
+ if (in_array($input, $this->ambiguousInputs) && !$this->disambiguated) {
+ $this->buffer[] = $input;
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ /**
+ * Private.
+ */
+ private function letTheStateMachineWork($input) {
+ if ($this->transitionIsValid($input)) {
+ $next_state = $this->getNextState($input);
+ $this->executeTransitionCallable($input, $next_state);
+ $this->currentState = $next_state;
+ }
+ else {
+ throw new \Exception("Invalid Input {$input}");
+ }
+ }
+
+ /**
+ * Private.
+ */
+ private function transitionIsValid($input) {
+ return isset($this->transitions[$this->currentState][$input]);
+ }
+
+ /**
+ * Private.
+ */
+ private function getNextState($input) {
+ $keys = array_keys($this->transitions[$this->currentState][$input]);
+ return $keys[0];
+ }
+
+ /**
+ * Private.
+ */
+ private function executeTransitionCallable($input, $next_state) {
+ $callable = $this->transitions[$this->currentState][$input][$next_state];
+ if (!is_bool($callable)) {
+ if (!call_user_func($callable, $input) == FALSE) {
+ $json = json_encode($callable);
+ throw new \Exception("Unable to call callable {$json}");
+ }
+ }
+ }
+
+ /**
+ * Check state validity.
+ */
+ private function stateIsValid($state) {
+ return in_array($state, $this->states);
+ }
+
+}
diff --git a/profiles/dkan/modules/dkan/dkan_environment/dkan_environment.info b/profiles/dkan/modules/dkan/dkan_environment/dkan_environment.info
index 3b9b81cd4e3..949659a52f4 100755
--- a/profiles/dkan/modules/dkan/dkan_environment/dkan_environment.info
+++ b/profiles/dkan/modules/dkan/dkan_environment/dkan_environment.info
@@ -3,4 +3,4 @@ description = Basic environments management (Local, Development, Production, etc
core = 7.x
dependencies[] = environment
dependencies[] = environment_indicator
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_environment/dkan_environment.module b/profiles/dkan/modules/dkan/dkan_environment/dkan_environment.module
index de6fbeb95ea..2a7e28d5526 100644
--- a/profiles/dkan/modules/dkan/dkan_environment/dkan_environment.module
+++ b/profiles/dkan/modules/dkan/dkan_environment/dkan_environment.module
@@ -11,9 +11,9 @@ function dkan_environment_environment() {
'description' => t('Local Development Environment.'),
);
- $environments['stage'] = array(
- 'label' => t('Stage'),
- 'description' => t('Staging Environment.'),
+ $environments['test'] = array(
+ 'label' => t('Test'),
+ 'description' => t('Test/Staging Environment.'),
);
return $environments;
diff --git a/profiles/dkan/modules/dkan/dkan_fixtures/dkan_fixtures.info b/profiles/dkan/modules/dkan/dkan_fixtures/dkan_fixtures.info
index 51b8d7eba45..b8433a41448 100644
--- a/profiles/dkan/modules/dkan/dkan_fixtures/dkan_fixtures.info
+++ b/profiles/dkan/modules/dkan/dkan_fixtures/dkan_fixtures.info
@@ -14,4 +14,4 @@ files[] = includes/page.inc
files[] = includes/panelized_node.inc
files[] = includes/resource.inc
files[] = includes/visualization_entity.inc
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_fixtures/modules/dkan_default_content/data/files/resource/Polling_Places_Madison_0.csv b/profiles/dkan/modules/dkan/dkan_fixtures/modules/dkan_default_content/data/files/resource/Polling_Places_Madison_0.csv
deleted file mode 100644
index eacf9fc6485..00000000000
--- a/profiles/dkan/modules/dkan/dkan_fixtures/modules/dkan_default_content/data/files/resource/Polling_Places_Madison_0.csv
+++ /dev/null
@@ -1,118 +0,0 @@
-Ward,Aldermanic District,Name,Address,Comments,Handicap Accessible,Latitude,Longitude
-1,16,Glendale Elementary School,"1201 Tompkins Dr Madison, WI (43.058534053250526, -89.31246859534969)",LMC,TRUE,43.05853405,-89.3124686
-2,16,Glendale Elementary School,"1201 Tompkins Dr Madison, WI (43.058534053250526, -89.31246859534969)",LMC,TRUE,43.05853405,-89.3124686
-3,16,City Church,"4909 Buckeye Rd Madison, WI (43.07356172436016, -89.311955981793)",Lower Level - Rear Entrance,TRUE,43.07356172,-89.31195598
-4,16,City Church,"4909 Buckeye Rd Madison, WI (43.07356172436016, -89.311955981793)",Lower Level - Rear Entrance,TRUE,43.07356172,-89.31195598
-5,16,Elvehjem Elementary,"5106 Academy Dr Madison, WI (43.07798638874431, -89.29494980221892)","West Hall by Gym B, Gym B used in November",TRUE,43.07798639,-89.2949498
-6,16,Elvehjem Elementary,"5106 Academy Dr Madison, WI (43.07798638874431, -89.29494980221892)","West Hall by Gym B, Gym B used in November",TRUE,43.07798639,-89.2949498
-7,16,City Church,"4909 Buckeye Rd Madison, WI (43.07356172436016, -89.311955981793)",Lower Level - Rear Entrance,TRUE,43.07356172,-89.31195598
-8,3,Door Creek Church,"6602 Dominion Dr Madison, WI (43.09104454460976, -89.26444479898504)",Gym,TRUE,43.09104454,-89.2644448
-9,3,East Police District,"809 Thompson Dr Madison, WI (43.10891702887152, -89.29824374052728)",Community room,TRUE,43.10891703,-89.29824374
-10,3,Kennedy Elementary,"221 Meadowlark Dr Madison, WI (43.09445297510081, -89.29816937508383)","LMC for most elections, gym for November",TRUE,43.09445298,-89.29816938
-11,3,American Family Insurance,"302 Walbridge Ave Madison, WI (43.102844085356566, -89.31148201090909)",Walbridge Room - park in front & use main entrance,TRUE,43.10284409,-89.31148201
-12,3,New Beginnings Church,"602 Acewood Blvd Madison, WI (43.089344825974365, -89.30234222929977)",Enter off parking lot - lower level,TRUE,43.08934483,-89.30234223
-13,15,LaFollette High School,"700 Pflaum Rd Madison, WI (43.06214214871068, -89.3187490170764)",Gym,TRUE,43.06214215,-89.31874902
-14,15,LaFollette High School,"700 Pflaum Rd Madison, WI (43.06214214871068, -89.3187490170764)",Gym,TRUE,43.06214215,-89.31874902
-15,15,LaFollette High School,"700 Pflaum Rd Madison, WI (43.06214214871068, -89.3187490170764)",Gym,TRUE,43.06214215,-89.31874902
-16,15,Whitehorse Middle School,"218 Schenk St Madison, WI (43.092755677235736, -89.32320250199108)",Gym,TRUE,43.09275568,-89.3232025
-17,15,American Family Insurance,"302 Walbridge Ave Madison, WI (43.102844085356566, -89.31148201090909)",Walbridge Room - park in front & use main entrance,TRUE,43.10284409,-89.31148201
-18,15,Hy-Vee,"3801 Washington Ave Madison, WI (43.11887443505523, -89.321263469189)",Community Room,TRUE,43.11887444,-89.32126347
-19,15,Madison Armory,"1402 Wright St Madison, WI (43.11571294787342, -89.33068885966333)","Drill Room, use front entrance on Wright St",TRUE,43.11571295,-89.33068886
-20,15,Madison Armory,"1402 Wright St Madison, WI (43.11571294787342, -89.33068885966333)","Drill Room, use front entrance on Wright St",TRUE,43.11571295,-89.33068886
-21,17,Hy-Vee,"3801 Washington Ave Madison, WI (43.11887443505523, -89.321263469189)",Community Room,TRUE,43.11887444,-89.32126347
-22,17,Streets East,"4602 Sycamore Ave Madison, WI (43.11401890912613, -89.30349081948516)", ,TRUE,43.11401891,-89.30349082
-23,17,Fire Station #11,"4011 Morgan Way Madison, WI (43.144131523999306, -89.28029984636163)", ,TRUE,43.14413152,-89.28029985
-24,17,Oakwood Village Prairie Ridge,"5565 Tancho Dr Madison, WI (43.15474754741973, -89.28390001630164)",Chapel,TRUE,43.15474755,-89.28390002
-25,17,Eastside Lutheran,"2310 Independence Ln Madison, WI (43.12947257492175, -89.31007639875173)",Gym,TRUE,43.12947257,-89.3100764
-26,17,Eastside Lutheran,"2310 Independence Ln Madison, WI (43.12947257492175, -89.31007639875173)",Gym,TRUE,43.12947257,-89.3100764
-27,12,Madison College - Commercial Avenue,"2125 Commercial Ave Madison, WI (43.10677111972665, -89.35743254298585)",Commons,TRUE,43.10677112,-89.35743254
-28,12,Madison College - Commercial Avenue,"2125 Commercial Ave Madison, WI (43.10677111972665, -89.35743254298585)",Commons,TRUE,43.10677112,-89.35743254
-29,12,East High School,"2222 Washington Ave Madison, WI (43.0964583786623, -89.35391055715205)","Fifth Street entrance, enter at door #10,Gym in November",TRUE,43.09645838,-89.35391056
-30,12,Sherman Middle School,"1610 Ruskin St Madison, WI (43.1178443410829, -89.36176043577564)",Wood Gym,TRUE,43.11784434,-89.36176044
-31,12,St Paul Lutheran,"2126 Sherman Ave Madison, WI (43.1017546654448, -89.36513826922703)",Fellowship Hall - enter off rear parking lot,TRUE,43.10175467,-89.36513827
-32,12,Packers Ave Townhomes,"1927 Northport Dr Madison, WI (43.1282444355317, -89.35764368075546)", ,TRUE,43.12824444,-89.35764368
-33,12,Warner Park Community Recreation Center,"1625 Northport Dr Madison, WI (43.13226190387762, -89.3673540801938)",Meeting Room,TRUE,43.1322619,-89.36735408
-34,18,Warner Park Community Recreation Center,"1625 Northport Dr Madison, WI (43.13226190387762, -89.3673540801938)",Meeting Room,TRUE,43.1322619,-89.36735408
-35,18,Mendota Elementary,"4002 School Rd Madison, WI (43.13672111047788, -89.38315530685264)","Main Entrance, Gym in November",TRUE,43.13672111,-89.38315531
-36,18,Mendota Elementary,"4002 School Rd Madison, WI (43.13672111047788, -89.38315530685264)","Main Entrance, Gym in November",TRUE,43.13672111,-89.38315531
-37,18,Lindbergh Elementary,"4500 Kennedy Rd Madison, WI (43.14364589010353, -89.3878857407268)",Gym,TRUE,43.14364589,-89.38788574
-38,18,Blackhawk Middle School,"1402 Wyoming Way Madison, WI (43.146137012124655, -89.37093352047447)",LMC,TRUE,43.14613701,-89.37093352
-39,6,Hawthorne Branch Library,"2707 Washington Ave Madison, WI (43.10192089186353, -89.34703102579658)",Community Room,TRUE,43.10192089,-89.34703103
-40,6,Olbrich Gardens,"3330 Atwood Ave Madison, WI (43.091620110708504, -89.33564412404473)",Community Room,TRUE,43.09162011,-89.33564412
-41,6,O'Keeffe Middle School,"510 Thornton Ave Madison, WI (43.08697936482315, -89.35702344211596)",Cafeteria - Enter off Spaight Street,TRUE,43.08697936,-89.35702344
-42,6,Wil-Mar Neighborhood Center,"953 Jenifer St Madison, WI (43.07995590398849, -89.3671833476123)",Yahara Room,TRUE,43.0799559,-89.36718335
-43,6,Madison Municipal Building,"215 Martin Luther King Jr Blvd Madison, WI (43.07286025317728, -89.3816714255043)",First Floor Lobby,TRUE,43.07286025,-89.38167143
-44,2,Tenney Park Pavilion,"402 Thornton Avenue Madison, WI (43.09350700384561, -89.36862302567746)", ,TRUE,43.093507,-89.36862303
-45,2,Lapham Elementary,"1045 Dayton St Madison, WI (43.085548841623904, -89.37270239744994)","Ingersoll Entrance, Gym in November",TRUE,43.08554884,-89.3727024
-46,2,Gates of Heaven,"302 Gorham Street Madison, WI (43.07965719714889, -89.38492560816975)", ,TRUE,43.0796572,-89.38492561
-47,2,Lowell Center,"610 Langdon St Madison, WI (43.075801036213136, -89.39583574746581)",Front Lobby,TRUE,43.07580104,-89.39583575
-48,2,Lowell Center,"610 Langdon Street Madison, WI (43.075801036213136, -89.39583574746581)",Front Lobby,TRUE,43.07580104,-89.39583575
-49,4,Madison Fresh Market,"703 University Avenue Madison, WI (43.07308494205887, -89.39748051436113)",Event Center on second floor,TRUE,43.07308494,-89.39748051
-50,4,Doyle Administration,"545 Dayton St Madison, WI (43.07078281153979, -89.39481679720781)",Auditorium,TRUE,43.07078281,-89.3948168
-51,4,Madison Senior Center,"330 Mifflin St Madison, WI (43.077841691679396, -89.38209760850428)", ,TRUE,43.07784169,-89.38209761
-52,4,Madison Municipal Building,"215 Martin Luther King Jr Blvd Madison, WI (43.07286025317728, -89.3816714255043)",Front Lobby,TRUE,43.07286025,-89.38167143
-53,4,Capitol Lakes Retirement,"333 Main St Madison, WI (43.07611418744057, -89.37975802114629)", ,TRUE,43.07611419,-89.37975802
-54,8,"Sept - May: UW Welcome Center, 21 N Park St","545 Dayton St Madison, WI (43.07078281153979, -89.39481679720781)", ,TRUE,43.07078281,-89.3948168
-55,8,Porchlight,"306 Brooks St Madison, WI (43.07225870892025, -89.4025007411189)",Dining Room,TRUE,43.07225871,-89.40250074
-56,8,"Sept - May: Gordon Dining, 770 W Dayton St","728 State St Madison, WI (43.074910707404115, -89.3984977406725)", ,TRUE,43.07491071,-89.39849774
-57,8,UW Memorial Library,"728 State St Madison, WI (43.074910707404115, -89.3984977406725)",Room 116,TRUE,43.07491071,-89.39849774
-58,8,UW Memorial Union,"800 Langdon St Madison, WI (43.07588300326599, -89.39893937645297)",Paul Bunyan Room or Tripp Commons,TRUE,43.075883,-89.39893938
-59,8,"Sept - May: Holt Commons, 1640 Kronshage Dr","800 Langdon St Madison, WI (43.07588300326599, -89.39893937645297)", ,TRUE,43.075883,-89.39893938
-60,5,Eagle Heights Community Center,"611 Eagle Hts Madison, WI (43.087673723008464, -89.43624462373921)",Room 101,TRUE,43.08767372,-89.43624462
-61,5,First Congregational Church,"1609 University Ave Madison, WI (43.0733908531746, -89.41412795754178)",First Floor Student Lounge or Lower Level Gym,TRUE,43.07339085,-89.41412796
-62,5,Blessed Sacrament Catholic Church,"2131 Rowley Ave Madison, WI (43.0670416396477, -89.42356184562914)",Lower Level Friary,TRUE,43.06704164,-89.42356185
-63,5,West High School,"30 Ash St Madison, WI (43.069038134552784, -89.42556733375443)",Van Hise Gym Entrance,TRUE,43.06903813,-89.42556733
-64,5,Hoyt School,"3802 Regent St Madison, WI (43.06801290743937, -89.43927300148096)","Room 13, Gym in November",TRUE,43.06801291,-89.439273
-65,13,Wingra School,"718 Gilmore St Madison, WI (43.055116629539214, -89.43382310998345)",First Floor Commons - Enter off rear parking lot,TRUE,43.05511663,-89.43382311
-66,13,St James Catholic School,"1204 St James Ct Madison, WI (43.06588151422994, -89.40580023555043)",Church basement,TRUE,43.06588151,-89.40580024
-67,13,Brittingham Apartments,"755 Braxton Pl Madison, WI (43.06622325660106, -89.3984977406725)",Library,TRUE,43.06622326,-89.39849774
-68,13,Trinity United Methodist Church,"1123 Vilas Ave Madison, WI (43.063111602379706, -89.4050717847067)",Lower Level Fellowship Hall - enter from west lot,TRUE,43.0631116,-89.40507178
-69,13,Bjarne Romnes Apartments,"540 Olin Ave Madison, WI (43.0540399812132, -89.39084379063013)",Community Room - Lower Level,TRUE,43.05403998,-89.39084379
-70,14,Bridge - Lake Point - Waunona Community Center,"1917 Lake Point Dr Madison, WI (43.04819184991362, -89.34713745957919)",Front Room,TRUE,43.04819185,-89.34713746
-71,14,Badger Rock Middle School,"501 Badger Road Madison, WI (43.03825268622941, -89.37899460473784)",Community Room,TRUE,43.03825269,-89.3789946
-72,14,Boys & Girls Club,"2001 Taft St Madison, WI (43.043920070274396, -89.3930028830735)","Second Floor conference room, or gym",TRUE,43.04392007,-89.39300288
-73,14,Village on Park,"2300 Park St Madison, WI (43.040604809545584, -89.39418405899839)",Community Room,TRUE,43.04060481,-89.39418406
-74,14,Leopold Elementary,"2602 Post Rd Madison, WI (43.02656315510893, -89.42174521512993)","Lobby, Gym in November",TRUE,43.02656316,-89.42174522
-75,14,Arbor Gate Center,"2501 Beltline Hwy Madison, WI (43.034753074596786, -89.41964433771305)",Lobby,TRUE,43.03475307,-89.41964434
-76,10,Head Start,"2096 Red Arrow Trl Madison, WI (43.0317432499977, -89.45696266579733)",Multipurpose Room,TRUE,43.03174325,-89.45696267
-77,10,Toki Middle School,"5606 Russett Rd Madison, WI (43.03304032661395, -89.47517968416098)",LMC,TRUE,43.03304033,-89.47517968
-78,10,Thoreau Elementary,"3870 Nakoma Rd Madison, WI (43.04570072789244, -89.44183606926447)",LMC,TRUE,43.04570073,-89.44183607
-79,10,Sequoya Branch Library,"4340 Tokay Boulevard Madison, WI (43.05364815940464, -89.45009305865894)",Large meeting room,TRUE,43.05364816,-89.45009306
-80,11,Midvale Elementary,"502 Caromar Dr Madison, WI (43.057337954951606, -89.44923748956563)",Lobby/front foyer or lower level gym,TRUE,43.05733795,-89.44923749
-81,11,Midvale Elementary,"502 Caromar Dr Madison, WI (43.057337954951606, -89.44923748956563)",Lobby/front foyer or lower level gym,TRUE,43.05733795,-89.44923749
-82,11,Hoyt School,"3802 Regent St Madison, WI (43.06801290743937, -89.43927300148096)","Room 13, Gym in November",TRUE,43.06801291,-89.439273
-83,11,Covenant Presbyterian Church,"326 Segoe Rd Madison, WI (43.070505395164275, -89.4546051546962)",Narthex,TRUE,43.0705054,-89.45460515
-84,11,Hill Farm State Office Building,"4802 Sheboygan Ave Madison, WI (43.07289622605845, -89.46065887933776)",Main Entrance,TRUE,43.07289623,-89.46065888
-85,11,Stephens Elementary,"120 Rosa Rd Madison, WI (43.06936703673043, -89.4782147477172)","Use main entrance, Gym in November",TRUE,43.06936704,-89.47821475
-86,19,Spring Harbor Middle School,"1110 Spring Harbor Dr Madison, WI (43.080783280255446, -89.47144175046907)",Room 7,TRUE,43.08078328,-89.47144175
-87,19,Crestwood Elementary,"5930 Old Sauk Rd Madison, WI (43.07529163808273, -89.48190010439991)",Lounge - Room 102,TRUE,43.07529164,-89.4819001
-88,19,Alicia Ashman Branch Library,"733 High Point Rd Madison, WI (43.07574529728328, -89.51852896407267)",Meeting Room,TRUE,43.0757453,-89.51852896
-89,19,Oakwood Village University Woods,"6205 Mineral Point Rd Madison, WI (43.060714848842935, -89.4876042740839)",Nakoma/Westmorland Rooms,TRUE,43.06071485,-89.48760427
-90,19,Madison Ice Arena,"725 Forward Dr Madison, WI (43.04842567364119, -89.4881781334343)",Conference Room,TRUE,43.04842567,-89.48817813
-91,20,Falk Elementary,"6323 Woodington Way Madison, WI (43.040862375374786, -89.4907591876584)",Front Entrance,TRUE,43.04086238,-89.49075919
-92,20,Our Redeemer Lutheran Church,"1701 McKenna Blvd Madison, WI (43.03373075168247, -89.49682961135619)",Community Room,TRUE,43.03373075,-89.49682961
-93,20,Good Shepherd Lutheran Church,"5701 Raymond Rd Madison, WI (43.03181519576003, -89.47779995721618)",Fellowship Hall - Front Entrance,TRUE,43.0318152,-89.47779996
-94,20,Good Shepherd Lutheran Church,"5701 Raymond Rd Madison, WI (43.03181519576003, -89.47779995721618)",Fellowship Hall - Front Entrance,TRUE,43.0318152,-89.47779996
-95,20,Huegel Elementary,"2601 Prairie Rd Madison, WI (43.02445874156035, -89.48884363173602)",Gym,TRUE,43.02445874,-89.48884363
-96,20,Heritage Congregational Church,"3102 Prairie Rd Madison, WI (43.019350592433966, -89.49308843171428)",Church Narthex,TRUE,43.01935059,-89.49308843
-97,7,St Mary's Care Center,"3401 Maple Grove Rd Madison, WI (43.010847536110305, -89.4988941474842)",Activities Room,TRUE,43.01084754,-89.49889415
-98,7,Chavez Elementary,"3502 Maple Grove Dr Madison, WI (43.01024945349741, -89.49901496388736)","Room 102-C, Gym in November",TRUE,43.01024945,-89.49901496
-99,7,Meriter McKee Clinic,"3102 Meriter Way Madison, WI (43.072950000242486, -89.38668999974357)",Community Room,TRUE,43.07295,-89.38669
-100,7,Meriter McKee Clinic,"3102 Meriter Way Madison, WI (43.072950000242486, -89.38668999974357)",Community Room,TRUE,43.07295,-89.38669
-101,1,West Police District,"1710 McKenna Blvd Madison, WI (43.033937595749194, -89.4969195435591)",Community Room,TRUE,43.0339376,-89.49691954
-102,1,West Police District,"1710 McKenna Blvd Madison, WI (43.033937595749194, -89.4969195435591)",Community Room,TRUE,43.0339376,-89.49691954
-103,1,Our Redeemer Lutheran Church,"1701 McKenna Blvd Madison, WI (43.03373075168247, -89.49682961135619)",Community Room,TRUE,43.03373075,-89.49682961
-104,1,Madison Ice Arena,"725 Forward Dr Madison, WI (43.04842567364119, -89.4881781334343)",Conference Room,TRUE,43.04842567,-89.48817813
-105,1,Coventry Village,"7707 Brookline Dr Madison, WI (43.04950883036192, -89.51793330096713)",Community Room,TRUE,43.04950883,-89.5179333
-106,1,Blackhawk Church,"9620 Brader Way Madison, WI (43.06139389730373, -89.55404447886025)",Meeting Room on second floor,TRUE,43.0613939,-89.55404448
-107,9,Coventry Village,"7707 Brookline Dr Madison, WI (43.04950883036192, -89.51793330096713)",Community Room,TRUE,43.04950883,-89.5179333
-108,9,Lussier Community Education Center,"55 Gammon Rd Madison, WI (43.0690426470485, -89.50249223958838)",Classroom,TRUE,43.06904265,-89.50249224
-109,9,High Point Church,"7702 Old Sauk Rd Madison, WI (43.07505459892877, -89.51692046549073)","Micah Center - Park behind building, side entrance",TRUE,43.0750546,-89.51692047
-110,9,Attic Angel Association,"640 Junction Road Madison, WI (43.07381353452837, -89.52684898069424)",Community Room,TRUE,43.07381353,-89.52684898
-111,9,The Jefferson,"9401 Old Sauk Rd Madison, WI (43.074739836218555, -89.55049215684454)",Activity Room,TRUE,43.07473984,-89.55049216
-112,18,Blackhawk Middle School,"1402 Wyoming Way Madison, WI (43.146137012124655, -89.37093352047447)",LMC,TRUE,43.14613701,-89.37093352
-113,14,Bridge - Lake Point - Waunona Community Center,"1917 Lake Point Dr Madison, WI (43.04819184991362, -89.34713745957919)",Front Room,TRUE,43.04819185,-89.34713746
-114,1,Our Redeemer Lutheran Church,"1701 McKenna Blvd Madison, WI (43.03373075168247, -89.49682961135619)",Community Room,TRUE,43.03373075,-89.49682961
-115,1,Our Redeemer Lutheran Church,"1701 McKenna Blvd Madison, WI (43.03373075168247, -89.49682961135619)",Community Room,TRUE,43.03373075,-89.49682961
-116,1,Our Redeemer Lutheran Church,"1701 McKenna Blvd Madison, WI (43.03373075168247, -89.49682961135619)",Community Room,TRUE,43.03373075,-89.49682961
-117,19,Madison Ice Arena,"725 Forward Drive Madison, WI (43.04842567364119, -89.4881781334343)", ,TRUE,43.04842567,-89.48817813
diff --git a/profiles/dkan/modules/dkan/dkan_fixtures/modules/dkan_default_content/dkan_default_content.info b/profiles/dkan/modules/dkan/dkan_fixtures/modules/dkan_default_content/dkan_default_content.info
index 7ec0d9c524c..126c07ac19b 100644
--- a/profiles/dkan/modules/dkan/dkan_fixtures/modules/dkan_default_content/dkan_default_content.info
+++ b/profiles/dkan/modules/dkan/dkan_fixtures/modules/dkan_default_content/dkan_default_content.info
@@ -15,4 +15,4 @@ files[] = includes/page.inc
files[] = includes/panelized_node.inc
files[] = includes/resource.inc
files[] = includes/visualization_entity.inc
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_harvest/dkan_harvest.features.field_base.inc b/profiles/dkan/modules/dkan/dkan_harvest/dkan_harvest.features.field_base.inc
index 5c925115696..1982e09939a 100644
--- a/profiles/dkan/modules/dkan/dkan_harvest/dkan_harvest.features.field_base.inc
+++ b/profiles/dkan/modules/dkan/dkan_harvest/dkan_harvest.features.field_base.inc
@@ -143,10 +143,10 @@ function dkan_harvest_field_default_field_bases() {
'settings' => array(
'allow_machine_changes' => 0,
'complete_path_label' => 'Complete path',
- 'machine_description' => 'A URL-safe version of the text. It may only contain lowercase letters, numbers and underscores. Leave blank to re-generate.',
+ 'machine_description' => 'A URL-safe version of the text. It may only contain lowercase letters, numbers and underscores and may not exceed 31 characters. Leave blank to re-generate.',
'machine_label' => 'Machine Name',
- 'max_length' => 255,
- 'replace_pattern' => '(--|<[^<>]+>|[^/a-z0-9-_]|/)+',
+ 'max_length' => 31,
+ 'replace_pattern' => '(-|--|<[^<>]+>|[^/a-z0-9-_]|/)+',
'replace_value' => '_',
'show_complete_path' => 0,
'unique' => 1,
diff --git a/profiles/dkan/modules/dkan/dkan_harvest/dkan_harvest.info b/profiles/dkan/modules/dkan/dkan_harvest/dkan_harvest.info
index d7014cae762..8748aff3a4b 100644
--- a/profiles/dkan/modules/dkan/dkan_harvest/dkan_harvest.info
+++ b/profiles/dkan/modules/dkan/dkan_harvest/dkan_harvest.info
@@ -72,4 +72,4 @@ files[] = includes/HarvestItem.php
files[] = includes/HarvestCache.php
files[] = includes/HarvestMigrateSQLMap.php
files[] = includes/HarvestMigrateSourceList.php
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_harvest/dkan_harvest.migrate.inc b/profiles/dkan/modules/dkan/dkan_harvest/dkan_harvest.migrate.inc
index 340b2b22d33..7cd50a596a5 100644
--- a/profiles/dkan/modules/dkan/dkan_harvest/dkan_harvest.migrate.inc
+++ b/profiles/dkan/modules/dkan/dkan_harvest/dkan_harvest.migrate.inc
@@ -258,8 +258,8 @@ class HarvestMigration extends MigrateDKAN {
// Disable any rules passed in the arguments array
// Most probably in the processImport() method.
- if (module_exists('rules') &&
- !empty(variable_get('dkan_harvest_disable_rules', array()))) {
+ $disable_rules = variable_get('dkan_harvest_disable_rules', array());
+ if (module_exists('rules') && !empty($disable_rules)) {
// Make sure that we only alter the status of already enabled rules.
$rules = db_select('rules_config', 'rc')
->fields('rc', array('name'))
@@ -759,7 +759,8 @@ class HarvestMigration extends MigrateDKAN {
// in a previous harvest migrate.
// Get rows marked with HarvestMigrateSQLMap::STATUS_IGNORED_NO_SOURCE
// and part of the imported datasets imported.
- if (is_array($this->getIdList()) && !empty($this->getIdList())) {
+ $id_list = $this->getIdList();
+ if (is_array($id_list) && !empty($id_list)) {
$query = $this->map->getConnection()->select($this->map->getMapTable(), 'map')
->fields('map')
->condition("needs_update", HarvestMigrateSQLMap::STATUS_IGNORED_NO_SOURCE)
@@ -782,9 +783,10 @@ class HarvestMigration extends MigrateDKAN {
$dataset_emw = entity_metadata_wrapper('node', $dataset_nid);
$related_count = 0;
- // Unpublish attached resources.
+ // Publish attached resources.
if (isset($dataset_emw->field_resources)) {
foreach ($dataset_emw->field_resources->getIterator() as $delta => $resource_emw) {
+ $resource_emw->field_orphan->set(0);
$resource_emw->status->set(1);
$resource_emw->save();
$related_count++;
@@ -799,6 +801,7 @@ class HarvestMigration extends MigrateDKAN {
}
// Publish the dataset.
+ $dataset_emw->field_orphan->set(0);
$dataset_emw->status->set(1);
$dataset_emw->save();
@@ -836,7 +839,8 @@ class HarvestMigration extends MigrateDKAN {
$query = $this->map->getConnection()->select($this->map->getMapTable(), 'map')
->fields('map')
->condition("needs_update", HarvestMigrateSQLMap::STATUS_IGNORED_NO_SOURCE, '<>');
- if (is_array($this->getIdList()) && !empty($this->getIdList())) {
+ $id_list = $this->getIdList();
+ if (is_array($this->getIdList()) && !empty($id_list)) {
foreach ($this->map->getSourceKeyMap() as $key_name) {
$query = $query->condition("map.$key_name", $this->getIdList(), 'NOT IN');
}
@@ -1142,12 +1146,11 @@ class HarvestMigration extends MigrateDKAN {
// page.
if (!is_null($remoteFileInfo->getType())
&& !in_array($remoteFileInfo->getExtension(), $html_extensions)) {
-
// Check if this file extension is allowed for field_link_remote_file.
- $link_field = field_info_instance('node', 'field_link_remote_file', 'resource');
// Reduce everything to lowercase to support files with uppercase
// extensions.
- $file_extensions = array_map('strtolower', explode(' ', $link_field['settings']['file_extensions']));
+ $file_extensions = array_map('strtolower', explode(' ', dkan_allowed_extensions()));
+
// Reject if the extension is not allowed.
if (!in_array($remoteFileInfo->getExtension(), $file_extensions)) {
$message = t('Resource remote url (@url) extension (@extension) not allowed',
diff --git a/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_dashboard/css/dkan_harvest_dashboard.css b/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_dashboard/css/dkan_harvest_dashboard.css
index cd8f420f8c1..0bc3e9d82d2 100644
--- a/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_dashboard/css/dkan_harvest_dashboard.css
+++ b/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_dashboard/css/dkan_harvest_dashboard.css
@@ -15,3 +15,12 @@
.page-admin-dkan-harvest-dashboard a.btn-add-source {
margin: 0px 0px 10px 0px;
}
+#edit-field-harvest-source-issued-value-wrapper .form-item {
+ float: left;
+}
+#edit-field-harvest-source-issued-value-wrapper .form-item .help-block {
+ display: none;
+}
+#edit-field-harvest-source-issued-value-wrapper .views-widget div {
+ display: inline-block;
+}
diff --git a/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_dashboard/dkan_harvest_dashboard.info b/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_dashboard/dkan_harvest_dashboard.info
index de537e879a2..12c4393ca83 100644
--- a/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_dashboard/dkan_harvest_dashboard.info
+++ b/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_dashboard/dkan_harvest_dashboard.info
@@ -23,4 +23,4 @@ features[views_view][] = dkan_harvest_datasets
files[] = views/handlers/views_handler_field_date_harvest_date.inc
files[] = views/handlers/views_handler_field_numeric_harvest_count.inc
files[] = views/handlers/views_handler_field_boolean_harvest_status.inc
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_dashboard/dkan_harvest_dashboard.module b/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_dashboard/dkan_harvest_dashboard.module
index c22e4a6802a..b6be00c67c0 100644
--- a/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_dashboard/dkan_harvest_dashboard.module
+++ b/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_dashboard/dkan_harvest_dashboard.module
@@ -64,7 +64,6 @@ function dkan_harvest_dashboard_form_alter(&$form, $form_state, $form_id) {
// Update the way date fields are rendered.
$date_fields = array(
'field_harvest_source_issued_value',
- 'field_harvest_source_modified_value',
);
$date_placeholders = array(
'min' => t('From'),
diff --git a/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_dashboard/dkan_harvest_dashboard.views_default.inc b/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_dashboard/dkan_harvest_dashboard.views_default.inc
index d57cb1a66a9..d23957b746a 100644
--- a/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_dashboard/dkan_harvest_dashboard.views_default.inc
+++ b/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_dashboard/dkan_harvest_dashboard.views_default.inc
@@ -269,7 +269,7 @@ function dkan_harvest_dashboard_views_default_views() {
),
'rewrite' => array(
'filter_rewrite_values' => 'On|Orphans only
-Off|Non-orphans only',
+ Off|Non-orphans only',
),
),
),
@@ -294,6 +294,10 @@ Off|Non-orphans only',
),
);
$handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['pager']['options']['items_per_page'] = '30';
+ $handler->display->display_options['pager']['options']['offset'] = '0';
+ $handler->display->display_options['pager']['options']['id'] = '0';
+ $handler->display->display_options['pager']['options']['quantity'] = '9';
$handler->display->display_options['style_plugin'] = 'table';
$handler->display->display_options['style_options']['columns'] = array(
'nid' => 'nid',
@@ -354,6 +358,10 @@ Off|Non-orphans only',
'empty_column' => 0,
),
);
+ /* Header: Global: Result summary */
+ $handler->display->display_options['header']['result']['id'] = 'result';
+ $handler->display->display_options['header']['result']['table'] = 'views';
+ $handler->display->display_options['header']['result']['field'] = 'result';
/* No results behavior: Global: Text area */
$handler->display->display_options['empty']['area']['id'] = 'area';
$handler->display->display_options['empty']['area']['table'] = 'views';
@@ -397,8 +405,8 @@ Off|Non-orphans only',
$handler->display->display_options['fields']['og_group_ref']['id'] = 'og_group_ref';
$handler->display->display_options['fields']['og_group_ref']['table'] = 'og_membership';
$handler->display->display_options['fields']['og_group_ref']['field'] = 'og_group_ref';
- $handler->display->display_options['fields']['og_group_ref']['label'] = 'Group';
$handler->display->display_options['fields']['og_group_ref']['settings'] = array(
+ 'bypass_access' => 0,
'link' => 1,
);
$handler->display->display_options['fields']['og_group_ref']['delta_offset'] = '0';
@@ -420,28 +428,16 @@ Off|Non-orphans only',
'multiple_to' => '',
'show_remaining_days' => 0,
);
- /* Field: Content: Harvest Source Modified */
- $handler->display->display_options['fields']['field_harvest_source_modified']['id'] = 'field_harvest_source_modified';
- $handler->display->display_options['fields']['field_harvest_source_modified']['table'] = 'field_data_field_harvest_source_modified';
- $handler->display->display_options['fields']['field_harvest_source_modified']['field'] = 'field_harvest_source_modified';
- $handler->display->display_options['fields']['field_harvest_source_modified']['settings'] = array(
- 'format_type' => 'iso_8601_date',
- 'fromto' => 'both',
- 'multiple_number' => '',
- 'multiple_from' => '',
- 'multiple_to' => '',
- 'show_remaining_days' => 0,
- );
- /* Field: Content: Orphan */
- $handler->display->display_options['fields']['field_orphan']['id'] = 'field_orphan';
- $handler->display->display_options['fields']['field_orphan']['table'] = 'field_data_field_orphan';
- $handler->display->display_options['fields']['field_orphan']['field'] = 'field_orphan';
- $handler->display->display_options['fields']['field_orphan']['type'] = 'list_key';
/* Field: Content: Published */
$handler->display->display_options['fields']['status']['id'] = 'status';
$handler->display->display_options['fields']['status']['table'] = 'node';
$handler->display->display_options['fields']['status']['field'] = 'status';
$handler->display->display_options['fields']['status']['not'] = 0;
+ /* Field: Content: Orphan */
+ $handler->display->display_options['fields']['field_orphan']['id'] = 'field_orphan';
+ $handler->display->display_options['fields']['field_orphan']['table'] = 'field_data_field_orphan';
+ $handler->display->display_options['fields']['field_orphan']['field'] = 'field_orphan';
+ $handler->display->display_options['fields']['field_orphan']['type'] = 'list_key';
/* Sort criterion: Content: Post date */
$handler->display->display_options['sorts']['created']['id'] = 'created';
$handler->display->display_options['sorts']['created']['table'] = 'node';
@@ -486,18 +482,36 @@ Off|Non-orphans only',
6 => 0,
);
$handler->display->display_options['filters']['field_harvest_source_issued_value']['form_type'] = 'date_popup';
- /* Filter criterion: Content: Harvest Source Modified (field_harvest_source_modified) */
- $handler->display->display_options['filters']['field_harvest_source_modified_value']['id'] = 'field_harvest_source_modified_value';
- $handler->display->display_options['filters']['field_harvest_source_modified_value']['table'] = 'field_data_field_harvest_source_modified';
- $handler->display->display_options['filters']['field_harvest_source_modified_value']['field'] = 'field_harvest_source_modified_value';
- $handler->display->display_options['filters']['field_harvest_source_modified_value']['operator'] = 'between';
- $handler->display->display_options['filters']['field_harvest_source_modified_value']['group'] = 1;
- $handler->display->display_options['filters']['field_harvest_source_modified_value']['exposed'] = TRUE;
- $handler->display->display_options['filters']['field_harvest_source_modified_value']['expose']['operator_id'] = 'field_harvest_source_modified_value_op';
- $handler->display->display_options['filters']['field_harvest_source_modified_value']['expose']['label'] = 'Harvest Source Modified';
- $handler->display->display_options['filters']['field_harvest_source_modified_value']['expose']['operator'] = 'field_harvest_source_modified_value_op';
- $handler->display->display_options['filters']['field_harvest_source_modified_value']['expose']['identifier'] = 'field_harvest_source_modified_value';
- $handler->display->display_options['filters']['field_harvest_source_modified_value']['expose']['remember_roles'] = array(
+ /* Filter criterion: Content: Published */
+ $handler->display->display_options['filters']['status']['id'] = 'status';
+ $handler->display->display_options['filters']['status']['table'] = 'node';
+ $handler->display->display_options['filters']['status']['field'] = 'status';
+ $handler->display->display_options['filters']['status']['value'] = 'All';
+ $handler->display->display_options['filters']['status']['group'] = 1;
+ $handler->display->display_options['filters']['status']['exposed'] = TRUE;
+ $handler->display->display_options['filters']['status']['expose']['operator_id'] = '';
+ $handler->display->display_options['filters']['status']['expose']['label'] = 'Published';
+ $handler->display->display_options['filters']['status']['expose']['operator'] = 'status_op';
+ $handler->display->display_options['filters']['status']['expose']['identifier'] = 'status';
+ $handler->display->display_options['filters']['status']['expose']['remember_roles'] = array(
+ 2 => '2',
+ 3 => 0,
+ 1 => 0,
+ 5 => 0,
+ 4 => 0,
+ 6 => 0,
+ );
+ /* Filter criterion: Content: Publisher (og_group_ref) (reference filter) */
+ $handler->display->display_options['filters']['og_group_ref_target_id_entityreference_filter']['id'] = 'og_group_ref_target_id_entityreference_filter';
+ $handler->display->display_options['filters']['og_group_ref_target_id_entityreference_filter']['table'] = 'og_membership';
+ $handler->display->display_options['filters']['og_group_ref_target_id_entityreference_filter']['field'] = 'og_group_ref_target_id_entityreference_filter';
+ $handler->display->display_options['filters']['og_group_ref_target_id_entityreference_filter']['group'] = 1;
+ $handler->display->display_options['filters']['og_group_ref_target_id_entityreference_filter']['exposed'] = TRUE;
+ $handler->display->display_options['filters']['og_group_ref_target_id_entityreference_filter']['expose']['operator_id'] = 'og_group_ref_target_id_entityreference_filter_op';
+ $handler->display->display_options['filters']['og_group_ref_target_id_entityreference_filter']['expose']['label'] = 'Publisher';
+ $handler->display->display_options['filters']['og_group_ref_target_id_entityreference_filter']['expose']['operator'] = 'og_group_ref_target_id_entityreference_filter_op';
+ $handler->display->display_options['filters']['og_group_ref_target_id_entityreference_filter']['expose']['identifier'] = 'og_group_ref_target_id_entityreference_filter';
+ $handler->display->display_options['filters']['og_group_ref_target_id_entityreference_filter']['expose']['remember_roles'] = array(
2 => '2',
3 => 0,
1 => 0,
@@ -505,7 +519,7 @@ Off|Non-orphans only',
4 => 0,
6 => 0,
);
- $handler->display->display_options['filters']['field_harvest_source_modified_value']['form_type'] = 'date_popup';
+ $handler->display->display_options['filters']['og_group_ref_target_id_entityreference_filter']['reference_display'] = 'entity_reference_groups_list:entityreference_1';
/* Filter criterion: Content: Orphan (field_orphan) */
$handler->display->display_options['filters']['field_orphan_value']['id'] = 'field_orphan_value';
$handler->display->display_options['filters']['field_orphan_value']['table'] = 'field_data_field_orphan';
@@ -611,199 +625,6 @@ Off|Non-orphans only',
$handler->display->display_options['defaults']['style_options'] = FALSE;
$handler->display->display_options['defaults']['row_plugin'] = FALSE;
$handler->display->display_options['defaults']['row_options'] = FALSE;
- $handler->display->display_options['defaults']['fields'] = FALSE;
- /* Field: Bulk operations: Content */
- $handler->display->display_options['fields']['views_bulk_operations']['id'] = 'views_bulk_operations';
- $handler->display->display_options['fields']['views_bulk_operations']['table'] = 'views_entity_node';
- $handler->display->display_options['fields']['views_bulk_operations']['field'] = 'views_bulk_operations';
- $handler->display->display_options['fields']['views_bulk_operations']['vbo_settings']['display_type'] = '0';
- $handler->display->display_options['fields']['views_bulk_operations']['vbo_settings']['enable_select_all_pages'] = 1;
- $handler->display->display_options['fields']['views_bulk_operations']['vbo_settings']['row_clickable'] = 1;
- $handler->display->display_options['fields']['views_bulk_operations']['vbo_settings']['force_single'] = 0;
- $handler->display->display_options['fields']['views_bulk_operations']['vbo_settings']['entity_load_capacity'] = '10';
- $handler->display->display_options['fields']['views_bulk_operations']['vbo_operations'] = array(
- 'action::views_bulk_operations_delete_item' => array(
- 'selected' => 1,
- 'postpone_processing' => 0,
- 'skip_confirmation' => 0,
- 'override_label' => 0,
- 'label' => '',
- ),
- 'action::node_unpublish_action' => array(
- 'selected' => 1,
- 'postpone_processing' => 0,
- 'skip_confirmation' => 0,
- 'override_label' => 0,
- 'label' => '',
- ),
- );
- /* Field: Content: Nid */
- $handler->display->display_options['fields']['nid']['id'] = 'nid';
- $handler->display->display_options['fields']['nid']['table'] = 'node';
- $handler->display->display_options['fields']['nid']['field'] = 'nid';
- $handler->display->display_options['fields']['nid']['label'] = '';
- $handler->display->display_options['fields']['nid']['exclude'] = TRUE;
- $handler->display->display_options['fields']['nid']['element_label_colon'] = FALSE;
- /* Field: Content: Publisher */
- $handler->display->display_options['fields']['og_group_ref']['id'] = 'og_group_ref';
- $handler->display->display_options['fields']['og_group_ref']['table'] = 'og_membership';
- $handler->display->display_options['fields']['og_group_ref']['field'] = 'og_group_ref';
- $handler->display->display_options['fields']['og_group_ref']['label'] = 'Group';
- $handler->display->display_options['fields']['og_group_ref']['settings'] = array(
- 'link' => 1,
- );
- $handler->display->display_options['fields']['og_group_ref']['delta_offset'] = '0';
- /* Field: Content: Title */
- $handler->display->display_options['fields']['title']['id'] = 'title';
- $handler->display->display_options['fields']['title']['table'] = 'node';
- $handler->display->display_options['fields']['title']['field'] = 'title';
- $handler->display->display_options['fields']['title']['alter']['word_boundary'] = FALSE;
- $handler->display->display_options['fields']['title']['alter']['ellipsis'] = FALSE;
- /* Field: Content: Harvest Source Issued */
- $handler->display->display_options['fields']['field_harvest_source_issued']['id'] = 'field_harvest_source_issued';
- $handler->display->display_options['fields']['field_harvest_source_issued']['table'] = 'field_data_field_harvest_source_issued';
- $handler->display->display_options['fields']['field_harvest_source_issued']['field'] = 'field_harvest_source_issued';
- $handler->display->display_options['fields']['field_harvest_source_issued']['settings'] = array(
- 'format_type' => 'iso_8601_date',
- 'fromto' => 'both',
- 'multiple_number' => '',
- 'multiple_from' => '',
- 'multiple_to' => '',
- 'show_remaining_days' => 0,
- );
- /* Field: Content: Harvest Source Modified */
- $handler->display->display_options['fields']['field_harvest_source_modified']['id'] = 'field_harvest_source_modified';
- $handler->display->display_options['fields']['field_harvest_source_modified']['table'] = 'field_data_field_harvest_source_modified';
- $handler->display->display_options['fields']['field_harvest_source_modified']['field'] = 'field_harvest_source_modified';
- $handler->display->display_options['fields']['field_harvest_source_modified']['settings'] = array(
- 'format_type' => 'iso_8601_date',
- 'fromto' => 'both',
- 'multiple_number' => '',
- 'multiple_from' => '',
- 'multiple_to' => '',
- 'show_remaining_days' => 0,
- );
- /* Field: Content: Orphan */
- $handler->display->display_options['fields']['field_orphan']['id'] = 'field_orphan';
- $handler->display->display_options['fields']['field_orphan']['table'] = 'field_data_field_orphan';
- $handler->display->display_options['fields']['field_orphan']['field'] = 'field_orphan';
- $handler->display->display_options['fields']['field_orphan']['type'] = 'list_key';
- /* Field: Content: Published */
- $handler->display->display_options['fields']['status']['id'] = 'status';
- $handler->display->display_options['fields']['status']['table'] = 'node';
- $handler->display->display_options['fields']['status']['field'] = 'status';
- $handler->display->display_options['fields']['status']['not'] = 0;
- /* Field: Content: Harvest Source */
- $handler->display->display_options['fields']['field_harvest_source_ref']['id'] = 'field_harvest_source_ref';
- $handler->display->display_options['fields']['field_harvest_source_ref']['table'] = 'field_data_field_harvest_source_ref';
- $handler->display->display_options['fields']['field_harvest_source_ref']['field'] = 'field_harvest_source_ref';
- $handler->display->display_options['fields']['field_harvest_source_ref']['label'] = 'Source';
- $handler->display->display_options['fields']['field_harvest_source_ref']['settings'] = array(
- 'link' => 0,
- );
- $handler->display->display_options['defaults']['filter_groups'] = FALSE;
- $handler->display->display_options['defaults']['filters'] = FALSE;
- /* Filter criterion: Content: Harvest Source Issued (field_harvest_source_issued) */
- $handler->display->display_options['filters']['field_harvest_source_issued_value']['id'] = 'field_harvest_source_issued_value';
- $handler->display->display_options['filters']['field_harvest_source_issued_value']['table'] = 'field_data_field_harvest_source_issued';
- $handler->display->display_options['filters']['field_harvest_source_issued_value']['field'] = 'field_harvest_source_issued_value';
- $handler->display->display_options['filters']['field_harvest_source_issued_value']['operator'] = 'between';
- $handler->display->display_options['filters']['field_harvest_source_issued_value']['group'] = 1;
- $handler->display->display_options['filters']['field_harvest_source_issued_value']['exposed'] = TRUE;
- $handler->display->display_options['filters']['field_harvest_source_issued_value']['expose']['operator_id'] = 'field_harvest_source_issued_value_op';
- $handler->display->display_options['filters']['field_harvest_source_issued_value']['expose']['label'] = 'Harvest Source Issued';
- $handler->display->display_options['filters']['field_harvest_source_issued_value']['expose']['operator'] = 'field_harvest_source_issued_value_op';
- $handler->display->display_options['filters']['field_harvest_source_issued_value']['expose']['identifier'] = 'field_harvest_source_issued_value';
- $handler->display->display_options['filters']['field_harvest_source_issued_value']['expose']['remember_roles'] = array(
- 2 => '2',
- 3 => 0,
- 1 => 0,
- 5 => 0,
- 4 => 0,
- 6 => 0,
- );
- $handler->display->display_options['filters']['field_harvest_source_issued_value']['form_type'] = 'date_popup';
- /* Filter criterion: Content: Harvest Source Modified (field_harvest_source_modified) */
- $handler->display->display_options['filters']['field_harvest_source_modified_value']['id'] = 'field_harvest_source_modified_value';
- $handler->display->display_options['filters']['field_harvest_source_modified_value']['table'] = 'field_data_field_harvest_source_modified';
- $handler->display->display_options['filters']['field_harvest_source_modified_value']['field'] = 'field_harvest_source_modified_value';
- $handler->display->display_options['filters']['field_harvest_source_modified_value']['operator'] = 'between';
- $handler->display->display_options['filters']['field_harvest_source_modified_value']['group'] = 1;
- $handler->display->display_options['filters']['field_harvest_source_modified_value']['exposed'] = TRUE;
- $handler->display->display_options['filters']['field_harvest_source_modified_value']['expose']['operator_id'] = 'field_harvest_source_modified_value_op';
- $handler->display->display_options['filters']['field_harvest_source_modified_value']['expose']['label'] = 'Harvest Source Modified';
- $handler->display->display_options['filters']['field_harvest_source_modified_value']['expose']['operator'] = 'field_harvest_source_modified_value_op';
- $handler->display->display_options['filters']['field_harvest_source_modified_value']['expose']['identifier'] = 'field_harvest_source_modified_value';
- $handler->display->display_options['filters']['field_harvest_source_modified_value']['expose']['remember_roles'] = array(
- 2 => '2',
- 3 => 0,
- 1 => 0,
- 5 => 0,
- 4 => 0,
- 6 => 0,
- );
- $handler->display->display_options['filters']['field_harvest_source_modified_value']['form_type'] = 'date_popup';
- /* Filter criterion: Content: Type */
- $handler->display->display_options['filters']['type']['id'] = 'type';
- $handler->display->display_options['filters']['type']['table'] = 'node';
- $handler->display->display_options['filters']['type']['field'] = 'type';
- $handler->display->display_options['filters']['type']['value'] = array(
- 'dataset' => 'dataset',
- );
- $handler->display->display_options['filters']['type']['group'] = 1;
- /* Filter criterion: Content: Harvest Source (field_harvest_source_ref) */
- $handler->display->display_options['filters']['field_harvest_source_ref_target_id']['id'] = 'field_harvest_source_ref_target_id';
- $handler->display->display_options['filters']['field_harvest_source_ref_target_id']['table'] = 'field_data_field_harvest_source_ref';
- $handler->display->display_options['filters']['field_harvest_source_ref_target_id']['field'] = 'field_harvest_source_ref_target_id';
- $handler->display->display_options['filters']['field_harvest_source_ref_target_id']['operator'] = 'not empty';
- $handler->display->display_options['filters']['field_harvest_source_ref_target_id']['value'] = array(
- 'min' => '',
- 'max' => '',
- 'value' => '',
- );
- $handler->display->display_options['filters']['field_harvest_source_ref_target_id']['group'] = 1;
- /* Filter criterion: Content: Orphan (field_orphan) */
- $handler->display->display_options['filters']['field_orphan_value']['id'] = 'field_orphan_value';
- $handler->display->display_options['filters']['field_orphan_value']['table'] = 'field_data_field_orphan';
- $handler->display->display_options['filters']['field_orphan_value']['field'] = 'field_orphan_value';
- $handler->display->display_options['filters']['field_orphan_value']['value'] = array(
- 'all' => 'all',
- 0 => '0',
- 1 => '1',
- );
- $handler->display->display_options['filters']['field_orphan_value']['group'] = 1;
- $handler->display->display_options['filters']['field_orphan_value']['exposed'] = TRUE;
- $handler->display->display_options['filters']['field_orphan_value']['expose']['operator_id'] = 'field_orphan_value_op';
- $handler->display->display_options['filters']['field_orphan_value']['expose']['label'] = 'Orphan Status';
- $handler->display->display_options['filters']['field_orphan_value']['expose']['operator'] = 'field_orphan_value_op';
- $handler->display->display_options['filters']['field_orphan_value']['expose']['identifier'] = 'field_orphan_value';
- $handler->display->display_options['filters']['field_orphan_value']['expose']['remember_roles'] = array(
- 2 => '2',
- 3 => 0,
- 1 => 0,
- 5 => 0,
- 4 => 0,
- 6 => 0,
- );
- $handler->display->display_options['filters']['field_orphan_value']['expose']['reduce'] = TRUE;
- /* Filter criterion: Content: Harvest Source (field_harvest_source_ref) */
- $handler->display->display_options['filters']['field_harvest_source_ref_target_id_1']['id'] = 'field_harvest_source_ref_target_id_1';
- $handler->display->display_options['filters']['field_harvest_source_ref_target_id_1']['table'] = 'field_data_field_harvest_source_ref';
- $handler->display->display_options['filters']['field_harvest_source_ref_target_id_1']['field'] = 'field_harvest_source_ref_target_id';
- $handler->display->display_options['filters']['field_harvest_source_ref_target_id_1']['group'] = 1;
- $handler->display->display_options['filters']['field_harvest_source_ref_target_id_1']['exposed'] = TRUE;
- $handler->display->display_options['filters']['field_harvest_source_ref_target_id_1']['expose']['operator_id'] = 'field_harvest_source_ref_target_id_1_op';
- $handler->display->display_options['filters']['field_harvest_source_ref_target_id_1']['expose']['label'] = 'Source';
- $handler->display->display_options['filters']['field_harvest_source_ref_target_id_1']['expose']['operator'] = 'field_harvest_source_ref_target_id_1_op';
- $handler->display->display_options['filters']['field_harvest_source_ref_target_id_1']['expose']['identifier'] = 'field_harvest_source_ref_target_id_1';
- $handler->display->display_options['filters']['field_harvest_source_ref_target_id_1']['expose']['remember_roles'] = array(
- 2 => '2',
- 3 => 0,
- 1 => 0,
- 5 => 0,
- 4 => 0,
- 6 => 0,
- );
/* Display: Source Page */
$handler = $view->new_display('page', 'Source Page', 'harvest_datasets_source_page');
diff --git a/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_datajson/dkan_harvest_datajson.info b/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_datajson/dkan_harvest_datajson.info
index 3a7e88df4bb..5633344fdb4 100644
--- a/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_datajson/dkan_harvest_datajson.info
+++ b/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_datajson/dkan_harvest_datajson.info
@@ -5,4 +5,4 @@ core = 7.x
dependencies[] = dkan_harvest
files[] = dkan_harvest_datajson.migrate.inc
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_datajson/dkan_harvest_datajson.migrate.inc b/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_datajson/dkan_harvest_datajson.migrate.inc
index 04bca05cf14..6b9111792c0 100644
--- a/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_datajson/dkan_harvest_datajson.migrate.inc
+++ b/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_datajson/dkan_harvest_datajson.migrate.inc
@@ -83,15 +83,45 @@ class DatajsonHarvestMigration extends HarvestMigration {
public function prepareRow($row) {
parent::prepareRow($row);
+ // The modified field is required. https://project-open-data.cio.gov/v1.1/schema/#modified.
+ // Check the value when harvesting, it should be a valid ISO 8601 Date.
+ if(isset($row->modified)) {
+ $d = dkan_dataset_validate_date($row->modified) ? true : false;
+ if ($d) {
+ // Valid date, no change.
+ }
+ elseif (substr($row->modified, 0, 2) == 'R/') {
+ try {
+ $v = substr($row->modified, 2);
+ $valid = new DateInterval($v);
+ }
+ catch (Exception $e) {
+ $message = t(
+ 'Modified value unknown or bad format (@value)',
+ array(
+ '@value' => $v,
+ ));
+ $this->saveMessage($message);
+ }
+ }
+ else {
+ $message = t(
+ 'The modified value is not a valid ISO 8601 Date (@value)',
+ array(
+ '@value' => $row->modified,
+ ));
+ $this->saveMessage($message);
+ }
+ }
+
// The issued field is not required. When missing, use the modified field to
// have consistent dataset display.
// https://project-open-data.cio.gov/v1.1/schema#issued
- if (!isset($row->issued)) {
- $row->issued = $row->modified;
- }
-
- if (property_exists($row, 'accrualPeriodicity')) {
- $row->accrualPeriodicity = dkan_dataset_content_types_iso2frequency($row->accrualPeriodicity);
+ if (!isset($row->issued) && isset($row->modified)) {
+ $d = dkan_dataset_validate_date($row->modified) ? true : false;
+ if ($d) {
+ $row->issued = $row->modified;
+ }
}
// Process contact name and email.
diff --git a/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_datajson/dkan_harvest_datajson.module b/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_datajson/dkan_harvest_datajson.module
index ace36b1e0be..1c9fb36395c 100644
--- a/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_datajson/dkan_harvest_datajson.module
+++ b/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_datajson/dkan_harvest_datajson.module
@@ -377,9 +377,11 @@ function dkan_harvest_datajson_prepare_item_id($identifier) {
if (filter_var($identifier, FILTER_VALIDATE_URL)) {
$identifier = parse_url($identifier, PHP_URL_PATH);
$frag = explode('/', $identifier);
- // Does not produce "Strict warning: Only variables should be passed by
- // reference" like end(explode('/', $identifier));.
- $identifier = $frag[count($frag) - 1];
+
+ // Return the last non empty URL Path element.
+ $frag = array_filter($frag);
+ $identifier = end($frag);
}
+
return $identifier;
}
diff --git a/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_test/dkan_harvest_test.info b/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_test/dkan_harvest_test.info
index 1ae9285e8e3..148c27c6153 100644
--- a/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_test/dkan_harvest_test.info
+++ b/profiles/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_test/dkan_harvest_test.info
@@ -3,4 +3,4 @@ description = Test module for dkan_harvest
core = 7.x
dependencies[] = 'dkan_harvest'
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_ipe/dkan_ipe.info b/profiles/dkan/modules/dkan/dkan_ipe/dkan_ipe.info
index 16c4a5e51f5..4b51071b842 100644
--- a/profiles/dkan/modules/dkan/dkan_ipe/dkan_ipe.info
+++ b/profiles/dkan/modules/dkan/dkan_ipe/dkan_ipe.info
@@ -8,4 +8,4 @@ dependencies[] = panels
dependencies[] = strongarm
features[features_api][] = api:2
project path = profiles/dkan/modules/dkan
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_linkchecker/dkan_linkchecker.info b/profiles/dkan/modules/dkan/dkan_linkchecker/dkan_linkchecker.info
index a99b3546cb6..9e56652099f 100644
--- a/profiles/dkan/modules/dkan/dkan_linkchecker/dkan_linkchecker.info
+++ b/profiles/dkan/modules/dkan/dkan_linkchecker/dkan_linkchecker.info
@@ -22,4 +22,4 @@ features[variable][] = linkchecker_scan_node_resource
features[views_view][] = dkan_linkchecker_reports
features_exclude[dependencies][ctools] = ctools
project path = profiles/dkan/modules/dkan
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_migrate_base/README.md b/profiles/dkan/modules/dkan/dkan_migrate_base/README.md
index c57e21e18b2..2905a9ee5a3 100644
--- a/profiles/dkan/modules/dkan/dkan_migrate_base/README.md
+++ b/profiles/dkan/modules/dkan/dkan_migrate_base/README.md
@@ -56,7 +56,7 @@ After the initial time the migration is run it will check each dataset and resou
```
##### POD 1.1
-```
+```
'title' => 'title',
'body' => 'description',
'og_group_ref' => 'group_id',
@@ -117,7 +117,7 @@ After the initial time the migration is run it will check each dataset and resou
##### Notes
**publisher POD:** is being mapped to a DKAN group. If that group doesn't exists then is created.
-**field_additional_info:** is a DKAN field that holds json keys that can't be mapped to any other DKAN field.
+**field_additional_info:** is a DKAN field that holds json keys that can't be mapped to any other DKAN field.
**open_data_federal_extras:** is a module that can be enabled to add fields that are present in the POD spec but aren't present in DKAN out-of-the-box. By enabling this module you are adding these fields to your DKAN entities (datasets and resources).
### Resources
@@ -134,8 +134,8 @@ We are accepting issues in the dkan issue thread only -> https://github.com/GetD
If you can, please cross reference commits in this repo to the corresponding issue in the dkan issue thread. You can do that easily adding this text:
```
-NuCivic/dkan#issue_id
-```
+GetDKAN/dkan#issue_id
+```
to any commit message or comment replacing **issue_id** with the corresponding issue id.
diff --git a/profiles/dkan/modules/dkan/dkan_migrate_base/dkan_migrate_base.info b/profiles/dkan/modules/dkan/dkan_migrate_base/dkan_migrate_base.info
index 8a56127697e..813856d40c6 100644
--- a/profiles/dkan/modules/dkan/dkan_migrate_base/dkan_migrate_base.info
+++ b/profiles/dkan/modules/dkan/dkan_migrate_base/dkan_migrate_base.info
@@ -11,4 +11,4 @@ files[] = dkan_migrate_base_group.inc
files[] = dkan_migrate_base_dataset.inc
files[] = dkan_migrate_base_resource.inc
files[] = dkan_migrate_base_data_json.inc
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_migrate_base/modules/dkan_migrate_base_example/dkan_migrate_base_example.info b/profiles/dkan/modules/dkan/dkan_migrate_base/modules/dkan_migrate_base_example/dkan_migrate_base_example.info
index 21fc1bcde2b..f17de05b671 100644
--- a/profiles/dkan/modules/dkan/dkan_migrate_base/modules/dkan_migrate_base_example/dkan_migrate_base_example.info
+++ b/profiles/dkan/modules/dkan/dkan_migrate_base/modules/dkan_migrate_base_example/dkan_migrate_base_example.info
@@ -4,4 +4,4 @@ package = "DKAN"
core = 7.x
dependencies[] = dkan_migrate_base
dependencies[] = open_data_federal_extras
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_permissions/dkan_permissions.features.roles_permissions.inc b/profiles/dkan/modules/dkan/dkan_permissions/dkan_permissions.features.roles_permissions.inc
index 7f7a55070f8..ef5fc69c7e2 100644
--- a/profiles/dkan/modules/dkan/dkan_permissions/dkan_permissions.features.roles_permissions.inc
+++ b/profiles/dkan/modules/dkan/dkan_permissions/dkan_permissions.features.roles_permissions.inc
@@ -138,7 +138,6 @@ function dkan_permissions_default_roles_permissions() {
'create fieldable text' => TRUE,
'create fieldable video' => TRUE,
'create files' => TRUE,
- 'create page content' => TRUE,
'create resource content' => TRUE,
'create url aliases' => TRUE,
'delete any audio files' => TRUE,
@@ -147,7 +146,6 @@ function dkan_permissions_default_roles_permissions() {
'delete any dkan_data_story content' => TRUE,
'delete any document files' => TRUE,
'delete any image files' => TRUE,
- 'delete any page content' => TRUE,
'delete any resource content' => TRUE,
'delete any video files' => TRUE,
'delete fieldable basic_file' => TRUE,
@@ -191,7 +189,6 @@ function dkan_permissions_default_roles_permissions() {
'edit any dkan_data_story content' => TRUE,
'edit any document files' => TRUE,
'edit any image files' => TRUE,
- 'edit any page content' => TRUE,
'edit any resource content' => TRUE,
'edit any video files' => TRUE,
'edit fieldable basic_file' => TRUE,
diff --git a/profiles/dkan/modules/dkan/dkan_permissions/dkan_permissions.info b/profiles/dkan/modules/dkan/dkan_permissions/dkan_permissions.info
index ed02ef2d800..d9b8c52608e 100644
--- a/profiles/dkan/modules/dkan/dkan_permissions/dkan_permissions.info
+++ b/profiles/dkan/modules/dkan/dkan_permissions/dkan_permissions.info
@@ -11,4 +11,4 @@ features[roles_permissions][] = editor
features[roles_permissions][] = site manager
features_exclude[dependencies][features] = features
project path = profiles/dkan/modules/dkan
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_plugins/dkan_plugins.info b/profiles/dkan/modules/dkan/dkan_plugins/dkan_plugins.info
index 43aeedc8799..9e6ec3299ee 100644
--- a/profiles/dkan/modules/dkan/dkan_plugins/dkan_plugins.info
+++ b/profiles/dkan/modules/dkan/dkan_plugins/dkan_plugins.info
@@ -6,4 +6,4 @@ dependencies[] = ctools
dependencies[] = panels
project path = profiles/dkan/modules/dkan
scripts[] = js/colorPicker.behavior.js
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_sitewide/dkan_sitewide.info b/profiles/dkan/modules/dkan/dkan_sitewide/dkan_sitewide.info
index 103410138ca..9950e0e1bad 100644
--- a/profiles/dkan/modules/dkan/dkan_sitewide/dkan_sitewide.info
+++ b/profiles/dkan/modules/dkan/dkan_sitewide/dkan_sitewide.info
@@ -56,4 +56,4 @@ features[variable][] = user_pictures
features[views_view][] = dkan_administration_files
features[views_view][] = dkan_administration_nodes
features[views_view][] = popular_tags
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_context/dkan_sitewide_context.info b/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_context/dkan_sitewide_context.info
index 15ba6ac2639..6b0a565c21d 100644
--- a/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_context/dkan_sitewide_context.info
+++ b/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_context/dkan_sitewide_context.info
@@ -19,4 +19,4 @@ features[context][] = sitewide
features[ctools][] = context:context:3
features[ctools][] = strongarm:strongarm:1
features[features_api][] = api:2
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_demo_front/dkan_sitewide_demo_front.info b/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_demo_front/dkan_sitewide_demo_front.info
index 00df5df3f21..ab6148c4bac 100644
--- a/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_demo_front/dkan_sitewide_demo_front.info
+++ b/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_demo_front/dkan_sitewide_demo_front.info
@@ -9,4 +9,4 @@ features[ctools][] = context:context:3
features[ctools][] = page_manager:pages_default:1
features[features_api][] = api:2
fetures[context][] = front
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_menu/dkan_sitewide_menu.info b/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_menu/dkan_sitewide_menu.info
index d1b18da87b0..8aeba71bda9 100644
--- a/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_menu/dkan_sitewide_menu.info
+++ b/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_menu/dkan_sitewide_menu.info
@@ -44,4 +44,4 @@ features[menu_links][] = menu-command-center-menu_site-information:admin/config/
features[menu_links][] = menu-command-center-menu_taxonomy:admin/structure/taxonomy
features[menu_links][] = menu-command-center-menu_visualization:admin/structure/entity-type/visualization/ve_chart/add
features[menu_links][] = menu-command-center-menu_visualizations:admin/structure/entity-type/visualization
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_panelizer/dkan_sitewide_panelizer.info b/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_panelizer/dkan_sitewide_panelizer.info
index b37d9748773..e58338dfa6f 100644
--- a/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_panelizer/dkan_sitewide_panelizer.info
+++ b/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_panelizer/dkan_sitewide_panelizer.info
@@ -17,4 +17,4 @@ features[variable][] = panelizer_node:page_allowed_layouts_default
features[variable][] = panelizer_node:page_allowed_types
features[variable][] = panelizer_node:page_allowed_types_default
features[variable][] = panelizer_node:page_default
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_panels/dkan_sitewide_panels.info b/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_panels/dkan_sitewide_panels.info
index 9cb610515cb..d5b85b9272d 100644
--- a/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_panels/dkan_sitewide_panels.info
+++ b/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_panels/dkan_sitewide_panels.info
@@ -51,4 +51,4 @@ features[views_view][] = dkan_datasets_filtered
features[views_view][] = dkan_groups
features[views_view][] = entity_reference_groups_list
features[views_view][] = list_of_users_groups
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_roles_perms/dkan_sitewide_roles_perms.info b/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_roles_perms/dkan_sitewide_roles_perms.info
index 0248dcbcefe..348321b1ec8 100644
--- a/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_roles_perms/dkan_sitewide_roles_perms.info
+++ b/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_roles_perms/dkan_sitewide_roles_perms.info
@@ -118,4 +118,4 @@ features[user_permission][] = view revisions
features[user_permission][] = view the administration theme
features[user_role][] = administrator
features[user_role][] = editor
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_search_db/dkan_sitewide_search_db.info b/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_search_db/dkan_sitewide_search_db.info
index 42fad322154..76a6c4d8203 100644
--- a/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_search_db/dkan_sitewide_search_db.info
+++ b/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_search_db/dkan_sitewide_search_db.info
@@ -35,4 +35,4 @@ features[search_api_index][] = datasets
features[search_api_server][] = datasets
features[variable][] = facetapi_pretty_paths_searcher_search_api@datasets
features[variable][] = facetapi_pretty_paths_searcher_search_api@datasets_options
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_user/dkan_sitewide_user.info b/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_user/dkan_sitewide_user.info
index 21cccbb8637..7c9780e5d8e 100644
--- a/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_user/dkan_sitewide_user.info
+++ b/profiles/dkan/modules/dkan/dkan_sitewide/modules/dkan_sitewide_user/dkan_sitewide_user.info
@@ -21,4 +21,4 @@ features[field_group][] = group_user_tabs|user|user|default
features[field_instance][] = user-user-field_about
features[views_view][] = user_profile_fields
features[views_view][] = user_profile_search
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_sitewide/modules/facet_icons/facet_icons.info b/profiles/dkan/modules/dkan/dkan_sitewide/modules/facet_icons/facet_icons.info
index 80f4b7d3fa6..58e8a76bb7a 100644
--- a/profiles/dkan/modules/dkan/dkan_sitewide/modules/facet_icons/facet_icons.info
+++ b/profiles/dkan/modules/dkan/dkan_sitewide/modules/facet_icons/facet_icons.info
@@ -6,4 +6,4 @@ dependencies[] = facetapi
dependencies[] = panels_style_collapsible
files[] = widget_term_icons.inc
files[] = widget_content_type_icons.inc
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_topics/dkan_topics.info b/profiles/dkan/modules/dkan/dkan_topics/dkan_topics.info
index 591ad2fcf70..e0a7d5c0398 100755
--- a/profiles/dkan/modules/dkan/dkan_topics/dkan_topics.info
+++ b/profiles/dkan/modules/dkan/dkan_topics/dkan_topics.info
@@ -57,4 +57,4 @@ features_exclude[dependencies][dkan_dataset_groups] = dkan_dataset_groups
features_exclude[dependencies][dkan_topics] = dkan_topics
no autodetect = 1
project path = profiles/dkan/modules/dkan
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_topics/modules/dkan_default_topics/dkan_default_topics.info b/profiles/dkan/modules/dkan/dkan_topics/modules/dkan_default_topics/dkan_default_topics.info
index 9d80c71e038..06a1c2eac23 100755
--- a/profiles/dkan/modules/dkan/dkan_topics/modules/dkan_default_topics/dkan_default_topics.info
+++ b/profiles/dkan/modules/dkan/dkan_topics/modules/dkan_default_topics/dkan_default_topics.info
@@ -5,4 +5,4 @@ package = DKAN Features
dependencies[] = dkan_topics
dependencies[] = taxonomy_fixtures
dependencies[] = taxonomy
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_workflow/dkan_workflow.features.workbench_email.inc b/profiles/dkan/modules/dkan/dkan_workflow/dkan_workflow.features.workbench_email.inc
index 2e2a12ab1fa..e1ed6a7a6b8 100644
--- a/profiles/dkan/modules/dkan/dkan_workflow/dkan_workflow.features.workbench_email.inc
+++ b/profiles/dkan/modules/dkan/dkan_workflow/dkan_workflow.features.workbench_email.inc
@@ -13,217 +13,251 @@ function dkan_workflow_workbench_email_export() {
'draft:needs_review:original author' => array(
'from_name' => 'draft',
'to_name' => 'needs_review',
- 'subject' => '[site:name] Status of [node:content-type] “[node:title]”',
+ 'author' => 1,
+ 'automatic' => 1,
+ 'subject' => 'Workbench moderation update ([site:name]) - Status of [node:content-type] "[node:title]" has changed.',
'message' => '[user:name],
-A moderator has changed [node:content-type], [node:title], from [workbench-email:email-transition].
+The status of the [node:content-type] "[node:title]" has been changed from [workbench-email:email-transition].
-For more details, view [node:content-type] at [node:url].
+For more details, view [node:content-type] at [node:url].
-To modify the moderation state of a Dataset , click “Edit” and click the ‘Moderate’ button at the top of the screen.
-To modify the moderation state of a Resource, Blog, or a page scroll to the bottom of the page to find the “Publishing Options” menu tab.
-You can access your full Workbench dashboard by logging in and clicking “My Workbench” on the left-hand side of the blue site navigation bar.
+There are three moderation states for content in DKAN: Draft, Needs Review and Published. To change the moderation state of content that you\'ve created or have permission to edit, click "Edit" and click the "Moderate" button located at the top of the screen.
-Content you have created, whether published or unpublished, is listed under “My Content.”
+To access your Workbench dashboard, log in and click "My Workbench" from the administration menu at the top.
-“My Drafts” displays all pieces of content that you have created and saved as drafts, and “Needs Review” displays all of the content that you’ve created that needs review by an editor, group manager or site administrator.
-',
- 'author' => 1,
- 'automatic' => 1,
+For assistance or support, please contact a Site Manager or an administrator from your organization.',
'role' => 'original author',
),
'draft:needs_review:Workflow Moderator' => array(
'from_name' => 'draft',
'to_name' => 'needs_review',
- 'subject' => '[site:name] Status of [node:content-type] “[node:title]”',
+ 'author' => 0,
+ 'automatic' => 1,
+ 'subject' => 'Workbench moderation update ([site:name]) - Status of [node:content-type] "[node:title]" has changed.',
'message' => '[user:name],
-A moderator has changed [node:content-type], [node:title], from [workbench-email:email-transition].
+The status of the [node:content-type] "[node:title]" has been changed from [workbench-email:email-transition].
-For more details, view [node:content-type] at [node:url].
+For more details, view [node:content-type] at [node:url].
-To modify the moderation state of a Dataset , click “Edit” and click the ‘Moderate’ button at the top of the screen.
-To modify the moderation state of a Resource, Blog, or a page scroll to the bottom of the page to find the “Publishing Options” menu tab.
-You can access your full Workbench dashboard by logging in and clicking “My Workbench” on the left-hand side of the blue site navigation bar.
+There are three moderation states for content in DKAN: Draft, Needs Review and Published. Items that are not reviewed within 72 hours are filed under "Stale Reviews."
-Content you have created, whether published or unpublished, is listed under “My Content.”
+To change the moderation state of content that you\'ve created or have permission to edit, click "Edit" and click the "Moderate" button located at the top of the screen.
-“My Drafts” displays all pieces of content that you have created and saved as drafts, and “Needs Review” displays all of the content that you’ve created that needs review by an editor, group manager or site administrator.
-',
- 'author' => 0,
- 'automatic' => 1,
+To access your Workbench dashboard, log in and click "My Workbench" from the administration menu at the top.
+
+For assistance or support, please contact a Site Manager or an administrator from your organization.',
'role' => 'Workflow Moderator',
),
'draft:needs_review:Workflow Supervisor' => array(
'from_name' => 'draft',
'to_name' => 'needs_review',
- 'subject' => '[site:name] Status of [node:content-type] “[node:title]”',
+ 'author' => 0,
+ 'automatic' => 1,
+ 'subject' => 'Workbench moderation update ([site:name]) - Status of [node:content-type] "[node:title]" has changed.',
'message' => '[user:name],
-A moderator has changed [node:content-type], [node:title], moderation state from [workbench-email:email-transition].
+The status of the [node:content-type] "[node:title]" has been changed from [workbench-email:email-transition].
-For more details, view your [node:content-type] at [node:url].
+For more details, view [node:content-type] at [node:url].
-To modify the moderation state of a Dataset , click “Edit” and click the ‘Moderate’ button at the top of the screen.
-To modify the moderation state of a Resource, Blog, or a page scroll to the bottom of the page to find the “Publishing Options” menu tab.
-You can access your full Workbench dashboard by logging in and clicking “My Workbench” on the left-hand side of the blue site navigation bar.
+There are three moderation states for content in DKAN: Draft, Needs Review and Published. Items that are not reviewed within 72 hours are filed under "Stale Reviews."
-Content you have created, whether published or unpublished, is listed under “My Content.”
+To change the moderation state of content that you\'ve created or have permission to edit, click "Edit" and click the "Moderate" button located at the top of the screen.
-“My Drafts” displays all pieces of content that you have created and saved as drafts, and “Needs Review” displays all of the content that you’ve created that needs review by an editor, group manager or site administrator.
-',
- 'author' => 0,
- 'automatic' => 1,
+To access your Workbench dashboard, log in and click "My Workbench" from the administration menu at the top.
+
+For assistance or support, please contact a Site Manager or an administrator from your organization.',
'role' => 'Workflow Supervisor',
),
'needs_review:draft:original author' => array(
'from_name' => 'needs_review',
'to_name' => 'draft',
- 'subject' => '[site:name] Status of [node:content-type] “[node:title]”',
+ 'author' => 1,
+ 'automatic' => 1,
+ 'subject' => 'Workbench moderation update ([site:name]) - Status of [node:content-type] "[node:title]" has changed.',
'message' => '[user:name],
-A moderator has changed [node:content-type], [node:title], moderation state from [workbench-email:email-transition].
+The status of the [node:content-type] "[node:title]" has been changed from [workbench-email:email-transition].
-For more details, view your [node:content-type] at [node:url].
+For more details, view [node:content-type] at [node:url].
-To modify the moderation state of a Dataset , click “Edit” and click the ‘Moderate’ button at the top of the screen.
-To modify the moderation state of a Resource, Blog, or a page scroll to the bottom of the page to find the “Publishing Options” menu tab.
-You can access your full Workbench dashboard by logging in and clicking “My Workbench” on the left-hand side of the blue site navigation bar.
+There are three moderation states for content in DKAN: Draft, Needs Review and Published. If your draft has been pushed from Needs Review back to Draft, you may edit and submit it once more for review.
-Content you have created, whether published or unpublished, is listed under “My Content.”
+To change the moderation state of content that you\'ve created or have permission to edit, click "Edit" and click the "Moderate" button located at the top of the screen.
-“My Drafts” displays all pieces of content that you have created and saved as drafts, and “Needs Review” displays all of the content that you’ve created that needs review by an editor, group manager or site administrator.
-',
- 'author' => 1,
- 'automatic' => 1,
+To access your Workbench dashboard, log in and click "My Workbench" from the administration menu at the top.
+
+For assistance or support, please contact a Site Manager or an administrator from your organization.',
'role' => 'original author',
),
'needs_review:draft:Workflow Moderator' => array(
'from_name' => 'needs_review',
'to_name' => 'draft',
- 'subject' => '[site:name] Status of [node:content-type] “[node:title]”',
+ 'author' => 0,
+ 'automatic' => 1,
+ 'subject' => 'Workbench moderation update ([site:name]) - Status of [node:content-type] "[node:title]" has changed.',
'message' => '[user:name],
-A moderator has changed [node:content-type], [node:title], moderation state from [workbench-email:email-transition].
+The status of the [node:content-type] "[node:title]" has been changed from [workbench-email:email-transition].
-For more details, view your [node:content-type] at [node:url].
+For more details, view [node:content-type] at [node:url].
-To modify the moderation state of a Dataset , click “Edit” and click the ‘Moderate’ button at the top of the screen.
-To modify the moderation state of a Resource, Blog, or a page scroll to the bottom of the page to find the “Publishing Options” menu tab.
-You can access your full Workbench dashboard by logging in and clicking “My Workbench” on the left-hand side of the blue site navigation bar.
+There are three moderation states for content in DKAN: Draft, Needs Review and Published. Content that has been pushed from Needs Review back to Draft may be edited and submitted once more for review.
-Content you have created, whether published or unpublished, is listed under “My Content.”
+To change the moderation state of content that you\'ve created or have permission to edit, click "Edit" and click the "Moderate" button located at the top of the screen.
-“My Drafts” displays all pieces of content that you have created and saved as drafts, and “Needs Review” displays all of the content that you’ve created that needs review by an editor, group manager or site administrator.
-',
- 'author' => 0,
- 'automatic' => 1,
+To access your Workbench dashboard, log in and click "My Workbench" from the administration menu at the top.
+
+For assistance or support, please contact a Site Manager or an administrator from your organization.',
'role' => 'Workflow Moderator',
),
'needs_review:draft:Workflow Supervisor' => array(
'from_name' => 'needs_review',
'to_name' => 'draft',
- 'subject' => '[site:name] Status of [node:content-type] “[node:title]”',
+ 'author' => 0,
+ 'automatic' => 1,
+ 'subject' => 'Workbench moderation update ([site:name]) - Status of [node:content-type] "[node:title]" has changed.',
'message' => '[user:name],
-A moderator has changed [node:content-type], [node:title], moderation state from [workbench-email:email-transition].
+The status of the [node:content-type] "[node:title]" has been changed from [workbench-email:email-transition].
-For more details, view your [node:content-type] at [node:url].
+For more details, view [node:content-type] at [node:url].
-To modify the moderation state of a Dataset , click “Edit” and click the ‘Moderate’ button at the top of the screen.
-To modify the moderation state of a Resource, Blog, or a page scroll to the bottom of the page to find the “Publishing Options” menu tab.
-You can access your full Workbench dashboard by logging in and clicking “My Workbench” on the left-hand side of the blue site navigation bar.
+There are three moderation states for content in DKAN: Draft, Needs Review and Published. Content that has been pushed from Needs Review back to Draft may be edited and submitted once more for review.
-Content you have created, whether published or unpublished, is listed under “My Content.”
+To change the moderation state of content that you\'ve created or have permission to edit, click "Edit" and click the "Moderate" button located at the top of the screen.
-“My Drafts” displays all pieces of content that you have created and saved as drafts, and “Needs Review” displays all of the content that you’ve created that needs review by an editor, group manager or site administrator.
-',
- 'author' => 0,
- 'automatic' => 1,
+To access your Workbench dashboard, log in and click "My Workbench" from the administration menu at the topr.
+
+For assistance or support, please contact a Site Manager or an administrator from your organization.',
'role' => 'Workflow Supervisor',
),
'needs_review:published:original author' => array(
'from_name' => 'needs_review',
'to_name' => 'published',
- 'subject' => '[site:name] Status of [node:content-type] “[node:title]”',
+ 'author' => 1,
+ 'automatic' => 1,
+ 'subject' => 'Workbench moderation update ([site:name]) - [node:content-type] "[node:title]" is now published',
'message' => '[user:name],
-A moderator has changed [node:content-type], [node:title], moderation state from [workbench-email:email-transition].
+The status of the [node:content-type] "[node:title]" has been changed from [workbench-email:email-transition].
-For more details, view your [node:content-type] at [node:url].
+For more details, view [node:content-type] at [node:url].
-To modify the moderation state of a Dataset , click “Edit” and click the ‘Moderate’ button at the top of the screen.
-To modify the moderation state of a Resource, Blog, or a page scroll to the bottom of the page to find the “Publishing Options” menu tab.
-You can access your full Workbench dashboard by logging in and clicking “My Workbench” on the left-hand side of the blue site navigation bar.
+There are three moderation states for content in DKAN: Draft, Needs Review and Published. If you\'ve published content but wish to remove it from the site, you can change its status back to "Draft" or consult a Moderator or Site Manager.
-Content you have created, whether published or unpublished, is listed under “My Content.”
+To change the moderation state of content that you\'ve created or have permission to edit, click "Edit" and click the "Moderate" button located at the top of the screen.
-“My Drafts” displays all pieces of content that you have created and saved as drafts, and “Needs Review” displays all of the content that you’ve created that needs review by an editor, group manager or site administrator.
-',
- 'author' => 1,
- 'automatic' => 1,
+To access your Workbench dashboard, log in and click "My Workbench" from the administration menu at the top.
+
+For assistance or support, please contact a Site Manager or an administrator from your organization.',
'role' => 'original author',
),
'needs_review:published:Workflow Moderator' => array(
'from_name' => 'needs_review',
'to_name' => 'published',
- 'subject' => '[site:name] Status of [node:content-type] “[node:title]”',
+ 'author' => 0,
+ 'automatic' => 1,
+ 'subject' => 'Workbench moderation update ([site:name]) - [node:content-type] "[node:title]" is now published',
'message' => '[user:name],
-A moderator has changed [node:content-type], [node:title], moderation state from [workbench-email:email-transition].
+The status of the [node:content-type] "[node:title]" has been changed from [workbench-email:email-transition].
-For more details, view your [node:content-type] at [node:url].
+For more details, view [node:content-type] at [node:url].
-To modify the moderation state of a Dataset , click “Edit” and click the ‘Moderate’ button at the top of the screen.
-To modify the moderation state of a Resource, Blog, or a page scroll to the bottom of the page to find the “Publishing Options” menu tab.
-You can access your full Workbench dashboard by logging in and clicking “My Workbench” on the left-hand side of the blue site navigation bar.
+There are three moderation states for content in DKAN: Draft, Needs Review and Published. If you\'ve published content but wish to remove it from the site, you will need to un-publish it.
-Content you have created, whether published or unpublished, is listed under “My Content.”
+To change the moderation state of content that you\'ve created or have permission to edit, click "Edit" and click the "Moderate" button located at the top of the screen. Under the moderation options is an "Unpublish" link that will let you set the state back to "Draft".
-“My Drafts” displays all pieces of content that you have created and saved as drafts, and “Needs Review” displays all of the content that you’ve created that needs review by an editor, group manager or site administrator.
-',
- 'author' => 0,
- 'automatic' => 1,
+To access your Workbench dashboard, log in and click "My Workbench" from the administration menu at the top.
+
+For assistance or support, please contact a Site Manager or an administrator from your organization.',
'role' => 'Workflow Moderator',
),
'needs_review:published:Workflow Supervisor' => array(
'from_name' => 'needs_review',
'to_name' => 'published',
- 'subject' => '[site:name] Status of [node:content-type] “[node:title]”',
+ 'author' => 0,
+ 'automatic' => 1,
+ 'subject' => 'Workbench moderation update ([site:name]) - [node:content-type] "[node:title]" is now published',
'message' => '[user:name],
-A moderator has changed [node:content-type], [node:title], moderation state from [workbench-email:email-transition].
+The status of the [node:content-type] "[node:title]" has been changed from [workbench-email:email-transition].
-For more details, view your [node:content-type] at [node:url].
+For more details, view [node:content-type] at [node:url].
-To modify the moderation state of a Dataset , click “Edit” and click the ‘Moderate’ button at the top of the screen.
-To modify the moderation state of a Resource, Blog, or a page scroll to the bottom of the page to find the “Publishing Options” menu tab.
-You can access your full Workbench dashboard by logging in and clicking “My Workbench” on the left-hand side of the blue site navigation bar.
+There are three moderation states for content in DKAN: Draft, Needs Review and Published. If you\'ve published content but wish to remove it from the site, you will need to un-publish it.
-Content you have created, whether published or unpublished, is listed under “My Content.”
+To change the moderation state of content that you\'ve created or have permission to edit, click "Edit" and click the "Moderate" button located at the top of the screen. Under the moderation options is an "Unpublish" link that will let you set the state back to "Draft".
-“My Drafts” displays all pieces of content that you have created and saved as drafts, and “Needs Review” displays all of the content that you’ve created that needs review by an editor, group manager or site administrator.
-',
- 'author' => 0,
- 'automatic' => 1,
+To access your Workbench dashboard, log in and click "My Workbench" from the administration menu at the top.
+
+For assistance or support, please contact a Site Manager or an administrator from your organization.',
'role' => 'Workflow Supervisor',
),
+ 'published:needs_review:original author' => array(
+ 'from_name' => 'published',
+ 'to_name' => 'needs_review',
+ 'author' => 1,
+ 'automatic' => 1,
+ 'subject' => 'Workbench moderation update ([site:name]) - Status of [node:content-type] "[node:title]" has changed.',
+ 'message' => '[user:name],
+
+The status of the [node:content-type] "[node:title]" has been changed from [workbench-email:email-transition].
+
+For more details, view [node:content-type] at [node:url].
+
+There are three moderation states for content in DKAN: Draft, Needs Review and Published.
+
+To change the moderation state of content that you\'ve created or have permission to edit, click "Edit" and click the "Moderate" button located at the top of the screen.
+
+To access your Workbench dashboard, log in and click "My Workbench" from the administration menu at the top.
+
+For assistance or support, please contact a Site Manager or an administrator from your organization.',
+ 'role' => 'original author',
+ ),
'published:needs_review:Workflow Moderator' => array(
'from_name' => 'published',
'to_name' => 'needs_review',
- 'subject' => NULL,
- 'message' => NULL,
'author' => 0,
'automatic' => 1,
+ 'subject' => 'Workbench moderation update ([site:name]) - Status of [node:content-type] "[node:title]" has changed.',
+ 'message' => '[user:name],
+
+The status of the [node:content-type] "[node:title]" has been changed from [workbench-email:email-transition].
+
+For more details, view [node:content-type] at [node:url].
+
+There are three moderation states for content in DKAN: Draft, Needs Review and Published.
+
+To change the moderation state of content that you\'ve created or have permission to edit, click "Edit" and click the "Moderate" button located at the top of the screen.
+
+To access your Workbench dashboard, log in and click "My Workbench" from the administration menu at the top.
+
+For assistance or support, please contact a Site Manager or an administrator from your organization.',
'role' => 'Workflow Moderator',
),
'published:needs_review:Workflow Supervisor' => array(
'from_name' => 'published',
'to_name' => 'needs_review',
- 'subject' => NULL,
- 'message' => NULL,
'author' => 0,
'automatic' => 1,
+ 'subject' => 'Workbench moderation update ([site:name]) - Status of [node:content-type] "[node:title]" has changed.',
+ 'message' => '[user:name],
+
+The status of the [node:content-type] "[node:title]" has been changed from [workbench-email:email-transition].
+
+For more details, view [node:content-type] at [node:url].
+
+There are three moderation states for content in DKAN: Draft, Needs Review and Published.
+
+To change the moderation state of content that you\'ve created or have permission to edit, click "Edit" and click the "Moderate" button located at the top of the screen.
+
+To access your Workbench dashboard, log in and click "My Workbench" from the administration menu at the top.
+
+For assistance or support, please contact a Site Manager or an administrator from your organization.',
'role' => 'Workflow Supervisor',
),
);
diff --git a/profiles/dkan/modules/dkan/dkan_workflow/dkan_workflow.info b/profiles/dkan/modules/dkan/dkan_workflow/dkan_workflow.info
index 711c06d21b8..e9fd04cfeb8 100644
--- a/profiles/dkan/modules/dkan/dkan_workflow/dkan_workflow.info
+++ b/profiles/dkan/modules/dkan/dkan_workflow/dkan_workflow.info
@@ -33,6 +33,7 @@ features[workbench_email][] = needs_review:draft::workflow supervisor
features[workbench_email][] = needs_review:published::original author
features[workbench_email][] = needs_review:published::workflow moderator
features[workbench_email][] = needs_review:published::workflow supervisor
+features[workbench_email][] = published:needs_review::original author
features[workbench_email][] = published:needs_review::workflow moderator
features[workbench_email][] = published:needs_review::workflow supervisor
features[workbench_moderation_states][] = draft
@@ -44,4 +45,4 @@ features[workbench_moderation_transitions][] = needs_review:published
features[workbench_moderation_transitions][] = published:needs_review
features_exclude[dependencies][ctools] = ctools
features_exclude[dependencies][dkan_dataset_content_types] = dkan_dataset_content_types
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_workflow/dkan_workflow.module b/profiles/dkan/modules/dkan/dkan_workflow/dkan_workflow.module
index eda2beda8d3..d8a0dad73a6 100644
--- a/profiles/dkan/modules/dkan/dkan_workflow/dkan_workflow.module
+++ b/profiles/dkan/modules/dkan/dkan_workflow/dkan_workflow.module
@@ -2,7 +2,7 @@
/**
* @file
- * Code for the NuCivic workflow feature.
+ * Code for the DKAN workflow feature.
*/
include_once 'dkan_workflow.features.inc';
@@ -102,6 +102,17 @@ function dkan_workflow_node_grants($user, $op) {
return $grants;
}
+/**
+ * Implements hook_workbench_moderation_acces_alter().
+ */
+function dkan_workflow_workbench_moderation_access_alter(&$access, $op, $node) {
+ global $user;
+ // Allow anon users to see revision history.
+ if ($op == 'view history' && $user->uid == 0) {
+ $access = 1;
+ }
+}
+
/**
* Implements hook_node_access_records().
*/
@@ -110,14 +121,17 @@ function dkan_workflow_node_access_records($node) {
// It's published, default handling is okay.
return;
}
- $grants[] = array(
- 'realm' => 'dkan_workflow',
- 'gid' => $node->uid,
- 'grant_view' => 1,
- 'grant_update' => 0,
- 'grant_delete' => 0,
- 'priority' => 0,
- );
+ // Ensure anon user doesn't have access.
+ if ($node->uid != 0) {
+ $grants[] = array(
+ 'realm' => 'dkan_workflow',
+ 'gid' => $node->uid,
+ 'grant_view' => 1,
+ 'grant_update' => 0,
+ 'grant_delete' => 0,
+ 'priority' => 0,
+ );
+ }
return !empty($grants) ? $grants : array();
}
diff --git a/profiles/dkan/modules/dkan/dkan_workflow/modules/dkan_workflow_permissions/dkan_workflow_permissions.info b/profiles/dkan/modules/dkan/dkan_workflow/modules/dkan_workflow_permissions/dkan_workflow_permissions.info
index 3fa9eac127d..4642ea16d78 100644
--- a/profiles/dkan/modules/dkan/dkan_workflow/modules/dkan_workflow_permissions/dkan_workflow_permissions.info
+++ b/profiles/dkan/modules/dkan/dkan_workflow/modules/dkan_workflow_permissions/dkan_workflow_permissions.info
@@ -9,4 +9,4 @@ features[roles_permissions][] = Workflow Contributor
features[roles_permissions][] = Workflow Moderator
features[roles_permissions][] = Workflow Supervisor
project path = profiles/dkan/modules/dkan/dkan_workflow/modules
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/dkan_workflow/modules/views_dkan_workflow_tree/views_dkan_workflow_tree.info b/profiles/dkan/modules/dkan/dkan_workflow/modules/views_dkan_workflow_tree/views_dkan_workflow_tree.info
index 8d36ae7b590..3de78358a34 100644
--- a/profiles/dkan/modules/dkan/dkan_workflow/modules/views_dkan_workflow_tree/views_dkan_workflow_tree.info
+++ b/profiles/dkan/modules/dkan/dkan_workflow/modules/views_dkan_workflow_tree/views_dkan_workflow_tree.info
@@ -6,4 +6,4 @@ dependencies[] = views
dependencies[] = workbench_moderation
files[] = ViewsDkanWorkflowTreePluginStyle.inc
stylesheets[all][] = views_dkan_workflow_tree.css
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/open_data_federal_extras/open_data_federal_extras.info b/profiles/dkan/modules/dkan/open_data_federal_extras/open_data_federal_extras.info
index bbd5f2f8982..e5bb660d91e 100644
--- a/profiles/dkan/modules/dkan/open_data_federal_extras/open_data_federal_extras.info
+++ b/profiles/dkan/modules/dkan/open_data_federal_extras/open_data_federal_extras.info
@@ -27,4 +27,4 @@ features[field_instance][] = node-dataset-field_odfe_data_quality
features[field_instance][] = node-dataset-field_odfe_investment_uii
features[field_instance][] = node-dataset-field_odfe_program_code
features[field_instance][] = node-dataset-field_odfe_system_of_records
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/modules/dkan/open_data_schema_map_dkan/open_data_schema_map_dkan.features.inc b/profiles/dkan/modules/dkan/open_data_schema_map_dkan/open_data_schema_map_dkan.features.inc
index 8767129ffca..59608cf1fee 100644
--- a/profiles/dkan/modules/dkan/open_data_schema_map_dkan/open_data_schema_map_dkan.features.inc
+++ b/profiles/dkan/modules/dkan/open_data_schema_map_dkan/open_data_schema_map_dkan.features.inc
@@ -127,7 +127,7 @@ function open_data_schema_map_dkan_open_data_schema_apis_defaults() {
'type' => '',
),
'url' => array(
- 'value' => '[node:field-resources:Nth:field-link-api:url] || [node:field-resources:Nth:field_link_remote_file] || [node:field-resources:Nth:field-upload:url]',
+ 'value' => '[node:field-resources:Nth:field-link-api:url] || [node:field-resources:Nth:field_link_remote_file:url] || [node:field-resources:Nth:field-upload:url]',
'type' => '',
),
'description' => array(
@@ -1321,7 +1321,7 @@ function open_data_schema_map_dkan_open_data_schema_apis_defaults() {
'type' => '',
),
'accessURL' => array(
- 'value' => '[node:field-resources:Nth:field-link-api:url] || [node:field-resources:Nth:field_link_remote_file] || [node:field-resources:Nth:field-upload:url]',
+ 'value' => '[node:field-resources:Nth:field-link-api:url] || [node:field-resources:Nth:field_link_remote_file:url] || [node:field-resources:Nth:field-upload:url]',
'type' => '',
),
'format' => array(
@@ -1362,7 +1362,7 @@ function open_data_schema_map_dkan_open_data_schema_apis_defaults() {
'type' => 'string',
),
'modified' => array(
- 'value' => '[node:field-harvest-source-modified:custom:Y-m-d] || [node:changed:custom:Y-m-d]',
+ 'value' => '[node:field-harvest-source-modified] || [node:changed:custom:Y-m-d]',
'type' => 'string',
),
'PrimaryITInvestmentUII' => array(
@@ -1486,7 +1486,7 @@ function open_data_schema_map_dkan_open_data_schema_apis_defaults() {
'type' => '',
),
'downloadURL' => array(
- 'value' => '[node:field-resources:Nth:field_link_remote_file] || [node:field-resources:Nth:field-upload:url]',
+ 'value' => '[node:field-resources:Nth:field_link_remote_file:url] || [node:field-resources:Nth:field-upload:url]',
'type' => '',
),
'mediaType' => array(
@@ -1535,7 +1535,7 @@ function open_data_schema_map_dkan_open_data_schema_apis_defaults() {
'type' => 'string',
),
'modified' => array(
- 'value' => '[node:field-harvest-source-modified:custom:Y-m-d] || [node:changed:custom:Y-m-d]',
+ 'value' => '[node:field-harvest-source-modified] || [node:changed:custom:Y-m-d]',
'type' => 'string',
),
'primaryITInvestmentUII' => array(
@@ -1772,7 +1772,7 @@ function open_data_schema_map_dkan_open_data_schema_apis_defaults() {
'type' => '',
),
'dcat:downloadURL' => array(
- 'value' => '[node:field-resources:Nth:field_link_remote_file] || [node:field-resources:Nth:field-upload:url]',
+ 'value' => '[node:field-resources:Nth:field_link_remote_file:url] || [node:field-resources:Nth:field-upload:url]',
'type' => '',
),
'dcat:mediaType' => array(
@@ -1972,7 +1972,7 @@ function open_data_schema_map_dkan_open_data_schema_apis_defaults() {
'type' => '',
),
'dcat:downloadURL' => array(
- 'value' => '[node:field-resources:Nth:field_link_remote_file] || [node:field-resources:Nth:field-upload:url]',
+ 'value' => '[node:field-resources:Nth:field_link_remote_file:url] || [node:field-resources:Nth:field-upload:url]',
'type' => '',
),
'dcat:mediaType' => array(
@@ -2167,7 +2167,7 @@ function open_data_schema_map_dkan_open_data_schema_apis_defaults() {
'type' => '',
),
'dcat:downloadURL' => array(
- 'value' => '[node:field-resources:Nth:field_link_remote_file] || [node:field-resources:Nth:field-upload:url]',
+ 'value' => '[node:field-resources:Nth:field_link_remote_file:url] || [node:field-resources:Nth:field-upload:url]',
'type' => '',
),
'dcat:mediaType' => array(
diff --git a/profiles/dkan/modules/dkan/open_data_schema_map_dkan/open_data_schema_map_dkan.info b/profiles/dkan/modules/dkan/open_data_schema_map_dkan/open_data_schema_map_dkan.info
index 291125e780d..8d63de71a09 100644
--- a/profiles/dkan/modules/dkan/open_data_schema_map_dkan/open_data_schema_map_dkan.info
+++ b/profiles/dkan/modules/dkan/open_data_schema_map_dkan/open_data_schema_map_dkan.info
@@ -21,4 +21,4 @@ features[open_data_schema_apis][] = data_json_1_1
features[open_data_schema_apis][] = dcat_ap_v1_1_dataset
features[open_data_schema_apis][] = dcat_v1_1
features[open_data_schema_apis][] = dcat_v1_1_json
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/test/dkanextension/src/Drupal/DKANExtension/Context/DatasetContext.php b/profiles/dkan/test/dkanextension/src/Drupal/DKANExtension/Context/DatasetContext.php
index fd5a4b21070..7cb1a366dab 100644
--- a/profiles/dkan/test/dkanextension/src/Drupal/DKANExtension/Context/DatasetContext.php
+++ b/profiles/dkan/test/dkanextension/src/Drupal/DKANExtension/Context/DatasetContext.php
@@ -241,8 +241,11 @@ public function iShouldSeeAllPublishedSearchContent(){
$results = array();
foreach ($indexes as $index) {
$query = new SearchApiQuery($index);
+ $filter = $query->createFilter();
+ $filter->condition('type', 'dataset', '=');
$result = $query->condition('status', '1')
+ ->filter($filter)
->execute();
$results[] = $result;
}
diff --git a/profiles/dkan/test/features/dataset.author.feature b/profiles/dkan/test/features/dataset.author.feature
index 62142591e8a..21c18b78a9a 100644
--- a/profiles/dkan/test/features/dataset.author.feature
+++ b/profiles/dkan/test/features/dataset.author.feature
@@ -87,7 +87,7 @@ Feature: Dataset Features
And I click "Log out"
When I am on "Dataset 01" page
And I click "Creative Commons Attribution"
- Then I should see "The Creative Commons Attribution license allows re-distribution and re-use of a licensed work"
+ Then I should see "Attribution — You must give appropriate credit, provide a link to the license"
@dataset_author_5 @fixme @noworkflow
# TODO: Needs definition. How can a data contributor unpublish content?
diff --git a/profiles/dkan/test/features/datastore.feature b/profiles/dkan/test/features/datastore.feature
index 4eb34ed2bf53b85b013053d59f8c14a3bb6490d7..511f2f752b5184e0c8d4edecd0f3bc4e8cf342ef 100644
GIT binary patch
delta 69
zcmeyWIYDB>5q48$1&5;4;{4L0KQ>AIaFx~a(6Tv_sl9xAgYzfjcfA?;{w?1Qf6M?@1xlGbYKv{$rr%3|(@Dy4GX
zJypbtc_{OoWR;UurCBM$^3u`FnzAqb2>aj2>|t)rvdS;a#5CLX9(6v!TpXUR9nl3vi63Zs%{(
zYgW3U2OjnI@oWI0l$D$VJYLu8yp|QK#F{*e`dbg_{8fGlUTjF^8sBR{>EslTJD)$bc?>qW-1ZXWjenGfR-$|OjXiq10J$qsmOFW{WJyLp|X&f(G@rhgf%Oe
z%H;g#F|^$_+rWIwa*DijKIB+Da5p4Nu0BkB3sb5Z6`h$W(&>M4CF6POiB4!Wl|RXY
z<;;B_nfrnp``C{YDlwrj^EnE$u1GS^hDy2Ceh~~4Dcy)bc)a0pu(e$k3Yi;R
zjOuqx{8n_YgXq2J22dBsHFU60t}k^
zim$FXPbyPAmIi$u*FHy$E|!zrEn>^v33DOuk~%6;KzowX#1x%SF(5T0cUmDkJ%c8{
zZLSY*HhxHs$@ult^
zVj~gTi6J!{=9j91^GvSz>VnV2W4fS!6~L$PL0AV~xXl6A>Erei!L0!x)iz!-c9NzK
zD{citzP{8?hBO}Hm!;jA=rUz6?QZ!39*f2y$YhnI901ziZivvueHWjf0b1(s4_k-#
zv|DtQc4RaCxm3M8ij;@`iMv(M6W3i`0Hcm=beV|;D9uw-j@Yq{YP7pe5=c0pUa_)QA(5}X_ALud#^xSNXbcBMuI
zp=Khu6?5&GlBM@Di_ImsVI;W6I=-8{x!)pY?+a>fXxreTBwZ+0an2SxFap9KI*A6?Zr*^NR8+O@t*^#lCSc@eElG3b$fBZ@gqh0B?P7E>O#MHGkbgCC)c=Ea9t~p^Oh=HhpIt-#(+LpN0U#4VdLluKIAQc
zI|J6$37VSBKIF=>WHK3`%
zk+$~)EUrB)oYni*C#?SttJTukjj6Br3E)=UlZnQC+5qUlpeA|QfkxHJs=PXIwk2O|
zb1e6I5u9b
zH}h8ch7&fxy%j}xb-}NPn15zI4+Jld3f^$qBNWi2=j42&N>oxc2~-k__#}N`O|K`f
z`Qlt>1KE74sNQzMU^+icFxtncwq-?t&H-)#ggc)9=9{(ytgAP
zorouuemr{=#xhxEkV|zuuTYb(oLUFPj1GauGfyGqImgOIh7(fc9Da}wJH|$gNOU<+
zUJv0PHzL(B{bZfSFoi%)7h&c@o|9Ag`-mX{5M4*!Zd^r$TCQ`+Yc6o1XA%AIqNh}B
zJ%_aDGv7LBm07_;ag7d4eCvT6^4WtHPPp-A#&f;y5yZWA7gCwG8RKk1;Dd91?qAPP
z;~#5q>!oR(*DEwG)?}rB=s0jzNE%#Y5;K@kDO_3-rsG
zVCrO0$aLes?-dzz%3i^ZvF1TrZVS>>Jov_L+8T@v
"
And I am on the "Harvest Dashboard Datasets" page
- And I fill in "edit-field-harvest-source-modified-value-min-datepicker-popup-0" with "Friday, January 1, 1999"
- And I fill in "edit-field-harvest-source-modified-value-max-datepicker-popup-0" with "Friday, January 1, 1999"
+ And I fill in "edit-status" with "0"
And I press "Apply"
Then I wait for "No harvested datasets were found"
- Then I fill in "edit-field-harvest-source-modified-value-min-datepicker-popup-0" with "Friday, January 1, 1999"
- And I fill in "edit-field-harvest-source-modified-value-max-datepicker-popup-0" with "Friday, December 31, 2100"
+ Then I fill in "edit-status" with "1"
And I press "Apply"
Then I wait for "3" seconds
And I should see a table with a class name "views-table"
diff --git a/profiles/dkan/test/features/page.editor.feature b/profiles/dkan/test/features/page.sm.feature
similarity index 91%
rename from profiles/dkan/test/features/page.editor.feature
rename to profiles/dkan/test/features/page.sm.feature
index 54fd638c1ff..a33f7fd1e32 100644
--- a/profiles/dkan/test/features/page.editor.feature
+++ b/profiles/dkan/test/features/page.sm.feature
@@ -10,7 +10,7 @@ Feature: Page
@api @javascript
Scenario: Add new page content as Editor
- Given I am logged in as a user with the "editor" role
+ Given I am logged in as a user with the "site manager" role
And I am on the "Add Page" page
# When I hover over the admin menu item "Add content"
# And I click "Page"
diff --git a/profiles/dkan/test/features/resource.admin.feature b/profiles/dkan/test/features/resource.admin.feature
index bdbf605b035..5798ef9a7c3 100644
--- a/profiles/dkan/test/features/resource.admin.feature
+++ b/profiles/dkan/test/features/resource.admin.feature
@@ -10,12 +10,12 @@ Feature: Resource
Given users:
| name | mail | roles |
| John | john@example.com | site manager |
- | Badmin | admin@example.com | site manager |
| Gabriel | gabriel@example.com | content creator |
| Jaz | jaz@example.com | editor |
| Katie | katie@example.com | content creator |
| Martin | martin@example.com | editor |
| Celeste | celeste@example.com | editor |
+ | Badmin | admin@example.com | site manager |
Given groups:
| title | author | published |
| Group 01 | Badmin | Yes |
@@ -44,7 +44,7 @@ Feature: Resource
| Resource 04 | Group 01 | Dataset 01 | Katie | No | Yes |
| Resource 05 | Group 01 | Dataset 02 | Celeste | Yes | Yes |
- @noworkflow
+ @resource_admin_1 @noworkflow
Scenario: Edit any resource
Given I am logged in as "John"
And I am on "Resource 02" page
@@ -55,7 +55,7 @@ Feature: Resource
When I am on "Content" page
Then I should see "Resource 02 edited"
- @noworkflow
+ @resource_admin_2 @noworkflow
Scenario: Publish any resource
Given I am logged in as "John"
And I am on "Resource 04" page
@@ -66,7 +66,7 @@ Feature: Resource
And I press "Save"
Then I should see "Resource Resource 04 has been updated"
- @noworkflow
+ @resource_admin_3 @noworkflow
Scenario: Delete any resource
Given I am logged in as "John"
And I am on "Resource 02" page
@@ -75,66 +75,37 @@ Feature: Resource
And I press "Delete"
Then I should see "Resource 02 has been deleted"
- @noworkflow
+ @resource_admin_4 @noworkflow @javascript
Scenario: Manage Datastore of any resource
Given I am logged in as "John"
And I am on "Resource 01" page
- When I click "Manage Datastore"
- Then I should see "There is nothing to manage! You need to upload or link to a file in order to use the datastore."
-
- @noworkflow @datastore @javascript
- Scenario: Import items on datastore of any resource
- Given I am logged in as "John"
- And I am on "Resource 02" page
And I click "Edit"
And I click "Remote file"
And I fill in "edit-field-link-remote-file-und-0-filefield-dkan-remotefile-url" with "https://s3.amazonaws.com/dkan-default-content-files/files/datastore-simple.csv"
And I press "Save"
When I click "Manage Datastore"
- And I wait for "Import"
- And I press "Import"
- And I wait for "Delete Items"
- Then "Resource 02" should have datastore records
-
- @noworkflow @datastore @javascript
- Scenario: Delete items on datastore of any resource
- # Backgorund steps to add a file to a resource
- Given I am logged in as "John"
- And I am on "Resource 04" page
- And I click "Edit"
- And I click "Remote file"
- And I fill in "edit-field-link-remote-file-und-0-filefield-dkan-remotefile-url" with "https://s3.amazonaws.com/dkan-default-content-files/files/datastore-simple1.csv"
- And I press "Save"
- And I am on "Resource 04" page
- When I click "Manage Datastore"
- And I press "Import"
- And I wait for "Delete Items"
- Then "Resource 04" should have datastore records
- And I click "Delete items"
- And I press "Delete"
- And I wait for "items have been deleted"
- Then "Resource 04" should have no datastore records
+ Then I should see "Datastore"
- @noworkflow @datastore @javascript
- Scenario: Drop datastore of any resource
- # Backgorund steps to add a file to a resource
+ @resource_admin_5 @noworkflow @datastore @javascript
+ Scenario: Import items on datastore of any resource and drop
Given I am logged in as "John"
- And I am on "Resource 04" page
+ And I am on "Resource 02" page
And I click "Edit"
And I click "Remote file"
- And I fill in "edit-field-link-remote-file-und-0-filefield-dkan-remotefile-url" with "https://s3.amazonaws.com/dkan-default-content-files/files/datastore-simple2.csv"
+ And I fill in "edit-field-link-remote-file-und-0-filefield-dkan-remotefile-url" with "https://s3.amazonaws.com/dkan-default-content-files/files/datastore-simple.csv"
And I press "Save"
- And I am on "Resource 04" page
When I click "Manage Datastore"
- And I press "Import"
- And I wait for "Delete Items"
- Then "Resource 04" should have datastore records
- When I click "Drop Datastore"
+ Then I should see "Status"
+ When I press "Import"
+ And I wait for "Done"
+ And I press "Drop"
And I press "Drop"
- Then I should see "Datastore dropped!"
- And "Resource 04" should have no datastore records
+ Then I should see "Records Imported"
+ And I should see "0"
+ And I should see "Data Importing"
+ And I should see "Ready"
- @noworkflow
+ @resource_admin_6 @noworkflow
Scenario: Add revision to any resource
Given I am logged in as "John"
And I am on "Resource 02" page
diff --git a/profiles/dkan/test/features/resource.author.feature b/profiles/dkan/test/features/resource.author.feature
index 53f68773428..c6597634abf 100644
--- a/profiles/dkan/test/features/resource.author.feature
+++ b/profiles/dkan/test/features/resource.author.feature
@@ -13,7 +13,7 @@ Feature: Resource
| Katie | katie@example.com | content creator |
| Celeste | celeste@example.com | editor |
- @resource_author_01 @noworkflow
+ @resource_author @resource_author_01 @noworkflow
Scenario: Create resource
Given I am logged in as "Katie"
And I am on the "Content" page
@@ -25,7 +25,7 @@ Feature: Resource
And I press "Save"
Then I should see "Resource Resource 06 has been created"
- @resource_author_02 @noworkflow
+ @resource_author @resource_author_02 @noworkflow
Scenario: See warning if full url not given when using the api/url option.
Given I am logged in as "Katie"
And I am on the "Content" page
@@ -35,7 +35,7 @@ Feature: Resource
And I press "Save"
Then I should see "Please enter a full url"
- @resource_author_03 @datastore @noworkflow @javascript
+ @resource_author @resource_author_03 @datastore @noworkflow @javascript
Scenario: Create resource with too many sources.
Given I am logged in as "Katie"
And I am on the "Content" page
@@ -49,7 +49,7 @@ Feature: Resource
Then I should see "Remote file is populated - only one resource type can be used at a time"
And I should see "API or Website URL is populated - only one resource type can be used at a time"
- @resource_author_04 @noworkflow @javascript
+ @resource_author @resource_author_04 @noworkflow @javascript
Scenario: Edit own resource as content creator
Given resources:
| title | author | published | description |
@@ -63,7 +63,7 @@ Feature: Resource
When I am on "User" page
Then I should see "Resource 01 edited"
- @resource_author_05 @noworkflow
+ @resource_author @resource_author_05 @noworkflow
Scenario: Delete own resource
Given resources:
| title | format | author | published | description |
@@ -75,7 +75,7 @@ Feature: Resource
And I press "Delete"
Then I should see "Resource 01 has been deleted"
- @resource_author_06 @dkanBug @noworkflow
+ @resource_author @resource_author_06 @dkanBug @noworkflow
Scenario: Change dataset on resource
Given groups:
| title | author | published |
@@ -101,7 +101,7 @@ Feature: Resource
Then I should see "Dataset 02" in the "dataset title" region
And I should see "Resource 01" in the "dataset resource list" region
- @resource_author_07 @noworkflow
+ @resource_author @resource_author_07 @noworkflow
Scenario: Add a resource with no datasets to a dataset with no resource
Given groups:
| title | author | published |
@@ -127,7 +127,7 @@ Feature: Resource
Then I should see "Dataset 01" in the "dataset title" region
And I should see "Resource 01" in the "dataset resource list" region
- @resource_author_08 @noworkflow
+ @resource_author @resource_author_08 @noworkflow
Scenario: Remove a resource with only one dataset from the dataset
Given groups:
| title | author | published |
@@ -151,7 +151,7 @@ Feature: Resource
And I should see "Groups were updated on 1 resource(s)"
And I should not see the link "Back to dataset"
- @resource_author_09 @noworkflow
+ @resource_author @resource_author_09 @noworkflow
Scenario: Add a resource with no group to a dataset with group
Given groups:
| title | author | published |
@@ -174,7 +174,7 @@ Feature: Resource
Then I should see "Resource 01 has been updated"
And I should see "Groups were updated on 1 resource(s)"
- @resource_author_10 @noworkflow
+ @resource_author @resource_author_10 @noworkflow
Scenario: Remove a resource from a dataset with group
Given groups:
| title | author | published |
@@ -199,7 +199,7 @@ Feature: Resource
When I am on "Dataset 01" page
Then I should not see "Resource 01" in the "dataset resource list" region
- @resource_author_11 @noworkflow
+ @resource_author @resource_author_11 @noworkflow
Scenario: Add a resource to multiple datasets with groups
Given groups:
| title | author | published |
@@ -225,7 +225,7 @@ Feature: Resource
Then I should see "Resource 01 has been updated"
And I should see "Groups were updated on 1 resource(s)"
- @resource_author_12 @noworkflow
+ @resource_author @resource_author_12 @noworkflow
Scenario: Remove one dataset with group from resource with multiple datasets
Given groups:
| title | author | published |
@@ -255,7 +255,7 @@ Feature: Resource
Then I should see "Resource 01 has been updated"
And I should see "Groups were updated on 1 resource(s)"
- @resource_author_13 @noworkflow
+ @resource_author @resource_author_13 @noworkflow
Scenario: Remove all datasets with groups from resource
Given groups:
| title | author | published |
@@ -287,82 +287,25 @@ Feature: Resource
Then I should see "Resource 01 has been updated"
And I should see "Groups were updated on 1 resource(s)"
- @resource_author_14 @dkanBug @noworkflow
- Scenario: Manage datastore of own resource
+ @resource_author @resource_author_15 @datastore @noworkflow @javascript
+ Scenario: Import items on datastore of own resource and drop
Given resources:
- | title | author | published | description |
- | Resource 01 | Celeste | Yes | No |
- Given I am logged in as "Celeste"
- And I am on "Resource 01" page
- When I click "Edit"
- And I click "Manage Datastore"
- Then I should see "There is nothing to manage! You need to upload or link to a file in order to use the datastore."
-
- @resource_author_15 @datastore @noworkflow @javascript @fixme
- Scenario: Import items on datastore of own resource
- Given resources:
- | title | author | published | description |
- | Resource 01 | Celeste | Yes | No |
- Given I am logged in as "Celeste"
- And I am on "Resource 01" page
- And I click "Edit"
- And I click "Remote file"
- And I fill in "edit-field-link-remote-file-und-0-filefield-dkan-remotefile-url" with "https://s3.amazonaws.com/dkan-default-content-files/files/datastore-simple4.csv"
- And I press "Save"
- And I am on "Resource 01" page
- When I click "Manage Datastore"
- And I press "Import"
- And I wait for "Delete items"
- Then I should see "Last import"
- And I wait for "imported items total"
-
- @resource_author_16 @datastore @noworkflow @javascript @fixme
- Scenario: Delete items on datastore of own resource
- Given resources:
- | title | author | published | description |
- | Resource 01 | Celeste | Yes | No |
- Given I am logged in as "John"
- And I am on "Resource 01" page
- And I click "Edit"
- And I click "Remote file"
- And I fill in "edit-field-link-remote-file-und-0-filefield-dkan-remotefile-url" with "https://s3.amazonaws.com/dkan-default-content-files/files/datastore-simple5.csv"
- And I press "Save"
- Given I am logged in as "Celeste"
- And I am on "Resource 01" page
- When I click "Manage Datastore"
- And I press "Import"
- And I wait for "Delete Items"
- And I click "Delete items"
- And I press "Delete"
- Then I wait for "items have been deleted"
- # This test is not really sufficient, but we are going to consolidate the
- # "drop" and "delete" datastore functions and do other refactoring, so will
- # revisit then.
-
- @resource_author_17 @datastore @noworkflow @javascript @fixme
- Scenario: Drop datastore of own resource
- Given resources:
- | title | author | published | description |
- | Resource 01 | Celeste | Yes | No |
- Given I am logged in as "John"
- And I am on "Resource 01" page
- And I click "Edit"
- And I click "Remote file"
- And I fill in "edit-field-link-remote-file-und-0-filefield-dkan-remotefile-url" with "https://s3.amazonaws.com/dkan-default-content-files/files/datastore-simple6.csv"
- And I press "Save"
+ | title | author | published | description | link file |
+ | Resource 01 | Celeste | Yes | No | https://s3.amazonaws.com/dkan-default-content-files/district_centerpoints_small.csv |
Given I am logged in as "Celeste"
And I am on "Resource 01" page
When I click "Manage Datastore"
- And I press "Import"
- And I wait for "Delete Items"
- When I click "Drop Datastore"
+ Then I should see "Status"
+ When I press "Import"
+ And I wait for "Done"
And I press "Drop"
- Then I should see "Datastore dropped!"
- And I should see "Your file for this resource is not added to the datastore"
- When I click "Manage Datastore"
- Then I wait for "No imported items."
+ And I press "Drop"
+ Then I should see "Records Imported"
+ And I should see "0"
+ And I should see "Data Importing"
+ And I should see "Ready"
- @resource_author_18 @noworkflow
+ @resource_author @resource_author_18 @noworkflow
Scenario: Add revision to own resource
Given resources:
| title | author | published | description |
@@ -378,7 +321,7 @@ Feature: Resource
# @todo Add test for URL w/o .csv
# We need to edit and save to trigger auto type discover
- @resource_author_19 @javascript
+ @resource_author @resource_author_19 @javascript
Scenario: Remote CSV preview
Given resources:
| title | author | published | description | link file |
@@ -389,7 +332,7 @@ Feature: Resource
And I press "Save"
Then I should see a recline preview
- @resource_author_20 @javascript
+ @resource_author @resource_author_20 @javascript
Scenario: Image preview
Given resources:
| title | author | published | description | link file |
@@ -400,7 +343,7 @@ Feature: Resource
And I press "Save"
Then I should see a image preview
- @resource_author_21
+ @resource_author @resource_author_21
Scenario: ZIP preview
Given resources:
| title | author | published | description | link file |
@@ -411,7 +354,7 @@ Feature: Resource
And I press "Save"
Then I should see a zip preview
- @resource_author_22 @javascript
+ @resource_author @resource_author_22 @javascript
Scenario: XML preview
Given resources:
| title | author | published | description | link file |
@@ -422,7 +365,7 @@ Feature: Resource
And I press "Save"
Then I should see a xml preview
- @resource_author_23
+ @resource_author @resource_author_23
Scenario: JSON preview
Given resources:
| title | author | published | description | link file |
@@ -433,7 +376,7 @@ Feature: Resource
And I press "Save"
Then I should see a json preview
- @resource_author_24
+ @resource_author @resource_author_24
Scenario: GEOJSON preview
Given resources:
| title | author | published | description | link file |
@@ -444,7 +387,7 @@ Feature: Resource
And I press "Save"
Then I should see a geojson preview
- @resource_author_25 @javascript
+ @resource_author @resource_author_25 @javascript
Scenario: Generated CSV preview
Given resources:
| title | author | published | description | link file |
@@ -455,7 +398,7 @@ Feature: Resource
And I press "Save"
Then I should see a recline preview
- @resource_author_26 @noworkflow
+ @resource_author @resource_author_26 @noworkflow
Scenario: Create resource with a tsv file
Given I am logged in as "John"
And I am on the "Content" page
@@ -470,7 +413,7 @@ Feature: Resource
When I click "Edit"
Then the "field_format[und][textfield]" field should contain "tsv"
- @resource_author_27 @noworkflow
+ @resource_author @resource_author_27 @noworkflow
Scenario: Create resource with a tab file
Given I am logged in as "John"
And I am on the "Content" page
diff --git a/profiles/dkan/test/features/resource.editor.feature b/profiles/dkan/test/features/resource.editor.feature
index bd51cf7ed27..ab9c2da2b40 100644
--- a/profiles/dkan/test/features/resource.editor.feature
+++ b/profiles/dkan/test/features/resource.editor.feature
@@ -90,65 +90,28 @@ Feature: Resource
Scenario: Manage datastore of resources associated with groups that I am a member of
Given I am logged in as "Celeste"
And I am on "Resource 01" page
- When I click "Manage Datastore"
- Then I should see "There is nothing to manage! You need to upload or link to a file in order to use the datastore."
+ Then I should not see "Manage Datastore"
- @resource_editor_6 @datastore @noworkflow @javascript @fixme
- Scenario: Import items on datastore of resources associated with groups that I am a member of
+ @resource_editor_6 @datastore @noworkflow @javascript
+ Scenario: Import and drop items on datastore of resources associated with groups that I am a member of
Given I am logged in as "John"
And I am on "Resource 02" page
And I click "Edit"
And I click "Remote file"
And I fill in "edit-field-link-remote-file-und-0-filefield-dkan-remotefile-url" with "https://s3.amazonaws.com/dkan-default-content-files/files/datastore-simple7.csv"
- And I press "Save"
- Given I am logged in as "Celeste"
- And I am on "Resource 02" page
- When I follow "View"
- When I click "Manage Datastore"
- And I wait for "Import"
- And I press "Import"
- And I wait for "Delete Items"
- Then "Resource 02" should have datastore records
-
- @resource_editor_7 @datastore @db @noworkflow @javascript @fixme
- Scenario: Delete items on datastore of resources associated with groups that I am a member of
- Given I am logged in as "John"
- And I am on "Resource 02" page
- And I click "Edit"
- And I click "Remote file"
- And I fill in "edit-field-link-remote-file-und-0-filefield-dkan-remotefile-url" with "https://s3.amazonaws.com/dkan-default-content-files/files/datastore-simple8.csv"
- And I press "Save"
- Given I am logged in as "Celeste"
- When I am on "Resource 02" page
- When I follow "View"
- When I click "Manage Datastore"
- And I wait for "Import"
- And I press "Import"
- And I wait for "Delete Items"
- Then "Resource 02" should have datastore records
- And I click "Delete items"
- And I press "Delete"
- And I wait for "items have been deleted"
- Then "Resource 02" should have no datastore records
-
- @resource_editor_8 @datastore @noworkflow @javascript @fixme
- Scenario: Drop datastore of resources associated with groups that I am a member of
- Given I am logged in as "John"
- And I am on "Resource 02" page
- And I click "Edit"
- And I click "Remote file"
- And I fill in "edit-field-link-remote-file-und-0-filefield-dkan-remotefile-url" with "https://s3.amazonaws.com/dkan-default-content-files/files/datastore-simple9.csv"
- And I press "Save"
- Given I am logged in as "Celeste"
+ Then I press "Save"
+ When I am logged in as "Celeste"
And I am on "Resource 02" page
When I click "Manage Datastore"
- And I wait for "Import"
- And I press "Import"
- And I wait for "Delete Items"
- Then "Resource 02" should have datastore records
- When I click "Drop Datastore"
+ Then I should see "Status"
+ When I press "Import"
+ And I wait for "Done"
+ And I press "Drop"
And I press "Drop"
- Then "Resource 02" should have no datastore records
+ Then I should see "Records Imported"
+ And I should see "0"
+ And I should see "Data Importing"
+ And I should see "Ready"
@resource_editor_9 @noworkflow
Scenario: Add revision to resources associated with groups that I am a member of
diff --git a/profiles/dkan/test/features/search.feature b/profiles/dkan/test/features/search.feature
index 4e5c5d261a2..33e6ecfe1bc 100644
--- a/profiles/dkan/test/features/search.feature
+++ b/profiles/dkan/test/features/search.feature
@@ -58,9 +58,10 @@ Feature: Search
@search_02
Scenario: See number of datasets on search page and Reset dataset search filters
Given I am on the "Dataset Search" page
- When I search for "DKANTest"
- Then I should see "4 results"
- And I should see "4" items in the "datasets" region
+ And I fill in "DKANTest" for "Search" in the "datasets" region
+ And I press "Apply"
+ Then I should see "2 results"
+ And I should see "2" items in the "datasets" region
When I press "Reset"
Then I should see all published search content
diff --git a/profiles/dkan/test/features/user.editor.feature b/profiles/dkan/test/features/user.editor.feature
index fb0bf1e63be..f2563fad7f4 100644
--- a/profiles/dkan/test/features/user.editor.feature
+++ b/profiles/dkan/test/features/user.editor.feature
@@ -19,8 +19,6 @@ Feature: User command center links for editor role.
And I click "Resource"
Then I should see "Add resource"
When I hover over the admin menu item "Add content"
- And I click "Page"
- Then I should see "Create Page"
When I hover over the admin menu item "Add content"
And I click "Data Story"
Then I should see "Create Data Story"
@@ -31,7 +29,7 @@ Feature: User command center links for editor role.
Then I hover over the admin menu item "Visualization"
And I click "Chart"
Then I should see "Add Chart"
-
+
Scenario: Editor role can view admin menu link Content
Given I am logged in as "Jaz"
When I click "Content" in the "admin menu" region
@@ -51,7 +49,7 @@ Feature: User command center links for editor role.
When I hover over the admin menu item "Visualizations"
And I click "Charts"
Then I should see "Chart"
-
+
@javascript
Scenario: Editor role can view admin menu links under Site Configuration
Given I am logged in as "Jaz"
diff --git a/profiles/dkan/test/phpunit/dkan_datastore/DkanDatastoreCsvParserTest.php b/profiles/dkan/test/phpunit/dkan_datastore/DkanDatastoreCsvParserTest.php
new file mode 100644
index 00000000000..1f0d7c2b94d
--- /dev/null
+++ b/profiles/dkan/test/phpunit/dkan_datastore/DkanDatastoreCsvParserTest.php
@@ -0,0 +1,378 @@
+feed($string);
+ $parser->finish();
+ return $parser;
+ }
+
+ public function testEmptyString() {
+ $parser = $this->parse('');
+
+ $record = $parser->getRecord();
+ $values = [''];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ public function testJustDelimiters() {
+ $parser = $this->parse(',,');
+
+ $record = $parser->getRecord();
+ $values = ['', '', ''];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ public function testEmptyNewLineString() {
+ $parser = $this->parse("\n");
+
+ $record = $parser->getRecord();
+ $values = [''];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ public function testJustDelimitersNewLineString() {
+ $parser = $this->parse(",,\n,,");
+
+ $record = $parser->getRecord();
+ $values = ['', '', ''];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $record = $parser->getRecord();
+ $values = ['', '', ''];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ public function testBlankString() {
+ $parser = $this->parse(' ');
+
+ $record = $parser->getRecord();
+ $values = [''];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ public function testBlankJustDelimiters() {
+ $parser = $this->parse(' , , ');
+
+ $record = $parser->getRecord();
+ $values = ['', '', ''];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ public function testBlankNewLineString() {
+ $parser = $this->parse(" \n ");
+
+ $record = $parser->getRecord();
+ $values = [''];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $record = $parser->getRecord();
+ $values = [''];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ public function testBlankJustDelimitersNewLineString() {
+ $parser = $this->parse(" , , \n , , ");
+
+ $record = $parser->getRecord();
+ $values = ['', '', ''];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $record = $parser->getRecord();
+ $values = ['', '', ''];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ public function testOther() {
+ $parser = $this->parse('A');
+
+ $record = $parser->getRecord();
+ $values = ['A'];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ public function testOtherJustDelimiters() {
+ $parser = $this->parse('A,B,C');
+
+ $record = $parser->getRecord();
+ $values = ['A', 'B', 'C'];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ public function testOtherNewLineString() {
+ $parser = $this->parse("A\nB");
+
+ $record = $parser->getRecord();
+ $values = ['A'];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $record = $parser->getRecord();
+ $values = ['B'];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ public function testOtherJustDelimitersNewLineString() {
+ $parser = $this->parse("A,B,C\nD,E,F");
+
+ $record = $parser->getRecord();
+ $values = ['A', 'B', 'C'];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $record = $parser->getRecord();
+ $values = ['D', 'E', 'F'];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ public function testOtherBlank() {
+ $parser = $this->parse(' A B ');
+
+ $record = $parser->getRecord();
+ $values = ['A B'];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ public function testOtherBlankJustDelimiters() {
+ $parser = $this->parse(' A B ,B C , CD');
+
+ $record = $parser->getRecord();
+ $values = ['A B', 'B C', 'CD'];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ public function testOtherBlankNewLineString() {
+ $parser = $this->parse("A B \n B C");
+
+ $record = $parser->getRecord();
+ $values = ['A B'];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $record = $parser->getRecord();
+ $values = ['B C'];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ public function testOtherBlankJustDelimitersNewLineString() {
+ $parser = $this->parse(" AB,B C \n D E,E F ");
+
+ $record = $parser->getRecord();
+ $values = ['AB', 'B C'];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $record = $parser->getRecord();
+ $values = ['D E', 'E F'];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ public function testOtherBlankEscape() {
+ $parser = $this->parse(' A \\' . "\n" . 'B\, ');
+
+ $record = $parser->getRecord();
+ $values = ["A \nB,"];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ public function testOtherBlankEscapeJustDelimiters() {
+ $parser = $this->parse(' A \\\B ,B \\' . "\n" . ' C , CD');
+
+ $record = $parser->getRecord();
+ $values = ['A \\B', "B \n C", 'CD'];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ public function testOtherBlankEscapeNewLineString() {
+ $parser = $this->parse('A B \\' . "\n \n" . ' \\, B C ');
+
+ $record = $parser->getRecord();
+ $values = ["A B \n"];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $record = $parser->getRecord();
+ $values = [', B C'];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ public function testOtherBlankEscapeJustDelimitersNewLineString() {
+ $parser = $this->parse(' A \\' . "\n" . ' B,B C ' . "\n" . ' \\\D E\,,E F ');
+
+ $record = $parser->getRecord();
+ $values = ["A \n B", 'B C'];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $record = $parser->getRecord();
+ $values = ['\D E,', 'E F'];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ public function testQuotes() {
+ $parser = $this->parse('""');
+
+ $record = $parser->getRecord();
+ $values = [''];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ public function testQuoteDelimiters() {
+ $parser = $this->parse('" A B " , " B ' . "\n" . ' C"');
+
+ $record = $parser->getRecord();
+ $values = [' A B ', " B \n C"];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ public function testQuoteNewLineString() {
+ $parser = $this->parse(' " A B ' . "\n" . '" ' . "\n" . ' ", B \" C "');
+
+ $record = $parser->getRecord();
+ $values = [" A B \n"];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $record = $parser->getRecord();
+ $values = [', B " C '];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ public function testQuoteJustDelimitersNewLineString() {
+ $parser = $this->parse(' "A '. "\n" . ' B" , "B C" ' . "\n" . ' "\\\D E," , "E F" ');
+
+ $record = $parser->getRecord();
+ $values = ["A \n B", 'B C'];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $record = $parser->getRecord();
+ $values = ['\D E,', 'E F'];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ public function testDoubleQuoteEscaping() {
+ $parser = $this->parse('"S ""H"""');
+
+ $record = $parser->getRecord();
+ $values = ['S "H"'];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+
+ $parser = $this->parse('"S ""H"" S"');
+
+ $record = $parser->getRecord();
+ $values = ['S "H" S'];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+
+ $parser = $this->parse('"""H"" S"');
+
+ $record = $parser->getRecord();
+ $values = ['"H" S'];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ /**
+ * Double quote parsing requires looking ahead or back as we process a string.
+ * We went to make sure that when a string is cut in the middle of double
+ * quote scaping, that the parser can still handle it without problems.
+ */
+ public function testBrokenLookAhead() {
+ $string1 = '"S "';
+ $string2 = '"H"""';
+ $parser = new \Dkan\Datastore\Parser\Csv(",", "\"", "\\", ["\r", "\n"]);
+ $parser->feed($string1);
+ $parser->feed($string2);
+ $parser->finish();
+
+ $record = $parser->getRecord();
+ $values = ['S "H"'];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ public function testTrailingDelimiter() {
+ $parser = $this->parse('H,F,' . "\n" . 'G,B,');
+
+ $record = $parser->getRecord();
+ $values = ['H', 'F', ''];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $record = $parser->getRecord();
+ $values = ['G', 'B', ''];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+
+ $parser = new \Dkan\Datastore\Parser\Csv(",", "\"", "\\", ["\r", "\n"]);
+ $parser->activateTrailingDelimiter();
+ $parser->feed('H,F ' . "\n" . 'G,B,');
+ $parser->finish();
+
+ $record = $parser->getRecord();
+ $values = ['H', 'F'];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $record = $parser->getRecord();
+ $values = ['G', 'B'];
+ $this->assertNumberOfFieldsAndValues($record, $values);
+
+ $this->assertNull($parser->getRecord());
+ }
+
+ private function assertNumberOfFieldsAndValues($record, $values) {
+ $this->assertEquals(count($record), count($values));
+
+ foreach ($record as $key => $value) {
+ $this->assertEquals($values[$key], $value);
+ }
+ }
+}
\ No newline at end of file
diff --git a/profiles/dkan/test/phpunit/dkan_datastore/DkanDatastoreTest.php b/profiles/dkan/test/phpunit/dkan_datastore/DkanDatastoreTest.php
index 35d6f74eda9..3fa0154bcbe 100644
--- a/profiles/dkan/test/phpunit/dkan_datastore/DkanDatastoreTest.php
+++ b/profiles/dkan/test/phpunit/dkan_datastore/DkanDatastoreTest.php
@@ -1,165 +1,68 @@
getMockBuilder(DkanDatastore::class)
- ->setMethods(['updateFromFile'])
- ->disableOriginalConstructor()
- ->disableOriginalClone()
- ->getMock();
-
- // Configure the stub.
- $dkanDatastorestub->expects($this->once())
- ->method('updateFromFile')
- ->willReturn(TRUE);
-
- $testfileuri = __DIR__ . '/data/countries.csv';
- // Calling $stub->doSomething() will now return
- // 'foo'.
- $this->assertEquals(TRUE, $dkanDatastorestub->updateFromFileUri($testfileuri));
- }
+ private $resource_node;
- /**
- * Test updateFromFileUri when no file is provided.
- *
- * @covers DkanDatastore::updateFromFileUri
- */
- public function testUpdateFromFileUriNoFile() {
- // Create a stub for the DkanDatastore class.
- $dkanDatastorestub = $this->getMockBuilder(DkanDatastore::class)
- ->setMethods(['updateFromFile'])
- ->disableOriginalConstructor()
- ->disableOriginalClone()
- ->getMock();
-
- // Configure the DkanDatastorestub method.
- $dkanDatastorestub->expects($this->never())
- ->method('updateFromFile')
- ->willReturn(TRUE);
-
- $this->assertEquals(FALSE, $dkanDatastorestub->updateFromFileUri());
+ protected function setUp() {
+ $node = (object) [];
+ $node->title = "Datastore Resource Test Object 23523";
+ $node->type = "resource";
+ $node->field_link_remote_file['und'][0]['uri'] = "https://s3.amazonaws.com/dkan-default-content-files/district_centerpoints_small.csv";
+ $node->status = 1;
+ node_save($node);
+ $this->resource_node = node_load($node->nid);
}
- /**
- * Test importByCli.
- *
- * @covers DkanDatastore::importByCli
- */
- public function testImportByCli() {
- $feedsSourceStub = $this->getMockBuilder(FeedsSource::class)
- ->setMethods(['import'])
- ->disableOriginalConstructor()
- ->disableOriginalClone()
- ->getMock();
-
- $feedsSourceStub->expects($this->once())
- ->method('import')
- ->willReturn(FEEDS_BATCH_COMPLETE);
-
- // Create a stub for the DkanDatastore class.
- $dkanDatastoreStub = $this->getMockBuilder(DkanDatastore::class)
- ->setMethods(['source', 'setupSourceBackground'])
- ->disableOriginalConstructor()
- ->disableOriginalClone()
- ->getMock();
-
- // Configure the DkanDatastorestub method.
- $dkanDatastoreStub->expects($this->once())
- ->method('source')
- ->willReturn($feedsSourceStub);
-
- // Configure the DkanDatastorestub method.
- $dkanDatastoreStub->expects($this->once())
- ->method('setupSourceBackground')
- ->willReturn(TRUE);
-
- $this->assertEquals(TRUE, $dkanDatastoreStub->importByCli());
- }
+ public function test() {
+
+ $resource = new Resource($this->resource_node->nid, __DIR__ . "/data/countries.csv");
+
+ /* @var $datastore \Dkan\Datastore\Manager\SimpleImport\SimpleImport */
+ $datastore = (new \Dkan\Datastore\Manager\Factory($resource))->get();
+
+ $status = $datastore->getStatus();
+ $this->assertEquals(SimpleImport::STORAGE_UNINITIALIZED, $status['storage']);
+ $this->assertEquals(SimpleImport::DATA_IMPORT_UNINITIALIZED, $status['data_import']);
+
+ $datastore->import();
+
+ $status = $datastore->getStatus();
+ $this->assertEquals(SimpleImport::STORAGE_INITIALIZED, $status['storage']);
+ $this->assertEquals(SimpleImport::DATA_IMPORT_DONE, $status['data_import']);
+
+
+ $query = db_select($datastore->getTableName(), "d");
+ $query->fields("d");
+ $results = $query->execute();
+ $results = $results->fetchAllAssoc("country");
+ $json = json_encode($results);
+ $this->assertEquals(
+ "{\"US\":{\"country\":\"US\",\"population\":\"315209000\",\"id\":\"1\",\"timestamp\":\"1359062329\"},\"CA\":{\"country\":\"CA\",\"population\":\"35002447\",\"id\":\"2\",\"timestamp\":\"1359062329\"},\"AR\":{\"country\":\"AR\",\"population\":\"40117096\",\"id\":\"3\",\"timestamp\":\"1359062329\"},\"JP\":{\"country\":\"JP\",\"population\":\"127520000\",\"id\":\"4\",\"timestamp\":\"1359062329\"}}",
+ $json);
+
+ $this->assertEquals(4, $datastore->numberOfRecordsImported());
+
+ $status = $datastore->getStatus();
+ $this->assertEquals(SimpleImport::STORAGE_INITIALIZED, $status['storage']);
+ $this->assertEquals(SimpleImport::DATA_IMPORT_DONE, $status['data_import']);
+
+ $datastore->drop();
+ $this->assertFalse(db_table_exists($datastore->getTableName()));
- /**
- * Test importByCli when Feeds Source preparation fails.
- *
- * @covers DkanDatastore::importByCli
- */
- public function testImportByCliFailedSourcePreparation() {
- $feedsSourceStub = $this->getMockBuilder(FeedsSource::class)
- ->setMethods(['import'])
- ->disableOriginalConstructor()
- ->disableOriginalClone()
- ->getMock();
-
- $feedsSourceStub->expects($this->never())
- ->method('import');
-
- // Create a stub for the DkanDatastore class.
- $dkanDatastoreStub = $this->getMockBuilder(DkanDatastore::class)
- ->setMethods(['source', 'setupSourceBackground'])
- ->disableOriginalConstructor()
- ->disableOriginalClone()
- ->getMock();
-
- // Configure the DkanDatastorestub method.
- $dkanDatastoreStub->expects($this->once())
- ->method('source')
- ->willReturn($feedsSourceStub);
-
- // Configure the DkanDatastorestub method.
- $dkanDatastoreStub->expects($this->once())
- ->method('setupSourceBackground')
- ->willReturn(FALSE);
-
- $this->assertEquals(FALSE, $dkanDatastoreStub->importByCli());
+ $status = $datastore->getStatus();
+ $this->assertEquals(SimpleImport::STORAGE_UNINITIALIZED, $status['storage']);
+ $this->assertEquals(SimpleImport::DATA_IMPORT_UNINITIALIZED, $status['data_import']);
}
- /**
- * Test importByCli when the Feed Source import fails.
- *
- * @covers DkanDatastore::importByCli
- */
- public function testImportByCliFailImport() {
- $feedsSourceStub = $this->getMockBuilder(FeedsSource::class)
- ->setMethods(['import'])
- ->disableOriginalConstructor()
- ->disableOriginalClone()
- ->getMock();
-
- $feedsSourceStub->expects($this->once())
- ->method('import')
- ->will($this->throwException(new Exception()));
-
- // Create a stub for the DkanDatastore class.
- $dkanDatastoreStub = $this->getMockBuilder(DkanDatastore::class)
- ->setMethods(['source', 'setupSourceBackground'])
- ->disableOriginalConstructor()
- ->disableOriginalClone()
- ->getMock();
-
- // Configure the DkanDatastorestub method.
- $dkanDatastoreStub->expects($this->once())
- ->method('source')
- ->willReturn($feedsSourceStub);
-
- // Configure the DkanDatastorestub method.
- $dkanDatastoreStub->expects($this->once())
- ->method('setupSourceBackground')
- ->willReturn(TRUE);
-
- $this->assertEquals(FALSE, $dkanDatastoreStub->importByCli());
+ protected function tearDown() {
+ node_delete($this->resource_node->nid);
}
}
diff --git a/profiles/dkan/test/phpunit/dkan_datastore_api/DkanDatastoreAPITest.php b/profiles/dkan/test/phpunit/dkan_datastore_api/DkanDatastoreAPITest.php
index e7bdd117417..3568188d285 100644
--- a/profiles/dkan/test/phpunit/dkan_datastore_api/DkanDatastoreAPITest.php
+++ b/profiles/dkan/test/phpunit/dkan_datastore_api/DkanDatastoreAPITest.php
@@ -1,4 +1,7 @@
field_upload[LANGUAGE_NONE][0] = (array)$file;
node_save($node);
- // Import it to the datastore.
- $importerId = 'dkan_file';
- $source = feeds_source($importerId, $node->nid);
- $config = array(
- 'process_in_background' => TRUE,
- );
- $source->importer->addConfig($config);
+ $resource = Resource::createFromDrupalNode($node);
+
+ /* @var $datastore \Dkan\Datastore\Manager\ManagerInterface */
+ $datastore = (new \Dkan\Datastore\Manager\Factory($resource))->get();
- while (FEEDS_BATCH_COMPLETE != $source->import());
+ if ($datastore instanceof DkanDatastoreFeedsImport) {
+ // Import it to the datastore.
+ $importerId = 'dkan_file';
+ $source = feeds_source($importerId, $node->nid);
+ $config = array(
+ 'process_in_background' => TRUE,
+ );
+ $source->importer->addConfig($config);
+ while (FEEDS_BATCH_COMPLETE != $source->import());
+ }
+ else {
+ $datastore->import();
+ }
}
/**
@@ -92,10 +102,27 @@ private static function addResource($resource) {
public static function tearDownAfterClass() {
$resources = self::getResources();
foreach ($resources as $resource) {
+ $r = \Dkan\Datastore\Resource::createFromDrupalNodeUuid($resource['uuid']);
+
+ /* @var $datastore \Dkan\Datastore\Manager\ManagerInterface */
+ $datastore = (new \Dkan\Datastore\Manager\Factory($r))->get();
+ $datastore->drop();
+
entity_uuid_delete('node', array($resource['uuid']));
}
}
+ public static function getFilterParams($filters) {
+ $params = array(
+ 'resource_id' => array(
+ 'gold_prices' => self::getUUID('gold_prices', self::getResources()),
+ ),
+ 'limit' => 1000,
+ 'filters' => $filters
+ );
+ return dkan_datastore_api_get_params($params);
+ }
+
/**
* Query test.
*/
@@ -109,7 +136,7 @@ public function test_dkan_datstore_api_query() {
);
$params = dkan_datastore_api_get_params($params);
$result = dkan_datastore_api_query($params);
- $this->assertEquals($result['result']->total, 3);
+ $this->assertEquals(3, $result['result']->total);
}
/**
@@ -131,30 +158,6 @@ public function test_dkan_dkan_datastore_api_filters_array_multivalue_format() {
$this->assertEquals($result['result']->records[1]->date, '1950-03-01');
}
- public function test_dkan_datstore_api_filters_prefixed_table() {
- $filters = array('gold_prices' => array('date' => '1950-02-01'));
- $params = self::getFilterParams($filters);
- $result = dkan_datastore_api_query($params);
- // print_r($result);
- }
-
- public function test_dkan_datstore_api_filters_prefixed_table_mixed() {
- $filters = array('gold_prices' => array('date' => '1950-02-01'));
- $params = self::getFilterParams($filters);
- $result = dkan_datastore_api_query($params);
- }
-
- public static function getFilterParams($filters) {
- $params = array(
- 'resource_id' => array(
- 'gold_prices' => self::getUUID('gold_prices', self::getResources()),
- ),
- 'limit' => 1000,
- 'filters' => $filters
- );
- return dkan_datastore_api_get_params($params);
- }
-
/**
* Offset test.
*/
@@ -168,7 +171,7 @@ public function test_dkan_datstore_api_offset() {
);
$params = dkan_datastore_api_get_params($params);
$result = dkan_datastore_api_query($params);
- $this->assertEquals($result['result']->records[0]->state_id, 2);
+ $this->assertEquals(2, $result['result']->records[0]->state_id);
}
/**
@@ -215,7 +218,7 @@ public function test_dkan_datstore_api_sort() {
);
$params = dkan_datastore_api_get_params($params);
$result = dkan_datastore_api_query($params);
- $this->assertEquals($result['result']->records[0]->state_id, 5);
+ $this->assertEquals(5, $result['result']->records[0]->state_id);
}
/**
@@ -251,6 +254,7 @@ public function test_dkan_datstore_api_join() {
);
$params = dkan_datastore_api_get_params($params);
$result = dkan_datastore_api_query($params);
+
$this->assertObjectHasAttribute('name', $result['result']->records[0]);
$this->assertObjectHasAttribute('price', $result['result']->records[0]);
}
@@ -311,12 +315,12 @@ public function test_dkan_datstore_api_multiquery() {
* Test aggregations
*/
public function test_dkan_datstore_api_aggregations() {
- $aggregations = array('sum', 'avg', 'min', 'max', 'count');
+ $aggregations = array('sum', 'avg', /*'min', 'max',*/ 'count');
$expect = array(
'sum' => 219726,
'avg' => 293,
- 'min' => 34,
- 'max' => 1780,
+ //'min' => 34,
+ //'max' => 1780,
'count' => 748,
);
foreach ($aggregations as $agg) {
@@ -329,9 +333,8 @@ public function test_dkan_datstore_api_aggregations() {
$params[$agg] = array('gold_prices' => 'price');
$params = dkan_datastore_api_get_params($params);
$result = dkan_datastore_api_query($params);
- $this->assertEquals(floor($result['result']->records[0]->{$agg.'_price'}) , $expect[$agg]);
+ $this->assertEquals($expect[$agg], floor($result['result']->records[0]->{$agg.'_price'}));
}
-
}
/**
@@ -355,4 +358,3 @@ public function test_dkan_dkan_datastore_api_empty_is_null() {
}
}
-
diff --git a/profiles/dkan/test/phpunit/dkan_datastore_fast_import/DkanDatastoreFastImportTest.php b/profiles/dkan/test/phpunit/dkan_datastore_fast_import/DkanDatastoreFastImportTest.php
deleted file mode 100644
index e6bc74e2ec8..00000000000
--- a/profiles/dkan/test/phpunit/dkan_datastore_fast_import/DkanDatastoreFastImportTest.php
+++ /dev/null
@@ -1,190 +0,0 @@
- $id) {
- return $id;
- }
- }
-
- /**
- * Retrieves an keyed array of resources.
- */
- private static function getResources() {
- $resources = array(
- 'polling_places' => array(
- 'filename' => 'polling_places.csv',
- 'title' => 'Polling Places',
- 'uuid' => '3a05eb9c-3733-11e6-ac61-9e71128cae79'
- ),
- 'null_check' => array(
- 'filename' => 'null_check.csv',
- 'title' => 'Empy Values Checker',
- 'uuid' => '3a05eb9c-3733-11e6-ac61-9e71128cae80'
- ),
- );
- return $resources;
- }
-
- /**
- * Given a resource key retrieves a uuid.
- */
- private static function getUuid($key, $resources) {
- if (array_key_exists($key, $resources)) {
- return $resources[$key]['uuid'];
- }
- else {
- throw new \Exception('Resource is not defined');
- }
-
- }
-
- /**
- * Add a resource to test.
- */
- private static function addResource($resource) {
-
- // Create resource.
- $filename = $resource['filename'];
- $node = new stdClass();
- $node->title = $resource['title'];
- $node->type = 'resource';
- $node->uid = 1;
- $node->uuid = $resource['uuid'];
- $node->language = 'und';
- $path = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'files', $filename));
- $file = file_save_data(file_get_contents($path), 'public://' . $filename);
- $node->field_upload[LANGUAGE_NONE][0] = (array) $file;
-
- node_save($node);
- }
-
- /**
- * Teardown function.
- */
- public static function tearDownAfterClass() {
- $resources = self::getResources();
- foreach ($resources as $resource) {
- entity_uuid_delete('node', array($resource['uuid']));
- }
- // Assuming the test module enabled by now. Restore original status of the
- // modules.
- if (!self::getDkanFastImportTestBeforeClassStatus()) {
- module_disable(array('dkan_datastore_fast_import'));
- }
- }
-
- /**
- * Test dkan_datastore_fast_import functionality.
- */
- public function testFastImportWithQuoteDelimiters() {
- // TODO: Fix fast import with MariaDB.
- return TRUE;
- $nid = self::getNodeFromUuid(self::getUuid('polling_places', self::getResources()));
- $importerId = 'dkan_file';
- $node = node_load($nid);
- $node = entity_metadata_wrapper('node', $node);
-
- $source = feeds_source($importerId, $nid);
-
- $table = feeds_flatstore_processor_table($source, array());
- $config = array(
- 'delimiter' => ',',
- 'no_headers' => 0,
- 'encoding' => 'UTF-8',
- );
-
- variable_set('dkan_datastore_load_data_type', 'load_data_local_infile');
- variable_set('quote_delimiters', '"');
- variable_set('lines_terminated_by', '\n');
- variable_set('fields_escaped_by', '');
- variable_set('dkan_datastore_fast_import_load_empty_cells_as_null', 0);
-
- $result = dkan_datastore_fast_import_import($source, $node, $table, $config);
- $this->assertEquals($result['total_imported_items'], 117);
- }
-
- /**
- * Test fast_import module with options for reading empty fields as null.
- */
- public function testFastImportLoadEmptyCellsAsNull() {
- // TODO: Fix fast import with MariaDB.
- return TRUE;
- $nid = self::getNodeFromUuid(self::getUuid('null_check', self::getResources()));
- $importerId = 'dkan_file';
- $node = node_load($nid);
- $node = entity_metadata_wrapper('node', $node);
-
- $source = feeds_source($importerId, $nid);
-
- $table = feeds_flatstore_processor_table($source, array());
- $config = array(
- 'delimiter' => ',',
- 'no_headers' => 0,
- 'encoding' => 'UTF-8',
- );
-
- variable_set('dkan_datastore_load_data_type', 'load_data_local_infile');
- variable_set('quote_delimiters', '"');
- variable_set('lines_terminated_by', '\n');
- variable_set('fields_escaped_by', '');
- variable_set('dkan_datastore_fast_import_load_empty_cells_as_null', 1);
-
- $result = dkan_datastore_fast_import_import($source, $node, $table, $config);
- $this->assertEquals($result['total_imported_items'], 2);
-
- $source = feeds_source($importerId, $nid);
- $preview = $source->preview();
-
- $this->assertEquals($preview->items[1]['ward'], '');
- $this->assertEquals($preview->items[1]['address'], '');
- }
-
-}
diff --git a/profiles/dkan/test/phpunit/dkan_datastore_fast_import/files/null_check.csv b/profiles/dkan/test/phpunit/dkan_datastore_fast_import/files/null_check.csv
deleted file mode 100644
index fb6ea4944fd..00000000000
--- a/profiles/dkan/test/phpunit/dkan_datastore_fast_import/files/null_check.csv
+++ /dev/null
@@ -1,3 +0,0 @@
-Ward,Aldermanic District,Name,Address,Comments,Handicap Accessible,Latitude,Longitude
-1,16,Glendale Elementary School,"1201 Tompkins Dr Madison, WI (43.058534053250526, -89.31246859534969)",LMC,TRUE,43.05853405,-89.3124686
-,0,Glendale Elementary School,"",LMC,TRUE,0.0,-89.3124686
diff --git a/profiles/dkan/test/phpunit/dkan_datastore_fast_import/files/polling_places.csv b/profiles/dkan/test/phpunit/dkan_datastore_fast_import/files/polling_places.csv
deleted file mode 100644
index eacf9fc6485..00000000000
--- a/profiles/dkan/test/phpunit/dkan_datastore_fast_import/files/polling_places.csv
+++ /dev/null
@@ -1,118 +0,0 @@
-Ward,Aldermanic District,Name,Address,Comments,Handicap Accessible,Latitude,Longitude
-1,16,Glendale Elementary School,"1201 Tompkins Dr Madison, WI (43.058534053250526, -89.31246859534969)",LMC,TRUE,43.05853405,-89.3124686
-2,16,Glendale Elementary School,"1201 Tompkins Dr Madison, WI (43.058534053250526, -89.31246859534969)",LMC,TRUE,43.05853405,-89.3124686
-3,16,City Church,"4909 Buckeye Rd Madison, WI (43.07356172436016, -89.311955981793)",Lower Level - Rear Entrance,TRUE,43.07356172,-89.31195598
-4,16,City Church,"4909 Buckeye Rd Madison, WI (43.07356172436016, -89.311955981793)",Lower Level - Rear Entrance,TRUE,43.07356172,-89.31195598
-5,16,Elvehjem Elementary,"5106 Academy Dr Madison, WI (43.07798638874431, -89.29494980221892)","West Hall by Gym B, Gym B used in November",TRUE,43.07798639,-89.2949498
-6,16,Elvehjem Elementary,"5106 Academy Dr Madison, WI (43.07798638874431, -89.29494980221892)","West Hall by Gym B, Gym B used in November",TRUE,43.07798639,-89.2949498
-7,16,City Church,"4909 Buckeye Rd Madison, WI (43.07356172436016, -89.311955981793)",Lower Level - Rear Entrance,TRUE,43.07356172,-89.31195598
-8,3,Door Creek Church,"6602 Dominion Dr Madison, WI (43.09104454460976, -89.26444479898504)",Gym,TRUE,43.09104454,-89.2644448
-9,3,East Police District,"809 Thompson Dr Madison, WI (43.10891702887152, -89.29824374052728)",Community room,TRUE,43.10891703,-89.29824374
-10,3,Kennedy Elementary,"221 Meadowlark Dr Madison, WI (43.09445297510081, -89.29816937508383)","LMC for most elections, gym for November",TRUE,43.09445298,-89.29816938
-11,3,American Family Insurance,"302 Walbridge Ave Madison, WI (43.102844085356566, -89.31148201090909)",Walbridge Room - park in front & use main entrance,TRUE,43.10284409,-89.31148201
-12,3,New Beginnings Church,"602 Acewood Blvd Madison, WI (43.089344825974365, -89.30234222929977)",Enter off parking lot - lower level,TRUE,43.08934483,-89.30234223
-13,15,LaFollette High School,"700 Pflaum Rd Madison, WI (43.06214214871068, -89.3187490170764)",Gym,TRUE,43.06214215,-89.31874902
-14,15,LaFollette High School,"700 Pflaum Rd Madison, WI (43.06214214871068, -89.3187490170764)",Gym,TRUE,43.06214215,-89.31874902
-15,15,LaFollette High School,"700 Pflaum Rd Madison, WI (43.06214214871068, -89.3187490170764)",Gym,TRUE,43.06214215,-89.31874902
-16,15,Whitehorse Middle School,"218 Schenk St Madison, WI (43.092755677235736, -89.32320250199108)",Gym,TRUE,43.09275568,-89.3232025
-17,15,American Family Insurance,"302 Walbridge Ave Madison, WI (43.102844085356566, -89.31148201090909)",Walbridge Room - park in front & use main entrance,TRUE,43.10284409,-89.31148201
-18,15,Hy-Vee,"3801 Washington Ave Madison, WI (43.11887443505523, -89.321263469189)",Community Room,TRUE,43.11887444,-89.32126347
-19,15,Madison Armory,"1402 Wright St Madison, WI (43.11571294787342, -89.33068885966333)","Drill Room, use front entrance on Wright St",TRUE,43.11571295,-89.33068886
-20,15,Madison Armory,"1402 Wright St Madison, WI (43.11571294787342, -89.33068885966333)","Drill Room, use front entrance on Wright St",TRUE,43.11571295,-89.33068886
-21,17,Hy-Vee,"3801 Washington Ave Madison, WI (43.11887443505523, -89.321263469189)",Community Room,TRUE,43.11887444,-89.32126347
-22,17,Streets East,"4602 Sycamore Ave Madison, WI (43.11401890912613, -89.30349081948516)", ,TRUE,43.11401891,-89.30349082
-23,17,Fire Station #11,"4011 Morgan Way Madison, WI (43.144131523999306, -89.28029984636163)", ,TRUE,43.14413152,-89.28029985
-24,17,Oakwood Village Prairie Ridge,"5565 Tancho Dr Madison, WI (43.15474754741973, -89.28390001630164)",Chapel,TRUE,43.15474755,-89.28390002
-25,17,Eastside Lutheran,"2310 Independence Ln Madison, WI (43.12947257492175, -89.31007639875173)",Gym,TRUE,43.12947257,-89.3100764
-26,17,Eastside Lutheran,"2310 Independence Ln Madison, WI (43.12947257492175, -89.31007639875173)",Gym,TRUE,43.12947257,-89.3100764
-27,12,Madison College - Commercial Avenue,"2125 Commercial Ave Madison, WI (43.10677111972665, -89.35743254298585)",Commons,TRUE,43.10677112,-89.35743254
-28,12,Madison College - Commercial Avenue,"2125 Commercial Ave Madison, WI (43.10677111972665, -89.35743254298585)",Commons,TRUE,43.10677112,-89.35743254
-29,12,East High School,"2222 Washington Ave Madison, WI (43.0964583786623, -89.35391055715205)","Fifth Street entrance, enter at door #10,Gym in November",TRUE,43.09645838,-89.35391056
-30,12,Sherman Middle School,"1610 Ruskin St Madison, WI (43.1178443410829, -89.36176043577564)",Wood Gym,TRUE,43.11784434,-89.36176044
-31,12,St Paul Lutheran,"2126 Sherman Ave Madison, WI (43.1017546654448, -89.36513826922703)",Fellowship Hall - enter off rear parking lot,TRUE,43.10175467,-89.36513827
-32,12,Packers Ave Townhomes,"1927 Northport Dr Madison, WI (43.1282444355317, -89.35764368075546)", ,TRUE,43.12824444,-89.35764368
-33,12,Warner Park Community Recreation Center,"1625 Northport Dr Madison, WI (43.13226190387762, -89.3673540801938)",Meeting Room,TRUE,43.1322619,-89.36735408
-34,18,Warner Park Community Recreation Center,"1625 Northport Dr Madison, WI (43.13226190387762, -89.3673540801938)",Meeting Room,TRUE,43.1322619,-89.36735408
-35,18,Mendota Elementary,"4002 School Rd Madison, WI (43.13672111047788, -89.38315530685264)","Main Entrance, Gym in November",TRUE,43.13672111,-89.38315531
-36,18,Mendota Elementary,"4002 School Rd Madison, WI (43.13672111047788, -89.38315530685264)","Main Entrance, Gym in November",TRUE,43.13672111,-89.38315531
-37,18,Lindbergh Elementary,"4500 Kennedy Rd Madison, WI (43.14364589010353, -89.3878857407268)",Gym,TRUE,43.14364589,-89.38788574
-38,18,Blackhawk Middle School,"1402 Wyoming Way Madison, WI (43.146137012124655, -89.37093352047447)",LMC,TRUE,43.14613701,-89.37093352
-39,6,Hawthorne Branch Library,"2707 Washington Ave Madison, WI (43.10192089186353, -89.34703102579658)",Community Room,TRUE,43.10192089,-89.34703103
-40,6,Olbrich Gardens,"3330 Atwood Ave Madison, WI (43.091620110708504, -89.33564412404473)",Community Room,TRUE,43.09162011,-89.33564412
-41,6,O'Keeffe Middle School,"510 Thornton Ave Madison, WI (43.08697936482315, -89.35702344211596)",Cafeteria - Enter off Spaight Street,TRUE,43.08697936,-89.35702344
-42,6,Wil-Mar Neighborhood Center,"953 Jenifer St Madison, WI (43.07995590398849, -89.3671833476123)",Yahara Room,TRUE,43.0799559,-89.36718335
-43,6,Madison Municipal Building,"215 Martin Luther King Jr Blvd Madison, WI (43.07286025317728, -89.3816714255043)",First Floor Lobby,TRUE,43.07286025,-89.38167143
-44,2,Tenney Park Pavilion,"402 Thornton Avenue Madison, WI (43.09350700384561, -89.36862302567746)", ,TRUE,43.093507,-89.36862303
-45,2,Lapham Elementary,"1045 Dayton St Madison, WI (43.085548841623904, -89.37270239744994)","Ingersoll Entrance, Gym in November",TRUE,43.08554884,-89.3727024
-46,2,Gates of Heaven,"302 Gorham Street Madison, WI (43.07965719714889, -89.38492560816975)", ,TRUE,43.0796572,-89.38492561
-47,2,Lowell Center,"610 Langdon St Madison, WI (43.075801036213136, -89.39583574746581)",Front Lobby,TRUE,43.07580104,-89.39583575
-48,2,Lowell Center,"610 Langdon Street Madison, WI (43.075801036213136, -89.39583574746581)",Front Lobby,TRUE,43.07580104,-89.39583575
-49,4,Madison Fresh Market,"703 University Avenue Madison, WI (43.07308494205887, -89.39748051436113)",Event Center on second floor,TRUE,43.07308494,-89.39748051
-50,4,Doyle Administration,"545 Dayton St Madison, WI (43.07078281153979, -89.39481679720781)",Auditorium,TRUE,43.07078281,-89.3948168
-51,4,Madison Senior Center,"330 Mifflin St Madison, WI (43.077841691679396, -89.38209760850428)", ,TRUE,43.07784169,-89.38209761
-52,4,Madison Municipal Building,"215 Martin Luther King Jr Blvd Madison, WI (43.07286025317728, -89.3816714255043)",Front Lobby,TRUE,43.07286025,-89.38167143
-53,4,Capitol Lakes Retirement,"333 Main St Madison, WI (43.07611418744057, -89.37975802114629)", ,TRUE,43.07611419,-89.37975802
-54,8,"Sept - May: UW Welcome Center, 21 N Park St","545 Dayton St Madison, WI (43.07078281153979, -89.39481679720781)", ,TRUE,43.07078281,-89.3948168
-55,8,Porchlight,"306 Brooks St Madison, WI (43.07225870892025, -89.4025007411189)",Dining Room,TRUE,43.07225871,-89.40250074
-56,8,"Sept - May: Gordon Dining, 770 W Dayton St","728 State St Madison, WI (43.074910707404115, -89.3984977406725)", ,TRUE,43.07491071,-89.39849774
-57,8,UW Memorial Library,"728 State St Madison, WI (43.074910707404115, -89.3984977406725)",Room 116,TRUE,43.07491071,-89.39849774
-58,8,UW Memorial Union,"800 Langdon St Madison, WI (43.07588300326599, -89.39893937645297)",Paul Bunyan Room or Tripp Commons,TRUE,43.075883,-89.39893938
-59,8,"Sept - May: Holt Commons, 1640 Kronshage Dr","800 Langdon St Madison, WI (43.07588300326599, -89.39893937645297)", ,TRUE,43.075883,-89.39893938
-60,5,Eagle Heights Community Center,"611 Eagle Hts Madison, WI (43.087673723008464, -89.43624462373921)",Room 101,TRUE,43.08767372,-89.43624462
-61,5,First Congregational Church,"1609 University Ave Madison, WI (43.0733908531746, -89.41412795754178)",First Floor Student Lounge or Lower Level Gym,TRUE,43.07339085,-89.41412796
-62,5,Blessed Sacrament Catholic Church,"2131 Rowley Ave Madison, WI (43.0670416396477, -89.42356184562914)",Lower Level Friary,TRUE,43.06704164,-89.42356185
-63,5,West High School,"30 Ash St Madison, WI (43.069038134552784, -89.42556733375443)",Van Hise Gym Entrance,TRUE,43.06903813,-89.42556733
-64,5,Hoyt School,"3802 Regent St Madison, WI (43.06801290743937, -89.43927300148096)","Room 13, Gym in November",TRUE,43.06801291,-89.439273
-65,13,Wingra School,"718 Gilmore St Madison, WI (43.055116629539214, -89.43382310998345)",First Floor Commons - Enter off rear parking lot,TRUE,43.05511663,-89.43382311
-66,13,St James Catholic School,"1204 St James Ct Madison, WI (43.06588151422994, -89.40580023555043)",Church basement,TRUE,43.06588151,-89.40580024
-67,13,Brittingham Apartments,"755 Braxton Pl Madison, WI (43.06622325660106, -89.3984977406725)",Library,TRUE,43.06622326,-89.39849774
-68,13,Trinity United Methodist Church,"1123 Vilas Ave Madison, WI (43.063111602379706, -89.4050717847067)",Lower Level Fellowship Hall - enter from west lot,TRUE,43.0631116,-89.40507178
-69,13,Bjarne Romnes Apartments,"540 Olin Ave Madison, WI (43.0540399812132, -89.39084379063013)",Community Room - Lower Level,TRUE,43.05403998,-89.39084379
-70,14,Bridge - Lake Point - Waunona Community Center,"1917 Lake Point Dr Madison, WI (43.04819184991362, -89.34713745957919)",Front Room,TRUE,43.04819185,-89.34713746
-71,14,Badger Rock Middle School,"501 Badger Road Madison, WI (43.03825268622941, -89.37899460473784)",Community Room,TRUE,43.03825269,-89.3789946
-72,14,Boys & Girls Club,"2001 Taft St Madison, WI (43.043920070274396, -89.3930028830735)","Second Floor conference room, or gym",TRUE,43.04392007,-89.39300288
-73,14,Village on Park,"2300 Park St Madison, WI (43.040604809545584, -89.39418405899839)",Community Room,TRUE,43.04060481,-89.39418406
-74,14,Leopold Elementary,"2602 Post Rd Madison, WI (43.02656315510893, -89.42174521512993)","Lobby, Gym in November",TRUE,43.02656316,-89.42174522
-75,14,Arbor Gate Center,"2501 Beltline Hwy Madison, WI (43.034753074596786, -89.41964433771305)",Lobby,TRUE,43.03475307,-89.41964434
-76,10,Head Start,"2096 Red Arrow Trl Madison, WI (43.0317432499977, -89.45696266579733)",Multipurpose Room,TRUE,43.03174325,-89.45696267
-77,10,Toki Middle School,"5606 Russett Rd Madison, WI (43.03304032661395, -89.47517968416098)",LMC,TRUE,43.03304033,-89.47517968
-78,10,Thoreau Elementary,"3870 Nakoma Rd Madison, WI (43.04570072789244, -89.44183606926447)",LMC,TRUE,43.04570073,-89.44183607
-79,10,Sequoya Branch Library,"4340 Tokay Boulevard Madison, WI (43.05364815940464, -89.45009305865894)",Large meeting room,TRUE,43.05364816,-89.45009306
-80,11,Midvale Elementary,"502 Caromar Dr Madison, WI (43.057337954951606, -89.44923748956563)",Lobby/front foyer or lower level gym,TRUE,43.05733795,-89.44923749
-81,11,Midvale Elementary,"502 Caromar Dr Madison, WI (43.057337954951606, -89.44923748956563)",Lobby/front foyer or lower level gym,TRUE,43.05733795,-89.44923749
-82,11,Hoyt School,"3802 Regent St Madison, WI (43.06801290743937, -89.43927300148096)","Room 13, Gym in November",TRUE,43.06801291,-89.439273
-83,11,Covenant Presbyterian Church,"326 Segoe Rd Madison, WI (43.070505395164275, -89.4546051546962)",Narthex,TRUE,43.0705054,-89.45460515
-84,11,Hill Farm State Office Building,"4802 Sheboygan Ave Madison, WI (43.07289622605845, -89.46065887933776)",Main Entrance,TRUE,43.07289623,-89.46065888
-85,11,Stephens Elementary,"120 Rosa Rd Madison, WI (43.06936703673043, -89.4782147477172)","Use main entrance, Gym in November",TRUE,43.06936704,-89.47821475
-86,19,Spring Harbor Middle School,"1110 Spring Harbor Dr Madison, WI (43.080783280255446, -89.47144175046907)",Room 7,TRUE,43.08078328,-89.47144175
-87,19,Crestwood Elementary,"5930 Old Sauk Rd Madison, WI (43.07529163808273, -89.48190010439991)",Lounge - Room 102,TRUE,43.07529164,-89.4819001
-88,19,Alicia Ashman Branch Library,"733 High Point Rd Madison, WI (43.07574529728328, -89.51852896407267)",Meeting Room,TRUE,43.0757453,-89.51852896
-89,19,Oakwood Village University Woods,"6205 Mineral Point Rd Madison, WI (43.060714848842935, -89.4876042740839)",Nakoma/Westmorland Rooms,TRUE,43.06071485,-89.48760427
-90,19,Madison Ice Arena,"725 Forward Dr Madison, WI (43.04842567364119, -89.4881781334343)",Conference Room,TRUE,43.04842567,-89.48817813
-91,20,Falk Elementary,"6323 Woodington Way Madison, WI (43.040862375374786, -89.4907591876584)",Front Entrance,TRUE,43.04086238,-89.49075919
-92,20,Our Redeemer Lutheran Church,"1701 McKenna Blvd Madison, WI (43.03373075168247, -89.49682961135619)",Community Room,TRUE,43.03373075,-89.49682961
-93,20,Good Shepherd Lutheran Church,"5701 Raymond Rd Madison, WI (43.03181519576003, -89.47779995721618)",Fellowship Hall - Front Entrance,TRUE,43.0318152,-89.47779996
-94,20,Good Shepherd Lutheran Church,"5701 Raymond Rd Madison, WI (43.03181519576003, -89.47779995721618)",Fellowship Hall - Front Entrance,TRUE,43.0318152,-89.47779996
-95,20,Huegel Elementary,"2601 Prairie Rd Madison, WI (43.02445874156035, -89.48884363173602)",Gym,TRUE,43.02445874,-89.48884363
-96,20,Heritage Congregational Church,"3102 Prairie Rd Madison, WI (43.019350592433966, -89.49308843171428)",Church Narthex,TRUE,43.01935059,-89.49308843
-97,7,St Mary's Care Center,"3401 Maple Grove Rd Madison, WI (43.010847536110305, -89.4988941474842)",Activities Room,TRUE,43.01084754,-89.49889415
-98,7,Chavez Elementary,"3502 Maple Grove Dr Madison, WI (43.01024945349741, -89.49901496388736)","Room 102-C, Gym in November",TRUE,43.01024945,-89.49901496
-99,7,Meriter McKee Clinic,"3102 Meriter Way Madison, WI (43.072950000242486, -89.38668999974357)",Community Room,TRUE,43.07295,-89.38669
-100,7,Meriter McKee Clinic,"3102 Meriter Way Madison, WI (43.072950000242486, -89.38668999974357)",Community Room,TRUE,43.07295,-89.38669
-101,1,West Police District,"1710 McKenna Blvd Madison, WI (43.033937595749194, -89.4969195435591)",Community Room,TRUE,43.0339376,-89.49691954
-102,1,West Police District,"1710 McKenna Blvd Madison, WI (43.033937595749194, -89.4969195435591)",Community Room,TRUE,43.0339376,-89.49691954
-103,1,Our Redeemer Lutheran Church,"1701 McKenna Blvd Madison, WI (43.03373075168247, -89.49682961135619)",Community Room,TRUE,43.03373075,-89.49682961
-104,1,Madison Ice Arena,"725 Forward Dr Madison, WI (43.04842567364119, -89.4881781334343)",Conference Room,TRUE,43.04842567,-89.48817813
-105,1,Coventry Village,"7707 Brookline Dr Madison, WI (43.04950883036192, -89.51793330096713)",Community Room,TRUE,43.04950883,-89.5179333
-106,1,Blackhawk Church,"9620 Brader Way Madison, WI (43.06139389730373, -89.55404447886025)",Meeting Room on second floor,TRUE,43.0613939,-89.55404448
-107,9,Coventry Village,"7707 Brookline Dr Madison, WI (43.04950883036192, -89.51793330096713)",Community Room,TRUE,43.04950883,-89.5179333
-108,9,Lussier Community Education Center,"55 Gammon Rd Madison, WI (43.0690426470485, -89.50249223958838)",Classroom,TRUE,43.06904265,-89.50249224
-109,9,High Point Church,"7702 Old Sauk Rd Madison, WI (43.07505459892877, -89.51692046549073)","Micah Center - Park behind building, side entrance",TRUE,43.0750546,-89.51692047
-110,9,Attic Angel Association,"640 Junction Road Madison, WI (43.07381353452837, -89.52684898069424)",Community Room,TRUE,43.07381353,-89.52684898
-111,9,The Jefferson,"9401 Old Sauk Rd Madison, WI (43.074739836218555, -89.55049215684454)",Activity Room,TRUE,43.07473984,-89.55049216
-112,18,Blackhawk Middle School,"1402 Wyoming Way Madison, WI (43.146137012124655, -89.37093352047447)",LMC,TRUE,43.14613701,-89.37093352
-113,14,Bridge - Lake Point - Waunona Community Center,"1917 Lake Point Dr Madison, WI (43.04819184991362, -89.34713745957919)",Front Room,TRUE,43.04819185,-89.34713746
-114,1,Our Redeemer Lutheran Church,"1701 McKenna Blvd Madison, WI (43.03373075168247, -89.49682961135619)",Community Room,TRUE,43.03373075,-89.49682961
-115,1,Our Redeemer Lutheran Church,"1701 McKenna Blvd Madison, WI (43.03373075168247, -89.49682961135619)",Community Room,TRUE,43.03373075,-89.49682961
-116,1,Our Redeemer Lutheran Church,"1701 McKenna Blvd Madison, WI (43.03373075168247, -89.49682961135619)",Community Room,TRUE,43.03373075,-89.49682961
-117,19,Madison Ice Arena,"725 Forward Drive Madison, WI (43.04842567364119, -89.4881781334343)", ,TRUE,43.04842567,-89.48817813
diff --git a/profiles/dkan/test/phpunit/dkan_harvest/DatajsonHarvestMigrationScenariosTest.php b/profiles/dkan/test/phpunit/dkan_harvest/DatajsonHarvestMigrationScenariosTest.php
index 992d9c0cdfa..ca2de4d8ee4 100644
--- a/profiles/dkan/test/phpunit/dkan_harvest/DatajsonHarvestMigrationScenariosTest.php
+++ b/profiles/dkan/test/phpunit/dkan_harvest/DatajsonHarvestMigrationScenariosTest.php
@@ -595,7 +595,7 @@ public function testResourceAccessUrl() {
}
/**
- * Test harvest source with resouce that does not have the issued field.
+ * Test harvest source with resource that does not have the issued field.
*
* The issued field is not required for the POD dataset. The Harvest should
* fallback to the modified field value (which is required).
@@ -611,7 +611,7 @@ public function testNoIssued() {
}, $migrationMap);
$this->assertEquals(1, count($dest_ids));
$dataset = entity_metadata_wrapper('node', array_pop($dest_ids));
- $this->assertEquals($dataset->field_harvest_source_modified->value(),
+ $this->assertEquals(strtotime($dataset->field_harvest_source_modified->value()),
$dataset->field_harvest_source_issued->value());
}
diff --git a/profiles/dkan/test/phpunit/dkan_harvest/DatajsonHarvestMigrationTest.php b/profiles/dkan/test/phpunit/dkan_harvest/DatajsonHarvestMigrationTest.php
index e5f78cb3246..f4398d87fbc 100644
--- a/profiles/dkan/test/phpunit/dkan_harvest/DatajsonHarvestMigrationTest.php
+++ b/profiles/dkan/test/phpunit/dkan_harvest/DatajsonHarvestMigrationTest.php
@@ -105,7 +105,7 @@ public function testIssued($dataset) {
* @depends testDatasetCount
*/
public function testModified($dataset) {
- $this->assertEquals(strtotime("2016-07-21"), $dataset->field_harvest_source_modified->value());
+ $this->assertEquals("2016-07-21", $dataset->field_harvest_source_modified->value());
}
/**
diff --git a/profiles/dkan/test/phpunit/phpunit.xml b/profiles/dkan/test/phpunit/phpunit.xml
index 122b7c00b71..7d0738d3e81 100644
--- a/profiles/dkan/test/phpunit/phpunit.xml
+++ b/profiles/dkan/test/phpunit/phpunit.xml
@@ -28,6 +28,7 @@
dkan_dataset/ApiTest.php
+ dkan_datastore/DkanDatastoreCSVParserTest.phpdkan_datastore/DkanDatastoreTest.php
diff --git a/profiles/dkan/themes/nuboot_radix/assets/css/nuboot_radix.style.css b/profiles/dkan/themes/nuboot_radix/assets/css/nuboot_radix.style.css
index a23d0f2c297..9c861da3bea 100644
--- a/profiles/dkan/themes/nuboot_radix/assets/css/nuboot_radix.style.css
+++ b/profiles/dkan/themes/nuboot_radix/assets/css/nuboot_radix.style.css
@@ -9591,11 +9591,6 @@ div.horizontal-tabs {
#dkan-dataset-form-settings .help-block {
margin: 5px 20px 10px; }
-#views-exposed-form-dkan-harvest-datasets-harvest-datasets-source-page .form-group {
- display: inline-block; }
- #views-exposed-form-dkan-harvest-datasets-harvest-datasets-source-page .form-group input {
- width: 100px; }
-
.view-dkan-harvest-dashboard .view-header {
margin-bottom: 10px; }
diff --git a/profiles/dkan/themes/nuboot_radix/nuboot_radix.info b/profiles/dkan/themes/nuboot_radix/nuboot_radix.info
index 89e0df63fe6..cb0f2697737 100644
--- a/profiles/dkan/themes/nuboot_radix/nuboot_radix.info
+++ b/profiles/dkan/themes/nuboot_radix/nuboot_radix.info
@@ -55,4 +55,4 @@ settings[toggle_favicon] = 1
settings[toggle_main_menu] = 1
settings[toggle_secondary_menu] = 1
settings[copyright][format] = 'html'
-version = 7.x-1.15.5
+version = 7.x-1.16.1
diff --git a/profiles/dkan/themes/nuboot_radix/scss/components/_harvest.scss b/profiles/dkan/themes/nuboot_radix/scss/components/_harvest.scss
index b77ab1b80f6..68ae25d3a31 100644
--- a/profiles/dkan/themes/nuboot_radix/scss/components/_harvest.scss
+++ b/profiles/dkan/themes/nuboot_radix/scss/components/_harvest.scss
@@ -1,11 +1,3 @@
-#views-exposed-form-dkan-harvest-datasets-harvest-datasets-source-page {
- .form-group {
- display: inline-block;
- input {
- width: 100px;
- }
- }
-}
.view-dkan-harvest-dashboard .view-header {
margin-bottom: 10px;
}
@@ -19,7 +11,7 @@
padding: 4px 5px;
}
}
-
+
h6 {
background: #eee;
color: #222;
@@ -37,7 +29,7 @@ div.datasets-count-preview span {
#harvest_source_summary_uri {
text-overflow: ellipsis;
overflow: hidden;
- white-space: nowrap;
+ white-space: nowrap;
}
// Harvest Errors Page.
diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php
index 09143f57954..7c534a4811b 100644
--- a/sites/default/default.settings.php
+++ b/sites/default/default.settings.php
@@ -478,6 +478,23 @@
*/
# $conf['block_cache_bypass_node_grants'] = TRUE;
+/**
+ * Expiration of cache_form entries:
+ *
+ * Drupal's Form API stores details of forms in cache_form and these entries are
+ * kept for at least 6 hours by default. Expired entries are cleared by cron.
+ * Busy sites can encounter problems with the cache_form table becoming very
+ * large. It's possible to mitigate this by setting a shorter expiration for
+ * cached forms. In some cases it may be desirable to set a longer cache
+ * expiration, for example to prolong cache_form entries for Ajax forms in
+ * cached HTML.
+ *
+ * @see form_set_cache()
+ * @see system_cron()
+ * @see ajax_get_form()
+ */
+# $conf['form_cache_expiration'] = 21600;
+
/**
* String overrides:
*