From 6a8ec3af0f49181420c73e2a282330c83654d4ff Mon Sep 17 00:00:00 2001 From: Janette Day Date: Tue, 8 Jan 2019 21:08:27 -0600 Subject: [PATCH] Upgrade dkan to 1.16.1 (#516) * Upgrade dkan to 1.16.1 * Remove reference to deprecated test files --- .circleci/config.yml | 2 +- build-dkan.make | 2 +- dkan/.github/ISSUE_TEMPLATE.md | 22 + dkan/CHANGELOG.txt | 37 + dkan/dkan.info | 2 +- dkan/dkan.install | 342 +++- .../data_and_content/data_management.rst | 137 +- dkan/docs/admin/people/dkan_workflow.rst | 166 -- dkan/docs/apis/datastore-api.rst | 6 +- dkan/docs/apis/rest-api.rst | 3 +- dkan/docs/components/datastore.rst | 225 +-- dkan/docs/components/permissions.rst | 2 +- dkan/docs/components/workflow.rst | 80 +- .../docs/development/display-num-datasets.rst | 159 ++ dkan/docs/development/index.rst | 1 + dkan/docs/introduction/dkan-sites.rst | 6 +- dkan/docs/releases/notes/1.15.4.md | 2 +- dkan/docs/releases/notes/1.15.5.md | 4 + dkan/docs/releases/notes/1.16.md | 47 + dkan/docs/releases/notes/index.rst | 2 + dkan/drupal-org-core.make | 2 +- 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 + dkan/modules/contrib/autoload/autoload.info | 13 + .../modules/contrib/autoload/autoload.install | 17 + dkan/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 +- 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 +- dkan/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 + 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 +- dkan/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 +- dkan/modules/contrib/honeypot/honeypot.info | 7 +- .../modules/contrib/honeypot/honeypot.install | 2 +- dkan/modules/contrib/honeypot/honeypot.module | 18 +- dkan/modules/contrib/honeypot/honeypot.test | 34 + .../contrib/honeypot/tests/honeypot_test.info | 7 +- dkan/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 +- dkan/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 +- dkan/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 +- dkan/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 +- dkan/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 +- dkan/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 dkan/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 +- dkan/modules/contrib/uuid/uuid.features.inc | 1 + 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 +- dkan/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 + dkan/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 + dkan/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 + dkan/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/dkan_dataset/dkan_dataset.module | 99 +- ...aset_content_types.features.field_base.inc | 50 +- ..._content_types.features.field_instance.inc | 21 +- ...an_dataset_content_types.license_field.inc | 16 +- .../dkan_dataset_content_types.module | 43 +- .../dkan_dataset_rest_api.info | 4 +- .../dkan_dataset_rest_api.module | 10 - .../dkan_dataset_rest_api.services.inc | 34 + dkan/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 | 12 +- .../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 | 1 + .../dkan_datastore_api.module | 556 ++--- .../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 | 5 + .../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.module | 6 +- .../resource/Polling_Places_Madison_0.csv | 118 -- .../dkan_harvest.features.field_base.inc | 6 +- .../dkan_harvest/dkan_harvest.migrate.inc | 19 +- .../css/dkan_harvest_dashboard.css | 9 + .../dkan_harvest_dashboard.module | 1 - .../dkan_harvest_dashboard.views_default.inc | 271 +-- .../dkan_harvest_datajson.migrate.inc | 42 +- .../dkan_harvest_datajson.module | 8 +- dkan/modules/dkan/dkan_migrate_base/README.md | 8 +- ...permissions.features.roles_permissions.inc | 3 - .../dkan/dkan_sitewide/dkan_sitewide.info | 1 - ...dkan_workflow.features.workbench_email.inc | 240 ++- .../dkan/dkan_workflow/dkan_workflow.info | 1 + .../dkan/dkan_workflow/dkan_workflow.module | 32 +- .../open_data_schema_map_dkan.features.inc | 16 +- .../DKANExtension/Context/DatasetContext.php | 3 + dkan/test/features/dataset.author.feature | 2 +- 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 +- dkan/test/features/resource.author.feature | 127 +- dkan/test/features/resource.editor.feature | 63 +- 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 +- dkan/test/phpunit/phpunit.xml | 1 + .../assets/css/nuboot_radix.style.css | 5 - .../scss/components/_harvest.scss | 12 +- 498 files changed, 25473 insertions(+), 6992 deletions(-) create mode 100644 dkan/.github/ISSUE_TEMPLATE.md delete mode 100644 dkan/docs/admin/people/dkan_workflow.rst create mode 100644 dkan/docs/development/display-num-datasets.rst create mode 100644 dkan/docs/releases/notes/1.15.5.md create mode 100644 dkan/docs/releases/notes/1.16.md create mode 100644 dkan/modules/contrib/autoload/README.md create mode 100644 dkan/modules/contrib/autoload/autoload.api.php create mode 100644 dkan/modules/contrib/autoload/autoload.cache.inc create mode 100644 dkan/modules/contrib/autoload/autoload.drush.inc create mode 100644 dkan/modules/contrib/autoload/autoload.info create mode 100644 dkan/modules/contrib/autoload/autoload.install create mode 100644 dkan/modules/contrib/autoload/autoload.module create mode 100644 dkan/modules/contrib/autoload/src/Tests/Unit/AutoloadTestBase.php create mode 100644 dkan/modules/contrib/autoload/src/Tests/Unit/CacheTest.php create mode 100644 dkan/modules/contrib/autoload/src/Tests/Unit/CustomTest.php create mode 100644 dkan/modules/contrib/autoload/src/Tests/Unit/DrupalTest.php create mode 100644 dkan/modules/contrib/autoload/src/Tests/Unit/EntityTest.php create mode 100644 dkan/modules/contrib/autoload/src/Tests/Unit/ExtensionsTest.php create mode 100644 dkan/modules/contrib/autoload/tests/autoload.php create mode 100644 dkan/modules/contrib/autoload/tests/autoload_test_custom/autoload_test_custom.info create mode 100644 dkan/modules/contrib/autoload/tests/autoload_test_custom/autoload_test_custom.module create mode 100644 dkan/modules/contrib/autoload/tests/autoload_test_custom/psr-0/Autoload/Tests/PSR0.inc create mode 100644 dkan/modules/contrib/autoload/tests/autoload_test_custom/psr-4-single-level-namespace/PSR4.inc create mode 100644 dkan/modules/contrib/autoload/tests/autoload_test_custom/psr-4/PSR4.inc create mode 100644 dkan/modules/contrib/autoload/tests/autoload_test_custom/tests/Test.inc create mode 100644 dkan/modules/contrib/autoload/tests/autoload_test_custom/wrong-namespace/WrongNamespace.php create mode 100644 dkan/modules/contrib/autoload/tests/autoload_test_drupal/autoload_test_drupal.info create mode 100644 dkan/modules/contrib/autoload/tests/autoload_test_drupal/autoload_test_drupal.module create mode 100644 dkan/modules/contrib/autoload/tests/autoload_test_drupal/lib/Drupal/autoload_test_drupal/PSR0.php create mode 100644 dkan/modules/contrib/autoload/tests/autoload_test_drupal/src/PSR4.inc create mode 100644 dkan/modules/contrib/autoload/tests/autoload_test_entity/autoload_test_entity.info create mode 100644 dkan/modules/contrib/autoload/tests/autoload_test_entity/autoload_test_entity.install create mode 100644 dkan/modules/contrib/autoload/tests/autoload_test_entity/autoload_test_entity.module create mode 100644 dkan/modules/contrib/autoload/tests/autoload_test_entity/modules/autoload_test_entity_ui/autoload_test_entity_ui.info create mode 100644 dkan/modules/contrib/autoload/tests/autoload_test_entity/modules/autoload_test_entity_ui/autoload_test_entity_ui.module create mode 100644 dkan/modules/contrib/autoload/tests/autoload_test_entity/modules/autoload_test_entity_ui/src/ViewsController.php create mode 100644 dkan/modules/contrib/autoload/tests/autoload_test_extensions/autoload_test_extensions.info create mode 100644 dkan/modules/contrib/autoload/tests/autoload_test_extensions/autoload_test_extensions.module create mode 100644 dkan/modules/contrib/autoload/tests/autoload_test_extensions/src/PSR4.test create mode 100644 dkan/modules/contrib/autoload/tests/autoload_test_lookup/autoload_test_lookup.info create mode 100644 dkan/modules/contrib/autoload/tests/autoload_test_lookup/autoload_test_lookup.module create mode 100644 dkan/modules/contrib/file_entity/README.txt create mode 100644 dkan/modules/contrib/libraries/css/libraries.admin.css create mode 100644 dkan/modules/contrib/libraries/libraries.theme.inc rename dkan/modules/contrib/menu_block/{menu-block-background-display-options.png => css/display-options-background.png} (100%) rename dkan/modules/contrib/menu_block/{ => css}/menu-block.admin.css (96%) rename dkan/modules/contrib/menu_block/{ => js}/menu-block.js (95%) create mode 100644 dkan/modules/contrib/recline/composer.json delete mode 100644 dkan/modules/contrib/recline/lib/highlight/highlight.pack.js delete mode 100644 dkan/modules/contrib/recline/lib/highlight/styles/default.css create mode 100644 dkan/modules/contrib/search_api/contrib/search_api_views/includes/handler_filter_numeric.inc create mode 100644 dkan/modules/contrib/search_api/contrib/search_api_views/includes/plugin_content_cache.inc delete mode 100644 dkan/modules/contrib/search_api_db/PATCHES.txt delete mode 100644 dkan/modules/contrib/tablefield/themeless/README.txt delete mode 100644 dkan/modules/contrib/tablefield/themeless/tablefield_themeless.info delete mode 100644 dkan/modules/contrib/tablefield/themeless/tablefield_themeless.module mode change 100755 => 100644 dkan/modules/contrib/taxonomy_menu/LICENSE.txt delete mode 100644 dkan/modules/contrib/taxonomy_menu/UPGRADE.txt create mode 100644 dkan/modules/contrib/uuid/uuid_services/uuid_services.install create mode 100644 dkan/modules/contrib/workbench_moderation/PATCHES.txt create mode 100644 dkan/modules/contrib/xautoload/.travis.yml rename dkan/modules/contrib/{date => xautoload}/LICENSE.txt (100%) create mode 100644 dkan/modules/contrib/xautoload/README.md create mode 100644 dkan/modules/contrib/xautoload/legacy/lib/FinderPlugin/CheckIncludePath.php create mode 100644 dkan/modules/contrib/xautoload/legacy/lib/FinderPlugin/Interface.php create mode 100644 dkan/modules/contrib/xautoload/legacy/lib/InjectedAPI/hookXautoload.php create mode 100644 dkan/modules/contrib/xautoload/lib/Drupal/xautoload/Tests/EnvironmentSnapshotMaker.php create mode 100644 dkan/modules/contrib/xautoload/lib/Drupal/xautoload/Tests/XAutoloadUnitTestCase.php create mode 100644 dkan/modules/contrib/xautoload/lib/Drupal/xautoload/Tests/XAutoloadWebTestCase.php create mode 100644 dkan/modules/contrib/xautoload/phpunit.xml.dist create mode 100644 dkan/modules/contrib/xautoload/src/Adapter/ClassFinderAdapter.php create mode 100644 dkan/modules/contrib/xautoload/src/Adapter/ClassFinderAdapterInterface.php create mode 100644 dkan/modules/contrib/xautoload/src/Adapter/DrupalExtensionAdapter.php create mode 100644 dkan/modules/contrib/xautoload/src/Adapter/LocalDirectoryAdapter.php create mode 100644 dkan/modules/contrib/xautoload/src/CacheManager/CacheManager.php create mode 100644 dkan/modules/contrib/xautoload/src/CacheManager/CacheManagerObserverInterface.php create mode 100644 dkan/modules/contrib/xautoload/src/CacheMissObserver/CacheMissLoaderSetFinder.php create mode 100644 dkan/modules/contrib/xautoload/src/CacheMissObserver/CacheMissObserverInterface.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassFinder/ClassFinder.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassFinder/ClassFinderInterface.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassFinder/CommonRegistrationInterface.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassFinder/ExtendedClassFinderInterface.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassFinder/GenericPrefixMap.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/AbstractInjectedApi.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/CollectFilesInjectedApi.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/FindFileInjectedApi.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/InjectedApiInterface.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/LoadClassGetFileInjectedApi.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/LoadClassInjectedAPI.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/DrupalCoreRegistryPlugin.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/DrupalExtensionNamespaceFinderPlugin.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/DrupalExtensionUnderscoreFinderPlugin.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/FinderPluginInterface.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/Psr4FinderPlugin.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassFinder/ProxyClassFinder.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassLoader/AbstractCachedClassLoader.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassLoader/AbstractClassLoader.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassLoader/AbstractClassLoaderDecorator.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassLoader/AbstractQueuedCachedClassLoader.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassLoader/ApcClassLoader.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassLoader/ApcuClassLoader.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassLoader/ApcuQueuedCachedClassLoader.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassLoader/CacheNotSupportedException.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassLoader/ClassLoaderInterface.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassLoader/DbCacheClassLoader.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassLoader/WinCacheClassLoader.php create mode 100644 dkan/modules/contrib/xautoload/src/ClassLoader/XCacheClassLoader.php create mode 100644 dkan/modules/contrib/xautoload/src/DIC/ServiceContainer.php create mode 100644 dkan/modules/contrib/xautoload/src/DIC/ServiceContainerInterface.php create mode 100644 dkan/modules/contrib/xautoload/src/DIC/ServiceFactory.php create mode 100644 dkan/modules/contrib/xautoload/src/DirectoryBehavior/DefaultDirectoryBehavior.php create mode 100644 dkan/modules/contrib/xautoload/src/DirectoryBehavior/DirectoryBehaviorInterface.php create mode 100644 dkan/modules/contrib/xautoload/src/DirectoryBehavior/Psr0DirectoryBehavior.php create mode 100644 dkan/modules/contrib/xautoload/src/Discovery/CachedClassMapGenerator.php create mode 100644 dkan/modules/contrib/xautoload/src/Discovery/ClassMapGenerator.php create mode 100644 dkan/modules/contrib/xautoload/src/Discovery/ClassMapGeneratorInterface.php create mode 100644 dkan/modules/contrib/xautoload/src/Discovery/ComposerDir.php create mode 100644 dkan/modules/contrib/xautoload/src/Discovery/ComposerJson.php create mode 100644 dkan/modules/contrib/xautoload/src/Discovery/ComposerJsonTargetDir.php create mode 100644 dkan/modules/contrib/xautoload/src/Discovery/FileInspector.php create mode 100644 dkan/modules/contrib/xautoload/src/Discovery/WildcardFileFinder.php create mode 100644 dkan/modules/contrib/xautoload/src/DrupalSystem/DrupalSystem.php create mode 100644 dkan/modules/contrib/xautoload/src/DrupalSystem/DrupalSystemInterface.php create mode 100644 dkan/modules/contrib/xautoload/src/Libraries/LibrariesFinderPlugin.php create mode 100644 dkan/modules/contrib/xautoload/src/Libraries/LibrariesInfoAlter.php create mode 100644 dkan/modules/contrib/xautoload/src/Libraries/LibrariesOnInit.php create mode 100644 dkan/modules/contrib/xautoload/src/Libraries/LibrariesPreLoadCallback.php create mode 100644 dkan/modules/contrib/xautoload/src/Libraries/LibraryCacheMissObserver.php create mode 100644 dkan/modules/contrib/xautoload/src/Libraries/SerializableClosureWrapper.php create mode 100644 dkan/modules/contrib/xautoload/src/Main.php create mode 100644 dkan/modules/contrib/xautoload/src/Phases/DrupalCoreRegistryRegistrator.php create mode 100644 dkan/modules/contrib/xautoload/src/Phases/DrupalPhaseControl.php create mode 100644 dkan/modules/contrib/xautoload/src/Phases/ExtensionNamespaces.php create mode 100644 dkan/modules/contrib/xautoload/src/Phases/HookXautoload.php create mode 100644 dkan/modules/contrib/xautoload/src/Phases/HookXautoloadEarly.php create mode 100644 dkan/modules/contrib/xautoload/src/Phases/PhaseObserverInterface.php create mode 100644 dkan/modules/contrib/xautoload/src/Util.php create mode 100644 dkan/modules/contrib/xautoload/tests/bootstrap.php create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/.libraries/ComposerTargetDirTestLib/Foo.php create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/.libraries/ComposerTargetDirTestLib/composer.json create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/.libraries/ComposerTestLib/composer.json create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/.libraries/ComposerTestLib/other/Foo.php create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/.libraries/ComposerTestLib/psr4/Foo.php create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/.libraries/testlib/src/Foo.php create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/.modules/libraries/libraries.module create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/.modules/system/system.module create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod/psr4/Foo.php create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod/testmod.install create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod/testmod.module create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_pearflat/lib/Foo.php create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_pearflat/testmod_pearflat.install create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_pearflat/testmod_pearflat.module create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_psr0_lib/lib/Drupal/testmod_psr0_lib/Foo.php create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_psr0_lib/testmod_psr0_lib.install create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_psr0_lib/testmod_psr0_lib.module create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_psr4_custom/psr4/Foo.php create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_psr4_custom/testmod_psr4_custom.install create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_psr4_custom/testmod_psr4_custom.module create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_psr4_src/src/Foo.php create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_psr4_src/testmod_psr4_src.install create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_psr4_src/testmod_psr4_src.module create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/handlers/bar.inc create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/handlers/bar.php create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/handlers/foo.inc create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/handlers/sub/foo.inc create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/misc/abc create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/misc/foo.bar create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/misc/sub/xyz create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/modules/foo.inc create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/modules/sub/foo.inc create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/modules/sub/sub/foo.inc create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/modules/sub/sub/sub/foo.inc create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/tests/foo.inc create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/tests/foo.test create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/tests/sub/foo.test create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/tests/sub/sub/foo.test create mode 100644 dkan/modules/contrib/xautoload/tests/fixtures/WildcardFileFinder/tests/sub/sub/sub/foo.test create mode 100644 dkan/modules/contrib/xautoload/tests/scripts/drupal-apc.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/BasicIntegrityTest.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/ClassFinderAdapterTest.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/ClassLoaderTest.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/ComposerJsonTest.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/DiscoveryTest.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/DrupalBootTest/AbstractDrupalBootTest.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/DrupalBootTest/DrupalBootHookTest.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/DrupalBootTest/DrupalBootTest.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/Example/AbstractExampleModules.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/Example/ExampleModules.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/Example/HookTestExampleModules.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/Filesystem/StreamWrapper.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/Filesystem/VirtualFilesystem.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/Mock/MockDrupalSystem.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/Util/CallLog.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/Util/HackyLog.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/Util/StaticCallLog.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/Cache.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalBootstrap.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalComponentContainer.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalEnvironment.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalGetFilename.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalLoad.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalStatic.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ExampleModulesInterface.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/HookSystem.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/LibrariesInfo.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/LibrariesLoad.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleBuildDependencies.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleEnable.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleImplements.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleList.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/PureFunctions.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemBuildModuleData.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemList.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemListLoader.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemListReset.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemRebuildModuleData.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemTable.php create mode 100644 dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemUpdateBootstrapStatus.php create mode 100644 dkan/modules/contrib/xautoload/tests/test_1/lib/Drupal/xautoload_test_1/ExampleClass.php create mode 100644 dkan/modules/contrib/xautoload/tests/test_1/xautoload_test_1.info create mode 100644 dkan/modules/contrib/xautoload/tests/test_1/xautoload_test_1.module create mode 100644 dkan/modules/contrib/xautoload/tests/test_2/lib/ExampleClass.php create mode 100644 dkan/modules/contrib/xautoload/tests/test_2/xautoload_test_2.info create mode 100644 dkan/modules/contrib/xautoload/tests/test_2/xautoload_test_2.module create mode 100644 dkan/modules/contrib/xautoload/tests/test_3/lib/ExampleClass.php create mode 100644 dkan/modules/contrib/xautoload/tests/test_3/xautoload_test_3.info create mode 100644 dkan/modules/contrib/xautoload/tests/test_3/xautoload_test_3.module create mode 100644 dkan/modules/contrib/xautoload/tests/test_4/testlib/src/TestClass.php create mode 100644 dkan/modules/contrib/xautoload/tests/test_4/xautoload_test_4.info create mode 100644 dkan/modules/contrib/xautoload/tests/test_4/xautoload_test_4.install create mode 100644 dkan/modules/contrib/xautoload/tests/test_4/xautoload_test_4.module create mode 100644 dkan/modules/contrib/xautoload/tests/test_5/src/FooNamespace/Foo_Class.php create mode 100644 dkan/modules/contrib/xautoload/tests/test_5/xautoload_test_5.info create mode 100644 dkan/modules/contrib/xautoload/tests/test_5/xautoload_test_5.install create mode 100644 dkan/modules/contrib/xautoload/tests/test_5/xautoload_test_5.module create mode 100644 dkan/modules/contrib/xautoload/xautoload.api.php create mode 100644 dkan/modules/contrib/xautoload/xautoload.early.inc create mode 100644 dkan/modules/contrib/xautoload/xautoload.early.lib.inc create mode 100644 dkan/modules/contrib/xautoload/xautoload.emulate.inc create mode 100644 dkan/modules/contrib/xautoload/xautoload.info create mode 100644 dkan/modules/contrib/xautoload/xautoload.install create mode 100644 dkan/modules/contrib/xautoload/xautoload.libraries.inc create mode 100644 dkan/modules/contrib/xautoload/xautoload.module create mode 100644 dkan/modules/contrib/xautoload/xautoload.system.inc create mode 100644 dkan/modules/contrib/xautoload/xautoload.ui.inc create mode 100644 dkan/modules/dkan/dkan_datastore/dkan_datastore.drush.inc delete mode 100644 dkan/modules/dkan/dkan_datastore/dkan_datastore.feeds_importer_default.inc delete mode 100644 dkan/modules/dkan/dkan_datastore/includes/Datastore.inc delete mode 100644 dkan/modules/dkan/dkan_datastore/includes/DkanDatastore.inc delete mode 100644 dkan/modules/dkan/dkan_datastore/includes/DkanDatastoreFastImport.inc delete mode 100644 dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_api/dkan_datastore_api.drush.inc delete mode 100644 dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/dkan_datastore_fast_import.js create mode 100644 dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/src/FastImport.php create mode 100644 dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_simple_import/dkan_datastore_simple_import.info create mode 100644 dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_simple_import/dkan_datastore_simple_import.module create mode 100644 dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_simple_import/src/SimpleImport.php create mode 100644 dkan/modules/dkan/dkan_datastore/resources/dkan_datastore_resource.inc create mode 100644 dkan/modules/dkan/dkan_datastore/src/LockableDrupalVariables.php create mode 100644 dkan/modules/dkan/dkan_datastore/src/Manager/Factory.php create mode 100644 dkan/modules/dkan/dkan_datastore/src/Manager/Info.php create mode 100644 dkan/modules/dkan/dkan_datastore/src/Manager/Manager.php create mode 100644 dkan/modules/dkan/dkan_datastore/src/Manager/ManagerInterface.php create mode 100644 dkan/modules/dkan/dkan_datastore/src/Page/Component/ManagerConfiguration.php create mode 100644 dkan/modules/dkan/dkan_datastore/src/Page/Component/ManagerSelection.php create mode 100644 dkan/modules/dkan/dkan_datastore/src/Page/Component/Status.php create mode 100644 dkan/modules/dkan/dkan_datastore/src/Page/Page.php create mode 100644 dkan/modules/dkan/dkan_datastore/src/Parser/Base.php create mode 100644 dkan/modules/dkan/dkan_datastore/src/Parser/Csv.php create mode 100644 dkan/modules/dkan/dkan_datastore/src/Parser/CsvBase.php create mode 100644 dkan/modules/dkan/dkan_datastore/src/Parser/Field.php create mode 100644 dkan/modules/dkan/dkan_datastore/src/Parser/QuotedField.php create mode 100644 dkan/modules/dkan/dkan_datastore/src/Resource.php create mode 100644 dkan/modules/dkan/dkan_datastore/src/StateMachine.php delete mode 100644 dkan/modules/dkan/dkan_fixtures/modules/dkan_default_content/data/files/resource/Polling_Places_Madison_0.csv create mode 100644 dkan/test/features/datastore.pages.access.feature delete mode 100644 dkan/test/features/datastore_fast_import.feature rename dkan/test/features/{page.editor.feature => page.sm.feature} (91%) create mode 100644 dkan/test/phpunit/dkan_datastore/DkanDatastoreCsvParserTest.php delete mode 100644 dkan/test/phpunit/dkan_datastore_fast_import/DkanDatastoreFastImportTest.php delete mode 100644 dkan/test/phpunit/dkan_datastore_fast_import/files/null_check.csv delete mode 100644 dkan/test/phpunit/dkan_datastore_fast_import/files/polling_places.csv diff --git a/.circleci/config.yml b/.circleci/config.yml index 6c38de47e..5742128ac 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -45,7 +45,7 @@ jobs: name: Remove empty test files command: | # These files have no tests after the flags we apply. - FILES="dataset.admin.feature panels.feature leaflet_draw_widget.feature page.editor.feature resource.editor.feature theme.admin.feature dataset.author.feature datastore_fast_import.feature widgets.feature resource.admin.feature dataset.editor.feature user.site_manager.feature datastore.feature topics.feature dataset_rest_api.feature resource.author.feature user.author.feature" + FILES="dataset.admin.feature panels.feature leaflet_draw_widget.feature resource.editor.feature theme.admin.feature dataset.author.feature widgets.feature resource.admin.feature dataset.editor.feature user.site_manager.feature datastore.feature topics.feature dataset_rest_api.feature resource.author.feature user.author.feature" for file in $FILES do rm dkan/test/features/$file diff --git a/build-dkan.make b/build-dkan.make index 847ed97a9..fa4513a14 100644 --- a/build-dkan.make +++ b/build-dkan.make @@ -8,4 +8,4 @@ projects: download: type: git url: https://github.com/GetDKAN/dkan.git - tag: 7.x-1.15.5 + tag: 7.x-1.16.1 diff --git a/dkan/.github/ISSUE_TEMPLATE.md b/dkan/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..ca5b736ae --- /dev/null +++ b/dkan/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,22 @@ +Add a brief description about the purpose of the issue. + +## Steps to Reproduce +Add steps for reproducing the issue including any file(s) needed to reproduce. + +1. +2. +3. + +## Acceptance Criteria +Add the goals and desired results that this issue should accomplish when complete. + +- [ ] +- [ ] +- [ ] + +## Subtasks +Add a list of things that need to be done to accomplish the Acceptance Criteria. + +- [ ] Is there a test for the issue +- [ ] Is there documentation for the issue +- [ ] Are follow up tickets needed diff --git a/dkan/CHANGELOG.txt b/dkan/CHANGELOG.txt index c1d371d57..5d702c444 100644 --- a/dkan/CHANGELOG.txt +++ b/dkan/CHANGELOG.txt @@ -1,3 +1,40 @@ +7.x-1.16.1 +---------- + - #2778 Add check for partial date elements when running harvest + - #2787 Update rest-api documentation + - #2788 Update contrib modules: diff, field_hidden, file_entity, honeypot, libraries, menu_block, migrate, panopoly_images, panopoly_widgets, search_api, tablefield, taxonomy_menu + - #2784 Update harvest node machine name field settings to limit length and replace single hyphen + - #2785 Fix harvest support for dataset identifiers that end with a '/' + - #2782 Fix dataset and resource orphan field update on post-harvest import operations + - #2781 Use dkan_allowed_extensions() during harvest + - #2760 Remove content page create/edit permissions from the editor role + - #2775 Add check for mysql reserved words in column names when importing data to the datastore + - #2777 Add check for SelectQuery object when running dkan_datastore_api_debug + - #2776 Update recline revision + - #2767 Update workbench email notification text + - #2774 Update the license options uri values + - #2773 Fix Only variables should be passed by reference errors in dkan_datastore_api + - #2772 Fix CartoDB preview error + - #2763 Update dkan_environment key from 'stage' to 'test' + - #2762 Update Drupal to 7.61 + +7.x-1.16 +---------- + - #2179 Refactor Datastore Importer + - #2627 Add support for ISO-8106 duration values in "modified" when harvesting + - #2729 Update recline to 2.1 + - #2713 CSV Parser Improvements: trailing commas & escape chars + - #2740 Fix dkan_workflow anonymous access to revisions and unpublished content + - #2724 Update contrib modules: better_exposed_filters, file_entity, media, panopoly_widgets, search_api_db, uuid, date: fixes issue 2843367 + - #2718 Update odsm_dkan token values for remote file + - #2707 DKAN workflow documentation updates + - #2723 Include dkan.profile to prevent undefined function in update + +7.x-1.15.5 +---------- + - #2710 Replace data stories page view with a non-search index version + - #2712 Remove unused xss code + 7.x-1.15.4 ---------- - #2701 Update drupal core to 7.60 diff --git a/dkan/dkan.info b/dkan/dkan.info index 83a710130..b5bccc502 100644 --- a/dkan/dkan.info +++ b/dkan/dkan.info @@ -122,7 +122,7 @@ dependencies[] = dkan_harvest_datajson dependencies[] = dkan_sitewide dependencies[] = dkan_dataset dependencies[] = dkan_datastore +dependencies[] = dkan_datastore_simple_import dependencies[] = dkan_datastore_api dependencies[] = open_data_schema_map_dkan dependencies[] = visualization_entity_charts_dkan -version = 7.x-1.15.5 diff --git a/dkan/dkan.install b/dkan/dkan.install index 22950eb43..af0ee3527 100644 --- a/dkan/dkan.install +++ b/dkan/dkan.install @@ -5,6 +5,11 @@ * Install functions and update hooks for DKAN profile. */ +use Dkan\Datastore\Manager\Factory; +use Dkan\Datastore\Resource; +use Dkan\Datastore\Manager\ManagerInterface; +use Dkan\Datastore\LockableDrupalVariables; + /** * Reverts dkan_sitewide to add Markdown filter. Sets up roleassign feature. */ @@ -411,6 +416,7 @@ function dkan_update_7023() { * Update default variables. */ function dkan_update_7024(&$context) { + require_once('dkan.profile'); dkan_misc_variables_set($context); } @@ -457,11 +463,11 @@ function dkan_update_7027() { // Delete the entries for non-harvested datasets. if (!in_array($nid, $harvested_nids)) { db_delete('field_data_field_harvest_source_modified') - ->condition('entity_id', $nid) - ->execute(); + ->condition('entity_id', $nid) + ->execute(); db_delete('field_data_field_harvest_source_issued') - ->condition('entity_id', $nid) - ->execute(); + ->condition('entity_id', $nid) + ->execute(); } $count = $count + 1; } @@ -478,3 +484,331 @@ function dkan_update_7028() { views_delete_view($view); } } + +/** + * Change field_harvest_source_modified from a datetime to text field. See #2612. + */ +function dkan_update_7029() { + features_revert_module('dkan_dataset_content_types'); + + // Add format column to harvest_source_modified tables. + $tables = array( + 'field_data_field_harvest_source_modified', + 'field_revision_field_harvest_source_modified' + ); + $spec = array( + 'type' => 'varchar', + 'length' => 100, + 'not null' => FALSE, + 'default' => '', + ); + foreach ($tables as $table) { + db_add_field($table, 'field_harvest_source_modified_format', $spec); + db_change_field($table, 'field_harvest_source_modified_value', 'field_harvest_source_modified_value', $spec); + + // Convert dates to strings. + $result = db_select($table, 'm') + ->fields('m', array( + 'field_harvest_source_modified_value', + 'revision_id' + )) + ->execute() + ->fetchAll(); + foreach($result as $row) { + $string =substr($row->field_harvest_source_modified_value, 0, 10); + db_update($table) + ->fields(array( + 'field_harvest_source_modified_value' => $string + )) + ->condition('revision_id', $row->revision_id) + ->execute(); + } + } + + // Fetch the current harvest_source_modified field_config, then update it. + $field_config = db_select('field_config', 'f') + ->fields('f', array('data')) + ->condition('field_name', 'field_harvest_source_modified') + ->execute() + ->fetchCol(); + $data = unserialize($field_config[0]); + + $data['indexes']['format'][0] = 'format'; + $data['settings'] = array(); + $data['settings'] = array('max_length' => '255'); + $data['foreign keys']['format']['table'] = 'filter_format'; + $data['foreign keys']['format']['columns']['format'] = 'format'; + + $data = serialize($data); + + // Update with new field configuration. + db_update('field_config') + ->fields(array( + 'data' => $data, + 'type' => 'text', + 'module' => 'text' + )) + ->condition('field_name', 'field_harvest_source_modified') + ->execute(); + + + // Fetch the current harvest_source_modified field_config_instance, then update it. + $field_config = db_select('field_config_instance', 'f') + ->fields('f', array('data')) + ->condition('field_name', 'field_harvest_source_modified') + ->execute() + ->fetchCol(); + $data = unserialize($field_config[0]); + + $data['default_value'] = NULL; + $data['settings'] = array(); + $data['settings']= array('text_processing' => 0, 'user_register_form' => FALSE); + $data['widget']['module'] = 'text'; + $data['widget']['settings'] = array(); + $data['widget']['settings'] = array('size' => 60); + $data['widget']['type'] = 'text_textfield'; + + $data = serialize($data); + + // Update with new field configuration. + db_update('field_config_instance') + ->fields(array( + 'data' => $data, + )) + ->condition('field_name', 'field_harvest_source_modified') + ->execute(); + +} + +/** + * Update field_frequency_value to store ISO code rather than integers. + */ +function dkan_update_7030() { + // Change frequency field_config from list_int to list_text. + $field_config = db_select('field_config', 'f') + ->fields('f', array('data')) + ->condition('field_name', 'field_frequency') + ->execute() + ->fetchCol(); + $data = unserialize($field_config[0]); + + $data['settings']['allowed_values'] = array(); + $data['settings']['allowed_values_function'] = 'dkan_dataset_content_types_iso_frecuency_map'; + + $data = serialize($data); + + db_update('field_config') + ->fields(array( + 'type' => 'list_text', + 'data' => $data, + )) + ->condition('field_name', 'field_frequency') + ->execute(); + + // Change field_frequency_value type from int to varchar. + $tables = array( + 'field_data_field_frequency', + 'field_revision_field_frequency' + ); + $spec = array( + 'type' => 'varchar', + 'length' => 15, + 'not null' => FALSE, + 'default' => '', + ); + foreach ($tables as $table) { + db_change_field($table, 'field_frequency_value', 'field_frequency_value', $spec); + + // Convert integers to strings. + $result = db_select($table, 'm') + ->fields('m', array( + 'field_frequency_value', + 'revision_id' + )) + ->execute() + ->fetchAll(); + foreach($result as $row) { + // Convert integer values to ISO 8601 value. + switch ($row->field_frequency_value) { + case '0': + $val = 'R/P1D'; + break; + + case '1': + $val = 'R/P1W'; + break; + + case '2': + $val = 'R/P1M'; + break; + + case '3': + $val = 'R/P1Y'; + break; + + case '4': + $val = 'R/PT1S'; + break; + + case '5': + $val = 'irregular'; + break; + + case '6': + $val = 'R/P10Y'; + break; + + case '7': + $val = 'R/P4Y'; + break; + + case '8': + $val = 'R/P2M'; + break; + + case '9': + $val = 'R/P3.5D'; + break; + + case '10': + $val = 'R/P2W'; + break; + + case '11': + $val = 'R/P6M'; + break; + + case '12': + $val = 'R/P2Y'; + break; + + case '13': + $val = 'R/P3Y'; + break; + + case '14': + $val = 'R/P0.33W'; + break; + + case '15': + $val = 'R/P0.33M'; + break; + + case '16': + $val = 'R/P3M'; + break; + + case '17': + $val = 'R/P4M'; + break; + + case '18': + $val = 'R/P0.5M'; + break; + + default: + $val = ''; + break; + } + db_update($table) + ->fields(array( + 'field_frequency_value' => $val, + )) + ->condition('revision_id', $row->revision_id) + ->execute(); + } + } +} + +/** + * Enable the simple import module. + */ +function dkan_update_7031() { + module_enable(array('dkan_datastore_simple_import')); +} + +function _dkan_remove_prefix($prefix, $string) { + if (substr($string, 0, strlen($prefix)) == $prefix) { + $string = substr($string, strlen($prefix)); + } + return $string; +} + +/** + * Rename feeds_datastore tables. + */ +function dkan_update_7032() { + global $databases; + + $prefix = ""; + + if (isset($databases['default']['default']['prefix'])) { + $prefix = $databases['default']['default']['prefix']; + } + + // I have seen both of these patterns used, I am not sure if it is related + // to a version of feeds or not, so we are checking both. + $possibilities = [ + "%feeds_datastore_dkan_%", + "%feeds_datastore_%", + ]; + + foreach ($possibilities as $string) { + foreach (db_find_tables($string) as $table_name) { + + // db_find_tables returns full table names with a prefix if + // configured. The rest of db functions will work without + // explicitely setting the prefix. We need to remove + // the prefix for db_rename_table to work correctly. + $table_name = _dkan_remove_prefix($prefix, $table_name); + + $clean = str_replace("%", "", $string); + + $new_table_name = str_replace("{$clean}file", "dkan_datastore", $table_name); + $new_table_name = str_replace("{$clean}link", "dkan_datastore", $new_table_name); + + db_rename_table($table_name, $new_table_name); + } + } +} + +/** + * Generate basic datastore configuration. + */ +function dkan_update_7033() { + global $databases; + + $prefix = ""; + + if (isset($databases['default']['default']['prefix'])) { + $prefix = $databases['default']['default']['prefix']; + } + + foreach (db_find_tables("%dkan_datastore%") as $table_name) { + $table_name = _dkan_remove_prefix($prefix, $table_name); + $nid = str_replace("dkan_datastore_", "", $table_name); + try { + $resource = Resource::createFromDrupalNodeNid($nid); + + /* @var $manager \Dkan\Datastore\Manager\SimpleImport\SimpleImport */ + $manager = (new Factory($resource))->get(); + $manager->saveState(); + } + catch( \Exception $e) {} + } +} + +/** + * Update the datastore configuration. + */ +function dkan_update_7034() { + $storage = new LockableDrupalVariables("dkan_datastore"); + $bin = $storage->borrowBin(); + + foreach ($bin as $nid => $state) { + $bin[$nid]['data_import'] = ManagerInterface::DATA_IMPORT_DONE; + $bin[$nid]['storage'] = ManagerInterface::STORAGE_INITIALIZED; + } + + $storage->returnBin($bin); +} diff --git a/dkan/docs/admin/data_and_content/data_management.rst b/dkan/docs/admin/data_and_content/data_management.rst index da1a640fd..14efedf36 100644 --- a/dkan/docs/admin/data_and_content/data_management.rst +++ b/dkan/docs/admin/data_and_content/data_management.rst @@ -2,59 +2,59 @@ Data Management =============== -DKAN supports broader data management strategies with tools that simplify and streamline data migration, storage, and usability. The tools are designed to be straightforward so that you don’t need to be a technical expert to use them. Migrate open data efficiently and effectively, store your data, and improve the overall usability of your data. +DKAN supports broader data management strategies with tools that simplify and streamline data migration, storage, and usability. The tools are designed to be straightforward so that you don’t need to be a technical expert to use them. Migrate open data efficiently and effectively, store your data, and improve the overall usability of your data. Harvest -------------- -As you build up your DKAN site with content and data, you can add datasets by uploading or linking to resources and APIs. You can also “harvest” datasets from other data portals that have a public data.json or an XML endpoint. All the datasets published in the source are added to your DKAN site as the Dataset content type. +As you build up your DKAN site with content and data, you can add datasets by uploading or linking to resources and APIs. You can also “harvest” datasets from other data portals that have a public data.json or an XML endpoint. All the datasets published in the source are added to your DKAN site as the Dataset content type. -Unlike linking to a file hosted on the web, harvested datasets are fully imported from an external source onto your DKAN site. That means that the datasets exist both on the external source and your site independently from one another with all the same information (including title, metadata, tags, etc.). +Unlike linking to a file hosted on the web, harvested datasets are fully imported from an external source onto your DKAN site. That means that the datasets exist both on the external source and your site independently from one another with all the same information (including title, metadata, tags, etc.). -By importing datasets from external sources, you can provide more open data on your site without manually managing the dataset content. Site visitors will see that a dataset was harvested and from which portal, promoting visibility across agencies and sectors. Harvest optimizes importing, publishing and updating imported datasets into a single streamlined process. +By importing datasets from external sources, you can provide more open data on your site without manually managing the dataset content. Site visitors will see that a dataset was harvested and from which portal, promoting visibility across agencies and sectors. Harvest optimizes importing, publishing and updating imported datasets into a single streamlined process. How Harvest Works ~~~~~~~~~~~~~~~~~~~~~~~ -The source is defined (fetching) - First, identify where the datasets should be imported from. Harvest is compatible with data.json and XML endpoints, and your source may be configured before import. By default, all the datasets and their metadata are imported from a source, but you can further configure exceptions to narrow what information is included (more on that in the Harvest Dashboard section). Site Managers define a source by creating a Harvest Source as a new piece of content. +The source is defined (fetching) + First, identify where the datasets should be imported from. Harvest is compatible with data.json and XML endpoints, and your source may be configured before import. By default, all the datasets and their metadata are imported from a source, but you can further configure exceptions to narrow what information is included (more on that in the Harvest Dashboard section). Site Managers define a source by creating a Harvest Source as a new piece of content. -Data is stored locally (caching) as a copy - Once the source is identified, Harvest pulls datasets and stores them on the computer’s local hard drive. This is called caching. Site Managers have two options for how cached data is handled. Datasets may be cached and migrated (step 3) automatically or they may be only cached to be reviewed and later migrated manually. You may use a different operation depending on the context and can choose on a case-by-case basis. +Data is stored locally (caching) as a copy + Once the source is identified, Harvest pulls datasets and stores them on the computer’s local hard drive. This is called caching. Site Managers have two options for how cached data is handled. Datasets may be cached and migrated (step 3) automatically or they may be only cached to be reviewed and later migrated manually. You may use a different operation depending on the context and can choose on a case-by-case basis. -Harvest adds cached data to your site (migration and publishing) - After the dataset information is cached onto the computer’s local hard drive as a JSON file, Harvest reads the file and imports it to your DKAN site. Datasets can be migrated and published to your DKAN site automatically as part of the caching operation. Alternatively, datasets can be only migrated (without caching). +Harvest adds cached data to your site (migration and publishing) + After the dataset information is cached onto the computer’s local hard drive as a JSON file, Harvest reads the file and imports it to your DKAN site. Datasets can be migrated and published to your DKAN site automatically as part of the caching operation. Alternatively, datasets can be only migrated (without caching). Harvest is used to check for changes to migrated datasets - Once the dataset information is imported to your DKAN site, its contents exist independently from the original source. That means changes made to a dataset on the original source won’t appear on your DKAN site unless the harvested dataset is updated. It also means that if you make changes to the dataset on your DKAN site, the changes will be overwritten when you run a harvest operation to update the file contents and metadata. + Once the dataset information is imported to your DKAN site, its contents exist independently from the original source. That means changes made to a dataset on the original source won’t appear on your DKAN site unless the harvested dataset is updated. It also means that if you make changes to the dataset on your DKAN site, the changes will be overwritten when you run a harvest operation to update the file contents and metadata. -With Harvest, you can make updates to your harvested datasets by repeating the process of fetching, caching and migrating. Harvest replaces the old information with the current datasets, updating the information to include any changes made to the original source. With defined sources, the process is a quick operation. +With Harvest, you can make updates to your harvested datasets by repeating the process of fetching, caching and migrating. Harvest replaces the old information with the current datasets, updating the information to include any changes made to the original source. With defined sources, the process is a quick operation. Though most of Harvest works in the background, Site Managers can use the Harvest Dashboard to manage Harvest operations. Harvest Dashboard ~~~~~~~~~~~~~~~~~ -Harvest Sources have special handling. The Harvest Dashboard displays all the Sources on a site and a comprehensive list of harvested datasets. +Harvest Sources have special handling. The Harvest Dashboard displays all the Sources on a site and a comprehensive list of harvested datasets. -From here, Site Managers can view Harvest Sources, Source metadata such as date last updated and number of datasets in the Source, view harvested datasets individually, filter and search Harvest content, and perform bulk operations on Harvest content. +From here, Site Managers can view Harvest Sources, Source metadata such as date last updated and number of datasets in the Source, view harvested datasets individually, filter and search Harvest content, and perform bulk operations on Harvest content. -The Harvest Dashboard is also used to perform Harvest operations, edit and configure a Source, check the status of a Source, and manage the datasets in a Source. +The Harvest Dashboard is also used to perform Harvest operations, edit and configure a Source, check the status of a Source, and manage the datasets in a Source. Harvest operations ~~~~~~~~~~~~~~~~~~ -Site Managers use special operations on the Harvest Dashboard to manage the harvesting process for existing Sources. From the Sources tab, Sources may be cached, migrated, harvested (cached and migrated) or deleted. +Site Managers use special operations on the Harvest Dashboard to manage the harvesting process for existing Sources. From the Sources tab, Sources may be cached, migrated, harvested (cached and migrated) or deleted. Cache Source(s): - This operation parses the Source endpoint (either data.json or XML) stores a subset of the Source data (reflecting parameters set by the Source configuration) on a computer. Caching data from the Source endpoint pulls the latest data, so the datasets on your DKAN site are current. + This operation parses the Source endpoint (either data.json or XML) stores a subset of the Source data (reflecting parameters set by the Source configuration) on a computer. Caching data from the Source endpoint pulls the latest data, so the datasets on your DKAN site are current. Migrate Source(s): - Migrating a Source imports cached data from local computer storage and uploads files as content to your site. For existing Sources, the new data will replace what was previously published or create a new dataset if it wasn’t previously published to your site. + Migrating a Source imports cached data from local computer storage and uploads files as content to your site. For existing Sources, the new data will replace what was previously published or create a new dataset if it wasn’t previously published to your site. Harvest Source(s): - The harvest operation combines the cache and migrate operations for a single, streamlined process. This option automates some of the work that would otherwise be done manually, but it also removes the ability to review datasets before migrating to your site. + The harvest operation combines the cache and migrate operations for a single, streamlined process. This option automates some of the work that would otherwise be done manually, but it also removes the ability to review datasets before migrating to your site. Delete Source(s): By deleting a Source, all the datasets associated with the defined endpoint are removed from your DKAN site. This is a permanent change. @@ -64,130 +64,69 @@ Edit and configure a Source The edit view of a Source opens the same options available when first adding a new Harvest Source. -Here, you can change the basic information about a Source, like the title and the URI. The basic information of a Source doesn’t typically change once it’s set. +Here, you can change the basic information about a Source, like the title and the URI. The basic information of a Source doesn’t typically change once it’s set. -In this same view, Site Managers configure harvests with filters, excludes, overrides, and defaults. With these options, Site Managers can customize what information is pulled from a Source and how metadata values are handled during the harvesting process. +In this same view, Site Managers configure harvests with filters, excludes, overrides, and defaults. With these options, Site Managers can customize what information is pulled from a Source and how metadata values are handled during the harvesting process. Filters: - Filters restrict which datasets imported by setting a pair of key values. For instance, if you are harvesting from a data.json endpoint and want to harvest only health-related datasets, you might add a filter with "keyword" in the first text box, and "health" in the second. With this configuration, only datasets that meet the stated criteria are imported. + Filters restrict which datasets imported by setting a pair of key values. For instance, if you are harvesting from a data.json endpoint and want to harvest only health-related datasets, you might add a filter with "keyword" in the first text box, and "health" in the second. With this configuration, only datasets that meet the stated criteria are imported. Excludes: - Excludes are the inverse of filters. Values in this field determine which datasets are left out of an import, while all other datasets are included. For example, if there is one publisher included in a Source whose datasets you do not want to bring onto your site, you might add "publisher" in the first text box and "Office of Public Affairs" in the second. + Excludes are the inverse of filters. Values in this field determine which datasets are left out of an import, while all other datasets are included. For example, if there is one publisher included in a Source whose datasets you do not want to bring onto your site, you might add "publisher" in the first text box and "Office of Public Affairs" in the second. Overrides: - Values included in the Overrides field will replace metadata values from the Source as it’s migrated in a harvest. For example, to change the name of the publisher, you might add "publisher" in the first text box to be replaced by the value in the second text box, like your own agency’s name. + Values included in the Overrides field will replace metadata values from the Source as it’s migrated in a harvest. For example, to change the name of the publisher, you might add "publisher" in the first text box to be replaced by the value in the second text box, like your own agency’s name. Defaults: - In some cases, datasets from a Source may not have all metadata fields filled with a value. Use defaults to replace an empty field. For example, the first box might designate the License metadata value to be replaced if empty. The second box designates which value should replace it, like “Creative Commons”. + In some cases, datasets from a Source may not have all metadata fields filled with a value. Use defaults to replace an empty field. For example, the first box might designate the License metadata value to be replaced if empty. The second box designates which value should replace it, like “Creative Commons”. Check the status of a Harvest Source ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -As Sources go through the harvesting process, Harvest captures the details and displays the results. After a Harvest Source is created and the datasets harvested are published to your DKAN site, the original source may change. Datasets may be added, removed, edited, and otherwise modified. These changes are reflected in a Harvest Source when a harvest operation is performed as part of the status of that Harvest Source. +As Sources go through the harvesting process, Harvest captures the details and displays the results. After a Harvest Source is created and the datasets harvested are published to your DKAN site, the original source may change. Datasets may be added, removed, edited, and otherwise modified. These changes are reflected in a Harvest Source when a harvest operation is performed as part of the status of that Harvest Source. -There are two places to find specific details about a harvest operation on the Harvest Dashboard: the Events tab and the Errors tab. +There are two places to find specific details about a harvest operation on the Harvest Dashboard: the Events tab and the Errors tab. Events: - Each Harvest Source has an event log under the Events tab. When a Source is harvested, the process is recorded as an event. Sources are updated by running the harvest operation, so there may be several events recorded and detailed in this log. The event log is helpful for checking harvest events and getting the status breakdown on the most recent harvest, the number of new datasets created, datasets updated, datasets that failed to upload, datasets that have become “orphaned” on your site, and unchanged datasets. + Each Harvest Source has an event log under the Events tab. When a Source is harvested, the process is recorded as an event. Sources are updated by running the harvest operation, so there may be several events recorded and detailed in this log. The event log is helpful for checking harvest events and getting the status breakdown on the most recent harvest, the number of new datasets created, datasets updated, datasets that failed to upload, datasets that have become “orphaned” on your site, and unchanged datasets. Errors: - Harvest Sources have an error log under the Errors tab to display the details of when a harvest encounters and error with the Source or a dataset in the Source. Error messages appear individually with the time and date it occurred as well as a message for the likely cause of an error. Details in the error log help identify the specifics of an error and find the best solution. + Harvest Sources have an error log under the Errors tab to display the details of when a harvest encounters and error with the Source or a dataset in the Source. Error messages appear individually with the time and date it occurred as well as a message for the likely cause of an error. Details in the error log help identify the specifics of an error and find the best solution. Manage Harvest Source datasets ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Though harvested datasets appear alongside directly-published Datasets on your DKAN site, it’s best practice for Site Managers to manage harvested datasets with the Harvest Dashboard. The Harvest Dashboard provides more specific information like when a dataset was updated from a harvest, its “orphan” status, and its Harvest Source. +Though harvested datasets appear alongside directly-published Datasets on your DKAN site, it’s best practice for Site Managers to manage harvested datasets with the Harvest Dashboard. The Harvest Dashboard provides more specific information like when a dataset was updated from a harvest, its “orphan” status, and its Harvest Source. -Site Managers can either permanently delete or unpublish (recommended) harvested datasets. +Site Managers can either permanently delete or unpublish (recommended) harvested datasets. Managing orphan datasets ~~~~~~~~~~~~~~~~~~~~~~~~ -After a Source is harvested, the datasets belonging to the source may change and may be deleted all together. When a dataset is deleted from the Source, but remains published to your DKAN site, the dataset is considered an orphan. +After a Source is harvested, the datasets belonging to the source may change and may be deleted all together. When a dataset is deleted from the Source, but remains published to your DKAN site, the dataset is considered an orphan. -Because the Source no longer contains the dataset, it isn’t updated as part of a harvest operation. But it isn’t deleted from your DKAN site automatically. Site Managers must make a judgment call on whether to delete the dataset and stay aligned with the Harvest Source, to unpublish the dataset and hide from public view, or to keep the dataset as a stand-alone dataset that won’t be updated through a harvest operation. +Because the Source no longer contains the dataset, it isn’t updated as part of a harvest operation. But it isn’t deleted from your DKAN site automatically. Site Managers must make a judgment call on whether to delete the dataset and stay aligned with the Harvest Source, to unpublish the dataset and hide from public view, or to keep the dataset as a stand-alone dataset that won’t be updated through a harvest operation. -Visit the Adding Content section to learn how to add a Harvest Source. +Visit the Adding Content section to learn how to add a Harvest Source. Datastore --------- -DKAN comes standard with a Datastore to house tabular data imported from your CSV files on DKAN. That is, the Datastore can support files with contents that appear as a table (rows and columns). You can think of the Datastore like a basic database. Files that are imported to the Datastore have the contents of the file copied into a table in the Datastore, and the Datastore as a whole is composed of all the tables copied from imported files on DKAN.The Datastore processes data, stores the contents of Resources (if CSV), and makes them ready to be queried. +DKAN comes standard with a Datastore to house tabular data imported from your CSV files on DKAN. That is, the Datastore can support files with contents that appear as a table (rows and columns). You can think of the Datastore like a basic database. Files that are imported to the Datastore have the contents of the file copied into a table in the Datastore, and the Datastore as a whole is composed of all the tables copied from imported files on DKAN.The Datastore processes data, stores the contents of Resources (if CSV), and makes them ready to be queried. As a Site Manager, you can manage the Datastore by adding and removing files from the Datastore. In most cases you want all CSV files included in the Datastore to support better data previewing, large files, and a more robust API. -Managing the Datastore +Managing the Datastore ~~~~~~~~~~~~~~~~~~~~~~ In broad strokes, managing the DKAN Datastore is deciding which Resources to include in the Datastore. There isn't any management further than that, and every user has the ability to import and remove Resources they've authored. As a Site Manager, you can import or remove any Resource regardless of the author. This allows you to manage what data is included in the Datastore API. -There may be some sensitive data that should not be included in the Datastore, but in general we recommend increasing your transparency and usability of your data by importing every Resource possible into your Datastore. +There may be some sensitive data that should not be included in the Datastore, but in general we recommend increasing your transparency and usability of your data by importing every Resource possible into your Datastore. Importing and removing files ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Uploading files to the Datastore has major benefits including enhancing the Datastore API and improved user experience of previewing data. The Datastore API makes the Resources more usable and accessible to technical users. Previews display resources as graphs, grids, or maps for geospatial data. In some cases files contain thousands (or millions) of rows. For data on such high order, users can only properly preview the data if the Resource has been imported into the DKAN Datastore. -.. figure:: ../../images/site_manager_playbook/data_management/resource_page_with_datastore_message.png - :alt: Your file for this resource is not added to the datastore. - - Above is what you should see on a resource page if your Resource file has not yet been added to the datastore. +:doc:`Read more on managing the datastore here <../../components/datastore>` -.. figure:: ../../images/site_manager_playbook/data_management/manage_datastore_page.png - :alt: manage datastore page - - Above is what you should see when you select Manage Datastore. - -**When to import:** - -- The file is formatted as a CSV. -- The file is very large (and formatted as a CSV). -- To include the file in the Datastore API. - -Importing -+++++++++ - -Once a Resource is added, you can import the file into the Datastore. Click the Manage Datastore button at the top of the screen to open the options included for importing a file into the Datastore. From the import screen, you can select the options that best reflect the contents of your data. By default, DKAN is set to match common data formatting practices. - -In the example below, the Site Manager is importing a Resource into the Datastore from the Manage Datastore page. From the Import tab, the Site Manager selects the delimeter and leaves the rest of the default settings for import. After clicking the Import button at the bottom of the page, the contents of the Resource are now included in the DKAN Datastore as well as the Datastore API. - -.. image:: ../../images/site_manager_playbook/data_management/datastore_import_animation.gif - :alt: animation of datastore import - -Removing -++++++++ - -If you need to remove a Resource from the Datastore you’ll make this change directly from editing the Resource on the Manage Datastore screen. You have two options for removing the contents from the Datastore: - -Delete items - By deleting items, the values are removed from the Datastore. However, the table itself generated by the file import still exists. In this sense, you can think of the file as imported to the Datastore but empty. Again, the CSV file on your DKAN site is separate from the Datastore. By deleting the values in the Datastore, your file won’t be changed. - -Drop Datastore - This option removes both the values in the file as well as the table generated upon import. In this sense, the file is not imported. If you don’t want the contents of a file in the Datastore, we recommend this option. - -.. image:: ../../images/site_manager_playbook/data_management/datastore_actions.png - :alt: delete and drop options - -Updating -++++++++ - -When you import a Resource to the Datastore, the contents of the associate file are copied and added to the Datastore. If the file is changed, the Datastore is not automatically updated with the contents of the new file. If you update a file associated with your Resource, you will also need to update the Datastore by importing the file again. All the old data will be overwritten with the contents of the new file. - -Using Fast Import -~~~~~~~~~~~~~~~~~ - -For datasets with thousands (or millions) of rows, the size of the file increases quickly. By default, DKAN processes the contents of a file before importing to the Datastore. The processing checks that the contents match delimiter, file encoding, etc. for a smooth import. - -Large file sizes can delay the time it takes to import a file into the Datastore as more information needs to be processed before it can be imported. - -.. image:: ../../images/site_manager_playbook/data_management/datastore_import_options.png - :alt: datastore options with fast import - -With the Fast Import option, the time to upload a large file is dramatically reduced. The contents of a file are not processed and directly imported to the Datastore, so files that otherwise might take hours to import only take minutes. - -For smaller files, we recommend using the standard processor and import. When large files must be imported, check the Use Fast Import checkbox to skip the processing and directly import. - -Special Note: This capability is not enabled by default and requires the DKAN Datastore Fast Import module to be enabled. Please contact your site administrator or developer for assistance. - -.. image:: ../../images/site_manager_playbook/data_management/datastore_fast_import_option.png - :alt: datastore import options close up diff --git a/dkan/docs/admin/people/dkan_workflow.rst b/dkan/docs/admin/people/dkan_workflow.rst deleted file mode 100644 index 2f7130401..000000000 --- a/dkan/docs/admin/people/dkan_workflow.rst +++ /dev/null @@ -1,166 +0,0 @@ -.. _`user-docs dkan workflow`: - -============= -DKAN Workflow -============= - -DKAN Workflow is an advanced feature that opens up additional functions to manage an editorial review process for the content on a DKAN site. It also adds a new set of user roles and permissions to distribute the work associated with a review process. - -While there are usually only one or two Site Managers maintaining the entire site, content can be added by dozens of different users. In some cases the amount of content that needs review and management may be on an order that cannot be done by just one or two people. DKAN Workflow helps ensure quality content by introducing a review process, and it distributes the workload with unique Workflow roles and permissions. - -Special Note: This feature is not enabled by default and requires the DKAN Workflow module to be enabled. If you are interested in getting DKAN Workflow, please talk to your site administrator or developer. - -Workflow and the Editorial Process ----------------------------------- - -Data portals can house thousands of files in the form of Resources organized into Datasets, and these Datasets and Resources may originate from a variety of departments and organizations. While there may be one or two Site Managers maintaining the entire site, content may be added by dozens of different users. This empowers agencies to add their data as it becomes available and allows for updating the data portal to be a sustainable endeavor. - -On the other hand, broad contributions add a complicating factor for Site Managers. Contributors from different Groups can add data to the site, but these users may be unfamiliar with open data standards and less knowledgeable about handling data in general. - -DKAN Workflow introduces an editorial process to ensure quality control at any scale. Workflow creates a moderation queue so that content is published only after a designated supervisor has reviewed and approved it. Contributors can still add content to the data catalog, but it is up to a supervisor to act as the gatekeeper in making the content public on the live site. - - -Workflow Roles and Permissions ------------------------------- - -Each user on a DKAN site will have a role (or multiple roles) and have certain permissions for moving content through Workflow. These roles allow users to interact differently with My Workbench, but they do not negate the need for core user roles. This means that every user must be assigned a core role granting a certain level of access to the site, independent of the user's Workflow role. Read about :ref:`user management in the Site Manager Playbook` for additional details. - -Core roles/permissions and Workflow roles/permissions serve different purposes but complement one another. Each set of roles has different permissions that enable the user to interact with certain functions on DKAN. As Workflow roles are assigned, the core role equivalent is automatically selected so that there are no gaps in a user’s permissions. - -There are three roles with Workflow-specific permissions: Workflow Contributor, Workflow Moderator, Workflow Supervisor. - -Workflow Contributor -~~~~~~~~~~~~~~~~~~~~ - -The Workflow Contributor role has the lowest level of permissions while still gaining access to My Workbench. These users add content to the data catalog, but their content needs approval before it is published and made live. Workflow Contributors can save content as a Draft or move it to Needs Review, but they do not have the power to publish content live. - -Workflow Contributors will be assigned the core role of Content Creator. - -Workflow Moderator -~~~~~~~~~~~~~~~~~~ - -The Workflow Moderator reviews the bulk of content that has been added by Workflow Contributors for a single Group and moves it through the publishing pipeline. This role reviews and publishes content--including what they have created themselves--for their Group, rather than the entire site. Read more about how Groups work in the `Group Roles and Permissions section`. - -Moderators can also unpublish content, leaving it in a Needs Review or Draft state and removing it from public view, or delete content altogether. Workflow Moderators will be assigned the core role of Editor. - -Workflow Moderators ensure that data uploaded to the site does not have any sensitive or private information included within it. They also check whether the file format is listed correctly, that the Resource follows open data best practices, and that it is associated with the correct licenses. Lastly, the Workflow Moderator should look over the Dataset or Resource’s metadata to ensure accuracy and completeness. - -Workflow Supervisor -~~~~~~~~~~~~~~~~~~~ - -The Workflow Supervisor role has the highest-level permissions within Workflow because users with this role are not restricted to Group to which they belong. - -Unlike a Moderator, Supervisors can access content from all Groups and moderate content as needed. However, the primary focus of the Supervisor is overseeing My Workbench for the entire site. In general, this is an administrative role rather than a practical one. Supervisors catch any issues that could otherwise fall through the cracks, especially in the case of content that isn’t associated with a specific Group. - -Workflow Supervisors will be assigned the core role of Site Managers. - -My Workbench ------------------- - -My Workbench provides additional content management via a system of “states”. A piece of content could be in either a “draft” state or a “needs review” state before ultimately being published. “Transitions” define in which order content can move from state to state, and who has permissions to do so. As content is drafted, it goes through an editorial workflow managed by trusted roles. - -Content exists in three states: - -Draft - A saved work in progress. -Needs Review - The author feels the content is ready to go on public on the live site, and would like the supervisor to review it. -Published - The content is public and visible on the live site. - -My Workbench stores unpublished content in the Draft and Needs Review states, while Workflow roles give certain users the ability to moderate content through the editorial workflow. Users can view the state of the content as well as its age. - -DKAN Workflow organizes content into five different tabs: My Content, My Drafts, Needs Review, Stale Drafts, and Stale Reviews. - -The Stale Drafts and Stale Reviews tabs contain content that has gone untouched for too long. The default time limit is 72 hours before drafts become stale. - -.. image:: ../../images/site_manager_playbook/workflow/my_workbench.png - :alt: my workbench view - -For Workflow Moderators reviewing a steady stream of content it’s helpful to know how many pieces of content need to be moderated. In the picture above, note that each tab has a bubble with a number located in the top right corner. This number reflects the total pieces of content within that tab. - -For example, a Workflow Moderator may have two drafts and 10 pieces of content in the Needs Review tab. Two of those drafts may have gone stale and would also appear in the Stale Drafts tab. Three of the reviews may also be stale and would appear both in the Needs Review tab as well as the Stale Reviews tab. The quantities of content within each category will appear as a count at the top of each tab. - -Workflow Roles and Permissions At-a-Glance ------------------------------------------- - -Users assigned a DKAN Workflow role are automatically assigned the corresponding level of core DKAN role. The following is the relationship between the roles. - -.. list-table:: - :stub-columns: 1 - - * - Core Role - - Content Creator - - Editor - - Site Manager - * - Workflow Role - - Workflow Contributor - - Workflow Moderator - - Workflow Supervisor - -Overview of workflow permissions: - -+---------------+-------------------------------------+---------------------------------------------------------------------------------------------------+ -| Tab Name | Role (Users that can view the tab) | Tab Function | -+===============+=====================================+===================================================================================================+ -| My Content | All Workflow Roles | All of the content that a user has authored, in any publishing stage. | -+---------------+-------------------------------------+---------------------------------------------------------------------------------------------------+ -| My Drafts | All Workflow Roles | All of the user's own drafts. | -+---------------+-------------------------------------+---------------------------------------------------------------------------------------------------+ -| Needs Review | All Workflow Roles | For Workflow Contributors, this will be content that they have moved to the Needs Review state. | -| | | | -| | | Workflow Moderators see Needs Review content for their specific Group. | -| | | | -| | | Workflow Supervisors see Needs Review content for the entire site. | -+---------------+-------------------------------------+---------------------------------------------------------------------------------------------------+ -| Stale Drafts | Workflow Moderators and Supervisors | All drafts that are more than 72 hours old. | -+---------------+-------------------------------------+---------------------------------------------------------------------------------------------------+ -| Stale Reviews | Workflow Moderators and Supervisors | All Needs Review content that has been in that state for more than 72 hours. | -+---------------+-------------------------------------+---------------------------------------------------------------------------------------------------+ - -Using Workflow --------------- - -Add Content -~~~~~~~~~~~ - -Adding content with Workflow enabled is similar to the general process for adding content. - -1. From the **Admin Menu** hover over the **Add Content** menu link. -2. From the drop-down menu, select the content type to add. By default, only Resources and Datasets may be moderated as part of Workflow. -3. Fill out the details of the content type. -4. At the bottom of the page, click the **Publishing options** menu item. -5. In this menu, users can change the state of the content. Workflow Supervisors and Moderators can directly publish content, but Contributors may only save content in the Draft or Needs Review states. -6. Choose the state of the Content and click the **Save** button. - -If the content was saved in a Draft state or moved to the Needs Review state, it will appear in the user's My Workbench. Users can draft content to come back to or move it to the review phase by changing the moderation state at any time. - -Content may cycle back and forth between draft and review as it goes through the revision process. - -Moderate Content -~~~~~~~~~~~~~~~~ - -All users moderate content in some capacity. Workflow Contributors moderate their content between the Draft state and the Needs Review state. Workflow Moderators are responsible for publishing their own content as well as content created by Workflow Contributors. - -All content in the Workflow pipeline is accessed in My Workbench. From My Workbench users can see at-a-glance a summary of content and the state it's in. - -1. From the Admin Menu click the My Workbench link. -2. Click on one of the tabs to see all the content in that publishing state. -3. Workflow Contributors can moderate from this page by clicking the Submit for Review button to send a draft to a Workflow Moderator to review. -4. Workflow Moderators can moderate content from this page by clicking the Reject or Publish buttons on a piece of content. - -.. image:: ../../images/site_manager_playbook/workflow/my_drafts.png - :alt: my drafts view - -Review content and make changes: - -1. From My Workbench, navigate to tab with publishing state of the content. -2. Click on the title link of the piece of content. -3. Click the **Edit Draft** button to make changes directly to the content. -4. At the bottom of the page, moderate the publishing state. -5. Workflow Moderators may change the state to draft for revisions or directly publish the content. - -Click the **Moderate** button to see the full revision history and change the publishing state. - -.. image:: ../../images/site_manager_playbook/workflow/moderate.png - :alt: moderate view diff --git a/dkan/docs/apis/datastore-api.rst b/dkan/docs/apis/datastore-api.rst index b49aaad7f..0915b44eb 100644 --- a/dkan/docs/apis/datastore-api.rst +++ b/dkan/docs/apis/datastore-api.rst @@ -69,7 +69,7 @@ above syntax, it also accepts an alternative format: :: - ...&sort=field1,field2 desc + ...&sort[field1]=desc Multiple queries ---------------- @@ -242,7 +242,7 @@ Simple query example :: - http://EXAMPLE.COM/api/dataset/search?resource_id=d3c099c6-1340-4ee5-b030-8faf22b4b424&filters[country]=AR,US&fields=country,population,timestamp&sort[country]=asc + http://EXAMPLE.COM/api/dataset/search?resource_id=d3c099c6-1340-4ee5-b030-8faf22b4b424&filters[country]=AR,US&fields[]=country&fields[]=population,timestamp&sort[country]=asc Returns the country, population, and timestamp fields for US and AR from dataset 1 sorting by the country in ascending order. @@ -253,7 +253,7 @@ Text Search Requests with the 'query' argument will search the listed fields within the dataset:: - http://example.com/api/dataset/search?resource_id=d3c099c6-1340-4ee5-b030-8faf22b4b424&&fields=country,population&query=US + http://example.com/api/dataset/search?resource_id=d3c099c6-1340-4ee5-b030-8faf22b4b424&&fields[]=country&fields[]=population&query=US This will return the country and population from US. diff --git a/dkan/docs/apis/rest-api.rst b/dkan/docs/apis/rest-api.rst index 6d0ef0cdc..6ab2c0450 100644 --- a/dkan/docs/apis/rest-api.rst +++ b/dkan/docs/apis/rest-api.rst @@ -259,7 +259,7 @@ Request { "type": "resource", - "field_dataset_ref": {"und": {"target_id": "75"}}, + "field_dataset_ref": {"und": [{"target_id": "75"}]}, "title": "Test Resource" } @@ -704,3 +704,4 @@ Known issues * Datasets and other content nodes can only be queried via node id or other entity. UUID support pending. * There is currently no way to request a previous revision of a dataset or resource. * Upon attaching a file to a resource via the API, DKAN will immediately import this file to the Datastore if it is a valid CSV. This may not always be the desired behavior; more control over datastore behavior should be available to API clients. + * Greater-than (>) and lesser-than (<) operations are not currently supported in queries. Equals (=) however should always work. diff --git a/dkan/docs/components/datastore.rst b/dkan/docs/components/datastore.rst index 8c162259a..182664ffd 100644 --- a/dkan/docs/components/datastore.rst +++ b/dkan/docs/components/datastore.rst @@ -1,176 +1,90 @@ -DKAN Datastore +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, allowing users to query them through a public API. +When you create a dataset with resources, DKAN is reading the data directly from the resource file or API. -**Drupal Architecture** +The Datastore component provides an option for you to parse **CSV** or **TSV** files and save the data into database tables. This allows users to query the data through a public API. -The DKAN Datastore's importer is a wrapper around the `Feeds `_ module. The custom `Feeds Flatstore Processor `_ and `Feeds Field Fetcher `_ plugins were created the file uploaded to the resource form a feed item. +So by adding your CSV resources to the datastore, you are getting the fullest functionality possible out of your datasets. -The `Data `_ module is used to manage datastore tables' schema. - -The Datastore API uses the `Services `_ module to provide an endpoint, although nearly all the underlying functionality is overridden and provided directly by the `DKAN Datastore API `_ module. - -Getting Started ----------------- - -When you create a dataset with resources, you have data in DKAN which you can display and store in several ways. However, DKAN is still reading this data directly from the file or API you added as a resource. - -To get the fullest functionality possible out of your datasets, you should add your CSV resources to the datastore. - -If you are exploring a resource that is not yet in the datastore, you will see a message advising you of this. - -.. image:: ../images/datastore-message.png - -Click the "Manage Datastore" button at the top of the screen. On the "Manage Datastore" page, confirm that the delimiter and file encoding options are correct, then use the "Import" button at the bottom of the page to import the data from your file or API into DKAN's local datastore. - -.. image:: ../images/datastore-resource.png - -Your data is now ready to use via the API! Click the "Data API" button at the top of the resource screen for specific instructions. - -TAB delimiter support ---------------------- - -DKAN supports TAB delimiters for csv files and other file extensions that commonly use TABs as delimiters. The autodetect format function is available for this file types (the format detected will be TSV) and the recline previews will work. - -The TAB delimiter support has been introduced to the datastore import functionality, so if your resource contains a csv file separated by TABs and you visit the "Manage Datastore" tab, you'll have an option in the 'Delimiter' dropdown to select TAB. Once you select that option and press the 'Import' button, your resource will be imported and should be shown as expected in the resource preview. - -Processing Options +Importing Resources ------------------- -By default Resource files are added to the DKAN Datastore manually. This can be changed to: - -* Import upon form submission -* Import in the background -* Import periodically - -Changing Default Datastore Import Behavior -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Default behavior for linked and uploaded files is controlled through the `Feeds module `_. To access the Feeds administrative interface, enable the **Feeds Admin UI** module (which is included but not enabled by default in DKAN). Once turned on you can access the Feeds UI at ``/admin/structure/feeds``. You should see two Feeds Importers by default: - -.. image:: ../images/datastore-feeds-importers.png - -Import on submission -^^^^^^^^^^^^^^^^^^^^^^ -To import a Resource file upon saving the resource, click **Import on submission** in the settings section for each importer: - -.. image:: ../images/datastore-import-submission.png - -This is not recommended for large imports as a batch screen will be triggered that will not stop until the entire file is imported. - -Process in background -^^^^^^^^^^^^^^^^^^^^^^^ -This setting means that once an import has started, it will be processed in 50 row increments in the background. Processing will occur during cron. The queue of imports is managed by the `Job Schedule `_ module. Each cron run will `process a maximum of 200 jobs in a maximum of 30 seconds `_. Note that an import won't be started by saving the Resource form. This will only be triggered by clicking "Import" on the "Manage Datastore" page or if triggered programatically. This setting can be used in addition to "Import on submission" option to start imports that will be imported in the background. - -Periodic import -^^^^^^^^^^^^^^^^^^^^^^^ -Importing items on a periodic basis makes the most sense if you have a file you are linking to that you want to periodically re-import. This setting requires that cron is running on a regular schedule. - -Geocoder ---------- - -DKAN's native Datastore can use the Drupal 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 :doc:`Data Preview ` or other map-based data visualizations. It is not included by default with DKAN but can be `downloaded here `_. - -Instructions -^^^^^^^^^^^^^ -1. Install and enabling the **geocoder** module. -2. Click the **Manage Datastore** tab on any resource with address information. -3. Check the "Geolocate" box. -4. Select the Geolocation Service you will be using. -5. In the Geolocate Addressses field enter the field or fields from the file that make up the address to geolocate. -6. Click the **Import** button - -.. image:: ../images/datastore-geolocate.png - -Geolocation Services -^^^^^^^^^^^^^^^^^^^^^ -Geolocation services offered are - -* `Google `_ -* `Yahoo `_ -* `Nominatim `_ -* `Yandex `_ - -Note that Nominatim is a driven by `Open Street Map `_ data, which is the most open of the options offered. +When viewing a resource you will see the "Manage Datastore" button among the local task options. -Geolocation Limits -^^^^^^^^^^^^^^^^^^^ -The number of rows that can be geolocated is determined by the service you select. Google, for example, allows you to geolocate up to 2500 times per day before paying. +Click the "Manage Datastore" button. The top of this screen will give you useful information about the current status of the resource and the datastore. -Adding Service API Keys -^^^^^^^^^^^^^^^^^^^^^^^^^^ -The `Geocoder `_ module supports adding API keys for the Yahoo and Google services. Users can sign up for those services and, in Google's case, geocode up to 100,000 addressees per day. +:Importer: The manager chosen. +:Records Imported: The number of rows from the CSV that have been imported to the datastore. +:Data Importing: The current state of the importing process. The following states are possible: ready, in progress, done, or error. +Step 1: Configure the Manager +***************************** -Managing datastores with Drush -------------------------------- -**To create a datastore from a local file:** +DKAN provides two ways to manage data imports. -.. code-block:: php +:Simple Import: this is the default manager and will be the only option unless you enable *Fast Import*. +:Fast Import: this is disabled by default as it is still experimental, you can enable it from the modules UI or with ``drush en dkan_datastore_fast_import``. See :ref:`how to set up fast import here ` - drush dsc (path-to-local-file) +Step 2: Configure the import option settings for proper parsing +*************************************************************** +Adjust the defaults if necessary. -**To update a datastore from a local file:** +:delimiter: the character that separates the values in your file. +:quote: the character that encloses the fields in your file. +:escape: the character used to escape other characters in your file. -.. code-block:: php +Step 3: Import +************** - drush dsu (datastore-id) (path-to-local-file) +Click the "Import" button at the bottom of the page. Most files will complete the import process right away. Larger files will be processed in 'chunks' during cron runs. Be sure you have [cron](https://www.drupal.org/docs/7/setting-up-cron/overview) running at regular intervals. +Once the import is complete the Data Importing state will display 'done'. The Records Imported should match the number of rows in your file. -**To delete a datastore file (imported items will be deleted as well):** -.. code-block:: php - - drush dsfd (datastore-id) - - -**To get the URI of the datastore file:** - -.. code-block:: php - - drush dsfuri (datastore-id) +Datastore API +--------------- +Your data is now available via the Datastore API! For more information, see the :doc:`Datastore API page <../apis/datastore-api>`. +Click the "Data API" button at the top of the resource screen for specific instructions. -Using the Fast Import Option ------------------------------ -DKAN Datastore's "fast import" allows for importing huge CSV files into the datastore at a fraction of the time it would take using the regular import. +Dropping the Datastore +---------------------- -When a CSV is imported using the regular import, this is what it happens under the hood: +To remove all records from the datastore: -1. PHP interpreter reads the file line-by-line from the disk -2. Each time a line is parsed it sends a query to the database -3. The database receives the query and parses it -4. The database creates a query execution plan -5. The database excecutes the plan (i.e., inserts a new row) +1. Visit the resource page. +2. Click the "Manage Datastore" button. +3. Click the "Drop" button. +4. Confirm by clicking the "Drop" button. -.. note:: - Steps 3, 4 and 5 are executed for *each row* in the CSV. +.. _fast_import_manager: -The Datastore Fast Import was designed to remove as many steps as possible from the previous list. It performs the following steps: +DKAN Fast Import Manager +------------------------ +.. warning:: + The *FastImport* Manager only works with files hosted in the web server and with a properly configured mysql client and server. -1. PHP interpreter sends a LOAD DATA query to the database -2. The database receive the query and parses it -3. The database reads and imports the whole file into a table +DKAN provides a second manager: *FastImport*. -Only one query is executed, so the amount of time required to import a big dataset is drastically reduced. On a multi-megabyte file, this could mean the difference between an import time of hours to minutes. +This manager allows the importing of huge CSV files into the datastore at a fraction of the time it would take using the regular import. Requirements -^^^^^^^^^^^^^^ +************ - A MySQL / MariaDB database - MySQL database should support `PDO::MYSQL_ATTR_LOCAL_INFILE` and `PDO::MYSQL_ATTR_USE_BUFFERED_QUERY` flags. - Cronjob or similar to execute periodic imports. -- Drush .. note:: Because of the above requirements, which may not be available on all hosting environments, this module is *disabled* by default in DKAN. Installation -^^^^^^^^^^^^^^ +************ - Inside your settings.php add a `pdo` element to your database configuration. For example: @@ -199,40 +113,6 @@ Installation 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 -- Set up the following command to run periodically using a cronjob or similar: ``drush queue-run dkan_datastore_fast_import_queue`` - - -Configuration -^^^^^^^^^^^^^^ - -To configure how Fast Import behaves go to *admin/dkan/datastore*. - -There are 3 basic configurations that control the Fast Import functionality: - -:Use regular import as default: **Use Fast Import** checkbox is uncheked by default on the resource's datastore import form so files are imported using the normal dkan datastore import. However you can still enable fast import for any resource by clicking that checkbox. - -:Use fast import as default: **Use Fast Import** checkbox is cheked by default so files are imported using DKAN Fast Import. Like the previous setting, you can uncheck **Use Fast Import** on the resource-specific datastore import form to use the normal import instead. - -:Use fast import for files with a weight over: From this setting you obtain a refined control about when **Use Fast Import** should be checked. This option reveals an additional setting: **"File size threshold."** "Use Fast Import" will be checked on the datastore import form for all the files over this size threshold. 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) - -Either of the two "Use fast import" options will also reveal the following additional settings: - -:Load Data Statement: Some hostings doesn't support ``LOAD DATA LOCAL INFILE``. If that's your case you can switch to ``LOAD DATA INFILE``. -:Queue Filesize Threshold: If a file is small enough, you can avoid waiting until the drush queue runs by configuring this threshold. Files with a size under this value won't be queued and will rather imported during the request. The time to perform the import should fit into the php request timeout, or your import could be aborted. - - -Usage -^^^^^^^^^^^^^^ - -To import a resource using Fast Import: - -- Create a resource using a CSV file (**node/add/resource**) or edit an existing one. -- Click on **Manage Datastore** -- Make sure the status says **No imported items** (You can use the **Drop Datastore** link if needed). -- Check **Use Fast Import** checkbox -- Press **import** -- If you get an error like ``SQLSTATE[28000]: invalid authorization specification: 1045 access denied for user 'drupal'@'%' (using password: yes)`` you will need to grant FILE permissions to your MYSQL user. To do so use this command: ``GRANT FILE ON *.* TO 'user-name'`` - .. note:: If you are using the docker-based development environment `described in the DKAN Starter documentation `_, you will need to execute the following commands (take note that admin123 is the password of the admin user in that mysql environment): @@ -243,15 +123,12 @@ To import a resource using Fast Import: mysql -u root -padmin123 GRANT FILE ON *.* TO 'drupal'; -When the option "Use Fast Import" is checked, some other options become visible that affect how MySQL will parse your file: - - - **Quote delimiters**: the character that encloses the fields in your CSV file. - - **Lines terminated by**: the character that works as line terminator in your CSV file. - - **Fields escaped by**: the character used to escape other characters in your CSV file. +Usage +***** -Also, you can choose if the empty cells will be read as NULL or zeros by checking the box for *"Read empty cells as NULL"*. +To import a resource using Fast Import, follow the instructions previously given in *"Importing Resources"*. -Datastore API --------------- +Troubleshoot +************ -Once processed, Datastore information is available via the Datastore API. For more information, see the :doc:`Datastore API page <../apis/datastore-api>`. +- If you get an error like ``SQLSTATE[28000]: invalid authorization specification: 1045 access denied for user 'drupal'@'%' (using password: yes)`` you will need to grant FILE permissions to your MYSQL user. To do so use this command: ``GRANT FILE ON *.* TO 'user-name'`` diff --git a/dkan/docs/components/permissions.rst b/dkan/docs/components/permissions.rst index 79fa57f4a..471162ead 100644 --- a/dkan/docs/components/permissions.rst +++ b/dkan/docs/components/permissions.rst @@ -61,7 +61,7 @@ This will typically be a person handling the content on a frequent basis. Someon **Permissions:** -* Create page, dataset, resource, data story, and data dashboard content. +* Create dataset, resource, data story, and data dashboard content. * Create chart visualizations. * Edit, delete, and manage versions of content added by other users. * Add and view files to site library. diff --git a/dkan/docs/components/workflow.rst b/dkan/docs/components/workflow.rst index 557eb7d0c..e427edfb3 100644 --- a/dkan/docs/components/workflow.rst +++ b/dkan/docs/components/workflow.rst @@ -1,8 +1,10 @@ +============= DKAN Workflow ============= Introduction ------- +------------- + For large organizations, it can be difficult to moderate vast amounts of content submitted by a wide range of publishers and agencies. DKAN Workflow is an optional module for `DKAN @@ -23,8 +25,10 @@ Workflow creates a moderation queue so that content is published to the live sit The above image displays what you see on My Workbench after login. The Workbench navigation bar contains your content, drafts, and more. The "Create Content" menu features a list of content types you can create. -Installation ------------- +There are also three different Workflow roles, each with their own moderation permissions. These roles are Workflow Contributor, Workflow Moderator and Workflow Supervisor. For more on Workflow Roles and Permissions, please skip ahead to the "Workflow Roles and Permissions" section of this document. + +Installing Workflow +=================== DKAN Workflow is included on all out-of-the-box DKAN sites; however, it is not enabled by default. It can be enabled either from the Modules management page or by using drush. @@ -41,6 +45,7 @@ You may also see a message instructing you to rebuild permissions. If so, click Requirements for DKAN Workflow -------------------------------- + The DKAN Workflow component as a whole is comprised of three modules: * DKAN Workflow @@ -64,6 +69,38 @@ Finally, the following Drupal contrib modules provide extra functionality (Menu All of the aforementioned dependencies are declared in the `drupal-org.make `_ file. +.. _`workflow-roles`: + +Workflow Roles and Permissions +============================= + +The three Workflow roles correspond with the three core DKAN `roles and permissions `_ If a user is given a Workflow role, they must also be granted the corresponding core DKAN role. + +* **Workflow Contributor = Content Creator** + +Workflow Contributor is the most basic role; users with this role can add content, save as Draft or move it to Needs Review, but cannot publish content directly to the live site. They can only view content that they've created, and cannot modify the content of others. + +* **Workflow Moderator = Editor** + +Workflow Moderator is the middle role, mostly pertaining to moderating specific groups. This role reviews and publishes (or unpublishes) content for their group(s), rather than for the entire site. Workflow Moderators can also view and approve of content that has not yet been assigned to a group. + +* **Workflow Supervisor = Site Manager** + +Workflow Supervisor is the most powerful role and should only be assigned to highly trusted users. Users with the role of Workbench Supervisor can add, edit, modify, publish, unpublish, moderate or delete _all_ site content. This role is the only role that have access to the "Stale Drafts" and "Stale Review" tabs (more information below). + +Here is how core roles in DKAN are automatically correlated to Workbench roles and permissions: + ++-------------------------+-------------------------------------+---------------------------------------------+ +| What a user will see | "My Drafts" | "Needs Review" | ++=========================+=====================================+=============================================+ +| Workflow Contributor | Only content that they submitted. | Can see only content they have submitted. | ++-------------------------+-------------------------------------+---------------------------------------------+ +| Workflow Moderator | The content submitted to their | The content submitted to their organic | +| | organic group. | group. | +| | Their own content. | Their own content. | ++-------------------------+-------------------------------------+---------------------------------------------+ +| Workflow Supervisor | Only content that they submitted. | All the "Needs review" content. | ++-------------------------+-------------------------------------+---------------------------------------------+ My Workbench ============ @@ -74,7 +111,7 @@ When logged in as a user that has been assigned a Workbench role, the "My Workbe .. image:: ../images/workflow/dkan_workflow_main_interface.png -The My Workbench Moderation Toolbar +The "My Workbench" Moderation Toolbar ------------------------------------ :My content: This tab provides a list of all of the content you've created. @@ -110,39 +147,6 @@ text area. .. image:: ../images/workflow/workflow_node_edit.png -.. _`workflow-roles`: - -Workflow Roles ---------------------------- - -The three Workflow roles correspond with the three core DKAN `roles and permissions `_ If a user is given a Workflow role, they must also be granted the corresponding core DKAN role. - -* **Workflow Contributor = Content Creator** - -Workflow Contributor is the most basic role; users with this role can add content, save as Draft or move it to Needs Review, but cannot publish content directly to the live site. They can only view content that they've created, and cannot modify the content of others. - -* **Workflow Moderator = Editor** - -Workflow Moderator is the middle role, mostly pertaining to moderating specific groups. This role reviews and publishes (or unpublishes) content for their group(s), rather than for the entire site. Workflow Moderators can also view and approve of content that has not yet been assigned to a group. - -* **Workflow Supervisor = Site Manager** - -Workflow Supervisor is the most powerful role and should only be assigned to highly trusted users. Users with the role of Workbench Supervisor can add, edit, modify, publish, unpublish, moderate or delete _all_ site content. This role is the only role that have access to the "Stale Drafts" and "Stale Review" tabs (more information below). - -Here is how core roles in DKAN are automatically correlated to Workbench roles and permissions: - -+-------------------------+-------------------------------------+---------------------------------------------+ -| What a user will see | "My Drafts" | "Needs Review" | -+=========================+=====================================+=============================================+ -| Workflow Contributor | Only content that they submitted. | Can see only content they have submitted. | -+-------------------------+-------------------------------------+---------------------------------------------+ -| Workflow Moderator | The content submitted to their | The content submitted to their organic | -| | organic group. | group. | -| | Their own content. | Their own content. | -+-------------------------+-------------------------------------+---------------------------------------------+ -| Workflow Supervisor | Only content that they submitted. | All the "Needs review" content. | -+-------------------------+-------------------------------------+---------------------------------------------+ - Changing Notification Email Settings ------------------------------------- @@ -159,7 +163,7 @@ Emails will display the context that had triggered the notification as well as l updated content. Advanced Options -============================================== +================== Tweaking the Email template --------------------------- diff --git a/dkan/docs/development/display-num-datasets.rst b/dkan/docs/development/display-num-datasets.rst new file mode 100644 index 000000000..9c47983d9 --- /dev/null +++ b/dkan/docs/development/display-num-datasets.rst @@ -0,0 +1,159 @@ +Display Number of Datasets, Topics or Other Items +================================================= + +Datasets, resources, tags and topics in DKAN are created using Drupal's content types and vocabularies. This means that we can use many of the Drupal modules and tools to extend the site display and functionality. + +The Views module is one of the most popular and powerful Drupal modules. It provides a user interface to query content and other site assets and choose display options without any code. + +The following recipe will use Views and Panals modules to create some simple site statistics and display them on the home page. The end result will look like: + +.. figure:: https://user-images.githubusercontent.com/512243/46543802-ca9d8700-c88f-11e8-9824-0cd7f7e52934.png + +Step 1 +------ + +- Go to `admin/structure/views/import` and import the following two views: + +Terms Count View: + +.. code-block:: php + + $view = new view(); + $view->name = 'term_counts'; + $view->description = ''; + $view->tag = 'default'; + $view->base_table = 'taxonomy_term_data'; + $view->human_name = 'Term Counts'; + $view->core = 7; + $view->api_version = '3.0'; + $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */ + + /* Display: Master */ + $handler = $view->new_display('default', 'Master', 'default'); + $handler->display->display_options['title'] = 'Tags'; + $handler->display->display_options['use_more_always'] = FALSE; + $handler->display->display_options['group_by'] = TRUE; + $handler->display->display_options['access']['type'] = 'perm'; + $handler->display->display_options['cache']['type'] = 'none'; + $handler->display->display_options['query']['type'] = 'views_query'; + $handler->display->display_options['exposed_form']['type'] = 'basic'; + $handler->display->display_options['pager']['type'] = 'none'; + $handler->display->display_options['pager']['options']['offset'] = '0'; + $handler->display->display_options['style_plugin'] = 'default'; + $handler->display->display_options['row_plugin'] = 'fields'; + /* Field: COUNT(DISTINCT Taxonomy term: Name) */ + $handler->display->display_options['fields']['name']['id'] = 'name'; + $handler->display->display_options['fields']['name']['table'] = 'taxonomy_term_data'; + $handler->display->display_options['fields']['name']['field'] = 'name'; + $handler->display->display_options['fields']['name']['group_type'] = 'count_distinct'; + $handler->display->display_options['fields']['name']['label'] = ''; + $handler->display->display_options['fields']['name']['alter']['word_boundary'] = FALSE; + $handler->display->display_options['fields']['name']['alter']['ellipsis'] = FALSE; + $handler->display->display_options['fields']['name']['element_type'] = 'h1'; + $handler->display->display_options['fields']['name']['element_label_colon'] = FALSE; + $handler->display->display_options['fields']['name']['element_default_classes'] = FALSE; + /* Filter criterion: Taxonomy vocabulary: Machine name */ + $handler->display->display_options['filters']['machine_name']['id'] = 'machine_name'; + $handler->display->display_options['filters']['machine_name']['table'] = 'taxonomy_vocabulary'; + $handler->display->display_options['filters']['machine_name']['field'] = 'machine_name'; + $handler->display->display_options['filters']['machine_name']['value'] = array( + 'tags' => 'tags', + ); + + /* Display: Topics Block */ + $handler = $view->new_display('block', 'Topics Block', 'block_1'); + $handler->display->display_options['defaults']['title'] = FALSE; + $handler->display->display_options['title'] = 'Topics'; + + /* Display: Tags Block */ + $handler = $view->new_display('block', 'Tags Block', 'block_2'); + +Node Counts view: + +.. code-block:: php + + $view = new view(); + $view->name = 'nodecounts'; + $view->description = ''; + $view->tag = 'default'; + $view->base_table = 'node'; + $view->human_name = 'Node counts'; + $view->core = 7; + $view->api_version = '3.0'; + $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */ + + /* Display: Master */ + $handler = $view->new_display('default', 'Master', 'default'); + $handler->display->display_options['title'] = 'Datasets'; + $handler->display->display_options['use_more_always'] = FALSE; + $handler->display->display_options['group_by'] = TRUE; + $handler->display->display_options['access']['type'] = 'perm'; + $handler->display->display_options['cache']['type'] = 'none'; + $handler->display->display_options['query']['type'] = 'views_query'; + $handler->display->display_options['query']['options']['query_comment'] = FALSE; + $handler->display->display_options['exposed_form']['type'] = 'basic'; + $handler->display->display_options['pager']['type'] = 'none'; + $handler->display->display_options['style_plugin'] = 'default'; + $handler->display->display_options['row_plugin'] = 'fields'; + $handler->display->display_options['row_options']['inline'] = array( + 'type_1' => 'type_1', + 'type' => 'type', + ); + $handler->display->display_options['row_options']['separator'] = ': '; + /* Field: COUNT(Content: Type) */ + $handler->display->display_options['fields']['type']['id'] = 'type'; + $handler->display->display_options['fields']['type']['table'] = 'node'; + $handler->display->display_options['fields']['type']['field'] = 'type'; + $handler->display->display_options['fields']['type']['group_type'] = 'count'; + $handler->display->display_options['fields']['type']['label'] = ''; + $handler->display->display_options['fields']['type']['element_type'] = 'h1'; + $handler->display->display_options['fields']['type']['element_label_colon'] = FALSE; + $handler->display->display_options['fields']['type']['element_default_classes'] = FALSE; + $handler->display->display_options['fields']['type']['separator'] = ''; + /* 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', + ); + + /* Display: Datasets Block */ + $handler = $view->new_display('block', 'Datasets Block', 'block'); + + /* Display: Resources Block */ + $handler = $view->new_display('block', 'Resources Block', 'block_1'); + $handler->display->display_options['defaults']['title'] = FALSE; + $handler->display->display_options['title'] = 'Resources'; + $handler->display->display_options['defaults']['filter_groups'] = FALSE; + $handler->display->display_options['defaults']['filters'] = FALSE; + /* 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( + 'resource' => 'resource', + ); + +These two views provide blocks for Dataset, Resource, Topic, and Tag counts. + +- Clear the Drupal cache. + +Step 2 +------ + +Add Blocks to Home page: +^^^^^^^^^^^^^^^^^^^^^^^^ +- Go to the Panelizer page for the home page. You can find this by finding the "Welcome" page in the content menu and clicking "edit". Replace the "edit" term in the URL with "panelizer". The URL should look like `node/1/panelizer`. +- Click "content" in the default view mode. +- Click the gear Icon under "Triplet First Column" and "Add Content" + +.. figure:: https://user-images.githubusercontent.com/512243/46544377-68458600-c891-11e8-8498-08d0086bb2a7.png + +Click "Views" in the left column and then "Node Counts" + +.. figure:: https://user-images.githubusercontent.com/512243/46544495-bfe3f180-c891-11e8-8972-ddc136d52d72.png + +Select "Dataset Block" as the display. Click "Finish" and then "Save" on the panelizer page. + +- Repeat the process for the other types of statistics you want to add. To add Topics or Tags statistics select "Term Counts" instead of "Node Counts". diff --git a/dkan/docs/development/index.rst b/dkan/docs/development/index.rst index 36fa5d0f1..b81cf63ee 100644 --- a/dkan/docs/development/index.rst +++ b/dkan/docs/development/index.rst @@ -40,3 +40,4 @@ custom extentions to DKAN. For now, read additional information about: modules metadatasource custom_file_extensions + display-num-datasets diff --git a/dkan/docs/introduction/dkan-sites.rst b/dkan/docs/introduction/dkan-sites.rst index 22eb2c0a8..96fb6686e 100644 --- a/dkan/docs/introduction/dkan-sites.rst +++ b/dkan/docs/introduction/dkan-sites.rst @@ -37,6 +37,7 @@ Nebraska https://nebraskamap.gov/ Louisville, KY https://data.louisvilleky.gov OpenOakland http://data.openoakland.org DigitalC http://civicinsights.org +Department of Conservation, Missouri https://research.mdc.mo.gov/ ================================================== ============= @@ -76,12 +77,12 @@ Opava Municipality, Czech Republic http://kod.opava-city.cz Decentralised Administration of Crete, Greece http://apdkritis.gov.gr/en Bosnia and Herzegovina http://opendata.ba Belarus http://opendata.by -Ukraine http://data.gov.ua Russia http://data.gov.ru Moscow Region, Russia http://data.mosreg.ru Instituto Politécnico de Bragança, Portugal http://observatoriottm.ipb.pt Barcelona Provicial Diputation, Spain https://dadesobertes.diba.cat/ Düsseldorf, Germany https://opendata.duesseldorf.de/ +The Republic of Cyprus http://www.data.gov.cy/ ================================================== ============= @@ -128,7 +129,7 @@ Ministry of Health, Peru http://datos.mins National Jury of Elections, Peru http://jnedatosabiertos.pe/ Ministry of Labor, Peru http://datos.trabajo.gob.pe/ Open Data Windsor Essex, Canada http://odwe.ca/ -City of Fredericton, Canada http://data.fredericton.ca) +São Paulo State's Department of Education, Brazil https://dados.educacao.sp.gov.br/ ============================================================== ============= @@ -146,4 +147,5 @@ Namibia http://www.namopendata.com/de South Africa http://data.gov.za Local Development Research Initiative, Kenya http://transformagriculture.org Sayada, Tunisia http://opendata.sayada.tn/fr +Edo, Nigeria http://data.edostate.gov.ng/ ============================================== ============= diff --git a/dkan/docs/releases/notes/1.15.4.md b/dkan/docs/releases/notes/1.15.4.md index b62103076..a9fa326eb 100644 --- a/dkan/docs/releases/notes/1.15.4.md +++ b/dkan/docs/releases/notes/1.15.4.md @@ -1,4 +1,4 @@ -- # DKAN 1.15.4 Release notes +# DKAN 1.15.4 Release notes - #2701 Update drupal core to 7.60 - #2541 Updates for php 7.1 compatibility diff --git a/dkan/docs/releases/notes/1.15.5.md b/dkan/docs/releases/notes/1.15.5.md new file mode 100644 index 000000000..e8b567256 --- /dev/null +++ b/dkan/docs/releases/notes/1.15.5.md @@ -0,0 +1,4 @@ +# DKAN 1.15.5 Release notes + + - #2710 Replace data stories page view with a non-search index version + - #2712 Remove unused xss code diff --git a/dkan/docs/releases/notes/1.16.md b/dkan/docs/releases/notes/1.16.md new file mode 100644 index 000000000..b0cecdd7e --- /dev/null +++ b/dkan/docs/releases/notes/1.16.md @@ -0,0 +1,47 @@ +# DKAN 1.16 Release notes + +## What's New +### Complete Refactor of DKAN Datastore + +The [Feeds](https://www.drupal.org/project/feeds) module has long been a key component of the DKAN Datastore. While it has served us well over the years as a community-supported framework for importing CSV and similar files into database tables, it has also added a lot of overhead and bloat to what should be a simple system. At the same time, much of the datastore code is written specifically to interact with Feeds, and separating the two proved to be impossible. The Datastore module has now been completely re-written to make it faster, more stable, and more modular. A properly object-oriented, decoupled architecture allows its various classes to be extended and plans to support additional options for datastore infrastructure (such as a second MySQL database or even a 3rd party service like [Carto](https://carto.com/platform/spatial-data-science/)) are in the works. + +#### New CSV importer w/o Feeds + +A simple parser will try to import the entire file into the datastore, and continue in the background on the next cron run if the file is too big. Improvements can be found both in the code and in the UI. See [the docs](http://docs.getdkan.com/en/latest/) for more information. + +#### Better API support + +Datastore behaviors can also now be controlled through the [Dataset REST API](http://docs.getdkan.com/en/latest/apis/rest-api.html), so that you can automate actions like importing resources to the datastore and dropping datastore tables. + +Note: While the system for getting data into the datastore has changed significantly, [the API for querying the datastore](http://docs.getdkan.com/en/latest/apis/datastore-api.html) remains the same. + +### Improvements to DKAN command-line tooling + +While not a change to the core DKAN codebase, this release marks the release of a new command-line tool for working with DKAN, [DKAN Tools](https://github.com/GetDKAN/dkan-tools). This will make it easier for anyone to stand up DKAN locally, manage Docker containers, and use different CI pipelines. + +### Support for non-date values in the dataset "modified" field + +The "Modified Date" field has been converted to a text field to accommodate ISO 8601 repeating interval values such as `R/P1D` and `R/P2W`. Previously, this field had been a date field, which was incompatible with certain values that would be allowed in Project Open Data's [modified](https://project-open-data.cio.gov/v1.1/schema/#modified) field. + +### Support PHP 7 + +Many great performance improvements happened in PHP 7. DKAN has been updated to be compatible with php 7.1 and allows users to take advantage of those improvements. Our CI and testing infrastructure has also been updated to use PHP 7.1. + +### Additional improvements in this release + + - #2179 Refactor Datastore Importer + - #2627 Add support for ISO-8106 duration values in "modified" when harvesting + - #2729 Update recline to 2.1 + - #2713 CSV Parser Improvements: trailing commas & escape chars + - #2740 Fix dkan_workflow anonymous access to revisions and unpublished content + - #2724 Update contrib modules: + * better_exposed_filters + * file_entity + * media + * panopoly_widgets + * search_api_db + * uuid + * date: fixes issue [2843367](https://www.drupal.org/node/2843367) + - #2718 Update odsm_dkan token values for remote file + - #2707 DKAN workflow documentation updates + - #2723 Include dkan.profile to prevent undefined function in update diff --git a/dkan/docs/releases/notes/index.rst b/dkan/docs/releases/notes/index.rst index 0bebafc80..2b397b605 100644 --- a/dkan/docs/releases/notes/index.rst +++ b/dkan/docs/releases/notes/index.rst @@ -7,6 +7,8 @@ Release notes here will be identical to the releases kept in the `Github reposit .. toctree:: :maxdepth: 1 + 1.16 <1.16> + 1.15.5 <1.15.5> 1.15.4 <1.15.4> 1.15.3 <1.15.3> 1.15.2 <1.15.2> diff --git a/dkan/drupal-org-core.make b/dkan/drupal-org-core.make index febcc869b..a9b8e6efc 100644 --- a/dkan/drupal-org-core.make +++ b/dkan/drupal-org-core.make @@ -3,7 +3,7 @@ core: 7.x projects: drupal: type: core - version: '7.60' + version: '7.61' # Use vocabulary machine name for permissions, see http://drupal.org/node/995156 patch: 995156: 'https://drupal.org/files/issues/995156-5_portable_taxonomy_permissions.patch' diff --git a/dkan/drupal-org.make b/dkan/drupal-org.make index 73d174f1a..3838c4978 100644 --- a/dkan/drupal-org.make +++ b/dkan/drupal-org.make @@ -5,7 +5,7 @@ includes: - https://raw.githubusercontent.com/NuCivic/visualization_entity/7.x-2.0/visualization_entity.make - https://raw.githubusercontent.com/NuCivic/open_data_schema_map/7.x-2.1/open_data_schema_map.make - https://raw.githubusercontent.com/NuCivic/leaflet_draw_widget/5a5f8faf664aeca02371f6692307580d9fab9116/leaflet_widget.make -- https://raw.githubusercontent.com/NuCivic/recline/7.x-2.0/recline.make +- https://raw.githubusercontent.com/NuCivic/recline/7.x-2.1/recline.make projects: admin_menu: version: 3.0-rc5 @@ -23,6 +23,12 @@ projects: version: '2.3' patch: 2833824: https://www.drupal.org/files/issues/autocomplete-deluxe-2833824-4.patch + autoload: + download: + type: git + url: https://git.drupal.org/project/autoload.git + branch: 7.x-2.x + revision: 80ea4d125a2edf1e3c68c5627b3afb4614828a27 beautytips: download: type: git @@ -32,7 +38,7 @@ projects: patch: 849232: https://drupal.org/files/include-excanvas-via-libraries-api-d7-849232-13.patch better_exposed_filters: - version: '3.5' + version: '3.6' bueditor: version: '1.8' bueditor_plus: @@ -59,13 +65,17 @@ projects: data: version: 1.x date: - version: '2.10' + download: + type: git + url: https://git.drupal.org/project/date.git + branch: 7.x-2.x + revision: a2ef952517f789bfd85659f96a0321a66936661a defaultconfig: version: 1.0-alpha11 devel: version: '1.5' diff: - version: '3.3' + version: '3.4' double_field: version: '2.5' drafty: @@ -130,8 +140,9 @@ projects: version: '1.6' patch: 2887897: https://www.drupal.org/files/issues/added_missing_isset_calls-2887897-2.patch + 3016830: https://www.drupal.org/files/issues/2018-11-28/undefined-index-classes-3016830-0.patch field_hidden: - version: '1.7' + version: '1.8' field_reference_delete: download: full_version: 7.x-1.0-beta1 @@ -142,7 +153,7 @@ projects: 2826182: https://www.drupal.org/files/issues/fieldable_panels_panes-title-shown-when-set-to-hidden-2826182-3.patch 2826205: https://www.drupal.org/files/issues/fieldable_panels_panes-n2826205-32.patch file_entity: - version: 2.21 + version: 2.25 file_resup: version: '1.5' filefield_sources: @@ -170,7 +181,7 @@ projects: patch: 1568162: https://drupal.org/files/views-display-user-picture-doesn-t-display-gravatar-1568162-10.patch honeypot: - version: '1.24' + version: '1.25' image_url_formatter: version: '1.4' imagecache_actions: @@ -190,7 +201,7 @@ projects: url: https://github.com/GetDKAN/leaflet_draw_widget.git revision: 5a5f8faf664aeca02371f6692307580d9fab9116 libraries: - version: '2.3' + version: '2.5' link: version: '1.5' link_badges: @@ -214,7 +225,7 @@ projects: patch: 2045225: https://drupal.org/files/remove-dsm-from-hook-install-2045225-1.patch media: - version: 2.19 + version: 2.21 media_youtube: version: '3.7' media_vimeo: @@ -226,9 +237,9 @@ projects: menu_badges: version: '1.3' menu_block: - version: '2.7' + version: '2.8' migrate: - version: '2.10' + version: '2.11' patch: 1989492: https://www.drupal.org/files/issues/migrate-append-map-messages-1989492-10.patch migrate_extras: @@ -262,12 +273,12 @@ projects: panels_style_collapsible: version: '1.3' panopoly_widgets: - version: '1.54' + version: '1.58' patch: 1: patches/panopoly_widgets_overrides.patch 2: patches/panopoly_widgets_add_jquery_ui_tabs.patch panopoly_images: - version: '1.54' + version: '1.58' path_breadcrumbs: version: '3.3' pathauto: @@ -285,7 +296,7 @@ projects: download: type: git url: https://github.com/GetDKAN/recline.git - tag: 7.x-2.0 + revision: 6db1ab729d14f40150f6571457331ff3bde8752d ref_field: download: type: git @@ -312,11 +323,9 @@ projects: version: '1.2' revision: 08b02458694d186f8ab3bd0b24fbc738f9271108 search_api: - version: '1.24' + version: '1.25' search_api_db: - version: '1.6' - patch: - 2855634: https://www.drupal.org/files/issues/2855634-23--fix_update_7107_for_different_db.patch + version: '1.7' select_or_other: version: '2.24' services: @@ -326,9 +335,9 @@ projects: strongarm: version: '2.0' tablefield: - version: '3.1' + version: '3.2' taxonomy_menu: - version: '1.5' + version: '1.6' taxonomy_fixtures: download: type: git @@ -339,7 +348,7 @@ projects: token_tweaks: version: 1.x-dev uuid: - version: '1.1' + version: '1.2' views: version: '3.20' views_autocomplete_filters: @@ -363,7 +372,11 @@ projects: version: '3.12' workbench_moderation: version: '3.0' - revision: 3fcb1a66e87a539f06664502141abc8f37712929 + patch: + 2360973: https://www.drupal.org/files/issues/workbench_moderation-install-warnings-2360973-3.patch + 1512442: https://www.drupal.org/files/issues/1512442-20-workbench_moderation-fix_access_check.patch + xautoload: + version: '5.7' libraries: chosen: download: diff --git a/dkan/modules/contrib/autoload/README.md b/dkan/modules/contrib/autoload/README.md new file mode 100644 index 000000000..3a4319a68 --- /dev/null +++ b/dkan/modules/contrib/autoload/README.md @@ -0,0 +1,76 @@ +# Autoload + +Want to not care about loading classes, traits or interfaces? The aim of this project is what you are looking for! + +## How it works? + +The module scans all Drupal infrastructure, generates a class map with all possible namespaces and stores it to `DRUPAL_ROOT . '/autoload.php'` (path can be changed by modifying the `autoload_file` Drupal variable). If a file is not writable (which must not be the case during development) then a class map will be dumped into the database. + +Note, that after downloading projects via Drush (e.g. `drush dl ctools_api pinmap`) the autoloading class map will be automatically rebuilt (only once, no matter how many projects were downloaded). + +### How to correctly add new classes/traits/interfaces? + +Well, since the autoloading - it is a generated static file, you need to update it once you adding new classes, traits or interfaces. Ideally, after removal as well in order to not keep outdated entries inside of the map thereby prevent its expansion. + +**In short**: added a new file? Execute `drush aur` and continue working. + +### How to correctly remove a module? + +First of all, it's applies only to modules, which use autoloading. If you decided to remove the project from filesystem then after the action execute the `drush autoload-rebuild` or `drush aur` to rebuild the autoloading class map. + +## Usage + +You able to use one or both of known autoloading standards - [PSR-0](http://www.php-fig.org/psr/psr-0) and [PSR-4](http://www.php-fig.org/psr/psr-4). + +Some of the projects using autoloading: + +- [CTools API](https://www.drupal.org/project/ctools_api) +- [Commerce Utilities](https://www.drupal.org/project/commerce_utils) +- [Commerce Adyen](https://www.drupal.org/project/commerce_adyen) +- [Commerce Bangkok Bank iPay](https://www.drupal.org/project/commerce_bangkokbank) +- [Commerce PayGate PayHost](https://www.drupal.org/project/commerce_paygate_payhost) + +### Drupal way + +Add `autolaod = TRUE` inside of module's `*.info` file and just place the objects inside of `lib/Drupal/YOUR_MODULE_NAME` and/or `src` directories. + +```ini +; All namespaces must be followed by "Drupal\YOUR_MODULE_NAME". +autoload = TRUE +``` + +Have a look at [tests](tests/autoload_test_drupal) as an example. + +### Custom namespaces + +**Please, avoid usage of custom namespaces due to their non-standardisation. They can cause a pain in the ass and supports only for backward compatibility.** + +Configure the `autoload` directive as an array where keys are subdirectories inside of the module directory and values - object namespaces. As many as needed directories and namespaces can be added in this way. + +#### PSR-0 + +```ini +; All objects, namespace path of which starts from "CTools", +; will be searched inside of "/psr-0". +autoload[psr-0][] = CTools +``` + +#### PSR-4 + +```ini +; All objects, namespace path of which starts from "CTools\Plugins", +; will be searched inside of "/psr-4". +autoload[psr-4][] = CTools\Plugins +``` + +#### PSR-4 (single namespace) + +Take into account the trailing slash! It must be at the end of global namespace to use `PSR-4` standard. It looks similar to `PSR-0`, but that slash telling that it's not true. + +```ini +; All objects, namespace path of which starts from "CTools", +; will be searched inside of "/psr-4". +autoload[psr-4][] = CTools\ +``` + +Have a look at [tests](tests/autoload_test_custom) as an example. diff --git a/dkan/modules/contrib/autoload/autoload.api.php b/dkan/modules/contrib/autoload/autoload.api.php new file mode 100644 index 000000000..7f5b66018 --- /dev/null +++ b/dkan/modules/contrib/autoload/autoload.api.php @@ -0,0 +1,21 @@ +data = $data; + } + + /** + * {@inheritdoc} + */ + public function offsetExists($offset) { + return isset($this->data[$offset]); + } + + /** + * {@inheritdoc} + */ + public function offsetGet($offset) { + return $this->data[$offset]; + } + + /** + * {@inheritdoc} + */ + public function offsetSet($offset, $value) { + $this->data[$offset] = $value; + } + + /** + * {@inheritdoc} + */ + public function offsetUnset($offset) { + unset($this->data[$offset]); + } + + /** + * {@inheritdoc} + */ + public function current() { + return current($this->data); + } + + /** + * {@inheritdoc} + */ + public function next() { + next($this->data); + } + + /** + * {@inheritdoc} + */ + public function key() { + return key($this->data); + } + + /** + * {@inheritdoc} + */ + public function valid() { + return $this->key() !== NULL; + } + + /** + * {@inheritdoc} + */ + public function rewind() { + reset($this->data); + } + + /** + * {@inheritdoc} + */ + public function count() { + return count($this->data); + } + +} + +/** + * Class AutoloadCache. + */ +class AutoloadCache extends ArrayContainer { + + /** + * Name of the table in database to store the data when IO is not available. + */ + const BIN = 'cache_autoload'; + + /** + * A path to file for storing the autoloading map. + * + * @var string + */ + protected $file = ''; + + /** + * {@inheritdoc} + * + * @param string $file + * A path to file for storing the autoloading map. + */ + public function __construct($file) { + parent::__construct(); + + $this->file = $file; + + if (is_readable($this->file)) { + $this->data = require $this->file; + } + + // If the file doesn't exist, not readable or doesn't contain a map. + if (empty($this->data) || !is_array($this->data)) { + $this->data = db_select(static::BIN) + ->fields(static::BIN) + ->execute() + ->fetchAllAssoc('namespace', \PDO::FETCH_ASSOC); + + // Rebuild if a database is empty, try to store data in file otherwise. + empty($this->data) ? $this->rebuild() : $this->updateFile(); + } + } + + /** + * {@inheritdoc} + */ + public function offsetSet($namespace, $value) { + if ( + !is_array($value) || + empty($value['file']) || + empty($value['provider']) || + !file_exists($value['file']) + ) { + throw new \InvalidArgumentException(sprintf('Incorrect value for the "%s()" method.', __METHOD__)); + } + + parent::offsetSet($namespace, $value); + } + + /** + * Rebuild the autoloading map. + */ + public function rebuild() { + // Hack, allowing bringing to life a site with a broken registry. + if (!function_exists('system_rebuild_module_data')) { + drupal_load('module', 'system'); + } + + $mask = sprintf( + '/^' . DRUPAL_PHP_FUNCTION_PATTERN . '(%s)$/', + implode('|', array_map('preg_quote', autoload_extensions())) + ); + + // Ensure map will be newly constructed. + $this->data = array(); + + foreach (system_rebuild_module_data() as $module_name => $data) { + if (empty($data->info['autoload'])) { + continue; + } + + $module_path = pathinfo($data->filename, PATHINFO_DIRNAME); + + // Allow "autoload = whatever" to enable Drupal-way namespaces. + if (!is_array($data->info['autoload'])) { + $data->info['autoload'] = array(); + } + + /* @see simpletest_test_get_all() */ + $data->info['autoload'] = array_merge_recursive($data->info['autoload'], array_fill_keys( + array("lib/Drupal/$module_name", 'src'), + array("Drupal\\$module_name") + )); + + foreach ($data->info['autoload'] as $subdirectory => $namespace_prefixes) { + // Handle "autoload[] = NS" instead of "autoload[dir][] = NS". + if (!is_array($namespace_prefixes)) { + continue; + } + + // Module path will always be without slashes but subdirectory can be + // with both because this value is user-related. + $sources_path = $module_path . '/' . trim($subdirectory, '/'); + + foreach (file_scan_directory($sources_path, $mask) as $pathinfo) { + // "array_filter()" - to remove emptinesses caused by slashes. + $relative_path = array_filter(explode('/', str_replace($sources_path, '', dirname($pathinfo->uri)))); + $relative_path[] = $pathinfo->name; + + foreach ($namespace_prefixes as $namespace_prefix) { + $namespace_prefix = explode('\\', $namespace_prefix); + $_relative_path = $relative_path; + + // Count without removing slashes because PSR-4 namespace can + // consist only of a single part with trailing backslash at the + // end. For example: "IamCorrectPsr4\". + if (count($namespace_prefix) === 1) { + // Use "reset()" instead of "[0]" for every operands in + // condition because "array_filter()" will change enumeration + // of keys in case of removing elements. + if (reset($namespace_prefix) !== reset($_relative_path)) { + trigger_error(sprintf('Incorrectly defined autoloading for the "%s" file provided by the "%s" module.', $pathinfo->uri, $module_name), E_USER_WARNING); + continue; + } + + array_shift($_relative_path); + } + + // Do not use the "offsetSet()" method here to omit redundant logic + // in there. + $this->data[implode('\\', array_merge(array_filter($namespace_prefix), $_relative_path))] = array( + 'file' => $pathinfo->uri, + 'provider' => $module_name, + ); + } + } + } + } + + drupal_alter('autoload_lookup', $this); + + // A file cannot be written so store the map to the database. + if (!$this->updateFile()) { + db_truncate(static::BIN)->execute(); + + foreach ($this->data as $namespace => $data) { + db_insert(static::BIN) + ->fields(array('namespace' => $namespace) + $data) + ->execute(); + } + + trigger_error(sprintf('Autoloading map saved to the database because the "%s" file is not writable. You are allowed to modify it during development only and never any more.', $this->file), E_USER_WARNING); + } + } + + /** + * Dumps the autoloading class map to the file. + * + * @return bool + * A status whether the file has been saved. + */ + protected function updateFile() { + $reference = __METHOD__; + $map = static::exportVariable($this->data); + $data = <<file, FILE_EXISTS_REPLACE); + } + + /** + * Export a variable as a well-formatted (in terms of Drupal CS) string. + * + * @param mixed $var + * Variable to dump into a string. + * @param int $prefix + * A number of spaces to put before the code. + * @param bool $init + * Internal variable to handle recursion. + * + * @return string + * Dumped variable. + */ + protected static function exportVariable($var, $prefix = 0, $init = TRUE) { + if (is_array($var)) { + if (empty($var)) { + $output = 'array()'; + } + else { + $output = "array(\n"; + foreach ($var as $key => $value) { + // Using normal "var_export()" on the key to ensure correct quoting. + $output .= ' ' . var_export($key, TRUE) . ' => ' . call_user_func(__METHOD__, $value, 2, FALSE) . ",\n"; + } + $output .= ')'; + } + } + elseif (is_bool($var)) { + $output = $var ? 'TRUE' : 'FALSE'; + } + elseif (is_int($var)) { + $output = intval($var); + } + elseif (is_numeric($var)) { + $floatval = floatval($var); + + if (is_string($var) && (string) $floatval !== $var) { + // Do not convert a string to a number if the string + // representation of that number is not identical to the + // original value. + $output = var_export($var, TRUE); + } + else { + $output = $floatval; + } + } + elseif (is_string($var) && strpos($var, "\n") !== FALSE) { + // Replace line breaks in strings with a token for replacement + // at the very end. This protects whitespace in strings from + // unintentional indentation. + $output = var_export(str_replace("\n", '***BREAK***', $var), TRUE); + } + else { + $output = var_export($var, TRUE); + } + + if ($prefix > 0) { + $output = str_replace("\n", "\n" . str_repeat(' ', $prefix), $output); + } + + if ($init) { + $output = str_replace('***BREAK***', "\n", $output); + } + + return $output; + } + + /** + * Returns schema for the table of autoloading storage. + * + * @return array[] + * Database table schemas. + * + * @see autoload_schema() + */ + public static function schema() { + $schema = array(); + + $schema['description'] = 'Stores fallback cache of autoloading mapping.'; + + foreach (array( + 'file' => 'A path to file relative to the Drupal root directory.', + 'provider' => 'Name of the module providing the file.', + 'namespace' => 'Fully-qualified path to object in terms of PHP.', + ) as $field => $description) { + $schema['fields'][$field] = array( + 'type' => 'varchar', + 'length' => 255, + 'default' => '', + 'not null' => TRUE, + 'description' => $description, + ); + } + + $schema['unique keys'] = array( + 'file' => array('file'), + ); + + $schema['primary key'] = array( + 'namespace', + ); + + return array(static::BIN => $schema); + } + +} diff --git a/dkan/modules/contrib/autoload/autoload.drush.inc b/dkan/modules/contrib/autoload/autoload.drush.inc new file mode 100644 index 000000000..e1f25a2b1 --- /dev/null +++ b/dkan/modules/contrib/autoload/autoload.drush.inc @@ -0,0 +1,46 @@ + dt('Rebuild the autoloading class map.'), + 'aliases' => array('aur'), + ); + + return $commands; +} + +/** + * Implements hook_drush_exit(). + */ +function autoload_drush_exit() { + $command = drush_get_command(); + + // Rebuild the autoloading class map only after downloading a bunch of + // projects instead of doing this for every single one inside of the. + /* @see hook_drush_pm_post_download() */ + if ('pm-download' === $command['command']) { + drush_autoload_rebuild(); + } +} + +/** + * Implements drush_COMMAND(). + */ +function drush_autoload_rebuild() { + if (!db_table_exists(AutoloadCache::BIN)) { + drupal_install_schema('autoload'); + } + + autoload(TRUE); + drush_log(dt('The autoloading class map has been rebuilt.'), 'success'); +} diff --git a/dkan/modules/contrib/autoload/autoload.info b/dkan/modules/contrib/autoload/autoload.info new file mode 100644 index 000000000..a8e7253c3 --- /dev/null +++ b/dkan/modules/contrib/autoload/autoload.info @@ -0,0 +1,13 @@ +name = Autoload +description = PHP 5.3+ autoloader for objects. +core = 7.x +php = 5.3 + +; For "autoload_test_entity". +test_dependencies[] = entity +test_dependencies[] = views + +; Information added by drush on 2017-07-24 +version = "7.x-2.0+0-dev" +project = "autoload" +datestamp = "1500910353" \ No newline at end of file diff --git a/dkan/modules/contrib/autoload/autoload.install b/dkan/modules/contrib/autoload/autoload.install new file mode 100644 index 000000000..a6d6cb1fb --- /dev/null +++ b/dkan/modules/contrib/autoload/autoload.install @@ -0,0 +1,17 @@ +rebuild(); + } + + return $map; +} + +/** + * Get list of file extensions which allowed for autoloading. + * + * @return string[] + * List of extensions. + */ +function autoload_extensions() { + $extensions = array_filter(explode(',', spl_autoload_extensions()), 'trim'); + // Make sure the basic extensions are registered! + $extensions[] = '.php'; + $extensions[] = '.inc'; + + return array_unique($extensions); +} diff --git a/dkan/modules/contrib/autoload/src/Tests/Unit/AutoloadTestBase.php b/dkan/modules/contrib/autoload/src/Tests/Unit/AutoloadTestBase.php new file mode 100644 index 000000000..d457a5c80 --- /dev/null +++ b/dkan/modules/contrib/autoload/src/Tests/Unit/AutoloadTestBase.php @@ -0,0 +1,80 @@ + end($parts), + // The "1" will always contain the name of module. + 'group' => $parts[1], + 'description' => $description, + ); + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + $this->autoloadFile = drupal_get_path('module', 'autoload') . '/tests/autoload.php'; + + parent::setUp(static::$modules); + + $this->assertTrue( + variable_get('autoload_file') === $this->autoloadFile, + sprintf('The autoloading class map will be used from the "%s" file.', $this->autoloadFile) + ); + } + + /** + * {@inheritdoc} + */ + protected function preloadRegistry() { + parent::preloadRegistry(); + + // Tests, possibly, doesn't have a writing permissions to a filesystem. + // So, let's just use actual generated file. + // + // WE MUST set this variable here and in this way because "setUp()" runs + // "resetAll()" method which triggers Drupal cache clearing during which + // a lot of modules start collecting the data to fill the cache (e.g. + // "entity", "views" etc.). In a case of using autoloading in one of the + // hooks providing the data, we'll get a fatal error not doing this. + /* @see entity_views_data() */ + variable_set('autoload_file', $this->autoloadFile); + } + +} diff --git a/dkan/modules/contrib/autoload/src/Tests/Unit/CacheTest.php b/dkan/modules/contrib/autoload/src/Tests/Unit/CacheTest.php new file mode 100644 index 000000000..b2bc3f6af --- /dev/null +++ b/dkan/modules/contrib/autoload/src/Tests/Unit/CacheTest.php @@ -0,0 +1,83 @@ +rebuild(); + + if ($this->assertTrue(is_readable($autoload_file), sprintf('The autoloading map successfully saved to the "%s" file.', $autoload_file))) { + $map = array(); + + // Check implementation of "\Iterator" interface. + foreach ($autoload as $namespace => $data) { + $map[$namespace] = $data; + } + + foreach (array( + 'file' => $autoload_file, + 'data' => $map, + ) as $property => $value) { + $reflection = new \ReflectionProperty($autoload, $property); + $reflection->setAccessible(TRUE); + $this->assertTrue($reflection->getValue($autoload) === $value, sprintf('The "%s" property of the "%s" class has expected value.', $property, get_class($autoload))); + $reflection->setAccessible(FALSE); + } + + $dump = require $autoload_file; + // Check implementation of "\Countable" interface. + $count = count($autoload); + + $this->assertTrue(in_array($count, array(count($map), count($dump)), TRUE), sprintf('The autoloading class map successfully dumped %d entries.', $count)); + $this->assertTrue($map === $dump, 'The autoloading class map successfully interpreted as PHP code.'); + } + + // The class is defined within a map but does not actually available + // since here we have a testing scenario that doesn't affect on the + // autoloading process. + /* @see autoload_test_lookup_autoload_lookup_alter() */ + $this->assertTrue( + isset($autoload['ArchiverInterface']), + 'A custom definition has been successfully provided by lookup alteration.' + ); + + foreach (array( + 'bla' => array(), + 'test' => 1, + // Correct provider but non-existent file. + 'alpha' => array( + 'file' => 'asdasd', + 'provider' => 'autoload', + ), + ) as $namespace => $data) { + try { + $autoload[$namespace] = $data; + $this->fail('An attempt to set incorrect value ended up with a success.'); + } + catch (\Exception $e) { + } + } + } + +} diff --git a/dkan/modules/contrib/autoload/src/Tests/Unit/CustomTest.php b/dkan/modules/contrib/autoload/src/Tests/Unit/CustomTest.php new file mode 100644 index 000000000..3d00eb4b9 --- /dev/null +++ b/dkan/modules/contrib/autoload/src/Tests/Unit/CustomTest.php @@ -0,0 +1,64 @@ +assertFalse( + isset($autoload['AutoloadWrongNamespace\WrongNamespace']), + 'A correct class does not exist in the autoloading map due to a wrong autoloading declaration.' + ); + $this->assertFalse( + class_exists('AutoloadWrongNamespace\WrongNamespace'), + 'A class cannot be loaded because of wrongly defined autoloading.' + ); + + // That's how namespace will be stored in a mapping. + $this->assertTrue( + isset($autoload['Autoload\WrongNamespace\WrongNamespace']), + 'A non-existent class within the autoloading map because autoloading was wrongly declared.' + ); + $this->assertFalse( + class_exists('Autoload\WrongNamespace\WrongNamespace'), + 'Cannot load non-existent class even if it is defined in a class map.' + ); + + // Despite on wrong namespace path, the path to file is correct and it'll + // be included by autoloader after an attempt to load the wrong namespace. + // This will allow a correct namespace to work. + $this->assertTrue( + class_exists('AutoloadWrongNamespace\WrongNamespace'), + 'A correct class became available because of including the file during appealing to non-existent class.' + ); + } + +} diff --git a/dkan/modules/contrib/autoload/src/Tests/Unit/DrupalTest.php b/dkan/modules/contrib/autoload/src/Tests/Unit/DrupalTest.php new file mode 100644 index 000000000..60522520c --- /dev/null +++ b/dkan/modules/contrib/autoload/src/Tests/Unit/DrupalTest.php @@ -0,0 +1,31 @@ +assertTrue( + class_exists($entity_info['views controller class']), + 'Views controller class for entity loaded successfully.' + ); + } + +} diff --git a/dkan/modules/contrib/autoload/src/Tests/Unit/ExtensionsTest.php b/dkan/modules/contrib/autoload/src/Tests/Unit/ExtensionsTest.php new file mode 100644 index 000000000..0d141845d --- /dev/null +++ b/dkan/modules/contrib/autoload/src/Tests/Unit/ExtensionsTest.php @@ -0,0 +1,51 @@ +assertExtensions(array('.php', '.inc')); + + // Only Drupal can change this. Modules cannot! + spl_autoload_extensions('.test'); + + // List of extensions changed as expected. + $this->assertExtensions(array('.php', '.inc', '.test')); + // Class must not exist since an extension was registered not by Drupal. + $this->assertFalse( + class_exists('Drupal\autoload_test_extensions\PSR4'), + 'A class cannot be loaded despite on registered file extension.' + ); + } + + /** + * Assert registered extensions for autoloading. + * + * @param string[] $extensions + * Extensions list. Each one must start from dot. + */ + protected function assertExtensions(array $extensions) { + $this->assertFalse(array_diff(autoload_extensions(), $extensions), 'Required extensions available.'); + } + +} diff --git a/dkan/modules/contrib/autoload/tests/autoload.php b/dkan/modules/contrib/autoload/tests/autoload.php new file mode 100644 index 000000000..79ed544d0 --- /dev/null +++ b/dkan/modules/contrib/autoload/tests/autoload.php @@ -0,0 +1,43 @@ + array( + 'file' => 'sites/all/modules/autoload/tests/autoload_test_custom/psr-0/Autoload/Tests/PSR0.inc', + 'provider' => 'autoload_test_custom', + ), + 'Autoload\\Tests\\PSR4' => array( + 'file' => 'sites/all/modules/autoload/tests/autoload_test_custom/psr-4/PSR4.inc', + 'provider' => 'autoload_test_custom', + ), + 'AutoloadTests\\PSR4' => array( + 'file' => 'sites/all/modules/autoload/tests/autoload_test_custom/psr-4-single-level-namespace/PSR4.inc', + 'provider' => 'autoload_test_custom', + ), + 'Autoload\\Tests\\Example\\Test' => array( + 'file' => 'sites/all/modules/autoload/tests/autoload_test_custom/tests/Test.inc', + 'provider' => 'autoload_test_custom', + ), + 'Autoload\\WrongNamespace\\WrongNamespace' => array( + 'file' => 'sites/all/modules/autoload/tests/autoload_test_custom/wrong-namespace/WrongNamespace.php', + 'provider' => 'autoload_test_custom', + ), + 'Drupal\\autoload_test_drupal\\PSR0' => array( + 'file' => 'sites/all/modules/autoload/tests/autoload_test_drupal/lib/Drupal/autoload_test_drupal/PSR0.php', + 'provider' => 'autoload_test_drupal', + ), + 'Drupal\\autoload_test_drupal\\PSR4' => array( + 'file' => 'sites/all/modules/autoload/tests/autoload_test_drupal/src/PSR4.inc', + 'provider' => 'autoload_test_drupal', + ), + 'Drupal\\autoload_test_entity_ui\\ViewsController' => array( + 'file' => 'sites/all/modules/autoload/tests/autoload_test_entity/modules/autoload_test_entity_ui/src/ViewsController.php', + 'provider' => 'autoload_test_entity_ui', + ), +); diff --git a/dkan/modules/contrib/autoload/tests/autoload_test_custom/autoload_test_custom.info b/dkan/modules/contrib/autoload/tests/autoload_test_custom/autoload_test_custom.info new file mode 100644 index 000000000..74680e1a9 --- /dev/null +++ b/dkan/modules/contrib/autoload/tests/autoload_test_custom/autoload_test_custom.info @@ -0,0 +1,26 @@ +name = Autolaod Custom Test +description = Testing autoloading of custom namespaces. +hidden = TRUE +core = 7.x + +; Wrong declaration! +autoload = Autoload +; Try PSR-0 standard. +autoload[psr-0][] = Autoload +; Try PSR-4 standard. +autoload[psr-4][] = Autoload\Tests +; Try PSR-4 standard with single-leveled namespace defined. +autoload[psr-4-single-level-namespace][] = AutoloadTests\ +; Try to fail autoloading in the same namespace but in another directory. +; @see https://www.drupal.org/node/2783081 +autoload[tests][] = Autoload\Tests\Example +autoload[wrong-namespace][] = Autoload\WrongNamespace +; Wrong declaration! +autoload[] = Autoload + +dependencies[] = autoload + +; Information added by drush on 2017-07-24 +version = "7.x-2.0+0-dev" +project = "autoload" +datestamp = "1500910353" \ No newline at end of file diff --git a/dkan/modules/contrib/autoload/tests/autoload_test_custom/autoload_test_custom.module b/dkan/modules/contrib/autoload/tests/autoload_test_custom/autoload_test_custom.module new file mode 100644 index 000000000..6741d7016 --- /dev/null +++ b/dkan/modules/contrib/autoload/tests/autoload_test_custom/autoload_test_custom.module @@ -0,0 +1,6 @@ + 'Autoload Test Entity', + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'The primary identifier.', + ), + ), + 'primary key' => array('id'), + ); + + return $schema; +} diff --git a/dkan/modules/contrib/autoload/tests/autoload_test_entity/autoload_test_entity.module b/dkan/modules/contrib/autoload/tests/autoload_test_entity/autoload_test_entity.module new file mode 100644 index 000000000..db5619742 --- /dev/null +++ b/dkan/modules/contrib/autoload/tests/autoload_test_entity/autoload_test_entity.module @@ -0,0 +1,22 @@ + t('Autoload Test Entity'), + 'base table' => 'autoload_test_entity', + 'controller class' => 'EntityAPIController', + 'entity keys' => array('id' => 'id'), + ); + + return $info; +} diff --git a/dkan/modules/contrib/autoload/tests/autoload_test_entity/modules/autoload_test_entity_ui/autoload_test_entity_ui.info b/dkan/modules/contrib/autoload/tests/autoload_test_entity/modules/autoload_test_entity_ui/autoload_test_entity_ui.info new file mode 100644 index 000000000..eeac40b75 --- /dev/null +++ b/dkan/modules/contrib/autoload/tests/autoload_test_entity/modules/autoload_test_entity_ui/autoload_test_entity_ui.info @@ -0,0 +1,13 @@ +name = Autoload Entity Test UI +hidden = TRUE +core = 7.x + +autoload = TRUE + +dependencies[] = autoload_test_entity +dependencies[] = views + +; Information added by drush on 2017-07-24 +version = "7.x-2.0+0-dev" +project = "autoload" +datestamp = "1500910353" \ No newline at end of file diff --git a/dkan/modules/contrib/autoload/tests/autoload_test_entity/modules/autoload_test_entity_ui/autoload_test_entity_ui.module b/dkan/modules/contrib/autoload/tests/autoload_test_entity/modules/autoload_test_entity_ui/autoload_test_entity_ui.module new file mode 100644 index 000000000..fd40e1268 --- /dev/null +++ b/dkan/modules/contrib/autoload/tests/autoload_test_entity/modules/autoload_test_entity_ui/autoload_test_entity_ui.module @@ -0,0 +1,13 @@ + DRUPAL_ROOT . '/includes/archiver.inc', + 'provider' => 'autoload_test_lookup', + ); +} diff --git a/dkan/modules/contrib/better_exposed_filters/better_exposed_filters.api.php b/dkan/modules/contrib/better_exposed_filters/better_exposed_filters.api.php index 91485192d..7622e248e 100644 --- a/dkan/modules/contrib/better_exposed_filters/better_exposed_filters.api.php +++ b/dkan/modules/contrib/better_exposed_filters/better_exposed_filters.api.php @@ -1,16 +1,19 @@ display as $display) { - if (!empty($display->display_options['exposed_form']['type']) && 'better_exposed_filters' == $display->display_options['exposed_form']['type']) { - $warnings[] = t('The %display_title display in the %view_name view. (Update this display)', - array( - '%display_title' => $display->display_title, - '%view_name' => $view->human_name, - '@link' => url('admin/structure/views/view/' . $view->name . '/edit/' . $display->id), - ) - ); - } - } - } - - if (!empty($warnings)) { - $message = t('The following Views displays are using the Better Exposed Filters plugin, which is no longer enabled. It is recommended that you update these displays (links open in a new window) before removing the code associated with this module. Not doing so may cause unexpected results.'); - $message .= '
  • ' . join('
  • ', $warnings) . '
'; - drupal_set_message($message, 'warning'); - } -} - -/* - * Implementations of hook_update_N. - * - * Comments above each function appear in the update database message. - */ -/** - * Rebuild the theme registry to discover new theme_bef_checkbox() function. - */ -function better_exposed_filters_update_7000() { - drupal_theme_rebuild(); - return t('Theme registry has been rebuilt.'); -} +display as $display) { + if (!empty($display->display_options['exposed_form']['type']) && 'better_exposed_filters' == $display->display_options['exposed_form']['type']) { + $warnings[] = t('The %display_title display in the %view_name view. (Update this display)', + array( + '%display_title' => $display->display_title, + '%view_name' => $view->human_name, + '@link' => url('admin/structure/views/view/' . $view->name . '/edit/' . $display->id), + ) + ); + } + } + } + + if (!empty($warnings)) { + $message = t('The following Views displays are using the Better Exposed Filters plugin, which is no longer enabled. It is recommended that you update these displays (links open in a new window) before removing the code associated with this module. Not doing so may cause unexpected results.'); + $message .= '
  • ' . join('
  • ', $warnings) . '
'; + drupal_set_message($message, 'warning'); + } +} + +/* + * Implementations of hook_update_N. + * + * Comments above each function appear in the update database message. + */ +/** + * Rebuild the theme registry to discover new theme_bef_checkbox() function. + */ +function better_exposed_filters_update_7000() { + drupal_theme_rebuild(); + return t('Theme registry has been rebuilt.'); +} diff --git a/dkan/modules/contrib/better_exposed_filters/better_exposed_filters.js b/dkan/modules/contrib/better_exposed_filters/better_exposed_filters.js index 40da193d5..e6d8348d9 100644 --- a/dkan/modules/contrib/better_exposed_filters/better_exposed_filters.js +++ b/dkan/modules/contrib/better_exposed_filters/better_exposed_filters.js @@ -318,21 +318,37 @@ // We have to prevent the page load triggered by the links. event.preventDefault(); event.stopPropagation(); - // Un select old select value. - $wrapper.find('select option').removeAttr('selected'); + // Un select if previously seleted toogle is selected. + var link_text = $(this).text(); + removed = ''; + $($options).each(function(i) { + if ($(this).attr('selected')) { + if (link_text == $(this).text()) { + removed = $(this).text(); + $(this).removeAttr('selected'); + } + } + }); // Set the corresponding option inside the select element as selected. - var link_text = $(this).text(); $selected = $options.filter(function() { - return $(this).text() == link_text; + return $(this).text() == link_text && removed != link_text; }); $selected.attr('selected', 'selected'); $wrapper.find('.bef-new-value').val($selected.val()); - $wrapper.find('a').removeClass('active'); + $wrapper.find('.bef-new-value[value=""]').attr("disabled", "disabled"); $(this).addClass('active'); // Submit the form. $wrapper.parents('form').find('.views-submit-button *[type=submit]').click(); }); + + $('.bef-select-as-link').ready(function() { + $('.bef-select-as-link').find('a').removeClass('active'); + $('.bef-new-value').each(function(i, val) { + id = $(this).parent().find('select').attr('id') + '-' + $(this).val(); + $('#'+id).find('a').addClass('active'); + }); + }); }); } }; diff --git a/dkan/modules/contrib/better_exposed_filters/better_exposed_filters.theme b/dkan/modules/contrib/better_exposed_filters/better_exposed_filters.theme index 98e7cd8f3..f97d9be8a 100644 --- a/dkan/modules/contrib/better_exposed_filters/better_exposed_filters.theme +++ b/dkan/modules/contrib/better_exposed_filters/better_exposed_filters.theme @@ -105,7 +105,8 @@ function theme_select_as_checkboxes($vars) { // Check for Taxonomy-based filters. if (is_object($elem)) { $slice = array_slice($elem->option, 0, 1, TRUE); - list($option, $elem) = each($slice); + $option = key($slice); + $elem = current($slice); } // Check for optgroups. Put subelements in the $element_set array and add @@ -176,7 +177,8 @@ function theme_select_as_hidden($vars) { // Check for Taxonomy-based filters. if (is_object($elem)) { $slice = array_slice($elem->option, 0, 1, TRUE); - list($option, $elem) = each($slice); + $option = key($slice); + $elem = current($slice); } // Check for optgroups. Put subelements in the $element_set array and add a @@ -331,7 +333,8 @@ function theme_select_as_tree($vars) { // Check for Taxonomy-based filters. if (is_object($option_label)) { $slice = array_slice($option_label->option, 0, 1, TRUE); - list($option_value, $option_label) = each($slice); + $option_value = key($slice); + $option_label = current($slice); } // Check for optgroups -- which is basically a two-level deep tree. @@ -518,7 +521,8 @@ function theme_select_as_links($vars) { // Check for Taxonomy-based filters. if (is_object($elem)) { $slice = array_slice($elem->option, 0, 1, TRUE); - list($option, $elem) = each($slice); + $option = key($slice); + $elem = current($slice); } // Check for optgroups. Put subelements in the $element_set array and add @@ -556,8 +560,11 @@ function theme_select_as_links($vars) { // Add "active" class to the currently active filter link. if (in_array((string) $key, $selected_options)) { $link_options['attributes'] = array('class' => array('active')); + $url = bef_replace_query_string_arg($name, $key, $multiple, TRUE, $path); + } + else { + $url = bef_replace_query_string_arg($name, $key, $multiple, FALSE, $path); } - $url = bef_replace_query_string_arg($name, $key, $multiple, FALSE, $path); $elem['#children'] = l($value, $url, $link_options); $element_output = theme('form_element', array('element' => $elem)); diff --git a/dkan/modules/contrib/better_exposed_filters/better_exposed_filters.views.inc b/dkan/modules/contrib/better_exposed_filters/better_exposed_filters.views.inc index d91d2f5bf..57253ca94 100644 --- a/dkan/modules/contrib/better_exposed_filters/better_exposed_filters.views.inc +++ b/dkan/modules/contrib/better_exposed_filters/better_exposed_filters.views.inc @@ -1,4 +1,5 @@ localization_keys = $this->unpack_translatable_keys(); $this->unpack_options($this->options, $options); - //$this->unpack_options($this->options, $options, NULL, FALSE); + // $this->unpack_options($this->options, $options, NULL, FALSE);. } function summary_title() { @@ -48,7 +52,7 @@ class better_exposed_filters_exposed_form_plugin extends views_plugin_exposed_fo '#type' => 'checkbox', '#default_value' => $existing['general']['input_required'], '#title' => t('Require input before results are shown'), - '#description' => t("Emulates the built in Input Required exposed filter handler") + '#description' => t("Emulates the built in Input Required exposed filter handler"), ); $bef_options['general']['text_input_required'] = array( @@ -104,7 +108,7 @@ class better_exposed_filters_exposed_form_plugin extends views_plugin_exposed_fo '#default_value' => $existing['general']['secondary_collapse_override'], '#description' => t( 'This setting overrides the secondary options fieldset collapsed value. By default the fieldset renders opened if a value within is selected and closed otherwise.' - ), + ), '#options' => array( 0 => t('Default'), 1 => t('Always open'), @@ -167,7 +171,7 @@ class better_exposed_filters_exposed_form_plugin extends views_plugin_exposed_fo '#suffix' => '', '#states' => array( 'visible' => array( - 'input[name="exposed_form_options[bef][sort][advanced][collapsible]"]' => array('checked' => TRUE) + 'input[name="exposed_form_options[bef][sort][advanced][collapsible]"]' => array('checked' => TRUE), ), ), ); @@ -178,7 +182,7 @@ class better_exposed_filters_exposed_form_plugin extends views_plugin_exposed_fo '#description' => t('Combines the sort by options and order (ascending or decending) into a single list. Use this to display "Option1 Desc", "Option1 Asc", "Option2 Desc", "Option2 Asc" in a single form element. "Expose sort order" must be checked to enable this option.'), '#states' => array( 'disabled' => array( - 'input[name="exposed_form_options[expose_sort_order]"]' => array('checked' => FALSE) + 'input[name="exposed_form_options[expose_sort_order]"]' => array('checked' => FALSE), ), ), ); @@ -232,7 +236,7 @@ Title Desc|Z -> A Leave the replacement text blank to remove an option alt '#suffix' => '', '#states' => array( 'required' => array( - 'input[name="exposed_form_options[bef][sort][advanced][reset]"]' => array('checked' => TRUE), + 'input[name="exposed_form_options[bef][sort][advanced][reset]"]' => array('checked' => TRUE), ), 'visible' => array( 'input[name="exposed_form_options[bef][sort][advanced][reset]"]' => array('checked' => TRUE), @@ -432,8 +436,8 @@ Title Desc|Z -> A Leave the replacement text blank to remove an option alt $bef_hidden = TRUE; } } - // Search API numeric filters support - elseif ($filter instanceof SearchApiViewsHandlerFilter && !($filter instanceof SearchApiViewsHandlerFilterFulltext)) { + // Search API numeric filters support. + elseif ($filter instanceof SearchApiViewsHandlerFilter && !($filter instanceof SearchApiViewsHandlerFilterFulltext)) { $bef_slider = TRUE; } elseif ($filter instanceof SearchApiViewsHandlerFilterBoolean) { @@ -478,7 +482,7 @@ Title Desc|Z -> A Leave the replacement text blank to remove an option alt $filter_key = $filter->options['is_grouped'] ? 'group_info' : 'expose'; $identifier = '"' . $filter->options[$filter_key]['identifier'] . '"'; if (!empty($filter->options[$filter_key]['label'])) { - $identifier .= t(' (Filter label: "@fl")', array('@fl' => $filter->options[$filter_key]['label'])); + $identifier .= ' ' . t('(Filter label: "@fl")', array('@fl' => $filter->options[$filter_key]['label'])); } $bef_options[$label]['bef_format'] = array( '#type' => 'select', @@ -512,7 +516,11 @@ Title Desc|Z -> A Leave the replacement text blank to remove an option alt ), ), '#description' => t('The minimum allowed value for the jQuery range slider. It can be positive, negative, or zero and have up to 11 decimal places.'), - '#element_validate' => array('element_validate_number', 'better_exposed_filters_element_validate_slider_required', 'better_exposed_filters_element_validate_slider_min_max'), + '#element_validate' => array( + 'element_validate_number', + 'better_exposed_filters_element_validate_slider_required', + 'better_exposed_filters_element_validate_slider_min_max', + ), ); $bef_options[$label]['slider_options']['bef_slider_max'] = array( '#type' => 'textfield', @@ -525,7 +533,11 @@ Title Desc|Z -> A Leave the replacement text blank to remove an option alt ), ), '#description' => t('The maximum allowed value for the jQuery range slider. It can be positive, negative, or zero and have up to 11 decimal places.'), - '#element_validate' => array('element_validate_number', 'better_exposed_filters_element_validate_slider_required', 'better_exposed_filters_element_validate_slider_min_max'), + '#element_validate' => array( + 'element_validate_number', + 'better_exposed_filters_element_validate_slider_required', + 'better_exposed_filters_element_validate_slider_min_max', + ), ); $bef_options[$label]['slider_options']['bef_slider_step'] = array( '#type' => 'textfield', @@ -538,9 +550,13 @@ Title Desc|Z -> A Leave the replacement text blank to remove an option alt ), ), '#description' => t('Determines the size or amount of each interval or step the slider takes between the min and max.') . '
' . - t('The full specified value range of the slider (Range maximum - Range minimum) must be evenly divisible by the step.') . '
' . - t('The step must be a positive number of up to 5 decimal places.'), - '#element_validate' => array('element_validate_number', 'better_exposed_filters_element_validate_slider_required', 'better_exposed_filters_element_validate_slider_step'), + t('The full specified value range of the slider (Range maximum - Range minimum) must be evenly divisible by the step.') . '
' . + t('The step must be a positive number of up to 5 decimal places.'), + '#element_validate' => array( + 'element_validate_number', + 'better_exposed_filters_element_validate_slider_required', + 'better_exposed_filters_element_validate_slider_step', + ), ); $bef_options[$label]['slider_options']['bef_slider_animate'] = array( '#type' => 'textfield', @@ -664,7 +680,7 @@ Title Desc|Z -> A Leave the replacement text blank to remove an option alt '#type' => 'textfield', '#title' => t('Override "Any" option label'), '#default_value' => $existing[$label]['more_options']['any_label'], - '#description' => t('Leave blank to use Views\' default value.'), + '#description' => t('Leave blank to use Views default value.'), ); // Build a description option form element -- available to all exposed @@ -707,7 +723,6 @@ Title Desc|Z -> A Leave the replacement text blank to remove an option alt } else { $bef_options[$label]['more_options']['tokens']['list'] = array( - //'#title' => t('Filter-specific tokens'), '#theme' => 'token_tree', '#token_types' => $filter_specific, '#global_types' => FALSE, @@ -785,9 +800,9 @@ dateFormat: "dd-mm-yy" * Tweak the exposed filter form to show Better Exposed Filter options. * * @param array $form - * Exposed form array + * Exposed form array. * @param array $form_state - * Current state of form variables + * Current state of form variables. */ function exposed_form_alter(&$form, &$form_state) { parent::exposed_form_alter($form, $form_state); @@ -903,16 +918,16 @@ dateFormat: "dd-mm-yy" // will be the first one if there are multiple sort criteria. $selected = "$by_key $order_key"; } - if ($settings['sort']['bef_format'] == 'bef_toggle_links') { - if (isset($used_sort_keys[$by_key]) + if ($settings['sort']['bef_format'] == 'bef_toggle_links') { + if (isset($used_sort_keys[$by_key]) || (!empty($form_state['input'][$settings['sort']['advanced']['combine_param']]) && $form_state['input'][$settings['sort']['advanced']['combine_param']] == "$by_key $order_key") || (empty($form_state['input'][$settings['sort']['advanced']['combine_param']]) && $selected == "$by_key $order_key") - ) { + ) { $hidden_options["$by_key $order_key"] = "$by_val $order_val"; - } - else { + } + else { $used_sort_keys[$by_key] = $order_key; - } + } } } } @@ -950,7 +965,7 @@ dateFormat: "dd-mm-yy" '#settings' => array( 'toggle_links' => ($settings['sort']['bef_format'] == 'bef_toggle_links'), 'combine_param' => $settings['sort']['advanced']['combine_param'], - ), + ), '#default_value' => $selected, // Already sanitized by Views. '#title' => $form['sort_by']['#title'], @@ -1198,10 +1213,10 @@ dateFormat: "dd-mm-yy" } else { if ($is_object) { - // dsm($form[$filter_id]['#options'][$index]->option, "$filter_id at $index"); // Taxonomy term filters are stored as objects. Use str_replace // to ensure that keep hyphens for hierarchical filters. - list($tid, $original) = each($form[$filter_id]['#options'][$index]->option); + $tid = key($form[$filter_id]['#options'][$index]->option); + $original = current($form[$filter_id]['#options'][$index]->option); $form[$filter_id]['#options'][$index]->option[$tid] = str_replace($option, $rewrite[$option], $original); } else { @@ -1341,7 +1356,7 @@ dateFormat: "dd-mm-yy" // Seconds, with leading zeros 00 through 59. // 's' => ' ', // Microseconds (added in PHP 5.2.2) Example: 654321. - // 'u' => ' ', + // 'u' => ' ',. ); $format = ''; @@ -1487,7 +1502,6 @@ dateFormat: "dd-mm-yy" // element to see whether the form is submitted or not and then we // need to look at $_GET directly to see whether the checkbox is // there. For security reasons, we must not copy the $_GET value. - // First, let's figure out a short name for the signal element and // then add it. if (empty($signal)) { @@ -1520,7 +1534,7 @@ dateFormat: "dd-mm-yy" } array_unshift($form[$filter_id]['#process'], 'form_process_radios'); - // Add description + // Add description. if (!empty($form[$filter_id]['#bef_description'])) { $form[$filter_id]['#description'] = $form[$filter_id]['#bef_description']; } @@ -1532,7 +1546,8 @@ dateFormat: "dd-mm-yy" foreach ($opts as $index => $opt) { if (is_object($opt)) { reset($opt->option); - list($key, $val) = each($opt->option); + $key = key($opt->option); + $val = current($opt->option); $form[$filter_id]['#options'][$key] = $val; } else { @@ -1575,7 +1590,7 @@ dateFormat: "dd-mm-yy" if ($filters[$label]->options['expose']['use_operator']) { $operator_id = $filters[$label]->options['expose']['operator_id']; $form[$filter_id]['#bef_operator'] = $form[$operator_id]; - unset ($form[$operator_id]); + unset($form[$operator_id]); } // Add collapse/expand Javascript and BEF CSS to prevent collapsed @@ -1624,7 +1639,7 @@ dateFormat: "dd-mm-yy" if ($filters[$label]->options['expose']['use_operator']) { $operator_id = $filters[$label]->options['expose']['operator_id']; $form[$filter_id]['#bef_operator'] = $form[$operator_id]; - unset ($form[$operator_id]); + unset($form[$operator_id]); } // Add collapse/expand Javascript and BEF CSS to prevent collapsed @@ -1667,7 +1682,7 @@ dateFormat: "dd-mm-yy" foreach ($form[$filter_id]['#options'] as $tid => $option) { if (is_object($option)) { reset($option->option); - list ($tid, ) = each($option->option); + $tid = key($option->option); } $tids[] = $tid; } @@ -1753,11 +1768,11 @@ dateFormat: "dd-mm-yy" // along to the theme function for rendering. Multiply existing // position by 2 so that we have room to stick the exposed operator // before the filter. - if (isset($filters[$identifier]->position)) { - $secondary[$identifier]['#bef_position'] = $filters[$identifier]->position * 2; + if (isset($filters[$label]->position)) { + $secondary[$identifier]['#bef_position'] = $filters[$label]->position * 2; } - // Move exposed operators with exposed filters + // Move exposed operators with exposed filters. if (!empty($filters[$label]->options['expose']['use_operator'])) { $op_id = $filters[$label]->options['expose']['operator_id']; $secondary[$op_id] = $form[$op_id]; @@ -1765,8 +1780,8 @@ dateFormat: "dd-mm-yy" // Make sure operators appear just before the filter they are // associated with. - if (isset($filters[$identifier]->position)) { - $secondary[$op_id]['#bef_position'] = ($filters[$identifier]->position * 2) - 1; + if (isset($filters[$label]->position)) { + $secondary[$op_id]['#bef_position'] = ($filters[$label]->position * 2) - 1; } } } @@ -1913,7 +1928,7 @@ dateFormat: "dd-mm-yy" /** * Returns an array of default or current existing values for BEF settings. * - * This helps us as we add new options and prevents a lot of + * This helps us as we add new options and prevents a lot of. * @code * if (isset($settings['new_settings'])) { ... } * @endcode @@ -2012,7 +2027,7 @@ dateFormat: "dd-mm-yy" /** * Utility function to determine if any filters have been applied. - * Borrowed from views_plugin_exposed_form_input_required + * Borrowed from views_plugin_exposed_form_input_required. */ function exposed_filter_applied() { static $cache = NULL; @@ -2098,11 +2113,27 @@ dateFormat: "dd-mm-yy" $keys = array( // @TODO: Do we need to give this a better key so it makes more sense in // the localization UI? - 'value' => array('bef', 'general', 'text_input_required', 'text_input_required', 'value'), + 'value' => array( + 'bef', + 'general', + 'text_input_required', + 'text_input_required', + 'value', + ), 'general_secondary_label' => array('bef', 'general', 'secondary_label'), - 'sort_collapsible_label' => array('bef', 'sort', 'advanced', 'collapsible_label'), - 'sort_combine_rewrite' => array('bef', 'sort', 'advanced', 'combine_rewrite'), + 'sort_collapsible_label' => array( + 'bef', + 'sort', + 'advanced', + 'collapsible_label', + ), + 'sort_combine_rewrite' => array( + 'bef', + 'sort', + 'advanced', + 'combine_rewrite', + ), 'sort_reset_label' => array('bef', 'sort', 'advanced', 'reset_label'), ); @@ -2111,10 +2142,31 @@ dateFormat: "dd-mm-yy" if (!$filter->options['exposed']) { continue; } - $keys[$label . '_filter_description'] = array('bef', $label, 'more_options', 'bef_filter_description'); - $keys[$label . '_any_label'] = array('bef', $label, 'more_options', 'any_label'); - $keys[$label . '_rewrite_values'] = array('bef', $label, 'more_options', 'rewrite', 'filter_rewrite_values'); - $keys[$label . '_datepicker_options'] = array('bef', $label, 'more_options', 'datepicker_options'); + $keys[$label . '_filter_description'] = array( + 'bef', + $label, + 'more_options', + 'bef_filter_description', + ); + $keys[$label . '_any_label'] = array( + 'bef', + $label, + 'more_options', + 'any_label', + ); + $keys[$label . '_rewrite_values'] = array( + 'bef', + $label, + 'more_options', + 'rewrite', + 'filter_rewrite_values', + ); + $keys[$label . '_datepicker_options'] = array( + 'bef', + $label, + 'more_options', + 'datepicker_options', + ); } return $keys; @@ -2128,8 +2180,8 @@ dateFormat: "dd-mm-yy" $parent = end($parents); $value = isset($options[$parent]) ? $options[$parent] : NULL; - // Don't localize strings during editing. When editing, we need to work with - // the original data, not the translated version. + // Don't localize strings during editing. When editing, we need to work + // with the original data, not the translated version. if (empty($this->view->editing) && !empty($value)) { if (!empty($this->view) && $this->view->is_translatable()) { // Allow other modules to make changes to the string before it's @@ -2148,9 +2200,10 @@ dateFormat: "dd-mm-yy" $storage[$parent] = t($value); } } - else if (!empty($value)) { + elseif (!empty($value)) { $storage[$parent] = $value; } } } + } diff --git a/dkan/modules/contrib/better_exposed_filters/tests/bef_test_content/bef_test_content.info b/dkan/modules/contrib/better_exposed_filters/tests/bef_test_content/bef_test_content.info index 7ac42b846..164268c7c 100644 --- a/dkan/modules/contrib/better_exposed_filters/tests/bef_test_content/bef_test_content.info +++ b/dkan/modules/contrib/better_exposed_filters/tests/bef_test_content/bef_test_content.info @@ -41,9 +41,8 @@ features_exclude[field][node-bef_test-field_price] = node-bef_test-field_price features_exclude[field][node-bef_test-field_date] = node-bef_test-field_date hidden = 1 -; Information added by Drupal.org packaging script on 2017-10-25 -version = "7.x-3.5" +; Information added by Drupal.org packaging script on 2018-09-03 +version = "7.x-3.6" core = "7.x" project = "better_exposed_filters" -datestamp = "1508952850" - +datestamp = "1536016685" diff --git a/dkan/modules/contrib/better_exposed_filters/tests/bef_test_content/bef_test_content.install b/dkan/modules/contrib/better_exposed_filters/tests/bef_test_content/bef_test_content.install index ed3b3175b..89fe22fd5 100644 --- a/dkan/modules/contrib/better_exposed_filters/tests/bef_test_content/bef_test_content.install +++ b/dkan/modules/contrib/better_exposed_filters/tests/bef_test_content/bef_test_content.install @@ -1,81 +1,81 @@ - array( - 'California' => array( - 'San Francisco', - 'San Diego', - 'Santa Barbara', - ), - 'Oregon' => array( - 'Portland', - 'Eugene', - ), - 'Washington' => array( - 'Seattle', - 'Spokane', - 'Walla Walla', - ), - ), - 'Canada' => array( - 'British Columbia' => array( - 'Vancouver', - 'Victoria', - 'Whistler', - ), - 'Alberta' => array( - 'Calgary', - 'Edmonton', - 'Lake Louise', - ), - ), - 'Mexico' => array(), - ); - foreach ($locations as $country => $states) { - $country_tid = _bef_test_content_add_term($country); - if ($country_tid && !empty($states)) { - foreach ($states as $state => $cities) { - $state_tid = _bef_test_content_add_term($state, $country_tid); - if ($state_tid && !empty($cities)) { - foreach ($cities as $city) { - _bef_test_content_add_term($city, $state_tid); - } - } - } - } - } -} - -/** - * Adds a new term to the bef_test-location vocabulary. If a TID is specified - * in $parent, the new term is added as a child of that term. - * - * @param string $name - * The name of the new term. - * @param int $parent - * The (optional) TID of the parent term. - * - * @return int - * TID of the newly created term or 0 on an error. - */ -function _bef_test_content_add_term($name, $parent = 0) { - $term = new stdClass(); - // Features manages to create a vocab machine name that includes illegal - // characters (taxonomy-bef_test-location -- the hyphen is not allowed). So - // we use the VID of the vocab instead. - $term->vid = 2; - $term->parent = $parent; - $term->name = $name; - if (taxonomy_term_save($term) == SAVED_NEW) { - return $term->tid; - } - return 0; -} + array( + 'California' => array( + 'San Francisco', + 'San Diego', + 'Santa Barbara', + ), + 'Oregon' => array( + 'Portland', + 'Eugene', + ), + 'Washington' => array( + 'Seattle', + 'Spokane', + 'Walla Walla', + ), + ), + 'Canada' => array( + 'British Columbia' => array( + 'Vancouver', + 'Victoria', + 'Whistler', + ), + 'Alberta' => array( + 'Calgary', + 'Edmonton', + 'Lake Louise', + ), + ), + 'Mexico' => array(), + ); + foreach ($locations as $country => $states) { + $country_tid = _bef_test_content_add_term($country); + if ($country_tid && !empty($states)) { + foreach ($states as $state => $cities) { + $state_tid = _bef_test_content_add_term($state, $country_tid); + if ($state_tid && !empty($cities)) { + foreach ($cities as $city) { + _bef_test_content_add_term($city, $state_tid); + } + } + } + } + } +} + +/** + * Adds a new term to the bef_test-location vocabulary. If a TID is specified + * in $parent, the new term is added as a child of that term. + * + * @param string $name + * The name of the new term. + * @param int $parent + * The (optional) TID of the parent term. + * + * @return int + * TID of the newly created term or 0 on an error. + */ +function _bef_test_content_add_term($name, $parent = 0) { + $term = new stdClass(); + // Features manages to create a vocab machine name that includes illegal + // characters (taxonomy-bef_test-location -- the hyphen is not allowed). So + // we use the VID of the vocab instead. + $term->vid = 2; + $term->parent = $parent; + $term->name = $name; + if (taxonomy_term_save($term) == SAVED_NEW) { + return $term->tid; + } + return 0; +} diff --git a/dkan/modules/contrib/better_exposed_filters/tests/better_exposed_filters.test b/dkan/modules/contrib/better_exposed_filters/tests/better_exposed_filters.test index 9784b8c19..2169e1c1d 100644 --- a/dkan/modules/contrib/better_exposed_filters/tests/better_exposed_filters.test +++ b/dkan/modules/contrib/better_exposed_filters/tests/better_exposed_filters.test @@ -15,6 +15,7 @@ class BEF_TestOptions extends BEF_TestBase { 'name' => 'BEF Options tests', 'description' => 'Checks that BEF options appear when should.', 'group' => 'Better Exposed Filters', + 'dependencies' => array('date', 'date_views', 'views', 'views_ui') ); } @@ -123,6 +124,7 @@ class BEF_TestRadios extends BEF_TestBase { 'name' => 'BEF radio button tests', 'description' => 'Verifies rendering filters as radio buttons.', 'group' => 'Better Exposed Filters', + 'dependencies' => array('date', 'date_views', 'views', 'views_ui') ); } @@ -179,6 +181,7 @@ class BEF_TestCheckboxes extends BEF_TestBase { 'name' => 'BEF checkbox tests', 'description' => 'Verifies rendering filter options as checkboxes.', 'group' => 'Better Exposed Filters', + 'dependencies' => array('date', 'date_views', 'views', 'views_ui') ); } @@ -247,6 +250,7 @@ class BEF_TestHidden extends BEF_TestBase { 'name' => 'BEF hidden tests', 'description' => 'Verifies rendering filter options as hidden elements.', 'group' => 'Better Exposed Filters', + 'dependencies' => array('date', 'date_views', 'views', 'views_ui') ); } @@ -297,6 +301,7 @@ class BEF_TestTaxonomyFilters extends BEF_TestBase { 'name' => 'BEF taxonomy filter tests', 'description' => 'Verifies rendering of taxonomy filters.', 'group' => 'Better Exposed Filters', + 'dependencies' => array('date', 'date_views', 'views', 'views_ui') ); } @@ -377,6 +382,7 @@ class BEF_TestSort extends BEF_TestBase { 'name' => 'BEF sort tests', 'description' => 'Verifies rendering exposed sort options.', 'group' => 'Better Exposed Filters', + 'dependencies' => array('date', 'date_views', 'views', 'views_ui') ); } @@ -439,6 +445,7 @@ class BEF_TestSliders extends BEF_TestBase { 'name' => 'BEF slider tests', 'description' => 'Verifies rendering filters jQueryUI sliders.', 'group' => 'Better Exposed Filters', + 'dependencies' => array('date', 'date_views', 'views', 'views_ui') ); } @@ -528,6 +535,7 @@ class BEF_TestDatepicker extends BEF_TestBase { 'name' => 'BEF datepicker tests', 'description' => 'Verifies rendering filter options as a jQueryUI Datepicker.', 'group' => 'Better Exposed Filters', + 'dependencies' => array('date', 'date_views', 'views', 'views_ui') ); } @@ -609,6 +617,7 @@ class BEF_TestLinks extends BEF_TestBase { 'name' => 'BEF links tests', 'description' => 'Verifies rendering filter options as toggle links.', 'group' => 'Better Exposed Filters', + 'dependencies' => array('date', 'date_views', 'views', 'views_ui') ); } @@ -718,6 +727,7 @@ class BEF_TestSecondaryFilters extends BEF_TestBase { 'name' => 'BEF secondary filter tests', 'description' => 'Verifies rendering filter options within the secondary filter fieldset.', 'group' => 'Better Exposed Filters', + 'dependencies' => array('date', 'date_views', 'views', 'views_ui') ); } @@ -826,6 +836,7 @@ class BEF_TestRewrite extends BEF_TestBase { 'name' => 'BEF rewrite tests', 'description' => 'Verifies rewriting filter and sort options.', 'group' => 'Better Exposed Filters', + 'dependencies' => array('date', 'date_views', 'views', 'views_ui') ); } @@ -906,6 +917,7 @@ class BEF_TestMisc extends BEF_TestBase { 'name' => 'BEF miscellaneous tests', 'description' => 'Verifies misc BEF functional.', 'group' => 'Better Exposed Filters', + 'dependencies' => array('date', 'date_views', 'views', 'views_ui') ); } diff --git a/dkan/modules/contrib/better_exposed_filters/tests/better_exposed_filters_TestBase.php b/dkan/modules/contrib/better_exposed_filters/tests/better_exposed_filters_TestBase.php index e7082ed44..463e14bac 100644 --- a/dkan/modules/contrib/better_exposed_filters/tests/better_exposed_filters_TestBase.php +++ b/dkan/modules/contrib/better_exposed_filters/tests/better_exposed_filters_TestBase.php @@ -1,275 +1,277 @@ - 'BEF Basic functionality tests', - 'description' => 'Basic tests for Better Exposed Filters.', - 'group' => 'Better Exposed Filters', - ); - } - - public function setUp() { - // For benchmarking. - $this->start = time(); - - // Enable any modules required for the test. - parent::setUp( - 'better_exposed_filters', - 'date', - 'date_views', - 'list', - 'number', - 'taxonomy', - 'text', - 'views', - 'views_ui' - ); - - // One of these days I'll figure out why Features is breaking all my tests. - module_enable(array('bef_test_content')); - - // User with edit views perms - $this->admin_user = $this->drupalCreateUser(); - $role = user_role_load_by_name('administrator'); - $this->assertTrue(!empty($role->rid), 'Found the "administrator" role.'); - user_save($this->admin_user, array('roles' => array($role->rid => $role->rid))); - $this->drupalLogin($this->admin_user); - - // Build a basic view for use in tests. - $this->createView(); - - // $this->createDisplay('Page', array('path' => array('path' => 'bef_test_page'))); - - // Add field to default display - // $this->addField('node.title'); - - // Turn of Better Exposed Filters - $this->setBefExposedForm(); - } - - public function tearDown() { - debug('This test run took ' . (time() - $this->start) . ' seconds.'); - unset($this->view); - parent::tearDown(); - } - - /******************************************************************************* - * Helper functions - ******************************************************************************/ - - /** - * Returns the URL for the BEF exposed form settings page. - */ - protected function getBefSettingsUrl() { - return 'admin/structure/views/nojs/display/' . $this->view['machine_name'] . '/default/exposed_form_options'; - } - - protected function createView($name = '') { - if (!empty($this->view)) { - debug('WARNING: createView called after view has already been created.'); - return; - } - - if (empty($name)) { - $name = $this->randomName(8); - } - $this->view['name'] = $name; - $this->view['machine_name'] = strtolower($name); - - $edit = array( - 'human_name' => $this->view['name'], - 'name' => $this->view['machine_name'], - // Default is to create a page display. - 'page[create]' => FALSE, - ); - $this->drupalPost('admin/structure/views/add', $edit, 'Save & exit'); - - // URL to edit this view. - $this->view['edit_url'] = 'admin/structure/views/view/' . $this->view['machine_name'] . '/edit'; - } - - /** - * Creates a display of $type. Currently supports: - * 'Page' - * - * @todo: support more types... - */ - protected function createDisplay($type = 'Page', $settings = NULL) { - if (!isset($this->view['displays'])) { - $this->view['displays'] = array(); - } - - // Add a display of $type to the view - $this->drupalPost($this->view['edit_url'], array(), "Add $type"); - - // Grab the name of the newly created display and store some info about it. - $url = $this->getUrl(); - $display_name = substr($url, strrpos($url, '/') + 1); - $this->view['displays'][$display_name] = array( - 'machine_name' => $display_name, - 'edit_url' => 'admin/structure/views/view/' . $this->view['machine_name'] . '/edit/' . $display_name, - 'settings_base_url' => 'admin/structure/views/nojs/display/' . $this->view['machine_name'] . '/' . $display_name, - ); - - // Settings should be in the form of 'path' => array_of_form_settings. Eg: - // to set the title for a new display as an override: - // 'title' => array( - // 'title' => 'This is an override title', - // 'override[dropdown]' => display_machine_name_goes_here, - // ) - // - // If you navigate to - // admin/structure/views/nojs/display///title - // you will see the form in question. - foreach ($settings as $path => $values) { - $this->drupalPost($this->view['displays'][$display_name]['settings_base_url'] . "/$path", $values, 'Apply'); - } - $this->saveView(); - } - - /** - * Adds a filter to a view display. - * - * $field: string in the form of node.status or - * field_data_field_example.field_example_value - * $settings: (array) Settings on the "Configure filter criterion" dialog. - * NOTE: called after the "Expose filter" button is pressed if $exposed - * is TRUE so you can set things like "Allow multiple items" or grouped - * filter options. - * $additional: (array) settings for any additional configuration forms such - * as taxonomy term settings. - * $display: machine name of the display to add this filter to. NOTE: - * Currently only allows filters on the master display, no overrides. - * @todo: fix that, if needed. - * $exposed: (bool) (optional, default: TRUE) Is this an exposed filter? - * - * Note: This routine expects the caller to save the view, as needed. - */ - protected function addFilter($field, $settings = array(), $additional = array(), $exposed = TRUE, $display = 'default') { - $edit = array( - "name[$field]" => TRUE, - ); - $url = 'admin/structure/views/nojs/add-item/' . $this->view['machine_name'] . "/$display/filter"; - $this->drupalPost($url, $edit, 'Add and configure filter criteria'); - - if (!empty($additional)) { - // Handle filter-specific options screen. - $this->drupalPost(NULL, $additional, 'Apply'); - } - - if ($exposed) { - $this->drupalPost(NULL, array(), 'Expose filter'); - } - $this->drupalPost(NULL, $settings, 'Apply'); - } - - /** - * Edits an existing filter in the current view. See addFilter for param - * definitions. - */ - protected function editFilter($field, $settings, $additional = array(), $display = 'default') { - if (FALSE !== ($pos = strpos($field, '.'))) { - $field = substr($field, $pos + 1); - } - $url = 'admin/structure/views/nojs/config-item/' . $this->view['machine_name'] . "/$display/filter/$field"; - $this->drupalPost($url, $settings, 'Apply'); - - if (!empty($additional)) { - // Handle filter-specific options screen. - $this->drupalPost(NULL, $additional, 'Apply'); - } - } - - /** - * Adds a sort to a view display. See addFilter for parameter options. - * - * Note: This routine expects the caller to save the view, as needed. - */ - protected function addSort($field, $settings = array(), $additional = array(), $exposed = TRUE, $display = 'default') { - $edit = array( - "name[$field]" => TRUE, - ); - $url = 'admin/structure/views/nojs/add-item/' . $this->view['machine_name'] . "/$display/sort"; - $this->drupalPost($url, $edit, 'Add and configure sort criteria'); - - if (!empty($additional)) { - // Handle filter-specific options screen. - $this->drupalPost(NULL, $additional, 'Apply'); - } - - if ($exposed) { - $this->drupalPost(NULL, array(), 'Expose sort'); - } - $this->drupalPost(NULL, $settings, 'Apply'); - } - - /** - * Adds a field to a view display. See addFilter for parameter options. - * - * Note: This routine expects the caller to save the view, as needed. - */ - protected function addField($field, $settings = array(), $display = 'default') { - $edit = array( - "name[$field]" => TRUE, - ); - $url = 'admin/structure/views/nojs/add-item/' . $this->view['machine_name'] . "/$display/field"; - $this->drupalPost($url, $edit, 'Add and configure fields'); - $this->drupalPost(NULL, $settings, 'Apply'); - } - - /** - * Ensures that BEF is selected as the exposed form option - * - * Note: This routine expects the caller to save the view, as needed. - */ - protected function setBefExposedForm($display = 'default') { - $edit = array( - "exposed_form[type]" => 'better_exposed_filters', - ); - $url = 'admin/structure/views/nojs/display/' . $this->view['machine_name'] . "/$display/exposed_form"; - $this->drupalPost($url, $edit, 'Apply'); - - // BEF settings is covered under setBefSettings() so we just accept the - // default values and move on. - $this->drupalPost(NULL, array(), 'Apply'); - } - - /** - * Sets various BEF exposed form settings. If $error is specified it also - * asserts that the error text apepars when trying to apply $settings. - * - * Note: This routine expects the caller to save the view, as needed. - */ - protected function setBefSettings($settings, $error = '') { - $this->drupalPost($this->getBefSettingsUrl(), $settings, 'Apply'); - if (!empty($error)) { - $this->assertText($error); - } - } - - /** - * Saves the view - */ - protected function saveView() { - $this->drupalPost($this->view['edit_url'], array(), 'Save'); - } -} + 'BEF Basic functionality tests', + 'description' => 'Basic tests for Better Exposed Filters.', + 'group' => 'Better Exposed Filters', + 'dependencies' => array('date', 'date_views', 'views', 'views_ui') + ); + } + + public function setUp() { + // For benchmarking. + $this->start = time(); + + // Enable any modules required for the test. + parent::setUp( + 'better_exposed_filters', + 'date', + 'date_views', + 'list', + 'number', + 'taxonomy', + 'text', + 'views', + 'views_ui' + ); + + // One of these days I'll figure out why Features is breaking all my tests. + module_enable(array('bef_test_content')); + + // User with edit views perms. + $this->admin_user = $this->drupalCreateUser(); + $role = user_role_load_by_name('administrator'); + $this->assertTrue(!empty($role->rid), 'Found the "administrator" role.'); + user_save($this->admin_user, array('roles' => array($role->rid => $role->rid))); + $this->drupalLogin($this->admin_user); + + // Build a basic view for use in tests. + $this->createView(); + + // $this->createDisplay('Page', array('path' => array('path' => 'bef_test_page'))); + + // Add field to default display + // $this->addField('node.title'); + + // Turn of Better Exposed Filters. + $this->setBefExposedForm(); + } + + public function tearDown() { + debug('This test run took ' . (time() - $this->start) . ' seconds.'); + unset($this->view); + parent::tearDown(); + } + + /******************************************************************************* + * Helper functions + ******************************************************************************/ + + /** + * Returns the URL for the BEF exposed form settings page. + */ + protected function getBefSettingsUrl() { + return 'admin/structure/views/nojs/display/' . $this->view['machine_name'] . '/default/exposed_form_options'; + } + + protected function createView($name = '') { + if (!empty($this->view)) { + debug('WARNING: createView called after view has already been created.'); + return; + } + + if (empty($name)) { + $name = $this->randomName(8); + } + $this->view['name'] = $name; + $this->view['machine_name'] = strtolower($name); + + $edit = array( + 'human_name' => $this->view['name'], + 'name' => $this->view['machine_name'], + // Default is to create a page display. + 'page[create]' => FALSE, + ); + $this->drupalPost('admin/structure/views/add', $edit, 'Save & exit'); + + // URL to edit this view. + $this->view['edit_url'] = 'admin/structure/views/view/' . $this->view['machine_name'] . '/edit'; + } + + /** + * Creates a display of $type. Currently supports: + * 'Page' + * + * @todo: support more types... + */ + protected function createDisplay($type = 'Page', $settings = NULL) { + if (!isset($this->view['displays'])) { + $this->view['displays'] = array(); + } + + // Add a display of $type to the view. + $this->drupalPost($this->view['edit_url'], array(), "Add $type"); + + // Grab the name of the newly created display and store some info about it. + $url = $this->getUrl(); + $display_name = substr($url, strrpos($url, '/') + 1); + $this->view['displays'][$display_name] = array( + 'machine_name' => $display_name, + 'edit_url' => 'admin/structure/views/view/' . $this->view['machine_name'] . '/edit/' . $display_name, + 'settings_base_url' => 'admin/structure/views/nojs/display/' . $this->view['machine_name'] . '/' . $display_name, + ); + + // Settings should be in the form of 'path' => array_of_form_settings. Eg: + // to set the title for a new display as an override: + // 'title' => array( + // 'title' => 'This is an override title', + // 'override[dropdown]' => display_machine_name_goes_here, + // ) + // + // If you navigate to + // admin/structure/views/nojs/display///title + // you will see the form in question. + foreach ($settings as $path => $values) { + $this->drupalPost($this->view['displays'][$display_name]['settings_base_url'] . "/$path", $values, 'Apply'); + } + $this->saveView(); + } + + /** + * Adds a filter to a view display. + * + * $field: string in the form of node.status or + * field_data_field_example.field_example_value + * $settings: (array) Settings on the "Configure filter criterion" dialog. + * NOTE: called after the "Expose filter" button is pressed if $exposed + * is TRUE so you can set things like "Allow multiple items" or grouped + * filter options. + * $additional: (array) settings for any additional configuration forms such + * as taxonomy term settings. + * $display: machine name of the display to add this filter to. NOTE: + * Currently only allows filters on the master display, no overrides. + * @todo: fix that, if needed. + * $exposed: (bool) (optional, default: TRUE) Is this an exposed filter? + * + * Note: This routine expects the caller to save the view, as needed. + */ + protected function addFilter($field, $settings = array(), $additional = array(), $exposed = TRUE, $display = 'default') { + $edit = array( + "name[$field]" => TRUE, + ); + $url = 'admin/structure/views/nojs/add-item/' . $this->view['machine_name'] . "/$display/filter"; + $this->drupalPost($url, $edit, 'Add and configure filter criteria'); + + if (!empty($additional)) { + // Handle filter-specific options screen. + $this->drupalPost(NULL, $additional, 'Apply'); + } + + if ($exposed) { + $this->drupalPost(NULL, array(), 'Expose filter'); + } + $this->drupalPost(NULL, $settings, 'Apply'); + } + + /** + * Edits an existing filter in the current view. See addFilter for param + * definitions. + */ + protected function editFilter($field, $settings, $additional = array(), $display = 'default') { + if (FALSE !== ($pos = strpos($field, '.'))) { + $field = substr($field, $pos + 1); + } + $url = 'admin/structure/views/nojs/config-item/' . $this->view['machine_name'] . "/$display/filter/$field"; + $this->drupalPost($url, $settings, 'Apply'); + + if (!empty($additional)) { + // Handle filter-specific options screen. + $this->drupalPost(NULL, $additional, 'Apply'); + } + } + + /** + * Adds a sort to a view display. See addFilter for parameter options. + * + * Note: This routine expects the caller to save the view, as needed. + */ + protected function addSort($field, $settings = array(), $additional = array(), $exposed = TRUE, $display = 'default') { + $edit = array( + "name[$field]" => TRUE, + ); + $url = 'admin/structure/views/nojs/add-item/' . $this->view['machine_name'] . "/$display/sort"; + $this->drupalPost($url, $edit, 'Add and configure sort criteria'); + + if (!empty($additional)) { + // Handle filter-specific options screen. + $this->drupalPost(NULL, $additional, 'Apply'); + } + + if ($exposed) { + $this->drupalPost(NULL, array(), 'Expose sort'); + } + $this->drupalPost(NULL, $settings, 'Apply'); + } + + /** + * Adds a field to a view display. See addFilter for parameter options. + * + * Note: This routine expects the caller to save the view, as needed. + */ + protected function addField($field, $settings = array(), $display = 'default') { + $edit = array( + "name[$field]" => TRUE, + ); + $url = 'admin/structure/views/nojs/add-item/' . $this->view['machine_name'] . "/$display/field"; + $this->drupalPost($url, $edit, 'Add and configure fields'); + $this->drupalPost(NULL, $settings, 'Apply'); + } + + /** + * Ensures that BEF is selected as the exposed form option. + * + * Note: This routine expects the caller to save the view, as needed. + */ + protected function setBefExposedForm($display = 'default') { + $edit = array( + "exposed_form[type]" => 'better_exposed_filters', + ); + $url = 'admin/structure/views/nojs/display/' . $this->view['machine_name'] . "/$display/exposed_form"; + $this->drupalPost($url, $edit, 'Apply'); + + // BEF settings is covered under setBefSettings() so we just accept the + // default values and move on. + $this->drupalPost(NULL, array(), 'Apply'); + } + + /** + * Sets various BEF exposed form settings. If $error is specified it also + * asserts that the error text apepars when trying to apply $settings. + * + * Note: This routine expects the caller to save the view, as needed. + */ + protected function setBefSettings($settings, $error = '') { + $this->drupalPost($this->getBefSettingsUrl(), $settings, 'Apply'); + if (!empty($error)) { + $this->assertText($error); + } + } + + /** + * Saves the view. + */ + protected function saveView() { + $this->drupalPost($this->view['edit_url'], array(), 'Save'); + } +} diff --git a/dkan/modules/contrib/date/CHANGELOG.txt b/dkan/modules/contrib/date/CHANGELOG.txt index e5e655e27..4ad826a1d 100644 --- a/dkan/modules/contrib/date/CHANGELOG.txt +++ b/dkan/modules/contrib/date/CHANGELOG.txt @@ -1,674 +1,1129 @@ -Date Module 7.x -================= - -====================== -Version 7.x-2.8 -====================== -- Issue #106713 by vijaycs85, heddn: Date Field title XSS(SA) -- Issue #2310123 by Temoor: Fixed Fatal error in Migrate UI by Date Migrate Example. -- Issue #2142277 by Temoor | lliss: Fixed Infinite Loop When Using Repeating Dates. -- Issue #1871136 by Temoor | funkimunky: Fixed Cannot remove migrate example content type. -- Issue #2261395 by minorOffense | kristofferwiklund: Fixed date_now is not respecting changes to timezone. -- Issue #1832400 by imot3k, balintk: Fixed "The year/month/... is missing" error message is not translatable. -- Issue #998076 by coredumperror, joelcollinsdc, brenk28: Fixed Problem with timezone handling (caused by date_get_timezone_db returning only UTC). -- Issue #1976014 by Harmageddon: Remove unnecessary   in date views pager. -- Issue #2198807 by stickywes: Refactor field tests to utilize DateFieldBasic. -- Issue #2146461 by vijaycs85 | Razia_b: Fixed Translated field's label is not used in its validation messages. -- Issue #1874422 by bluetegu, brockfanning, rv0: Fixed Week pager may cause incorrect "next" link on year change. -- Issue #2167015 by schifazl: HTML output breaks validation and accessibility. -- Issue #2200513 by scor: Fix encoding issues in CHANGELOG.txt. -- Issue #2115377 by coredumperror: Notice: Undefined index: add_microdata in theme_date_display_single(). -- Issue #2178299 by sandykadam | joachim: Incorrect variable name in hook_views_data_alter(). -- Issue #1431952 by Cadila | Shawn DeArmond: Date field will only set in Rules if it already has a value. -- Issue #355058 by Grimreaper | prunelle: Hardcoded day/month/week output format for theme_date_nav_title(). -- Issue #1968828 by pjcdawkins: Replace field_info_fields() with field_info_field_map() for Drupal >= 7.22. -- Issue #2167033 by podarok, vijaycs85: Fixing tests in 2014 . - -====================== -Version 7.x-2.7 -====================== -- Issue #1691342 by Cyberwolf: Field description is displayed multiple times. -- Issue #1668240 by ianthomas_uk: /themes/jquery.timeentry.css is not aggregated with other CSS files. -- Issue #1974056 by pjcdawkins: Add a CSS class to the date repeat rule. -- Issue #2065749 by vijaycs85, blackdog: $class in theme_date_repeat_rrule is not used. -- Issue #1840008 by cr0ss, Alan D.: Unlock "Date attributes" (granularity) field setting. -- Issue #1580032 by jmuzz | 30equals: Date values are not being saved when field is used in a nested field collection. -- Issue #2071629 by blackdog: #after_build wrongly added, overwrites other modules. -- Issue #1869962 by kaidjohnson: Date Context breaks context edit form. -- Issue #1571258 by David_Rothstein | ddalvi: Date and time form elements do not have accessible labels when the label position is set to 'Within' or 'None'. -- Issue #2130575 by kostajh: Unset() should be used with more caution in views_filter_handler_simple().inc. -- Issue #1826598 by eromba, jyee: 'c' and 'r' formatter causes date to be printed twice in views. -- Issue #1933472 by minorOffense: Added Optimize date_now() function. -- Issue #1248786 by kenneth.venken | dboulet: Fixed appearance of marker for required date fields. -- Issue #1810734 by ParisLiakos: Fixed Repeat checkboxes working reversed with updated jQuery. -- Issue #1844092 by dboulet, kardave, Spleshka: Fixed Untranslated strings: month, day, ... -- Issue #1202248 by james.williams, dawehner, mikehues | DamienMcKenna: Fixed Exported view doesn't include 'default_argument_options()' attribute. -- Issue #1409120 by anrikun, akamustang | marsbidon: Added Date format in views exposed filter does not respect configured format. -- Issue #1858112 by tomdearden: Fixed DateObject->difference not calculating correctly for future dates > 1 year away. -- Issue #1659466 by artkon: Fixed Date filter does not remember value in views if the identifier is something other than date_filter(). -- Issue #1905096 by gielfeldt: Fixed Wrong usage of database API. -- Issue #1791804 by 5n00py: Errors on form validation if date field placed in sub-form. -- Issue #636208 Date range: Expose a single filter to select events that start before the chosen date and end after the chosen date by anrikun. -- Issue #2024269 Date popup documentation fix by RoySegall. -- Issue #1835184 by Steven Jones, das-peter, jwhat: Fixed date_limit_format() can have poor performance. -- Issue #991830 by milesw | restyler: Fixed validation errors when date_popup() date is localized. -- Issue #2098715 by jhodgdon: Fixed Date field not obeying widget alters for #required. -- Issue #1266688 by linclark, rbayliss, fago, colette: Support microdata in date fields. -- Issue #1863610 by maximpodorov: Fixed Invalid date formatting. -- META #2034231 #1832544 Class registration for Migrate 2.5 or later - mikeryan, [#1835214] Automated tests failing - Exception thrown in Date2 migration - PatchRanger, [# -- Issue #1350604 by Alan D., johaziel: Added Diff support for Date fields. -- Issue #2086313 by dooug: Fixed Wrong path in date_popup() README.txt -- Issue #1455558 by BTMash | mediameriquat: Fixed Missing file in date_views().info causes error 500. -- Issue #1697322 by Alan D., ianmthomasuk | pandikamal: Fixed Call to a member function getName() on a non-object in date api. -- Code cleanup, remove #prev values that were never used in repeat functionality. -- Small fix needed to ensure cardinality gets set correctly if repeat option is changed in the UI. - -====================== -Version 7.x-2.6 -====================== - -- Issue #1423364 by catmat, Add file and path to hook_context_plugins(). -- Issue #1713248, Remove incorrect use of date_popup() function in previous commit. -- Issue #1143680 by kristiaanvandeneynde, Make Date Popup widget able to accept custom settings. -- Issue #1596546 by applicity_sam, Make sure incorrect month name creates validation error. -- Issue #1432702, Fix some miscellaneous problems with date example dates and formats. -- Issue #1512464 by Liam Moreland, Allow for undefined formatter in Date upgrade handling. -- Issue #1659638 by bojanz, Hide install message if Drupal is not installed (i.e. in install profiles). -- Issue #1605158 by covenantd, Be sure value2 is initiated in Date Context handler. -- Issue #1355256 by barraponto develCuy and KarenS, set default value for date text widget increment to 1, and update existing fields. -- Issue #1561306 by Cyberwolf, Date pager replacement was not working correctly when used with multiple dates. -- Issue #1541740 by hass, Make Date requirements more easily translatable. -- Issue #1543524 by iamEAP, Add update hook to remove the D6 date_timezone field from users. -- Issue #1603640 by bevan, Don't return anything for empty date interval. -- Issue #1235508, Make sure that ISO strings with '00' in the month and/or day don't create dates for the previous month and day. -- Issue #1289270 by pdrake: Fixed date arguments and filters do not work with relationships. -- Issue #895760 by Reinette: Added RFC2447 option 'STATUS' to date_api_ical().inc. -- Issue #606658 by johnmunro and KarenS, Make sure ical import processes multi-day all-day events correctly. -- Issue #1408216 follow up, Need to be sure that NULL is the default state for the sql functions. -- Issue #1540410, Update the Date Tools calendar creation wizard to use the new calendar path. - -====================== -Version 7.x-2.5 -====================== - -- Fix nasty errors on views that have no row indexes, like the frontage view. - -====================== -Version 7.x-2.4 -====================== - -- Issue #1534342 by bass, Make name of clean pager option easier to translate. -- Add a hook to alter the default date for a Date argument. -- Issue #1531904, Make sure that multiple value dates that have the delta set only display the selected value. -- Revert Issue #1266688 by fago, This change broke Entity token support. - -====================== -Version 7.x-2.3 -====================== - -- Issue #1444804, Remove html placeholder on text widget. -- Issue #1286230, Clean up date pager documentation, it contains deprecated information. -- Issue #1469020, Make sure page not found is working right for dates outside the specified range. -- Issue #1111626, Fix some problems caused by passing in a date string with an offset. -- Issue #1477860, Avoid undefined index errors in date_views_select_validate(). -- Issue #1469038, Add some clarity to the BYDAY description options and make sure a valid value is selected. -- Issue #1405364 by snufkin, Add display options to format_interval to match Views interval options. -- Issue #1266688 by linclark, Add microdata support to Date metadata. -- Issue #1509434 by casey, Add timezone info in hook_field_presave() instead of hook_field_insert() and hook_field_update(). -- Issue #1509012, Fix fatal error when viewing repeat tab. -- Issue #1037174 by dealancer, More work to allow ajax to work on other widgets. -- Issue #1473592 by zabelc, Make sure migrated date with missing end date still gets imported correctly. -- Issue #1442718 by jastraat and bones, Make sure show end date checkbox does not display when end date is empty. -- Issue #1504556, Fix invalid access permission name in Date API configuration menu. -- Issue #1484458, Vega timepicker code was incorrectly computing the default date because of the wrong format for the value passed to it. -- Issue #1459668, Add back Remove date_field_get_sql_handler() function temporarily since Signup module is still using it. Mark it deprecated. -- Issue #1478296, Display longer abbreviations for day names in date repeat widget so they can be translated. -- Issue #1488662 by martin.heidegger, Make error message more easily translatable. -- Issue #1478848 by onelittleant, Don't set default values on new translation entities. -- Issue #1360672, Fix limit format code to remove leftover non-ascii text as well as leftover ascii text. -- Issue #1464280 by geerlingguy Georgique and dealancer: Fix notice when using exposed between filter. -- Issue #1484640 by onelittleant, Some entities may not have base tables. -- Issue #1472966, Get rid of notice that appears for entities without repeats. -- Move pager css around. Some code in Calendar and Date API belongs in Date Views. -- Fix Date Popup default format which was ending up using 24 hour time no matter what format was used for short format. -- Remove outdated _repeat widget name from Date Tools. -- Date Tools is defaulting to creating a calendar even if Calendar module is not enabled. -- Issue #1472148 by arlinsandbulte: wvega jQuery time-picker doesn't show scrollbar. -- Issue #1392128 by kidrobot, tim.plunkett, willvincent: Fixed Repeats tab shows on nodes with non-repeating dates. -- Issue #1452882 by coredumperror: Fixed Date Migrate Example doesn't deregister it's migration when disabled. -- Issue #1452408 by reglogge: Fixed Trailing dots are removed from date formats even if they are needed for the format. -- Issue #1285260 by Steven Jones: Fixed 'Starting from' display option. -- Issue #1467244 by bluetegu: Fixed Recurring Sunday errors. -- Issue #1442718, Empty default values were getting populated by the code that tried to fix default values on hidden fields. -- Make sure date_handler->placeholders gets initialized even for queries that don't execute so the pager won't throw an exception if the value is missing. - -====================== -Version 7.x-2.2 -====================== - -NOTES: -The iCal templates were removed and moved to the new Date iCal module (http://drupal.org/project/date_ical). - -- Issue #1423364 by emptyvoid, Move Date context plugin into its own file. -- Issue #1447372 by yched, Memory friendlier version of date_repeat_field_bundles(). -- Issue #1447140 by tim.plunkett: Remove iCal-related theme functions/files. -- Issue #1284170 by benjifisher, Lots of cleanup of the iCal export and its template to be sure all day items are exported correctly. -- Issue #1346424, The calendar title got left out of the latest ical template changes. -- Add more information to the date_views_base_tables() function about why we can't use the entity type from views_fetch_data(). -- Additional handling is needed so that revisions are handled correctly in Views. -- Remove date_field_get_sql_handler() function which isn't being used anywhere. -- Rework the Date Views hooks that queue up the right fields and base tables so they work better across all types of entities. -- Fix the hooks for file tables, it is file_managed, not files. -- Add a helper function to map entity types to Views base tables to make it easier to know what type of entity is being used on a view. -- Add status information to Date help screen, hide the non-working change tab on the Date Tools screen, and remove some code that goes to the calendar module from Date Tools. - -====================== -Version 7.x-2.1 -====================== - -- Issue #1437242 by zerbash, Remove extraneous leading slashes in module_load_include(). -- Issue #1436722 by hefox: Fixed Undefined variable $form_set_error() used as function. -- Issue #1250626 by Gábor Hojtsy, B-Prod, hefox: Added start date and end date labels. -- Issue #1253482, Make sure $argument->is_default gets reset by the Date pager when altering results. -- Adjust Date Tools to work with changes to use Views templates to create calendars. -- Issue #1398584 by dhalbert and , Make sure groupby times is initialized. -- Issue #1394248, Make sure all date parts are selected when using an exposed select filter. -- Issue #1425774 by sgabe, Fix error in exposed filter, array_filter() -- first argument should be an array in date_select_input_date(). -- Issue #1432992 by bart.hanssens, Fix typo in php documentation. -- Issue #323852 by tim.plunkett and arlinsandbulte, Dropdowns shouldn't include a blank option when required. -- Issue #323852, Validation was broken for unlimited value select widgets with required dates, they were incorrectly getting their end dates cleared out. -- Issue #1424656 by tim.plunkett, Unify signature and alters of #process callbacks. -- Issue #1408014 by dasjo, We no longer need the $error_element value to display errors properly, second follow-up. -- Issue #1299030 by Vincent B: Ensure that 'To Date' is properly marked when required. - -====================== -Version 7.x-2.0-rc2 -====================== - -New Features/Major Changes - -- Issue #1358790 by tim.plunkett and redndahead, Store date objects in field_load to speed up processing, with a field setting option to control it. +Date 7.x-2.x-dev, xxxx-xx-xx +---------------------------- +#2937071 by DamienMcKenna: Tidy up the CHANGELOG.txt file. +#2514146 by ram4nd: Remove the .gitignore file. +#2533080 by jribeiro, DamienMcKenna, rajeev_drupal, badjava: 'clone' is a + reserved keyword introduced in PHP version 5.0 and cannot be invoked as a + function. +#2843367 by RemyAroundTown, alesr, joelpittet, DamienMcKenna, oadaeh, + ikeigenwijs, johnzzon, Novitsh, matthiasm11, glynster, robertgarrigos, + dchatry, szeidler, Majdi, Plazik, NancyDru, nanobyt3: String Offset errors on + edit page using PHP 7.1.0. + + +Date 7.x-2.10, 2017-04-07 +------------------------- +#1037150 by raggax2, vishy_singhal, >antroxim, Risse: date_popup element with + just time doesn't work. +#2648114 by tsapenkoum: DateTime::format(): The DateTime object has not been + correctly initialized by its constructor in DateObject->format(). +#2780415 by prince_zyxware, DamienMcKenna: As per Drupal coding standards, use + uppercase for PHP constants. +#2646646 by anpolimus, Stevel, DamienMcKenna: Tests are broken on current + 7.x-2.x branch. +#2412049 by Stevel: DateMigrateExampleUnitTest fails if migrate is missing +#2186191 by kaare, jiv_e, DamienMcKenna, welly, PascalAnimateur, lussoluca, + mErilainen, vijaycs85, paulwdru, zerolab, joep.hendrix, marc.groth, podarok, + pmackay, idebr, jastraat, WorldFallz, autopoietic, streever: Missing help text +#2377623 by ekli">Gergely Lekli, qzmenko: Remove unnecessary space in data_combo + title. + + +Date 7.x-2.10-rc1, 2016-04-27 +----------------------------- +#1355256 by develCuy, barraponto, KarenS, jgullstr, Romash: date field minutes + and seconds rounding after update/save. +#1889744 by tatyana, mstrelan, mariagwyn: PDOException when saving date as unix + timestamp. +#1504202 by seanB, steinmb: Allow entering of custom date format in Views field + handlers. +#2532636 by jstoller: Undefined index: #default_value in + date_popup_process_time_part(). +#1699312 by gmclelland, pounard, Niklas Fiekas, azinck, netlooker: Fatal error: + Call to undefined function date_field_widget_settings_form_validate() in + form.inc. +#1874422 by danwonac, bluetegu, SeanA, rv0, brockfanning: Pager may cause + incorrect "prev" or "next" link. +#2038057 by vijaycs85, djdevin, pivanov: date_select element's default value as + an array raises a warning. +#2026545 by mistermoper, Jon Nunan: Date field's interval formatter always uses + start date. +#2331975 by BarisW, StryKaizer: Sync end date for the date popup functionality. +#2339119 by maximpodorov, darkdim: Notice: Undefined index: date in + date_text_element_process(). +#2570239 by PascalAnimateur: Display date range as span instead of div +#2611968 by FluxSauce: PHP Fatal error: Class 'MigrateFieldHandler' not found +#2160843 by ohokare">nupur.lohokare: Grammar issue: "Date objects can be created + and cached as date fields are loaded rather than when they are displayed to + improve performance". + + +Date 7.x-2.10-beta1, 2016-01-08 +------------------------------- +#2325207 by jojonaloha, v1nk, podarok: DateTimeZone could not be converted to + string. +#2469189 by TommyK: Notice: Undefined index: show_remaining_days in + date_field_formatter_view() (line 210 of …/date/date.field.inc). +#1854220 by ryanissamson, paravibe: Add module_exists so that error isn't thrown + when disabling date_repeat_field module. +#2375235 by Lostboy22, steinmb, rsmylski, Jelle_S: Calendar block Next/Prev + navigation broken. +#1889744 by tatyana, mstrelan, mariagwyn: PDOException when saving date as unix + timestamp. +#1797630 by damiankloip, webby7097, Fabianx: 'Is empty(null)' views filter is + not working with exposed operators enabled when actual date values are left + empty. + + +Date 7.x-2.9, 2015-09-08 +------------------------ +#2072679 by leewillis77: SQLSTATE[42S22] error and no results when multiple + dates included in views filter. +#2503315 by CptCasual: Fatal error: Cannot unset string offsets in + date_elements.inc. +#2543442 by dawehner: Expose site-wide date input format in the UI. +#1993110 by Hanno, tsyvs, Valentine94: field type not translatable. +#1675844 by mpdonadio, justin2pin, paravibe, Valentine94: The "Repeat" checkbox + does not open / close the correct fieldset in some circumstances. + + +Date 7.x-2.9-rc1, 2015-03-27 +---------------------------- +#2199587 by oadaeh, jmuzz: Second validation of date popup causes error. +#2374287 by vijaycs85, herd45: Error messageNotice: Undefined index: value in + date_views_select_validate. +#2420323 by Michelle: Exposed date filter shouldn't automatically set a default + date if none requested. +#1437892 by stefan.r, interdruper: Undefined index notices in date_popup.module + and default values disappear. +#2372073 by vijaycs85, Lostboy22, maximpodorov, stefan.r: Undefined variable: + granularity in date_views_plugin_pager->query() (line 285 of date_views + /includes/date_views_plugin_pager.inc. +#2451027 by Chewie, stBorchert: Migrate module: undefined index timezone error. +#206127 by TravisCarden, BenPoole, dwightaspinwall, nerdcore, georgir: Add + ability to sort Views exposed filters SELECT lists in reverse. +#2235393 by Pere Orga, andrdrx: CSS pseudoselector 'ends with' should not be + used with the class attribute. +#1078788 by jacob.embree, mgifford: Coder issues in Date. +#1137062 by Sylvain Lecoy, schifazl, hughworm: date_week_range and + date_iso_week_range both returns 1 day too much. + + +Date 7.x-2.9-beta2, 2014-12-04 +------------------------------ +#1166036 by andriyun | Palmeus: Showing the remaining days until event. +#2359673 by korgik: Fixed Wrong condition in removeGranularity method. +#2375605 by cosmicdreams, ssdas: When configured to use Date's timezone and + using a muti-value date field, I expect each date field to remember the + timezone that was set. +#2371471 by schifazl, anrikun, esomething, MXT, maximpodorov, alayham, + Lostboy22, k.skarlatos: Error with entity_metadata_wrapper for Date Field with + Start But No End. +#2332181 by bneil, cafuego: Add update hook for date_views variables. + + +Date 7.x-2.9-beta1, 2014-11-08 +------------------------------- +#2366199 by vlad.dancer: Sniffer fixes. +#2325425 by catch: Fixed Trim input dates in date_popup. +#2238135 by Valentine94, cs_shadow, e5sego: Fixed 0 (zero) time value will be + rejected. +#2277107 by LinL: Fixed Date API's Status report entry repeats the word "to". +#2367791 by sergiuteaca: Fixed Extra white space before full stop on Repeat + title. +#2367779 by andreyks, sanchiz: Fixed Regression in date_popup.js after code + cleanup. +#1246940 by yingtho, andreyks | acbramley: Added more custom formats for Date + Popup. +#1909566 by Valentine94, Ronino: Fixed Devel generate fails for date fields + with absolute years in ranges. +#1997774 by Valentine94, ryan.armstrong, haggins: Added Update Time Entry plugin + to 1.5.2. +#2369691 by sanchiz: Code standarts in date.theme. +#1487700 by Valentine94, Jorrit, El Alemaño | casey: Added Views date pager, + option to skip empty pages. +#1878970 by Elijah Lynn, Valentine94: Fixed Entering 4 digit year manually. + Extra spacing at beginning/end is not trimmed, results in validation error. +#2279831 by itarato: Fixed Date field validation breaks if it's coming from a + Panels visibility rule. +#1467712 by ankur_novatree, anrikun, jmonkfish, fledev, MustangGB, DuaelFr, + jibran, l0coful: Added Make it possible to disable fieldset for date field. +#2026811 by kala4ek, forgenator: Fixed Views argument in contextual filter + doesn't have all granularity options. +#2367595 by jbubik: Fixed 'r' formatter produces RFC2822-incompliant results in + non-English environment. +#2361671 by mfernea: Fixed Notice returned in + date_default_formatter_settings_summary when using custom settings. +#1885270 by Eduardo Alvarez, mareksal, JvE | haffmans: Fixed UTC time zone + handling shows UTC, not converted, date/time. + + +Date 7.x-2.9-alpha2, 2014-09-24 +------------------------------- +#2309015 by msankhala, Temoor: Fixed views date field exposed filter default + value not being added in $form_state['input'] when initially views loads. +#2323191 by guillaumev: Fixed Issue with testing. +#2313587 by BWPanda: Fixed Incorrect permission name. + + +Date 7.x-2.9-alpha1, 2014-07-31 +------------------------------- +#2294973 by lsabug, micahw156, keesje: Fixed Date format in calendar title + remains default. + + +Date 7.x-2.8, 2014-07-29 +------------------------ +#106713 by vijaycs85, heddn: Date Field title XSS(SA). +#2310123 by Temoor: Fixed Fatal error in Migrate UI by Date Migrate Example. +#2142277 by Temoor, lliss: Fixed Infinite Loop When Using Repeating Dates. +#1871136 by Temoor, funkimunky: Fixed Cannot remove migrate example content + type. +#2261395 by minorOffense, kristofferwiklund: Fixed date_now is not respecting + changes to timezone. +#1832400 by imot3k, balintk: Fixed "The year/month/... is missing" error message + is not translatable. +#998076 by coredumperror, joelcollinsdc, brenk28: Fixed Problem with timezone + handling (caused by date_get_timezone_db returning only UTC). +#1976014 by Harmageddon: Remove unnecessary   in date views pager. +#2198807 by stickywes: Refactor field tests to utilize DateFieldBasic. +#2146461 by vijaycs85, Razia_b: Fixed Translated field's label is not used in + its validation messages. +#1874422 by bluetegu, brockfanning, rv0: Fixed Week pager may cause incorrect + "next" link on year change. +#2167015 by schifazl: HTML output breaks validation and accessibility. +#2200513 by scor: Fix encoding issues in CHANGELOG.txt. +#2115377 by coredumperror: Notice: Undefined index: add_microdata in + theme_date_display_single(). +#2178299 by sandykadam, joachim: Incorrect variable name in + hook_views_data_alter(). +#1431952 by Cadila, Shawn DeArmond: Date field will only set in Rules if it + already has a value. +#355058 by Grimreaper, prunelle: Hardcoded day/month/week output format for + theme_date_nav_title(). +#1968828 by pjcdawkins: Replace field_info_fields() with field_info_field_map() + for Drupal >= 7.22. +#2167033 by podarok, vijaycs85: Fixing tests in 2014. + + +Date 7.x-2.7, 2013-12-21 +------------------------ +#1691342 by Cyberwolf: Field description is displayed multiple times. +#1668240 by ianthomas_uk: /themes/jquery.timeentry.css is not aggregated with + other CSS files. +#1974056 by pjcdawkins: Add a CSS class to the date repeat rule. +#2065749 by vijaycs85, blackdog: $class in theme_date_repeat_rrule is not used. +#1840008 by cr0ss, Alan D: Unlock "Date attributes" (granularity) field setting. +#1580032 by jmuzz, 30equals: Date values are not being saved when field is used + in a nested field collection. +#2071629 by blackdog: #after_build wrongly added, overwrites other modules. +#1869962 by kaidjohnson: Date Context breaks context edit form. +#1571258 by David_Rothstein, ddalvi: Date and time form elements do not have + accessible labels when the label position is set to 'Within' or 'None'. +#2130575 by kostajh: Unset() should be used with more caution in + views_filter_handler_simple().inc. +#1826598 by eromba, jyee: 'c' and 'r' formatter causes date to be printed twice + in views. +#1933472 by minorOffense: Added Optimize date_now() function. +#1248786 by kenneth.venken, dboulet: Fixed appearance of marker for required + date fields. +#1810734 by ParisLiakos: Fixed Repeat checkboxes working reversed with updated + jQuery. +#1844092 by dboulet, kardave, Spleshka: Fixed Untranslated strings: month, day, + ... +#1202248 by james.williams, dawehner, mikehues, DamienMcKenna: Fixed Exported + view doesn't include 'default_argument_options()' attribute. +#1409120 by anrikun, akamustang, marsbidon: Added Date format in views exposed + filter does not respect configured format. +#1858112 by tomdearden: Fixed DateObject->difference not calculating correctly + for future dates > 1 year away. +#1659466 by artkon: Fixed Date filter does not remember value in views if the + identifier is something other than date_filter(). +#1905096 by gielfeldt: Fixed Wrong usage of database API. +#1791804 by 5n00py: Errors on form validation if date field placed in sub-form. +#636208 by KarenS: Date range: Expose a single filter to select events that + start before the chosen date and end after the chosen date by anrikun. +#2024269 by KarenS: Date popup documentation fix by RoySegall. +#1835184 by Steven Jones, das-peter, jwhat: Fixed date_limit_format() can have + poor performance. +#991830 by milesw, restyler: Fixed validation errors when date_popup() date is + localized. +#2098715 by jhodgdon: Fixed Date field not obeying widget alters for #required. +#1266688 by linclark, rbayliss, fago, colette: Support microdata in date fields. +#1863610 by maximpodorov: Fixed Invalid date formatting. +#2034231/#1832544 by mikeryan: Class registration for Migrate 2.5 or later. +#1835214 by PatchRanger: Automated tests failing - Exception thrown in Date2 + migration. +#1350604 by Alan D., johaziel: Added Diff support for Date fields. +#2086313 by dooug: Fixed Wrong path in date_popup() README.txt +#1455558 by BTMash, mediameriquat: Fixed Missing file in date_views().info + causes error 500. +#1697322 by Alan D., ianmthomasuk, pandikamal: Fixed Call to a member function + getName() on a non-object in date api. +By KarenS: Code cleanup, remove #prev values that were never used in repeat + functionality. +By KarenS: Small fix needed to ensure cardinality gets set correctly if repeat + option is changed in the UI. + + +Date 7.x-2.6, 2012-08-13 +------------------------ +#1423364 by catmat: Add file and path to hook_context_plugins(). +#1713248 by KarenS: Remove incorrect use of date_popup() function in previous + commit. +#1143680 by kristiaanvandeneynde: Make Date Popup widget able to accept custom + settings. +#1596546 by applicity_sam: Make sure incorrect month name creates validation + error. +#1432702 by KarenS: Fix some miscellaneous problems with date example dates and + formats. +#1512464 by Liam Moreland: Allow for undefined formatter in Date upgrade + handling. +#1659638 by bojanz: Hide install message if Drupal is not installed (i.e. in + install profiles). +#1605158 by covenantd: Be sure value2 is initiated in Date Context handler. +#1355256 by barraponto, develCuy, KarenS: Set default value for date text + widget increment to 1, and update existing fields. +#1561306 by Cyberwolf: Date pager replacement was not working correctly when + used with multiple dates. +#1541740 by hass: Make Date requirements more easily translatable. +#1543524 by iamEAP: Add update hook to remove the D6 date_timezone field from + users. +#1603640 by bevan: Don't return anything for empty date interval. +#1235508 by KarenS: Make sure that ISO strings with '00' in the month and/or day + don't create dates for the previous month and day. +#1289270 by pdrake: Fixed date arguments and filters do not work with + relationships. +#895760 by Reinette: Added RFC2447 option 'STATUS' to date_api_ical().inc. +#606658 by johnmunro, KarenS, Make sure ical import processes multi-day all-day + events correctly. +#1408216 by KarenS: Need to be sure that NULL is the default state for the SQL + functions. +#1540410 by KarenS: Update the Date Tools calendar creation wizard to use the + new calendar path. + + +Date 7.x-2.5, 2012-04-19 +------------------------ +By KarenS: Fix nasty errors on views that have no row indexes, like the frontage + view. + + +Date 7.x-2.4, 2012-04-19 +------------------------ +#1534342 by bass: Make name of clean pager option easier to translate. +By KarenS: Add a hook to alter the default date for a Date argument. +#1531904 by KarenS: Make sure that multiple value dates that have the delta set + only display the selected value. +Revert Issue #1266688 by fago, This change broke Entity token support. + + +Date 7.x-2.3, 2012-04-01 +------------------------------- +#1444804: Remove html placeholder on text widget. +#1286230: Clean up date pager documentation, it contains deprecated information. +#1469020: Make sure page not found is working right for dates outside the + specified range. +#1111626: Fix some problems caused by passing in a date string with an offset. +#1477860: Avoid undefined index errors in date_views_select_validate(). +#1469038: Add some clarity to the BYDAY description options and make sure a + valid value is selected. +#1405364 by snufkin: Add display options to format_interval to match Views + interval options. +#1266688 by linclark: Add microdata support to Date metadata. +#1509434 by casey: Add timezone info in hook_field_presave() instead of + hook_field_insert() and hook_field_update(). +#1509012, Fix fatal error when viewing repeat tab. +#1037174 by dealancer: More work to allow ajax to work on other widgets. +#1473592 by zabelc: Make sure migrated date with missing end date still gets + imported correctly. +#1442718 by jastraat, bones: Make sure show end date checkbox does not display + when end date is empty. +#1504556 by KarenS: Fix invalid access permission name in Date API configuration + menu. +#1484458 by KarenS: Vega timepicker code was incorrectly computing the default + date because of the wrong format for the value passed to it. +#1459668 by KarenS: Add back Remove date_field_get_sql_handler() function + temporarily since Signup module is still using it. Mark it deprecated. +#1478296 by KarenS: Display longer abbreviations for day names in date repeat + widget so they can be translated. +#1488662 by martin.heidegger: Make error message more easily translatable. +#1478848 by onelittleant: Don't set default values on new translation entities. +#1360672 by KarenS: Fix limit format code to remove leftover non-ascii text as + well as leftover ascii text. +#1464280 by geerlingguy, Georgique, dealancer: Fix notice when using exposed + between filter. +#1484640 by onelittleant: Some entities may not have base tables. +#1472966 by KarenS: Get rid of notice that appears for entities without repeats. +By KarenS: Move pager css around. Some code in Calendar and Date API belongs in + Date Views. +By KarenS: Fix Date Popup default format which was ending up using 24 hour time + no matter what format was used for short format. +By KarenS: Remove outdated _repeat widget name from Date Tools. +By KarenS: Date Tools is defaulting to creating a calendar even if Calendar + module is not enabled. +#1472148 by arlinsandbulte: wvega jQuery time-picker doesn't show scrollbar. +#1392128 by kidrobot, tim.plunkett, willvincent: Fixed Repeats tab shows on + nodes with non-repeating dates. +#1452882 by coredumperror: Fixed Date Migrate Example doesn't deregister it's + migration when disabled. +#1452408 by reglogge: Fixed Trailing dots are removed from date formats even if + they are needed for the format. +#1285260 by Steven Jones: Fixed 'Starting from' display option. +#1467244 by bluetegu: Fixed Recurring Sunday errors. +#1442718, Empty default values were getting populated by the code that tried to + fix default values on hidden fields. +By KarenS: Make sure date_handler->placeholders gets initialized even for + queries that don't execute so the pager won't throw an exception if the value + is missing. + + +Date 7.x-2.2, 2012-02-24 +------------------------ +By KarenS: The iCal templates were removed and moved to the new Date iCal module + (http://drupal.org/project/date_ical). +#1423364 by emptyvoid, Move Date context plugin into its own file. +#1447372 by yched, Memory friendlier version of date_repeat_field_bundles(). +#1447140 by tim.plunkett: Remove iCal-related theme functions/files. +#1284170 by benjifisher, Lots of cleanup of the iCal export and its template to + be sure all day items are exported correctly. +#1346424, The calendar title got left out of the latest ical template changes. +By KarenS: Add more information to the date_views_base_tables() function about + why we can't use the entity type from views_fetch_data(). +By KarenS: Additional handling is needed so that revisions are handled correctly + in Views. +By KarenS: Remove date_field_get_sql_handler() function which isn't being used + anywhere. +By KarenS: Rework the Date Views hooks that queue up the right fields and base + tables so they work better across all types of entities. +By KarenS: Fix the hooks for file tables, it is file_managed, not files. +By KarenS: Add a helper function to map entity types to Views base tables to + make it easier to know what type of entity is being used on a view. +By KarenS: Add status information to Date help screen, hide the non-working + change tab on the Date Tools screen, and remove some code that goes to the + calendar module from Date Tools. + + +Date 7.x-2.1, 2012-02-14 +------------------------ +#1437242 by zerbash, Remove extraneous leading slashes in module_load_include(). +#1436722 by hefox: Fixed Undefined variable $form_set_error() used as function. +#1250626 by Gábor Hojtsy, B-Prod, hefox: Added start date and end date labels. +#1253482, Make sure $argument->is_default gets reset by the Date pager when + altering results. +By KarenS: Adjust Date Tools to work with changes to use Views templates to + create calendars. +#1398584 by dhalbert and KarneS, Make sure groupby times is initialized. +#1394248 by KarenS: Make sure all date parts are selected when using an exposed + select filter. +#1425774 by sgabe: Fix error in exposed filter, array_filter() -- first argument + should be an array in date_select_input_date(). +#1432992 by bart.hanssens: Fix typo in php documentation. +#323852 by tim.plunkett and arlinsandbulte, Dropdowns shouldn't include a blank + option when required. +#323852 by KarenS: Validation was broken for unlimited value select widgets with + required dates, they were incorrectly getting their end dates cleared out. +#1424656 by tim.plunkett: Unify signature and alters of #process callbacks. +#1408014 by dasjo: We no longer need the $error_element value to display errors + properly, second follow-up. +#1299030 by Vincent B: Ensure that 'To Date' is properly marked when required. + + +Date 7.x-2.0-rc2, 2012-02-01 +---------------------------- +New Features/Major Changes: +#1358790 by tim.plunkett and redndahead: Store date objects in field_load to + speed up processing, with a field setting option to control it. + +Bugfixes: +#1423598 by KarenS: Found a way to flag items that are new so we know when to + add default values. +By KarenS: Made some fixes to find and test the right entities when checking + whether to use default values. More work is needed. +#1422600 by KarenS: Make sure end date cannot cause validation errors when show + end date checkbox is not checked. +#1417872 by KarenS: Remove code to compute missing date parts from empty values + now that the new validation prevents that from working. +#1417872 by KarenS: Make sure empty year field is validated in the same way + other date parts are validated. +By KarenS: Alter date field test to test with a complete end date instead of + using empty elements. +By KarenS: Fix broken logic in repeat additions. +By KarenS: Fix test broken by date repeat clean up. +#1419106 by hanoii: Added more info to hook_date_text_process_alter(). +#1209026 by KarenS: When date validation fails, Date Popup value is getting + cleared. +#1017216 by tim.plunkett, arlinsandbulte: Added custom date format without time + shows 'all day'. +#1408014 by dasjo: We no longer need the $error_element value to display errors + properly. +#554546 by master-of-magic: Fix Timezone list translation +#1411038 by jhodgdon: Fixed Date formatting is not obeying granularity. +#1359464 by KarenS: Make sure that default values on hidden elements have the + same construct as loaded values to avoid errors. +#1399744 by tim.plunkett: Rework the way filter groups are used to be sure the + Date filter group does not clobber the Views filter group. +#1411862 by KarenS: Move the date_views_fields() function into date_view.module + to be sure it is always available. +#1380350 by KarenS: Rework SQL query handling to pass in a comparison date for + computing offsets to better handle dates affected by DST adjustments. +#1359464 by KarenS: Temporary fix for broken handling of repeating dates on + users with a TODO to figure out where it's coming from. +#1404494 by byrond: Make sure hidden formatters don't get switched to + date_default in update hook. +#1386012 by KarenS: Order weekdays in Date Repeat form to match the site first + day of week settings. +#1376476 by pbfleetwood: Add commas to default formatting of week and day + headers. +#1408430 by KarenS: Views pagination with exposed, unset, filters, results in + invalid default values. Treat them as empty input. +#1408996 by KarenS: Exposed Date Popup widget with time not correctly + initialized. +#1271726 by KarenS: Text widgets in exposed filters used with pagers produce + unexpected results. +#1096000 by KarenS: Custom date formats not working right. +#1405364 by KarenS: Format interval settings were getting overridden by missing + break. +#1387890 by KarenS: Make sure disabled fields are shown and retain their + original values. +#1402232 by timplunkett: Switch 'Implementation of' to 'Implements' +#1402236 by timplunkett: All functions should be prefixed with your module name + to avoid name clashes +#1402238 by timplumkett: Remove references to CCK +#1011624 by KarenS: Filters and arguments were not always using the right base + table to select field options, causing 'column missing' errors. +#1397822 by KarenS: Fix 'Undefined index: field' error on line 207 of + date_views_filter_handler_simple.inc. +#1396536 by KarenS: Repeating fields weren't getting created with Devel Generate + because of the change in widgets. Add new hooks so Date Repeat Field can add + the repeats. +#1397126 by arlinsandbulte: Fix broken tests. +#1397158 by KarenS: The default value for the end date was using the wrong + timezone setting. +#1392128 by kidrobot, tim.plunkett: Don't show repeat tab unless the date is + repeating. +#1397126 by KarenS: Add Date API section to administration menu and consolidate + Date Popup and Date Tools settings there. +By KarenS: Follow up to Issue #1302052 by benjifisher, More clean up of ical + line endings. +#1388174 by travist: Remove redundant use of date_default_timezone(). +#1388586 by Moloc: Don't use check_plain() on system messages with links. +#1302052 by benjifisher, penguin25, helmo: Fix linespace ending problems in ical + files. +#1357216: The Date Views field list was including the Date Views filter itself, + creating errors about missing node.date_filter values. +#1259870: Reinstate the test for a missing date argument for the pager. +By KarenS: The new option to hide the add_delta for simple fields needs to be + adjusted to work right where there is no field. +By KarenS: Tweak the all day formula to check for 59:59 even for items that use + increments, to help fix problems when the calendar creates all day values. +By KarenS: Our test for whether this is a Date argument or filter needs to + include a check that the field was actually processed by Date Views. Some + dates are not. +By KarenS: Revert the change to 7.8. It breaks the modules page. +#1379172 by deviantpixel: Note that the Date Repeat form uses a function that + was re-named in 7.8. +By KarenS: Store the locale format in a static cache to avoid re-computing it + dozens of times on calendar views. +By KarenS: Sheesh. Fix syntax error in api.date.php. + + +Date 7.x-2.0-rc1, 2011-12-20 +---------------------------- +The Date Browser has been removed. Please use the Date Pager instead. If you +have existing views using the Date Browser the navigation will just disappear +from them. If you add a Date Pager to the view you should get it back. Then +delete the Date Browser attachment from the view, since it doesn't do anything +any more. + +The UNTIL date was not getting included in repeating results and that is now +fixed. This is an API change of sorts for anyone who worked around the issue by +setting it ahead. + +The All Day checkbox and All Day themes were moved into a separate module, using +new hooks added to the date processing. This module should serve as an example +of how other modules can inject functionality into date fields. + +A new module has been added for integrating the Date Repeat API into date +fields. Some of this code has been moved into the new module, more of it will be +moved later as I figure out how to unwind it from the base processing. An update +hook has been added to enable this module by default for existing sites. If you +don't use Repeating dates you can disable it. + +New Features/Major Changes: +#1229378 by ksenzee: Sync the end date with the start date on new dates using + select widgets. +#1236216 by KarenS: Move the repeating date integration out of the Date module + and into a separate, Date Repeat Field module. +#874322 by KarenS: The All Day functionality has been moved into a separate + module and hooks were added to make it easier for other modules to inject + steps into date processing. +#1267046 by KarenS: Include the UNTIL date in repeating results. +#1354606 by temaruk: Rework the repeating date UI to make it more user-friendly. +#1362654 by KarenS: Remove Advanced Help integration, it needs a total rewrite. +#1357362 by KarenS: Remove Date Browser, use Date Pager instead. +#1354606 by KarenS: Make sure COUNT option without UNTIL date can be handled + correctly by repeating date computations. Bugfixes - -- Issue #1423598, Found a way to flag items that are new so we know when to add default values. -- Made some fixes to find and test the right entities when checking whether to use default values. More work is needed. -- Issue #1422600, Make sure end date cannot cause validation errors when show end date checkbox is not checked. -- Issue #1417872, Remove code to compute missing date parts from empty values now that the new validation prevents that from working. -- Issue #1417872, Make sure empty year field is validated in the same way other date parts are validated. -- Alter date field test to test with a complete end date instead of using empty elements. -- Fix broken logic in repeat additions. -- Fix test broken by date repeat clean up. -- Issue #1419106 by hanoii: Added more info to hook_date_text_process_alter(). -- Issue #1209026, When date validation fails, Date Popup value is getting cleared. -- Issue #1017216 by tim.plunkett, arlinsandbulte: Added custom date format without time shows 'all day'. -- Issue #1408014 by dasjo, We no longer need the $error_element value to display errors properly. -- Issue #554546 by master-of-magic: Fix Timezone list translation -- Issue #1411038 by jhodgdon: Fixed Date formatting is not obeying granularity. -- Issue #1359464, Make sure that default values on hidden elements have the same construct as loaded values to avoid errors. -- Issue #1399744 by tim.plunkett, Rework the way filter groups are used to be sure the Date filter group does not clobber the Views filter group. -- Issue #1411862, Move the date_views_fields() function into date_view.module to be sure it is always available. -- Issue #1380350, Rework SQL query handling to pass in a comparison date for computing offsets to better handle dates affected by DST adjustments. -- Issue #1359464, Temporary fix for broken handling of repeating dates on users with a TODO to figure out where it's coming from. -- Issue #1404494 by byrond, Make sure hidden formatters don't get switched to date_default in update hook. -- Issue #1386012, Order weekdays in Date Repeat form to match the site first day of week settings. -- Issue #1376476 by pbfleetwood, Add commas to default formatting of week and day headers. -- Issue #1408430, Views pagination with exposed, unset, filters, results in invalid default values. Treat them as empty input. -- Issue #1408996, Exposed Date Popup widget with time not correctly initialized. -- Issue #1271726, Text widgets in exposed filters used with pagers produce unexpected results. -- Issue #1096000, Custom date formats not working right. -- Issue #1405364, Format interval settings were getting overridden by missing break. -- Issue #1387890, Make sure disabled fields are shown and retain their original values. -- Issue #1402232 by timplunkett, Switch 'Implementation of' to 'Implements' -- Issue #1402236 by timplunkett, All functions should be prefixed with your module name to avoid name clashes -- Issue #1402238 by timplumkett, Remove references to CCK -- Issue #1011624, Filters and arguments were not always using the right base table to select field options, causing 'column missing' errors. -- Issue #1397822, Fix 'Undefined index: field' error on line 207 of date_views_filter_handler_simple.inc. -- Issue #1396536, Repeating fields weren't getting created with Devel Generate because of the change in widgets. Add new hooks so Date Repeat Field can add the repeats. -- Issue #1397126 follow-up by arlinsandbulte, Fix broken tests. -- Issue #1397158, The default value for the end date was using the wrong timezone setting. -- Issue #1392128 by kidrobot and tim.plunkett, Don't show repeat tab unless the date is repeating. -- Issue #1397126, Add Date API section to administration menu and consolidate Date Popup and Date Tools settings there. -- Follow up to Issue #1302052 by benjifisher, More clean up of ical line endings. -- Issue #1388174 by travist, Remove redundant use of date_default_timezone(). -- Issue #1388586 by Moloc, Don't use check_plain() on system messages with links. -- Issue #1302052 by benjifisher, penguin25, and helmo, Fix linespace ending problems in ical files. -- Issue #1357216, The Date Views field list was including the Date Views filter itself, creating errors about missing node.date_filter values. -- Issue #1259870, Reinstate the test for a missing date argument for the pager. -- The new option to hide the add_delta for simple fields needs to be adjusted to work right where there is no field. -- Tweak the all day formula to check for 59:59 even for items that use increments, to help fix problems when the calendar creates all day values. -- Our test for whether this is a Date argument or filter needs to include a check that the field was actually processed by Date Views. Some dates are not. -- Revert the change to 7.8. It breaks the modules page. -- Issue #1379172 by deviantpixel, Note that the Date Repeat form uses a function that was re-named in 7.8. -- Store the locale format in a static cache to avoid re-computing it dozens of times on calendar views. -- Sheesh. Fix syntax error in api.date.php. - -====================== -Version 7.x-2.0-rc1 -====================== - -Notes: -The Date Browser has been removed. Please use the Date Pager instead. If you have existing views using the Date -Browser the navigation will just disappear from them. If you add a Date Pager to the view you should get it back. -Then delete the Date Browser attachment from the view, since it doesn't do anything any more. - -The UNTIL date was not getting included in repeating results and that is now fixed. This is an API change of sorts -for anyone who worked around the issue by setting it ahead. - -The All Day checkbox and All Day themes were moved into a separate module, using new hooks added to the date -processing. This module should serve as an example of how other modules can inject functionality into date fields. - -A new module has been added for integrating the Date Repeat API into date fields. Some of this code has been -moved into the new module, more of it will be moved later as I figure out how to unwind it from the base -processing. An update hook has been added to enable this module by default for existing sites. If you don't -use Repeating dates you can disable it. - -New Features/Major Changes - -- Issue #1229378 by ksenzee, Sync the end date with the start date on new dates using select widgets. -- Issue #1236216, Move the repeating date integration out of the Date module and into a separate, Date Repeat Field module. -- Issue #874322, The All Day functionality has been moved into a separate module and hooks were added to make it easier for other modules to inject steps into date processing. -- Issue #1267046, Include the UNTIL date in repeating results.. -- Issue #1354606 by temaruk, Rework the repeating date UI to make it more user-friendly. -- Issue #1362654, Remove Advanced Help integration, it needs a total rewrite. -- Issue #1357362, Remove Date Browser, use Date Pager instead. -- Issue #1354606, Make sure COUNT option without UNTIL date can be handled correctly by repeating date computations. - -Bugfixes - -- Fix to new default date handling, the default date has to set a date in the database timezone, not the display timezone. -- Issue #1245106 by Gábor Hojtsy, Hide the option to add the delta into the view for single value fields. -- Issue #1370876, Make sure new Date All Day code does not try to set the popup values if Date Popup is disabled. -- Issue #874322, Add back the date_field_all_day() function to avoid breaking other modules that are using it. -- Fix Date text placeholder to display a formatted date instead of a format. Follow up to Date repeat UI changes. -- Issue #1248520 by fearlsgroove, Use attribute selector on all day and end day checkboxes. -- Issue #952446 and #1031690, Select dates with only year and month were not working. -- Issue #1292152 by pfrenssen, Remove debug functions. -- Issue #1338976 by Josh Benner, Adjust iCal unwrap to allow for leading spaces, per standard. -- Issue #1352486 by d.novikov, Rework date SQL formatting for Microsoft SQL. -- Issue #1266536, Keep timezone adjustments from altering the values for repeating date EXCEPTIONS and ADDITIONS. -- Issue #1353488, Some repeating date calculations using PHP 5.3.6+ were not computing correctly. -- Issue #1364026, Fix link to documentation, also move help to Date API module. -- Issue #1276270, Fix fatal error when using repeat date on user. -- Issue #1362758, Add empty file for the date_plugin_display_attachment.inc file that was removed, to avoid fatal errors if it is missing on a site that formerly used it. -- Issue #1363460, Make sure widget dates do not end up with the current date when they should be empty. -- Issue #1353488, Attempt to fix problem using date_date_set() in PHP 5.3.6+. -- Date Migrate tests needed some fixes to conform to latest code. -- Issue #1353790, Hide missing index notice if the value in the Date Popup does not match the expected value, it will still get trapped as a validation error. -- Issue #1353790, Make sure Date Popup properly translates the default value if it includes month or day names. -- Issue #1217796, Make sure that All Day formatting works correctly even if the All Day label is empty. -- The repeat description was lost by a recent change to the theme that sent the element through the theme twice. -- Remove date_get_nested_elements() function. Not being used and I don't want other code to start expecting it. -- Issue #1266144, The end date should get set to blank if it is optional and matches the start date, but only if it is not populated by default values. -- Issue #1349510, Make sure default values get populated before processing so they still get set on hidden fields. - -====================== -Version 7.x-2.0-alpha5 -====================== - -Notes: -The date repeat widgets have been removed to keep users from trying to change repeating dates into non-repeating dates. -There are now just three widgets, Date Select, Date Text, and Date Popup. Whether or not a date is a repeating date -is now controlled by a field setting. - -New Features/UX Changes/API Changes - -- Issue #1304056 by DamienMcKenna, Add option to date_difference to indicate direction of difference. -- Issue #1238660, Add custom format option for the date format used in summary arguments. -- Issue #1038482 by somanyfish, iCal import failing due to colon instead of semi-colon -- Issue #1252952 by eosrei: Make "all day" checkbox configurable on a per field instance basis. -- Issue #1266144 by arlinsandbulte: Clarify Default End Date Setting -- Issue #1261478 by stevector and KarenS, Reconfigure the back/next buttons into item lists so Views ajax pager works right. -- Issue #1262960, Add a new module to work with the Context module to set a condition based on the value of a date field. -- Issue #1216878, Re-introduce 'repeat' as a field setting rather than a widget type so people can't try to switch back and forth between repeating and non-repeating dates. Eliminate repeat widgets. - -Bugfixes - -- Issue #1280658 and follow up to Issue #1238660 by KarenS and tockliasteroid, Rework the format control over the summary title and apply it to the title() callback. -- Issue #1276622 by ArtistConk, Make sure Except date contains no default value. -- Issue #1153766 by fago, More work on the entity property setters and getters. -- Issue #1331214, Make sure Date Migrate properly handles empty date values. -- Issue #1285224, Make sure Date Migrate works for importing repeating dates now that widgets have changed. -- Issue #1162290, Make sure date for example formats avoids confusing short and long month names. -- Re-organize handling of custom date formats. -- Issue #1122038, Make sure empty values are not passed to Views to be themed for repeating dates. -- Issue #1101284 by pfournier, Expand regex for Month names to catch more possible variations. -- Issue #1343406 by slashrsm, add back the caching of the views fields list. -- Issue #1302374 by miro_dietiker, Account for an inconsistency in core handling of non-existant date formats. -- Issue #1344014 by James Sharpe, Get rid of 'Repeats every 0 days' description. -- Issue #1302212, Change the way default dates using custom code are created. -- Issue #1292898, Check for all day checkbox in the basic date element validation so empty time for newly added elements still passes validation. -- Issue #1252952 follow up, Move checkbox setting to the same spot where time is set, and don't show option on dates without time. -- Issue #1335818 by joelpitter, Don't create a date for empty values in the date getters and setters. -- Issue #1271726, Keep default_value out of exposed form so it won't show up in pager. -- Issue #1017866, Fix miscellaneous problems where filter/argument incorrectly do timezone adjustment. -- Issue #1103032, Attempt to add setters to date entity metadata so Rules can use them. -- Issue #1153766, Adapt metadata functions to the pattern in the current Entity code, add $info to the getters. -- Issue #1278876 by basicmagic.net, Typo in date.css -- Issue #752550 by Fonant, Week number gets printed twice -- Issue #1052586 by jpsolero, Problem with Date API when using Calendar with argument set to "Week" granularity and "current date" default argument -- Issue #1266144, End date same as start date default not being respected. -- Issue #1239934 by casey, Fix javascript exception in date_year_range.js. -- Issue #1310558, Don't show extra label on exposed date filter. -- Fix bug in Date Popup that still uses the current date when the default date is empty. -- Issue #1335578 by aaronbauman, Make it possible to pass in +/- 1000 years to years back/forward. -- Issue #1292516 by mcarbone, Fix Uninitialized string offset in Date Migrate. -- Issue #1084980 by jwilson3, Set default value for $granularity to be array in date_formatter_format. -- Issue #1227350 by grendzy, Summary view should not be calling date_forbid(). -- Issue #1286570, Fix undefined index error caused by using a remember value without checking if it exists. -- Issue #1337440 by phoenix, Fix syntax error in vcalendar tpl file. -- Issue #1103032, Add plain date formatter and set it to be the default token formatter. -- Issue #1177684 by tim.plunket, Fix typo in last commit. -- Issue #1177684, Wrong translation of short month name. -- Issue #1201342 by colinlee and alexprv, Comment out SQLSVR timezone adjustment until it gets fixed properly. -- Issue #1308274 by sneyerst, SQL Server DATEPART function cannot accept composed datetime formats. -- Issue #1308266 by sneyerst, Fix arithmetic overflow in SQLSVR. -- Issue #1333104 by tim.plunkett, Check that $field['settings'] exists in date_is_repeat_field(). -- Error message for years back and forward doesn't match new labels, should be Starting year and Ending year. -- Issue #1179715, Switch drupal_array_get_nested_value() to use 'values' instead of 'input', where it makes sense, and simplify some of this code. -- Issue #1179715, Create a helper function for testing hidden/disabled dates and test each date element and validator to skip processing in that case. -- Issue #1179715, When hidden by #access=FALSE, repeating date fields were getting removed and not replaced. -- Issue #1179715, By pass date repeat widget processing and validation when element is hidden from user. -- Issue #1179715, Don't do timezone adjustments in the widget, wait for #process so we can skip it when the date field has been hidden by #access. -- Issue #1338194, Logic for creating end date wasn't taking into account the possibility that a field might have no value2. -- Issue #1179715, Default value callback for the timezone widget was not returning an array. -- Issue #1179716, Remove value_callback for date_repeat and date_combo forms, the default behavior works fine. -- Issue #1178716 by das-peter, Use drupal_array_get_nested_value() in Date Repeat instead of trying to find it manually. -- Issue #1178716 by das-peter and KarenS, Tweak the date repeat widget to identify empty input when used on nodes with translation. -- Issue #1178716 by das-peter and KarenS, Fix date repeat form values that are not arrays when hidden on a node that has translation. -- Issue #1178176 by das-peter, Fix date_combo_value_callback to return NULL to avoid data lost on untranslatable dates used with Entity Translation. -- Date Context module was making incorrect assumptions about the $language of the field. -- Issue #1237974, Schema module wants the datetime column to be lower case. -- Issue #1233084, fix a few places that were wrapping $instance['label'] with t(). -- Issue #1188380 by Xen and jherencia, use #title instead of field['label'] in date display because that has the i18n translation. -- Issue #1330768 by das-peter, remove whitespace. -- Tweak date_pager_url to allow a way to create non-absolute urls. -- Make sure deleted displays won't create errors in Views by returning FALSE in date_forbid() if there is no date argument. -- Issue #1257830, Beef up the logic when creating the date repeat tabs to work for more kinds of entities. -- Issue #1260962, Repeating date fields should save the date even if there is no repeat information. -- Issue #1241836 by kzoli, Fix undefined cardinality indexes. -- Issue #1233722, Fix undefined index notice in pager when other filters are used. - -====================== -Version 7.x-2.0-alpha4 -====================== - +By KarenS: Fix to new default date handling, the default date has to set a date + in the database timezone, not the display timezone. +#1245106 by Gábor Hojtsy, Hide the option to add the delta into the view for + single value fields. +#1370876 by KarenS: Make sure new Date All Day code does not try to set the + popup values if Date Popup is disabled. +#874322 by KarenS: Add back the date_field_all_day() function to avoid breaking + other modules that are using it. +By KarenS: Fix Date text placeholder to display a formatted date instead of a + format. Follow up to Date repeat UI changes. +#1248520 by fearlsgroove, Use attribute selector on all day and end day + checkboxes. +#952446 and #1031690 by KarenS: Select dates with only year and month were not + working. +#1292152 by pfrenssen, Remove debug functions. +#1338976 by Josh Benner, Adjust iCal unwrap to allow for leading spaces, per + standard. +#1352486 by d.novikov, Rework date SQL formatting for Microsoft SQL. +#1266536 by KarenS: Keep timezone adjustments from altering the values for + repeating date EXCEPTIONS and ADDITIONS. +#1353488 by KarenS: Some repeating date calculations using PHP 5.3.6+ were not + computing correctly. +#1364026 by KarenS: Fix link to documentation, also move help to Date API + module. +#1276270 by KarenS: Fix fatal error when using repeat date on user. +#1362758 by KarenS: Add empty file for the date_plugin_display_attachment.inc + file that was removed, to avoid fatal errors if it is missing on a site that + formerly used it. +#1363460 by KarenS: Make sure widget dates do not end up with the current date + when they should be empty. +#1353488 by KarenS: Attempt to fix problem using date_date_set() in PHP 5.3.6+. +By KarenS: Date Migrate tests needed some fixes to conform to latest code. +#1353790 by KarenS: Hide missing index notice if the value in the Date Popup + does not match the expected value, it will still get trapped as a validation + error. +#1353790 by KarenS: Make sure Date Popup properly translates the default value + if it includes month or day names. +#1217796 by KarenS: Make sure that All Day formatting works correctly even if + the All Day label is empty. +By KarenS: The repeat description was lost by a recent change to the theme that + sent the element through the theme twice. +By KarenS: Remove date_get_nested_elements() function. Not being used and I + don't want other code to start expecting it. +#1266144 by KarenS: The end date should get set to blank if it is optional and + matches the start date, but only if it is not populated by default values. +#1349510 by KarenS: Make sure default values get populated before processing so + they still get set on hidden fields. + + +Date 7.x-2.0-alpha5, 2011-11-29 +------------------------------- +The date repeat widgets have been removed to keep users from trying to change +repeating dates into non-repeating dates. There are now just three widgets, Date +Select, Date Text, and Date Popup. Whether or not a date is a repeating date is +now controlled by a field setting. + +New Features/UX Changes/API Changes: +#1304056 by DamienMcKenna: Add option to date_difference to indicate direction + of difference. +#1238660 by KarenS: Add custom format option for the date format used in summary + arguments. +#1038482 by somanyfish, iCal import failing due to colon instead of semi-colon. +#1252952 by eosrei: Make "all day" checkbox configurable on a per field instance + basis. +#1266144 by arlinsandbulte: Clarify Default End Date Setting. +#1261478 by stevector and KarenS, Reconfigure the back/next buttons into item + lists so Views ajax pager works right. +#1262960 by KarenS: Add a new module to work with the Context module to set a + condition based on the value of a date field. +#1216878 by KarenS: Re-introduce 'repeat' as a field setting rather than a + widget type so people can't try to switch back and forth between repeating and + non-repeating dates. Eliminate repeat widgets. + +Bugfixes: +#1280658 and follow up to Issue #1238660 by KarenS, tockliasteroid: Rework the + format control over the summary title and apply it to the title() callback. +#1276622 by ArtistConk: Make sure Except date contains no default value. +#1153766 by fago: More work on the entity property setters and getters. +#1331214 by KarenS: Make sure Date Migrate properly handles empty date values. +#1285224 by KarenS: Make sure Date Migrate works for importing repeating dates + now that widgets have changed. +#1162290 by KarenS: Make sure date for example formats avoids confusing short + and long month names. +By KarenS: Re-organize handling of custom date formats. +#1122038 by KarenS: Make sure empty values are not passed to Views to be themed + for repeating dates. +#1101284 by pfournier: Expand regex for Month names to catch more possible + variations. +#1343406 by slashrsm: Add back the caching of the views fields list. +#1302374 by miro_dietiker: Account for an inconsistency in core handling of + non-existant date formats. +#1344014 by James Sharpe: Get rid of 'Repeats every 0 days' description. +#1302212 by KarenS: Change the way default dates using custom code are created. +#1292898 by KarenS: Check for all day checkbox in the basic date element + validation so empty time for newly added elements still passes validation. +#1252952 by KarenS: Follow up, Move checkbox setting to the same spot where time + is set, and don't show option on dates without time. +#1335818 by joelpitter: Don't create a date for empty values in the date getters + and setters. +#1271726 by KarenS: Keep default_value out of exposed form so it won't show up + in pager. +#1017866 by KarenS: Fix miscellaneous problems where filter/argument incorrectly + do timezone adjustment. +#1103032 by KarenS: Attempt to add setters to date entity metadata so Rules can + use them. +#1153766 by KarenS: Adapt metadata functions to the pattern in the current + Entity code, add $info to the getters. +#1278876 by basicmagic.net: Typo in date.css. +#752550 by Fonant: Week number gets printed twice. +#1052586 by jpsolero: Problem with Date API when using Calendar with argument + set to "Week" granularity and "current date" default argument +#1266144 by KarenS: End date same as start date default not being respected. +#1239934 by casey: Fix javascript exception in date_year_range.js. +#1310558 by KarenS: Don't show extra label on exposed date filter. +By KarenS: Fix bug in Date Popup that still uses the current date when the + default date is empty. +#1335578 by aaronbauman: Make it possible to pass in +/- 1000 years to years + back/forward. +#1292516 by mcarbone: Fix Uninitialized string offset in Date Migrate. +#1084980 by jwilson3: Set default value for $granularity to be array in + date_formatter_format. +#1227350 by grendzy: Summary view should not be calling date_forbid(). +#1286570 by KarenS: Fix undefined index error caused by using a remember value + without checking if it exists. +#1337440 by phoenix: Fix syntax error in vcalendar tpl file. +#1103032 by KarenS: Add plain date formatter and set it to be the default token + formatter. +#1177684 by tim.plunket: Fix typo in last commit. +#1177684 by KarenS: Wrong translation of short month name. +#1201342 by colinlee, alexprv: Comment out SQLSVR timezone adjustment until it + gets fixed properly. +#1308274 by sneyerst: SQL Server DATEPART function cannot accept composed + datetime formats. +#1308266 by sneyerst: Fix arithmetic overflow in SQLSVR. +#1333104 by tim.plunkett: Check that $field['settings'] exists in + date_is_repeat_field(). +By KarenS: Error message for years back and forward doesn't match new labels, + should be Starting year and Ending year. +#1179715 by KarenS: Switch drupal_array_get_nested_value() to use 'values' + instead of 'input', where it makes sense, and simplify some of this code. +#1179715 by KarenS: Create a helper function for testing hidden/disabled dates + and test each date element and validator to skip processing in that case. +#1179715 by KarenS: When hidden by #access=FALSE, repeating date fields were + getting removed and not replaced. +#1179715 by KarenS: By pass date repeat widget processing and validation when + element is hidden from user. +#1179715 by KarenS: Don't do timezone adjustments in the widget, wait for + #process so we can skip it when the date field has been hidden by #access. +#1338194 by KarenS: Logic for creating end date wasn't taking into account the + possibility that a field might have no value2. +#1179715 by KarenS: Default value callback for the timezone widget was not + returning an array. +#1179716 by KarenS: Remove value_callback for date_repeat and date_combo forms, + the default behavior works fine. +#1178716 by das-peter: Use drupal_array_get_nested_value() in Date Repeat + instead of trying to find it manually. +#1178716 by das-peter, KarenS: Tweak the date repeat widget to identify empty + input when used on nodes with translation. +#1178716 by das-peter, KarenS: Fix date repeat form values that are not arrays + when hidden on a node that has translation. +#1178176 by das-peter: Fix date_combo_value_callback to return NULL to avoid + data lost on untranslatable dates used with Entity Translation. +By KarenS: Date Context module was making incorrect assumptions about the + $language of the field. +#1237974 by KarenS: Schema module wants the datetime column to be lower case. +#1233084 by KarenS: fix a few places that were wrapping $instance['label'] with + t(). +#1188380 by Xen, jherencia: use #title instead of field['label'] in date display + because that has the i18n translation. +#1330768 by das-peter: remove whitespace. +By KarenS: Tweak date_pager_url to allow a way to create non-absolute urls. +By KarenS: Make sure deleted displays won't create errors in Views by returning + FALSE in date_forbid() if there is no date argument. +#1257830 by KarenS: Beef up the logic when creating the date repeat tabs to work + for more kinds of entities. +#1260962 by KarenS: Repeating date fields should save the date even if there is + no repeat information. +#1241836 by kzoli: Fix undefined cardinality indexes. +#1233722 by KarenS: Fix undefined index notice in pager when other filters are + used. + + +Date 7.x-2.0-alpha4, 2011-08-23 +------------------------------- Notes to themers: - -Previous versions put dates with both From and To dates into a fieldset and other dates were not. -The new code adds additional floating elements that are hard to contain, so now all dates are -enclosed in fieldsets in the node form. There are also new elements on the form, an optional -checkbox for hiding/showing the To date and an optional checkbox for hiding/showing time. -Previously dates on the node form had 'From date' and 'To date' labels above them, this -has been changed to remove those labels, using the Google calendar date entry screen -as a model. This simplifies the node form and dates take up less space. A light grey border -has been added around each collection of dates (the From date and the To date). The display -of labels above the date parts (year, month, day, date, time, etc) is controlled in the -field settings. Previous versions did not always honor those settings, this one does. - -New Features/UX Improvements - -- Issue #1249724 by KarenS, Gábor Hojtsy, David_Rothstein, Improve usability of date and time input configuration. -- Issue #1250784 by David_Rothstein, Add user-friendly labels for start and end date values in Views. -- Issue #742146, Add option to remove X-WR-CALNAME if VEVENT is not a feed. -- Add option to change method from PUBLISH to REQUEST in VCALENDAR. -- Issue #334435, Add a theme function for the 'Date' and 'Time' labels in the Popup widget. -- Issue #1239956 by David_Rothstein, Change the default separation between from and to dates to use 'to' instead of a hyphen. -- Issue #1240628 by David_Rothstein, Make the date increment default to 15 instead of 1. -- Issue #1249724 by David_Rothstein: Improve usability of date and time input configuration -- Issue #1177198 by tim.plunkett: Allow CTools to process #dependency for date elements. -- Issue #1245562 by David_Rothstein, Rename the default date display format to something friendlier -- Issue #1239934 by David_Rothstein and Gábor Hojtsy, Reuse the "years back and forward" dropdown widget on the Views filter settings page. -- Issue #1239228 by Gábor Hojtsy, Date Views filter form UI improvements, clarify the way absolute and relative dates work. -- Issue #233047 by ksenzee and David_Rothstein, Add the Vegas jQuery timepicker as a new time selector option. -- Issue #1145976 by tim.plunkett and KarenS, Add 'is date' identifier to all date handlers. -- Issue #1234140 by arlinsundbulte, Change terminology in user-facing text from 'From/To Date' to 'Start/End Date'. -- Issue #1233948 by KarenS, Add 'All Day' checkbox to hide/show the time parts of the date form. If All day is chosen, any time is replaced with 00:00:00 when the form is submitted. -- Issue #1233612 by KarenS, Add 'Collect end date' checkbox for dates with optional end dates to hide/show the end date. -- Issue #821200 by scor and KarenS, Add RDF support to the date field. -- Issue #1211744 by EclipseGC and KarenS, Add a Date pager plugin that is designed to add paging in conjunction with a Date argument. -- Issue #1180612 by mikeryan, Add support for Migrate module. -- Issue #1198320 by ksenzee, David_Rothstein, Noyz: Make UI improvements to field settings page. -- Issue #1215738 by ksenzee, Make granularity settings checkboxes horizontal. -- Issue #1215686 by ksenzee, Change name of date field types to be more intuitive. -- Issue #1216996 by ksenzee, Change the years back/forward setting into two drop down boxes. -- Issue #1222468 by ksenzee, Hide timezone options when using granularity without time. -- Issue #1229388 by ksenzee, Hide formatter from/to choices on fields without multiple values. - -Removed deprecated functions - -- Removed date_handler_fields(), only applicable to D6 code. -- Removed date_views_real_url() and date_views_page_url(), used by older calendar version. -- Remove unused date_handler_field_multiple. - -Tests - -- Issue #1251592 by David_Rothstein, Set reasonable default values if date formats have not been configured, and add tests for that. -- Issue #1242764 Add tests for every possible combination of field type, timezone handling, and granularity. -- More work on tests, add a foreach loop to run through all field_type/widget_type combinations. -- Rework tests to use a base class rather than copying the same functions everywhere. -- Issue #1209408 Make sure date_repeat_calc() returns empty array for FREQ=NONE and INTERVAL=0, also add a test for that. -- Issue #1161006 by justinrandall, Add tests to check that dates that should have time but do not are correctly caught in validation. - -Bugfixes - -- Issue #1256406 by q0rban, use variable_get() in hook_requirments(). -- Follow up to Issue #1145976, Make sure 'is date' only gets applied to the date values, not delta, timezone, etc. -- Issue #1238364, Make sure the Date pager doesn't throw errors if the default date is missing. -- Issue #1246416, Add info to the Date Popup README.txt about how to download the WVega timepicker. -- Issue #1253230, Package name of Date Migrate was different than the other Date components. -- Issue #1251592, Add installation message and system requirements warnings about missing system date settings. -- Issue #1250784, Don't save psuedo field settings created by the new UI in the field itself. -- Issue #1239228 by David_Rothstein, Always add Date Views css to View UI edit forms. -- Issue #1254540 by David_Rothstein, Move borders off of the wrappers so they don't appear when the dates inside them are hidden. -- Issue #1253248 by David_Rothstein, Move RDF attributes handling to preprocess function. -- Issue #1087798 by anj, Fix X-WR-CALNAME in VCALENDAR. -- Issue #1254582 Repeat additions need to be adjusted to use the same time as the original date. -- Move vcalendar and vevent templates from Date Views to Date API modules. -- Follow up to Issue #1250344, We don't need extra space when there is a description, only when there is not. -- Issue #1239228 by Gábor Hojtsy, More tweaks to filter css. -- Issue #1244924 by Gábor Hojtsy, Minor text improvements in date filter configuration -- Issue #1245556 by David_Rothstein, Date granularity description incorrectly implies that it affects the date attributes that are displayed -- Issue #1247444 by Gábor Hojtsy, Give a little breathing space to the date year range "other" field -- Issue #1250344 by jessebeach, Fix padding around date fields by adding clearfix class. -- Issue #1249116 by yched, Fix various glitches with D6 migration code. -- Issue #1243022 by fmosca and KarenS, Make sure all_day #states visibility is only set when there is a value for all_day. -- Issue #1236192 by loganfsmyth, Make sure #date_label_position has a default value in the Date Popup module. -- Issue #1246416, Test whether libraries_get_path() returns a valid path before using it. -- Issue #1235994, Don't display 'All Day' when using a format that has no time. -- Issue #1245690 by mikeryan, Migration plugin missing seconds from date formats -- Issue #1229406 by David Rothstein, Gábor Hojtsy, and tim.plunkett Fix broken timepicker in Chrome and Safari. -- Issue #1239412 by keithm, Fix validation error when #access is false. -- Issue #1232522, Don't alter field_ui_field_edit form except on date fields. -- Issue #1243842, Make sure the All Day and Show End Date flags work correctly in unlimited value fields that use ajax. -- Follow up to Issue #1239956, Fix tests broken by change in date separator. -- Issue #1241576, Fix date combo validation for all-day dates so they don't fail validation because the format is unexpected. -- Take the guesswork out of examining Date info for Views, add is_field flag. -- Follow up to Issue #874322, Date popup field needs to accept date without time for the all day flag to work. -- Follow up to Issue #1130814, It looks like the modification is not needed in date_repeat_set_month_day and date_repeat_set_year_day. -- Issue #1160132 by seanbfuller and KarenS, Exposed filter widgets were not displaying the default values. -- Adjust the way widgets are styled in Views exposed filters after the latest style changes for form elements. -- Issue #1234114 by arlinsandbulte, Add more space between checkboxes. -- Issue #1234090, Fix undefined variable 'all_day'. -- Issue #1232570 by dboulet, Remove some unneeded duplication of core clearfix and other css cleanup. -- Issue #1232614 by dboulet, Date css should not be setting font family. -- Timezone and Date Popup weren't obeying the settings for displaying the date part label. -- Issue #1130814 by cdracars, mikeryan, mayobutter, Fix date_modify difference between PHP 5.3.5 and 5.3.6 so both work to compute repeating dates. -- Issue #1017866 by KarenS and Jason89s, Make sure views argument and filter don't do timezone adjustment for dates that don't have time. -- Issue #1204988 by paulsheldrake, Remove IE6 code from datepicker CSS. -- Issue #1231864 by Damien McKenna, Clean up line endings in vevent and vcalendar tpl files. -- Issue #1231382 by dboulet, Clean up date css, code style fixes. -- Date repeat additions and exceptions need to be reworked into full, timezone-adjusted, datetime values when passing them to FAPI as default values. -- Issue #1223034 by c4rl, Make sure repeating date additions and exceptions work correctly when there is more than one repeating date in the same form. -- Issue #1229362, Date Popup module needs date_is_date() function, which should not require Date module. Move that to Date API. -- Issue #1227208 by ksenzee, Minor text changes. -- Issue #1094408, Change method of identifying Field module filters, using the name of the group is not robust enough. -- Issue #1227264 by gapa, Fix wrong class in date-pager.tpl.php. -- Issue #1207540, Summary grouping won't work right unless the formula alias doesn't match an actual field value. -- Issue #1227350 Summary query still needs the formula, add it back. -- Fix 'variables cannot be passed by reference' notice on repeats page. -- Issue #1222736 Fix export errors caused when previous export fix of using export plugins got broken by changes in Views. -- Issue #1077490 Fix notices about missing #date_flexible. -- Move the 'top' date pager to below the header instead of below attachment_before so you can add header text above it. -- The value for variable_get('date_first_day') should default to zero to match core default. -- Issue #1147620 by KarenS with an assist from tim.plunkett, Fix the query so it will locate dates that span days or months by checking the intersection of the date range and the query range. Also add an option to the argument so you can do a simple query for either the from or to date when checking the whole range isn't the right solution. -- Issue #307274 by ksenzee, Fix broken validation for absolute value in years back/forward. -- Issue #1173374 by fietserwin, Remove translation of the jQuery datepicker day and month names, now handled by core. -- Issue #1192020 by tim.plunkett: Fixed date granularity is too fragile in date_field_all_day(). -- Issue #1110012 Remove 'parent' items from Views plugins, no longer needed? See if this fixes the issue. -- Issue #1183892, Initialize $identifier in date_views_filter_handler_simple. -- Issue #1103290 by stickywes and ingaro, Change postgres 'FMYYYY-FMMM-FMDDTFMHH:FMMI:FMSS' to 'FMYYYY-FMMM-FMDDTFMHH24:FMMI:FMSS'. -- Issue #1186528 by jox, Make date field combo label translatable. -- Issue #1123186 by KingJoshi, Fix misnamed date_part_hour_prefix in hook_theme(). -- Issue #1032942 by fietserwin, Rename date popup functions that are getting picked up by theme('date'). -- Issue #1206756 More tweaks to the validation changes. They were blocking zero times and causing some test failures. -- Issue #1197352 Don't display language about from and to dates when there is no todate. -- Issue #1201288 by rafa0961, fix broken references to SQLSERVER. -- More work on cleaning up validation, add in some ideas from fearlsgrove about checking the granularity of the input array against the expected granularity. -- Issue #1161006 Dates that should have time but do not were not correctly caught in validation. -- Get rid of overlapping formatter functionality. There should be a default formatter with the option to choose a date format as a setting, not a formatter for each format. This was a leftover from the D6 functionality. -- Issue #1159404 by mikeryan, Fix incorrect call to parse an rrule in date_repeat_build_dates(). -- Issue #1160656 by jjs, Replace missing break in date_api.sql.inc that breaks PostgreSQL. -- Issue #1150454, Fix undefined index notices for repeat_collapsed value. -- Issue #1150462, Put length limit on content type names created by Date Tools so block delta won't overflow the allowed size. -- Issue #1136734, When migrating date format data from D6 to D7, don't try to overwrite existing custom values. -- Issue #1161042 by Ollie222, Date filters using time were inconsistently formatted. -- Issue #1130884, Bad logic in 'between' filter SQL, should always join with AND. -- Issue #1139418 by ankur, Bad logic in week argument SQL, should always join with AND. -- Issue #1118356, Disabling the Timepicker was having no effect. -- Issue #408770 by vkareh, Make sure dates with #access = FALSE get passed through date_combo_element_process(). -- Issue #1037174 by das-peter, add ajax support to date popup. -- Issue #1160614 by joelstein, Make sure date repeat rule gets split correctly no matter which line endings were used. -- Issue #1110708 by mr.baileys, Fix problem combining date filter with other exposed filters. -- Switch some references to $node to use $entity instead. -- Issue #1112206, add a dummy field to the date navigation bar query to keep it from trying to create invalid sql. -- Issue #1112206, $this->view->query->clear_fields() is still need for date_browser to keep Views from trying to use missing field values. -- Date browser only works with date_argument, should work with any argument derived from date. - -====================== -Version 7.x-2.0-alpha3 -====================== - -- Issue #1138700, missed a couple references to the construct() function. - -====================== -Version 7.x-2.0-alpha2 -====================== - -- Follow up to Issue #1103290, constructor was not set up correctly and did not get triggered, so none of the date handlers had a db_type. -- Issue #1138622, preliminary pass at adding support for SQL Server. -- Issue #1136618 by ksenzee, Fix broken hide/show capability for date filter values. -- Issue #1059078 Add preliminary support for SQLite dates. -- Issue #1103290 by kevintheday, Use db_driver() to determine database engine. -- The 'now' values got broken again somewhere along the line. Now we need to switch the ISO format used by our SQL queries back to the datetime format the widgets use. -- Looks like Views changed ['expose']['operator'] to ['expose']['operator_id']. -- Issue #1115770, Make sure filters values are switched back to ISO format so time comparisons work correctly. -- Issue #1132916 by znerol, Fix a couple more usages of date_default_timezone_name(). -- Issue #1131308 Don't try to do timzone conversion when there is no localzone for a field. -- Issue #1093222 Fix broken function to remove calendar views. -- Issue #820670 Add update to move D6 date format data to D7 data. -- Issue 1074344 Fix problem with date select widget that keeps resetting pm back to am. -- Issue #1001186 Make sure that a 2 digit year is flagged as an error. -- Issue #1117420 by threewestwinds, Make timepicker formats more useful. -- Issue #1126408 by thekevinday, Add more sql formats to date api. -- Issue #1129326 by robertom, add missing value to element in date_combo. -- Issue #998220 by jamsilver and yched, fix handling of validation in date_combo. This also fixes errors when using a date in Profile 2 and Field Collections. -- Issue #1022592 by andypost and tim.plunkett optimize hook_form_alter into hook_form FORMID_alter. -- Issue 1021424 by joostvdl and wiizzard, fix context in translations to use core values. -- Remove extra fields from the Date field. I think the filter and argument now get the right tables joined in without that. We still need the extra fields on calendar, but now we will only add them in the calendar view. -- Fix some notices in vcalendar theme. -- Fix the mini calendar querystring. -- Fix logic for day argument formula. -- Fix logic for week argument formula. -- Issue #1086582 by bojanz, summary options are in a different place now, fix the method of removing the summary option on multiple-date arguments. -- Views renamed 'wildcard' to 'exception'. -- Apparently the handler->argument value is not always populated. -- Fix fallback value for date_group. -- Issue #1103032 by tim.plunkett, Remove token integration code until Token module is fixed. -- Now that there is no group of 'Fields' we need different tests to tell if this is a field filter. -- Views changed the group name of fields from 'Fields' to 'Content'. Blech, broke everything. -- Fix potential error if handler is broken. -- Remove reference to a function that no longer exists in Views. -- Issue #1116962 by jpontani, mordonez: Parse Error - date_views_fields.inc on line 119. -- Fix the logic for year and month only dates. -- Add helper function to test if handler is a date handler. -- Fix broken handling when creating dates from timestamps. -- Set some defaults for dates without month or day. -- Issue ##1094408 by Boobaa: Date field not showing up in views arguments in localized site -- Issue #1018412 by omerida: get_class() called without object. -- There were some problems when creating a year-only date using the select filter caused by trying to create a date with zeros in the month and day. Fix the date building logic to force a valid month and day into them. -- Complex filters were not using the date handler of the individual fields. -- Issue #1100934, Replace deprecated date_array() function. -- #1096246 Date argument also was not applying the group method in the right place in the query, causing that method to be applied to all filters. -- Missed a couple places when re-naming the get_value() function in the filter. -- Fix more notices. -- #1096246 Date filter was not apply the group method in the right place in the query, causing that method to be applied to all filters. -- Fix undefined index notice. -- Remove non-used function. -- Issue #1082654 by arlinsandbulte: Remove master branch files and explain in readme.txt -- Issue #906622 by bfroehle, Start cleanup of Date token code. -- Fix typo in form processing. -- Clean up the admin summary for the date filter. -- Fix another use of get_value() in the filter. -- Fix some strict errors in the views filter. -- #1094408 by Boobaa: Date field not showing up in views arguments in localized site -- #1041428, by tim.plunkett: Fix logic errors in 'All Day' computations. -- #1054458: Move date_increment_round() function into Date API module so it is always available. -- #745074, by rsevero: Don't test for valid timezone-adjusted date for dates that don't use time. -- #1076992: The timezone element never got completely updated to D7. -- #1013662 by developer-x: Remove check for start and end date - this prevented multiday-all day events from showing 'All Day' - -====================== -Version 7.x-2.0-alpha1 -====================== - -- #1082658: Saving the options as arrays breaks other things. Add a custom export plugin instead. -- #1082658, Views options need to be declared as arrays or they are not saved in the export in Views 3. -- #1075896 Break out the code into the simple argument and a complex argument that combines date fields. -- #1075890 Break out the code into the simple filter and a complex filter that combines date fields. - -====================== -Version 7.x-1.0-dev -====================== - -Abandoned the 7.x-1.x branch. - -Start a new 7.x-2.x branch that will contain a complete re-work of the Views handling. - -The 7.x-2.x branch will totally re-work and simplify the Views filters. These changes may/will -break some views and will require that you check views that use the date filter or -argument to be sure they are still configured correctly. - -There is a new date_filter_handler_simple for an individual date field that simplifies -the Views handling by using most of the core Views field handling but adds in the -possibility to set a default date using options like 'now', along with a choice of a -month/day/year drop-down selector or popup calendar selector. - -The date_filter_handler is an extension of the simple handler that allows you to combine -multiple dates into a single filter so that you can control all of them with the same -filter widget, allowing you to either 'OR' or 'AND' them together. - -The arguments will be rewritten in the same way, with a simple argument handler for -individual dates and another handler that extends that to control multiple dates with -a single argument. - -- Add some error trapping in case the parent dateObject is unable to make a date out of a string to avoid showing the user errors. -- #1027752 by B-Prod and dboulet, Fix missing table join on argument summary view. -- #1047412 by das-peter - Using date filter in February causes "The day is invalid" error. -- #1014162 Now that the datefield processing expands to date & time, the form_value is corrupted. -- Changes to the data migration code to work with latest iteration of Content Migrate. - -====================== -Version 7.x-1.0-alpha2 -====================== - -- Views made lots of changes to field handling just before the new release, which broke lots of things here. -- Adding some work-arounds to get things working again and waiting until the dust settles on how the new core fields will be handled in Views before doing much more with the Views integration. These changes should get things working with latest Views release. - -====================== -Version 7.x-1.0-alpha1 -====================== - -- Initial release, probably still buggy. -- Remove Date PHP4, no longer needed because PHP4 is not supported any more. -- Remove Date Timezone, now handled by core. +Previous versions put dates with both From and To dates into a fieldset and +other dates were not. The new code adds additional floating elements that are +hard to contain, so now all dates are enclosed in fieldsets in the node form. +There are also new elements on the form, an optional checkbox for hiding/showing +the To date and an optional checkbox for hiding/showing time. Previously dates +on the node form had 'From date' and 'To date' labels above them, this has been +changed to remove those labels, using the Google calendar date entry screen as a +model. This simplifies the node form and dates take up less space. A light grey +border has been added around each collection of dates (the From date and the To +date). The display of labels above the date parts (year, month, day, date, time, +etc) is controlled in the field settings. Previous versions did not always honor +those settings, this one does. + +New Features/UX Improvements: +#1249724 by KarenS, Gábor Hojtsy, David_Rothstein: Improve usability of date and + time input configuration. +#1250784 by David_Rothstein: Add user-friendly labels for start and end date + values in Views. +#742146 by KarenS: Add option to remove X-WR-CALNAME if VEVENT is not a feed. +By KarenS: Add option to change method from PUBLISH to REQUEST in VCALENDAR. +#334435 by KarenS: Add a theme function for the 'Date' and 'Time' labels in the + Popup widget. +#1239956 by David_Rothstein: Change the default separation between from and t + dates to use 'to' instead of a hyphen. +#1240628 by David_Rothstein: Make the date increment default to 15 instead of 1. +#1249724 by David_Rothstein: Improve usability of date and time input + configuration. +#1177198 by tim.plunkett: Allow CTools to process #dependency for date elements. +#1245562 by David_Rothstein: Rename the default date display format to something + friendlier +#1239934 by David_Rothstein, Gábor Hojtsy: Reuse the "years back and forward" + dropdown widget on the Views filter settings page. +#1239228 by Gábor Hojtsy: Date Views filter form UI improvements, clarify the + way absolute and relative dates work. +#233047 by ksenzee, David_Rothstein: Add the Vegas jQuery timepicker as a new + time selector option. +#1145976 by tim.plunkett, KarenS: Add 'is date' identifier to all date handlers. +#1234140 by arlinsundbulte: Change terminology in user-facing text from 'From/To + Date' to 'Start/End Date'. +#1233948 by KarenS: Add 'All Day' checkbox to hide/show the time parts of the + date form. If All day is chosen, any time is replaced with 00:00:00 when the + form is submitted. +#1233612 by KarenS: Add 'Collect end date' checkbox for dates with optional end + dates to hide/show the end date. +#821200 by scor, KarenS: Add RDF support to the date field. +#1211744 by EclipseGC, KarenS: Add a Date pager plugin that is designed to add + paging in conjunction with a Date argument. +#1180612 by mikeryan: Add support for Migrate module. +#1198320 by ksenzee: David_Rothstein, Noyz: Make UI improvements to field + settings page. +#1215738 by ksenzee: Make granularity settings checkboxes horizontal. +#1215686 by ksenzee: Change name of date field types to be more intuitive. +#1216996 by ksenzee: Change the years back/forward setting into two drop down + boxes. +#1222468 by ksenzee: Hide timezone options when using granularity without time. +#1229388 by ksenzee: Hide formatter from/to choices on fields without multiple + values. + +Removed deprecated functions: +By KarenS: Removed date_handler_fields(), only applicable to D6 code. +By KarenS: Removed date_views_real_url() and date_views_page_url(), used by + older calendar version. +By KarenS: Remove unused date_handler_field_multiple. + +Tests: +#1251592 by David_Rothstein: Set reasonable default values if date formats have + not been configured, and add tests for that. +#1242764 by KarenS: Add tests for every possible combination of field type, + timezone handling, and granularity. +By KarenS: More work on tests, add a foreach loop to run through all field_type + /widget_type combinations. +By KarenS: Rework tests to use a base class rather than copying the same + functions everywhere. +#1209408 by KarenS: Make sure date_repeat_calc() returns empty array for + FREQ=NONE and INTERVAL=0, also add a test for that. +#1161006 by justinrandall: Add tests to check that dates that should have time + but do not are correctly caught in validation. + +Bugfixes: +#1256406 by q0rban: use variable_get() in hook_requirments(). +By KarenS: Follow up to Issue #1145976, Make sure 'is date' only gets applied to + the date values, not delta, timezone, etc. +#1238364 by KarenS: Make sure the Date pager doesn't throw errors if the default + date is missing. +#1246416 by KarenS: Add info to the Date Popup README.txt about how to download + the WVega timepicker. +#1253230 by KarenS: Package name of Date Migrate was different than the other + Date components. +#1251592 by KarenS: Add installation message and system requirements warnings + about missing system date settings. +#1250784 by KarenS: Don't save psuedo field settings created by the new UI in + the field itself. +#1239228 by David_Rothstein: Always add Date Views css to View UI edit forms. +#1254540 by David_Rothstein: Move borders off of the wrappers so they don't + appear when the dates inside them are hidden. +#1253248 by David_Rothstein: Move RDF attribute handling to preprocess function. +#1087798 by anj: Fix X-WR-CALNAME in VCALENDAR. +#1254582 by KarenS: Repeat additions need to be adjusted to use the same time as + the original date. +By KarenS: Move vcalendar and vevent templates from Date Views to Date API + modules. +By KarenS: Follow up to Issue #1250344, We don't need extra space when there is + a description, only when there is not. +#1239228 by Gábor Hojtsy: More tweaks to filter css. +#1244924 by Gábor Hojtsy: Minor text improvements in date filter configuration. +#1245556 by David_Rothstein: Date granularity description incorrectly implies + that it affects the date attributes that are displayed +#1247444 by Gábor Hojtsy: Give a little breathing space to the date year range + "other" field +#1250344 by jessebeach: Fix padding around date fields by adding clearfix class. +#1249116 by yched: Fix various glitches with D6 migration code. +#1243022 by fmosca, KarenS: Make sure all_day #states visibility is only set + when there is a value for all_day. +#1236192 by loganfsmyth: Make sure #date_label_position has a default value in + the Date Popup module. +#1246416 by KarenS: Test whether libraries_get_path() returns a valid path + before using it. +#1235994 by KarenS: Don't display 'All Day' when using a format that has no + time. +#1245690 by mikeryan: Migration plugin missing seconds from date formats. +#1229406 by David Rothstein, Gábor Hojtsy, tim.plunkett: Fix broken timepicker + in Chrome and Safari. +#1239412 by keithm: Fix validation error when #access is false. +#1232522 by KarenS: Don't alter field_ui_field_edit form except on date fields. +#1243842 by KarenS: Make sure the All Day and Show End Date flags work correctly + in unlimited value fields that use ajax. +By KarenS: Follow up to Issue #1239956, Fix tests broken by change in date + separator. +#1241576 by KarenS: Fix date combo validation for all-day dates so they don't + fail validation because the format is unexpected. +By KarenS: Take the guesswork out of examining Date info for Views, add is_field + flag. +By KarenS: Follow up to Issue #874322, Date popup field needs to accept date + without time for the all day flag to work. +By KarenS: Follow up to Issue #1130814, It looks like the modification is not + needed in date_repeat_set_month_day and date_repeat_set_year_day. +#1160132 by seanbfuller, KarenS: Exposed filter widgets were not displaying the + default values. +By KarenS: Adjust the way widgets are styled in Views exposed filters after the + latest style changes for form elements. +#1234114 by arlinsandbulte: Add more space between checkboxes. +#1234090 by KarenS: Fix undefined variable 'all_day'. +#1232570 by dboulet: Remove some unneeded duplication of core clearfix and other + css cleanup. +#1232614 by dboulet: Date css should not be setting font family. +By KarenS: Timezone and Date Popup weren't obeying the settings for displaying + the date part label. +#1130814 by cdracars, mikeryan, mayobutter: Fix date_modify difference between + PHP 5.3.5 and 5.3.6 so both work to compute repeating dates. +#1017866 by KarenS, Jason89s: Make sure views argument and filter don't do + timezone adjustment for dates that don't have time. +#1204988 by paulsheldrake: Remove IE6 code from datepicker CSS. +#1231864 by Damien McKenna: Clean up line endings in vevent and vcalendar tpl + files. +#1231382 by dboulet: Clean up date css, code style fixes. +By KarenS: Date repeat additions and exceptions need to be reworked into full, + timezone-adjusted, datetime values when passing them to FAPI as default + values. +#1223034 by c4rl: Make sure repeating date additions and exceptions work + correctly when there is more than one repeating date in the same form. +#1229362 by KarenS: Date Popup module needs date_is_date() function, which + should not require Date module. Move that to Date API. +#1227208 by ksenzee: Minor text changes. +#1094408 by KarenS: Change method of identifying Field module filters, using the + name of the group is not robust enough. +#1227264 by gapa: Fix wrong class in date-pager.tpl.php. +#1207540 by KarenS: Summary grouping won't work right unless the formula alias + doesn't match an actual field value. +#1227350 by KarenS: Summary query still needs the formula, add it back. +By KarenS: Fix 'variables cannot be passed by reference' notice on repeats page. +#1222736 by KarenS: Fix export errors caused when previous export fix of using + export plugins got broken by changes in Views. +#1077490 by KarenS: Fix notices about missing #date_flexible. +By KarenS: Move the 'top' date pager to below the header instead of below + attachment_before so you can add header text above it. +By KarenS: The value for variable_get('date_first_day') should default to zero + to match core default. +#1147620 by KarenS: With an assist from tim.plunkett, Fix the query so it will + locate dates that span days or months by checking the intersection of the date + range and the query range. Also add an option to the argument so you can do a + simple query for either the from or to date when checking the whole range + isn't the right solution. +#307274 by ksenzee: Fix broken validation for absolute value in years back + /forward. +#1173374 by fietserwin: Remove translation of the jQuery datepicker day and + month names, now handled by core. +#1192020 by tim.plunkett: Fixed date granularity is too fragile in + date_field_all_day(). +#1110012 by KarenS: Remove 'parent' items from Views plugins, no longer needed? + See if this fixes the issue. +#1183892 by KarenS: Initialize $identifier in date_views_filter_handler_simple. +#1103290 by stickywes, ingaro: Change postgres 'FMYYYY-FMMM-FMDDTFMHH:FMMI:FMSS' + to 'FMYYYY-FMMM-FMDDTFMHH24:FMMI:FMSS'. +#1186528 by jox: Make date field combo label translatable. +#1123186 by KingJoshi: Fix misnamed date_part_hour_prefix in hook_theme(). +#1032942 by fietserwin: Rename date popup functions that are getting picked up + by theme('date'). +#1206756 by KarenS: More tweaks to the validation changes. They were blocking + zero times and causing some test failures. +#1197352 by KarenS: Don't display language about from and to dates when there + is no todate. +#1201288 by rafa0961: fix broken references to SQLSERVER. +By KarenS: More work on cleaning up validation, add in some ideas from + fearlsgrove about checking the granularity of the input array against the + expected granularity. +#1161006 Dates that should have time but do not were not correctly caught in + validation. +By KarenS: Get rid of overlapping formatter functionality. There should be a + default formatter with the option to choose a date format as a setting, not a + formatter for each format. This was a leftover from the D6 functionality. +#1159404 by mikeryan: Fix incorrect call to parse an rrule in + date_repeat_build_dates(). +#1160656 by jjs: Replace missing break in date_api.sql.inc that breaks + PostgreSQL. +#1150454 by KarenS: Fix undefined index notices for repeat_collapsed value. +#1150462 by KarenS: Put length limit on content type names created by Date Tools + so block delta won't overflow the allowed size. +#1136734 by KarenS: When migrating date format data from D6 to D7, don't try to + overwrite existing custom values. +#1161042 by Ollie222: Date filters using time were inconsistently formatted. +#1130884 by KarenS: Bad logic in 'between' filter SQL, should always join with + AND. +#1139418 by ankur: Bad logic in week argument SQL, should always join with AND. +#1118356 by KarenS: Disabling the Timepicker was having no effect. +#408770 by vkareh: Make sure dates with #access = FALSE get passed through + date_combo_element_process(). +#1037174 by das-peter: add ajax support to date popup. +#1160614 by joelstein: Make sure date repeat rule gets split correctly no matter + which line endings were used. +#1110708 by mr.baileys: Fix problem combining date filter with other exposed + filters. +By KarenS: Switch some references to $node to use $entity instead. +#1112206, add a dummy field to the date navigation bar query to keep it from + trying to create invalid sql. +#1112206, $this->view->query->clear_fields() is still need for date_browser to + keep Views from trying to use missing field values. +By KarenS: Date browser only works with date_argument, should work with any + argument derived from date. + + +Date 7.x-2.0-alpha3, 2011-04-26 +------------------------------- +#1138700 by KarenS: missed a couple references to the construct() function. + + +Date 7.x-2.0-alpha2, 2011-04-26 +------------------------------- +By KarenS: Follow up to Issue #1103290, constructor was not set up correctly and + did not get triggered, so none of the date handlers had a db_type. +#1138622 by KarenS: preliminary pass at adding support for SQL Server. +#1136618 by ksenzee, Fix broken hide/show capability for date filter values. +#1059078 Add preliminary support for SQLite dates. +#1103290 by kevintheday, Use db_driver() to determine database engine. +By KarenS: The 'now' values got broken again somewhere along the line. Now we + need to switch the ISO format used by our SQL queries back to the datetime + format the widgets use. +By KarenS: Looks like Views changed ['expose']['operator'] to + ['expose']['operator_id']. +#1115770, Make sure filters values are switched back to ISO format so time + comparisons work correctly. +#1132916 by znerol, Fix a couple more usages of date_default_timezone_name(). +#1131308 by KarenS: Don't try to do timzone conversion when there is no + localzone for a field. +#1093222 Fix broken function to remove calendar views. +#820670 Add update to move D6 date format data to D7 data. +1074344 Fix problem with date select widget that keeps resetting pm back to am. +#1001186 by KarenS: Make sure that a 2 digit year is flagged as an error. +#1117420 by threewestwinds: Make timepicker formats more useful. +#1126408 by thekevinday: Add more sql formats to date api. +#1129326 by robertom: Add missing value to element in date_combo. +#998220 by jamsilver, yched: Fix handling of validation in date_combo. This + also fixes errors when using a date in Profile 2 and Field Collections. +#1022592 by andypost, tim.plunkett optimize hook_form_alter into + hook_form_FORMID_alter. +1021424 by joostvdl, wiizzard: Fix context in translations to use core values. +By KarenS: Remove extra fields from the Date field. I think the filter and + argument now get the right tables joined in without that. We still need the + extra fields on calendar, but now we will only add them in the calendar view. +By KarenS: Fix some notices in vcalendar theme. +By KarenS: Fix the mini calendar querystring. +By KarenS: Fix logic for day argument formula. +By KarenS: Fix logic for week argument formula. +#1086582 by bojanz: Summary options are in a different place now, fix the method + of removing the summary option on multiple-date arguments. +By KarenS: Views renamed 'wildcard' to 'exception'. +By KarenS: Apparently the handler->argument value is not always populated. +By KarenS: Fix fallback value for date_group. +#1103032 by tim.plunkett: Remove token integration code until Token module is + fixed. +By KarenS: Now that there is no group of 'Fields' we need different tests to + tell if this is a field filter. +By KarenS: Views changed the group name of fields from 'Fields' to 'Content'. + Blech, broke everything. +By KarenS: Fix potential error if handler is broken. +By KarenS: Remove reference to a function that no longer exists in Views. +#1116962 by jpontani, mordonez: Parse Error - date_views_fields.inc on line 119. +By KarenS: Fix the logic for year and month only dates. +By KarenS: Add helper function to test if handler is a date handler. +By KarenS: Fix broken handling when creating dates from timestamps. +By KarenS: Set some defaults for dates without month or day. +##1094408 by Boobaa: Date field not showing up in views arguments in localized + site +#1018412 by omerida: get_class() called without object. +By KarenS: There were some problems when creating a year-only date using the + select filter caused by trying to create a date with zeros in the month and + day. Fix the date building logic to force a valid month and day into them. +By KarenS: Complex filters were not using the date handler of the individual + fields. +#1100934 by KarenS: Replace deprecated date_array() function. +By KarenS: #1096246 Date argument also was not applying the group method in the + right place in the query, causing that method to be applied to all filters. +By KarenS: Missed a couple places when re-naming the get_value() function in the + filter. +By KarenS: Fix more notices. +#1096246 by KarenS: Date filter was not apply the group method in the right + place in the query, causing that method to be applied to all filters. +By KarenS: Fix undefined index notice. +By KarenS: Remove non-used function. +#1082654 by arlinsandbulte: Remove master branch files and explain in readme.txt +#906622 by bfroehle: Start cleanup of Date token code. +By KarenS: Fix typo in form processing. +By KarenS: Clean up the admin summary for the date filter. +By KarenS: Fix another use of get_value() in the filter. +By KarenS: Fix some strict errors in the views filter. +#1094408 by Boobaa: Date field not showing up in views arguments in localized. + site. +#1041428 by tim.plunkett: Fix logic errors in 'All Day' computations. +#1054458 by KarenS: Move date_increment_round() function into Date API module so + it is always available. +#745074 by rsevero: Don't test for valid timezone-adjusted date for dates that + don't use time. +#1076992 by KarenS: The timezone element never got completely updated to D7. +#1013662 by developer-x: Remove check for start and end date - this prevented + multiday-all day events from showing 'All Day'. + + +Date 7.x-2.0-alpha1, 2011-03-09 +------------------------------- +Start a new 7.x-2.x branch that will contain a complete re-work of the Views +handling. + +The 7.x-2.x branch will totally re-work and simplify the Views filters. These +changes may/will break some views and will require that you check views that use +the date filter or argument to be sure they are still configured correctly. + +There is a new date_filter_handler_simple for an individual date field that +simplifies the Views handling by using most of the core Views field handling but +adds in the possibility to set a default date using options like 'now', along +with a choice of a month/day/year drop-down selector or popup calendar selector. + +The date_filter_handler is an extension of the simple handler that allows you to +combine multiple dates into a single filter so that you can control all of them +with the same filter widget, allowing you to either 'OR' or 'AND' them together. + +The arguments will be rewritten in the same way, with a simple argument handler +for individual dates and another handler that extends that to control multiple +dates with a single argument. + +#1082658 by KarenS: Saving the options as arrays breaks other things. Add a + custom export plugin instead. +#1082658 by KarenS: Views options need to be declared as arrays or they are not + saved in the export in Views 3. +#1075896 by KarenS: Break out the code into the simple argument and a complex + argument that combines date fields. +#1075890 by KarenS: Break out the code into the simple filter and a complex + filter that combines date fields. +By KarenS: Add some error trapping in case the parent dateObject is unable to + make a date out of a string to avoid showing the user errors. +#1027752 by B-Prod and dboulet, Fix missing table join on argument summary + view. +#1047412 by das-peter - Using date filter in February causes "The day is + invalid" error. +#1014162 Now that the datefield processing expands to date & time, the + form_value is corrupted. +By KarenS: Changes to the data migration code to work with latest iteration of + Content Migrate. diff --git a/dkan/modules/contrib/date/date.devel_generate.inc b/dkan/modules/contrib/date/date.devel_generate.inc index 6c61e23cf..739d0d084 100644 --- a/dkan/modules/contrib/date/date.devel_generate.inc +++ b/dkan/modules/contrib/date/date.devel_generate.inc @@ -41,7 +41,7 @@ function date_devel_generate($entity, $field, $instance, $bundle) { // Modify End date by 1 hour to 3 days, shorter for repeating dates // longer for others. - $start2 = clone($start); + $start2 = clone $start; $max = !empty($field['settings']['repeat']) ? 720 : 4320; $max = 240; date_modify($start2, '+' . mt_rand(60, $max) . ' minutes'); diff --git a/dkan/modules/contrib/date/date.field.inc b/dkan/modules/contrib/date/date.field.inc index 7aef97bee..319109f5c 100644 --- a/dkan/modules/contrib/date/date.field.inc +++ b/dkan/modules/contrib/date/date.field.inc @@ -375,7 +375,7 @@ function date_field_validate($entity_type, $entity, $field, $instance, $langcode $process = date_process_values($field, $instance); $date1 = new DateObject($item['value'], $item['timezone'], date_type_format($field['type'])); if (count($process) == 1 || (empty($item['value2']) && $item['value2'] !== 0)) { - $date2 = clone($date1); + $date2 = clone $date1; } else { $date2 = new DateObject($item['value2'], $item['timezone'], date_type_format($field['type'])); diff --git a/dkan/modules/contrib/date/date.info b/dkan/modules/contrib/date/date.info index 28263b420..bd4467ebe 100644 --- a/dkan/modules/contrib/date/date.info +++ b/dkan/modules/contrib/date/date.info @@ -15,9 +15,7 @@ files[] = tests/date_views_pager.test files[] = tests/date_views_popup.test files[] = tests/date_form.test -; Information added by Drupal.org packaging script on 2017-04-07 -version = "7.x-2.10" -core = "7.x" +; Information added by drush on 2018-01-16 +version = "7.x-2.10+4-dev" project = "date" -datestamp = "1491562090" - +datestamp = "1516128997" \ No newline at end of file diff --git a/dkan/modules/contrib/date/date_all_day/date_all_day.info b/dkan/modules/contrib/date/date_all_day/date_all_day.info index fcdc673a0..7a4175300 100644 --- a/dkan/modules/contrib/date/date_all_day/date_all_day.info +++ b/dkan/modules/contrib/date/date_all_day/date_all_day.info @@ -5,9 +5,7 @@ dependencies[] = date package = Date/Time core = 7.x -; Information added by Drupal.org packaging script on 2017-04-07 -version = "7.x-2.10" -core = "7.x" +; Information added by drush on 2018-01-16 +version = "7.x-2.10+4-dev" project = "date" -datestamp = "1491562090" - +datestamp = "1516128997" \ No newline at end of file diff --git a/dkan/modules/contrib/date/date_api/date_api.info b/dkan/modules/contrib/date/date_api/date_api.info index c537ca040..6c5e1bb94 100644 --- a/dkan/modules/contrib/date/date_api/date_api.info +++ b/dkan/modules/contrib/date/date_api/date_api.info @@ -9,9 +9,7 @@ stylesheets[all][] = date.css files[] = date_api.module files[] = date_api_sql.inc -; Information added by Drupal.org packaging script on 2017-04-07 -version = "7.x-2.10" -core = "7.x" +; Information added by drush on 2018-01-16 +version = "7.x-2.10+4-dev" project = "date" -datestamp = "1491562090" - +datestamp = "1516128997" \ No newline at end of file diff --git a/dkan/modules/contrib/date/date_api/date_api.module b/dkan/modules/contrib/date/date_api/date_api.module index 160bc9fbd..804a2732c 100644 --- a/dkan/modules/contrib/date/date_api/date_api.module +++ b/dkan/modules/contrib/date/date_api/date_api.module @@ -998,8 +998,8 @@ class DateObject extends DateTime { public function difference($date2_in, $measure = 'seconds', $absolute = TRUE) { // Create cloned objects or original dates will be impacted by the // date_modify() operations done in this code. - $date1 = clone($this); - $date2 = clone($date2_in); + $date1 = clone $this; + $date2 = clone $date2_in; if (is_object($date1) && is_object($date2)) { $diff = date_format($date2, 'U') - date_format($date1, 'U'); if ($diff == 0) { @@ -2060,7 +2060,7 @@ function date_week_range($week, $year) { date_modify($min_date, '-' . strval((7 + $day_wday - $first_day) % 7) . ' days'); // Move forwards to the last day of the week. - $max_date = clone($min_date); + $max_date = clone $min_date; date_modify($max_date, '+6 days'); if (date_format($min_date, 'Y') != $year) { @@ -2097,7 +2097,7 @@ function date_iso_week_range($week, $year) { } // Move forwards to the last day of the week. - $max_date = clone($min_date); + $max_date = clone $min_date; date_modify($max_date, '+6 days'); return array($min_date, $max_date); } @@ -2148,7 +2148,7 @@ function date_week($date) { // Remove the leap week if it's present. if ($date_year > intval($parts[0])) { - $last_date = clone($date); + $last_date = clone $date; date_modify($last_date, '-7 days'); $week = date_format($last_date, 'W') + 1; } diff --git a/dkan/modules/contrib/date/date_api/date_api_ical.inc b/dkan/modules/contrib/date/date_api/date_api_ical.inc index 4911298c5..9f7b89444 100644 --- a/dkan/modules/contrib/date/date_api/date_api_ical.inc +++ b/dkan/modules/contrib/date/date_api/date_api_ical.inc @@ -535,7 +535,7 @@ function date_ical_parse_duration(&$subgroup, $field = 'DURATION') { $timezone = 'UTC'; } $date = new DateObject($start_date, $timezone); - $date2 = clone($date); + $date2 = clone $date; foreach ($items as $item => $count) { if ($count > 0) { date_modify($date2, '+' . $count . ' ' . $item); diff --git a/dkan/modules/contrib/date/date_api/date_api_sql.inc b/dkan/modules/contrib/date/date_api/date_api_sql.inc index a95ca4efa..6294fc6e8 100644 --- a/dkan/modules/contrib/date/date_api/date_api_sql.inc +++ b/dkan/modules/contrib/date/date_api/date_api_sql.inc @@ -1143,7 +1143,7 @@ class date_sql_handler { // Build a range from a period-only argument (assumes the min date is now.) if (empty($parts[0]['date']) && !empty($parts[0]['period']) && (empty($parts[1]))) { $min_date = date_now(); - $max_date = clone($min_date); + $max_date = clone $min_date; foreach ($parts[0]['period'] as $part => $value) { date_modify($max_date, "+$value $part"); } @@ -1153,7 +1153,7 @@ class date_sql_handler { // Build a range from a period to period argument. if (empty($parts[0]['date']) && !empty($parts[0]['period']) && !empty($parts[1]['period'])) { $min_date = date_now(); - $max_date = clone($min_date); + $max_date = clone $min_date; foreach ($parts[0]['period'] as $part => $value) { date_modify($min_date, "+$value $part"); } @@ -1176,7 +1176,7 @@ class date_sql_handler { // Build a range from start date + period. elseif (!empty($parts[1]['period'])) { foreach ($parts[1]['period'] as $part => $value) { - $max_date = clone($min_date); + $max_date = clone $min_date; date_modify($max_date, "+$value $part"); } date_modify($max_date, '-1 second'); diff --git a/dkan/modules/contrib/date/date_context/date_context.info b/dkan/modules/contrib/date/date_context/date_context.info index 97cb8c9d9..3e181c86e 100644 --- a/dkan/modules/contrib/date/date_context/date_context.info +++ b/dkan/modules/contrib/date/date_context/date_context.info @@ -8,9 +8,7 @@ dependencies[] = context files[] = date_context.module files[] = plugins/date_context_date_condition.inc -; Information added by Drupal.org packaging script on 2017-04-07 -version = "7.x-2.10" -core = "7.x" +; Information added by drush on 2018-01-16 +version = "7.x-2.10+4-dev" project = "date" -datestamp = "1491562090" - +datestamp = "1516128997" \ No newline at end of file diff --git a/dkan/modules/contrib/date/date_elements.inc b/dkan/modules/contrib/date/date_elements.inc index 656d49989..462bc18f9 100644 --- a/dkan/modules/contrib/date/date_elements.inc +++ b/dkan/modules/contrib/date/date_elements.inc @@ -292,6 +292,9 @@ function date_combo_element_process($element, &$form_state, $form) { $process = date_process_values($field, $instance); foreach ($process as $processed) { if (!isset($element['#default_value'][$processed])) { + if (empty($element['#default_value']) || !is_array($element['#default_value'])) { + $element['#default_value'] = array(); + } $element['#default_value'][$processed] = ''; } $date = date_local_date($element['#default_value'], $element['#date_timezone'], $field, $instance, $processed); diff --git a/dkan/modules/contrib/date/date_migrate/date_migrate.info b/dkan/modules/contrib/date/date_migrate/date_migrate.info index 41e416a36..4390ba675 100644 --- a/dkan/modules/contrib/date/date_migrate/date_migrate.info +++ b/dkan/modules/contrib/date/date_migrate/date_migrate.info @@ -4,9 +4,7 @@ core = 7.x package = Date/Time hidden = TRUE -; Information added by Drupal.org packaging script on 2017-04-07 -version = "7.x-2.10" -core = "7.x" +; Information added by drush on 2018-01-16 +version = "7.x-2.10+4-dev" project = "date" -datestamp = "1491562090" - +datestamp = "1516128997" \ No newline at end of file diff --git a/dkan/modules/contrib/date/date_migrate/date_migrate_example/date_migrate_example.info b/dkan/modules/contrib/date/date_migrate/date_migrate_example/date_migrate_example.info index ba07220d4..2348b6d22 100644 --- a/dkan/modules/contrib/date/date_migrate/date_migrate_example/date_migrate_example.info +++ b/dkan/modules/contrib/date/date_migrate/date_migrate_example/date_migrate_example.info @@ -20,9 +20,7 @@ package = "Features" project = "date_migrate_example" version = "7.x-2.0" -; Information added by Drupal.org packaging script on 2017-04-07 -version = "7.x-2.10" -core = "7.x" +; Information added by drush on 2018-01-16 +version = "7.x-2.10+4-dev" project = "date" -datestamp = "1491562090" - +datestamp = "1516128997" \ No newline at end of file diff --git a/dkan/modules/contrib/date/date_popup/date_popup.info b/dkan/modules/contrib/date/date_popup/date_popup.info index 41945a636..f87819174 100644 --- a/dkan/modules/contrib/date/date_popup/date_popup.info +++ b/dkan/modules/contrib/date/date_popup/date_popup.info @@ -7,9 +7,7 @@ configure = admin/config/date/date_popup stylesheets[all][] = themes/datepicker.1.7.css -; Information added by Drupal.org packaging script on 2017-04-07 -version = "7.x-2.10" -core = "7.x" +; Information added by drush on 2018-01-16 +version = "7.x-2.10+4-dev" project = "date" -datestamp = "1491562090" - +datestamp = "1516128997" \ No newline at end of file diff --git a/dkan/modules/contrib/date/date_repeat/date_repeat.info b/dkan/modules/contrib/date/date_repeat/date_repeat.info index 0a63175c7..2f41bb6d8 100644 --- a/dkan/modules/contrib/date/date_repeat/date_repeat.info +++ b/dkan/modules/contrib/date/date_repeat/date_repeat.info @@ -7,9 +7,7 @@ php = 5.2 files[] = tests/date_repeat.test files[] = tests/date_repeat_form.test -; Information added by Drupal.org packaging script on 2017-04-07 -version = "7.x-2.10" -core = "7.x" +; Information added by drush on 2018-01-16 +version = "7.x-2.10+4-dev" project = "date" -datestamp = "1491562090" - +datestamp = "1516128997" \ No newline at end of file diff --git a/dkan/modules/contrib/date/date_repeat/date_repeat_calc.inc b/dkan/modules/contrib/date/date_repeat/date_repeat_calc.inc index d840df629..bb0292823 100644 --- a/dkan/modules/contrib/date/date_repeat/date_repeat_calc.inc +++ b/dkan/modules/contrib/date/date_repeat/date_repeat_calc.inc @@ -132,7 +132,7 @@ function _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additi if (!empty($rrule['BYMONTHDAY'])) { $finished = FALSE; - $current_day = clone($start_date); + $current_day = clone $start_date; $direction_days = array(); // Deconstruct the day in case it has a negative modifier. foreach ($rrule['BYMONTHDAY'] as $day) { @@ -193,7 +193,7 @@ function _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additi elseif (empty($rrule['BYDAY'])) { // $current_day will keep track of where we are in the calculation. - $current_day = clone($start_date); + $current_day = clone $start_date; $finished = FALSE; $months = !empty($rrule['BYMONTH']) ? $rrule['BYMONTH'] : array(); while (!$finished) { @@ -275,7 +275,7 @@ function _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additi // need to be processed one month or year at a time. if (!empty($direction_days) && in_array($rrule['FREQ'], array('MONTHLY', 'YEARLY'))) { $finished = FALSE; - $current_day = clone($start_date); + $current_day = clone $start_date; while (!$finished) { foreach ($direction_days as $day) { // Find the BYDAY date in the current month. @@ -314,7 +314,7 @@ function _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additi if (!empty($week_days) && in_array($rrule['FREQ'], array('MONTHLY', 'WEEKLY', 'YEARLY'))) { $finished = FALSE; - $current_day = clone($start_date); + $current_day = clone $start_date; $format = $rrule['FREQ'] == 'YEARLY' ? 'Y' : 'n'; $current_period = date_format($current_day, $format); diff --git a/dkan/modules/contrib/date/date_repeat_field/date_repeat_field.info b/dkan/modules/contrib/date/date_repeat_field/date_repeat_field.info index c9aa9d67b..5d78f6936 100644 --- a/dkan/modules/contrib/date/date_repeat_field/date_repeat_field.info +++ b/dkan/modules/contrib/date/date_repeat_field/date_repeat_field.info @@ -7,9 +7,7 @@ stylesheets[all][] = date_repeat_field.css package = Date/Time core = 7.x -; Information added by Drupal.org packaging script on 2017-04-07 -version = "7.x-2.10" -core = "7.x" +; Information added by drush on 2018-01-16 +version = "7.x-2.10+4-dev" project = "date" -datestamp = "1491562090" - +datestamp = "1516128997" \ No newline at end of file diff --git a/dkan/modules/contrib/date/date_repeat_field/date_repeat_field.module b/dkan/modules/contrib/date/date_repeat_field/date_repeat_field.module index d4d4c9021..042136f72 100644 --- a/dkan/modules/contrib/date/date_repeat_field/date_repeat_field.module +++ b/dkan/modules/contrib/date/date_repeat_field/date_repeat_field.module @@ -503,7 +503,7 @@ function date_repeat_build_dates($rrule = NULL, $rrule_values = NULL, $field, $i $date_start = new DateObject($date, $timezone, DATE_FORMAT_DATETIME); $date_start->limitGranularity($field['settings']['granularity']); date_timezone_set($date_start, timezone_open($timezone_db)); - $date_end = clone($date_start); + $date_end = clone $date_start; date_modify($date_end, '+' . $duration . ' seconds'); $value[$delta] = array( 'value' => date_format($date_start, date_type_format($field['type'])), diff --git a/dkan/modules/contrib/date/date_tools/date_tools.info b/dkan/modules/contrib/date/date_tools/date_tools.info index 04bfb03f9..cdb678f82 100644 --- a/dkan/modules/contrib/date/date_tools/date_tools.info +++ b/dkan/modules/contrib/date/date_tools/date_tools.info @@ -6,9 +6,7 @@ core = 7.x configure = admin/config/date/tools files[] = tests/date_tools.test -; Information added by Drupal.org packaging script on 2017-04-07 -version = "7.x-2.10" -core = "7.x" +; Information added by drush on 2018-01-16 +version = "7.x-2.10+4-dev" project = "date" -datestamp = "1491562090" - +datestamp = "1516128997" \ No newline at end of file diff --git a/dkan/modules/contrib/date/date_views/date_views.info b/dkan/modules/contrib/date/date_views/date_views.info index 68b32cc2e..11355cb7f 100644 --- a/dkan/modules/contrib/date/date_views/date_views.info +++ b/dkan/modules/contrib/date/date_views/date_views.info @@ -12,9 +12,7 @@ files[] = includes/date_views_filter_handler_simple.inc files[] = includes/date_views.views.inc files[] = includes/date_views_plugin_pager.inc -; Information added by Drupal.org packaging script on 2017-04-07 -version = "7.x-2.10" -core = "7.x" +; Information added by drush on 2018-01-16 +version = "7.x-2.10+4-dev" project = "date" -datestamp = "1491562090" - +datestamp = "1516128997" \ No newline at end of file diff --git a/dkan/modules/contrib/date/date_views/includes/date_views_plugin_pager.inc b/dkan/modules/contrib/date/date_views/includes/date_views_plugin_pager.inc index 1addd2000..d6b3b33f4 100644 --- a/dkan/modules/contrib/date/date_views/includes/date_views_plugin_pager.inc +++ b/dkan/modules/contrib/date/date_views/includes/date_views_plugin_pager.inc @@ -279,9 +279,9 @@ class date_views_plugin_pager extends views_plugin_pager { $this->view->date_info->next_date = $nextdate ? new DateObject($nextdate, NULL, $format) : NULL; } else { - $this->view->date_info->prev_date = clone($argument->min_date); + $this->view->date_info->prev_date = clone $argument->min_date; date_modify($this->view->date_info->prev_date, '-1 ' . $argument->date_handler->granularity); - $this->view->date_info->next_date = clone($argument->min_date); + $this->view->date_info->next_date = clone $argument->min_date; date_modify($this->view->date_info->next_date, '+1 ' . $argument->date_handler->granularity); } // Write the date_info properties that depend on the current value. diff --git a/dkan/modules/contrib/date/tests/date_test/date_test.info b/dkan/modules/contrib/date/tests/date_test/date_test.info index 2f265fa75..d7126731b 100644 --- a/dkan/modules/contrib/date/tests/date_test/date_test.info +++ b/dkan/modules/contrib/date/tests/date_test/date_test.info @@ -6,9 +6,7 @@ core = 7.x hidden = TRUE dependencies[] = date -; Information added by Drupal.org packaging script on 2017-04-07 -version = "7.x-2.10" -core = "7.x" +; Information added by drush on 2018-01-16 +version = "7.x-2.10+4-dev" project = "date" -datestamp = "1491562090" - +datestamp = "1516128997" \ No newline at end of file diff --git a/dkan/modules/contrib/diff/DiffEngine.php b/dkan/modules/contrib/diff/DiffEngine.php index 04234b764..07d3554e0 100644 --- a/dkan/modules/contrib/diff/DiffEngine.php +++ b/dkan/modules/contrib/diff/DiffEngine.php @@ -312,7 +312,8 @@ function _diag($xoff, $xlim, $yoff, $ylim, $nchunks) { } $matches = $ymatches[$line]; reset($matches); - while (list ($junk, $y) = each($matches)) { + while ($y = current($matches)) { + next($matches); if (empty($this->in_seq[$y])) { $k = $this->_lcs_pos($y); USE_ASSERTS && assert($k > 0); @@ -320,7 +321,8 @@ function _diag($xoff, $xlim, $yoff, $ylim, $nchunks) { break; } } - while (list ($junk, $y) = each($matches)) { + while ($y = current($matches)) { + next($matches); if ($y > $this->seq[$k-1]) { USE_ASSERTS && assert($y < $this->seq[$k]); // Optimization: this is a common case: @@ -454,7 +456,7 @@ function _shift_boundaries($lines, &$changed, $other_changed) { $i = 0; $j = 0; - USE_ASSERTS && assert('sizeof($lines) == sizeof($changed)'); + USE_ASSERTS && assert(sizeof($lines) == sizeof($changed)); $len = sizeof($lines); $other_len = sizeof($other_changed); @@ -474,7 +476,7 @@ function _shift_boundaries($lines, &$changed, $other_changed) { $j++; } while ($i < $len && ! $changed[$i]) { - USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]'); + USE_ASSERTS && assert($j < $other_len && ! $other_changed[$j]); $i++; $j++; while ($j < $other_len && $other_changed[$j]) { @@ -510,11 +512,11 @@ function _shift_boundaries($lines, &$changed, $other_changed) { while ($start > 0 && $changed[$start - 1]) { $start--; } - USE_ASSERTS && assert('$j > 0'); + USE_ASSERTS && assert($j > 0); while ($other_changed[--$j]) { continue; } - USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]'); + USE_ASSERTS && assert($j >= 0 && !$other_changed[$j]); } /* @@ -537,7 +539,7 @@ function _shift_boundaries($lines, &$changed, $other_changed) { while ($i < $len && $changed[$i]) { $i++; } - USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]'); + USE_ASSERTS && assert($j < $other_len && ! $other_changed[$j]); $j++; if ($j < $other_len && $other_changed[$j]) { $corresponding = $i; @@ -555,11 +557,11 @@ function _shift_boundaries($lines, &$changed, $other_changed) { while ($corresponding < $i) { $changed[--$start] = 1; $changed[--$i] = 0; - USE_ASSERTS && assert('$j > 0'); + USE_ASSERTS && assert($j > 0); while ($other_changed[--$j]) { continue; } - USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]'); + USE_ASSERTS && assert($j >= 0 && !$other_changed[$j]); } } } @@ -742,8 +744,8 @@ class MappedDiff extends Diff { * have the same number of elements as $to_lines. */ function __construct($from_lines, $to_lines, $mapped_from_lines, $mapped_to_lines) { - assert(sizeof($from_lines) == sizeof($mapped_from_lines)); - assert(sizeof($to_lines) == sizeof($mapped_to_lines)); + USE_ASSERTS && assert(sizeof($from_lines) == sizeof($mapped_from_lines)); + USE_ASSERTS && assert(sizeof($to_lines) == sizeof($mapped_to_lines)); parent::__construct($mapped_from_lines, $mapped_to_lines); @@ -1000,7 +1002,7 @@ function addWords($words, $tag = '') { $this->_flushLine($tag); $word = drupal_substr($word, 1); } - assert(!strstr($word, "\n")); + USE_ASSERTS && assert(!strstr($word, "\n")); $this->_group .= $word; } } diff --git a/dkan/modules/contrib/diff/diff.admin.inc b/dkan/modules/contrib/diff/diff.admin.inc index 8c5202f27..1a61ba1d7 100644 --- a/dkan/modules/contrib/diff/diff.admin.inc +++ b/dkan/modules/contrib/diff/diff.admin.inc @@ -112,7 +112,12 @@ function diff_admin_field_overview() { '#markup' => '

' . t('This table provides a summary of the field type support found on the system. It is recommended that you use global settings whenever possible to configure field comparison settings.') . '

', ); - $header = array(t('Type'), t('Module'), t('Operations')); + $header = array( + t('Type'), + t('Machine name'), + t('Module'), + t('Operations'), + ); $rows = array(); // Skip field types which have no widget types. @@ -129,10 +134,8 @@ function diff_admin_field_overview() { foreach ($field_types as $field_name => $field_type) { if (!empty($widgets[$field_name])) { $row = array(); - $row[] = t('@field_label (%field_type)', array( - '@field_label' => $field_type['label'], - '%field_type' => $field_name, - )); + $row[] = $field_type['label']; + $row[] = $field_name; $row[] = $field_type['module']; $row[] = l(t('Global settings'), 'admin/config/content/diff/fields/' . $field_name); $rows[] = $row; diff --git a/dkan/modules/contrib/diff/diff.api.php b/dkan/modules/contrib/diff/diff.api.php index e2b6f8485..f85beb7e5 100644 --- a/dkan/modules/contrib/diff/diff.api.php +++ b/dkan/modules/contrib/diff/diff.api.php @@ -108,6 +108,22 @@ function hook_entity_diff($old_entity, $new_entity, $context) { * @see hook_entity_diff() */ function hook_entity_diff_alter(&$entity_diffs, $context) { + if ($context['entity_type'] == 'node') { + $old_entity = $context['old_entity']; + $new_entity = $context['new_entity']; + $entity_diffs['custom_vid'] = array( + '#name' => t('Second VID'), + '#old' => array($old_entity->vid), + '#new' => array($new_entity->vid), + '#weight' => 5, + ); + $entity_diffs['custom_log'] = array( + '#name' => t('Second log'), + '#old' => array($old_entity->log), + '#new' => array($new_entity->log), + '#weight' => 6, + ); + } } /** diff --git a/dkan/modules/contrib/diff/diff.info b/dkan/modules/contrib/diff/diff.info index 77b731686..4bc64e406 100644 --- a/dkan/modules/contrib/diff/diff.info +++ b/dkan/modules/contrib/diff/diff.info @@ -5,9 +5,8 @@ configure = admin/config/content/diff files[] = DiffEngine.php -; Information added by Drupal.org packaging script on 2016-12-20 -version = "7.x-3.3" +; Information added by Drupal.org packaging script on 2018-11-05 +version = "7.x-3.4" core = "7.x" project = "diff" -datestamp = "1482211686" - +datestamp = "1541401388" diff --git a/dkan/modules/contrib/diff/diff.install b/dkan/modules/contrib/diff/diff.install index 9d8b585dd..6af5801a0 100644 --- a/dkan/modules/contrib/diff/diff.install +++ b/dkan/modules/contrib/diff/diff.install @@ -133,9 +133,14 @@ function diff_update_7306() { } /** - * Grants access to the Diff "View Changes" button permission to all users. + * Grants access to the Diff "View Changes" button permission to all users + * if the Diff module is enabled. You need to manually update the permissions + * if the module is currently disabled. */ function diff_update_7307() { - user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('diff view changes')); - user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array('diff view changes')); + // The update hooks run even if disabled, and this throws an error. + if (module_exists('diff')) { + user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('diff view changes')); + user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array('diff view changes')); + } } diff --git a/dkan/modules/contrib/diff/diff.module b/dkan/modules/contrib/diff/diff.module index d3050e3bb..a8193c0b2 100644 --- a/dkan/modules/contrib/diff/diff.module +++ b/dkan/modules/contrib/diff/diff.module @@ -475,7 +475,8 @@ function diff_node_form_build_preview_changes($form, &$form_state) { $node = node_form_submit_build_node($form, $form_state); // Create diff of old node and edited node. - $rows = _diff_body_rows($old_node, $node); + $state = variable_get('diff_default_state_node', 'raw'); + $rows = _diff_body_rows($old_node, $node, $state); $header = _diff_default_header(t('Original'), t('Changes')); $changes = theme('table__diff__preview', array( diff --git a/dkan/modules/contrib/field_group_table/PATCHES.txt b/dkan/modules/contrib/field_group_table/PATCHES.txt index 92bd59404..2e97eca71 100644 --- a/dkan/modules/contrib/field_group_table/PATCHES.txt +++ b/dkan/modules/contrib/field_group_table/PATCHES.txt @@ -1,4 +1,5 @@ The following patches have been applied to this project: - https://www.drupal.org/files/issues/added_missing_isset_calls-2887897-2.patch +- https://www.drupal.org/files/issues/2018-11-28/undefined-index-classes-3016830-0.patch This file was automatically generated by Drush Make (http://drupal.org/project/drush). diff --git a/dkan/modules/contrib/field_group_table/field_group_table.module b/dkan/modules/contrib/field_group_table/field_group_table.module index 5532f89bb..5d5b07e07 100644 --- a/dkan/modules/contrib/field_group_table/field_group_table.module +++ b/dkan/modules/contrib/field_group_table/field_group_table.module @@ -186,7 +186,7 @@ function field_group_table_field_group_pre_render(&$element, $group, &$form) { '#groups' => array_keys($form['#groups']), '#settings' => $settings, '#attributes' => array( - 'class' => array_merge(array('field-group-table', $group->group_name), explode(' ', $settings['classes'])), + 'class' => isset($settings['classes']) ? array_merge(array('field-group-table', $group->group_name), explode(' ', $settings['classes'])) : array_merge(array('field-group-table', $group->group_name)), ), '#caption' => $caption, // We will add the table rows upon rendering, as doing it here means diff --git a/dkan/modules/contrib/field_hidden/CHANGELOG.txt b/dkan/modules/contrib/field_hidden/CHANGELOG.txt index 05d8b27d7..15cf28d4d 100644 --- a/dkan/modules/contrib/field_hidden/CHANGELOG.txt +++ b/dkan/modules/contrib/field_hidden/CHANGELOG.txt @@ -1,3 +1,8 @@ +field_hidden 7.x-1.x, 2018-12-22 +-------------------------------- +- create_function() deprecated (issue #3022047). +- Release 7.x-1.8. + field_hidden 7.x-1.x, 2015-01-24 -------------------------------- - Issue #1472050 by jacobfriis: Fixed Rules integration for numeric field types. diff --git a/dkan/modules/contrib/field_hidden/field_hidden.admin.inc b/dkan/modules/contrib/field_hidden/field_hidden.admin.inc index f5ab36230..c201d44f5 100644 --- a/dkan/modules/contrib/field_hidden/field_hidden.admin.inc +++ b/dkan/modules/contrib/field_hidden/field_hidden.admin.inc @@ -152,14 +152,11 @@ function _field_hidden_readme2html($readme_txt, $headline_tag = 'h5') { //echo $s_toc; exit; $s_toc = preg_replace_callback( '/ \* ([^\n]+)\n/', - create_function( - '$ms', - ' - return \' * \' . $ms[1] . \'\' . "\n"; - ' - ), + function($ms) { + return ' * ' . $ms[1] . '' . "\n"; + }, $s_toc ); $s_start = substr($s, 0, $pos_start); @@ -170,16 +167,13 @@ function _field_hidden_readme2html($readme_txt, $headline_tag = 'h5') { // Format headlines, and insert anchor. $s = preg_replace_callback( '/\n([^\n\-])([^\n]*[^\n\-])\n[\-]{2,}\n+/', - create_function( - '$ms', - ' - return \'

\' - . $ms[1] - . \'\' . $ms[2] . \'

\'; - ' - ), + function($ms) { + return '

' + . $ms[1] + . '' . $ms[2] . '

'; + }, $s ); $s = str_replace('headlineTag', $headline_tag, $s); @@ -197,23 +191,20 @@ function _field_hidden_readme2html($readme_txt, $headline_tag = 'h5') { // And http://your-drupal-site.tld/path should become /path $s = preg_replace_callback( '/(https?\:\/\/)(\S+)([\x20\t\n])/', - create_function( - '$ms', - ' - $protocol = $ms[1]; - $le = strlen($addr = $ms[2]); - $dot = \'\'; - if (!preg_match(\'/[\/a-zA-Z\d]/\', $addr{$le - 1})) { - $dot = $addr{$le - 1}; - $addr = substr($addr, 0, $le - 1); - } - if (strpos($addr, \'your-drupal-site.tld/\') === 0) { - $protocol = \'/\'; - $addr = str_replace(\'your-drupal-site.tld/\', \'\', $addr); - } - return \'\' . $addr . \'\' . $dot . $ms[3]; - ' - ), + function($ms) { + $protocol = $ms[1]; + $le = strlen($addr = $ms[2]); + $dot = ''; + if (!preg_match('/[\/a-zA-Z\d]/', $addr{$le - 1})) { + $dot = $addr{$le - 1}; + $addr = substr($addr, 0, $le - 1); + } + if (strpos($addr, 'your-drupal-site.tld/') === 0) { + $protocol = '/'; + $addr = str_replace('your-drupal-site.tld/', '', $addr); + } + return '' . $addr . '' . $dot . $ms[3]; + }, $s ); $ndls = array( diff --git a/dkan/modules/contrib/field_hidden/field_hidden.info b/dkan/modules/contrib/field_hidden/field_hidden.info index 4e9f2ddf5..9e28ee315 100644 --- a/dkan/modules/contrib/field_hidden/field_hidden.info +++ b/dkan/modules/contrib/field_hidden/field_hidden.info @@ -6,9 +6,8 @@ core = 7.x stylesheets[all][] = field_hidden.css configure = admin/config/fields/field_hidden -; Information added by Drupal.org packaging script on 2015-01-24 -version = "7.x-1.7" +; Information added by Drupal.org packaging script on 2018-12-22 +version = "7.x-1.8" core = "7.x" project = "field_hidden" -datestamp = "1422091682" - +datestamp = "1545466084" diff --git a/dkan/modules/contrib/field_hidden/field_hidden_migrate/field_hidden_migrate.info b/dkan/modules/contrib/field_hidden/field_hidden_migrate/field_hidden_migrate.info index e4f587ff1..7f67f0c2e 100644 --- a/dkan/modules/contrib/field_hidden/field_hidden_migrate/field_hidden_migrate.info +++ b/dkan/modules/contrib/field_hidden/field_hidden_migrate/field_hidden_migrate.info @@ -6,9 +6,8 @@ core = 7.x dependencies[] = migrate dependencies[] = field_hidden files[] = field_hidden.migrate.inc -; Information added by Drupal.org packaging script on 2015-01-24 -version = "7.x-1.7" +; Information added by Drupal.org packaging script on 2018-12-22 +version = "7.x-1.8" core = "7.x" project = "field_hidden" -datestamp = "1422091682" - +datestamp = "1545466084" diff --git a/dkan/modules/contrib/file_entity/README.txt b/dkan/modules/contrib/file_entity/README.txt new file mode 100644 index 000000000..fd481adc7 --- /dev/null +++ b/dkan/modules/contrib/file_entity/README.txt @@ -0,0 +1,27 @@ + +If you want to translate file entities the Drupal 7 entity_translation module needs to be enabled. + +Step 1) download and enable the file_entity module +Step 2) download and enable the entity_translation module +Step 3) enable the locale module and add your additional languages here: +/admin/config/regional/language , be sure to configure language detection as well, I prefer prefix, configure the prefix for all enabled languages. + +Step 4) +go to the /admin/config/regional/entity_translation +page and check off the file entity type underneith "Translatable entity types" +then save, after you have done this, translation options for each enabled language will show up if you also enable the field translation option in the structure of your file entity type for 'managed fields' , so for instance alt field and the title field would be a good thing to enable translatable field option for. + +Step 5) enable field translation on alt and title fields for file entity image type go to : /admin/structure/file-types/manage/image/fields + +Step 6) Once you do this, you will get translation options for the translatable fields that have translatable field option enabled. +so to if you've correctly configured things you'll be able to add the translation in this page here: /file/1/translate + +More translation usage with views: +Translate one file entity alt and title fields + +and then create a view of unformated fields with the article type +add the image field to the view +save the view + +hover your mouse over the image in the default language (most likely english /en) , the default language hover should show the image title in that language +switch language, hover the mouse over the image and this language (in my case French /fr ) the hover should display the image title in french. diff --git a/dkan/modules/contrib/file_entity/file_entity.admin.inc b/dkan/modules/contrib/file_entity/file_entity.admin.inc index ae1a1c73f..5837c760d 100644 --- a/dkan/modules/contrib/file_entity/file_entity.admin.inc +++ b/dkan/modules/contrib/file_entity/file_entity.admin.inc @@ -1174,5 +1174,12 @@ function file_entity_settings_form($form, &$form_state) { '#description' => t('Rename the newly uploaded file to the name of the original file. This action cannot be undone.'), ); + $form['file_entity_protect_repeated_render'] = array( + '#type' => 'checkbox', + '#title' => t('Protect against repeat rendering'), + '#default_value' => variable_get('file_entity_protect_repeated_render', TRUE), + '#description' => t('Avoid rendering the same entity more than 20 times. This can be a sign of an image entity getting caught in a recursive render, but it can also be triggered when the same image is rendered more than 20 times, e.g. in an long content list or data feed.'), + ); + return system_settings_form($form); } diff --git a/dkan/modules/contrib/file_entity/file_entity.api.php b/dkan/modules/contrib/file_entity/file_entity.api.php index 01c6e881d..882edeb99 100644 --- a/dkan/modules/contrib/file_entity/file_entity.api.php +++ b/dkan/modules/contrib/file_entity/file_entity.api.php @@ -405,3 +405,19 @@ function hook_file_metadata_info() { function hook_file_metadata_info_alter() { } + +/** + * Alters skip fields status. + * + * Use this to choose to skip or complete step 4 of the file upload process. + * + * @param bool &$skip_fields + * Set to TRUE to skip the form for editing extra file entity fields. + * @param array $form_state + * State array of the current upload form. + */ +function hook_file_entity_file_upload_skip_fields_alter(&$skip_fields, $form_state) { + if ($form_state['file']->type == 'video') { + $skip_fields = TRUE; + } +} diff --git a/dkan/modules/contrib/file_entity/file_entity.field.inc b/dkan/modules/contrib/file_entity/file_entity.field.inc index fa47a0379..8181469b5 100644 --- a/dkan/modules/contrib/file_entity/file_entity.field.inc +++ b/dkan/modules/contrib/file_entity/file_entity.field.inc @@ -346,29 +346,32 @@ function file_entity_field_formatter_view($entity_type, $entity, $field, $instan switch ($display['type']) { case 'file_rendered': foreach ($items as $delta => $item) { - // Protect ourselves from recursive rendering. - static $recursive_render_depth = array(); if (!empty($item)) { - $recursive_render_id = $entity_type . $field['field_name'] . $item['fid']; - if (isset($recursive_render_depth[$recursive_render_id])) { - $recursive_render_depth[$recursive_render_id]++; - } - else { - $recursive_render_depth[$recursive_render_id] = 1; - } + // The repeat-rendering protection may be disabled if necessary. + if (variable_get('file_entity_protect_repeated_render', TRUE)) { + // Protect ourselves from repeated rendering. + static $repeated_render_depth = array(); + $repeated_render_id = $entity_type . $field['field_name'] . $item['fid']; + if (isset($repeated_render_depth[$repeated_render_id])) { + $repeated_render_depth[$repeated_render_id]++; + } + else { + $repeated_render_depth[$repeated_render_id] = 1; + } - if ($recursive_render_depth[$recursive_render_id] > 20) { - watchdog( - 'file_entity', - 'Recursive rendering detected when rendering entity %entity_type: %entity_id, using the %field_name field. Aborting rendering.', - array( - '%entity_type' => 'file', - '%entity_id' => $item['fid'], - '%field_name' => $field['field_name'], - ), - WATCHDOG_ERROR - ); - return $element; + if ($repeated_render_depth[$repeated_render_id] > 20) { + watchdog( + 'file_entity', + 'Repeated rendering detected when rendering entity %entity_type: %entity_id, using the %field_name field. Aborting rendering.', + array( + '%entity_type' => 'file', + '%entity_id' => $item['fid'], + '%field_name' => $field['field_name'], + ), + WATCHDOG_ERROR + ); + return $element; + } } $file = file_load($item['fid']); diff --git a/dkan/modules/contrib/file_entity/file_entity.file.inc b/dkan/modules/contrib/file_entity/file_entity.file.inc index 683b1ca71..4949f4b69 100644 --- a/dkan/modules/contrib/file_entity/file_entity.file.inc +++ b/dkan/modules/contrib/file_entity/file_entity.file.inc @@ -116,14 +116,24 @@ function file_entity_file_update($file) { _file_entity_update_image_field_dimensions($file); } - // Flush image style derivatives whenever an image is updated. - image_path_flush($file->uri); + // Flush image style derivatives whenever an image is replaced. + if (empty($file->file_entity_skip_image_flush) && file_entity_has_file_changed($file)) { + image_path_flush($file->uri); + } } // Clear any related field caches. file_entity_invalidate_field_caches($file); } +/** + * Returns whether the file has changed + */ +function file_entity_has_file_changed($file) { + return empty($file->is_new) || empty($file->original) || $file->filesize != $file->original->filesize || $file->uri != $file->original->uri; +} + + /** * Implements hook_file_delete(). */ diff --git a/dkan/modules/contrib/file_entity/file_entity.info b/dkan/modules/contrib/file_entity/file_entity.info index 8076a4e20..924d0d157 100644 --- a/dkan/modules/contrib/file_entity/file_entity.info +++ b/dkan/modules/contrib/file_entity/file_entity.info @@ -32,8 +32,8 @@ configure = admin/config/media/file-settings ; We have to add a fake version so Git checkouts do not fail Media dependencies version = 7.x-2.x-dev -; Information added by Drupal.org packaging script on 2018-05-02 -version = "7.x-2.21" +; Information added by Drupal.org packaging script on 2018-11-09 +version = "7.x-2.25" core = "7.x" project = "file_entity" -datestamp = "1525293788" +datestamp = "1541794687" diff --git a/dkan/modules/contrib/file_entity/file_entity.install b/dkan/modules/contrib/file_entity/file_entity.install index 3fb3051fa..5f4d14cdd 100644 --- a/dkan/modules/contrib/file_entity/file_entity.install +++ b/dkan/modules/contrib/file_entity/file_entity.install @@ -167,7 +167,6 @@ function file_entity_schema_alter(&$schema) { $schema['file_managed']['indexes']['file_type'] = array('type'); } - /** * Implements hook_install(). */ @@ -246,6 +245,7 @@ function file_entity_uninstall() { variable_del('file_entity_file_upload_wizard_skip_file_type'); variable_del('file_entity_file_upload_wizard_skip_scheme'); variable_del('file_entity_file_upload_wizard_skip_fields'); + variable_del('file_entity_protect_repeated_render'); // Remove any items from the file type queue if still there. DrupalQueue::get('file_entity_type_determine')->deleteQueue(); @@ -315,7 +315,11 @@ function file_entity_update_7001() { if (!empty($display)) { db_merge('file_display') ->key(array( - 'name' => implode('__', array($file_type, $view_mode, $formatter_name)), + 'name' => implode('__', array( + $file_type, + $view_mode, + $formatter_name, + )), )) ->fields(array( 'status' => isset($display['status']) ? $display['status'] : 0, @@ -336,7 +340,8 @@ function file_entity_update_7001() { /** * Empty update function to trigger a theme registry rebuild. */ -function file_entity_update_7100() { } +function file_entity_update_7100() { +} /** * Update all files with empty types to use the first part of filemime. @@ -545,7 +550,6 @@ function file_entity_update_7203() { } } - /** * Add title and alt text to image file types. */ @@ -822,7 +826,11 @@ function file_entity_update_7207() { * Add expanded file type permissions to roles with existing file permissions. */ function file_entity_update_7208() { - foreach (array('edit own files', 'edit any files', 'delete own files', 'delete any files', 'download own files', 'download any files') as $old_permission) { + foreach (array( + 'edit own files', 'edit any files', + 'delete own files', 'delete any files', + 'download own files', 'download any files', + ) as $old_permission) { $roles = user_roles(FALSE, $old_permission); foreach ($roles as $rid => $name) { @@ -1004,10 +1012,11 @@ function file_entity_update_7212(&$sandbox) { 'value' => serialize((int) $result->{$key}), ); } - $sandbox['progress'] += count($results); $sandbox['current_fid'] = $result->fid; } + $sandbox['progress'] += count($results); + if (!empty($values)) { $query = db_insert('file_metadata'); $query->fields(array('fid', 'name', 'value')); diff --git a/dkan/modules/contrib/file_entity/file_entity.module b/dkan/modules/contrib/file_entity/file_entity.module index 6c8c4068f..2ca63b804 100644 --- a/dkan/modules/contrib/file_entity/file_entity.module +++ b/dkan/modules/contrib/file_entity/file_entity.module @@ -2334,6 +2334,11 @@ function pathauto_file_update_alias(stdClass $file, $op, array $options = array( return; } + // Skip processing if pathauto_entity module is enabled. + if (module_exists('pathauto_entity')) { + return; + } + module_load_include('inc', 'pathauto'); $uri = entity_uri('file', $file); pathauto_create_alias('file', $op, $uri['path'], array('file' => $file), $file->type, $options['language']); diff --git a/dkan/modules/contrib/file_entity/file_entity.pages.inc b/dkan/modules/contrib/file_entity/file_entity.pages.inc index 2e448380a..79f1d33d3 100644 --- a/dkan/modules/contrib/file_entity/file_entity.pages.inc +++ b/dkan/modules/contrib/file_entity/file_entity.pages.inc @@ -435,6 +435,8 @@ function file_entity_add_upload_submit($form, &$form_state) { // Save the file with blanks fields. $save = TRUE; } + // Allow other modules to choose to skip or complete step 4. + drupal_alter('file_entity_file_upload_skip_fields', $save, $form_state); } // Form id's can vary depending on how many other forms are displayed, so we @@ -750,7 +752,7 @@ function file_entity_edit($form, &$form_state, $file) { '#title' => t('Associated with'), '#maxlength' => 60, '#autocomplete_path' => 'user/autocomplete', - '#default_value' => !empty($file->uid) ? user_load($file->uid)->name : '', + '#default_value' => (!empty($file->uid) && $user = user_load($file->uid)) ? $user->name : '', '#weight' => -1, '#description' => t('Leave blank for %anonymous.', array('%anonymous' => variable_get('anonymous', t('Anonymous')))), ); @@ -834,35 +836,37 @@ function file_entity_edit_submit($form, &$form_state) { // Check if a replacement file has been uploaded. if (!empty($form_state['values']['replace_upload'])) { $replacement = $form_state['values']['replace_upload']; - // Move file from temp to permanent home. - if (pathinfo($replacement->uri, PATHINFO_EXTENSION) == pathinfo($file->uri, PATHINFO_EXTENSION)) { - if ($new_file_uri = file_unmanaged_copy($replacement->uri, $file->uri, FILE_EXISTS_REPLACE)) { - // Remove temporary file. - file_delete($replacement); - } - } else { - if (!empty($form_state['values']['replace_keep_original_filename']) - && $form_state['values']['replace_keep_original_filename']) { - $destination_uri = rtrim($file->uri, drupal_basename($file->uri)) . drupal_basename($file->uri); - } - else { - $destination_uri = rtrim($file->uri, drupal_basename($file->uri)) . drupal_basename($replacement->uri); + // Existing image metadata is stored in $file->height and $file->width. + // Loop through the replacement metadata and update existing values. + if (!empty($replacement->metadata)) { + foreach ($replacement->metadata as $metadata_key => $metadata_value) { + if (isset($file->{$metadata_key})) { + $file->{$metadata_key} = $metadata_value; + } } - $replace_mode = $destination_uri == $file->uri ? FILE_EXISTS_REPLACE : FILE_EXISTS_RENAME; - if ($new_file_uri = file_unmanaged_copy($replacement->uri, $destination_uri, $replace_mode)) { - // @todo Add watchdog() about replaced file here? + } + // Move file from temp to permanent home. + if (!empty($form_state['values']['replace_keep_original_filename']) + && $form_state['values']['replace_keep_original_filename']) { + $destination_uri = rtrim($file->uri, drupal_basename($file->uri)) . drupal_basename($file->uri); + } + else { + $destination_uri = rtrim($file->uri, drupal_basename($file->uri)) . drupal_basename($replacement->uri); + } + $replace_mode = $destination_uri == $file->uri ? FILE_EXISTS_REPLACE : FILE_EXISTS_RENAME; + if ($new_file_uri = file_unmanaged_copy($replacement->uri, $destination_uri, $replace_mode)) { + // @todo Add watchdog() about replaced file here? - // Remove temporary file. - file_delete($replacement); + // Remove temporary file. + file_delete($replacement); - // Update if the uri target has changed. - if ($new_file_uri != $file->uri) { - // Store the original file uri to delete if save is successful. - $orphaned_uri = $file->uri; + // Update if the uri target has changed. + if ($new_file_uri != $file->uri) { + // Store the original file uri to delete if save is successful. + $orphaned_uri = $file->uri; - // Update file entity uri. - $file->uri = $new_file_uri; - } + // Update file entity uri. + $file->uri = $new_file_uri; } } } diff --git a/dkan/modules/contrib/file_entity/file_entity.pathauto.inc b/dkan/modules/contrib/file_entity/file_entity.pathauto.inc index 98befe3ac..553f28a73 100644 --- a/dkan/modules/contrib/file_entity/file_entity.pathauto.inc +++ b/dkan/modules/contrib/file_entity/file_entity.pathauto.inc @@ -20,6 +20,11 @@ function file_entity_path_alias_types() { * Implements hook_pathauto(). */ function file_entity_pathauto($op) { + // Allow Pathauto Entity settings to override File Entity's pathauto. + if (module_exists('pathauto_entity')) { + return; + } + switch ($op) { case 'settings': $settings = array(); diff --git a/dkan/modules/contrib/file_entity/plugins/content_types/file_display.inc b/dkan/modules/contrib/file_entity/plugins/content_types/file_display.inc index 922b1e5b3..0da8dab5d 100644 --- a/dkan/modules/contrib/file_entity/plugins/content_types/file_display.inc +++ b/dkan/modules/contrib/file_entity/plugins/content_types/file_display.inc @@ -114,7 +114,12 @@ function file_entity_file_display_content_type_edit_form($form, &$form_state) { $defaults = !empty($formatter['default settings']) ? $formatter['default settings'] : array(); $settings = !empty($conf['displays'][$name]['settings']) ? $conf['displays'][$name]['settings'] : array(); $settings += $defaults; - $settings_form = $function($form, $form_state, $settings, $name, $file_type, $view_mode); + if (strpos($name, 'file_field_') === 0) { + $settings_form = $function($form, $form_state, $settings, $name, '', ''); + } + else { + $settings_form = $function($form, $form_state, $settings); + } if (!empty($settings_form)) { $form['displays']['settings'][$name] = array( '#type' => 'fieldset', diff --git a/dkan/modules/contrib/file_entity/tests/file_entity_test.info b/dkan/modules/contrib/file_entity/tests/file_entity_test.info index 924d0eb47..43a8c4135 100644 --- a/dkan/modules/contrib/file_entity/tests/file_entity_test.info +++ b/dkan/modules/contrib/file_entity/tests/file_entity_test.info @@ -5,8 +5,8 @@ core = 7.x dependencies[] = file_entity hidden = TRUE -; Information added by Drupal.org packaging script on 2018-05-02 -version = "7.x-2.21" +; Information added by Drupal.org packaging script on 2018-11-09 +version = "7.x-2.25" core = "7.x" project = "file_entity" -datestamp = "1525293788" +datestamp = "1541794687" diff --git a/dkan/modules/contrib/honeypot/honeypot.info b/dkan/modules/contrib/honeypot/honeypot.info index 99be9dc7f..6ce86d53c 100644 --- a/dkan/modules/contrib/honeypot/honeypot.info +++ b/dkan/modules/contrib/honeypot/honeypot.info @@ -6,9 +6,8 @@ package = "Spam control" files[] = honeypot.test -; Information added by Drupal.org packaging script on 2018-02-27 -version = "7.x-1.24" +; Information added by Drupal.org packaging script on 2018-08-09 +version = "7.x-1.25" core = "7.x" project = "honeypot" -datestamp = "1519706887" - +datestamp = "1533849190" diff --git a/dkan/modules/contrib/honeypot/honeypot.install b/dkan/modules/contrib/honeypot/honeypot.install index 150c68c35..c17b4cb45 100644 --- a/dkan/modules/contrib/honeypot/honeypot.install +++ b/dkan/modules/contrib/honeypot/honeypot.install @@ -69,7 +69,7 @@ function honeypot_uninstall() { } // Delete 'honeypot' directory from files directory. - file_unmanaged_delete_recursive(file_default_scheme() . '://honeypot'); + file_unmanaged_delete_recursive(honeypot_file_default_scheme() . '://honeypot'); } /** diff --git a/dkan/modules/contrib/honeypot/honeypot.module b/dkan/modules/contrib/honeypot/honeypot.module index 8623e3087..e8137966c 100644 --- a/dkan/modules/contrib/honeypot/honeypot.module +++ b/dkan/modules/contrib/honeypot/honeypot.module @@ -78,7 +78,7 @@ function honeypot_form_alter(&$form, &$form_state, $form_id) { if (variable_get('honeypot_protect_all_forms', 0) && !in_array($form_id, $unprotected_forms)) { // Don't protect system forms - only admins should have access, and system // forms may be programmatically submitted by drush and other modules. - if (strpos($form_id, 'system_') === FALSE && strpos($form_id, 'search_') === FALSE && strpos($form_id, 'views_exposed_form_') === FALSE) { + if (preg_match('/[^a-zA-Z]system_/', $form_id) === 0 && preg_match('/[^a-zA-Z]search_/', $form_id) === 0 && preg_match('/[^a-zA-Z]views_exposed_form_/', $form_id) === 0) { honeypot_add_form_protection($form, $form_state, array('honeypot', 'time_restriction')); } } @@ -503,7 +503,7 @@ function honeypot_log_failure($form_id, $type) { * The path to the honeypot.css file. */ function honeypot_get_css_file_path() { - return file_default_scheme() . '://honeypot/honeypot.css'; + return honeypot_file_default_scheme() . '://honeypot/honeypot.css'; } /** @@ -513,7 +513,7 @@ function honeypot_get_css_file_path() { * The honeypot element class name (e.g. 'url'). */ function honeypot_create_css($element_name) { - $path = file_default_scheme() . '://honeypot'; + $path = honeypot_file_default_scheme() . '://honeypot'; if (!file_prepare_directory($path, FILE_CREATE_DIRECTORY)) { drupal_set_message(t('Unable to create Honeypot CSS directory, %path. Check the permissions on your files directory.', array('%path' => file_uri_target($path))), 'error'); @@ -592,3 +592,15 @@ function honeypot_get_time_from_signed_timestamp($signed_timestamp) { return $honeypot_time; } + +/** + * Gets the default file stream for honeypot. + * + * @return + * 'public', 'private' or any other file scheme defined as the default. + * + * @see file_default_scheme() + */ +function honeypot_file_default_scheme() { + return variable_get('honeypot_file_default_scheme', file_default_scheme()); +} diff --git a/dkan/modules/contrib/honeypot/honeypot.test b/dkan/modules/contrib/honeypot/honeypot.test index c0c1fffe3..aab7f6e03 100644 --- a/dkan/modules/contrib/honeypot/honeypot.test +++ b/dkan/modules/contrib/honeypot/honeypot.test @@ -387,6 +387,39 @@ class HoneypotCssTestCase extends DrupalWebTestCase { // Make sure the Honeypot CSS file exists. $this->assertTrue(file_exists($honeypot_css)); } + + /** + * Test CSS file availability. + */ + public function testHoneypotCssAvailability() { + // Public CSS file can be consumed. + variable_set('file_default_scheme', 'public'); + if ($wrapper = file_stream_wrapper_get_instance_by_uri(honeypot_get_css_file_path())) { + $url = $wrapper->getExternalUrl(); + } + $this->drupalGet($url); + $this->assertResponse(200); + + + // Private CSS file can not be consumed. + variable_set('file_default_scheme', 'private'); + honeypot_cron(); + if ($wrapper = file_stream_wrapper_get_instance_by_uri(honeypot_get_css_file_path())) { + $url = $wrapper->getExternalUrl(); + } + $this->drupalGet($url); + $this->assertNoResponse(200); + + // Site default is private, but override honeypot's to public to consume. + variable_set('honeypot_file_default_scheme', 'public'); + honeypot_cron(); + if ($wrapper = file_stream_wrapper_get_instance_by_uri(honeypot_get_css_file_path())) { + $url = $wrapper->getExternalUrl(); + } + $this->drupalGet($url); + $this->assertResponse(200); + } + } /** @@ -444,4 +477,5 @@ class HoneypotTriggerTestCase extends DrupalWebTestCase { $this->drupalGet('node'); $this->assertText(t('has been banned'), 'User banned successfully.'); } + } diff --git a/dkan/modules/contrib/honeypot/tests/honeypot_test.info b/dkan/modules/contrib/honeypot/tests/honeypot_test.info index d8e102b97..89aa78bce 100644 --- a/dkan/modules/contrib/honeypot/tests/honeypot_test.info +++ b/dkan/modules/contrib/honeypot/tests/honeypot_test.info @@ -4,9 +4,8 @@ core = 7.x package = Testing hidden = true -; Information added by Drupal.org packaging script on 2018-02-27 -version = "7.x-1.24" +; Information added by Drupal.org packaging script on 2018-08-09 +version = "7.x-1.25" core = "7.x" project = "honeypot" -datestamp = "1519706887" - +datestamp = "1533849190" diff --git a/dkan/modules/contrib/libraries/CHANGELOG.txt b/dkan/modules/contrib/libraries/CHANGELOG.txt index 33fa5e8a5..bec304010 100644 --- a/dkan/modules/contrib/libraries/CHANGELOG.txt +++ b/dkan/modules/contrib/libraries/CHANGELOG.txt @@ -1,4 +1,21 @@ +Libraries 7.x-2.5, 2018-10-5 +----------------------------- +#2815965 by plach, Manav, tstoeckler, Proteo: Base theme is not loaded when checking for theme library info +#2999116 by Anghelu, joshbrown81: Finds no library after update + +Libraries 7.x-2.4, 2018-09-10 +----------------------------- +#2779591 by mark_fullmer, cglauren, improved PHP 7.x.x support. +#2699799 by flaviovs, hanoii: Support reading version from package.json. +#2816781 by joelstein: Add a 'access library reports' permission. +#2823735 by amanaplan, tstoeckler: Add admin_menu cache clear integration. +#2745763 by Albert Volkman, tstoeckler: Allow downloading all libraries at once. +#2310753 by tstoeckler: Avoid libraries_get_libraries() scanning the root. +#2341955 by sadashiv, tstoeckler: Clear library cache on library report. +#819610 by tstoeckler: Show variants and dependencies in the UI. +#2724925 by ron_s, tstoeckler: Separate installed from uninstalled libraries. + Libraries 7.x-2.3, 2016-05-12 ----------------------------- #1884246 by BR0kEN, tstoeckler et al: Allow downloading libraries via Drush. @@ -94,17 +111,11 @@ by sun: Fixed testbot breaks upon .info file without .module file. #719896 by tstoeckler, sun: Added starting point for hook_libraries_info(). -Libraries 7.x-1.x, xxxx-xx-xx ------------------------------ - Libraries 7.x-1.0, 2010-01-27 ----------------------------- #743522 by sun: Ported to D7. -Libraries 6.x-1.x, xxxx-xx-xx ------------------------------ - Libraries 6.x-1.0, 2010-01-27 ----------------------------- #1028744 by tstoeckler: Code clean-up. diff --git a/dkan/modules/contrib/libraries/css/libraries.admin.css b/dkan/modules/contrib/libraries/css/libraries.admin.css new file mode 100644 index 000000000..f344ff0cb --- /dev/null +++ b/dkan/modules/contrib/libraries/css/libraries.admin.css @@ -0,0 +1,3 @@ +.libraries-table { + margin-bottom: 2em; +} diff --git a/dkan/modules/contrib/libraries/libraries.admin.inc b/dkan/modules/contrib/libraries/libraries.admin.inc index 8d9e0fbdf..29ddec145 100644 --- a/dkan/modules/contrib/libraries/libraries.admin.inc +++ b/dkan/modules/contrib/libraries/libraries.admin.inc @@ -20,41 +20,112 @@ * The form array for the overview form. */ function libraries_admin_overview(array $form, array &$form_state) { - $header = array(t('Name'), t('Status'), t('Installed version'), t('Provider'), t('Links')); - $rows = array(); - - $libraries = libraries_detect(); - uasort($libraries, 'libraries_admin_sort_title'); - - foreach ($libraries as $machine_name => $library) { + // Only show variants for installed libraries. + $header_installed = array(t('Name'), t('Version'), t('Variants'), t('Dependencies'), t('Provider'), t('Links')); + // Only show status for libraries with an error. + $header_error = array(t('Name'), t('Status'), t('Version'), t('Dependencies'), t('Provider'), t('Links')); + // For unregistered libraries the only information we can show is the path. + $header_unregistered = array(t('Name'), t('Path')); + + $rows_installed = array(); + $rows_error = array(); + $rows_unregistered = array(); + + // Registered libraries: we prefer to use libraries_detect() since it provides + // library metadata. + $libraries_registered = libraries_detect(); + uasort($libraries_registered, 'libraries_admin_sort_title'); + + // Unregistered libraries: modules can depend on Libraries API without sharing + // metadata by using libraries_get_path(). Libraries can also be placed in the + // filesystem that are incorrectly installed, a wrong version, or a standalone + // not connected to any module. In these cases, libraries_get_libraries() + // provides a full library list. Libraries found by libraries_get_libraries(), + // but not identified by libraries_detect, are displayed in a separate table. + $libraries_unregistered = libraries_get_libraries(); + natcasesort($libraries_unregistered); + + foreach ($libraries_registered as $machine_name => $library) { $actions = array(); + $row = array(); if ($library['vendor url']) { - $actions[] = l('Homepage', $library['vendor url']); + $actions[] = l(t('Homepage'), $library['vendor url']); } if ($library['download url']) { - $actions[] = l('Download', $library['download url']); + $actions[] = l(t('Download'), $library['download url']); + } + + $row['data'][] = l($library['name'], 'admin/reports/libraries/' . $machine_name); + // Only show status for libraries with an error. See above. + if (!$library['installed']) { + $row['data'][] = drupal_ucfirst($library['error']); + } + $row['data'][] = isset($library['version']) ? $library['version'] : ''; + if ($library['installed']) { + $row['data'][] = implode(', ', array_keys($library['variants'])); + } + $row['data'][] = libraries_admin_get_dependencies($library); + $row['data'][] = libraries_admin_get_provider_with_type($library); + $row['data'][] = implode(' | ', $actions); + $row['class'] = $library['installed'] ? array('ok') : array('warning'); + + if ($library['installed']) { + $rows_installed[] = $row; } + else { + $rows_error[] = $row; + } + + // Filter registered libraries from unregistered libraries. + unset($libraries_unregistered[$library['machine name']]); + } + + // Build table of registered libraries with installed status. + $form['libraries']['installed'] = array( + '#theme' => 'libraries_table_with_title', + '#title' => t('Installed'), + '#header' => $header_installed, + '#rows' => $rows_installed, + '#description' => t('These libraries are registered and installed correctly.'), + '#empty' => t('There are currently no libraries that are registered and installed.'), + ); + + // Build table of registered libraries with error status. + $form['libraries']['error'] = array( + '#theme' => 'libraries_table_with_title', + '#title' => t('Uninstalled'), + '#header' => $header_error, + '#rows' => $rows_error, + '#description' => t('These libraries are registered but not installed. They may not need to be installed in case a module or theme provides optional integration with a library.'), + '#empty' => t('There are currently no libraries that are registered but not installed.'), + ); - $rows[] = array( + // Build table of unregistered libraries. + foreach ($libraries_unregistered as $name => $path) { + $rows_unregistered[] = array( 'data' => array( - l($library['name'], 'admin/reports/libraries/' . $machine_name), - ($library['installed'] ? t('OK') : drupal_ucfirst($library['error'])), - (isset($library['version']) ? $library['version'] : ''), - libraries_admin_get_provider_with_type($library), - implode(' | ', $actions), + $name, + $path, ), - 'class' => ($library['installed'] ? array('ok') : array('error')), ); } - - $form['libraries']['list'] = array( - '#theme' => 'table', - '#header' => $header, - '#rows' => $rows, - '#empty' => t('There are currently no libraries installed'), + $form['libraries']['unregistered'] = array( + '#theme' => 'libraries_table_with_title', + '#title' => t('Unregistered'), + '#header' => $header_unregistered, + '#rows' => $rows_unregistered, + '#description' => t('These libraries were found in the filesystem but there is no metadata about them.'), + // Do not show the table at all, if there are no unregistered libraries. + '#access' => (bool) $libraries_unregistered, ); + // Clear the cached library information so that the library can be loaded if + // it was just downloaded. Because these instructions use libraries_detect() + // directly, they will never use the cached information, but this avoids the + // overview showing a library as installed but it not being loadable. + libraries_cache_clear(); + return $form; } @@ -99,11 +170,11 @@ function libraries_admin_library_status_form(array $form, array &$form_state, $l break; case 'missing dependency': - $form['instructions']['instruction']['#markup'] = t('There a missing dependency in your configuration that prevent this library to work properly.') . '
'; + $form['instructions']['instruction']['#markup'] = t('There is a missing dependency in your configuration that prevents this library from working properly.') . '
'; break; case 'incompatible dependency': - $form['instructions']['instruction']['#markup'] = t('There an incompatible dependency in your configuration that prevent this library to work properly.') . '
'; + $form['instructions']['instruction']['#markup'] = t('There is an incompatible dependency in your configuration that prevents this library from working properly.') . '
'; break; } } @@ -483,6 +554,28 @@ function libraries_admin_sort_title(array $a, array $b) { return strnatcasecmp($a['name'], $b['name']); } +/** + * Returns the library's dependencies, if any. + * + * @param array $library + * A library information array. + * + * @return string + * The dependencies. + */ +function libraries_admin_get_dependencies($library) { + $dependencies = array(); + foreach ($library['dependencies'] as $dependency_name) { + if ($dependency = libraries_info($dependency_name)) { + $dependencies[] = $dependency['name']; + } + else { + $dependencies[] = $dependency_name; + } + } + return implode(', ', $dependencies); +} + /** * Returns the library's provider. * diff --git a/dkan/modules/contrib/libraries/libraries.api.php b/dkan/modules/contrib/libraries/libraries.api.php index 71e90e0c2..fe0c24184 100644 --- a/dkan/modules/contrib/libraries/libraries.api.php +++ b/dkan/modules/contrib/libraries/libraries.api.php @@ -47,6 +47,8 @@ * Unless 'version' is declared or libraries_get_version() is being used as * a version callback, 'version callback' must be declared. In the latter * case, however, 'version arguments' must be declared in the specified way. + * For libraries that provide a package.json file, use + * 'libraries_get_package_json_version' as the version callback. * - version arguments: (optional) A list of arguments to pass to the version * callback. Version arguments can be declared either as an associative * array whose keys are the argument names or as an indexed array without diff --git a/dkan/modules/contrib/libraries/libraries.drush.inc b/dkan/modules/contrib/libraries/libraries.drush.inc index 325871355..550b7af52 100644 --- a/dkan/modules/contrib/libraries/libraries.drush.inc +++ b/dkan/modules/contrib/libraries/libraries.drush.inc @@ -23,7 +23,9 @@ function libraries_drush_command() { 'arguments' => array( 'libraries' => 'A comma delimited list of library machine names.', ), - 'required-arguments' => TRUE, + 'options' => array( + 'all' => 'Download all registered libraries.', + ), ); return $items; @@ -42,10 +44,7 @@ function libraries_drush_cache_clear(array &$types) { * Clears the library cache. */ function libraries_drush_invalidate_cache() { - // @see drupal_flush_all_caches() - foreach (libraries_flush_caches() as $table) { - cache_clear_all('*', $table, TRUE); - } + libraries_cache_clear(); } /** @@ -109,28 +108,61 @@ function drush_libraries_list() { function drush_libraries_download() { drush_command_include('pm-download'); - $libraries = libraries_info(); + $all_libraries = libraries_detect(); - // @todo Consider supporting downloading all downloadable libraries. - // @todo Consider offering a selection if no library is specified. - foreach (pm_parse_arguments(func_get_args(), FALSE) as $machine_name) { - if (!isset($libraries[$machine_name])) { - $message = dt("The !library library is not registered with Libraries API.\n", array('!library' => $machine_name)); - $message .= dt("Provide an info file for it or implement hook_libraries_info().\n"); - $message .= dt("See hook_libraries_info() for more information.\n"); - drush_set_error('DRUSH_LIBRARY_UKNOWN', $message); - continue; + // Prepare a list of names of downloadable libraries. + $downloadable_names = array(); + foreach ($all_libraries as $machine_name => $library) { + // Skip libraries that are already installed. + // @todo Allow (optionally) re-downloading installing libraries. + if (!empty($library['download file url']) && !$library['installed']) { + $downloadable_names[] = $machine_name; } - $library = $libraries[$machine_name]; + } - if (empty($library['download file url'])) { - $message = dt("The !library library cannot be downloaded.\n", array('!library' => $machine_name)); - $message .= dt("Libraries need to specify a download file URL to support being downloaded via Drush.\n"); - $message .= dt("See hook_libraries_info() for more information.\n"); - drush_set_error('DRUSH_LIBRARY_NOT_DOWNLOADABLE', $message); - continue; + // Gather a list of libraries to download. If '--all' was specified, that + // takes precedence over any other arguments. Otherwise and if no arguments + // are specified, we present a choice of all downloadable libraries. + if (drush_get_option('all', FALSE) && $downloadable_names) { + $machine_names = $downloadable_names; + } + elseif (pm_parse_arguments(func_get_args(), FALSE)) { + $machine_names = array(); + foreach (pm_parse_arguments(func_get_args(), FALSE) as $machine_name) { + // If there was an error with with one of the libraries, continue to try + // to install any remaining libraries. + if (!isset($all_libraries[$machine_name])) { + $message = dt("The !library library is not registered with Libraries API.\n", array('!library' => $machine_name)); + $message .= dt("Provide an info file for it or implement hook_libraries_info().\n"); + $message .= dt("See hook_libraries_info() for more information.\n"); + drush_set_error('DRUSH_LIBRARY_UKNOWN', $message); + continue; + } + if (empty($all_libraries[$machine_name]['download file url'])) { + $message = dt("The !library library cannot be downloaded.\n", array('!library' => $machine_name)); + $message .= dt("Libraries need to specify a download file URL to support being downloaded via Drush.\n"); + $message .= dt("See hook_libraries_info() for more information.\n"); + drush_set_error('DRUSH_LIBRARY_NOT_DOWNLOADABLE', $message); + continue; + } + $machine_names[] = $machine_name; + } + } + elseif ($downloadable_names) { + $machine_names = drush_choice_multiple(drupal_map_assoc($downloadable_names), FALSE, 'Select which libraries to download.'); + // If the operation was cancelled by the user, or if no libraries were + // selected, bail out without any further error message. + if (!$machine_names) { + return; } - $download_url = $library['download file url']; + } + else { + drush_log(dt('There are no registered, uninstalled libraries that can be downloaded.'), 'warning'); + return; + } + + foreach ($machine_names as $machine_name) { + $download_url = $all_libraries[$machine_name]['download file url']; drush_log(dt('Downloading library !name ...', array('!name' => $machine_name))); @@ -205,7 +237,7 @@ function drush_libraries_download() { drush_delete_dir($install_location, TRUE); } else { - drush_log(dt("Skip installation of !project to !dest.", array('!project' => $library['machine name'], '!dest' => $install_location)), 'warning'); + drush_log(dt("Skip installation of !project to !dest.", array('!project' => $machine_name, '!dest' => $install_location)), 'warning'); continue; } } diff --git a/dkan/modules/contrib/libraries/libraries.info b/dkan/modules/contrib/libraries/libraries.info index dcd2d486c..e721cb4cc 100644 --- a/dkan/modules/contrib/libraries/libraries.info +++ b/dkan/modules/contrib/libraries/libraries.info @@ -8,9 +8,8 @@ files[] = tests/LibrariesLoadWebTest.test files[] = tests/LibrariesUnitTest.test files[] = tests/LibrariesWebTestBase.test -; Information added by Drupal.org packaging script on 2016-05-12 -version = "7.x-2.3" +; Information added by Drupal.org packaging script on 2018-10-05 +version = "7.x-2.5" core = "7.x" project = "libraries" -datestamp = "1463077450" - +datestamp = "1538770685" diff --git a/dkan/modules/contrib/libraries/libraries.install b/dkan/modules/contrib/libraries/libraries.install index ebc4087ba..e8c795ff3 100644 --- a/dkan/modules/contrib/libraries/libraries.install +++ b/dkan/modules/contrib/libraries/libraries.install @@ -34,3 +34,13 @@ function libraries_update_7201() { // during the 7.x-2.x cycle. registry_rebuild(); } + +/** + * Grant the "View library reports" permission to roles with the "View site reports" permission. + */ +function libraries_update_7202() { + $rids = array_keys(user_roles(FALSE, 'access site reports')); + foreach ($rids as $rid) { + _update_7000_user_role_grant_permissions($rid, array('access library reports'), 'libraries'); + } +} diff --git a/dkan/modules/contrib/libraries/libraries.module b/dkan/modules/contrib/libraries/libraries.module index 48275250f..480289cdb 100644 --- a/dkan/modules/contrib/libraries/libraries.module +++ b/dkan/modules/contrib/libraries/libraries.module @@ -25,6 +25,29 @@ function libraries_flush_caches() { } } +/** + * Implements hook_admin_menu_cache_info(). + */ +function libraries_admin_menu_cache_info() { + $caches['libraries'] = array( + 'title' => t('Libraries'), + 'callback' => 'libraries_cache_clear', + ); + return $caches; +} + +/** + * Clears the cached library information. + */ +function libraries_cache_clear() { + foreach (libraries_flush_caches() as $bin) { + // Using the wildcard argument leads to DrupalDatabaseCache::clear() + // truncating the libraries cache table which is more performant that + // deleting the rows. + cache_clear_all('*', $bin, TRUE); + } +} + /** * Gets the path of a library. * @@ -56,6 +79,52 @@ function libraries_get_path($name, $base_path = FALSE) { return $path; } +/** + * Returns all enabled themes. + * + * Themes are sorted so that base themes always precede their child themes. + * + * @return array + * An associative array of theme objects keyed by theme name. + */ +function libraries_get_enabled_themes() { + $themes = array(); + foreach (list_themes() as $name => $theme) { + if ($theme->status) { + $themes[$name] = $theme; + } + } + + return libraries_sort_themes($themes); +} + +/** + * Sort a themes array. + * + * @param array $themes + * Array of themes as objects, keyed by theme name. + * @param string $base + * A base theme (internal use only). + * + * @return array + * A similar array to $themes, but sorted in such a way that subthemes are + * always located after its base theme. + */ +function libraries_sort_themes($themes, $base = '') { + $output = array(); + foreach ($themes as $name => $theme) { + if (!isset($theme->base_theme) || $theme->base_theme == $base) { + $output[$name] = $theme; + unset($themes[$name]); + $subthemes = libraries_sort_themes($themes, $name); + foreach ($subthemes as $sub_name => $subtheme) { + $output[$sub_name] = $subtheme; + } + } + } + return $output; +} + /** * Returns an array of library directories. * @@ -77,6 +146,13 @@ function libraries_get_libraries() { $profile = drupal_get_path('profile', drupal_get_profile()); $config = conf_path(); + // $config and $profile should never be empty in a proper Drupal setup. + // However, we should never search into the root filesystem under any + // circumstances, so just bail out in that case. + if (!$profile && !$config) { + return array(); + } + // 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. @@ -358,10 +434,12 @@ function &libraries_info($name = NULL) { } } - // Gather information from hook_libraries_info() in enabled themes. + // Gather information from hook_libraries_info() in enabled themes. Themes + // are sorted to ensure that a base theme's template.php is included before + // its children's ones. $themes = array(); - foreach (list_themes() as $theme_name => $theme_info) { - if ($theme_info->status && file_exists(drupal_get_path('theme', $theme_name) . '/template.php')) { + foreach (libraries_get_enabled_themes() as $theme_name => $theme_info) { + if (file_exists(drupal_get_path('theme', $theme_name) . '/template.php')) { // Collect a list of viable themes for re-use when calling the alter // hook. $themes[] = $theme_name; @@ -546,7 +624,7 @@ function libraries_detect($name = NULL) { $library['version'] = call_user_func_array($library['version callback'], array_merge(array($library), $library['version arguments'])); } else { - $library['version'] = call_user_func($library['version callback'], $library, $library['version arguments']); + $library['version'] = call_user_func_array($library['version callback'], array(&$library, $library['version arguments'])); } if (empty($library['version'])) { $library['error'] = 'not detected'; @@ -887,16 +965,56 @@ function libraries_get_version($library, $options) { fclose($file); } +/** + * Gets the version information from a library's package.json file. + * + * @param $library + * An associative array containing all information about the library. + * @param $options + * This callback expects no option. + * @return + * A string containing the version of the library. + * + * @see libraries_get_path() + */ +function libraries_get_package_json_version($library, $options) { + $file = DRUPAL_ROOT . '/' . $library['library path'] . '/package.json'; + if (!file_exists($file)) { + return; + } + + $content = file_get_contents($file); + if (!$content) { + return; + } + + $data = drupal_json_decode($content); + if (isset($data['version'])) { + return $data['version']; + } +} + /** * Implements hook_help(). */ function libraries_help($path, $arg) { switch ($path) { case 'admin/reports/libraries': - return t('Click on a library for a status report or detailed installation instructions in case the library is not installed correctly.'); + return t('Click on a library for a status report or detailed installation instructions.'); } } +/** + * Implements hook_permission(). + */ +function libraries_permission() { + return array( + 'access library reports' => array( + 'title' => t('View library reports'), + ), + ); +} + /** * Implements hook_menu(). */ @@ -907,7 +1025,7 @@ function libraries_menu() { 'description' => 'An overview of libraries installed on this site.', 'page callback' => 'drupal_get_form', 'page arguments' => array('libraries_admin_overview'), - 'access arguments' => array('access site reports'), + 'access arguments' => array('access library reports'), 'file' => 'libraries.admin.inc' ); $items['admin/reports/libraries/%libraries_ui'] = array( @@ -915,7 +1033,7 @@ function libraries_menu() { 'description' => 'Status overview for a single library', 'page callback' => 'drupal_get_form', 'page arguments' => array('libraries_admin_library_status_form', 3), - 'access arguments' => array('access site reports'), + 'access arguments' => array('access library reports'), 'file' => 'libraries.admin.inc' ); return $items; @@ -944,3 +1062,19 @@ function libraries_menu() { function libraries_ui_load($name) { return libraries_detect($name); } + +/** + * Implements hook_theme(). + */ +function libraries_theme($existing, $type, $theme, $path) { + // Because we extend the 'table' theme function, fetch the respective + // variables dynamically. + $common_theme = drupal_common_theme(); + $variables = $common_theme['table']['variables'] + array('title' => '', 'description' => ''); + return array( + 'libraries_table_with_title' => array( + 'variables' => $variables, + 'file' => 'libraries.theme.inc', + ), + ); +} diff --git a/dkan/modules/contrib/libraries/libraries.theme.inc b/dkan/modules/contrib/libraries/libraries.theme.inc new file mode 100644 index 000000000..ff3ed7199 --- /dev/null +++ b/dkan/modules/contrib/libraries/libraries.theme.inc @@ -0,0 +1,36 @@ + array()); + $variables['attributes']['class'][] = 'libraries-table'; +} + +/** + * Returns HTML for a table with a title. + * + * @param array $variables + * An array theme variables. + * + * @return string + * The HTML output for this table with a title. + */ +function theme_libraries_table_with_title(array $variables) { + $output = ''; + $output .= '

' . $variables['title'] . '

'; + $output .= '
' . $variables['description'] . '
'; + $output .= theme_table($variables); + return $output; +} diff --git a/dkan/modules/contrib/libraries/tests/LibrariesAdminWebTest.test b/dkan/modules/contrib/libraries/tests/LibrariesAdminWebTest.test index a4d2188be..87c9de0ef 100644 --- a/dkan/modules/contrib/libraries/tests/LibrariesAdminWebTest.test +++ b/dkan/modules/contrib/libraries/tests/LibrariesAdminWebTest.test @@ -40,8 +40,20 @@ class LibrariesAdminWebTest extends LibrariesWebTestBase { * Tests the libraries report at /admin/reports/libraries. */ public function testLibrariesReportOverview() { - $this->getWithPermissions(array('access site reports'), 'admin/reports/libraries'); - $this->assertRaw('Libraries'); + $this->getWithPermissions(array('access library reports'), 'admin/reports/libraries'); + // Assert the page title and table titles show up. + $this->assertText('Libraries'); + $this->assertRaw('

Installed

'); + $this->assertRaw('

Uninstalled

'); + + // Make sure the table headings show up. + $this->assertText('Name'); + $this->assertText('Status'); + $this->assertText('Version'); + $this->assertText('Variants'); + $this->assertText('Dependencies'); + $this->assertText('Provider'); + $this->assertText('Links'); // Make sure that all the libraries are listed. $libraries = libraries_info(); @@ -51,8 +63,7 @@ class LibrariesAdminWebTest extends LibrariesWebTestBase { $this->assertLinkByHref('admin/reports/libraries/' . $library['machine name']); } - // Make sure that all possible statuses are displayed. - $this->assertText('OK'); + // Make sure that all possible error statuses are displayed. $this->assertText('Not found'); $this->assertText('Not detected'); $this->assertText('Not supported'); @@ -73,7 +84,7 @@ class LibrariesAdminWebTest extends LibrariesWebTestBase { * Tests the libraries report for an installed library. */ public function testLibrariesReportInstalled() { - $this->getWithPermissions(array('access site reports'), 'admin/reports/libraries/example_files'); + $this->getWithPermissions(array('access library reports'), 'admin/reports/libraries/example_files'); $this->assertRaw('Status report for library Example files'); $this->assertRaw('The Example files library is installed correctly.'); // Check that the information in the status report is displayed. @@ -88,7 +99,7 @@ class LibrariesAdminWebTest extends LibrariesWebTestBase { * Tests the libraries report for a missing library. */ public function testLibrariesReportMissing() { - $this->getWithPermissions(array('access site reports'), 'admin/reports/libraries/example_missing'); + $this->getWithPermissions(array('access library reports'), 'admin/reports/libraries/example_missing'); $this->assertRaw('Status report for library Example missing'); $this->assertRaw('The Example missing library could not be found.'); // Check that the download link is being displayed. @@ -100,7 +111,7 @@ class LibrariesAdminWebTest extends LibrariesWebTestBase { * Tests the libraries report for a missing library. */ public function testLibrariesReportNotDetected() { - $this->getWithPermissions(array('access site reports'), 'admin/reports/libraries/example_undetected_version'); + $this->getWithPermissions(array('access library reports'), 'admin/reports/libraries/example_undetected_version'); $this->assertRaw('Status report for library Example undetected version'); $this->assertRaw('The version of the Example undetected version library could not be detected.'); } @@ -109,7 +120,7 @@ class LibrariesAdminWebTest extends LibrariesWebTestBase { * Tests the libraries report for a missing library. */ public function testLibrariesReportNotSupported() { - $this->getWithPermissions(array('access site reports'), 'admin/reports/libraries/example_unsupported_version'); + $this->getWithPermissions(array('access library reports'), 'admin/reports/libraries/example_unsupported_version'); $this->assertRaw('Status report for library Example unsupported version'); $this->assertRaw('The installed version 1 of the Example unsupported version library is not supported.'); // Check that the download link is being displayed. diff --git a/dkan/modules/contrib/libraries/tests/libraries/example_info_file.libraries.info b/dkan/modules/contrib/libraries/tests/libraries/example_info_file.libraries.info index 111527314..83ba8a835 100644 --- a/dkan/modules/contrib/libraries/tests/libraries/example_info_file.libraries.info +++ b/dkan/modules/contrib/libraries/tests/libraries/example_info_file.libraries.info @@ -2,9 +2,8 @@ name = Example info file -; Information added by Drupal.org packaging script on 2016-05-12 -version = "7.x-2.3" +; Information added by Drupal.org packaging script on 2018-10-05 +version = "7.x-2.5" core = "7.x" project = "libraries" -datestamp = "1463077450" - +datestamp = "1538770685" diff --git a/dkan/modules/contrib/libraries/tests/modules/libraries_test_module/libraries_test_module.info b/dkan/modules/contrib/libraries/tests/modules/libraries_test_module/libraries_test_module.info index 7ad319a2f..756d6a8f4 100644 --- a/dkan/modules/contrib/libraries/tests/modules/libraries_test_module/libraries_test_module.info +++ b/dkan/modules/contrib/libraries/tests/modules/libraries_test_module/libraries_test_module.info @@ -5,9 +5,8 @@ package = Testing dependencies[] = libraries hidden = TRUE -; Information added by Drupal.org packaging script on 2016-05-12 -version = "7.x-2.3" +; Information added by Drupal.org packaging script on 2018-10-05 +version = "7.x-2.5" core = "7.x" project = "libraries" -datestamp = "1463077450" - +datestamp = "1538770685" diff --git a/dkan/modules/contrib/libraries/tests/themes/libraries_test_theme/libraries_test_theme.info b/dkan/modules/contrib/libraries/tests/themes/libraries_test_theme/libraries_test_theme.info index 255d1040e..194954e70 100644 --- a/dkan/modules/contrib/libraries/tests/themes/libraries_test_theme/libraries_test_theme.info +++ b/dkan/modules/contrib/libraries/tests/themes/libraries_test_theme/libraries_test_theme.info @@ -3,9 +3,8 @@ description = Tests that themes can provide and alter library information. core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2016-05-12 -version = "7.x-2.3" +; Information added by Drupal.org packaging script on 2018-10-05 +version = "7.x-2.5" core = "7.x" project = "libraries" -datestamp = "1463077450" - +datestamp = "1538770685" diff --git a/dkan/modules/contrib/media/includes/media.fields.inc b/dkan/modules/contrib/media/includes/media.fields.inc index 6240b5c90..ce750afe1 100644 --- a/dkan/modules/contrib/media/includes/media.fields.inc +++ b/dkan/modules/contrib/media/includes/media.fields.inc @@ -127,9 +127,11 @@ function media_field_widget_form(&$form, &$form_state, $field, $instance, $langc // on the elements for further usage in media_element_process(). if (module_invoke('entity_translation', 'enabled', $element['#entity_type'], $element['#entity'])) { $translation_handler = entity_translation_get_handler($element['#entity_type'], $element['#entity']); - $element['#media_parent_entity_form_langcode'] = $translation_handler->getActiveLanguage(); - if ($source_langcode = $translation_handler->getSourceLanguage()) { - $element['#media_parent_entity_source_langcode'] = $source_langcode; + if ($translation_handler) { + $element['#media_parent_entity_form_langcode'] = $translation_handler->getActiveLanguage(); + if ($source_langcode = $translation_handler->getSourceLanguage()) { + $element['#media_parent_entity_source_langcode'] = $source_langcode; + } } } elseif (module_exists('translation') && $element['#entity_type'] == 'node' && translation_supported_type($element['#entity']->type)) { diff --git a/dkan/modules/contrib/media/media.info b/dkan/modules/contrib/media/media.info index ba4369589..211f4b7df 100644 --- a/dkan/modules/contrib/media/media.info +++ b/dkan/modules/contrib/media/media.info @@ -24,8 +24,8 @@ configure = admin/config/media/browser ; We have to add a fake version so Git checkouts do not fail Media dependencies version = 7.x-2.x-dev -; Information added by Drupal.org packaging script on 2018-04-25 -version = "7.x-2.19" +; Information added by Drupal.org packaging script on 2018-10-24 +version = "7.x-2.21" core = "7.x" project = "media" -datestamp = "1524677887" +datestamp = "1540363086" diff --git a/dkan/modules/contrib/media/media.module b/dkan/modules/contrib/media/media.module index cccf8797c..1315dff9e 100644 --- a/dkan/modules/contrib/media/media.module +++ b/dkan/modules/contrib/media/media.module @@ -668,12 +668,20 @@ function media_parse_to_file($url, $params = array()) { } /** - * Utility function to recursively run check_plain on an array. + * Custom implementation of array_walk_recursive() that works around a crash + * some users have been experiencing with that function in PHP 7. * - * @todo There is probably something in core I am not aware of that does this. + * @see https://www.drupal.org/project/media/issues/2998097 */ -function media_recursive_check_plain(&$value, $key) { - $value = check_plain($value); +function media_array_walk_recursive(&$array) { + foreach ($array as $key => $value) { + if (is_array($array[$key])) { + media_array_walk_recursive($array[$key]); + } + else { + $array[$key] = check_plain($array[$key]); + } + } } /** @@ -1193,6 +1201,9 @@ function media_file_displays_alter(&$displays, $file, $view_mode) { if (!empty($title)) { $file->title = decode_entities(token_replace($title, array('file' => $file), $replace_options)); } + + // Reduce memory footprint and response size in media browser. + $file->file_contents = ''; } /** @@ -1260,7 +1271,7 @@ function media_set_browser_params() { } } - array_walk_recursive($params, 'media_recursive_check_plain'); + media_array_walk_recursive($params); // Provide some default parameters. $params += array( diff --git a/dkan/modules/contrib/media/media.views.inc b/dkan/modules/contrib/media/media.views.inc index 5fd2c02f2..bf22d4919 100644 --- a/dkan/modules/contrib/media/media.views.inc +++ b/dkan/modules/contrib/media/media.views.inc @@ -127,7 +127,7 @@ function template_preprocess_media_views_view_media_browser(&$vars) { $vars['wrapper_suffix'] = ''; $vars['list_type_prefix'] = '<' . $handler->options['type'] . ' id="media-browser-library-list" class="' . implode(' ', $class) . '">'; $vars['list_type_suffix'] = 'options['type'] . '>'; - $vars['aria_role'] = $params['multiselect'] ? 'checkbox' : 'radio'; + $vars['aria_role'] = (isset($params['multiselect']) && $params['multiselect']) ? 'checkbox' : 'radio'; // Run theming variables through a standard Views preprocess function. template_preprocess_views_view_unformatted($vars); diff --git a/dkan/modules/contrib/media/modules/media_bulk_upload/includes/media_bulk_upload.pages.inc b/dkan/modules/contrib/media/modules/media_bulk_upload/includes/media_bulk_upload.pages.inc index 41287ebb0..f595dc66b 100644 --- a/dkan/modules/contrib/media/modules/media_bulk_upload/includes/media_bulk_upload.pages.inc +++ b/dkan/modules/contrib/media/modules/media_bulk_upload/includes/media_bulk_upload.pages.inc @@ -38,6 +38,7 @@ function media_bulk_upload_file_page_edit_multiple($files) { // Remove the 'replace file' functionality. $form['multiform'][$key]['replace_upload']['#access'] = FALSE; + $form['multiform'][$key]['replace_keep_original_filename']['#access'] = FALSE; // Remove any actions. $form['multiform'][$key]['actions']['#access'] = FALSE; diff --git a/dkan/modules/contrib/media/modules/media_bulk_upload/media_bulk_upload.info b/dkan/modules/contrib/media/modules/media_bulk_upload/media_bulk_upload.info index 95ab5467f..fb18da268 100644 --- a/dkan/modules/contrib/media/modules/media_bulk_upload/media_bulk_upload.info +++ b/dkan/modules/contrib/media/modules/media_bulk_upload/media_bulk_upload.info @@ -15,8 +15,8 @@ test_dependencies[] = plupload files[] = includes/MediaBrowserBulkUpload.inc files[] = tests/media_bulk_upload.test -; Information added by Drupal.org packaging script on 2018-04-25 -version = "7.x-2.19" +; Information added by Drupal.org packaging script on 2018-10-24 +version = "7.x-2.21" core = "7.x" project = "media" -datestamp = "1524677887" +datestamp = "1540363086" diff --git a/dkan/modules/contrib/media/modules/media_internet/media_internet.info b/dkan/modules/contrib/media/modules/media_internet/media_internet.info index a27a34c3c..d328c766a 100644 --- a/dkan/modules/contrib/media/modules/media_internet/media_internet.info +++ b/dkan/modules/contrib/media/modules/media_internet/media_internet.info @@ -12,8 +12,8 @@ files[] = includes/MediaInternetNoHandlerException.inc files[] = includes/MediaInternetValidationException.inc files[] = tests/media_internet.test -; Information added by Drupal.org packaging script on 2018-04-25 -version = "7.x-2.19" +; Information added by Drupal.org packaging script on 2018-10-24 +version = "7.x-2.21" core = "7.x" project = "media" -datestamp = "1524677887" +datestamp = "1540363086" diff --git a/dkan/modules/contrib/media/modules/media_internet/tests/media_internet_test.info b/dkan/modules/contrib/media/modules/media_internet/tests/media_internet_test.info index b8e3a6bee..364ac0f58 100644 --- a/dkan/modules/contrib/media/modules/media_internet/tests/media_internet_test.info +++ b/dkan/modules/contrib/media/modules/media_internet/tests/media_internet_test.info @@ -7,8 +7,8 @@ hidden = TRUE files[] = includes/MediaInternetTestStreamWrapper.inc files[] = includes/MediaInternetTestHandler.inc -; Information added by Drupal.org packaging script on 2018-04-25 -version = "7.x-2.19" +; Information added by Drupal.org packaging script on 2018-10-24 +version = "7.x-2.21" core = "7.x" project = "media" -datestamp = "1524677887" +datestamp = "1540363086" diff --git a/dkan/modules/contrib/media/modules/media_migrate_file_types/media_migrate_file_types.info b/dkan/modules/contrib/media/modules/media_migrate_file_types/media_migrate_file_types.info index 4d836facc..0c91735dd 100644 --- a/dkan/modules/contrib/media/modules/media_migrate_file_types/media_migrate_file_types.info +++ b/dkan/modules/contrib/media/modules/media_migrate_file_types/media_migrate_file_types.info @@ -8,8 +8,8 @@ dependencies[] = media configure = admin/structure/file-types/upgrade -; Information added by Drupal.org packaging script on 2018-04-25 -version = "7.x-2.19" +; Information added by Drupal.org packaging script on 2018-10-24 +version = "7.x-2.21" core = "7.x" project = "media" -datestamp = "1524677887" +datestamp = "1540363086" diff --git a/dkan/modules/contrib/media/modules/media_wysiwyg/includes/media_wysiwyg.filter.inc b/dkan/modules/contrib/media/modules/media_wysiwyg/includes/media_wysiwyg.filter.inc index a307c2f28..79e3ba665 100644 --- a/dkan/modules/contrib/media/modules/media_wysiwyg/includes/media_wysiwyg.filter.inc +++ b/dkan/modules/contrib/media/modules/media_wysiwyg/includes/media_wysiwyg.filter.inc @@ -5,7 +5,7 @@ * Functions related to the WYSIWYG editor and the media input filter. */ -define('MEDIA_WYSIWYG_TOKEN_REGEX', '/\[\[\{.*?"type":"media".+?\}\]\]/s'); +define('MEDIA_WYSIWYG_TOKEN_REGEX', '/\[\[\{.*?"type":"media".*?\}\]\]/s'); /** * Filter callback for media markup filter. @@ -140,6 +140,16 @@ function media_wysiwyg_token_to_markup($match, $wysiwyg = FALSE, $langcode = NUL $match = str_replace("]]", "", $match); $tag = $match[0]; + // Drupal modules with email support often include site name in the subject line + // wrapped in brackets. With a token, this is rendered as "[[site:name]]". Such a + // format will cause a conflict with media_wysiwyg, which is looking for the same. + if (module_exists('token_filter')) { + $token_filter = _token_filter_filter_tokens('[' . $tag . ']', '', '', $langcode, NULL, NULL); + if ($token_filter != '[' . $tag . ']') { + return '[[' . $tag . ']]'; + } + } + try { if (!is_string($tag)) { throw new Exception('Unable to find matching tag'); diff --git a/dkan/modules/contrib/media/modules/media_wysiwyg/includes/media_wysiwyg.pages.inc b/dkan/modules/contrib/media/modules/media_wysiwyg/includes/media_wysiwyg.pages.inc index c126058a4..a3404de87 100644 --- a/dkan/modules/contrib/media/modules/media_wysiwyg/includes/media_wysiwyg.pages.inc +++ b/dkan/modules/contrib/media/modules/media_wysiwyg/includes/media_wysiwyg.pages.inc @@ -180,6 +180,7 @@ function media_wysiwyg_format_form_view_mode(&$form, $form_state, $file) { 'class' => 'button', 'title' => t('Use for replace fox or edit file fields.'), ), + 'query' => drupal_get_destination(), ); if (!empty($_GET['render'])) { $link_options['query']['render'] = $_GET['render']; diff --git a/dkan/modules/contrib/media/modules/media_wysiwyg/media_wysiwyg.info b/dkan/modules/contrib/media/modules/media_wysiwyg/media_wysiwyg.info index 88a037747..ce687f67f 100644 --- a/dkan/modules/contrib/media/modules/media_wysiwyg/media_wysiwyg.info +++ b/dkan/modules/contrib/media/modules/media_wysiwyg/media_wysiwyg.info @@ -16,8 +16,8 @@ files[] = tests/media_wysiwyg.paragraph_fix_filter.test configure = admin/config/media/browser -; Information added by Drupal.org packaging script on 2018-04-25 -version = "7.x-2.19" +; Information added by Drupal.org packaging script on 2018-10-24 +version = "7.x-2.21" core = "7.x" project = "media" -datestamp = "1524677887" +datestamp = "1540363086" diff --git a/dkan/modules/contrib/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.info b/dkan/modules/contrib/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.info index bbeff2411..5fc3f6f85 100644 --- a/dkan/modules/contrib/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.info +++ b/dkan/modules/contrib/media/modules/media_wysiwyg_view_mode/media_wysiwyg_view_mode.info @@ -3,8 +3,8 @@ description = DEPRECATED, this folder is only here so that the module can be uni package = Media core = 7.x -; Information added by Drupal.org packaging script on 2018-04-25 -version = "7.x-2.19" +; Information added by Drupal.org packaging script on 2018-10-24 +version = "7.x-2.21" core = "7.x" project = "media" -datestamp = "1524677887" +datestamp = "1540363086" diff --git a/dkan/modules/contrib/media/modules/mediafield/mediafield.info b/dkan/modules/contrib/media/modules/mediafield/mediafield.info index 61c26869b..af92f57e0 100644 --- a/dkan/modules/contrib/media/modules/mediafield/mediafield.info +++ b/dkan/modules/contrib/media/modules/mediafield/mediafield.info @@ -4,8 +4,8 @@ package = Media core = 7.x dependencies[] = media -; Information added by Drupal.org packaging script on 2018-04-25 -version = "7.x-2.19" +; Information added by Drupal.org packaging script on 2018-10-24 +version = "7.x-2.21" core = "7.x" project = "media" -datestamp = "1524677887" +datestamp = "1540363086" diff --git a/dkan/modules/contrib/media/tests/media_module_test.info b/dkan/modules/contrib/media/tests/media_module_test.info index c7360b8e6..d3541be5d 100644 --- a/dkan/modules/contrib/media/tests/media_module_test.info +++ b/dkan/modules/contrib/media/tests/media_module_test.info @@ -6,8 +6,8 @@ hidden = TRUE files[] = includes/MediaModuleTest.inc -; Information added by Drupal.org packaging script on 2018-04-25 -version = "7.x-2.19" +; Information added by Drupal.org packaging script on 2018-10-24 +version = "7.x-2.21" core = "7.x" project = "media" -datestamp = "1524677887" +datestamp = "1540363086" diff --git a/dkan/modules/contrib/menu_block/menu-block-background-display-options.png b/dkan/modules/contrib/menu_block/css/display-options-background.png similarity index 100% rename from dkan/modules/contrib/menu_block/menu-block-background-display-options.png rename to dkan/modules/contrib/menu_block/css/display-options-background.png diff --git a/dkan/modules/contrib/menu_block/menu-block.admin.css b/dkan/modules/contrib/menu_block/css/menu-block.admin.css similarity index 96% rename from dkan/modules/contrib/menu_block/menu-block.admin.css rename to dkan/modules/contrib/menu_block/css/menu-block.admin.css index 14c36615f..d23c87715 100644 --- a/dkan/modules/contrib/menu_block/menu-block.admin.css +++ b/dkan/modules/contrib/menu_block/css/menu-block.admin.css @@ -38,7 +38,7 @@ label#item-label { border: 1px solid #666; color: #666; font-weight: bold; - background-image: url(menu-block-background-display-options.png); + background-image: url(display-options-background.png); background-position: left top; background-repeat: no-repeat; } diff --git a/dkan/modules/contrib/menu_block/menu-block.js b/dkan/modules/contrib/menu_block/js/menu-block.js similarity index 95% rename from dkan/modules/contrib/menu_block/menu-block.js rename to dkan/modules/contrib/menu_block/js/menu-block.js index 73a99adbb..2d7d85e62 100644 --- a/dkan/modules/contrib/menu_block/menu-block.js +++ b/dkan/modules/contrib/menu_block/js/menu-block.js @@ -22,7 +22,7 @@ Drupal.behaviors.menu_block = { } }); - // Syncronize the display of menu and parent item selects. + // Synchronize the display of menu and parent item selects. $('.menu-block-parent-mlid', context).change( function() { var menuItem = $(this).val().split(':'); $('.menu-block-menu-name').val(menuItem[0]); diff --git a/dkan/modules/contrib/menu_block/menu_block.admin.inc b/dkan/modules/contrib/menu_block/menu_block.admin.inc index eda4fd621..04902624c 100644 --- a/dkan/modules/contrib/menu_block/menu_block.admin.inc +++ b/dkan/modules/contrib/menu_block/menu_block.admin.inc @@ -241,8 +241,8 @@ function menu_block_configure_form($form, &$form_state) { } // Build the standard form. - $form['#attached']['js'][] = drupal_get_path('module', 'menu_block') . '/menu-block.js'; - $form['#attached']['css'][] = drupal_get_path('module', 'menu_block') . '/menu-block.admin.css'; + $form['#attached']['js'][] = drupal_get_path('module', 'menu_block') . '/js/menu-block.js'; + $form['#attached']['css'][] = drupal_get_path('module', 'menu_block') . '/css/menu-block.admin.css'; $form['#attached']['library'][] = array('system', 'ui.button'); $form['menu-block-wrapper-start'] = array( @@ -279,6 +279,12 @@ function menu_block_configure_form($form, &$form_state) { ), ); } + $form['display_empty'] = array( + '#type' => 'checkbox', + '#title' => t('Always display title'), + '#description' => t('Display the block with its title even if the block content is empty.'), + '#default_value' => $config['display_empty'], + ); $form['admin_title'] = array( '#type' => 'textfield', '#default_value' => $config['admin_title'], @@ -395,10 +401,12 @@ function menu_block_configure_form($form, &$form_state) { $form['menu-block-wrapper-close'] = array('#markup' => ''); // Set visibility of advanced options. - foreach (array('title_link', 'follow', 'depth_relative', 'follow_parent', 'expanded', 'sort', 'parent') as $key) { + foreach (array('title_link', 'display_empty', 'follow', 'depth_relative', 'follow_parent', 'expanded', 'sort', 'parent') as $key) { $form[$key]['#states']['visible'][':input[name=display_options]'] = array('value' => 'advanced'); } - if ($config['title_link'] || $follow || $config['expanded'] || $config['sort'] || $config['parent_mlid']) { + // depth_relative and follow_parent aren't listed below because they require + // $follow to be true. + if ($config['title_link'] || $config['display_empty'] || $follow || $config['expanded'] || $config['sort'] || $config['parent_mlid']) { $form['display_options']['#default_value'] = 'advanced'; } @@ -443,6 +451,7 @@ function _menu_block_block_save($delta = '', $edit = array()) { variable_set("menu_block_{$delta}_parent", $edit['parent']); variable_set("menu_block_{$delta}_level", $edit['level']); variable_set("menu_block_{$delta}_follow", $edit['follow']); + variable_set("menu_block_{$delta}_display_empty", $edit['display_empty']); variable_set("menu_block_{$delta}_depth", $edit['depth']); variable_set("menu_block_{$delta}_depth_relative", $edit['depth_relative']); variable_set("menu_block_{$delta}_expanded", $edit['expanded']); diff --git a/dkan/modules/contrib/menu_block/menu_block.info b/dkan/modules/contrib/menu_block/menu_block.info index f64bcf4da..5a845ab34 100644 --- a/dkan/modules/contrib/menu_block/menu_block.info +++ b/dkan/modules/contrib/menu_block/menu_block.info @@ -6,9 +6,8 @@ dependencies[] = menu (>7.11) configure = admin/config/user-interface/menu-block -; Information added by Drupal.org packaging script on 2015-06-30 -version = "7.x-2.7" +; Information added by Drupal.org packaging script on 2018-12-04 +version = "7.x-2.8" core = "7.x" project = "menu_block" -datestamp = "1435676232" - +datestamp = "1543950483" diff --git a/dkan/modules/contrib/menu_block/menu_block.module b/dkan/modules/contrib/menu_block/menu_block.module index a5bf3964f..705fc85c0 100644 --- a/dkan/modules/contrib/menu_block/menu_block.module +++ b/dkan/modules/contrib/menu_block/menu_block.module @@ -5,7 +5,7 @@ */ /** - * Denotes that the tree should use the menu picked by the curent page. + * Denotes that the tree should use the menu picked by the current page. */ define('MENU_TREE__CURRENT_PAGE_MENU', '_active'); @@ -241,6 +241,7 @@ function menu_block_default_config() { 'admin_title' => '', 'level' => 1, 'follow' => 0, + 'display_empty' => 0, 'depth' => 0, 'depth_relative' => 0, 'expanded' => 0, @@ -532,13 +533,18 @@ function menu_tree_build(array &$config) { $data['subject_array'] = $title; $data['subject'] = drupal_render($title); $data['content'] = array(); - if (!empty($tree) && $output = menu_block_tree_output($tree, $config)) { - $data['content']['#content'] = $output; - $data['content']['#theme'] = array( - 'menu_block_wrapper__' . str_replace('-', '_', $config['delta']), - 'menu_block_wrapper__' . str_replace('-', '_', $config['menu_name']), - 'menu_block_wrapper' - ); + if (!empty($tree) || !empty($config['display_empty'])) { + if ($output = menu_block_tree_output($tree, $config)) { + $data['content']['#content'] = $output; + $data['content']['#theme'] = array( + 'menu_block_wrapper__' . str_replace('-', '_', $config['delta']), + 'menu_block_wrapper__' . str_replace('-', '_', $config['menu_name']), + 'menu_block_wrapper' + ); + } + else { + $data['content']['#content'] = NULL; + } $data['content']['#config'] = $config; $data['content']['#delta'] = $config['delta']; } diff --git a/dkan/modules/contrib/menu_block/menu_block_export.info b/dkan/modules/contrib/menu_block/menu_block_export.info index 996c9ac54..167a62c0f 100644 --- a/dkan/modules/contrib/menu_block/menu_block_export.info +++ b/dkan/modules/contrib/menu_block/menu_block_export.info @@ -9,9 +9,8 @@ files[] = menu_block_export.admin.inc configure = admin/config/user-interface/menu-block/export -; Information added by Drupal.org packaging script on 2015-06-30 -version = "7.x-2.7" +; Information added by Drupal.org packaging script on 2018-12-04 +version = "7.x-2.8" core = "7.x" project = "menu_block" -datestamp = "1435676232" - +datestamp = "1543950483" diff --git a/dkan/modules/contrib/menu_block/plugins/content_types/menu_tree/menu_tree.inc b/dkan/modules/contrib/menu_block/plugins/content_types/menu_tree/menu_tree.inc index e57eded27..a31b41793 100644 --- a/dkan/modules/contrib/menu_block/plugins/content_types/menu_tree/menu_tree.inc +++ b/dkan/modules/contrib/menu_block/plugins/content_types/menu_tree/menu_tree.inc @@ -33,8 +33,8 @@ function menu_block_menu_tree_content_type_content_types() { 'defaults' => menu_block_get_config(), // JavaScript and CSS for the config form. - 'js' => array(drupal_get_path('module', 'menu_block') . '/menu-block.js'), - 'css' => array(drupal_get_path('module', 'menu_block') . '/menu-block-admin.css'), + 'js' => array(drupal_get_path('module', 'menu_block') . '/js/menu-block.js'), + 'css' => array(drupal_get_path('module', 'menu_block') . '/css/menu-block.admin.css'), ); $menus = menu_block_get_all_menus(); diff --git a/dkan/modules/contrib/migrate/includes/migration.inc b/dkan/modules/contrib/migrate/includes/migration.inc index 747bc6dfa..a3252afd4 100644 --- a/dkan/modules/contrib/migrate/includes/migration.inc +++ b/dkan/modules/contrib/migrate/includes/migration.inc @@ -725,8 +725,9 @@ abstract class Migration extends MigrationBase { $this->source->rewind(); } catch (Exception $e) { self::displayMessage( - t('Migration failed with source plugin exception: %e, in %file:%line', + t('Migration for %class failed with source plugin exception: %e, in %file:%line', array( + '%class' => get_class($this), '%e' => $e->getMessage(), '%file' => $e->getFile(), '%line' => $e->getLine(), @@ -760,7 +761,7 @@ abstract class Migration extends MigrationBase { else { $this->map->saveIDMapping($this->sourceValues, array(), MigrateMap::STATUS_FAILED, $this->rollbackAction, - $data_row->migrate_map_hash); + NULL); if ($this->map->messageCount() == 0) { $message = t('New object was not saved, no error provided'); $this->saveMessage($message); @@ -775,7 +776,7 @@ abstract class Migration extends MigrationBase { } catch (Exception $e) { $this->map->saveIDMapping($this->sourceValues, array(), MigrateMap::STATUS_FAILED, $this->rollbackAction, - $data_row->migrate_map_hash); + NULL); $this->handleException($e); } $this->total_processed++; @@ -807,8 +808,9 @@ abstract class Migration extends MigrationBase { $this->source->next(); } catch (Exception $e) { self::displayMessage( - t('Migration failed with source plugin exception: %e, in %file:%line', + t('Migration for %class failed with source plugin exception: %e, in %file:%line', array( + '%class' => get_class($this), '%e' => $e->getMessage(), '%file' => $e->getFile(), '%line' => $e->getLine(), @@ -1543,12 +1545,12 @@ abstract class Migration extends MigrationBase { } $i = 1; $candidate = $original; - while ($candidate_found = db_select($dedupe['table'], 't') + while (db_select($dedupe['table'], 't') ->fields('t', array($dedupe['column'])) ->range(0, 1) ->condition('t.' . $dedupe['column'], $candidate) ->execute() - ->fetchField()) { + ->rowCount() > 0) { // We already have the candidate value. Find a non-existing value. $i++; // @TODO: support custom replacement pattern instead of just append. diff --git a/dkan/modules/contrib/migrate/migrate.info b/dkan/modules/contrib/migrate/migrate.info index 27190b6ee..695bbb032 100644 --- a/dkan/modules/contrib/migrate/migrate.info +++ b/dkan/modules/contrib/migrate/migrate.info @@ -51,8 +51,8 @@ files[] = tests/plugins/destinations/term.test files[] = tests/plugins/destinations/user.test files[] = tests/plugins/sources/xml.test -; Information added by Drupal.org packaging script on 2018-05-31 -version = "7.x-2.10" +; Information added by Drupal.org packaging script on 2018-06-10 +version = "7.x-2.11" core = "7.x" project = "migrate" -datestamp = "1527801792" +datestamp = "1528674486" diff --git a/dkan/modules/contrib/migrate/migrate_example/migrate_example.info b/dkan/modules/contrib/migrate/migrate_example/migrate_example.info index 0cf2f0e63..bef523c82 100644 --- a/dkan/modules/contrib/migrate/migrate_example/migrate_example.info +++ b/dkan/modules/contrib/migrate/migrate_example/migrate_example.info @@ -18,8 +18,8 @@ files[] = wine.inc ; For testing table_copy plugin. Since is infrequently used, we comment it out. ; files[] = example.table_copy.inc -; Information added by Drupal.org packaging script on 2018-05-31 -version = "7.x-2.10" +; Information added by Drupal.org packaging script on 2018-06-10 +version = "7.x-2.11" core = "7.x" project = "migrate" -datestamp = "1527801792" +datestamp = "1528674486" diff --git a/dkan/modules/contrib/migrate/migrate_example/migrate_example_oracle/migrate_example_oracle.info b/dkan/modules/contrib/migrate/migrate_example/migrate_example_oracle/migrate_example_oracle.info index f926c2510..a2be32ff3 100644 --- a/dkan/modules/contrib/migrate/migrate_example/migrate_example_oracle/migrate_example_oracle.info +++ b/dkan/modules/contrib/migrate/migrate_example/migrate_example_oracle/migrate_example_oracle.info @@ -11,8 +11,8 @@ name = "Migrate example - Oracle" package = "Migration" project = "migrate_example_oracle" -; Information added by Drupal.org packaging script on 2018-05-31 -version = "7.x-2.10" +; Information added by Drupal.org packaging script on 2018-06-10 +version = "7.x-2.11" core = "7.x" project = "migrate" -datestamp = "1527801792" +datestamp = "1528674486" diff --git a/dkan/modules/contrib/migrate/migrate_example_baseball/migrate_example_baseball.info b/dkan/modules/contrib/migrate/migrate_example_baseball/migrate_example_baseball.info index 54a51a43c..a5af1849a 100644 --- a/dkan/modules/contrib/migrate/migrate_example_baseball/migrate_example_baseball.info +++ b/dkan/modules/contrib/migrate/migrate_example_baseball/migrate_example_baseball.info @@ -24,8 +24,8 @@ name = "migrate_example_baseball" package = "Migration" php = "5.2.4" -; Information added by Drupal.org packaging script on 2018-05-31 -version = "7.x-2.10" +; Information added by Drupal.org packaging script on 2018-06-10 +version = "7.x-2.11" core = "7.x" project = "migrate" -datestamp = "1527801792" +datestamp = "1528674486" diff --git a/dkan/modules/contrib/migrate/migrate_ui/migrate_ui.info b/dkan/modules/contrib/migrate/migrate_ui/migrate_ui.info index a1fb5b9df..7f3763b6e 100644 --- a/dkan/modules/contrib/migrate/migrate_ui/migrate_ui.info +++ b/dkan/modules/contrib/migrate/migrate_ui/migrate_ui.info @@ -6,8 +6,8 @@ core = 7.x dependencies[] = migrate files[] = migrate_ui.wizard.inc -; Information added by Drupal.org packaging script on 2018-05-31 -version = "7.x-2.10" +; Information added by Drupal.org packaging script on 2018-06-10 +version = "7.x-2.11" core = "7.x" project = "migrate" -datestamp = "1527801792" +datestamp = "1528674486" diff --git a/dkan/modules/contrib/migrate/plugins/sources/json.inc b/dkan/modules/contrib/migrate/plugins/sources/json.inc index 1a276fbb9..9844d1094 100644 --- a/dkan/modules/contrib/migrate/plugins/sources/json.inc +++ b/dkan/modules/contrib/migrate/plugins/sources/json.inc @@ -53,7 +53,7 @@ class MigrateListJSON extends MigrateList { migrate_instrument_stop("Retrieve $this->listUrl"); if ($json) { $data = drupal_json_decode($json); - if ($data) { + if ($data !== NULL) { return $this->getIDsFromJSON($data); } } diff --git a/dkan/modules/contrib/panopoly_images/CHANGELOG.txt b/dkan/modules/contrib/panopoly_images/CHANGELOG.txt index 82c669b5a..e82918b6e 100644 --- a/dkan/modules/contrib/panopoly_images/CHANGELOG.txt +++ b/dkan/modules/contrib/panopoly_images/CHANGELOG.txt @@ -1,3 +1,19 @@ +7.x-1.58, 2018-12-11 +------------------- +- No changes since last release. + +7.x-1.57, 2018-10-18 +------------------- +- No changes since last release. + +7.x-1.56, 2018-10-10 +------------------- +- No changes since last release. + +7.x-1.55, 2018-07-04 +------------------- +- No changes since last release. + 7.x-1.54, 2018-05-10 ------------------- - Update manualcrop to version 1.7. diff --git a/dkan/modules/contrib/panopoly_images/panopoly_images.info b/dkan/modules/contrib/panopoly_images/panopoly_images.info index 0c876c3b6..55922ec6b 100644 --- a/dkan/modules/contrib/panopoly_images/panopoly_images.info +++ b/dkan/modules/contrib/panopoly_images/panopoly_images.info @@ -27,8 +27,8 @@ features[image][] = panopoly_image_square features[image][] = panopoly_image_thumbnail features[image][] = panopoly_image_video -; Information added by Drupal.org packaging script on 2018-05-10 -version = "7.x-1.54" +; Information added by Drupal.org packaging script on 2018-12-12 +version = "7.x-1.58" core = "7.x" project = "panopoly_images" -datestamp = "1525979341" +datestamp = "1544590730" diff --git a/dkan/modules/contrib/panopoly_widgets/CHANGELOG.txt b/dkan/modules/contrib/panopoly_widgets/CHANGELOG.txt index 8ad6c5e9a..621a7f196 100644 --- a/dkan/modules/contrib/panopoly_widgets/CHANGELOG.txt +++ b/dkan/modules/contrib/panopoly_widgets/CHANGELOG.txt @@ -1,3 +1,22 @@ +7.x-1.58, 2018-12-11 +------------------- +- File entity overwriting slide title on spotlight widgets. +- Normalize "Slide duration" for Spotlight widgets. +- Update Media to 2.21. +- Update file_entity to 2.22. + +7.x-1.57, 2018-10-18 +------------------- +- No changes since last release. + +7.x-1.56, 2018-10-10 +------------------- +- No changes since last release. + +7.x-1.55, 2018-07-04 +------------------- +- No changes since last release. + 7.x-1.54, 2018-05-10 ------------------- - Upgrade media and media_youtube in panopoly_widgets. diff --git a/dkan/modules/contrib/panopoly_widgets/panopoly_widgets.features.field_instance.inc b/dkan/modules/contrib/panopoly_widgets/panopoly_widgets.features.field_instance.inc index ba81d0652..9d5fa0552 100644 --- a/dkan/modules/contrib/panopoly_widgets/panopoly_widgets.features.field_instance.inc +++ b/dkan/modules/contrib/panopoly_widgets/panopoly_widgets.features.field_instance.inc @@ -415,7 +415,11 @@ function panopoly_widgets_field_default_field_instances() { // 'fieldable_panels_pane-spotlight-field_basic_spotlight_duration'. $field_instances['fieldable_panels_pane-spotlight-field_basic_spotlight_duration'] = array( 'bundle' => 'spotlight', - 'default_value' => NULL, + 'default_value' => array( + 0 => array( + 'value' => 5, + ), + ), 'deleted' => 0, 'description' => '', 'display' => array( @@ -429,7 +433,7 @@ function panopoly_widgets_field_default_field_instances() { 'entity_type' => 'fieldable_panels_pane', 'field_name' => 'field_basic_spotlight_duration', 'label' => 'Slide Duration', - 'required' => 0, + 'required' => 1, 'settings' => array( 'max' => 60, 'min' => 1, diff --git a/dkan/modules/contrib/panopoly_widgets/panopoly_widgets.info b/dkan/modules/contrib/panopoly_widgets/panopoly_widgets.info index 0a4e67095..338c8eaf9 100644 --- a/dkan/modules/contrib/panopoly_widgets/panopoly_widgets.info +++ b/dkan/modules/contrib/panopoly_widgets/panopoly_widgets.info @@ -125,8 +125,8 @@ features[linkit_profiles][] = content_fields features[views_view][] = panopoly_widgets_general_content features_exclude[dependencies][linkit] = linkit -; Information added by Drupal.org packaging script on 2018-05-10 -version = "7.x-1.54" +; Information added by Drupal.org packaging script on 2018-12-12 +version = "7.x-1.58" core = "7.x" project = "panopoly_widgets" -datestamp = "1525979476" +datestamp = "1544590822" diff --git a/dkan/modules/contrib/panopoly_widgets/panopoly_widgets.install b/dkan/modules/contrib/panopoly_widgets/panopoly_widgets.install index 7465dc064..1536a95e9 100644 --- a/dkan/modules/contrib/panopoly_widgets/panopoly_widgets.install +++ b/dkan/modules/contrib/panopoly_widgets/panopoly_widgets.install @@ -485,3 +485,57 @@ function panopoly_widgets_update_7022() { } } } + +/** + * Make sure that "Slide duration" on Spotlight widgets always has a value. + */ +function panopoly_widgets_update_7023(&$sandbox) { + if (!isset($sandbox['fpids'])) { + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'fieldable_panels_pane') + ->entityCondition('bundle', 'spotlight'); + $result = $query->execute(); + if (isset($result['fieldable_panels_pane'])) { + $sandbox['fpids'] = array_keys($result['fieldable_panels_pane']); + } + } + else { + $count = 10; + while (!empty($sandbox['fpids']) && $count >= 0) { + $fpp = fieldable_panels_panes_load(array_shift($sandbox['fpids'])); + $wrapper = entity_metadata_wrapper('fieldable_panels_pane', $fpp); + if (!$wrapper->field_basic_spotlight_duration->value()) { + $wrapper->field_basic_spotlight_duration->set(variable_get('panopoly_widgets_spotlight_rotation_time', 5)); + $wrapper->save(); + } + } + } + + $sandbox['#finished'] = empty($sandbox['fpids']); + + if ($sandbox['#finished']) { + variable_del('panopoly_widgets_spotlight_rotation_time'); + } +} + +/** + * Update field instance settings for duration fields on Spotlight widgets. + */ +function panopoly_widgets_update_7024() { + // Remove legacy field if it's still around. + if ($instance_info = field_info_instance('fieldable_panels_pane', 'field_spotlight_rotation_time', 'spotlight')) { + field_delete_instance($instance_info); + } + + // Make the default value 5 and mark as required. + if ($instance_info = field_info_instance('fieldable_panels_pane', 'field_basic_spotlight_duration', 'spotlight')) { + $instance_info['default_value'] = array( + 0 => array( + 'value' => 5, + ), + ); + $instance_info['required'] = 1; + field_update_instance($instance_info); + } +} diff --git a/dkan/modules/contrib/panopoly_widgets/panopoly_widgets.make b/dkan/modules/contrib/panopoly_widgets/panopoly_widgets.make index 29e83641e..25bad686c 100644 --- a/dkan/modules/contrib/panopoly_widgets/panopoly_widgets.make +++ b/dkan/modules/contrib/panopoly_widgets/panopoly_widgets.make @@ -20,10 +20,10 @@ projects[menu_block][subdir] = contrib ; Panopoly - Contrib - Files & Media -projects[file_entity][version] = 2.21 +projects[file_entity][version] = 2.22 projects[file_entity][subdir] = contrib -projects[media][version] = 2.19 +projects[media][version] = 2.21 projects[media][subdir] = contrib projects[media_youtube][version] = 3.7 diff --git a/dkan/modules/contrib/panopoly_widgets/panopoly_widgets.spotlight.inc b/dkan/modules/contrib/panopoly_widgets/panopoly_widgets.spotlight.inc index fb50364fe..ec4fffab5 100644 --- a/dkan/modules/contrib/panopoly_widgets/panopoly_widgets.spotlight.inc +++ b/dkan/modules/contrib/panopoly_widgets/panopoly_widgets.spotlight.inc @@ -159,7 +159,7 @@ function panopoly_widgets_field_formatter_view($entity_type, $entity, $field, $i $duration = $duration_field['value']; } if (empty($duration)) { - $duration = variable_get('panopoly_widgets_spotlight_rotation_time', 4); + $duration = variable_get('panopoly_widgets_spotlight_rotation_time', 5); } $pager_style = 'full'; // Default to the Full pager if field isn't filled out (i.e., legacy data). @@ -208,6 +208,12 @@ function panopoly_widgets_field_formatter_view($entity_type, $entity, $field, $i // Assemble the spotlight items (rendered in panopoly_spotlight_wrapper()) foreach ($items as $delta => $item_data) { + if (isset($item_data['field_title'])) { + $item_data['title'] = $item_data['field_title']; + } + if (isset($item_data['field_alt'])) { + $item_data['alt'] = $item_data['field_alt']; + } $element[0]['slides'][] = array( '#theme' => 'panopoly_spotlight_view', '#items' => $item_data, @@ -235,7 +241,7 @@ function panopoly_widgets_field_widget_form(&$form, &$form_state, $field, $insta $element['title'] = array( '#title' => t('Title'), '#type' => 'textfield', - '#default_value' => isset($items[$delta]['title']) ? $items[$delta]['title'] : NULL, + '#default_value' => isset($items[$delta]['field_title']) ? $items[$delta]['field_title'] : NULL, ); $element['link'] = array( @@ -283,7 +289,7 @@ function panopoly_widgets_field_widget_form(&$form, &$form_state, $field, $insta $element['alt'] = array( '#title' => t('Alt text'), '#type' => 'textfield', - '#default_value' => isset($items[$delta]['alt']) ? $items[$delta]['alt'] : NULL, + '#default_value' => isset($items[$delta]['field_alt']) ? $items[$delta]['field_alt'] : NULL, ); $element['description'] = array( @@ -327,6 +333,12 @@ function panopoly_widgets_field_presave($entity_type, $entity, $field, $instance $item['fid'] = $fid; } } + if (isset($item['field_title'])) { + $item['title'] = $item['field_title']; + } + if (isset($item['field_alt'])) { + $item['alt'] = $item['field_alt']; + } } image_field_presave($entity_type, $entity, $field, $instance, $langcode, $items); } @@ -338,11 +350,13 @@ function panopoly_widgets_field_presave($entity_type, $entity, $field, $instance function panopoly_widgets_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) { $entity_items_copy = $items; image_field_load($entity_type, $entities, $field, $instances, $langcode, $items, $age); - // image_field_load() will overwrite 'title' with the image title. Here we - // return it to the item title. + // Both image_field_load() and file_entity_entity_load() will overwrite the + // 'title' and 'alt' with the one from the file. Here we save the originals + // for later. foreach ($entity_items_copy as $entity_id => $items_copy) { foreach ($items_copy as $delta => $item) { - $items[$entity_id][$delta]['title'] = $item['title']; + $items[$entity_id][$delta]['field_title'] = $item['title']; + $items[$entity_id][$delta]['field_alt'] = $item['alt']; } } } diff --git a/dkan/modules/contrib/recline/backend.ckan_get.js b/dkan/modules/contrib/recline/backend.ckan_get.js index 69b0fa099..bbf63134b 100644 --- a/dkan/modules/contrib/recline/backend.ckan_get.js +++ b/dkan/modules/contrib/recline/backend.ckan_get.js @@ -46,7 +46,7 @@ this.recline.Backend.Ckan = this.recline.Backend.Ckan || {}; wrapper = my.DataStore(out.endpoint); } var dfd = new Deferred(); - var jqxhr = wrapper.search({resource_id: dataset.id, limit: 0}); + var jqxhr = wrapper.search({resource_id: dataset.id, limit: 0, q: ''}); jqxhr.done(function(results, status, req) { if(results.error) { diff --git a/dkan/modules/contrib/recline/composer.json b/dkan/modules/contrib/recline/composer.json new file mode 100644 index 000000000..e6cfda65c --- /dev/null +++ b/dkan/modules/contrib/recline/composer.json @@ -0,0 +1,221 @@ +{ + "name": "getdkan/recline", + "description": "recline.js module for DKAN/Drupal", + "version": "2.x-dev", + "type": "drupal-module", + "require": { + "composer/installers": "~1.0", + "library/recline": "1.0", + "library/lodash": "1.0", + "library/backbone": "1.0", + "library/csv": "1.0", + "library/slickgrid": "1.0", + "library/mustache": "1.0", + "library/moment": "1.0", + "library/leaflet": "1.0", + "library/flot": "1.0", + "library/deep_diff": "1.0", + "library/recline_deeplink": "1.0", + "library/leaflet_markercluster": "1.0", + "library/xls": "1.0", + "library/jsxlsx": "1.0", + "library/jsonview": "1.0" + }, + "repositories": { + "libraries/recline": { + "type": "package", + "package": { + "name": "getdkan/recline.js", + "version": "1.0", + "type": "drupal-library", + "source": { + "url": "https://github.com/NuCivic/recline.js.git", + "type": "git", + "reference": "master" + } + } + }, + "libraries/lodash": { + "type": "package", + "package": { + "name": "library/lodash", + "version": "1.0", + "type": "drupal-library", + "source": { + "url": "https://github.com/lodash/lodash.git", + "type": "git", + "reference": "e21e993729861a2bc1d01c858cfabce7a27d2861" + } + } + }, + "libraries/backbone": { + "type": "package", + "package": { + "name": "library/backbone", + "version": "1.0", + "type": "drupal-library", + "source": { + "url": "https://github.com/jashkenas/backbone.git", + "type": "git", + "reference": "e109f6d3e7a366f933f1f34405ca97effecae6c5" + } + } + }, + "libraries/csv": { + "type": "package", + "package": { + "name": "library/csv", + "version": "1.0", + "type": "drupal-library", + "source": { + "url": "https://github.com/okfn/csv.js.git", + "type": "git", + "reference": "7150de4c8d5e02626ac6a7fb9c178e955c964faf" + } + } + }, + "libraries/slickgrid": { + "type": "package", + "package": { + "name": "library/slickgrid", + "version": "1.0", + "type": "drupal-library", + "source": { + "url": "https://github.com/mleibman/SlickGrid.git", + "type": "git", + "reference": "e004912b5ce29ac0d0cb04df50fe66db5e3af9ea" + } + } + }, + "libraries/mustache": { + "type": "package", + "package": { + "name": "library/mustache", + "version": "1.0", + "type": "drupal-library", + "source": { + "url": "https://github.com/janl/mustache.js.git", + "type": "git", + "reference": "d4ba5a19d4d04b139bbf7840fe342bb43930aee3" + } + } + }, + "libraries/moment": { + "type": "package", + "package": { + "name": "library/moment", + "version": "1.0", + "type": "drupal-library", + "source": { + "url": "https://github.com/moment/moment.git", + "type": "git", + "reference": "78a53b8cb53e967c6dac2e7325e18da2a472fc2d" + } + } + }, + "libraries/leaflet": { + "type": "package", + "package": { + "name": "library/leaflet", + "version": "1.0", + "type": "drupal-library", + "source": { + "url": "https://github.com/NuCivic/Leaflet.git", + "type": "git", + "reference": "v1.0.2-alt-marker-shadow-5258" + } + } + }, + "libraries/flot": { + "type": "package", + "package": { + "name": "library/flot", + "version": "1.0", + "type": "drupal-library", + "source": { + "url": "https://github.com/flot/flot.git", + "type": "git", + "reference": "7f5f90384ed6d6c30b232580d358c84e355919ab" + } + } + }, + "libraries/deep_diff": { + "type": "package", + "package": { + "name": "library/deep_diff", + "version": "1.0", + "type": "drupal-library", + "source": { + "url": "https://github.com/flitbit/diff.git", + "type": "git", + "reference": "07e91c624e5016be5c5c6560a9eabe49ef3ba2d0" + } + } + }, + "libraries/recline_deeplink": { + "type": "package", + "package": { + "name": "library/recline_deeplink", + "version": "1.0", + "type": "drupal-library", + "source": { + "url": "https://github.com/NuCivic/recline-deeplink.git", + "type": "git", + "reference": "c1695d669f8314ed8b66e5907eb4f1bc4a8a9495" + } + } + }, + "libraries/leaflet_markercluster": { + "type": "package", + "package": { + "name": "library/leaflet_markercluster", + "version": "1.0", + "type": "drupal-library", + "source": { + "url": "https://github.com/Leaflet/Leaflet.markercluster.git", + "type": "git", + "reference": "eb922a3646d2e1ef9ed9de99e20200709f1f9bb5" + } + } + }, + "libraries/xls": { + "type": "package", + "package": { + "name": "library/xls", + "version": "1.0", + "type": "drupal-library", + "source": { + "url": "https://github.com/NuCivic/recline.backend.xlsx", + "type": "git", + "reference": "a5d49f7305895f147c3e9a12115c2456a897f941" + } + } + }, + "libraries/jsxlsx": { + "type": "package", + "package": { + "name": "library/jsxlsx", + "version": "1.0", + "type": "drupal-library", + "source": { + "url": "https://github.com/SheetJS/js-xlsx", + "type": "git", + "reference": "53f7f6d9446ccd680c9b13992d6dcdccde49a8f6" + } + } + }, + "libraries/jsonview": { + "type": "package", + "package": { + "name": "library/jsonview", + "version": "1.0", + "type": "drupal-library", + "source": { + "url": "https://github.com/yesmeck/jquery-jsonview.git", + "type": "git", + "reference": "84fb68cc25e4749d2adf5af8dc3adefd80b4c430" + } + } + } + } +} diff --git a/dkan/modules/contrib/recline/dkan-module-init.sh b/dkan/modules/contrib/recline/dkan-module-init.sh index 6bcbfb4bd..cd4460545 100644 --- a/dkan/modules/contrib/recline/dkan-module-init.sh +++ b/dkan/modules/contrib/recline/dkan-module-init.sh @@ -1,4 +1,3 @@ - # Name of the current module. DKAN_MODULE=`ls *.info | cut -d'.' -f1` @@ -30,6 +29,7 @@ if wget -q "$URL"; then bash dkan-init.sh dkan --skip-init --deps cd .. echo -ne 'y\n' | ahoy dkan drupal-rebuild $DATABASE_URL + ahoy dkan remake echo -ne 'N\n' | ahoy dkan reinstall else wget -O /tmp/dkan-init.sh https://raw.githubusercontent.com/NuCivic/dkan/$DKAN_VERSION/dkan-init.sh @@ -46,6 +46,7 @@ else bash /tmp/dkan-init.sh $DKAN_MODULE $@ --skip-reinstall --branch=$DKAN_VERSION fi +echo "Linking/Building Module..." ahoy dkan module-link $DKAN_MODULE ahoy dkan module-make $DKAN_MODULE @@ -59,6 +60,7 @@ else fi ahoy drush en $DKAN_MODULE -y +ahoy drush updb -y #Fix for behat bug not recognizing symlinked feature files or files outside it's root. See https://jira.govdelivery.com/browse/CIVIC-1005 -#cp dkan_workflow/test/features/dkan_workflow.feature dkan/test/features/. +#cp dkan_workflow/test/features/dkan_workflow.feature dkan/test/features/. \ No newline at end of file diff --git a/dkan/modules/contrib/recline/js/restdataview.js b/dkan/modules/contrib/recline/js/restdataview.js index 4ba611c26..bc79fa3b0 100644 --- a/dkan/modules/contrib/recline/js/restdataview.js +++ b/dkan/modules/contrib/recline/js/restdataview.js @@ -16,9 +16,9 @@ var bounds = L.latLngBounds([]); fl.metadata(function(error, metadata){ - let layersIds = metadata.layers.map(l => l.id); + let layersIds = metadata.layers.map(function(l) {return l.id}); let counter = sl.length; - layersIds.forEach(id => { + layersIds.forEach(function(id) { L.esri.query({ url: Drupal.settings.recline.url + '/' + id }).bounds(function(error, latLngBounds, response){ diff --git a/dkan/modules/contrib/recline/lib/highlight/highlight.pack.js b/dkan/modules/contrib/recline/lib/highlight/highlight.pack.js deleted file mode 100644 index d88023165..000000000 --- a/dkan/modules/contrib/recline/lib/highlight/highlight.pack.js +++ /dev/null @@ -1 +0,0 @@ -!function(e){"undefined"!=typeof exports?e(exports):(window.hljs=e({}),"function"==typeof define&&define.amd&&define("hljs",[],function(){return window.hljs}))}(function(e){function n(e){return e.replace(/&/gm,"&").replace(//gm,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0==t.index}function a(e){return/^(no-?highlight|plain|text)$/i.test(e)}function i(e){var n,t,r,i=e.className+" ";if(i+=e.parentNode?e.parentNode.className:"",t=/\blang(?:uage)?-([\w-]+)\b/i.exec(i))return w(t[1])?t[1]:"no-highlight";for(i=i.split(/\s+/),n=0,r=i.length;r>n;n++)if(w(i[n])||a(i[n]))return i[n]}function o(e,n){var t,r={};for(t in e)r[t]=e[t];if(n)for(t in n)r[t]=n[t];return r}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3==i.nodeType?a+=i.nodeValue.length:1==i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!=r[0].offset?e[0].offset"}function u(e){f+=""}function c(e){("start"==e.event?o:u)(e.node)}for(var s=0,f="",l=[];e.length||r.length;){var g=i();if(f+=n(a.substr(s,g[0].offset-s)),s=g[0].offset,g==e){l.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g==e&&g.length&&g[0].offset==s);l.reverse().forEach(o)}else"start"==g[0].event?l.push(g[0].node):l.pop(),c(g.splice(0,1)[0])}return f+n(a.substr(s))}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var u={},c=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");u[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?c("keyword",a.k):Object.keys(a.k).forEach(function(e){c(e,a.k[e])}),a.k=u}a.lR=t(a.l||/\b\w+\b/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),void 0===a.r&&(a.r=1),a.c||(a.c=[]);var s=[];a.c.forEach(function(e){e.v?e.v.forEach(function(n){s.push(o(e,n))}):s.push("self"==e?a:e)}),a.c=s,a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var f=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=f.length?t(f.join("|"),!0):{exec:function(){return null}}}}r(e)}function f(e,t,a,i){function o(e,n){for(var t=0;t";return i+=e+'">',i+n+o}function p(){if(!L.k)return n(y);var e="",t=0;L.lR.lastIndex=0;for(var r=L.lR.exec(y);r;){e+=n(y.substr(t,r.index-t));var a=g(L,r);a?(B+=a[1],e+=h(a[0],n(r[0]))):e+=n(r[0]),t=L.lR.lastIndex,r=L.lR.exec(y)}return e+n(y.substr(t))}function d(){var e="string"==typeof L.sL;if(e&&!x[L.sL])return n(y);var t=e?f(L.sL,y,!0,M[L.sL]):l(y,L.sL.length?L.sL:void 0);return L.r>0&&(B+=t.r),e&&(M[L.sL]=t.top),h(t.language,t.value,!1,!0)}function b(){return void 0!==L.sL?d():p()}function v(e,t){var r=e.cN?h(e.cN,"",!0):"";e.rB?(k+=r,y=""):e.eB?(k+=n(t)+r,y=""):(k+=r,y=t),L=Object.create(e,{parent:{value:L}})}function m(e,t){if(y+=e,void 0===t)return k+=b(),0;var r=o(t,L);if(r)return k+=b(),v(r,t),r.rB?0:t.length;var a=u(L,t);if(a){var i=L;i.rE||i.eE||(y+=t),k+=b();do L.cN&&(k+=""),B+=L.r,L=L.parent;while(L!=a.parent);return i.eE&&(k+=n(t)),y="",a.starts&&v(a.starts,""),i.rE?0:t.length}if(c(t,L))throw new Error('Illegal lexeme "'+t+'" for mode "'+(L.cN||"")+'"');return y+=t,t.length||1}var N=w(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var R,L=i||N,M={},k="";for(R=L;R!=N;R=R.parent)R.cN&&(k=h(R.cN,"",!0)+k);var y="",B=0;try{for(var C,j,I=0;;){if(L.t.lastIndex=I,C=L.t.exec(t),!C)break;j=m(t.substr(I,C.index-I),C[0]),I=C.index+j}for(m(t.substr(I)),R=L;R.parent;R=R.parent)R.cN&&(k+="");return{r:B,value:k,language:e,top:L}}catch(O){if(-1!=O.message.indexOf("Illegal"))return{r:0,value:n(t)};throw O}}function l(e,t){t=t||E.languages||Object.keys(x);var r={r:0,value:n(e)},a=r;return t.forEach(function(n){if(w(n)){var t=f(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}}),a.language&&(r.second_best=a),r}function g(e){return E.tabReplace&&(e=e.replace(/^((<[^>]+>|\t)+)/gm,function(e,n){return n.replace(/\t/g,E.tabReplace)})),E.useBR&&(e=e.replace(/\n/g,"
")),e}function h(e,n,t){var r=n?R[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function p(e){var n=i(e);if(!a(n)){var t;E.useBR?(t=document.createElementNS("http://www.w3.org/1999/xhtml","div"),t.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):t=e;var r=t.textContent,o=n?f(n,r,!0):l(r),s=u(t);if(s.length){var p=document.createElementNS("http://www.w3.org/1999/xhtml","div");p.innerHTML=o.value,o.value=c(s,u(p),r)}o.value=g(o.value),e.innerHTML=o.value,e.className=h(e.className,n,o.language),e.result={language:o.language,re:o.r},o.second_best&&(e.second_best={language:o.second_best.language,re:o.second_best.r})}}function d(e){E=o(E,e)}function b(){if(!b.called){b.called=!0;var e=document.querySelectorAll("pre code");Array.prototype.forEach.call(e,p)}}function v(){addEventListener("DOMContentLoaded",b,!1),addEventListener("load",b,!1)}function m(n,t){var r=x[n]=t(e);r.aliases&&r.aliases.forEach(function(e){R[e]=n})}function N(){return Object.keys(x)}function w(e){return e=e.toLowerCase(),x[e]||x[R[e]]}var E={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},x={},R={};return e.highlight=f,e.highlightAuto=l,e.fixMarkup=g,e.highlightBlock=p,e.configure=d,e.initHighlighting=b,e.initHighlightingOnLoad=v,e.registerLanguage=m,e.listLanguages=N,e.getLanguage=w,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e});hljs.registerLanguage("markdown",function(e){return{aliases:["md","mkdown","mkd"],c:[{cN:"header",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"blockquote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"`.+?`"},{b:"^( {4}| )",e:"$",r:0}]},{cN:"horizontal_rule",b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].*?[\\)\\]]",rB:!0,c:[{cN:"link_label",b:"\\[",e:"\\]",eB:!0,rE:!0,r:0},{cN:"link_url",b:"\\]\\(",e:"\\)",eB:!0,eE:!0},{cN:"link_reference",b:"\\]\\[",e:"\\]",eB:!0,eE:!0}],r:10},{b:"^\\[.+\\]:",rB:!0,c:[{cN:"link_reference",b:"\\[",e:"\\]:",eB:!0,eE:!0,starts:{cN:"link_url",e:"$"}}]}]}});hljs.registerLanguage("ruby",function(e){var c="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",r="and false then defined module in return redo if BEGIN retry end for true self when next until do begin unless END rescue nil else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",b={cN:"doctag",b:"@[A-Za-z]+"},a={cN:"value",b:"#<",e:">"},n=[e.C("#","$",{c:[b]}),e.C("^\\=begin","^\\=end",{c:[b],r:10}),e.C("^__END__","\\n$")],s={cN:"subst",b:"#\\{",e:"}",k:r},t={cN:"string",c:[e.BE,s],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/`/,e:/`/},{b:"%[qQwWx]?\\(",e:"\\)"},{b:"%[qQwWx]?\\[",e:"\\]"},{b:"%[qQwWx]?{",e:"}"},{b:"%[qQwWx]?<",e:">"},{b:"%[qQwWx]?/",e:"/"},{b:"%[qQwWx]?%",e:"%"},{b:"%[qQwWx]?-",e:"-"},{b:"%[qQwWx]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/}]},i={cN:"params",b:"\\(",e:"\\)",k:r},d=[t,a,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{cN:"inheritance",b:"<\\s*",c:[{cN:"parent",b:"("+e.IR+"::)?"+e.IR}]}].concat(n)},{cN:"function",bK:"def",e:"$|;",c:[e.inherit(e.TM,{b:c}),i].concat(n)},{cN:"constant",b:"(::)?(\\b[A-Z]\\w*(::)?)+",r:0},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",r:0},{cN:"symbol",b:":",c:[t,{b: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},{cN:"variable",b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{b:"("+e.RSR+")\\s*",c:[a,{cN:"regexp",c:[e.BE,s],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}].concat(n),r:0}].concat(n);s.c=d,i.c=d;var o="[>?]>",l="[\\w#]+\\(\\w+\\):\\d+:\\d+>",u="(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>",N=[{b:/^\s*=>/,cN:"status",starts:{e:"$",c:d}},{cN:"prompt",b:"^("+o+"|"+l+"|"+u+")",starts:{e:"$",c:d}}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:r,c:n.concat(N).concat(d)}});hljs.registerLanguage("makefile",function(e){var a={cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]};return{aliases:["mk","mak"],c:[e.HCM,{b:/^\w+\s*\W*=/,rB:!0,r:0,starts:{cN:"constant",e:/\s*\W*=/,eE:!0,starts:{e:/$/,r:0,c:[a]}}},{cN:"title",b:/^[\w]+:\s*$/},{cN:"phony",b:/^\.PHONY:/,e:/$/,k:".PHONY",l:/[\.\w]+/},{b:/^\t+/,e:/$/,r:0,c:[e.QSM,a]}]}});hljs.registerLanguage("json",function(e){var t={literal:"true false null"},i=[e.QSM,e.CNM],l={cN:"value",e:",",eW:!0,eE:!0,c:i,k:t},c={b:"{",e:"}",c:[{cN:"attribute",b:'\\s*"',e:'"\\s*:\\s*',eB:!0,eE:!0,c:[e.BE],i:"\\n",starts:l}],i:"\\S"},n={b:"\\[",e:"\\]",c:[e.inherit(l,{cN:null})],i:"\\S"};return i.splice(i.length,0,c,n),{c:i,k:t,i:"\\S"}});hljs.registerLanguage("xml",function(t){var s="[A-Za-z0-9\\._:-]+",c={b:/<\?(php)?(?!\w)/,e:/\?>/,sL:"php"},e={eW:!0,i:/]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xsl","plist"],cI:!0,c:[{cN:"doctype",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},t.C("",{r:10}),{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"|$)",e:">",k:{title:"style"},c:[e],starts:{e:"",rE:!0,sL:"css"}},{cN:"tag",b:"|$)",e:">",k:{title:"script"},c:[e],starts:{e:"",rE:!0,sL:["actionscript","javascript","handlebars"]}},c,{cN:"pi",b:/<\?\w+/,e:/\?>/,r:10},{cN:"tag",b:"",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.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:""},{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:""}]}]},{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:"",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:/\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/dkan/modules/contrib/recline/lib/highlight/styles/default.css b/dkan/modules/contrib/recline/lib/highlight/styles/default.css deleted file mode 100644 index eb45fc684..000000000 --- a/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/dkan/modules/contrib/recline/recline.theme.inc b/dkan/modules/contrib/recline/recline.theme.inc index 28599ffae..d855ccbcb 100644 --- a/dkan/modules/contrib/recline/recline.theme.inc +++ b/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/dkan/modules/contrib/search_api/CHANGELOG.txt b/dkan/modules/contrib/search_api/CHANGELOG.txt index 24c120fed..1b61a9a19 100644 --- a/dkan/modules/contrib/search_api/CHANGELOG.txt +++ b/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/dkan/modules/contrib/search_api/contrib/search_api_facetapi/search_api_facetapi.info b/dkan/modules/contrib/search_api/contrib/search_api_facetapi/search_api_facetapi.info index 9c80f6e6e..2fd1c653b 100644 --- a/dkan/modules/contrib/search_api/contrib/search_api_facetapi/search_api_facetapi.info +++ b/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/dkan/modules/contrib/search_api/contrib/search_api_views/README.txt b/dkan/modules/contrib/search_api/contrib/search_api_views/README.txt index b36a7b55b..50cfce1c4 100644 --- a/dkan/modules/contrib/search_api/contrib/search_api_views/README.txt +++ b/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/dkan/modules/contrib/search_api/contrib/search_api_views/includes/handler_filter_date.inc b/dkan/modules/contrib/search_api/contrib/search_api_views/includes/handler_filter_date.inc index c78972459..1259aa0bb 100644 --- a/dkan/modules/contrib/search_api/contrib/search_api_views/includes/handler_filter_date.inc +++ b/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/dkan/modules/contrib/search_api/contrib/search_api_views/includes/handler_filter_numeric.inc b/dkan/modules/contrib/search_api/contrib/search_api_views/includes/handler_filter_numeric.inc new file mode 100644 index 000000000..f29a3e6e5 --- /dev/null +++ b/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/dkan/modules/contrib/search_api/contrib/search_api_views/includes/handler_filter_options.inc b/dkan/modules/contrib/search_api/contrib/search_api_views/includes/handler_filter_options.inc index 3040fb0fb..2184fc855 100644 --- a/dkan/modules/contrib/search_api/contrib/search_api_views/includes/handler_filter_options.inc +++ b/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/dkan/modules/contrib/search_api/contrib/search_api_views/includes/plugin_cache.inc b/dkan/modules/contrib/search_api/contrib/search_api_views/includes/plugin_cache.inc index c6bd41d45..c63aed5e7 100644 --- a/dkan/modules/contrib/search_api/contrib/search_api_views/includes/plugin_cache.inc +++ b/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/dkan/modules/contrib/search_api/contrib/search_api_views/includes/plugin_content_cache.inc b/dkan/modules/contrib/search_api/contrib/search_api_views/includes/plugin_content_cache.inc new file mode 100644 index 000000000..555fe89a1 --- /dev/null +++ b/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/dkan/modules/contrib/search_api/contrib/search_api_views/search_api_views.info b/dkan/modules/contrib/search_api/contrib/search_api_views/search_api_views.info index 82c2f8f39..ee66b2fb1 100644 --- a/dkan/modules/contrib/search_api/contrib/search_api_views/search_api_views.info +++ b/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/dkan/modules/contrib/search_api/contrib/search_api_views/search_api_views.views.inc b/dkan/modules/contrib/search_api/contrib/search_api_views/search_api_views.views.inc index ff52d692f..cc1cf4e52 100644 --- a/dkan/modules/contrib/search_api/contrib/search_api_views/search_api_views.views.inc +++ b/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/dkan/modules/contrib/search_api/includes/callback_add_aggregation.inc b/dkan/modules/contrib/search_api/includes/callback_add_aggregation.inc index 2e744f60f..55ed611c5 100644 --- a/dkan/modules/contrib/search_api/includes/callback_add_aggregation.inc +++ b/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/dkan/modules/contrib/search_api/search_api.admin.inc b/dkan/modules/contrib/search_api/search_api.admin.inc index 3afbaa782..89680dbf9 100644 --- a/dkan/modules/contrib/search_api/search_api.admin.inc +++ b/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.") . '

', '#collapsible' => TRUE, ); + if ($index->server) { + $form['processors']['#description'] .= '

' . 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/dkan/modules/contrib/search_api/search_api.drush.inc b/dkan/modules/contrib/search_api/search_api.drush.inc index 61957b9ae..8c995b17b 100644 --- a/dkan/modules/contrib/search_api/search_api.drush.inc +++ b/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/dkan/modules/contrib/search_api/search_api.info b/dkan/modules/contrib/search_api/search_api.info index 1c85bdadc..201c3db41 100644 --- a/dkan/modules/contrib/search_api/search_api.info +++ b/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/dkan/modules/contrib/search_api/search_api.install b/dkan/modules/contrib/search_api/search_api.install index 5dc268954..61f686d7f 100644 --- a/dkan/modules/contrib/search_api/search_api.install +++ b/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/dkan/modules/contrib/search_api/search_api.module b/dkan/modules/contrib/search_api/search_api.module index 8fe3e2a7c..a509302cd 100644 --- a/dkan/modules/contrib/search_api/search_api.module +++ b/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/dkan/modules/contrib/search_api/tests/search_api_test.info b/dkan/modules/contrib/search_api/tests/search_api_test.info index 5286e5eba..e3a3695da 100644 --- a/dkan/modules/contrib/search_api/tests/search_api_test.info +++ b/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/dkan/modules/contrib/search_api/tests/search_api_test_2.info b/dkan/modules/contrib/search_api/tests/search_api_test_2.info index 72d47e90e..e84483ed2 100644 --- a/dkan/modules/contrib/search_api/tests/search_api_test_2.info +++ b/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/dkan/modules/contrib/search_api_db/CHANGELOG.txt b/dkan/modules/contrib/search_api_db/CHANGELOG.txt index 4b30f594b..da676895c 100644 --- a/dkan/modules/contrib/search_api_db/CHANGELOG.txt +++ b/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/dkan/modules/contrib/search_api_db/PATCHES.txt b/dkan/modules/contrib/search_api_db/PATCHES.txt deleted file mode 100644 index 3a3c534fe..000000000 --- a/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/dkan/modules/contrib/search_api_db/search_api_db.info b/dkan/modules/contrib/search_api_db/search_api_db.info index df593332c..3a2bd8481 100644 --- a/dkan/modules/contrib/search_api_db/search_api_db.info +++ b/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/dkan/modules/contrib/search_api_db/service.inc b/dkan/modules/contrib/search_api_db/service.inc index 609e5d4f1..b98d5b793 100644 --- a/dkan/modules/contrib/search_api_db/service.inc +++ b/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/dkan/modules/contrib/tablefield/README.txt b/dkan/modules/contrib/tablefield/README.txt index d9fcb2a09..7c1fd7a08 100644 --- a/dkan/modules/contrib/tablefield/README.txt +++ b/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/dkan/modules/contrib/tablefield/tablefield.info b/dkan/modules/contrib/tablefield/tablefield.info index 42bc3df66..f6d33a418 100644 --- a/dkan/modules/contrib/tablefield/tablefield.info +++ b/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/dkan/modules/contrib/tablefield/tablefield.install b/dkan/modules/contrib/tablefield/tablefield.install index 4eb31864d..81de25839 100644 --- a/dkan/modules/contrib/tablefield/tablefield.install +++ b/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/dkan/modules/contrib/tablefield/tablefield.module b/dkan/modules/contrib/tablefield/tablefield.module index 1992b13e9..75c42d7bd 100644 --- a/dkan/modules/contrib/tablefield/tablefield.module +++ b/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 = ''; + $export = ''; } // Prepare variables for theme_tablefield(). @@ -1898,7 +1961,7 @@ function theme_tablefield_view($variables) { if (isset($variables['sortable'])) { $theme_variables['sortable'] = $variables['sortable']; } - return '
' + return '
' . theme('tablefield', $theme_variables) . $export . '
'; @@ -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/dkan/modules/contrib/tablefield/themeless/README.txt b/dkan/modules/contrib/tablefield/themeless/README.txt deleted file mode 100644 index 4cd777056..000000000 --- a/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/dkan/modules/contrib/tablefield/themeless/tablefield_themeless.info b/dkan/modules/contrib/tablefield/themeless/tablefield_themeless.info deleted file mode 100644 index 39d3661fb..000000000 --- a/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/dkan/modules/contrib/tablefield/themeless/tablefield_themeless.module b/dkan/modules/contrib/tablefield/themeless/tablefield_themeless.module deleted file mode 100644 index 885dbd0f5..000000000 --- a/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/dkan/modules/contrib/taxonomy_menu/LICENSE.txt b/dkan/modules/contrib/taxonomy_menu/LICENSE.txt old mode 100755 new mode 100644 diff --git a/dkan/modules/contrib/taxonomy_menu/README.txt b/dkan/modules/contrib/taxonomy_menu/README.txt index cb404b39c..83250ab19 100644 --- a/dkan/modules/contrib/taxonomy_menu/README.txt +++ b/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/dkan/modules/contrib/taxonomy_menu/UPGRADE.txt b/dkan/modules/contrib/taxonomy_menu/UPGRADE.txt deleted file mode 100644 index 76391dae8..000000000 --- a/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/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.batch.inc b/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.batch.inc index 1efec70e6..8d534c032 100644 --- a/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.batch.inc +++ b/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/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.database.inc b/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.database.inc index 2f127e35d..aadf46327 100644 --- a/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.database.inc +++ b/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/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.info b/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.info index 295734e23..d0f6be973 100644 --- a/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.info +++ b/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/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.install b/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.install index 53ee938f8..83b76209c 100644 --- a/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.install +++ b/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/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.module b/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.module index 7bcea7124..7bec26f40 100644 --- a/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.module +++ b/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/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.test b/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.test index 89ec8327c..9713b475c 100644 --- a/dkan/modules/contrib/taxonomy_menu/taxonomy_menu.test +++ b/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/dkan/modules/contrib/uuid/uuid.features.inc b/dkan/modules/contrib/uuid/uuid.features.inc index 03b7d55b2..93b418ef0 100644 --- a/dkan/modules/contrib/uuid/uuid.features.inc +++ b/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/dkan/modules/contrib/uuid/uuid.info b/dkan/modules/contrib/uuid/uuid.info index bd1e87359..cefaf2913 100644 --- a/dkan/modules/contrib/uuid/uuid.info +++ b/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/dkan/modules/contrib/uuid/uuid_path/uuid_path.info b/dkan/modules/contrib/uuid/uuid_path/uuid_path.info index 0f4fa2ba4..77ad15c31 100644 --- a/dkan/modules/contrib/uuid/uuid_path/uuid_path.info +++ b/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/dkan/modules/contrib/uuid/uuid_services/uuid_services.admin.inc b/dkan/modules/contrib/uuid/uuid_services/uuid_services.admin.inc index d9afe29b1..28e447cf2 100644 --- a/dkan/modules/contrib/uuid/uuid_services/uuid_services.admin.inc +++ b/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/dkan/modules/contrib/uuid/uuid_services/uuid_services.info b/dkan/modules/contrib/uuid/uuid_services/uuid_services.info index 911f29654..dd1ae423b 100644 --- a/dkan/modules/contrib/uuid/uuid_services/uuid_services.info +++ b/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/dkan/modules/contrib/uuid/uuid_services/uuid_services.install b/dkan/modules/contrib/uuid/uuid_services/uuid_services.install new file mode 100644 index 000000000..1dfeb6ccf --- /dev/null +++ b/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/dkan/modules/contrib/uuid/uuid_services_example/uuid_services_example.info b/dkan/modules/contrib/uuid/uuid_services_example/uuid_services_example.info index 65cd2df1b..5874051f1 100644 --- a/dkan/modules/contrib/uuid/uuid_services_example/uuid_services_example.info +++ b/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/dkan/modules/contrib/workbench_moderation/PATCHES.txt b/dkan/modules/contrib/workbench_moderation/PATCHES.txt new file mode 100644 index 000000000..4870ef67c --- /dev/null +++ b/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/dkan/modules/contrib/workbench_moderation/workbench_moderation.features.inc b/dkan/modules/contrib/workbench_moderation/workbench_moderation.features.inc index 21090f11c..418fa795c 100644 --- a/dkan/modules/contrib/workbench_moderation/workbench_moderation.features.inc +++ b/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/dkan/modules/contrib/workbench_moderation/workbench_moderation.module b/dkan/modules/contrib/workbench_moderation/workbench_moderation.module index c28261b6d..15a81064c 100644 --- a/dkan/modules/contrib/workbench_moderation/workbench_moderation.module +++ b/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/dkan/modules/contrib/workbench_moderation/workbench_moderation.node.inc b/dkan/modules/contrib/workbench_moderation/workbench_moderation.node.inc index 9afba9845..1cdfa699e 100644 --- a/dkan/modules/contrib/workbench_moderation/workbench_moderation.node.inc +++ b/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/dkan/modules/contrib/xautoload/.travis.yml b/dkan/modules/contrib/xautoload/.travis.yml new file mode 100644 index 000000000..23902b4d6 --- /dev/null +++ b/dkan/modules/contrib/xautoload/.travis.yml @@ -0,0 +1,5 @@ +language: php +php: + - 5.5 + - 5.4 + - 5.3 diff --git a/dkan/modules/contrib/date/LICENSE.txt b/dkan/modules/contrib/xautoload/LICENSE.txt similarity index 100% rename from dkan/modules/contrib/date/LICENSE.txt rename to dkan/modules/contrib/xautoload/LICENSE.txt diff --git a/dkan/modules/contrib/xautoload/README.md b/dkan/modules/contrib/xautoload/README.md new file mode 100644 index 000000000..732a04d2a --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/legacy/lib/FinderPlugin/CheckIncludePath.php b/dkan/modules/contrib/xautoload/legacy/lib/FinderPlugin/CheckIncludePath.php new file mode 100644 index 000000000..8b3e1dcb8 --- /dev/null +++ b/dkan/modules/contrib/xautoload/legacy/lib/FinderPlugin/CheckIncludePath.php @@ -0,0 +1,17 @@ +suggestFile_checkIncludePath($path); + } +} diff --git a/dkan/modules/contrib/xautoload/legacy/lib/FinderPlugin/Interface.php b/dkan/modules/contrib/xautoload/legacy/lib/FinderPlugin/Interface.php new file mode 100644 index 000000000..6890b97d2 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/legacy/lib/InjectedAPI/hookXautoload.php b/dkan/modules/contrib/xautoload/legacy/lib/InjectedAPI/hookXautoload.php new file mode 100644 index 000000000..eb9586416 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/lib/Drupal/xautoload/Tests/EnvironmentSnapshotMaker.php b/dkan/modules/contrib/xautoload/lib/Drupal/xautoload/Tests/EnvironmentSnapshotMaker.php new file mode 100644 index 000000000..4355028e5 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/lib/Drupal/xautoload/Tests/XAutoloadWebTestCase.php b/dkan/modules/contrib/xautoload/lib/Drupal/xautoload/Tests/XAutoloadWebTestCase.php new file mode 100644 index 000000000..3d80f4bd4 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/phpunit.xml.dist b/dkan/modules/contrib/xautoload/phpunit.xml.dist new file mode 100644 index 000000000..4acf8cc6d --- /dev/null +++ b/dkan/modules/contrib/xautoload/phpunit.xml.dist @@ -0,0 +1,19 @@ + + + + + + ./tests/src + + + diff --git a/dkan/modules/contrib/xautoload/src/Adapter/ClassFinderAdapter.php b/dkan/modules/contrib/xautoload/src/Adapter/ClassFinderAdapter.php new file mode 100644 index 000000000..e2904337d --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/Adapter/ClassFinderAdapterInterface.php b/dkan/modules/contrib/xautoload/src/Adapter/ClassFinderAdapterInterface.php new file mode 100644 index 000000000..0fdd0d025 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/Adapter/LocalDirectoryAdapter.php b/dkan/modules/contrib/xautoload/src/Adapter/LocalDirectoryAdapter.php new file mode 100644 index 000000000..6949214c4 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/CacheManager/CacheManager.php b/dkan/modules/contrib/xautoload/src/CacheManager/CacheManager.php new file mode 100644 index 000000000..633adb827 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/CacheManager/CacheManagerObserverInterface.php b/dkan/modules/contrib/xautoload/src/CacheManager/CacheManagerObserverInterface.php new file mode 100644 index 000000000..4d13554e0 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/CacheMissObserver/CacheMissObserverInterface.php b/dkan/modules/contrib/xautoload/src/CacheMissObserver/CacheMissObserverInterface.php new file mode 100644 index 000000000..bd1d21973 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/ClassFinder/ClassFinderInterface.php b/dkan/modules/contrib/xautoload/src/ClassFinder/ClassFinderInterface.php new file mode 100644 index 000000000..bb421d3bb --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/ClassFinder/CommonRegistrationInterface.php b/dkan/modules/contrib/xautoload/src/ClassFinder/CommonRegistrationInterface.php new file mode 100644 index 000000000..6582738dd --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/ClassFinder/ExtendedClassFinderInterface.php b/dkan/modules/contrib/xautoload/src/ClassFinder/ExtendedClassFinderInterface.php new file mode 100644 index 000000000..c5a8b4913 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/ClassFinder/GenericPrefixMap.php b/dkan/modules/contrib/xautoload/src/ClassFinder/GenericPrefixMap.php new file mode 100644 index 000000000..9f2e5f604 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/AbstractInjectedApi.php b/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/AbstractInjectedApi.php new file mode 100644 index 000000000..f4aba9643 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/CollectFilesInjectedApi.php b/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/CollectFilesInjectedApi.php new file mode 100644 index 000000000..b62ef29b1 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/FindFileInjectedApi.php b/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/FindFileInjectedApi.php new file mode 100644 index 000000000..67107cd3e --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/InjectedApiInterface.php b/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/InjectedApiInterface.php new file mode 100644 index 000000000..6e836e90e --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/LoadClassInjectedAPI.php b/dkan/modules/contrib/xautoload/src/ClassFinder/InjectedApi/LoadClassInjectedAPI.php new file mode 100644 index 000000000..a233628cb --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/DrupalCoreRegistryPlugin.php b/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/DrupalCoreRegistryPlugin.php new file mode 100644 index 000000000..50b45f033 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/DrupalExtensionNamespaceFinderPlugin.php b/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/DrupalExtensionNamespaceFinderPlugin.php new file mode 100644 index 000000000..5e23d4698 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/DrupalExtensionUnderscoreFinderPlugin.php b/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/DrupalExtensionUnderscoreFinderPlugin.php new file mode 100644 index 000000000..794653658 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/FinderPluginInterface.php b/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/FinderPluginInterface.php new file mode 100644 index 000000000..4803d0997 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/Psr4FinderPlugin.php b/dkan/modules/contrib/xautoload/src/ClassFinder/Plugin/Psr4FinderPlugin.php new file mode 100644 index 000000000..8ecd8b7ea --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/ClassFinder/ProxyClassFinder.php b/dkan/modules/contrib/xautoload/src/ClassFinder/ProxyClassFinder.php new file mode 100644 index 000000000..97ce40681 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/ClassLoader/AbstractCachedClassLoader.php b/dkan/modules/contrib/xautoload/src/ClassLoader/AbstractCachedClassLoader.php new file mode 100644 index 000000000..a8cd44a43 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/ClassLoader/AbstractClassLoader.php b/dkan/modules/contrib/xautoload/src/ClassLoader/AbstractClassLoader.php new file mode 100644 index 000000000..f5b7ca828 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/ClassLoader/AbstractClassLoaderDecorator.php b/dkan/modules/contrib/xautoload/src/ClassLoader/AbstractClassLoaderDecorator.php new file mode 100644 index 000000000..f37741225 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/ClassLoader/AbstractQueuedCachedClassLoader.php b/dkan/modules/contrib/xautoload/src/ClassLoader/AbstractQueuedCachedClassLoader.php new file mode 100644 index 000000000..004c0a331 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/ClassLoader/ApcClassLoader.php b/dkan/modules/contrib/xautoload/src/ClassLoader/ApcClassLoader.php new file mode 100644 index 000000000..ebf57b6c4 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/ClassLoader/ApcuClassLoader.php b/dkan/modules/contrib/xautoload/src/ClassLoader/ApcuClassLoader.php new file mode 100644 index 000000000..53753e6f5 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/ClassLoader/ApcuQueuedCachedClassLoader.php b/dkan/modules/contrib/xautoload/src/ClassLoader/ApcuQueuedCachedClassLoader.php new file mode 100644 index 000000000..dc7cf7145 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/ClassLoader/CacheNotSupportedException.php b/dkan/modules/contrib/xautoload/src/ClassLoader/CacheNotSupportedException.php new file mode 100644 index 000000000..b7623bd42 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/ClassLoader/WinCacheClassLoader.php b/dkan/modules/contrib/xautoload/src/ClassLoader/WinCacheClassLoader.php new file mode 100644 index 000000000..ee8376d5d --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/ClassLoader/XCacheClassLoader.php b/dkan/modules/contrib/xautoload/src/ClassLoader/XCacheClassLoader.php new file mode 100644 index 000000000..a61cb9170 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/DIC/ServiceContainer.php b/dkan/modules/contrib/xautoload/src/DIC/ServiceContainer.php new file mode 100644 index 000000000..83fd0ebee --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/DIC/ServiceContainerInterface.php b/dkan/modules/contrib/xautoload/src/DIC/ServiceContainerInterface.php new file mode 100644 index 000000000..312c4f124 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/DIC/ServiceFactory.php b/dkan/modules/contrib/xautoload/src/DIC/ServiceFactory.php new file mode 100644 index 000000000..8c21899b6 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/DirectoryBehavior/DefaultDirectoryBehavior.php b/dkan/modules/contrib/xautoload/src/DirectoryBehavior/DefaultDirectoryBehavior.php new file mode 100644 index 000000000..4d089b82e --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/Discovery/ClassMapGenerator.php b/dkan/modules/contrib/xautoload/src/Discovery/ClassMapGenerator.php new file mode 100644 index 000000000..213b3e965 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/Discovery/ClassMapGeneratorInterface.php b/dkan/modules/contrib/xautoload/src/Discovery/ClassMapGeneratorInterface.php new file mode 100644 index 000000000..d3547a399 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/Discovery/ComposerJson.php b/dkan/modules/contrib/xautoload/src/Discovery/ComposerJson.php new file mode 100644 index 000000000..6e4e85a70 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/Discovery/ComposerJsonTargetDir.php b/dkan/modules/contrib/xautoload/src/Discovery/ComposerJsonTargetDir.php new file mode 100644 index 000000000..26a452807 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/Discovery/FileInspector.php b/dkan/modules/contrib/xautoload/src/Discovery/FileInspector.php new file mode 100644 index 000000000..f2df02589 --- /dev/null +++ b/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) !== '.+<\?}s', '?>'); + if (FALSE !== $pos && FALSE === strpos(substr($contents, $pos), '])(?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/dkan/modules/contrib/xautoload/src/Discovery/WildcardFileFinder.php b/dkan/modules/contrib/xautoload/src/Discovery/WildcardFileFinder.php new file mode 100644 index 000000000..436386824 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/DrupalSystem/DrupalSystem.php b/dkan/modules/contrib/xautoload/src/DrupalSystem/DrupalSystem.php new file mode 100644 index 000000000..9b8ea60a8 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/DrupalSystem/DrupalSystemInterface.php b/dkan/modules/contrib/xautoload/src/DrupalSystem/DrupalSystemInterface.php new file mode 100644 index 000000000..6a76143a3 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/Libraries/LibrariesInfoAlter.php b/dkan/modules/contrib/xautoload/src/Libraries/LibrariesInfoAlter.php new file mode 100644 index 000000000..dc3340adc --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/Libraries/LibrariesOnInit.php b/dkan/modules/contrib/xautoload/src/Libraries/LibrariesOnInit.php new file mode 100644 index 000000000..a2c3dce1b --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/Libraries/LibrariesPreLoadCallback.php b/dkan/modules/contrib/xautoload/src/Libraries/LibrariesPreLoadCallback.php new file mode 100644 index 000000000..8048bfd60 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/Libraries/LibraryCacheMissObserver.php b/dkan/modules/contrib/xautoload/src/Libraries/LibraryCacheMissObserver.php new file mode 100644 index 000000000..bda0df719 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/Libraries/SerializableClosureWrapper.php b/dkan/modules/contrib/xautoload/src/Libraries/SerializableClosureWrapper.php new file mode 100644 index 000000000..90f8982b9 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/Main.php b/dkan/modules/contrib/xautoload/src/Main.php new file mode 100644 index 000000000..7d9204a08 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/Phases/DrupalCoreRegistryRegistrator.php b/dkan/modules/contrib/xautoload/src/Phases/DrupalCoreRegistryRegistrator.php new file mode 100644 index 000000000..73200f5a1 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/Phases/DrupalPhaseControl.php b/dkan/modules/contrib/xautoload/src/Phases/DrupalPhaseControl.php new file mode 100644 index 000000000..40bc209fa --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/Phases/ExtensionNamespaces.php b/dkan/modules/contrib/xautoload/src/Phases/ExtensionNamespaces.php new file mode 100644 index 000000000..a9a4a1894 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/Phases/HookXautoload.php b/dkan/modules/contrib/xautoload/src/Phases/HookXautoload.php new file mode 100644 index 000000000..2f6215645 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/Phases/HookXautoloadEarly.php b/dkan/modules/contrib/xautoload/src/Phases/HookXautoloadEarly.php new file mode 100644 index 000000000..1306cfbf4 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/src/Phases/PhaseObserverInterface.php b/dkan/modules/contrib/xautoload/src/Phases/PhaseObserverInterface.php new file mode 100644 index 000000000..976fc5b59 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/bootstrap.php b/dkan/modules/contrib/xautoload/tests/bootstrap.php new file mode 100644 index 000000000..7f4770260 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/fixtures/.libraries/ComposerTargetDirTestLib/Foo.php b/dkan/modules/contrib/xautoload/tests/fixtures/.libraries/ComposerTargetDirTestLib/Foo.php new file mode 100644 index 000000000..2b8fedacf --- /dev/null +++ b/dkan/modules/contrib/xautoload/tests/fixtures/.libraries/ComposerTargetDirTestLib/Foo.php @@ -0,0 +1,8 @@ +librariesLoad($name); +} diff --git a/dkan/modules/contrib/xautoload/tests/fixtures/.modules/system/system.module b/dkan/modules/contrib/xautoload/tests/fixtures/.modules/system/system.module new file mode 100644 index 000000000..b3d9bbc7f --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_pearflat/lib/Foo.php b/dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_pearflat/lib/Foo.php new file mode 100644 index 000000000..9890a23ef --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_psr4_src/src/Foo.php b/dkan/modules/contrib/xautoload/tests/fixtures/.modules/testmod_psr4_src/src/Foo.php new file mode 100644 index 000000000..35e1e6a81 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/ClassFinderAdapterTest.php b/dkan/modules/contrib/xautoload/tests/src/ClassFinderAdapterTest.php new file mode 100644 index 000000000..cbb2c1ee3 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/ClassLoaderTest.php b/dkan/modules/contrib/xautoload/tests/src/ClassLoaderTest.php new file mode 100644 index 000000000..b1b88c211 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/ComposerJsonTest.php b/dkan/modules/contrib/xautoload/tests/src/ComposerJsonTest.php new file mode 100644 index 000000000..688a04964 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/DiscoveryTest.php b/dkan/modules/contrib/xautoload/tests/src/DiscoveryTest.php new file mode 100644 index 000000000..9ea0e08ef --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/DrupalBootTest/AbstractDrupalBootTest.php b/dkan/modules/contrib/xautoload/tests/src/DrupalBootTest/AbstractDrupalBootTest.php new file mode 100644 index 000000000..e3552e92c --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/DrupalBootTest/DrupalBootHookTest.php b/dkan/modules/contrib/xautoload/tests/src/DrupalBootTest/DrupalBootHookTest.php new file mode 100644 index 000000000..5b57b696b --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/DrupalBootTest/DrupalBootTest.php b/dkan/modules/contrib/xautoload/tests/src/DrupalBootTest/DrupalBootTest.php new file mode 100644 index 000000000..80b5fe6ed --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/Example/AbstractExampleModules.php b/dkan/modules/contrib/xautoload/tests/src/Example/AbstractExampleModules.php new file mode 100644 index 000000000..172a740cd --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/Example/ExampleModules.php b/dkan/modules/contrib/xautoload/tests/src/Example/ExampleModules.php new file mode 100644 index 000000000..647a19bc4 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/Example/HookTestExampleModules.php b/dkan/modules/contrib/xautoload/tests/src/Example/HookTestExampleModules.php new file mode 100644 index 000000000..59871d244 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/Filesystem/StreamWrapper.php b/dkan/modules/contrib/xautoload/tests/src/Filesystem/StreamWrapper.php new file mode 100644 index 000000000..6eb23e802 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/Filesystem/VirtualFilesystem.php b/dkan/modules/contrib/xautoload/tests/src/Filesystem/VirtualFilesystem.php new file mode 100644 index 000000000..ca8787dc1 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/Util/CallLog.php b/dkan/modules/contrib/xautoload/tests/src/Util/CallLog.php new file mode 100644 index 000000000..72911a7e0 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/Util/HackyLog.php b/dkan/modules/contrib/xautoload/tests/src/Util/HackyLog.php new file mode 100644 index 000000000..63452fbfe --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/Cache.php b/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/Cache.php new file mode 100644 index 000000000..5254d7eb7 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalBootstrap.php b/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalBootstrap.php new file mode 100644 index 000000000..38932f86f --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalComponentContainer.php b/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalComponentContainer.php new file mode 100644 index 000000000..e0d7f6d7a --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalEnvironment.php b/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalEnvironment.php new file mode 100644 index 000000000..bafef37a2 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalGetFilename.php b/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalGetFilename.php new file mode 100644 index 000000000..d50039a27 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalLoad.php b/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalLoad.php new file mode 100644 index 000000000..f6e857bc2 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalStatic.php b/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/DrupalStatic.php new file mode 100644 index 000000000..1747d7a0f --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ExampleModulesInterface.php b/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ExampleModulesInterface.php new file mode 100644 index 000000000..21b2b9e67 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/HookSystem.php b/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/HookSystem.php new file mode 100644 index 000000000..7a0d74097 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/LibrariesInfo.php b/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/LibrariesInfo.php new file mode 100644 index 000000000..9aa0d6100 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/LibrariesLoad.php b/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/LibrariesLoad.php new file mode 100644 index 000000000..d4a5ee891 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleBuildDependencies.php b/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleBuildDependencies.php new file mode 100644 index 000000000..9510d3ea2 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleEnable.php b/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleEnable.php new file mode 100644 index 000000000..0338e2ab2 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleImplements.php b/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleImplements.php new file mode 100644 index 000000000..861614f25 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleList.php b/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/ModuleList.php new file mode 100644 index 000000000..b00e89af1 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/PureFunctions.php b/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/PureFunctions.php new file mode 100644 index 000000000..bb07c8587 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemList.php b/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemList.php new file mode 100644 index 000000000..1814f8eaf --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemListLoader.php b/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemListLoader.php new file mode 100644 index 000000000..21dead08e --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemListReset.php b/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemListReset.php new file mode 100644 index 000000000..e84497475 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemRebuildModuleData.php b/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemRebuildModuleData.php new file mode 100644 index 000000000..b4633e64f --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemTable.php b/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemTable.php new file mode 100644 index 000000000..713a0272a --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemUpdateBootstrapStatus.php b/dkan/modules/contrib/xautoload/tests/src/VirtualDrupal/SystemUpdateBootstrapStatus.php new file mode 100644 index 000000000..84dc6d067 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/test_1/lib/Drupal/xautoload_test_1/ExampleClass.php b/dkan/modules/contrib/xautoload/tests/test_1/lib/Drupal/xautoload_test_1/ExampleClass.php new file mode 100644 index 000000000..3ff9602fd --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/test_2/lib/ExampleClass.php b/dkan/modules/contrib/xautoload/tests/test_2/lib/ExampleClass.php new file mode 100644 index 000000000..2f9ff893b --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/test_3/lib/ExampleClass.php b/dkan/modules/contrib/xautoload/tests/test_3/lib/ExampleClass.php new file mode 100644 index 000000000..d36ed25b3 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/test_4/testlib/src/TestClass.php b/dkan/modules/contrib/xautoload/tests/test_4/testlib/src/TestClass.php new file mode 100644 index 000000000..9acaeec8a --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/tests/test_5/src/FooNamespace/Foo_Class.php b/dkan/modules/contrib/xautoload/tests/test_5/src/FooNamespace/Foo_Class.php new file mode 100644 index 000000000..6c6394005 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/xautoload.early.inc b/dkan/modules/contrib/xautoload/xautoload.early.inc new file mode 100644 index 000000000..4882e8b20 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/xautoload.emulate.inc b/dkan/modules/contrib/xautoload/xautoload.emulate.inc new file mode 100644 index 000000000..4843968b2 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/xautoload.libraries.inc b/dkan/modules/contrib/xautoload/xautoload.libraries.inc new file mode 100644 index 000000000..f6a97fb68 --- /dev/null +++ b/dkan/modules/contrib/xautoload/xautoload.libraries.inc @@ -0,0 +1,15 @@ +librariesInfoAlter->librariesInfoAlter($info); +} diff --git a/dkan/modules/contrib/xautoload/xautoload.module b/dkan/modules/contrib/xautoload/xautoload.module new file mode 100644 index 000000000..c20584408 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/xautoload.system.inc b/dkan/modules/contrib/xautoload/xautoload.system.inc new file mode 100644 index 000000000..b37df33c5 --- /dev/null +++ b/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/dkan/modules/contrib/xautoload/xautoload.ui.inc b/dkan/modules/contrib/xautoload/xautoload.ui.inc new file mode 100644 index 000000000..5f9aca833 --- /dev/null +++ b/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/dkan/modules/dkan/dkan_dataset/dkan_dataset.module b/dkan/modules/dkan/dkan_dataset/dkan_dataset.module index d9cd5c7a2..90a4d7ec8 100644 --- a/dkan/modules/dkan/dkan_dataset/dkan_dataset.module +++ b/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/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.features.field_base.inc b/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.features.field_base.inc index d4f5dba5f..23f39b2e6 100644 --- a/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.features.field_base.inc +++ b/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/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.features.field_instance.inc b/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.features.field_instance.inc index cfa7abb0a..1e4dcdebe 100644 --- a/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.features.field_instance.inc +++ b/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/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.license_field.inc b/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.license_field.inc index befca24cf..034b56637 100644 --- a/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.license_field.inc +++ b/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/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.module b/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.module index 1fb6693ff..8f710a9ff 100644 --- a/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_content_types/dkan_dataset_content_types.module +++ b/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/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_rest_api/dkan_dataset_rest_api.info b/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_rest_api/dkan_dataset_rest_api.info index b651da93e..245315b35 100644 --- a/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_rest_api/dkan_dataset_rest_api.info +++ b/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_rest_api/dkan_dataset_rest_api.info @@ -1,12 +1,12 @@ 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 diff --git a/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_rest_api/dkan_dataset_rest_api.module b/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_rest_api/dkan_dataset_rest_api.module index e6e64c625..fb68fb6dc 100644 --- a/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_rest_api/dkan_dataset_rest_api.module +++ b/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/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_rest_api/dkan_dataset_rest_api.services.inc b/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_rest_api/dkan_dataset_rest_api.services.inc index 23c2f6927..867fb1931 100644 --- a/dkan/modules/dkan/dkan_dataset/modules/dkan_dataset_rest_api/dkan_dataset_rest_api.services.inc +++ b/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/dkan/modules/dkan/dkan_datastore/README.md b/dkan/modules/dkan/dkan_datastore/README.md index affbd7bc7..fab1c84d6 100644 --- a/dkan/modules/dkan/dkan_datastore/README.md +++ b/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/dkan/modules/dkan/dkan_datastore/dkan_datastore.drush.inc b/dkan/modules/dkan/dkan_datastore/dkan_datastore.drush.inc new file mode 100644 index 000000000..31f75e9c5 --- /dev/null +++ b/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/dkan/modules/dkan/dkan_datastore/dkan_datastore.features.inc b/dkan/modules/dkan/dkan_datastore/dkan_datastore.features.inc index b5df1eaf7..5377b837b 100644 --- a/dkan/modules/dkan/dkan_datastore/dkan_datastore.features.inc +++ b/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/dkan/modules/dkan/dkan_datastore/dkan_datastore.feeds_importer_default.inc b/dkan/modules/dkan/dkan_datastore/dkan_datastore.feeds_importer_default.inc deleted file mode 100644 index f1b06acf7..000000000 --- a/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/dkan/modules/dkan/dkan_datastore/dkan_datastore.info b/dkan/modules/dkan/dkan_datastore/dkan_datastore.info index 23796c234..897b05d93 100644 --- a/dkan/modules/dkan/dkan_datastore/dkan_datastore.info +++ b/dkan/modules/dkan/dkan_datastore/dkan_datastore.info @@ -2,23 +2,13 @@ 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 diff --git a/dkan/modules/dkan/dkan_datastore/dkan_datastore.module b/dkan/modules/dkan/dkan_datastore/dkan_datastore.module index 3edd79be7..e0d317387 100644 --- a/dkan/modules/dkan/dkan_datastore/dkan_datastore.module +++ b/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/dkan/modules/dkan/dkan_datastore/dkan_datastore.pages.inc b/dkan/modules/dkan/dkan_datastore/dkan_datastore.pages.inc index 4e1ce69cf..ae73bcec9 100644 --- a/dkan/modules/dkan/dkan_datastore/dkan_datastore.pages.inc +++ b/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/dkan/modules/dkan/dkan_datastore/includes/Datastore.inc b/dkan/modules/dkan/dkan_datastore/includes/Datastore.inc deleted file mode 100644 index 1a75d19e8..000000000 --- a/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/dkan/modules/dkan/dkan_datastore/includes/DkanDatastore.inc b/dkan/modules/dkan/dkan_datastore/includes/DkanDatastore.inc deleted file mode 100644 index a90594a68..000000000 --- a/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/dkan/modules/dkan/dkan_datastore/includes/DkanDatastoreFastImport.inc b/dkan/modules/dkan/dkan_datastore/includes/DkanDatastoreFastImport.inc deleted file mode 100644 index 7e595b502..000000000 --- a/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/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_api/dkan_datastore_api.drush.inc b/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_api/dkan_datastore_api.drush.inc deleted file mode 100644 index 5b172b99f..000000000 --- a/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/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_api/dkan_datastore_api.info b/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_api/dkan_datastore_api.info index 42f503c41..694e02391 100644 --- a/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_api/dkan_datastore_api.info +++ b/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_api/dkan_datastore_api.info @@ -3,3 +3,4 @@ description = Access datastore info over json. package = DKAN API core = 7.x dependencies[] = services +dependencies[] = dkan_datastore diff --git a/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_api/dkan_datastore_api.module b/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_api/dkan_datastore_api.module index 7dc0d355d..9e6e6f038 100644 --- a/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_api/dkan_datastore_api.module +++ b/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/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/dkan_datastore_fast_import.install b/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/dkan_datastore_fast_import.install index 63a1eb923..4e0ae7dea 100644 --- a/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/dkan_datastore_fast_import.install +++ b/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/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/dkan_datastore_fast_import.js b/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/dkan_datastore_fast_import.js deleted file mode 100644 index c2388aa12..000000000 --- a/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/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/dkan_datastore_fast_import.module b/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/dkan_datastore_fast_import.module index 32561ae1d..6df750870 100644 --- a/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/dkan_datastore_fast_import.module +++ b/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/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/src/FastImport.php b/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_fast_import/src/FastImport.php new file mode 100644 index 000000000..7f4cd2833 --- /dev/null +++ b/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/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_simple_import/dkan_datastore_simple_import.info b/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_simple_import/dkan_datastore_simple_import.info new file mode 100644 index 000000000..e448abecc --- /dev/null +++ b/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_simple_import/dkan_datastore_simple_import.info @@ -0,0 +1,5 @@ +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 diff --git a/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_simple_import/dkan_datastore_simple_import.module b/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_simple_import/dkan_datastore_simple_import.module new file mode 100644 index 000000000..943fb73ae --- /dev/null +++ b/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/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_simple_import/src/SimpleImport.php b/dkan/modules/dkan/dkan_datastore/modules/dkan_datastore_simple_import/src/SimpleImport.php new file mode 100644 index 000000000..d724b1c57 --- /dev/null +++ b/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/dkan/modules/dkan/dkan_datastore/resources/dkan_datastore_resource.inc b/dkan/modules/dkan/dkan_datastore/resources/dkan_datastore_resource.inc new file mode 100644 index 000000000..298fa3a0d --- /dev/null +++ b/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/dkan/modules/dkan/dkan_datastore/src/LockableDrupalVariables.php b/dkan/modules/dkan/dkan_datastore/src/LockableDrupalVariables.php new file mode 100644 index 000000000..eb41fbf95 --- /dev/null +++ b/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/dkan/modules/dkan/dkan_datastore/src/Manager/Factory.php b/dkan/modules/dkan/dkan_datastore/src/Manager/Factory.php new file mode 100644 index 000000000..ee17f2609 --- /dev/null +++ b/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/dkan/modules/dkan/dkan_datastore/src/Manager/Info.php b/dkan/modules/dkan/dkan_datastore/src/Manager/Info.php new file mode 100644 index 000000000..50ca0ff8d --- /dev/null +++ b/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/dkan/modules/dkan/dkan_datastore/src/Manager/Manager.php b/dkan/modules/dkan/dkan_datastore/src/Manager/Manager.php new file mode 100644 index 000000000..b245015b1 --- /dev/null +++ b/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/dkan/modules/dkan/dkan_datastore/src/Manager/ManagerInterface.php b/dkan/modules/dkan/dkan_datastore/src/Manager/ManagerInterface.php new file mode 100644 index 000000000..3350d6e7d --- /dev/null +++ b/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/dkan/modules/dkan/dkan_datastore/src/Page/Component/ManagerSelection.php b/dkan/modules/dkan/dkan_datastore/src/Page/Component/ManagerSelection.php new file mode 100644 index 000000000..f406458b2 --- /dev/null +++ b/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/dkan/modules/dkan/dkan_datastore/src/Page/Component/Status.php b/dkan/modules/dkan/dkan_datastore/src/Page/Component/Status.php new file mode 100644 index 000000000..752b54a6f --- /dev/null +++ b/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/dkan/modules/dkan/dkan_datastore/src/Page/Page.php b/dkan/modules/dkan/dkan_datastore/src/Page/Page.php new file mode 100644 index 000000000..496f588f9 --- /dev/null +++ b/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.

'; + $this->form['help'] = [ + '#type' => 'item', + '#markup' => $html, + ]; + + $html = (new Status($manager))->getHtml(); + $this->form['status'] = [ + '#type' => 'item', + '#title' => t('Datastore Status'), + '#markup' => "
{$html}
", + ]; + + $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/dkan/modules/dkan/dkan_datastore/src/Parser/Base.php b/dkan/modules/dkan/dkan_datastore/src/Parser/Base.php new file mode 100644 index 000000000..0b507943f --- /dev/null +++ b/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/dkan/modules/dkan/dkan_datastore/src/Parser/Csv.php b/dkan/modules/dkan/dkan_datastore/src/Parser/Csv.php new file mode 100644 index 000000000..a79c4c73f --- /dev/null +++ b/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/dkan/modules/dkan/dkan_datastore/src/Parser/CsvBase.php b/dkan/modules/dkan/dkan_datastore/src/Parser/CsvBase.php new file mode 100644 index 000000000..94e0f6437 --- /dev/null +++ b/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/dkan/modules/dkan/dkan_datastore/src/Parser/Field.php b/dkan/modules/dkan/dkan_datastore/src/Parser/Field.php new file mode 100644 index 000000000..4d998ae26 --- /dev/null +++ b/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/dkan/modules/dkan/dkan_datastore/src/Parser/QuotedField.php b/dkan/modules/dkan/dkan_datastore/src/Parser/QuotedField.php new file mode 100644 index 000000000..537f51bc7 --- /dev/null +++ b/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/dkan/modules/dkan/dkan_datastore/src/Resource.php b/dkan/modules/dkan/dkan_datastore/src/Resource.php new file mode 100644 index 000000000..92b040f3d --- /dev/null +++ b/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/dkan/modules/dkan/dkan_datastore/src/StateMachine.php b/dkan/modules/dkan/dkan_datastore/src/StateMachine.php new file mode 100644 index 000000000..b958f0ef9 --- /dev/null +++ b/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/dkan/modules/dkan/dkan_environment/dkan_environment.module b/dkan/modules/dkan/dkan_environment/dkan_environment.module index de6fbeb95..2a7e28d55 100644 --- a/dkan/modules/dkan/dkan_environment/dkan_environment.module +++ b/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/dkan/modules/dkan/dkan_fixtures/modules/dkan_default_content/data/files/resource/Polling_Places_Madison_0.csv b/dkan/modules/dkan/dkan_fixtures/modules/dkan_default_content/data/files/resource/Polling_Places_Madison_0.csv deleted file mode 100644 index eacf9fc64..000000000 --- a/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/dkan/modules/dkan/dkan_harvest/dkan_harvest.features.field_base.inc b/dkan/modules/dkan/dkan_harvest/dkan_harvest.features.field_base.inc index 5c9251156..1982e0993 100644 --- a/dkan/modules/dkan/dkan_harvest/dkan_harvest.features.field_base.inc +++ b/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/dkan/modules/dkan/dkan_harvest/dkan_harvest.migrate.inc b/dkan/modules/dkan/dkan_harvest/dkan_harvest.migrate.inc index 340b2b22d..7cd50a596 100644 --- a/dkan/modules/dkan/dkan_harvest/dkan_harvest.migrate.inc +++ b/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/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_dashboard/css/dkan_harvest_dashboard.css b/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_dashboard/css/dkan_harvest_dashboard.css index cd8f420f8..0bc3e9d82 100644 --- a/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_dashboard/css/dkan_harvest_dashboard.css +++ b/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/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_dashboard/dkan_harvest_dashboard.module b/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_dashboard/dkan_harvest_dashboard.module index c22e4a680..b6be00c67 100644 --- a/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_dashboard/dkan_harvest_dashboard.module +++ b/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/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_dashboard/dkan_harvest_dashboard.views_default.inc b/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_dashboard/dkan_harvest_dashboard.views_default.inc index d57cb1a66..d23957b74 100644 --- a/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_dashboard/dkan_harvest_dashboard.views_default.inc +++ b/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/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_datajson/dkan_harvest_datajson.migrate.inc b/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_datajson/dkan_harvest_datajson.migrate.inc index 04bca05cf..6b9111792 100644 --- a/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_datajson/dkan_harvest_datajson.migrate.inc +++ b/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/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_datajson/dkan_harvest_datajson.module b/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_datajson/dkan_harvest_datajson.module index ace36b1e0..1c9fb3639 100644 --- a/dkan/modules/dkan/dkan_harvest/modules/dkan_harvest_datajson/dkan_harvest_datajson.module +++ b/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/dkan/modules/dkan/dkan_migrate_base/README.md b/dkan/modules/dkan/dkan_migrate_base/README.md index c57e21e18..2905a9ee5 100644 --- a/dkan/modules/dkan/dkan_migrate_base/README.md +++ b/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/dkan/modules/dkan/dkan_permissions/dkan_permissions.features.roles_permissions.inc b/dkan/modules/dkan/dkan_permissions/dkan_permissions.features.roles_permissions.inc index 7f7a55070..ef5fc69c7 100644 --- a/dkan/modules/dkan/dkan_permissions/dkan_permissions.features.roles_permissions.inc +++ b/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/dkan/modules/dkan/dkan_sitewide/dkan_sitewide.info b/dkan/modules/dkan/dkan_sitewide/dkan_sitewide.info index 103410138..43a229fe6 100644 --- a/dkan/modules/dkan/dkan_sitewide/dkan_sitewide.info +++ b/dkan/modules/dkan/dkan_sitewide/dkan_sitewide.info @@ -56,4 +56,3 @@ 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 diff --git a/dkan/modules/dkan/dkan_workflow/dkan_workflow.features.workbench_email.inc b/dkan/modules/dkan/dkan_workflow/dkan_workflow.features.workbench_email.inc index 2e2a12ab1..e1ed6a7a6 100644 --- a/dkan/modules/dkan/dkan_workflow/dkan_workflow.features.workbench_email.inc +++ b/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/dkan/modules/dkan/dkan_workflow/dkan_workflow.info b/dkan/modules/dkan/dkan_workflow/dkan_workflow.info index a606f2f3c..f6fee20ed 100644 --- a/dkan/modules/dkan/dkan_workflow/dkan_workflow.info +++ b/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 diff --git a/dkan/modules/dkan/dkan_workflow/dkan_workflow.module b/dkan/modules/dkan/dkan_workflow/dkan_workflow.module index eda2beda8..d8a0dad73 100644 --- a/dkan/modules/dkan/dkan_workflow/dkan_workflow.module +++ b/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/dkan/modules/dkan/open_data_schema_map_dkan/open_data_schema_map_dkan.features.inc b/dkan/modules/dkan/open_data_schema_map_dkan/open_data_schema_map_dkan.features.inc index 8767129ff..59608cf1f 100644 --- a/dkan/modules/dkan/open_data_schema_map_dkan/open_data_schema_map_dkan.features.inc +++ b/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/dkan/test/dkanextension/src/Drupal/DKANExtension/Context/DatasetContext.php b/dkan/test/dkanextension/src/Drupal/DKANExtension/Context/DatasetContext.php index fd5a4b210..7cb1a366d 100644 --- a/dkan/test/dkanextension/src/Drupal/DKANExtension/Context/DatasetContext.php +++ b/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/dkan/test/features/dataset.author.feature b/dkan/test/features/dataset.author.feature index 62142591e..21c18b78a 100644 --- a/dkan/test/features/dataset.author.feature +++ b/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/dkan/test/features/datastore.feature b/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?+afXxreTBwZ+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/dkan/test/features/page.editor.feature b/dkan/test/features/page.sm.feature similarity index 91% rename from dkan/test/features/page.editor.feature rename to dkan/test/features/page.sm.feature index 54fd638c1..a33f7fd1e 100644 --- a/dkan/test/features/page.editor.feature +++ b/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/dkan/test/features/resource.admin.feature b/dkan/test/features/resource.admin.feature index bdbf605b0..5798ef9a7 100644 --- a/dkan/test/features/resource.admin.feature +++ b/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/dkan/test/features/resource.author.feature b/dkan/test/features/resource.author.feature index 53f687734..c6597634a 100644 --- a/dkan/test/features/resource.author.feature +++ b/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/dkan/test/features/resource.editor.feature b/dkan/test/features/resource.editor.feature index bd51cf7ed..ab9c2da2b 100644 --- a/dkan/test/features/resource.editor.feature +++ b/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/dkan/test/features/search.feature b/dkan/test/features/search.feature index 4e5c5d261..33e6ecfe1 100644 --- a/dkan/test/features/search.feature +++ b/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/dkan/test/features/user.editor.feature b/dkan/test/features/user.editor.feature index fb0bf1e63..f2563fad7 100644 --- a/dkan/test/features/user.editor.feature +++ b/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/dkan/test/phpunit/dkan_datastore/DkanDatastoreCsvParserTest.php b/dkan/test/phpunit/dkan_datastore/DkanDatastoreCsvParserTest.php new file mode 100644 index 000000000..1f0d7c2b9 --- /dev/null +++ b/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/dkan/test/phpunit/dkan_datastore/DkanDatastoreTest.php b/dkan/test/phpunit/dkan_datastore/DkanDatastoreTest.php index 35d6f74ed..3fa0154bc 100644 --- a/dkan/test/phpunit/dkan_datastore/DkanDatastoreTest.php +++ b/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/dkan/test/phpunit/dkan_datastore_api/DkanDatastoreAPITest.php b/dkan/test/phpunit/dkan_datastore_api/DkanDatastoreAPITest.php index e7bdd1174..3568188d2 100644 --- a/dkan/test/phpunit/dkan_datastore_api/DkanDatastoreAPITest.php +++ b/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/dkan/test/phpunit/dkan_datastore_fast_import/DkanDatastoreFastImportTest.php b/dkan/test/phpunit/dkan_datastore_fast_import/DkanDatastoreFastImportTest.php deleted file mode 100644 index e6bc74e2e..000000000 --- a/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/dkan/test/phpunit/dkan_datastore_fast_import/files/null_check.csv b/dkan/test/phpunit/dkan_datastore_fast_import/files/null_check.csv deleted file mode 100644 index fb6ea4944..000000000 --- a/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/dkan/test/phpunit/dkan_datastore_fast_import/files/polling_places.csv b/dkan/test/phpunit/dkan_datastore_fast_import/files/polling_places.csv deleted file mode 100644 index eacf9fc64..000000000 --- a/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/dkan/test/phpunit/dkan_harvest/DatajsonHarvestMigrationScenariosTest.php b/dkan/test/phpunit/dkan_harvest/DatajsonHarvestMigrationScenariosTest.php index 992d9c0cd..ca2de4d8e 100644 --- a/dkan/test/phpunit/dkan_harvest/DatajsonHarvestMigrationScenariosTest.php +++ b/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/dkan/test/phpunit/dkan_harvest/DatajsonHarvestMigrationTest.php b/dkan/test/phpunit/dkan_harvest/DatajsonHarvestMigrationTest.php index e5f78cb32..f4398d87f 100644 --- a/dkan/test/phpunit/dkan_harvest/DatajsonHarvestMigrationTest.php +++ b/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/dkan/test/phpunit/phpunit.xml b/dkan/test/phpunit/phpunit.xml index 122b7c00b..7d0738d3e 100644 --- a/dkan/test/phpunit/phpunit.xml +++ b/dkan/test/phpunit/phpunit.xml @@ -28,6 +28,7 @@ dkan_dataset/ApiTest.php + dkan_datastore/DkanDatastoreCSVParserTest.php dkan_datastore/DkanDatastoreTest.php diff --git a/dkan/themes/nuboot_radix/assets/css/nuboot_radix.style.css b/dkan/themes/nuboot_radix/assets/css/nuboot_radix.style.css index a23d0f2c2..9c861da3b 100644 --- a/dkan/themes/nuboot_radix/assets/css/nuboot_radix.style.css +++ b/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/dkan/themes/nuboot_radix/scss/components/_harvest.scss b/dkan/themes/nuboot_radix/scss/components/_harvest.scss index b77ab1b80..68ae25d3a 100644 --- a/dkan/themes/nuboot_radix/scss/components/_harvest.scss +++ b/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.