Further modules included.
authorJeff Veit <jeff.veit@gmail.com>
Fri, 23 Jun 2017 01:05:03 +0000 (02:05 +0100)
committerJeff Veit <jeff.veit@gmail.com>
Fri, 23 Jun 2017 01:05:03 +0000 (02:05 +0100)
731 files changed:
web/modules/contrib/advanced_help [deleted submodule]
web/modules/contrib/advanced_help/README.txt [new file with mode: 0644]
web/modules/contrib/advanced_help/advanced_help.info.yml [new file with mode: 0644]
web/modules/contrib/advanced_help/advanced_help.install [new file with mode: 0644]
web/modules/contrib/advanced_help/advanced_help.libraries.yml [new file with mode: 0644]
web/modules/contrib/advanced_help/advanced_help.links.menu.yml [new file with mode: 0644]
web/modules/contrib/advanced_help/advanced_help.links.task.yml [new file with mode: 0644]
web/modules/contrib/advanced_help/advanced_help.module [new file with mode: 0644]
web/modules/contrib/advanced_help/advanced_help.permissions.yml [new file with mode: 0644]
web/modules/contrib/advanced_help/advanced_help.routing.yml [new file with mode: 0644]
web/modules/contrib/advanced_help/advanced_help.services.yml [new file with mode: 0644]
web/modules/contrib/advanced_help/composer.json [new file with mode: 0644]
web/modules/contrib/advanced_help/css/help-icon.css [new file with mode: 0644]
web/modules/contrib/advanced_help/css/help.css [new file with mode: 0644]
web/modules/contrib/advanced_help/help/advanced_help.help.yml [new file with mode: 0644]
web/modules/contrib/advanced_help/help/ahelp_tab.png [new file with mode: 0755]
web/modules/contrib/advanced_help/help/click_icon.png [new file with mode: 0644]
web/modules/contrib/advanced_help/help/ini-file.html [new file with mode: 0644]
web/modules/contrib/advanced_help/help/readme.html [new file with mode: 0644]
web/modules/contrib/advanced_help/help/translation.html [new file with mode: 0644]
web/modules/contrib/advanced_help/help/using-advanced-help.html [new file with mode: 0644]
web/modules/contrib/advanced_help/help/why-advanced-help.html [new file with mode: 0644]
web/modules/contrib/advanced_help/help_example/help/180px-Andi_Gutmans_1.jpg [new file with mode: 0644]
web/modules/contrib/advanced_help/help_example/help/180px-Lerdorf.jpg [new file with mode: 0644]
web/modules/contrib/advanced_help/help_example/help/180px-PHP_Hello_World_screenshot.png [new file with mode: 0644]
web/modules/contrib/advanced_help/help_example/help/about-php.html [new file with mode: 0644]
web/modules/contrib/advanced_help/help_example/help/help_example.help.ini [new file with mode: 0644]
web/modules/contrib/advanced_help/help_example/help/history.html [new file with mode: 0644]
web/modules/contrib/advanced_help/help_example/help/security.html [new file with mode: 0644]
web/modules/contrib/advanced_help/help_example/help/syntax.html [new file with mode: 0644]
web/modules/contrib/advanced_help/help_example/help/usage.html [new file with mode: 0644]
web/modules/contrib/advanced_help/help_example/help_example.info [new file with mode: 0644]
web/modules/contrib/advanced_help/help_example/help_example.module [new file with mode: 0644]
web/modules/contrib/advanced_help/images/help.png [new file with mode: 0644]
web/modules/contrib/advanced_help/src/AdvancedHelpManager.php [new file with mode: 0644]
web/modules/contrib/advanced_help/src/Controller/AdvancedHelpController.php [new file with mode: 0644]
web/modules/contrib/advanced_help/src/HelpInterface.php [new file with mode: 0644]
web/modules/contrib/advanced_help/src/Plugin/Derivative/DynamicLocalTasks.php [new file with mode: 0644]
web/modules/contrib/advanced_help/src/Plugin/Search/AdvancedHelpSearch.php [new file with mode: 0644]
web/modules/contrib/advanced_help/templates/advanced-help-topic.html.twig [new file with mode: 0644]
web/modules/contrib/better_formats [deleted submodule]
web/modules/contrib/better_formats/README.txt [new file with mode: 0644]
web/modules/contrib/better_formats/better_formats.info.yml [new file with mode: 0644]
web/modules/contrib/better_formats/better_formats.install [new file with mode: 0644]
web/modules/contrib/better_formats/better_formats.links.task.yml [new file with mode: 0644]
web/modules/contrib/better_formats/better_formats.module [new file with mode: 0644]
web/modules/contrib/better_formats/better_formats.permissions.yml [new file with mode: 0644]
web/modules/contrib/better_formats/better_formats.routing.yml [new file with mode: 0644]
web/modules/contrib/better_formats/composer.json [new file with mode: 0644]
web/modules/contrib/better_formats/config/install/better_formats.settings.yml [new file with mode: 0644]
web/modules/contrib/better_formats/config/schema/better_formats.schema.yml [new file with mode: 0644]
web/modules/contrib/better_formats/src/BetterFormatsPermissions.php [new file with mode: 0644]
web/modules/contrib/better_formats/src/Form/SettingsForm.php [new file with mode: 0644]
web/modules/contrib/better_formats/src/Tests/BetterFormatsFilterFormatAccessTest.php [new file with mode: 0644]
web/modules/contrib/better_formats/src/Tests/BetterFormatsTermTest.php [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets [deleted submodule]
web/modules/contrib/ckeditor_widgets/CHANGELOG.txt [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/README.md [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/ckeditor_widgets.info.yml [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/contents.css [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/dialogs/widgetbootstrapAlert.js [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/dialogs/widgetfoundationAccordion.js [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapAccordion.png [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapAlert.png [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapLeftCol.png [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapRightCol.png [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapThreeCol.png [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapTwoCol.png [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/plugin.js [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/samples/index.html [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/samples/simplebox.html [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/contents.css [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/dialogs/foundation2Col.js [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/dialogs/widgetfoundationAccordion.js [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/icons/widgetcommonBox.png [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/icons/widgetcommonQuotebox.png [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/plugin.js [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/samples/contents.css [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/samples/simplebox.html [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/extraIcons/codesnippet.png [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/extraIcons/drupalbreak.png [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/extraIcons/fontawesome.png [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/extraIcons/leaflet.png [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/extraIcons/oembed.png [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/icons/widgettemplatemenu.png [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/plugin.js [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/samples/contents.css [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/samples/simplebox.html [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/src/Plugin/CKEditorPlugin/WidgetBootstrap.php [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/src/Plugin/CKEditorPlugin/WidgetCommon.php [new file with mode: 0644]
web/modules/contrib/ckeditor_widgets/src/Plugin/CKEditorPlugin/WidgetTemplateMenu.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader [deleted submodule]
web/modules/contrib/drupalmoduleupgrader/.gitignore [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/.gitmodules [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/README.txt [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/composer.json [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/composer.lock [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.entity_operations.yml [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.functions.yml [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.grep.yml [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.hooks.yml [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.rewriters.yml [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.tags.yml [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/drupalmoduleupgrader.drush.inc [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/drupalmoduleupgrader.info.yml [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/drupalmoduleupgrader.module [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/drupalmoduleupgrader.services.yml [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/phpunit.xml.dist [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/AnalyzerBase.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/AnalyzerInterface.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Annotation/Analyzer.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Annotation/Converter.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Annotation/Fixer.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Annotation/Indexer.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Annotation/Rewriter.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/ArrayIndexer.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/ConverterBase.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/ConverterInterface.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/DependencyCollectorTrait.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/DeriverBase.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/FixerBase.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/FixerInterface.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/IOException.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/IndexerBase.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/IndexerExecutionInterface.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/IndexerInterface.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/IndexerUsageInterface.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Issue.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/IssueInterface.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/DB.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/DBDeriver.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/FlagHook.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/FlagHookDeriver.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/FunctionCall.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/FunctionCallDeriver.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/Grep.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/HookFormAlter.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/HookPermission.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/HookUninstall.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/InfoFile.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/PSR4.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/Tests.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Blocks.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/EntityHooks.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CToolsGetPlugins.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CToolsObjectCacheGet.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CToolsObjectCacheSet.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CacheGet.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CacheSet.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CommentLoad.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DB.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DBDeriver.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/Disable.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DisableDeriver.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DrupalGetTitle.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DrupalIsCLI.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DrupalMapAssoc.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DrupalWriteRecord.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/EntityCreate.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/EntityGetInfo.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/EntityLoad.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/EntityOperation.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/EntityOperationDeriver.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldInfoFieldTypes.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldInfoFormatterTypes.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldInfoWidgetTypes.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldUpdateField.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldUpdateInstance.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldViewField.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldViewValue.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FormExecuteHandlers.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FormLoadInclude.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FormSetValue.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FormStateDefaults.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FormStateValuesClean.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FunctionCallModifier.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/GetT.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/L.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/LoadMultiple.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/LoadMultipleDeriver.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/ModuleInvoke.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/ModuleInvokeAll.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/NodeLoad.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/St.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/ThemeGetRegistry.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/URL.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/UserAccess.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/UserLoad.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/UserSave.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/VariableAPI.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/VariableDel.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/VariableGet.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/VariableSet.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/Watchdog.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Grep.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookBoot.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookEntityInfo.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookEntityTypeView.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookExit.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFieldAttachCreateBundle.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFieldAttachDeleteBundle.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFieldAttachRenameBundle.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFieldFormatterInfo.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFieldWidgetInfo.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFormAlter.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookInit.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookLibrary.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookMenuAlter.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookNodePrepare.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookPermission.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookURLOutboundAlter.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookUserLogin.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookWatchdog.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/InfoToYAML.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Links.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/PSR4.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Routing.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Tests.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/UnitTests.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/UserHooks.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/CreateClass.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/Define.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/Delete.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/Disable.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/FormCallbackToMethod.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/HookToYAML.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/Implement.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/ImplementHook.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/NodeCollectorTrait.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/Notify.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/PSR4.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Indexer/Classes.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Indexer/Constants.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Indexer/FunctionCalls.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Indexer/Functions.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Rewriter/FormState.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Rewriter/Generic.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Rewriter/GenericDeriver.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Routing/ContentRoute.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Routing/FormRoute.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/PluginBase.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Report.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/ReportInterface.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/RewriterInterface.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Routing/Drupal7/RouteWrapper.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Routing/Drupal7/Router.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Routing/Drupal8/RouteWrapper.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Routing/HookMenu.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkBinding/LinkBinding.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkBinding/LinkBindingFactory.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkBinding/LocalActionLinkBinding.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkBinding/LocalTaskLinkBinding.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkBinding/MenuLinkBinding.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkIndex.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Routing/ParameterBinding.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Routing/ParameterMap.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Routing/RouteConverterInterface.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Routing/RouteWrapperInterface.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Routing/RouterBase.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Routing/RouterBuiltEvent.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Routing/RouterInterface.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Target.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/TargetInterface.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Utility/Filter/ContainsLogicFilter.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Utility/Filter/FieldValueFilter.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Utility/Filter/FunctionCallArgumentFilter.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Utility/Filter/NodeAssignmentFilter.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Utility/FormConverter.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Utility/FormConverterFactory.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/Drupal7/PathComponent.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/Drupal7/PathUtility.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/Drupal8/PathComponent.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/Drupal8/PathUtility.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/PathComponentBase.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/PathComponentInterface.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/PathUtilityBase.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/PathUtilityInterface.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/src/Utility/StringTransformTrait.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/templates/Block.html.twig [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/templates/Controller.html.twig [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/templates/EntityType.html.twig [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/templates/EventSubscriber.html.twig [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/templates/Form.html.twig [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/templates/Formatter.html.twig [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/templates/Issue.html.twig [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/templates/Logger.html.twig [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/templates/OutboundPathProcessor.html.twig [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/templates/Report.html.twig [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/templates/RouteSubscriber.html.twig [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/templates/Widget.html.twig [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/bootstrap.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/ContainerMockTrait.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/IssueTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/ModuleMockerTrait.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/AnalyzerTestBase.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/DBTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/FlagHookTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/FunctionCallTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/HookFormAlterTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/HookPermissionTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/HookUninstallTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/InfoFileTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/PSR4Test.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/TestsTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CToolsGetPluginsTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CToolsObjectCacheGetTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CToolsObjectCacheSetTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CacheGetTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CacheSetTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CommentLoadTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DBTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DisableTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DrupalGetTitleTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DrupalIsCLITest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DrupalMapAssocTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DrupalWriteRecordTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/EntityCreateTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/EntityGetInfoTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldInfoFieldTypesTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldInfoFormatterTypesTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldInfoWidgetTypesTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldUpdateFieldTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldUpdateInstanceTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldViewFieldTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldViewValueTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FormExecuteHandlersTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FormLoadIncludeTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FormSetValueTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FormStateValuesCleanTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FunctionCallModifierTestBase.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/GetTTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/ModuleInvokeAllTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/ModuleInvokeTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/NodeLoadTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/StTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/ThemeGetRegistryTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/UserAccessTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/UserLoadTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/UserSaveTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/VariableDelTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/VariableGetTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/VariableSetTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/WatchdogTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/CreateClassTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/DefineTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/DeleteTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/DisableTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/FormCallbackToMethodTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/HookToYAMLTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/ImplementHookTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/ImplementTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/NotifyTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/PSR4Test.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Indexer/ClassesTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Indexer/FunctionsTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Indexer/IndexerTestBase.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Rewriter/FormStateTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Rewriter/GenericTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/ReportTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/Drupal7/RouteWrapperTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/Drupal7/RouterTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/Drupal8/RouteWrapperTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/LinkBinding/LinkBindingTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/ParameterBindingTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/RouterBaseTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/SQLiteDatabaseTrait.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/TargetTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/TestBase.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Filter/ContainsLogicFilterTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Filter/FieldValueFilterTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Filter/FunctionCallArgumentFilterTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Filter/NodeAssignmentFilterTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Path/Drupal7/PathComponentTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Path/Drupal7/PathUtilityTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Path/Drupal8/PathComponentTest.php [new file with mode: 0644]
web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Path/Drupal8/PathUtilityTest.php [new file with mode: 0644]
web/modules/contrib/filefield_sources [deleted submodule]
web/modules/contrib/filefield_sources/README.txt [new file with mode: 0644]
web/modules/contrib/filefield_sources/config/schema/filefield_sources.data_types.schema.yml [new file with mode: 0644]
web/modules/contrib/filefield_sources/config/schema/filefield_sources.schema.yml [new file with mode: 0644]
web/modules/contrib/filefield_sources/config/schema/filefield_sources.setting.source_attach.schema.yml [new file with mode: 0644]
web/modules/contrib/filefield_sources/config/schema/filefield_sources.setting.source_imce.schema.yml [new file with mode: 0644]
web/modules/contrib/filefield_sources/config/schema/filefield_sources.setting.source_reference.schema.yml [new file with mode: 0644]
web/modules/contrib/filefield_sources/config/schema/filefield_sources.setting.sources.schema.yml [new file with mode: 0644]
web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.attach.schema.yml [new file with mode: 0644]
web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.clipboard.schema.yml [new file with mode: 0644]
web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.imce.schema.yml [new file with mode: 0644]
web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.reference.schema.yml [new file with mode: 0644]
web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.remote.schema.yml [new file with mode: 0644]
web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.upload.schema.yml [new file with mode: 0644]
web/modules/contrib/filefield_sources/css/filefield_sources.css [new file with mode: 0644]
web/modules/contrib/filefield_sources/filefield_sources.api.php [new file with mode: 0644]
web/modules/contrib/filefield_sources/filefield_sources.info.yml [new file with mode: 0644]
web/modules/contrib/filefield_sources/filefield_sources.install [new file with mode: 0644]
web/modules/contrib/filefield_sources/filefield_sources.libraries.yml [new file with mode: 0644]
web/modules/contrib/filefield_sources/filefield_sources.module [new file with mode: 0644]
web/modules/contrib/filefield_sources/filefield_sources.routing.yml [new file with mode: 0644]
web/modules/contrib/filefield_sources/filefield_sources.services.yml [new file with mode: 0644]
web/modules/contrib/filefield_sources/js/filefield_sources.js [new file with mode: 0644]
web/modules/contrib/filefield_sources/src/Access/FieldAccessCheck.php [new file with mode: 0644]
web/modules/contrib/filefield_sources/src/Annotation/FilefieldSource.php [new file with mode: 0644]
web/modules/contrib/filefield_sources/src/Controller/ImceController.php [new file with mode: 0644]
web/modules/contrib/filefield_sources/src/File/MimeType/ExtensionMimeTypeGuesser.php [new file with mode: 0644]
web/modules/contrib/filefield_sources/src/FilefieldSourceInterface.php [new file with mode: 0644]
web/modules/contrib/filefield_sources/src/FilefieldSourceManager.php [new file with mode: 0644]
web/modules/contrib/filefield_sources/src/FilefieldSourcesServiceProvider.php [new file with mode: 0644]
web/modules/contrib/filefield_sources/src/ImceScanner.php [new file with mode: 0644]
web/modules/contrib/filefield_sources/src/Plugin/FilefieldSource/Attach.php [new file with mode: 0644]
web/modules/contrib/filefield_sources/src/Plugin/FilefieldSource/Clipboard.php [new file with mode: 0644]
web/modules/contrib/filefield_sources/src/Plugin/FilefieldSource/Imce.php [new file with mode: 0644]
web/modules/contrib/filefield_sources/src/Plugin/FilefieldSource/Reference.php [new file with mode: 0644]
web/modules/contrib/filefield_sources/src/Plugin/FilefieldSource/Remote.php [new file with mode: 0644]
web/modules/contrib/filefield_sources/src/ProxyClass/File/MimeType/ExtensionMimeTypeGuesser.php [new file with mode: 0644]
web/modules/contrib/filefield_sources/src/Routing/FilefieldSourcesRoutes.php [new file with mode: 0644]
web/modules/contrib/filefield_sources/src/Tests/AttachSourceTest.php [new file with mode: 0644]
web/modules/contrib/filefield_sources/src/Tests/ClipboardSourceTest.php [new file with mode: 0644]
web/modules/contrib/filefield_sources/src/Tests/EmptyValuesTest.php [new file with mode: 0644]
web/modules/contrib/filefield_sources/src/Tests/FileFieldSourcesTestBase.php [new file with mode: 0644]
web/modules/contrib/filefield_sources/src/Tests/ImceSourceTest.php [new file with mode: 0644]
web/modules/contrib/filefield_sources/src/Tests/MultipleValuesTest.php [new file with mode: 0644]
web/modules/contrib/filefield_sources/src/Tests/ReferenceSourceTest.php [new file with mode: 0644]
web/modules/contrib/filefield_sources/src/Tests/RemoteSourceTest.php [new file with mode: 0644]
web/modules/contrib/filefield_sources/src/Tests/UploadSourceTest.php [new file with mode: 0644]
web/modules/contrib/libraries [deleted submodule]
web/modules/contrib/libraries/CHANGELOG.txt [new file with mode: 0644]
web/modules/contrib/libraries/README.txt [new file with mode: 0644]
web/modules/contrib/libraries/composer.json [new file with mode: 0644]
web/modules/contrib/libraries/config/install/libraries.settings.yml [new file with mode: 0644]
web/modules/contrib/libraries/config/schema/libraries.schema.yml [new file with mode: 0644]
web/modules/contrib/libraries/libraries.api.php [new file with mode: 0644]
web/modules/contrib/libraries/libraries.drush.inc [new file with mode: 0644]
web/modules/contrib/libraries/libraries.info.yml [new file with mode: 0644]
web/modules/contrib/libraries/libraries.install [new file with mode: 0644]
web/modules/contrib/libraries/libraries.module [new file with mode: 0644]
web/modules/contrib/libraries/libraries.services.yml [new file with mode: 0644]
web/modules/contrib/libraries/src/Annotation/LibraryType.php [new file with mode: 0644]
web/modules/contrib/libraries/src/Annotation/Locator.php [new file with mode: 0644]
web/modules/contrib/libraries/src/Annotation/VersionDetector.php [new file with mode: 0644]
web/modules/contrib/libraries/src/Config/LibrariesConfigSubscriber.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Asset/AssetLibrary.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Asset/AssetLibraryInterface.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Asset/AttachableAssetLibraryRegistrationInterface.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Asset/LocalRemoteAssetTrait.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Asset/MultipleAssetLibrary.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Asset/MultipleAssetLibraryInterface.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Definition/ChainDefinitionDiscovery.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Definition/DefinitionDiscoveryFactory.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Definition/DefinitionDiscoveryInterface.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Definition/FileDefinitionDiscovery.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Definition/FileDefinitionDiscoveryBase.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Definition/GuzzleDefinitionDiscovery.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Definition/WritableDefinitionDiscoveryInterface.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Definition/WritableFileDefinitionDiscovery.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Dependency/DependentLibraryInterface.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Dependency/DependentLibraryTrait.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Exception/InvalidLibraryDependencyException.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Exception/LibraryDefinitionNotFoundException.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Exception/LibraryNotInstalledException.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Exception/LibraryTypeNotFoundException.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Exception/UnknownLibraryVersionException.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/LibraryBase.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/LibraryInterface.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/LibraryManager.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/LibraryManagerInterface.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Local/LocalLibraryInterface.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Local/LocalLibraryTrait.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Local/LocatorInterface.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Local/LocatorManager.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/PhpFile/PhpFileLibrary.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/PhpFile/PhpFileLibraryInterface.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/PhpFile/PhpFileLoaderInterface.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/PhpFile/PhpRequireLoader.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Remote/RemoteLibraryInterface.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Remote/RemoteLibraryTrait.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Type/LibraryCreationListenerInterface.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Type/LibraryLoadingListenerInterface.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Type/LibraryTypeBase.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Type/LibraryTypeFactory.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Type/LibraryTypeInterface.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Utility/DependencyAccessorTrait.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Utility/IdAccessorTrait.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Utility/LibraryAccessorInterface.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Utility/LibraryAccessorTrait.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Utility/LibraryIdAccessorInterface.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Utility/LibraryIdAccessorTrait.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Version/VersionDetectorInterface.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Version/VersionDetectorManager.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Version/VersionedLibraryInterface.php [new file with mode: 0644]
web/modules/contrib/libraries/src/ExternalLibrary/Version/VersionedLibraryTrait.php [new file with mode: 0644]
web/modules/contrib/libraries/src/Plugin/MissingPluginConfigurationException.php [new file with mode: 0644]
web/modules/contrib/libraries/src/Plugin/libraries/Locator/ChainLocator.php [new file with mode: 0644]
web/modules/contrib/libraries/src/Plugin/libraries/Locator/GlobalLocator.php [new file with mode: 0644]
web/modules/contrib/libraries/src/Plugin/libraries/Locator/UriLocator.php [new file with mode: 0644]
web/modules/contrib/libraries/src/Plugin/libraries/Type/AssetLibraryType.php [new file with mode: 0644]
web/modules/contrib/libraries/src/Plugin/libraries/Type/MultipleAssetLibraryType.php [new file with mode: 0644]
web/modules/contrib/libraries/src/Plugin/libraries/Type/PhpFileLibraryType.php [new file with mode: 0644]
web/modules/contrib/libraries/src/Plugin/libraries/VersionDetector/LinePatternDetector.php [new file with mode: 0644]
web/modules/contrib/libraries/src/Plugin/libraries/VersionDetector/StaticDetector.php [new file with mode: 0644]
web/modules/contrib/libraries/src/StreamWrapper/AssetLibrariesStream.php [new file with mode: 0644]
web/modules/contrib/libraries/src/StreamWrapper/LibraryDefinitionsStream.php [new file with mode: 0644]
web/modules/contrib/libraries/src/StreamWrapper/LocalHiddenStreamTrait.php [new file with mode: 0644]
web/modules/contrib/libraries/src/StreamWrapper/PhpFileLibrariesStream.php [new file with mode: 0644]
web/modules/contrib/libraries/src/StreamWrapper/PrivateStreamTrait.php [new file with mode: 0644]
web/modules/contrib/libraries/src/Tests/LibrariesUnitTest.php [new file with mode: 0644]
web/modules/contrib/libraries/src/Tests/LibrariesWebTest.php [new file with mode: 0644]
web/modules/contrib/libraries/tests/assets/vendor/test_asset_library/example.css [new file with mode: 0644]
web/modules/contrib/libraries/tests/assets/vendor/test_asset_library/example.js [new file with mode: 0644]
web/modules/contrib/libraries/tests/assets/vendor/test_asset_multiple_library/example.first.css [new file with mode: 0644]
web/modules/contrib/libraries/tests/assets/vendor/test_asset_multiple_library/example.first.js [new file with mode: 0644]
web/modules/contrib/libraries/tests/assets/vendor/test_asset_multiple_library/example.second.css [new file with mode: 0644]
web/modules/contrib/libraries/tests/assets/vendor/test_asset_multiple_library/example.second.js [new file with mode: 0644]
web/modules/contrib/libraries/tests/example/README.txt [new file with mode: 0644]
web/modules/contrib/libraries/tests/example/example_1.css [new file with mode: 0644]
web/modules/contrib/libraries/tests/example/example_1.js [new file with mode: 0644]
web/modules/contrib/libraries/tests/example/example_1.php [new file with mode: 0644]
web/modules/contrib/libraries/tests/example/example_2.css [new file with mode: 0644]
web/modules/contrib/libraries/tests/example/example_2.js [new file with mode: 0644]
web/modules/contrib/libraries/tests/example/example_2.php [new file with mode: 0644]
web/modules/contrib/libraries/tests/example/example_3.css [new file with mode: 0644]
web/modules/contrib/libraries/tests/example/example_3.js [new file with mode: 0644]
web/modules/contrib/libraries/tests/example/example_3.php [new file with mode: 0644]
web/modules/contrib/libraries/tests/example/example_4.css [new file with mode: 0644]
web/modules/contrib/libraries/tests/example/example_4.js [new file with mode: 0644]
web/modules/contrib/libraries/tests/example/example_4.php [new file with mode: 0644]
web/modules/contrib/libraries/tests/example/example_info_file.libraries.info.yml [new file with mode: 0644]
web/modules/contrib/libraries/tests/libraries/test_php_file_library/test_php_file_library.php [new file with mode: 0644]
web/modules/contrib/libraries/tests/library_definitions/test_asset_library.json [new file with mode: 0644]
web/modules/contrib/libraries/tests/library_definitions/test_asset_multiple_library.json [new file with mode: 0644]
web/modules/contrib/libraries/tests/library_definitions/test_php_file_library.json [new file with mode: 0644]
web/modules/contrib/libraries/tests/modules/libraries_test/libraries_test.css [new file with mode: 0644]
web/modules/contrib/libraries/tests/modules/libraries_test/libraries_test.inc [new file with mode: 0644]
web/modules/contrib/libraries/tests/modules/libraries_test/libraries_test.info.yml [new file with mode: 0644]
web/modules/contrib/libraries/tests/modules/libraries_test/libraries_test.js [new file with mode: 0644]
web/modules/contrib/libraries/tests/modules/libraries_test/libraries_test.module [new file with mode: 0644]
web/modules/contrib/libraries/tests/modules/libraries_test/libraries_test.routing.yml [new file with mode: 0644]
web/modules/contrib/libraries/tests/modules/libraries_test/src/Controller/ExampleController.php [new file with mode: 0644]
web/modules/contrib/libraries/tests/src/Functional/ExternalLibrary/Definition/DefinitionDiscoveryFactoryTest.php [new file with mode: 0644]
web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTest.php [new file with mode: 0644]
web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTestBase.php [new file with mode: 0644]
web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/Asset/MultipleAssetLibraryTest.php [new file with mode: 0644]
web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/GlobalLocatorTest.php [new file with mode: 0644]
web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/PhpFile/PhpFileLibraryTest.php [new file with mode: 0644]
web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/TestLibraryFilesStream.php [new file with mode: 0644]
web/modules/contrib/libraries/tests/src/Kernel/LibraryTypeKernelTestBase.php [new file with mode: 0644]
web/modules/contrib/libraries/tests/src/Unit/Plugin/libraries/VersionDetector/LinePatternDetectorTest.php [new file with mode: 0644]
web/modules/contrib/linkchecker [deleted submodule]
web/modules/contrib/linkchecker/CHANGELOG.txt [new file with mode: 0644]
web/modules/contrib/linkchecker/README.txt [new file with mode: 0644]
web/modules/contrib/linkchecker/composer.json [new file with mode: 0644]
web/modules/contrib/linkchecker/config/install/linkchecker.settings.yml [new file with mode: 0644]
web/modules/contrib/linkchecker/config/schema/linkchecker.schema.yml [new file with mode: 0644]
web/modules/contrib/linkchecker/js/linkchecker.content_types.js [new file with mode: 0644]
web/modules/contrib/linkchecker/linkchecker.batch.inc [new file with mode: 0644]
web/modules/contrib/linkchecker/linkchecker.drush.inc [new file with mode: 0644]
web/modules/contrib/linkchecker/linkchecker.info.yml [new file with mode: 0644]
web/modules/contrib/linkchecker/linkchecker.install [new file with mode: 0644]
web/modules/contrib/linkchecker/linkchecker.libraries.yml [new file with mode: 0644]
web/modules/contrib/linkchecker/linkchecker.links.menu.yml [new file with mode: 0644]
web/modules/contrib/linkchecker/linkchecker.links.task.yml [new file with mode: 0644]
web/modules/contrib/linkchecker/linkchecker.module [new file with mode: 0644]
web/modules/contrib/linkchecker/linkchecker.pages.inc [new file with mode: 0644]
web/modules/contrib/linkchecker/linkchecker.permissions.yml [new file with mode: 0644]
web/modules/contrib/linkchecker/linkchecker.redirect.inc [new file with mode: 0644]
web/modules/contrib/linkchecker/linkchecker.routing.yml [new file with mode: 0644]
web/modules/contrib/linkchecker/migration_templates/d6_linkchecker_settings.yml [new file with mode: 0644]
web/modules/contrib/linkchecker/migration_templates/d7_linkchecker_settings.yml [new file with mode: 0644]
web/modules/contrib/linkchecker/src/Controller/LinkCheckerAdminReportPage.php [new file with mode: 0644]
web/modules/contrib/linkchecker/src/Controller/LinkCheckerUserReportPage.php [new file with mode: 0644]
web/modules/contrib/linkchecker/src/Form/LinkCheckerAdminSettingsForm.php [new file with mode: 0644]
web/modules/contrib/linkchecker/src/Form/LinkCheckerEditLinkSettingsForm.php [new file with mode: 0644]
web/modules/contrib/linkchecker/src/Tests/LinkCheckerInterfaceTest.php [new file with mode: 0644]
web/modules/contrib/linkchecker/src/Tests/LinkCheckerLinkExtractionTest.php [new file with mode: 0644]
web/modules/contrib/livereload [deleted submodule]
web/modules/contrib/livereload/README.txt [new file with mode: 0644]
web/modules/contrib/livereload/config/install/livereload.settings.yml [new file with mode: 0644]
web/modules/contrib/livereload/config/schema/livereload.schema.yml [new file with mode: 0644]
web/modules/contrib/livereload/js/pseudo.js [new file with mode: 0644]
web/modules/contrib/livereload/livereload.info.yml [new file with mode: 0644]
web/modules/contrib/livereload/livereload.libraries.yml [new file with mode: 0644]
web/modules/contrib/livereload/livereload.module [new file with mode: 0644]
web/modules/contrib/livereload/livereload.permissions.yml [new file with mode: 0644]
web/modules/contrib/media [deleted submodule]
web/modules/contrib/media/.travis.yml [new file with mode: 0644]
web/modules/contrib/media/README.md [new file with mode: 0644]
web/modules/contrib/media/composer.json [new file with mode: 0644]
web/modules/contrib/media/config/install/core.entity_form_display.media.document.default.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/core.entity_form_display.media.gallery.default.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/core.entity_form_display.media.image.default.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/core.entity_form_display.media.instagram.default.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/core.entity_form_display.media.tweet.default.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/core.entity_form_display.media.video.default.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/core.entity_view_display.media.document.default.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/core.entity_view_display.media.document.media_library.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/core.entity_view_display.media.gallery.default.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/core.entity_view_display.media.gallery.media_library.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/core.entity_view_display.media.image.default.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/core.entity_view_display.media.image.media_library.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/core.entity_view_display.media.instagram.default.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/core.entity_view_display.media.instagram.media_library.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/core.entity_view_display.media.tweet.default.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/core.entity_view_display.media.tweet.media_library.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/core.entity_view_display.media.video.default.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/core.entity_view_display.media.video.media_library.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/core.entity_view_mode.media.gallery.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/core.entity_view_mode.media.media_library.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/crop.type.media_crop.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/embed.button.media.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/entity_browser.browser.gallery_media_library.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/entity_browser.browser.media_embed.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/entity_browser.browser.media_library.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.field.media.document.field_document.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.field.media.document.field_document_size.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.field.media.document.field_media_in_library.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.field.media.document.field_mime_type.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.field.media.gallery.field_media_in_library.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.field.media.gallery.field_slide.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.field.media.image.field_image.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.field.media.image.field_media_in_library.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.field.media.instagram.field_instagram_shortcode.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.field.media.instagram.field_instagram_url.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.field.media.instagram.field_media_in_library.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.field.media.tweet.field_media_in_library.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.field.media.tweet.field_tweet_author.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.field.media.tweet.field_tweet_id.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.field.media.tweet.field_tweet_url.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.field.media.video.field_id.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.field.media.video.field_media_in_library.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.field.media.video.field_source.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.field.media.video.field_video.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.storage.media.field_document.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.storage.media.field_document_size.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.storage.media.field_id.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.storage.media.field_image.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.storage.media.field_instagram_shortcode.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.storage.media.field_instagram_url.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.storage.media.field_media_in_library.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.storage.media.field_mime_type.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.storage.media.field_slide.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.storage.media.field_source.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.storage.media.field_tweet_author.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.storage.media.field_tweet_id.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.storage.media.field_tweet_url.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/field.storage.media.field_video.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/image.style.gallery_item.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/image.style.media_crop.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/image.style.media_crop_preview.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/image.style.media_library_item.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/media_entity.bundle.document.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/media_entity.bundle.gallery.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/media_entity.bundle.image.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/media_entity.bundle.instagram.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/media_entity.bundle.tweet.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/media_entity.bundle.video.yml [new file with mode: 0644]
web/modules/contrib/media/config/install/views.view.media_library.yml [new file with mode: 0644]
web/modules/contrib/media/css/media.view.css [new file with mode: 0644]
web/modules/contrib/media/drupal_ti/before/before_script.sh [new file with mode: 0644]
web/modules/contrib/media/files/Test.doc [new file with mode: 0644]
web/modules/contrib/media/files/Test.docx [new file with mode: 0644]
web/modules/contrib/media/files/Test.ods [new file with mode: 0644]
web/modules/contrib/media/files/Test.odt [new file with mode: 0644]
web/modules/contrib/media/files/Test.ott [new file with mode: 0644]
web/modules/contrib/media/files/Test.pdf [new file with mode: 0644]
web/modules/contrib/media/files/Test.ppt [new file with mode: 0644]
web/modules/contrib/media/files/Test.pptx [new file with mode: 0644]
web/modules/contrib/media/files/Test.rtf [new file with mode: 0644]
web/modules/contrib/media/files/Test.txt [new file with mode: 0644]
web/modules/contrib/media/files/Test.xls [new file with mode: 0644]
web/modules/contrib/media/files/Test.xlsx [new file with mode: 0644]
web/modules/contrib/media/images/checkmark.svg [new file with mode: 0755]
web/modules/contrib/media/images/icons/application-msword.png [new file with mode: 0644]
web/modules/contrib/media/images/icons/application-pdf.png [new file with mode: 0644]
web/modules/contrib/media/images/icons/application-rtf.png [new file with mode: 0644]
web/modules/contrib/media/images/icons/application-vnd.ms-excel.png [new file with mode: 0644]
web/modules/contrib/media/images/icons/application-vnd.ms-powerpoint.png [new file with mode: 0644]
web/modules/contrib/media/images/icons/application-vnd.oasis.opendocument.spreadsheet.png [new file with mode: 0644]
web/modules/contrib/media/images/icons/application-vnd.oasis.opendocument.text-template.png [new file with mode: 0644]
web/modules/contrib/media/images/icons/application-vnd.oasis.opendocument.text.png [new file with mode: 0644]
web/modules/contrib/media/images/icons/application-vnd.openxmlformats-officedocument.presentationml.presentation.png [new file with mode: 0644]
web/modules/contrib/media/images/icons/application-vnd.openxmlformats-officedocument.spreadsheetml.sheet.png [new file with mode: 0644]
web/modules/contrib/media/images/icons/application-vnd.openxmlformats-officedocument.wordprocessingml.document.png [new file with mode: 0644]
web/modules/contrib/media/images/icons/text-plain.png [new file with mode: 0644]
web/modules/contrib/media/images/media_embed_icon.png [new file with mode: 0644]
web/modules/contrib/media/js/media.view.js [new file with mode: 0644]
web/modules/contrib/media/media.info.yml [new file with mode: 0644]
web/modules/contrib/media/media.install [new file with mode: 0644]
web/modules/contrib/media/media.libraries.yml [new file with mode: 0644]
web/modules/contrib/media/media.module [new file with mode: 0644]
web/modules/contrib/media/src/Plugin/Validation/Constraint/GalleryMediaBundleConstraint.php [new file with mode: 0644]
web/modules/contrib/media/src/Plugin/Validation/Constraint/GalleryMediaBundleConstraintValidator.php [new file with mode: 0644]
web/modules/contrib/media/src/Tests/DocumentBundleTest.php [new file with mode: 0644]
web/modules/contrib/media/src/Tests/GalleryBundleTest.php [new file with mode: 0644]
web/modules/contrib/media/src/Tests/ImageBundleTest.php [new file with mode: 0644]
web/modules/contrib/media/src/Tests/InstagramBundleTest.php [new file with mode: 0644]
web/modules/contrib/media/src/Tests/TweetBundleTest.php [new file with mode: 0644]
web/modules/contrib/media/src/Tests/VideoBundleTest.php [new file with mode: 0644]
web/modules/contrib/media/tests/modules/media_embed_test/config/install/core.entity_form_display.node.page.default.yml [new file with mode: 0644]
web/modules/contrib/media/tests/modules/media_embed_test/config/install/editor.editor.basic_html.yml [new file with mode: 0644]
web/modules/contrib/media/tests/modules/media_embed_test/config/install/editor.editor.full_html.yml [new file with mode: 0644]
web/modules/contrib/media/tests/modules/media_embed_test/config/install/field.field.node.page.body.yml [new file with mode: 0644]
web/modules/contrib/media/tests/modules/media_embed_test/config/install/filter.format.basic_html.yml [new file with mode: 0644]
web/modules/contrib/media/tests/modules/media_embed_test/config/install/filter.format.full_html.yml [new file with mode: 0644]
web/modules/contrib/media/tests/modules/media_embed_test/config/install/node.type.page.yml [new file with mode: 0644]
web/modules/contrib/media/tests/modules/media_embed_test/media_embed_test.info.yml [new file with mode: 0644]
web/modules/contrib/media/tests/src/FunctionalJavascript/EmbedButtonTest.php [new file with mode: 0644]
web/modules/contrib/php [deleted submodule]
web/modules/contrib/php/composer.json [new file with mode: 0644]
web/modules/contrib/php/config/install/filter.format.php_code.yml [new file with mode: 0644]
web/modules/contrib/php/js/php.admin.js [new file with mode: 0644]
web/modules/contrib/php/php.info.yml [new file with mode: 0644]
web/modules/contrib/php/php.libraries.yml [new file with mode: 0644]
web/modules/contrib/php/php.module [new file with mode: 0644]
web/modules/contrib/php/php.permissions.yml [new file with mode: 0644]
web/modules/contrib/php/src/Plugin/Condition/Php.php [new file with mode: 0644]
web/modules/contrib/php/src/Plugin/Filter/Php.php [new file with mode: 0644]
web/modules/contrib/php/src/Plugin/views/argument_default/Php.php [new file with mode: 0644]
web/modules/contrib/php/src/Plugin/views/argument_validator/Php.php [new file with mode: 0644]
web/modules/contrib/php/src/Tests/Condition/PhpConditionTest.php [new file with mode: 0644]
web/modules/contrib/php/src/Tests/PhpAccessTest.php [new file with mode: 0644]
web/modules/contrib/php/src/Tests/PhpFilterTest.php [new file with mode: 0644]
web/modules/contrib/php/src/Tests/PhpTestBase.php [new file with mode: 0644]
web/modules/contrib/php/src/Tests/Plugin/views/PhpArgumentValidatorTest.php [new file with mode: 0644]
web/modules/contrib/php/tests/modules/php_views_test_config/php_views_test_config.info.yml [new file with mode: 0644]
web/modules/contrib/php/tests/modules/php_views_test_config/test_views/views.view.test_view_argument_validate_php.yml [new file with mode: 0644]
web/modules/contrib/views_responsive_grid [deleted submodule]
web/modules/contrib/views_responsive_grid/README.txt [new file with mode: 0644]
web/modules/contrib/views_responsive_grid/lib/Drupal/views_responsive_grid/Plugin/views/style/ResponsiveGrid.php [new file with mode: 0644]
web/modules/contrib/views_responsive_grid/templates/views-view-responsive-grid.html.twig [new file with mode: 0644]
web/modules/contrib/views_responsive_grid/views_responsive_grid.css [new file with mode: 0644]
web/modules/contrib/views_responsive_grid/views_responsive_grid.info.yml [new file with mode: 0644]
web/modules/contrib/views_responsive_grid/views_responsive_grid.module [new file with mode: 0644]
web/modules/contrib/views_responsive_grid/views_responsive_grid.theme.inc [new file with mode: 0644]

diff --git a/web/modules/contrib/advanced_help b/web/modules/contrib/advanced_help
deleted file mode 160000 (submodule)
index ed80f44..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit ed80f440c8bb0c3c5b1868f3429f296e52c841a6
diff --git a/web/modules/contrib/advanced_help/README.txt b/web/modules/contrib/advanced_help/README.txt
new file mode 100644 (file)
index 0000000..e32ce02
--- /dev/null
@@ -0,0 +1,49 @@
+CONTENTS OF THIS FILE
+--------------------
+
+* Introduction
+* Requirements
+* Recommended modules
+* Installation
+* Configuration
+* Maintainers
+
+
+INTRODUCTION
+------------
+
+The Advanced help module allows module developers to store their help outside the module system, in pure .html or .md (MarkDown) files.  It provides a framework that allows module and theme developers to integrate help texts in a Drupal site, as well as exposing help to site administrators through the administrative interface.
+
+* For a full description of the module visit https://www.drupal.org/node/2461741
+
+* To submit bug reports and feature suggestions, or to track changes visit https://www.drupal.org/project/issues/advanced_help
+
+
+REQUIREMENTS
+------------
+
+This module has no required dependencies outside of Drupal core.
+
+
+RECOMMENDED MODULES
+-------------------
+
+* Advanced help hint - If Advanced help is not enabled, this module will generate a hint string that can be used in the project's hook_help to hint that Advanced help should be enabled. (https://www.drupal.org/project/advanced_help_hint)
+
+
+INSTALLATION
+------------
+
+* Install the Advanced help module as you would normally install a contributed Drupal module. Visit https://www.drupal.org/node/1897420 for further information.
+
+
+CONFIGURATION
+--------------
+
+By itself, this module doesn't do much. The Advanced help assists other modules and themes in showing help texts. Nothing will show up until you enable at least one other module that makes use of the advanced help framework or comes with a file named README.md or README.txt.
+
+
+MAINTAINERS
+-----------
+
+* David Valdez - https://www.drupal.org/u/gnuget
diff --git a/web/modules/contrib/advanced_help/advanced_help.info.yml b/web/modules/contrib/advanced_help/advanced_help.info.yml
new file mode 100644 (file)
index 0000000..28a815a
--- /dev/null
@@ -0,0 +1,6 @@
+name: Advanced help
+type: module
+description: 'Provide extended help and documentation.'
+version: VERSION
+core: 8.x
+
diff --git a/web/modules/contrib/advanced_help/advanced_help.install b/web/modules/contrib/advanced_help/advanced_help.install
new file mode 100644 (file)
index 0000000..7e735e4
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * Implements hook_schema().
+ */
+function advanced_help_schema() {
+  $schema['advanced_help_index'] = [
+    'description' => 'Stores search index correlations for advanced help topics.',
+    'fields' => [
+      'sid' => [
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'description' => 'The primary key to give to the search engine for this topic.',
+        'no export' => TRUE,
+      ],
+      'module' => [
+        'type' => 'varchar',
+        'length' => '255',
+        'default' => '',
+        'not null' => TRUE,
+        'description' => 'The module that owns this topic.',
+      ],
+      'topic' => [
+        'type' => 'varchar',
+        'length' => '255',
+        'default' => '',
+        'not null' => TRUE,
+        'description' => 'The topic id.',
+      ],
+      'langcode' => [
+        'type' => 'varchar',
+        'length' => 12,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'The langcode this search index relates to.',
+      ],
+    ],
+    'primary key' => ['sid'],
+    'indexes' => ['langcode' => ['langcode']],
+    'foreign keys' => [
+      'system' => [
+        'table' => 'system',
+        'columns' => ['name' => 'name'],
+      ],
+    ],
+  ];
+
+  return $schema;
+}
\ No newline at end of file
diff --git a/web/modules/contrib/advanced_help/advanced_help.libraries.yml b/web/modules/contrib/advanced_help/advanced_help.libraries.yml
new file mode 100644 (file)
index 0000000..821d172
--- /dev/null
@@ -0,0 +1,10 @@
+help:
+  version: 0
+  css:
+    theme:
+      css/help.css: {}
+help.icon:
+  version: 0
+  css:
+    theme:
+      css/help-icon.css: {}
\ No newline at end of file
diff --git a/web/modules/contrib/advanced_help/advanced_help.links.menu.yml b/web/modules/contrib/advanced_help/advanced_help.links.menu.yml
new file mode 100644 (file)
index 0000000..5f7ca75
--- /dev/null
@@ -0,0 +1,6 @@
+advanced_help.main:
+  title: 'Advanced help'
+  description: 'Advanced help'
+  route_name: advanced_help.main
+  weight: 9
+  parent: system.admin
diff --git a/web/modules/contrib/advanced_help/advanced_help.links.task.yml b/web/modules/contrib/advanced_help/advanced_help.links.task.yml
new file mode 100644 (file)
index 0000000..470c9b8
--- /dev/null
@@ -0,0 +1,3 @@
+advanced_help.local_tasks:
+  deriver: 'Drupal\advanced_help\Plugin\Derivative\DynamicLocalTasks'
+  weight: 100
\ No newline at end of file
diff --git a/web/modules/contrib/advanced_help/advanced_help.module b/web/modules/contrib/advanced_help/advanced_help.module
new file mode 100644 (file)
index 0000000..9bfba51
--- /dev/null
@@ -0,0 +1,76 @@
+<?php
+use Drupal\Core\Template\Attribute;
+use Drupal\Component\Serialization\Json;
+
+/**
+ * Implements hook_menu_links_discovered_alter().
+ *
+ * Remove the admin menu in case the help module is enabled.
+ */
+function advanced_help_menu_links_discovered_alter(&$links) {
+
+  if (Drupal::moduleHandler()->moduleExists('help')) {
+    unset($links['advanced_help.main']);
+  }
+}
+
+/**
+ * Implements hook_modules_installed().
+ */
+function advanced_help_modules_installed($modules) {
+  $language = \Drupal::languageManager()->getCurrentLanguage()->getId();
+  \Drupal::cache('discovery')->invalidate('advanced_help_ini_' . $language);
+}
+
+/**
+ * Implements hook_theme().
+ */
+function advanced_help_theme() {
+  return [
+    'advanced_help_topic' => [
+      'variables' => [
+        'module' => NULL,
+        'topic'  => NULL,
+        'type'   => 'icon',
+      ]
+    ]
+  ];
+}
+
+/**
+ * Implements hook_preprocess_HOOK().
+ */
+function advanced_help_preprocess_advanced_help_topic(&$variables) {
+  $module = $variables['module'];
+  $topic  = $variables['topic'];
+  $type   = $variables['type'];
+
+  $advancedHelp = \Drupal::service('plugin.manager.advanced_help');
+  $info = $variables['topic_exists'] = $advancedHelp->getTopic($module, $topic);
+  $variables['attributes'] = new Attribute();
+  $variables['attributes']['class'] = [];
+  $variables['attributes']['title'] = $info['title'];
+
+  if (\Drupal::currentUser()->hasPermission('view advanced help popup')) {
+    $variables['attributes']['class'][] = 'advanced-help-link';
+    $variables['attributes']['class'][] = 'use-ajax';
+    $variables['attributes']['data-dialog-type'] = 'modal';
+    $variables['attributes']['data-dialog-options'] = Json::encode(['width' => $info['popup width'], 'height' => $info['popup height']]);
+    $variables['#attached']['library'][] = 'advanced_help/help.icon';
+  }
+  switch ($type) {
+    case 'icon':
+      $variables['text'] = '<span>' . t('Help') . '</span>';
+      break;
+
+    case 'title':
+      $variables['text'] = $info['title'];
+      $variables['attributes']['class'][] = 'advanced-help-title';
+      break;
+
+    default:
+      $variables['text'] = $type;
+      $variables['attributes']['class'][] = 'advanced-help-title';
+      break;
+  }
+}
\ No newline at end of file
diff --git a/web/modules/contrib/advanced_help/advanced_help.permissions.yml b/web/modules/contrib/advanced_help/advanced_help.permissions.yml
new file mode 100644 (file)
index 0000000..3c37465
--- /dev/null
@@ -0,0 +1,9 @@
+view advanced help index:
+  title: 'View help index'
+  restrict access: TRUE
+view advanced help topic:
+  title: 'View help topics'
+  restrict access: TRUE
+view advanced help popup:
+  title: 'View help popups'
+  restrict access: TRUE
diff --git a/web/modules/contrib/advanced_help/advanced_help.routing.yml b/web/modules/contrib/advanced_help/advanced_help.routing.yml
new file mode 100644 (file)
index 0000000..2fe8b92
--- /dev/null
@@ -0,0 +1,23 @@
+advanced_help.main:
+  path: '/admin/help/ah'
+  defaults:
+    _controller: '\Drupal\advanced_help\Controller\AdvancedHelpController::main'
+    _title: 'Module help index'
+  requirements:
+    _permission: 'view advanced help index'
+
+advanced_help.module_index:
+  path: '/admin/help/ah/{module}'
+  defaults:
+    _controller: '\Drupal\advanced_help\Controller\AdvancedHelpController::moduleIndex'
+    _title_callback: '\Drupal\advanced_help\Controller\AdvancedHelpController::moduleIndexTitle'
+  requirements:
+    _permission: 'view advanced help index'
+
+advanced_help.help:
+  path: '/help/ah/{module}/{topic}'
+  defaults:
+    _controller: '\Drupal\advanced_help\Controller\AdvancedHelpController::topicPage'
+    _title_callback: '\Drupal\advanced_help\Controller\AdvancedHelpController::topicPageTitle'
+  requirements:
+    _permission: 'view advanced help topic'
\ No newline at end of file
diff --git a/web/modules/contrib/advanced_help/advanced_help.services.yml b/web/modules/contrib/advanced_help/advanced_help.services.yml
new file mode 100644 (file)
index 0000000..130b448
--- /dev/null
@@ -0,0 +1,4 @@
+services:
+  plugin.manager.advanced_help:
+    class: Drupal\advanced_help\AdvancedHelpManager
+    arguments: ['@module_handler', '@theme_handler', '@cache.discovery', '@string_translation']
\ No newline at end of file
diff --git a/web/modules/contrib/advanced_help/composer.json b/web/modules/contrib/advanced_help/composer.json
new file mode 100644 (file)
index 0000000..7b7e22c
--- /dev/null
@@ -0,0 +1,22 @@
+{
+  "name": "drupal/advanced_help",
+  "description": "Provide extended help and documentation.",
+  "type": "drupal-module",
+  "license": "GPL-2.0+",
+  "minimum-stability": "dev",
+  "authors": [
+    {
+      "name": "David Valdez (gnuget)",
+      "homepage": "https://www.drupal.org/u/gnuget",
+      "role": "Maintainer"
+    }
+  ],
+  "support": {
+    "issues": "https://www.drupal.org/project/issues/advanced_help",
+    "irc": "irc://irc.freenode.org/drupal-contribute",
+    "source": "https://cgit.drupalcode.org/advanced_help"
+  },
+  "require": {
+    "michelf/php-markdown": "^1.7"
+  }
+}
diff --git a/web/modules/contrib/advanced_help/css/help-icon.css b/web/modules/contrib/advanced_help/css/help-icon.css
new file mode 100644 (file)
index 0000000..a3cbc54
--- /dev/null
@@ -0,0 +1,18 @@
+
+.advanced-help-link {
+  background: transparent url('../images/help.png') no-repeat top left;
+  background-position: 0px 0px;
+  display: inline-block;
+  height: 12px;
+  margin-top: 2px;
+  padding: 0px;
+  width: 12px;
+}
+
+.advanced-help-link span {
+  display: none;
+}
+
+.advanced-help-link:hover {
+  background-position: 0px -12px;
+}
diff --git a/web/modules/contrib/advanced_help/css/help.css b/web/modules/contrib/advanced_help/css/help.css
new file mode 100644 (file)
index 0000000..b4a8087
--- /dev/null
@@ -0,0 +1,69 @@
+
+.advanced-help-topic code,
+.advanced-help-topic pre {
+  background: #f1f1f1;
+  border: 1px solid #444;
+  display: block;
+  margin: 1em;
+  padding: .2em;
+}
+
+.advanced-help-topic h3,
+.advanced-help-topic h4,
+.advanced-help-topic h5,
+.advanced-help-topic h6,
+.advanced-help-topic dt {
+  font-weight: bold;
+}
+
+.advanced-help-topic li h3,
+.advanced-help-topic li h4,
+.advanced-help-topic li h5,
+.advanced-help-topic li h6 {
+  font-weight: normal;
+}
+
+.help-left {
+  display: block;
+  float: left; /* LTR */
+  text-align: left;
+  width: 42%;
+}
+
+.help-up {
+  display: block;
+  float: left; /* LTR */
+  margin: 0 5%;
+  width: 4%;
+}
+
+.help-up-noleft {
+  display: block;
+  float: left; /* LTR */
+  margin: 0 5%;
+  text-align: right;
+  width: 42%;
+}
+
+.help-right {
+  display: block;
+  float: right;
+  text-align: right;
+  width: 42%;
+}
+
+.help-box {
+  margin: .5em;
+}
+
+.help-navigation {
+  border-top: 1px dotted #ccc;
+}
+
+.help-previous {
+  float: left;
+}
+
+.help-next {
+  float: right;
+}
diff --git a/web/modules/contrib/advanced_help/help/advanced_help.help.yml b/web/modules/contrib/advanced_help/help/advanced_help.help.yml
new file mode 100644 (file)
index 0000000..00eb598
--- /dev/null
@@ -0,0 +1,14 @@
+readme:
+  title: 'README'
+  weight: -11
+using-advanced-help:
+  title: 'Using advanced help'
+  weight: -10
+translation:
+    title: 'Translating advanced help'
+ini-file:
+    title: 'Advanced help .init file format'
+    line break: false
+why-advanced-help:
+    title: 'Why advanced help?'
+    line break: true
diff --git a/web/modules/contrib/advanced_help/help/ahelp_tab.png b/web/modules/contrib/advanced_help/help/ahelp_tab.png
new file mode 100755 (executable)
index 0000000..37ef6be
Binary files /dev/null and b/web/modules/contrib/advanced_help/help/ahelp_tab.png differ
diff --git a/web/modules/contrib/advanced_help/help/click_icon.png b/web/modules/contrib/advanced_help/help/click_icon.png
new file mode 100644 (file)
index 0000000..0f05444
Binary files /dev/null and b/web/modules/contrib/advanced_help/help/click_icon.png differ
diff --git a/web/modules/contrib/advanced_help/help/ini-file.html b/web/modules/contrib/advanced_help/help/ini-file.html
new file mode 100644 (file)
index 0000000..05b85e4
--- /dev/null
@@ -0,0 +1,117 @@
+<p>The advanced help configuration file is in simple .ini file format.
+It has an optional section for global settings that might be inherited
+for each help file, followed by sections for each help file.</p>
+
+<p>Global settings may be put into a section named <code>[advanced help
+settings]</code>.  This means that this name is reserved and it cannot
+be a help file in any module or theme. The following settings may be
+set in this section, with the default value (if any) in brackets.</p>
+
+<dl>
+<dt><code>line break</code> (FALSE)</dt>
+<dd>If set, the line break filter will be applied to all help files
+defined by this module or theme, unless that help file specifically is
+set otherwise.  The line break converts line breaks
+into <code>br</code> and <code>p</code> tags automatically.</dd>
+
+<dt><code>navigation</code> (TRUE)</dt>
+<dd>If set, this navigation will be displayed at the end of the topic:
+<em>previous topic</em>, Up (parent), <em>next topic</em>.</dd>
+
+<dt><code>css</code></dt>
+<dd>Specify a css file that will be used for all help files (unless
+overridden), including the .css extension. This .css file must reside
+in the help directory along with the .html files, and will not be
+affected by translation.</dd>
+
+<dt><code>name</code></dt>
+<dd>May be set to override the module or theme name as displayed in
+both the module/theme index as well as the navigation and breadcrumb
+trail. Usually, this is not set, but for some projects you may want to
+use a more friendly name than appears in the project's .info file.</dd>
+
+<dt><code>index name</code></dt>
+<dd>This may be set to change the name of the module or theme in the
+module/theme index. It overrides the <code>name</code> setting above,
+as well as the project's name in its .info file.</dd>
+
+<dt><code>hide</code> (FALSE)</dt>
+<dd>This may be used to hide a module or theme in the module/theme
+index. This is particularly useful for modules who insert their help
+files into the hierarchy of another module or theme, as might be done
+by modules that extend <strong>Views</strong> or derived themes that
+extend base themes like <strong>Zen</strong>. By setting this to TRUE
+the project will not appear as its own entry.</dd>
+</dl>
+
+<p>Each section after that will correspond to a single help file for a
+single topic.  It starts with the topic name in square brackets
+(e.g. <code>[ini-file]</code>).  The following settings may be set for
+each file/topic, with the default value (if any) in brackets.</p>
+
+<dl>
+<dt><code>title</code></dt>
+<dd>The title of the topic, presented to the user and used in
+links. If you have special characters in your title, be sure to
+enclose it in quotes.  (This setting is currently not optional.)</dd>
+
+<dt><code>file</code> (topic name)</dt>
+<dd>The filename, without the .html extension, used for the file with
+the help text for the topic. This is optional; if not specified, the
+topic name wil be the file name.</dd>
+
+<dt><code>weight</code> (0)</dt>
+<dd>The weight, used for sorting topics on the administration
+page. The default is 0 (zero) if unspecified. Items with the same weight
+are sorted alphabetically.</dd>
+
+<dt><code>parent</code></dt>
+<dd>The topic ID to use in a hierarchy; children will be listed
+beneath parents in the topic list, and will have the parent in their
+breadcrumb trail. You may parent this topic to a topic in another
+module or theme by using <code>module%topic</code>
+or <code>theme%topic</code> as the identifier,
+where <code>module</code> or <code>theme</code> is the project's short
+name. For example if parent is set to, '<code>views%display</code>',
+the topic will be regarded as a child of the
+<code>display</code> topic in the <strong>Views</strong> module.</dd>
+
+<dt><code>line break</code> (FALSE)</dt>
+<dd>Set the line break filter for this topic. Set to FALSE to disable
+the line break filter if this has been turned on in the global
+settings.</dd>
+
+<dt><code>css</code></dt>
+<dd>Specify a css file that will be used for this file. This .css file
+must reside in the help directory along with the .html files. This
+will override any .css file added by the global settings.</dd>
+
+<dt><code>popup width</code> (500)</dt>
+<dd>The width in pixels of the popup window.</dd>
+
+<dt><code>popup height</code> (500)</dt>
+<dd>The height in pixels of the popup window.</dd>
+</dl>
+
+<p>For example, here is a version of the <code>advanced_help.help.ini</code> file:</p>
+
+<pre>
+[readme]
+title = README
+weight = -11
+
+[using-advanced-help]
+title = Using advanced help
+weight = -10
+
+[translation]
+title = Translating advanced help
+
+[ini-file]
+title = Help .ini file format
+line break = FALSE
+
+[why-advanced-help]
+title = Why advanced help?
+line break = TRUE
+</pre>
diff --git a/web/modules/contrib/advanced_help/help/readme.html b/web/modules/contrib/advanced_help/help/readme.html
new file mode 100644 (file)
index 0000000..99f0e51
--- /dev/null
@@ -0,0 +1,107 @@
+<h2 id="project-description">Synopsis</h2>
+
+<p>The <strong>Advanced help</strong> module provides a framework that allows
+module and theme developers integrate help texts in a Drupal site.</p>
+
+<p>These help texts are stored in ordinary <code>.html</code>-files
+that lives in the file system (as opposed to the database).  These
+files are distributed from the project Drupal.org repo in the same
+package as the module or theme, and placed in a subdirectory named
+<code>help</code> in the project or theme directory.  This means that
+the help texts can be easily kept in sync with the project they
+provide help texts for, but also that read access to these files
+are not managed by any content access restrictions imposed by Drupal.</p>
+  
+<p>The help texts can be marked up with standard HTML. They will be
+rendered using your site's theme.</p>
+
+<p>If the module or theme author does not make use of the
+<em>Advanced help</em> HTML-framework, but if there is a
+<code>README.md</code> or <code>README.txt</code> in the package,
+the content of that file will be shown instead.</p>
+  
+<p>The help texts may appear in a popup or not as the project prefers.
+By taking away access to view the popups, a site can hide popups from
+users.</p>
+
+<p>The help texts can be placed in a hierarchy, allowing for top down
+navigation of help.</p>
+
+<p>The help texts may be made searchable. If advanced help search is
+enabled, all help texts are fully indexed. This means that the entire
+contents of the advanced help set of pages can be searched for
+keywords.</p>
+
+<h2 id="use">Using the module</h2>
+
+<p>When you enable the module, a new tab with the legend “Advanced
+help” will show up under “Help”:
+
+<div class="ta-center">
+<img class="help-img-center" alt="ahelp_tab.png" src="&path&ahelp_tab.png" width="661" height="225" border="1" />
+</div>
+
+<p>By itself, this module doesn't do much.  The <strong>Advanced
+help</strong> assists other modules and themes in showing help texts.
+Nothing will show up until you enable at least one other module that
+makes use of the advanced help framework or comes with a file
+named <code>README.md</code> or <code>README.txt</code>.  However, it
+comes with a small companion demo module named
+<strong>Advanced help example</strong> to demonstrate how it works.
+For more extensive example of use of the advanced help features, see
+the <strong>Views</strong> project.</p>
+
+<!--
+<h2 id="project-recommended">Recommended modules</h2>
+
+<ul>
+<li><a href="https://www.drupal.org/project/markdown">Markdown filter</a>:<br>
+When this module is enabled, display of any <code>README.md</code> the
+module shows will be rendered with markdown.</li>
+<li><a href="https://www.drupal.org/project/attributions">Attributions</a>:<br>
+When this module is enabled, attributions of third party content used
+by the project (i.e. some text from Wikipedia) will be available in an
+attribution block and on an attribution page.</li>
+</ul>
+-->
+
+<h2 id="support-status">Support status</h2>
+
+<p>Reported bugs for the Drupal 7 branch will be fixed in a timely
+manner.  Bugs in the issue queue for the Drupal 6 branch will only be
+fixed if accompanied with a patch, after the patch has been reviewed
+and tested by the community.  No Drupal 8 version is currently under
+development.  Post a message in
+the <a href="https://www.drupal.org/node/1928218">issue queue</a> if
+you're interested in managing a port of the project to to Drupal
+8. Older versions are no longer supported.</p>
+
+<p>Community support in the form of patches are very welcome for both
+Drupal 6 and Drupal 7 versions, and will be given priority. For QA,
+the project needs community support in the form of reviews of patches,
+development versions and releases.</p>
+
+<p>The primary goal of the module is to remain <strong>light-weight
+and simple</strong>.  This means that not all feature requests will be
+implemented, even if they are a good idea.  Feature requests
+accompanied by patches are more likely to make it into a release.</p>
+
+<p>The maintainer hopes that the community is willing to help out by
+answering &amp; closing support requests.</p>
+
+<!--
+<h2 id="project-problems">Known problems</h2>
+-->
+
+
+
+<h2 id="project-maintainers">Credits</h2>
+
+<ul>
+<li><a href="https://www.drupal.org/u/merlinofchaos">merlinofchaos</a> (52 commits, original creator)</li>
+<li><a href="https://www.drupal.org/u/redndahead">redndahead</a> (8 commits)</li>
+<li><a href="https://www.drupal.org/u/dmitrig01">dmitrig01</a> (3 commits)</li>
+<li><a href="https://www.drupal.org/u/amitgoyal">amitgoyal </a> (5 commits)</li>
+<li><a href="https://www.drupal.org/u/gisle">gisle</a> (current maintainer, D7)</li>
+<li><a href="https://www.drupal.org/u/gnuget">gnuget</a> (current maintainer, D8)</li>
+</ul>
diff --git a/web/modules/contrib/advanced_help/help/translation.html b/web/modules/contrib/advanced_help/help/translation.html
new file mode 100644 (file)
index 0000000..75de70f
--- /dev/null
@@ -0,0 +1,44 @@
+<p>To translate a help-file indexed by <strong>Advanced help</strong>,
+first create a directory
+<code>translations/help/<em>language</em></code> in the project's 
+root directory. The <em>language</em>  is the language code that
+appears on the <em>Languages</em> page in the administrative UI.</p>
+
+<p>Then, copy the <code>.ini</code> file and all
+the <code>.html</code> files from the help directory into this.  If
+you need to alter an image to use it in a translation, you may also
+put the altered image there.</p>
+
+<p>In the topics section, the <code>.ini</code> file only needs to
+keep the topic names (unaltered) and titles (translated). If there is
+a <code>name</code> or <code>index name</code> setting in the
+'advanced help settings' portion, that should be retained. Any
+retained settings should be translated. The rest of the data in the
+<code>.ini</code> file may be discarded or ignored.</p>
+
+<p>Each <code>.html</code> file should then be translated in place.</p>
+
+<p>When translating a <code>.html</code> file, you will find that
+the <code>&amp;path&amp;</code> keyword (used for images and links)
+will lead to the original directory. If you must translate items that
+are linked, such as images containing text,
+use <code>&amp;trans_path&amp;</code> instead, which will lead to the
+translated directory. This will allow you to pick and choose which
+linked items, if any, will be translated.</p>
+
+<p>If a topic is not translated, the default (untranslated) version
+will be shown instead.</p>
+
+<h2>Translating Advanced help's help files</h2>
+
+<p>If you want to help with the translation of
+<strong>Advanced help</strong> help texts for a particular language, look for an issue named named “Translation to XXX” (where
+“XXX” is the language you want to translate the help texts to) in the <a href="https://www.drupal.org/project/issues/advanced_help">issue queue for Advanced help</a>.
+If such an issue does not exist, please can create it.
+Choose <em>Category</em>   “Task”,
+<em>Status</em> “Needs review” and
+<em>Component</em>  “Documentation”.
+Upload translated files as an attachment (change the file type from <code>.html</code> to <code>.txt</code> to be allowed to upload). </p>
+
+<p>Uploaded translations will be included in the next version if
+reviewed and approved by other users (i.e. gets to status “RTBC”).</p>
diff --git a/web/modules/contrib/advanced_help/help/using-advanced-help.html b/web/modules/contrib/advanced_help/help/using-advanced-help.html
new file mode 100644 (file)
index 0000000..c47750b
--- /dev/null
@@ -0,0 +1,152 @@
+<p>The <strong>Advanced help</strong> module provides a framework that
+allows module and theme developers integrate help texts in a Drupal
+site.  Although the <strong>Advanced help</strong> does not provide
+general help by itself, it provides a powerful and easy framework that
+modules and themes may use to provide their own help.</p>
+
+<p>Modules and themes utilizing <strong>Advanced help</strong> should
+create a subdirectory named <code>help</code> inside their own main
+directory. Place the file
+<em>MODULENAME</em>.help.ini (resp. <em>THEMENAME</em>.help.ini) in this subdirectory.
+formatted similar to the following example:</p>
+
+<pre>
+[about-php]
+title = About PHP
+file = about-php
+weight = -10
+
+[history]
+title = History of PHP
+parent = about-php
+
+[usage]
+title = Usage of PHP
+weight = 1
+
+[security] 
+title = Security of PHP
+weight = 2
+
+[syntax]
+title = PHP syntax
+parent = usage
+</pre>
+
+<p>This file defines five help topics (inside the square brackets), and
+some settings for them.
+See: <a href="&topic:advanced_help/ini-file&">Advanced help .ini file format</a> for
+a list of defined settings.</p>
+
+
+<p>All topics are addressed by the module or theme providing the
+topic, and by the topic id. To produce a themed link to popup
+about a topic, use the a format similar to the following example:</p>
+
+<!-- D6
+<pre>
+$output = theme('advanced_help_topic', 'help_example', 'about-php');
+$output .= '&nbsp;' . t('Click the help icon!');
+</pre>
+-->
+
+<!-- D7 -->
+<pre>
+$output = theme('advanced_help_topic', array(
+  'module' => 'help_example',
+  'topic' => 'about-php',
+));
+$output .= '&nbsp;' . t('Click the help icon!');
+</pre>
+
+<p>This produces the following output:</p>
+
+<pre>
+&lt;a class="advanced-help-link" title="About PHP"
+  onclick="var w=window.open(this.href, 'advanced_help_window',
+  'width=500,height=500,scrollbars,resizable');
+  w.focus(); return false;"
+  href="/help/help_example/about-php?popup=1"&gt;
+&lt;span&gt;Help&lt;/span&gt;
+&lt;/a&gt;
+ Click the help icon!
+&lt;/div&gt;
+</pre>
+
+<p>This produces a clickable help icon like the one shown below:</p>
+
+<div class="ta-center">
+<img class="help-img-center" alt="clickable icon" src="&path&click_icon.png" width="180" height="90" border="0" />
+</div>
+
+<p>Inside your help file, you may link to other help topics using this format:</p>
+<pre>
+&lt;a href="&amp;topic:module/topic&amp;"&gt;topic&lt;/a&gt;
+</pre>
+<p>This format will ensure the popup status remains consistent when
+switching between links.</p>
+
+<p>To reference items within the help directory, such as images you wish to embed  within the help text, use:</p>
+
+<pre>
+&lt;img src="&amp;path&amp;example.png"/&gt;
+&lt;img src="&amp;trans_path&amp;example.png"/&gt;
+</pre>
+
+<p>The <code>trans_path</code> keyword refers to a translated version of the image in the translation directory and may be used it differs from the original.</p>
+
+<p>To reference any normal path in the site, use:</p>
+<pre>
+&lt;a href="&amp;base_url&amp;admin/settings/site-configuration"&gt;anchor text&lt;/a&gt;
+</pre>
+
+<p><strong>NOTE: </strong> In previous versions <strong>Advanced
+help</strong> did not require the &amp;'s to be wrapped around
+<code>topic</code>, <code>path</code>, and <code>base_url</code>.
+This is currently still supported, but will be removed in a future
+version.  By adding the &amp;'s these tokens are now not limited
+to <code>href=""</code> and <code>src=""</code> parameters.</p>
+
+<h2 id="access-control">Access control</h2>
+
+<p>When this module is installed, users with the
+<code>view advanced help index</code>
+permission can access the advanced help index by going to
+<em>Administer &rarr; Advanced Help</em>
+(<code>admin/advanced_help</code>). Additional permissions
+<code>view advanced help topic</code>  and
+<code>view advanced help popup</code>
+enable users to access the actual help pages and popups.</p>
+
+<p>The help texts are stored as plain .html-files and can, unless
+protected, be accessed by anyone who knows their URL.  To protect
+them, place the following four lines in a file named
+<code>.htaccess</code> in project's <code>help</code> directory:</p>
+
+<pre>
+&lt;Files *\.html&gt;
+Order Allow,Deny
+Deny from all
+&lt;/Files&gt;
+</pre>
+
+<p>It as the responsibility of the site manager to make sure this type
+of protection is in place if the site has help files that merits
+protection from direct access.</p>
+
+<p>See also this tracker in the project's issue queue:  
+<a href="https://www.drupal.org/node/1980936">#1980936 Typing complete path to .html help files in module bypasses user permissions</a>.</p>
+
+<h2 id="search">Search</h2>
+
+<p>To enable advanced help search, navigate to
+<em>Administration → Configuration → Search and metadata → Search settings</em>.
+Scroll down to <em>Active search modules</em> and tick the box to the
+left of “Advanced help”.  The search form will appear on the top of
+the advanced help index pages.</p>
+
+<p>If the core <strong>Search</strong> module is enabled, the contents
+of the advanced help framework will be indexed on cron. If you enable
+new modules or themes and wish to immediately index their help text,
+navigate to <em>Administration → Reports → Status report</em> and
+click the link “run cron manually”.</p>
diff --git a/web/modules/contrib/advanced_help/help/why-advanced-help.html b/web/modules/contrib/advanced_help/help/why-advanced-help.html
new file mode 100644 (file)
index 0000000..3f36b3d
--- /dev/null
@@ -0,0 +1,44 @@
+The <strong>Advanced help</strong> framework was designed to replace the original Drupal help system, which has several flaws that make it hard to create new help, hard to maintain existing help, and particularly hard to access help.
+
+The primary goal, then, is to increase the accessibility of help, meaning the ability of both the user and the help text author to access the needed tools to use, create, maintain and translate the help.
+
+This system is completely separate from Drupal's <code>hook_help()</code>. In Drupal 6 and 7, it actually co-exists with it; in the future, it is hoped that it will completely replace it allowing <code>hook_help()</code> to be deprecated and removed.
+
+Messages added to the top of a page are not really “help”. Often these messages are an introduction to a form or a short blurb telling a user what to do with a particular page. The problem is, these messages are always there, they are easily ignored, and they come before the actual page. In general, when users are learning, they want to see the page first, then ask questions. The reverse order is much less conducive to actually teaching a user how to use something. By allowing help to be available on request, the system conforms more naturally to how most people work.
+
+<h2>Advanced help is organized by topic</h2>
+With the <code>hook_help()</code> method, help text is organized by URL path. This is fine if you have help text describing how to use a particular page or what a particular page does, but ultimately is limiting because manuals and documentation are usually grouped by topic, and those topics are determined by the material itself.
+
+<strong>Advanced help</strong> allows the documentation author to organize topics as he or she sees fit; the only restriction, really, is that each individual chunk of text needs to stand on its own as a discrete topic.
+
+What's more, modules and themes can insert their topics into another's hierarchy. This would allow the Drupal core to create a task based help navigation system which allows modules and themes to insert topics into that navigation fluidly. This allows modules and themes to continue to keep their systems separate, yet integrate into the main system.
+
+<h2>Advanced help topics are processed HTML in their own files</h2>
+This separation makes it easy to find and modify. Currently, everything is lumped together in <code>hook_help()</code> in PHP strings that are run through <code>t()</code>, and there is a fair amount of PHP code necessary in this system that actually gets in the way of writing good, explanatory text.
+
+In fact, requiring a documentation author to understand PHP at all is a detriment. The idea that documentation writers need to have PHP development as a skill seriously reduces the number of available contributors. HTML, on the other hand, is a much more common skill, is relatively easy to learn, and the amount of HTML needed to write documentation is only a little bit more than the HTML used in forum posts.
+
+Another benefit to not using PHP is that the files themselves are safe. They are filtered to escape malicious script code that could take over the server or do worse things. This means that these files can be used relatively easily on Drupal.org itself. It also means that descriptions of the project pulled from the project's help pages or README.txt or README.md can be made on the project's Drupal.org without the need to download the module or theme to look at the contents of these files.  This also simplifies maintenance, as the files can be corrected easily by patches, which are updated by tyhe maintainer and pushed to git.
+
+By moving all docymentation into help files, we could, if we wanted, package the Drupal.org handbooks, or a subset of them, directly into a Drupal distribution, or a Drupal add-on, so that Drupal administrators can have Drupal help without needing to visit Drupal.org. This can be valuable in locked down corporate environments and on planes. But more importantly, the handbooks can be made version aware much more easily than the current method on Drupal.org.
+
+The downside to this method is that these books can't easily be made dynamic. Though the use of alter hooks could allow a module or theme to make modifications to the help as needed, doing this could make the help files less useful when you take them out of context.
+
+<h2>Advanced help files are translated as a file</h2>
+It is actually not easy to translate documents as strings, particularly when the language being used is very much unlike English. In fact, when translating a document, the organization of the sentences may change drastically. It is also a burden on the CPU to do this, as you are indexing on very long strings.
+
+Translators have a much better time translating a document as a unit, because of the presence of the entire context.
+
+<h2>Advanced help has its own navigation system</h2>
+By making use of a navigation system specified in a .ini file (which is not PHP code and therefore safe to use), the help can be structured like a book, which is typical of online manuals. This is familiar to users, can be organized (and re-organized) and allows a module or theme to include significantly richer text without burdening the PHP code with having its help loaded unnecessarily.
+
+This book can be navigated hierarchically as well, making it easy to keep related topics together.
+<h2>Advanced help is indexed by the search engine</h2>
+An important goal of this system was to add searchability to the help. By being able to enter keywords into the search box and find relevant topics, we come up with a system that resembles the kind of help that comes with many operating systems. This is very valuable when searching through manuals trying to find out how to do a particular thing.
+
+This search is specific to the help, meaning that the help will not be mixed in with the global node search. This can be considered both an advantage and a disadvantage. For the most part, this help system is meant to provide help to site administrators, and content searches should not find it. The disadvantage, of course, is when you want to use it for end user help, you will not be able to.
+
+<h2>Inline help can be brought in via popups</h2>
+In addition to the manual-like hierarchical navigation, <strong>Advanced help</strong> can also provide context-sensitive additional help through a popup. While popups are controversial, the argument for using them is that when getting help while on a form, <i>a popup will not throw away a user's data.</i> Browsers are not very friendly to input forms if they are not submitted, and navigating away from the form can be dangerous. There are various other solutions to this problem, but each one has a drawback. The drawbacks to popups are well known, but mostly it is the irritation of having new windows. When getting help, though, a popup is usually invited. Help should not interfere with what the operation the user is trying to complete. It differs greatly from the uninvited popup, which are usually ads or popups meant to prevent users from navigating away from a site.
+
+Popups can be added to a page with plain text links or themed icon links.
diff --git a/web/modules/contrib/advanced_help/help_example/help/180px-Andi_Gutmans_1.jpg b/web/modules/contrib/advanced_help/help_example/help/180px-Andi_Gutmans_1.jpg
new file mode 100644 (file)
index 0000000..b182676
Binary files /dev/null and b/web/modules/contrib/advanced_help/help_example/help/180px-Andi_Gutmans_1.jpg differ
diff --git a/web/modules/contrib/advanced_help/help_example/help/180px-Lerdorf.jpg b/web/modules/contrib/advanced_help/help_example/help/180px-Lerdorf.jpg
new file mode 100644 (file)
index 0000000..5a437f4
Binary files /dev/null and b/web/modules/contrib/advanced_help/help_example/help/180px-Lerdorf.jpg differ
diff --git a/web/modules/contrib/advanced_help/help_example/help/180px-PHP_Hello_World_screenshot.png b/web/modules/contrib/advanced_help/help_example/help/180px-PHP_Hello_World_screenshot.png
new file mode 100644 (file)
index 0000000..38f33b8
Binary files /dev/null and b/web/modules/contrib/advanced_help/help_example/help/180px-PHP_Hello_World_screenshot.png differ
diff --git a/web/modules/contrib/advanced_help/help_example/help/about-php.html b/web/modules/contrib/advanced_help/help_example/help/about-php.html
new file mode 100644 (file)
index 0000000..f19f30c
--- /dev/null
@@ -0,0 +1,5 @@
+<p><b>PHP</b> (<i>PHP: Hypertext Preprocessor</i>) is a computer <a target="_blank" href="http://en.wikipedia.org/wiki/Scripting_language" title="Scripting language">scripting language</a>, originally designed for producing <a target="_blank" href="http://en.wikipedia.org/wiki/Dynamic_web_page" title="Dynamic web page">dynamic web pages</a>. It is mainly used in <a target="_blank" href="http://en.wikipedia.org/wiki/Server-side_scripting" title="Server-side scripting">server-side scripting</a>, but can be used from a <a target="_blank" href="http://en.wikipedia.org/wiki/Command_line_interface" title="Command line interface">command line interface</a> or in <a target="_blank" href="http://en.wikipedia.org/wiki/Standalone" title="Standalone">standalone</a> <a target="_blank" href="http://en.wikipedia.org/wiki/Graphical_user_interface" title="Graphical user interface">graphical applications</a>.<sup id="cite_ref-1" class="reference"><a href="#cite_note-1" title="">[2]</a></sup></p>
+
+<p>While PHP was originally created by <a target="_blank" href="http://en.wikipedia.org/wiki/Rasmus_Lerdorf" title="Rasmus Lerdorf">Rasmus Lerdorf</a> in 1994, the main implementation of PHP is now produced by The PHP Group and serves as the <a target="_blank" href="http://en.wikipedia.org/wiki/De_facto_standard" title="De facto standard"><i>de facto</i> standard</a> for PHP as there is no <a target="_blank" href="http://en.wikipedia.org/wiki/Formal_specification" title="Formal specification">formal specification</a>.<sup id="cite_ref-history_2-0" class="reference"><a href="#cite_note-history-2" title="">[3]</a></sup> Released under the <a target="_blank" href="http://en.wikipedia.org/wiki/PHP_License" title="PHP License">PHP License</a>, the <a target="_blank" href="http://en.wikipedia.org/wiki/Free_Software_Foundation" title="Free Software Foundation">Free Software Foundation</a> considers it to be <a target="_blank" href="http://en.wikipedia.org/wiki/Free_software" title="Free software">free software</a>.<sup id="cite_ref-3" class="reference"><a href="#cite_note-3" title="">[4]</a></sup></p>
+
+<p>PHP is a widely-used general-purpose scripting language that is especially suited for <a target="_blank" href="http://en.wikipedia.org/wiki/Web_development" title="Web development">web development</a> and can be embedded into <a target="_blank" href="http://en.wikipedia.org/wiki/HTML" title="HTML">HTML</a>. It generally runs on a <a target="_blank" href="http://en.wikipedia.org/wiki/Web_server" title="Web server">web server</a>, taking PHP code as its input and creating <a target="_blank" href="http://en.wikipedia.org/wiki/Web_page" title="Web page">web pages</a> as output. It can be deployed on most web servers and on almost every <a target="_blank" href="http://en.wikipedia.org/wiki/Operating_system" title="Operating system">operating system</a> and <a target="_blank" href="http://en.wikipedia.org/wiki/Platform_%28computing%29" class="mw-redirect" title="Platform (computing)">platform</a> free of charge.<sup id="cite_ref-foundations_4-0" class="reference"><a href="#cite_note-foundations-4" title="">[5]</a></sup> PHP is installed on more than 20 million websites and 1 million <a target="_blank" href="http://en.wikipedia.org/wiki/Server_%28computing%29" title="Server (computing)">servers</a>, although the number of websites with PHP <a target="_blank" href="http://en.wikipedia.org/wiki/Installation_%28computer_programs%29" title="Installation (computer programs)">installed</a> has declined since August 2005.<sup id="cite_ref-usage_5-0" class="reference"><a href="#cite_note-usage-5" title="">[6]</a></sup> It is also the most popular <a target="_blank" href="http://en.wikipedia.org/wiki/Apache_HTTP_Server" title="Apache HTTP Server">Apache</a> module among computers using Apache as a web server.<sup id="cite_ref-usage_5-1" class="reference"><a href="#cite_note-usage-5" title="">[6]</a></sup> The most recent major release of PHP was version 5.2.5 on <a target="_blank" href="http://en.wikipedia.org/wiki/November_8" title="November 8">November 8</a>, <a target="_blank" href="http://en.wikipedia.org/wiki/2007" title="2007">2007</a>.<sup id="cite_ref-php5changelog_6-0" class="reference"><a href="#cite_note-php5changelog-6" title="">[7]</a></sup></p>
diff --git a/web/modules/contrib/advanced_help/help_example/help/help_example.help.ini b/web/modules/contrib/advanced_help/help_example/help/help_example.help.ini
new file mode 100644 (file)
index 0000000..83f9ec7
--- /dev/null
@@ -0,0 +1,24 @@
+[about-php]
+title = About PHP
+file = about-php
+weight = -10
+
+[history]
+title = History of PHP
+file = history
+parent = about-php
+
+[usage]
+title = Usage of PHP
+file = usage
+weight = 1
+
+[security] 
+title = Security of PHP
+file = security
+weight = 2
+
+[syntax]
+title = PHP syntax
+file = syntax
+parent = usage
\ No newline at end of file
diff --git a/web/modules/contrib/advanced_help/help_example/help/history.html b/web/modules/contrib/advanced_help/help_example/help/history.html
new file mode 100644 (file)
index 0000000..9993403
--- /dev/null
@@ -0,0 +1,23 @@
+<div class="help-box help-left">
+<div class="thumbinner" style="width:182px;"><a href="http://en.wikipedia.org/wiki/Image:Lerdorf.jpg" class="image" title="Rasmus Lerdorf, who wrote the original Common Gateway Interface binaries"><img alt="Rasmus Lerdorf, who wrote the original Common Gateway Interface binaries" src="path:180px-Lerdorf.jpg" width="180" height="270" border="0" class="thumbimage" /></a>
+<div class="thumbcaption">
+<div class="magnify"><a href="/wiki/Image:Lerdorf.jpg" class="internal" title="Enlarge"><img src="/skins-1.5/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>
+<a href="/wiki/Rasmus_Lerdorf" title="Rasmus Lerdorf">Rasmus Lerdorf</a>, who wrote the original <a href="/wiki/Common_Gateway_Interface" title="Common Gateway Interface">Common Gateway Interface</a> binaries</div>
+</div>
+</div>
+
+
+<p>PHP, standing for Personal Home Page, began as a set of <a target="_blank" href="http://en.wikipedia.org/wiki/Common_Gateway_Interface" title="Common Gateway Interface">Common Gateway Interface</a> <a target="_blank" href="http://en.wikipedia.org/wiki/Binary_file" title="Binary file">binaries</a> written in the <a target="_blank" href="http://en.wikipedia.org/wiki/C_programming_language" class="mw-redirect" title="C programming language">C programming language</a> in 1994 by the <a target="_blank" href="http://en.wikipedia.org/wiki/Danish_people" title="Danish people">Danish</a>/<a target="_blank" href="http://en.wikipedia.org/wiki/Greenland" title="Greenland">Greenlandic</a> programmer <a target="_blank" href="http://en.wikipedia.org/wiki/Rasmus_Lerdorf" title="Rasmus Lerdorf">Rasmus Lerdorf</a>. Lerdorf initially created these Personal Home Page Tools to replace a small set of <a target="_blank" href="http://en.wikipedia.org/wiki/Perl" title="Perl">Perl</a> scripts he had been using to maintain his <a target="_blank" href="http://en.wikipedia.org/wiki/Personal_homepage" class="mw-redirect" title="Personal homepage">personal homepage</a>. The tools were originally created to perform tasks such as displaying his <a target="_blank" href="http://en.wikipedia.org/wiki/R%C3%A9sum%C3%A9" title="Résumé">résumé</a> and recording how much <a target="_blank" href="http://en.wikipedia.org/wiki/Web_traffic" title="Web traffic">traffic</a> his page was receiving.<sup id="cite_ref-history_2-1" class="reference"><a href="#cite_note-history-2" title="">[3]</a></sup> He combined these binaries with his Form Interpreter to create PHP/FI, which had more functionality. It included a larger <a target="_blank" href="http://en.wikipedia.org/wiki/C_%28programming_language%29" title="C (programming language)">C implementation</a> which could communicate with <a target="_blank" href="http://en.wikipedia.org/wiki/Database" title="Database">databases</a> and helped build simple, dynamic <a target="_blank" href="http://en.wikipedia.org/wiki/Web_application" title="Web application">web applications</a>. He released PHP publicly on <a target="_blank" href="http://en.wikipedia.org/wiki/June_8" title="June 8">June 8</a>, <a target="_blank" href="http://en.wikipedia.org/wiki/1995" title="1995">1995</a> to speed up the finding of <a target="_blank" href="http://en.wikipedia.org/wiki/Software_bug" title="Software bug">bugs</a> and improving the code.<sup id="cite_ref-7" class="reference"><a href="#cite_note-7" title="">[8]</a></sup> This release was named PHP version 2, and already had basic functionality that PHP has today. This includes Perl-like variables, form handling, and the ability to embed HTML. The syntax was similar to Perl but was more limited, simpler, and less consistent.<sup id="cite_ref-history_2-2" class="reference"><a href="#cite_note-history-2" title="">[3]</a></sup></p>
+
+<div class="help-box help-right">
+<div class="thumbinner" style="width:182px;"><a target="_blank" href="http://en.wikipedia.org/wiki/Image:Andi_Gutmans_1.jpg" class="image" title="Andi Gutmans, who, along with Zeev Suraski, rewrote the parser that formed PHP 3"><img alt="Andi Gutmans, who, along with Zeev Suraski, rewrote the parser that formed PHP 3" src="path:180px-Andi_Gutmans_1.jpg" width="180" height="244" border="0" class="thumbimage" /></a>
+<div class="thumbcaption">
+<div class="magnify"><a target="_blank" href="http://en.wikipedia.org/wiki/Image:Andi_Gutmans_1.jpg" class="internal" title="Enlarge"><img src="/skins-1.5/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>
+<a target="_blank" href="http://en.wikipedia.org/wiki/Andi_Gutmans" title="Andi Gutmans">Andi Gutmans</a>, who, along with <a target="_blank" href="http://en.wikipedia.org/wiki/Zeev_Suraski" title="Zeev Suraski">Zeev Suraski</a>, rewrote the <a target="_blank" href="http://en.wikipedia.org/wiki/Parser" class="mw-redirect" title="Parser">parser</a> that formed PHP 3</div>
+</div>
+</div>
+<p><a target="_blank" href="http://en.wikipedia.org/wiki/Zeev_Suraski" title="Zeev Suraski">Zeev Suraski</a> and <a target="_blank" href="http://en.wikipedia.org/wiki/Andi_Gutmans" title="Andi Gutmans">Andi Gutmans</a>, two <a target="_blank" href="http://en.wikipedia.org/wiki/Israelis" title="Israelis">Israeli</a> developers at the <a target="_blank" href="http://en.wikipedia.org/wiki/Technion_IIT" class="mw-redirect" title="Technion IIT">Technion IIT</a>, rewrote the <a target="_blank" href="http://en.wikipedia.org/wiki/Parser" class="mw-redirect" title="Parser">parser</a> in 1997 and formed the base of PHP 3, changing the language's name to the <a target="_blank" href="http://en.wikipedia.org/wiki/Recursive_initialism" class="mw-redirect" title="Recursive initialism">recursive initialism</a> <i>PHP: Hypertext Preprocessor</i>.<sup id="cite_ref-history_2-3" class="reference"><a href="#cite_note-history-2" title="">[3]</a></sup> The development team officially released PHP/FI 2 in November 1997 after months of <a target="_blank" href="http://en.wikipedia.org/wiki/Development_stage#beta" class="mw-redirect" title="Development stage">beta</a> testing. Afterwards, public testing of PHP 3 began, and the official launch came in June 1998. Suraski and Gutmans then started a new <a target="_blank" href="http://en.wikipedia.org/wiki/Rewrite_%28programming%29" title="Rewrite (programming)">rewrite</a> of PHP's core, producing the <a target="_blank" href="http://en.wikipedia.org/wiki/Zend_Engine" title="Zend Engine">Zend Engine</a> in 1999.<sup id="cite_ref-8" class="reference"><a href="#cite_note-8" title="">[9]</a></sup> They also founded <a target="_blank" href="http://en.wikipedia.org/wiki/Zend_Technologies" title="Zend Technologies">Zend Technologies</a> in <a target="_blank" href="http://en.wikipedia.org/wiki/Ramat_Gan" title="Ramat Gan">Ramat Gan</a>, Israel, which manages the development of PHP.<sup id="cite_ref-history_2-4" class="reference"><a href="#cite_note-history-2" title="">[3]</a></sup></p>
+
+<p>On <a target="_blank" href="http://en.wikipedia.org/wiki/May_22" title="May 22">May 22</a>, <a target="_blank" href="http://en.wikipedia.org/wiki/2000" title="2000">2000</a>, PHP 4, powered by the Zend Engine 1.0, was released.<sup id="cite_ref-history_2-5" class="reference"><a href="#cite_note-history-2" title="">[3]</a></sup> On <a target="_blank" href="http://en.wikipedia.org/wiki/July_13" title="July 13">July 13</a>, <a target="_blank" href="http://en.wikipedia.org/wiki/2004" title="2004">2004</a>, PHP 5 was released and is powered by the new Zend Engine II.<sup id="cite_ref-history_2-6" class="reference"><a href="#cite_note-history-2" title="">[3]</a></sup> PHP 5 included new features such as improved support for <a target="_blank" href="http://en.wikipedia.org/wiki/Object-oriented_programming" title="Object-oriented programming">object-oriented programming</a>, the PHP Data Objects extension (which defines a lightweight and consistent interface for accessing databases), and numerous performance enhancements.<sup id="cite_ref-9" class="reference"><a href="#cite_note-9" title="">[10]</a></sup> The most recent update released by The PHP Group is for the older PHP version 4 code branch. As of January 2008, this branch is up to version 4.4.8. PHP 4 will be supported by security updates until <a target="_blank" href="http://en.wikipedia.org/wiki/August_8" title="August 8">August 8</a>, <a target="_blank" href="http://en.wikipedia.org/wiki/2008" title="2008">2008</a>.<sup id="cite_ref-2007_news_10-0" class="reference"><a href="#cite_note-2007_news-10" title="">[11]</a></sup></p>
+
+<p>PHP 5 is the only stable version still being developed. <a target="_blank" href="http://en.wikipedia.org/wiki/Late_static_binding" class="mw-redirect" title="Late static binding">Late static binding</a> has been missing from PHP and will be added in version 5.3.<sup id="cite_ref-11" class="reference"><a href="#cite_note-11" title="">[12]</a></sup> <sup id="cite_ref-12" class="reference"><a href="#cite_note-12" title="">[13]</a></sup> Development on PHP 4 ceased at the end of 2007, except for the critical security updates for PHP 4 already mentioned.<sup id="cite_ref-13" class="reference"><a href="#cite_note-13" title="">[14]</a></sup><sup id="cite_ref-2007_news_10-1" class="reference"><a href="#cite_note-2007_news-10" title="">[11]</a></sup> PHP 6 is now under development and major changes include the removal of <code>register_globals</code><sup id="cite_ref-14" class="reference"><a href="#cite_note-14" title="">[15]</a></sup>, <a target="_blank" href="http://en.wikipedia.org/wiki/Magic_quotes" title="Magic quotes">magic quotes</a>, and <a target="_blank" href="http://en.wikipedia.org/wiki/Safe_mode#Application_software_safe_mode" title="Safe mode">safe mode</a>.<sup id="cite_ref-2007_news_10-2" class="reference"><a href="#cite_note-2007_news-10" title="">[11]</a></sup><sup id="cite_ref-15" class="reference"><a href="#cite_note-15" title="">[16]</a></sup> PHP does not have complete native support for <a target="_blank" href="http://en.wikipedia.org/Unicode" title="Unicode">Unicode</a> or multibyte strings;<sup id="cite_ref-16" class="reference"><a href="#cite_note-16" title="">[17]</a></sup> unicode support will be added in PHP 6.<sup id="cite_ref-17" class="reference"><a href="#cite_note-17" title="">[18]</a></sup> Many high profile open source projects ceased to support PHP 4 in new code as of <a target="_blank" href="http://en.wikipedia.org/wiki/February_5" title="February 5">February 5</a>, <a target="_blank" href="http://en.wikipedia.org/wiki/2008" title="2008">2008</a>, due to the GoPHP5 initiative, provided by a consortium of PHP developers promoting the transition from PHP 4 to PHP 5.<sup id="cite_ref-gophp5_18-0" class="reference"><a href="#cite_note-gophp5-18" title="">[19]</a></sup><sup id="cite_ref-19" class="reference"><a href="#cite_note-19" title="">[20]</a></sup></p>
diff --git a/web/modules/contrib/advanced_help/help_example/help/security.html b/web/modules/contrib/advanced_help/help_example/help/security.html
new file mode 100644 (file)
index 0000000..31d43dc
--- /dev/null
@@ -0,0 +1 @@
+<p>PHP is a popular target of <a target="_blank" href="http://en.wikipedia.org/wiki/Hacker" title="Hacker">hackers</a> who exploit vulnerable applications written in PHP. Software vulnerabilities related to PHP are identified among the <a target="_blank" href="http://en.wikipedia.org/wiki/Common_Vulnerabilities_and_Exposures" title="Common Vulnerabilities and Exposures">CVE (Common Vulnerabilities and Exposures)</a> records, available from the <a target="_blank" href="http://en.wikipedia.org/wiki/National_Vulnerability_Database" title="National Vulnerability Database">National Vulnerability Database</a>. The proportion of vulnerabilities related to PHP, out of the total of all common vulnerabilities, amounted to: 12% in 2003, 20% in 2004, 28% in 2005, 43% in 2006, 36% in 2007, and 33.8% for the first quarter of 2008. More than a quarter of all software vulnerabilities listed in this database are related to PHP, and more than a third of vulnerabilities listed recently. Most of these vulnerabilities can be exploited remotely, that is without being logged on the computer hosting the vulnerable application.<sup id="cite_ref-27" class="reference"><a href="#cite_note-27" title="">[28]</a></sup> Such exploitation is made possible due to poor programming habits, such as failing to check data before entering it into a database, and features of the language such as <code>register_globals</code>, which is now deprecated.<sup id="cite_ref-register_globals_21-1" class="reference"><a href="#cite_note-register_globals-21" title="">[22]</a></sup> These result in <a target="_blank" href="http://en.wikipedia.org/wiki/Code_injection" title="Code injection">code injection</a>, <a target="_blank" href="http://en.wikipedia.org/wiki/Cross-site_scripting" title="Cross-site scripting">cross-site scripting</a> and other <a target="_blank" href="http://en.wikipedia.org/wiki/Application_security" title="Application security">application security</a> issues. It's important to note that none of these attacks are exclusive to PHP and all are avoidable by following proper coding techniques and principles.</p>
diff --git a/web/modules/contrib/advanced_help/help_example/help/syntax.html b/web/modules/contrib/advanced_help/help_example/help/syntax.html
new file mode 100644 (file)
index 0000000..48d8109
--- /dev/null
@@ -0,0 +1,38 @@
+<h2> <span class="mw-headline">Syntax</span></h2>
+<div class="help-right">
+<div class="thumbinner" style="width:182px;"><a target="_blank" href="http://en.wikipedia.org/wiki/Image:PHP_Hello_World_screenshot.png" class="image" title="Syntax-highlighted PHP code"><img alt="Syntax-highlighted PHP code" src="path:180px-PHP_Hello_World_screenshot.png" width="180" height="87" border="0" class="thumbimage" /></a>
+<div class="thumbcaption">
+
+<div class="magnify"><a target="_blank" href="http://en.wikipedia.org/wiki/Image:PHP_Hello_World_screenshot.png" class="internal" title="Enlarge"><img src="/skins-1.5/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>
+<a target="_blank" href="http://en.wikipedia.org/wiki/Syntax-highlighted" class="mw-redirect" title="Syntax-highlighted">Syntax-highlighted</a> PHP code</div>
+</div>
+</div>
+<p>PHP only parses code within its <a target="_blank" href="http://en.wikipedia.org/wiki/Delimiter" title="Delimiter">delimiters</a>. Anything outside its delimiters is sent directly to the output and is not parsed by PHP. The most common delimiters are <span s>&lt;?php</span> and <span>?&gt;</span>, which are open and close delimiters respectively. <span>&lt;script language="php"&gt;</span> and <span>&lt;/script&gt;</span> delimiters are also available. Short tags (<span>&lt;?</span> or <span>&lt;?=</span> and <span>?&gt;</span>) are also commonly used, but like ASP-style tags (<span>&lt;%</span> or <span>&lt;%=</span> and <span>%&gt;</span>), they are less portable as they can be disabled in the PHP configuration. For this reason, the use of short tags and ASP-style tags is discouraged.<sup id="cite_ref-basic_syntax_28-0" class="reference"><a href="#cite_note-basic_syntax-28" title="">[29]</a></sup> The purpose of these delimiters is to separate PHP code from non-PHP code, including HTML. Everything outside the delimiters is ignored by the parser and is passed through as output.<sup id="cite_ref-29" class="reference"><a href="#cite_note-29" title="">[30]</a></sup></p>
+
+<p>Variables are prefixed with a <a target="_blank" href="http://en.wikipedia.org/wiki/Dollar_sign" title="Dollar sign">dollar symbol</a> and a <a target="_blank" href="http://en.wikipedia.org/wiki/Primitive_type" title="Primitive type">type</a> does not need to be specified in advance. Unlike function and class names, variable names are case sensitive. Both double-quoted (<span>""</span>) and <a target="_blank" href="http://en.wikipedia.org/wiki/Heredoc" class="mw-redirect" title="Heredoc">heredoc</a> strings allow the ability to embed the variable's value into the string.<sup id="cite_ref-30" class="reference"><a href="#cite_note-30" title="">[31]</a></sup> PHP treats <a target="_blank" href="http://en.wikipedia.org/wiki/Newline" title="Newline">newlines</a> as <a target="_blank" href="http://en.wikipedia.org/wiki/Whitespace_%28computer_science%29" title="Whitespace (computer science)">whitespace</a> in the manner of a <a target="_blank" href="http://en.wikipedia.org/wiki/Free-form_language" title="Free-form language">free-form language</a> (except when inside string quotes), and statements are terminated by a semicolon.<sup id="cite_ref-31" class="reference"><a href="#cite_note-31" title="">[32]</a></sup> PHP has three types of <a target="_blank" href="http://en.wikipedia.org/wiki/Comparison_of_programming_languages_%28syntax%29#Comments" title="Comparison of programming languages (syntax)">comment syntax</a>: <span>/* */</span> serves as block comments, and <span>//</span> as well as <span>#</span> are used for inline comments.<sup id="cite_ref-32" class="reference"><a href="#cite_note-32" title="">[33]</a></sup> To output text to the browser, either the <tt>print</tt> function or the <tt>echo</tt> function is used. Both functions are nearly identical; the major difference is that <tt>print</tt> is slower than <tt>echo</tt> because the former will return a status indicating if it was successful or not, whereas the latter does not return a status and only returns the text for output.<sup id="cite_ref-33" class="reference"><a href="#cite_note-33" title="">[34]</a></sup></p>
+
+<p><a name="Data_types" id="Data_types"></a></p>
+<h3><span class="mw-headline">Data types</span></h3>
+<p>PHP stores whole numbers in a platform-dependent range. This range is typically that of 32-bit <a target="_blank" href="http://en.wikipedia.org/wiki/Signed_number_representations" title="Signed number representations">signed</a> <a target="_blank" href="http://en.wikipedia.org/wiki/Integer_%28computer_science%29" title="Integer (computer science)">integers</a>. Unsigned integers are converted to signed values in certain situations; this behavior is different from other programming languages.<sup id="cite_ref-34" class="reference"><a href="#cite_note-34" title="">[35]</a></sup> Integer variables can be assigned using decimal (positive and negative), <a target="_blank" href="http://en.wikipedia.org/wiki/Octal" title="Octal">octal</a>, and <a target="_blank" href="http://en.wikipedia.org/wiki/Hexadecimal" title="Hexadecimal">hexadecimal</a> notations. <a target="_blank" href="http://en.wikipedia.org/wiki/Real_numbers" class="mw-redirect" title="Real numbers">Real numbers</a> are also stored in a platform-specific range. They can be specified using <a target="_blank" href="http://en.wikipedia.org/wiki/Floating_point" title="Floating point">floating point</a> notation, or two forms of <a target="_blank" href="http://en.wikipedia.org/wiki/Scientific_notation" title="Scientific notation">scientific notation</a>.<sup id="cite_ref-types_35-0" class="reference"><a href="#cite_note-types-35" title="">[36]</a></sup> PHP has a native <a target="_blank" href="http://en.wikipedia.org/wiki/Boolean_datatype" title="Boolean datatype">Boolean</a> type that is similar to the native Boolean types in <a target="_blank" href="http://en.wikipedia.org/wiki/Java_%28programming_language%29" title="Java (programming language)">Java</a> and <a target="_blank" href="http://en.wikipedia.org/wiki/C%2B%2B" title="C++">C++</a>. Using the Boolean type conversion rules, non-zero values are interpreted as true and zero as false, as in Perl and C++.<sup id="cite_ref-types_35-1" class="reference"><a href="#cite_note-types-35" title="">[36]</a></sup> The null data type represents a variable that has no value. The only value in the null data type is <i>NULL</i>.<sup id="cite_ref-types_35-2" class="reference"><a href="#cite_note-types-35" title="">[36]</a></sup> Variables of the "resource" type represent references to resources from external sources. These are typically created by functions from a particular extension, and can only be processed by functions from the same extension; examples include file, image, and database resources.<sup id="cite_ref-types_35-3" class="reference"><a href="#cite_note-types-35" title="">[36]</a></sup> Arrays can contain elements of any type that PHP can handle, including resources, objects, and even other arrays. Order is preserved in lists of values and in <a target="_blank" href="http://en.wikipedia.org/wiki/Hash_table" title="Hash table">hashes</a> with both keys and values, and the two can be intermingled.<sup id="cite_ref-types_35-4" class="reference"><a href="#cite_note-types-35" title="">[36]</a></sup> PHP also supports <a target="_blank" href="http://en.wikipedia.org/wiki/String_%28computing%29" class="mw-redirect" title="String (computing)">strings</a>, which can be used with single quotes, double quotes, or <a target="_blank" href="http://en.wikipedia.org/wiki/Heredoc" class="mw-redirect" title="Heredoc">heredoc syntax</a>.<sup id="cite_ref-36" class="reference"><a href="#cite_note-36" title="">[37]</a></sup></p>
+
+<p><a name="Functions" id="Functions"></a></p>
+<h3><span class="mw-headline">Functions</span></h3>
+<p>PHP has hundreds of base functions and thousands more from extensions. Functions are not <a target="_blank" href="http://en.wikipedia.org/wiki/First-class_function" title="First-class function">first-class functions</a> and can only be referenced by their name. <sup id="cite_ref-functions_37-0" class="reference"><a href="#cite_note-functions-37" title="">[38]</a></sup> User-defined functions can be created at any time without being prototyped.<sup id="cite_ref-functions_37-1" class="reference"><a href="#cite_note-functions-37" title="">[38]</a></sup> Functions can be defined inside code blocks, permitting a <a target="_blank" href="http://en.wikipedia.org/wiki/Dynamic_dispatch" title="Dynamic dispatch">run-time decision</a> as to whether or not a function should be defined. Function calls must use parentheses, with the exception of zero argument class <a target="_blank" href="http://en.wikipedia.org/wiki/Constructor_%28computer_science%29" title="Constructor (computer science)">constructor</a> functions called with the PHP <span>new</span> operator, where parentheses are optional. PHP supports quasi-<a target="_blank" href="http://en.wikipedia.org/wiki/Anonymous_function" title="Anonymous function">anonymous functions</a> through the <span>create_function()</span> function, although they are not true anonymous functions because anonymous functions are nameless, but functions can only be referenced by name, or indirectly through a variable <span>$function_name();</span>, in PHP.<sup id="cite_ref-functions_37-2" class="reference"><a href="#cite_note-functions-37" title="">[38]</a></sup></p>
+
+<p><a name="Objects" id="Objects"></a></p>
+<h3><span class="mw-headline">Objects</span></h3>
+<p>Basic <a target="_blank" href="http://en.wikipedia.org/wiki/Object-oriented_programming" title="Object-oriented programming">object-oriented programming</a> functionality was added in PHP 3.<sup id="cite_ref-history_2-10" class="reference"><a href="#cite_note-history-2" title="">[3]</a></sup> Object handling was completely rewritten for PHP 5, expanding the feature set and enhancing performance.<sup id="cite_ref-php_5_objects_38-0" class="reference"><a href="#cite_note-php_5_objects-38" title="">[39]</a></sup> In previous versions of PHP, objects were handled like <a target="_blank" href="http://en.wikipedia.org/wiki/Primitive_type" title="Primitive type">primitive types</a>.<sup id="cite_ref-php_5_objects_38-1" class="reference"><a href="#cite_note-php_5_objects-38" title="">[39]</a></sup> The drawback of this method was that the whole object was copied when a variable was assigned or passed as a parameter to a method. In the new approach, objects are referenced by <a target="_blank" href="http://en.wikipedia.org/wiki/Smart_pointer#Handles" title="Smart pointer">handle</a>, and not by value. PHP 5 introduced private and protected <a target="_blank" href="http://en.wikipedia.org/wiki/Member_variable" class="mw-redirect" title="Member variable">member variables</a> and methods, along with <a target="_blank" href="http://en.wikipedia.org/wiki/Abstract_type" title="Abstract type">abstract classes</a> and <a target="_blank" href="http://en.wikipedia.org/wiki/Final_type" class="mw-redirect" title="Final type">final classes</a> as well as <a target="_blank" href="http://en.wikipedia.org/wiki/Abstract_method" class="mw-redirect" title="Abstract method">abstract methods</a> and <a target="_blank" href="http://en.wikipedia.org/wiki/Final_method" class="mw-redirect" title="Final method">final methods</a>. It also introduced a standard way of declaring <a target="_blank" href="http://en.wikipedia.org/wiki/Constructor_%28computer_science%29" title="Constructor (computer science)">constructors</a> and <a target="_blank" href="http://en.wikipedia.org/wiki/Destructor_%28computer_science%29" title="Destructor (computer science)">destructors</a>, similar to that of other object-oriented languages such as <a target="_blank" href="http://en.wikipedia.org/wiki/C%2B%2B" title="C++">C++</a>, and a standard <a target="_blank" href="http://en.wikipedia.org/wiki/Exception_handling" title="Exception handling">exception handling</a> model. Furthermore, PHP 5 added <a target="_blank" href="http://en.wikipedia.org/wiki/Interfaces" class="mw-redirect" title="Interfaces">interfaces</a> and allowed for multiple interfaces to be implemented. There are special interfaces that allow objects to interact with the runtime system. <a target="_blank" href="http://en.wikipedia.org/wiki/Object" title="Object">Objects</a> implementing <a target="_blank" href="http://en.wikipedia.org/wiki/ArrayAccess" class="mw-redirect" title="ArrayAccess">ArrayAccess</a> can be used with array syntax and <a target="_blank" href="http://en.wikipedia.org/wiki/Object" title="Object">objects</a> implementing <a target="_blank" href="http://en.wikipedia.org/wiki/Iterator" title="Iterator">Iterator</a> or <a target="_blank" href="http://en.wikipedia.org/wiki/IteratorAggregate" class="mw-redirect" title="IteratorAggregate">IteratorAggregate</a> can be used with the <span>foreach</span> language construct. There is no <a target="_blank" href="http://en.wikipedia.org/wiki/Virtual_table" class="mw-redirect" title="Virtual table">virtual table</a> feature in the engine, so <a target="_blank" href="http://en.wikipedia.org/wiki/Static_variable" title="Static variable">static variables</a> are bound with a name instead of a reference at compile time.<sup id="cite_ref-zend_engine_2_39-0" class="reference"><a href="#cite_note-zend_engine_2-39" title="">[40]</a></sup></p>
+
+<p>If the developer creates a copy of an object using the reserved word <i>clone</i>, the Zend engine will check if a <tt>__clone()</tt> method has been defined or not. If not, it will call a default <tt>__clone()</tt> which will copy the object's properties. If a <tt>__clone()</tt> method is defined, then it will be responsible for setting the necessary properties in the created object. For convenience, the engine will supply a function that imports the properties of the source object, so that the programmer can start with a by-value <a href="http://en.wiktionary.org/wiki/replica" class="extiw" title="wikt:replica">replica</a> of the source object and only override properties that need to be changed.<sup id="cite_ref-40" class="reference"><a href="#cite_note-40" title="">[41]</a></sup></p>
+
+<p><a name="Resources" id="Resources"></a></p>
+<h2> <span class="mw-headline">Resources</span></h2>
+<p>PHP includes <a target="_blank" href="http://en.wikipedia.org/wiki/List_of_PHP_libraries" title="List of PHP libraries">free and open source libraries</a> with the core build. PHP is a fundamentally <a target="_blank" href="http://en.wikipedia.org/wiki/Internet" title="Internet">Internet</a>-aware system with modules built in for accessing <a target="_blank" href="http://en.wikipedia.org/wiki/File_transfer_protocol" class="mw-redirect" title="File transfer protocol">FTP</a> servers, many database servers, embedded SQL libraries such as embedded <a target="_blank" href="http://en.wikipedia.org/wiki/MySQL" title="MySQL">MySQL</a> and <a target="_blank" href="http://en.wikipedia.org/wiki/SQLite" title="SQLite">SQLite</a>, <a target="_blank" href="http://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol" title="Lightweight Directory Access Protocol">LDAP</a> servers, and others. Many functions familiar to C programmers such as those in the <tt><a target="_blank" href="http://en.wikipedia.org/wiki/Stdio.h" title="Stdio.h">stdio</a></tt> family are available in the standard PHP build.<sup id="cite_ref-41" class="reference"><a href="#cite_note-41" title="">[42]</a></sup> PHP has traditionally used features such as "<a target="_blank" href="http://en.wikipedia.org/wiki/Magic_quotes" title="Magic quotes">magic_quotes_gpc</a>" and "magic_quotes_runtime" which attempt to escape apostrophes (') and quotes (") in strings in the assumption that they will be used in databases, to prevent <a target="_blank" href="http://en.wikipedia.org/wiki/SQL_injection" title="SQL injection">SQL injection</a> attacks. This leads to confusion over which data is escaped and which is not, and to problems when data is not in fact used as input to a database and when the escaping used is not completely correct.<sup id="cite_ref-42" class="reference"><a href="#cite_note-42" title="">[43]</a></sup> To make code portable between servers which do and do not use magic quotes, developers can preface their code with a script to reverse the effect of magic quotes when it is applied.<sup id="cite_ref-43" class="reference"><a href="#cite_note-43" title="">[44]</a></sup></p>
+
+<p>PHP allows developers to write <a target="_blank" href="http://en.wikipedia.org/wiki/Extension_%28computing%29" class="mw-redirect" title="Extension (computing)">extensions</a> in <a target="_blank" href="http://en.wikipedia.org/wiki/C_%28programming_language%29" title="C (programming language)">C</a> to add functionality to the PHP language. These can then be compiled into PHP or loaded dynamically at runtime. Extensions have been written to add support for the <a target="_blank" href="http://en.wikipedia.org/wiki/Windows_API" title="Windows API">Windows API</a>, process management on <a target="_blank" href="http://en.wikipedia.org/wiki/Unix-like" title="Unix-like">Unix-like</a> <a target="_blank" href="http://en.wikipedia.org/wiki/Operating_system" title="Operating system">operating systems</a>, multibyte strings (<a target="_blank" href="http://en.wikipedia.org/wiki/Unispan" title="Unispan">Unispan</a>), <a target="_blank" href="http://en.wikipedia.org/wiki/CURL" title="CURL">cURL</a>, and several popular <a target="_blank" href="http://en.wikipedia.org/wiki/Compression_formats" class="mw-redirect" title="Compression formats">compression formats</a>. Some more unusual features include integration with <a target="_blank" href="http://en.wikipedia.org/wiki/Internet_relay_chat" class="mw-redirect" title="Internet relay chat">Internet relay chat</a>, dynamic generation of images and <a target="_blank" href="http://en.wikipedia.org/wiki/Adobe_Flash" title="Adobe Flash">Adobe Flash</a> content, and even <a target="_blank" href="http://en.wikipedia.org/wiki/Speech_synthesis" title="Speech synthesis">speech synthesis</a>. The <a target="_blank" href="http://en.wikipedia.org/wiki/PHP_Extension_Community_Library" class="mw-redirect" title="PHP Extension Community Library">PHP Extension Community Library</a> (PECL) project is a repository for extensions to the PHP language.<sup id="cite_ref-44" class="reference"><a href="#cite_note-44" title="">[45]</a></sup></p>
+
+<p>As with many scripting languages, PHP scripts are normally kept as human-readable source code, even on production web servers.<sup id="cite_ref-45" class="reference"><a href="#cite_note-45" title="">[46]</a></sup> While this allows flexibility, releasing scripts in source form is undesirable for commercial software developers, and can raise issues with security of web servers; as an example, if a hacker acquires control of a server, database passwords may be quickly discovered, and undesirable changes to scripts may be made that remain undiscovered indefinitely. Various encoding tools are available for PHP to offer code protection.<sup class="noprint Template-Fact"><span title="This claim needs references to reliable sources&#160;since March 2008" style="white-space: nowrap;">[<i><a target="_blank" href="http://en.wikipedia.org/wiki/Wikipedia:Citation_needed" title="Wikipedia:Citation needed">citation needed</a></i>]</span></sup></p>
+<p>span optimizers improve the quality of the compiled code by reducing its size and making changes that can reduce the execution time and improve performance. The nature of the PHP <a target="_blank" href="http://en.wikipedia.org/wiki/Compiler" title="Compiler">compiler</a> is such that there are often opportunities for <a target="_blank" href="http://en.wikipedia.org/wiki/Optimization_%28computer_science%29" title="Optimization (computer science)">span optimization</a><sup id="cite_ref-46" class="reference"><a href="#cite_note-46" title="">[47]</a></sup>, and an example of a code optimizer is the <a target="_blank" href="http://en.wikipedia.org/wiki/PHP_accelerator#Zend_Optimizer" title="PHP accelerator">Zend Optimizer</a> PHP extension.<sup id="cite_ref-47" class="reference"><a href="#cite_note-47" title="">[48]</a></sup></p>
+
+<p><a target="_blank" href="http://en.wikipedia.org/wiki/PHP_accelerator" title="PHP accelerator">PHP accelerators</a> can offer significant performance gains by <a target="_blank" href="http://en.wikipedia.org/wiki/Caching" class="mw-redirect" title="Caching">caching</a> the compiled form of a PHP script in <a target="_blank" href="http://en.wikipedia.org/wiki/Shared_memory" title="Shared memory">shared memory</a> to avoid the overhead of <a target="_blank" href="http://en.wikipedia.org/wiki/Parsing" title="Parsing">parsing</a> and <a target="_blank" href="http://en.wikipedia.org/wiki/Compiling" class="mw-redirect" title="Compiling">compiling</a> the code every time the script runs. They may also perform <a target="_blank" href="http://en.wikipedia.org/wiki/span_optimization" class="mw-redirect" title="span optimization">span optimization</a> to provide increased execution performance.<sup class="noprint Template-Fact"><span title="This claim needs references to reliable sources&#160;since March 2008" style="white-space: nowrap;">[<i><a target="_blank" href="http://en.wikipedia.org/wiki/Wikipedia:Citation_needed" title="Wikipedia:Citation needed">citation needed</a></i>]</span></sup></p>
+
diff --git a/web/modules/contrib/advanced_help/help_example/help/usage.html b/web/modules/contrib/advanced_help/help_example/help/usage.html
new file mode 100644 (file)
index 0000000..8f1e9ad
--- /dev/null
@@ -0,0 +1,9 @@
+<p>PHP is a general-purpose scripting language that is especially suited for <a target="_blank" href="http://en.wikipedia.org/wiki/Web_development" title="Web development">web development</a>. It is the fourth most popular computer programming language, ranking behind <a target="_blank" href="http://en.wikipedia.org/wiki/Java_%28programming_language%29" title="Java (programming language)">Java</a>, <a target="_blank" href="http://en.wikipedia.org/wiki/C_%28programming_language%29" title="C (programming language)">C</a>, and <a target="_blank" href="http://en.wikipedia.org/wiki/Visual_Basic" title="Visual Basic">Visual Basic</a>.<sup id="cite_ref-22" class="reference"><a href="#cite_note-22" title="">[23]</a></sup> PHP generally runs on a <a target="_blank" href="http://en.wikipedia.org/wiki/Web_server" title="Web server">web server</a>, taking PHP code as its input and creating <a target="_blank" href="http://en.wikipedia.org/wiki/Web_page" title="Web page">web pages</a> as output. It can also be used for <a target="_blank" href="http://en.wikipedia.org/wiki/Command-line" class="mw-redirect" title="Command-line">command-line</a> scripting and <a target="_blank" href="http://en.wikipedia.org/wiki/Client-side" title="Client-side">client-side</a> <a target="_blank" href="http://en.wikipedia.org/wiki/Graphical_user_interface" title="Graphical user interface">GUI</a> applications. PHP can be deployed on most <a target="_blank" href="http://en.wikipedia.org/wiki/Web_server" title="Web server">web servers</a>, many <a target="_blank" href="http://en.wikipedia.org/wiki/Operating_system" title="Operating system">operating systems</a> and <a target="_blank" href="http://en.wikipedia.org/wiki/Platform_%28computing%29" class="mw-redirect" title="Platform (computing)">platforms</a>, and can be used with many <a target="_blank" href="http://en.wikipedia.org/wiki/Relational_database_management_system" title="Relational database management system">relational database management systems</a>. It is available free of charge, and the PHP Group provides the complete source code for users to build, customize and extend for their own use.<sup id="cite_ref-foundations_4-1" class="reference"><a href="#cite_note-foundations-4" title="">[5]</a></sup></p>
+
+<p>PHP primarily acts as a <a target="_blank" href="http://en.wikipedia.org/wiki/Filter_%28software%29" title="Filter (software)">filter</a><sup id="cite_ref-23" class="reference"><a href="#cite_note-23" title="">[24]</a></sup>, taking input from a file or stream containing text and/or PHP instructions and outputs another stream of data; most commonly the output will be HTML. From PHP 4, the PHP <a target="_blank" href="http://en.wikipedia.org/wiki/Parser" class="mw-redirect" title="Parser">parser</a> <a target="_blank" href="http://en.wikipedia.org/wiki/Compiler" title="Compiler">compiles</a> input to produce <a target="_blank" href="http://en.wikipedia.org/wiki/Bytecode" title="Bytecode">bytecode</a> for processing by the <a target="_blank" href="http://en.wikipedia.org/wiki/Zend_Engine" title="Zend Engine">Zend Engine</a>, giving improved performance over its <a target="_blank" href="http://en.wikipedia.org/wiki/Interpreter_%28computing%29" title="Interpreter (computing)">interpreter</a> predecessor.<sup id="cite_ref-24" class="reference"><a href="#cite_note-24" title="">[25]</a></sup></p>
+
+<p>Originally designed to create dynamic web pages, PHP's principal focus is <a target="_blank" href="http://en.wikipedia.org/wiki/Server-side_scripting" title="Server-side scripting">server-side scripting</a><sup id="cite_ref-25" class="reference"><a href="#cite_note-25" title="">[26]</a></sup>, and it is similar to other server-side scripting languages that provide dynamic content from a web server to a <a target="_blank" href="http://en.wikipedia.org/wiki/Client_%28computing%29" title="Client (computing)">client</a>, such as <a target="_blank" href="http://en.wikipedia.org/wiki/Microsoft" title="Microsoft">Microsoft</a>'s <a target="_blank" href="http://en.wikipedia.org/wiki/ASP.NET" title="ASP.NET">ASP.NET</a> system, <a target="_blank" href="http://en.wikipedia.org/wiki/Sun_Microsystems" title="Sun Microsystems">Sun Microsystems</a>' <a target="_blank" href="http://en.wikipedia.org/wiki/JavaServer_Pages" title="JavaServer Pages">JavaServer Pages</a><sup id="cite_ref-26" class="reference"><a href="#cite_note-26" title="">[27]</a></sup>, and <a target="_blank" href="http://en.wikipedia.org/wiki/Mod_perl" title="Mod perl">mod_perl</a>. PHP has also attracted the development of many <a target="_blank" href="http://en.wikipedia.org/wiki/Software_framework" title="Software framework">frameworks</a> that provide building blocks and a design structure to promote <a target="_blank" href="http://en.wikipedia.org/wiki/Rapid_application_development" title="Rapid application development">rapid application development</a> (RAD). Some of these include <a target="_blank" href="http://en.wikipedia.org/wiki/CakePHP" title="CakePHP">CakePHP</a>, <a target="_blank" href="http://en.wikipedia.org/wiki/PRADO" title="PRADO">PRADO</a>, <a target="_blank" href="http://en.wikipedia.org/wiki/Symfony" title="Symfony">Symfony</a> and <a target="_blank" href="http://en.wikipedia.org/wiki/Zend_Framework" title="Zend Framework">Zend Framework</a>, offering features similar to other <a target="_blank" href="http://en.wikipedia.org/wiki/List_of_web_application_frameworks" title="List of web application frameworks">web application frameworks</a>.</p>
+
+<p>The <a target="_blank" href="http://en.wikipedia.org/wiki/LAMP_%28software_bundle%29" title="LAMP (software bundle)">LAMP</a> architecture has become popular in the web industry as a way of deploying web applications. PHP is commonly used as the <i>P</i> in this bundle alongside <a target="_blank" href="http://en.wikipedia.org/wiki/Linux" title="Linux">Linux</a>, <a target="_blank" href="http://en.wikipedia.org/wiki/Apache_HTTP_Server" title="Apache HTTP Server">Apache</a> and <a target="_blank" href="http://en.wikipedia.org/wiki/MySQL" title="MySQL">MySQL</a>, although the <i>P</i> can also refer to <a target="_blank" href="http://en.wikipedia.org/wiki/Python_%28programming_language%29" title="Python (programming language)">Python</a> or <a target="_blank" href="http://en.wikipedia.org/wiki/Perl" title="Perl">Perl</a>.</p>
+
+<p>As of April 2007, over 20 million Internet domains were hosted on servers with PHP installed, and PHP was recorded as the most popular Apache module.<sup id="cite_ref-usage_5-2" class="reference"><a href="#cite_note-usage-5" title="">[6]</a></sup></p>
diff --git a/web/modules/contrib/advanced_help/help_example/help_example.info b/web/modules/contrib/advanced_help/help_example/help_example.info
new file mode 100644 (file)
index 0000000..f396100
--- /dev/null
@@ -0,0 +1,4 @@
+name = Advanced help example
+description = A example help module to demonstrate the advanced help module.
+core = 7.x
+dependencies[] = advanced_help
diff --git a/web/modules/contrib/advanced_help/help_example/help_example.module b/web/modules/contrib/advanced_help/help_example/help_example.module
new file mode 100644 (file)
index 0000000..1815112
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+/**
+ * @file
+ * Provide example help for the advanced help module.
+ */
+
+/**
+ * Implements hook_menu().
+ */
+function help_example_menu() {
+  // View help topic index.
+  $items['admin/help_example'] = [
+    'title' => 'Example help',
+    'page callback' => 'help_example_index_page',
+    'access arguments' => ['view advanced help index'],
+    'weight' => 9,
+  ];
+  return $items;
+}
+
+/**
+ * Help topic index.
+ */
+function help_example_index_page() {
+  $output = theme('advanced_help_topic', [
+    'module' => 'help_example',
+    'topic' => 'about-php',
+  ]);
+  $output .= '&nbsp;' . t('Click the help icon to view some example help about the PHP programming language (from wikipedia.org). Be sure to run cron to update the index if you want to try out the search features.');
+  return $output;
+}
diff --git a/web/modules/contrib/advanced_help/images/help.png b/web/modules/contrib/advanced_help/images/help.png
new file mode 100644 (file)
index 0000000..7645023
Binary files /dev/null and b/web/modules/contrib/advanced_help/images/help.png differ
diff --git a/web/modules/contrib/advanced_help/src/AdvancedHelpManager.php b/web/modules/contrib/advanced_help/src/AdvancedHelpManager.php
new file mode 100644 (file)
index 0000000..db9abe5
--- /dev/null
@@ -0,0 +1,233 @@
+<?php
+
+namespace Drupal\advanced_help;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Extension\ThemeHandlerInterface;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Component\Serialization\Yaml;
+
+/**
+ * AdvancedHelp plugin manager.
+ */
+class AdvancedHelpManager extends DefaultPluginManager {
+  use StringTranslationTrait;
+
+  /**
+   * Constructs an AdvancedHelpManager instance.
+   *
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   Cache backend instance to use.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler to invoke the alter hook with.
+   * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
+   *   Theme handler.
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
+   *   The string translation service.
+   */
+  public function __construct(ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, CacheBackendInterface $cache_backend, TranslationInterface $string_translation) {
+    $this->module_handler = $module_handler;
+    $this->theme_handler = $theme_handler;
+    $this->setStringTranslation($string_translation);
+    $this->alterInfo('advanced_help');
+    $this->setCacheBackend($cache_backend, 'advanced_help', ['advanced_help']);
+  }
+
+  /**
+   * Get the modules/theme list.
+   * @todo cache
+   */
+  public function getModuleList() {
+    $modules = $this->module_handler->getModuleList();
+    $themes = $this->theme_handler->listInfo();
+    $result = [];
+
+    foreach ($modules + $themes  as $name => $data) {
+      $result[$name] = $this->module_handler->getName($name);
+    }
+    return $result;
+  }
+
+  /**
+   * Get the information for a single help topic.
+   * @param $module
+   * @param $topic
+   * @return string|bool
+   */
+  function getTopic($module, $topic) {
+    $topics = $this->getTopics();
+    if (!empty($topics[$module][$topic])) {
+      return $topics[$module][$topic];
+    }
+    return FALSE;
+  }
+
+  /**
+   * Return the name of the module.
+   * @param string $module
+   * @return string
+   */
+  function getModuleName($module) {
+    return $this->module_handler->getName($module);
+  }
+
+  /**
+   * Search the system for all available help topics.
+   * @todo check visibility of the method.
+   */
+  public function getTopics() {
+    $ini = $this->parseHelp();
+    return $ini['topics'];
+  }
+
+  /**
+   * Returns advanced help settings.
+   * @todo check visibility of the method.
+   */
+  public function getSettings() {
+    $ini = $this->parseHelp();
+    return $ini['settings'];
+  }
+
+
+  /**
+   * Function to parse yml / txt files.
+   *
+   * @todo implement cache
+   * @todo check visibility of the method.
+   */
+  public function parseHelp() {
+    $language = \Drupal::languageManager()->getCurrentLanguage()->getId();
+    static $ini = NULL;
+
+    $cache = $this->cacheGet('advanced_help_ini_' . $language);
+    if ($cache) {
+      $ini = $cache->data;
+    }
+
+    if (!isset($ini)) {
+      $ini = ['topics' => [], 'settings' => []];
+
+      foreach ($this->module_handler->getModuleList() + $this->theme_handler->listInfo() as $plugin_name => $extension) {
+        $module = $plugin_name;
+        $module_path = $extension->getPath();
+        $info = [];
+
+        if (file_exists("$module_path/help/$module.help.yml")) {
+          $path = "$module_path/help";
+          $info =  Yaml::decode(file_get_contents("$module_path/help/$module.help.yml"));
+        }
+        elseif (!file_exists("$module_path/help")) {
+          // Look for one or more README files.
+          $files = file_scan_directory("./$module_path",
+            '/^(readme).*\.(txt|md)$/i', ['recurse' => FALSE]);
+          $path = "./$module_path";
+          foreach ($files as $name => $fileinfo) {
+            $info[$fileinfo->filename] = [
+              'line break' => TRUE,
+              'readme file' => TRUE,
+              'file' => $fileinfo->filename,
+              'title' => $fileinfo->name,
+            ];
+          }
+        }
+
+        if (!empty($info)) {
+          // Get translated titles:
+          $translation = [];
+          if (file_exists("$module_path/translations/help/$language/$module.help.yml")) {
+            $translation = Yaml::decode(file_get_contents("$module_path/translations/help/$language/$module.help.yml"));
+          }
+
+          $ini['settings'][$module] = [];
+          if (!empty($info['advanced help settings'])) {
+            $ini['settings'][$module] = $info['advanced help settings'];
+            unset($info['advanced help settings']);
+
+            // Check translated strings for translatable global settings.
+            if (isset($translation['advanced help settings']['name'])) {
+              $ini['settings']['name'] = $translation['advanced help settings']['name'];
+            }
+            if (isset($translation['advanced help settings']['index name'])) {
+              $ini['settings']['index name'] = $translation['advanced help settings']['index name'];
+            }
+
+          }
+
+          foreach ($info as $name => $topic) {
+            // Each topic should have a name, a title, a file and path.
+            $file = !empty($topic['file']) ? $topic['file'] : $name;
+            $ini['topics'][$module][$name] = [
+              'name' => $name,
+              'module' => $module,
+              'ini' => $topic,
+              'title' => !empty($translation[$name]['title']) ? $translation[$name]['title'] : $topic['title'],
+              'weight' => isset($topic['weight']) ? $topic['weight'] : 0,
+              'parent' => isset($topic['parent']) ? $topic['parent'] : 0,
+              'popup width' => isset($topic['popup width']) ? $topic['popup width'] : 500,
+              'popup height' => isset($topic['popup height']) ? $topic['popup height'] : 500,
+              // Require extension.
+              'file' => isset($topic['readme file']) ? $file : $file . '.html',
+              // Not in .ini file.
+              'path' => $path,
+              'line break' => isset($topic['line break']) ? $topic['line break'] : (isset($ini['settings'][$module]['line break']) ? $ini['settings'][$module]['line break'] : FALSE),
+              'navigation' => isset($topic['navigation']) ? $topic['navigation'] : (isset($ini['settings'][$module]['navigation']) ? $ini['settings'][$module]['navigation'] : TRUE),
+              'css' => isset($topic['css']) ? $topic['css'] : (isset($ini['settings'][$module]['css']) ? $ini['settings'][$module]['css'] : NULL),
+              'readme file' => isset($topic['readme file']) ? $topic['readme file'] : FALSE,
+            ];
+          }
+        }
+      }
+      // drupal_alter('advanced_help_topic_info', $ini);
+      $this->cacheSet('advanced_help_ini_' . $language, $ini);
+    }
+    return $ini;
+  }
+
+  /**
+   * Load and render a help topic.
+   *
+   * @todo allow the theme override the help.
+   * @param $module.
+   * @param $topic.
+   * @return array.
+  */
+  public function getTopicFileInfo($module, $topic) {
+    $language = \Drupal::languageManager()->getCurrentLanguage()->getId();
+
+    $info = $this->getTopic($module, $topic);
+    if (empty($info)) {
+      return FALSE;
+    }
+
+    $path_type = (preg_match('/themes/', $info['path'])) ? 'theme' : 'module';
+    // Search paths:
+    $paths = [
+//      // Allow theme override.
+//      path_to_theme() . '/help',
+      // Translations.
+      drupal_get_path($path_type, $module) . "/translations/help/$language",
+      // In same directory as .inc file.
+      $info['path'],
+    ];
+
+    foreach ($paths as $path) {
+      if (file_exists("$path/$info[file]")) {
+        return ['path' => $path, 'file' => $info['file']];
+      }
+    }
+
+    return FALSE;
+  }
+
+  public function getTopicFileName($module, $topic) {
+    $info = $this->getTopicFileInfo($module, $topic);
+    if ($info) {
+      return "./{$info['path']}/{$info['file']}";
+    }
+  }
+
+}
diff --git a/web/modules/contrib/advanced_help/src/Controller/AdvancedHelpController.php b/web/modules/contrib/advanced_help/src/Controller/AdvancedHelpController.php
new file mode 100644 (file)
index 0000000..8e06706
--- /dev/null
@@ -0,0 +1,391 @@
+<?php
+
+namespace Drupal\advanced_help\Controller;
+
+use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Component\Utility\Xss;
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
+use Drupal\Core\Url;
+use Michelf\MarkdownExtra;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+
+/**
+ * Class AdvancedHelpController.
+ *
+ * @package Drupal\advanced_help\Controller\AdvancedHelpController.
+ */
+class AdvancedHelpController extends ControllerBase {
+
+  /**
+   * The advanced help plugin manager.
+   *
+   * @var \Drupal\Component\Plugin\PluginManagerInterface
+   */
+  private $advanced_help;
+
+
+  public function __construct(PluginManagerInterface $advanced_help) {
+    $this->advanced_help = $advanced_help;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('plugin.manager.advanced_help')
+    );
+  }
+
+  /**
+   * Content.
+   *
+   * @todo Implement search integration.
+   * @return array
+   *   Returns module index.
+   */
+  public function main() {
+    $topics = $this->advanced_help->getTopics();
+    $settings = $this->advanced_help->getSettings();
+
+    // Print a module index.
+    $modules = $this->advanced_help->getModuleList();
+    asort($modules);
+
+    $items = [];
+    foreach ($modules as $module => $module_name) {
+      if (!empty($topics[$module]) && empty($settings[$module]['hide'])) {
+        if (isset($settings[$module]['index name'])) {
+          $name = $settings[$module]['index name'];
+        }
+        elseif (isset($settings[$module]['name'])) {
+          $name = $settings[$module]['name'];
+        }
+        else {
+          $name = $this->t($module_name);
+        }
+        $items[] = $this->l($name, new Url('advanced_help.module_index', ['module' => $module]));
+      }
+    }
+
+    return [
+      'help_modules' => [
+        '#theme' => 'item_list',
+        '#items' => $items,
+        '#title' => $this->t('Module help index'),
+      ]
+    ];
+  }
+
+  /**
+   * Build a hierarchy for a single module's topics.
+   *
+   * @param $topics array.
+   * @return array.
+   */
+  private function getTopicHierarchy($topics) {
+    foreach ($topics as $module => $module_topics) {
+      foreach ($module_topics as $topic => $info) {
+        $parent_module = $module;
+        // We have a blank topic that we don't want parented to itself.
+        if (!$topic) {
+          continue;
+        }
+
+        if (empty($info['parent'])) {
+          $parent = '';
+        }
+        elseif (strpos($info['parent'], '%')) {
+          list($parent_module, $parent) = explode('%', $info['parent']);
+          if (empty($topics[$parent_module][$parent])) {
+            // If it doesn't exist, top level.
+            $parent = '';
+          }
+        }
+        else {
+          $parent = $info['parent'];
+          if (empty($module_topics[$parent])) {
+            // If it doesn't exist, top level.
+            $parent = '';
+          }
+        }
+
+        if (!isset($topics[$parent_module][$parent]['children'])) {
+          $topics[$parent_module][$parent]['children'] = [];
+        }
+        $topics[$parent_module][$parent]['children'][] = [$module, $topic];
+        $topics[$module][$topic]['_parent'] = [$parent_module, $parent];
+      }
+    }
+    return $topics;
+  }
+
+  /**
+   * Helper function to sort topics.
+   * @param string $id_a
+   * @param string $id_b
+   * @return mixed
+   */
+  private function helpUasort($id_a, $id_b) {
+    $topics = $this->advanced_help->getTopics();
+    list($module_a, $topic_a) = $id_a;
+    $a = $topics[$module_a][$topic_a];
+    list($module_b, $topic_b) = $id_b;
+    $b = $topics[$module_b][$topic_b];
+
+    $a_weight = isset($a['weight']) ? $a['weight'] : 0;
+    $b_weight = isset($b['weight']) ? $b['weight'] : 0;
+    if ($a_weight != $b_weight) {
+      return ($a_weight < $b_weight) ? -1 : 1;
+    }
+
+    if ($a['title'] != $b['title']) {
+      return ($a['title'] < $b['title']) ? -1 : 1;
+    }
+    return 0;
+  }
+
+  /**
+   * Build a tree of advanced help topics.
+   *
+   * @param array $topics
+   *   Topics.
+   * @param array $topic_ids
+   *   Topic Ids.
+   * @param int $max_depth
+   *   Maximum depth for subtopics.
+   * @param int $depth
+   *   Default depth for subtopics.
+   *
+   * @return array
+   *   Returns list of topics/subtopics.
+   */
+  private function getTree($topics, $topic_ids, $max_depth = -1, $depth = 0) {
+    uasort($topic_ids, [$this, 'helpUasort']);
+    $items = [];
+    foreach ($topic_ids as $info) {
+      list($module, $topic) = $info;
+      $item = $this->l($topics[$module][$topic]['title'], new Url('advanced_help.help', ['module' => $module, 'topic' => $topic]));
+      if (!empty($topics[$module][$topic]['children']) && ($max_depth == -1 || $depth < $max_depth)) {
+        $link = [
+          '#theme' => 'item_list',
+          '#items' => advanced_help_get_tree($topics, $topics[$module][$topic]['children'], $max_depth, $depth + 1),
+        ];
+        $item .= \Drupal::service('renderer')->render($link, FALSE);
+      }
+      $items[] = $item;
+    }
+
+    return $items;
+  }
+
+
+  public function moduleIndex($module) {
+    $topics = $this->advanced_help->getTopics();
+
+    if (empty($topics[$module])) {
+      throw new NotFoundHttpException();
+    }
+
+    $topics = $this->getTopicHierarchy($topics);
+    $items = $this->getTree($topics, $topics[$module]['']['children']);
+
+    return [
+      'index' => [
+        '#theme' => 'item_list',
+        '#items' => $items,
+      ]
+    ];
+  }
+
+  /**
+   * Set the name of the module in the index page.
+   *
+   * @param string $module Module name
+   * @return string
+   */
+  public function moduleIndexTitle($module) {
+    return $this->advanced_help->getModuleName($module) . ' help index';
+  }
+
+  public function topicPage(Request $request, $module, $topic) {
+    $is_modal = ($request->query->get(MainContentViewSubscriber::WRAPPER_FORMAT) === 'drupal_modal');
+    $info = $this->advanced_help->getTopic($module, $topic);
+    if (!$info) {
+      throw new NotFoundHttpException();
+    }
+
+    $parent = $info;
+    $pmodule = $module;
+
+    // Loop checker.
+    $checked = [];
+    while (!empty($parent['parent'])) {
+      if (strpos($parent['parent'], '%')) {
+        list($pmodule, $ptopic) = explode('%', $parent['parent']);
+      }
+      else {
+        $ptopic = $parent['parent'];
+      }
+
+      if (!empty($checked[$pmodule][$ptopic])) {
+        break;
+      }
+
+      $checked[$pmodule][$ptopic] = TRUE;
+
+      $parent = $this->advanced_help->getTopic($pmodule, $ptopic);
+      if (!$parent) {
+        break;
+      }
+
+    }
+
+    $build = $this->viewTopic($module, $topic, $is_modal);
+    if (empty($build['#markup'])) {
+      $build['#markup'] = $this->t('Missing help topic.');
+    }
+    $build['#attached']['library'][] = 'advanced_help/help';
+    return $build;
+  }
+
+    /**
+   * Load and render a help topic.
+   *
+   * @param string $module
+   *   Name of the module.
+   * @param string $topic
+   *   Name of the topic.
+   * @todo port the drupal_alter functionality.
+   *
+   * @return string
+   *   Returns formatted topic.
+   */
+  public function viewTopic($module, $topic, $is_modal = false) {
+    $file_info = $this->advanced_help->getTopicFileInfo($module, $topic);
+    if ($file_info) {
+      $info = $this->advanced_help->getTopic($module, $topic);
+      $file = "{$file_info['path']}/{$file_info['file']}";
+      $build = [
+        '#type' => 'markup',
+      ];
+
+      if (!empty($info['css'])) {
+        $build['#attached']['library'][] = $info['module'] . '/' . $info['css'];
+      }
+
+      $build['#markup'] = file_get_contents($file);
+      if (isset($info['readme file']) && $info['readme file']) {
+        $ext = pathinfo($file, PATHINFO_EXTENSION);
+        if ('md' == $ext) {
+          $build['#markup'] = '<div class="advanced-help-topic">' . Xss::filterAdmin(MarkdownExtra::defaultTransform($build['#markup'])) . '</div>';
+        }
+        return $build;
+      }
+
+      // Change 'topic:' to the URL for another help topic.
+      preg_match('/&topic:([^"]+)&/', $build['#markup'], $matches);
+      if (isset($matches[1]) && preg_match('/[\w\-]\/[\w\-]+/', $matches[1])) {
+        list($umodule, $utopic) = explode('/', $matches[1]);
+        $path = new Url('advanced_help.help', ['module' => $umodule, 'topic' => $utopic]);
+        $build['#markup'] = preg_replace('/&topic:([^"]+)&/', $path->toString(), $build['#markup']);
+      }
+
+      global $base_path;
+
+      // Change 'path:' to the URL to the base help directory.
+      $build['#markup'] = str_replace('&path&', $base_path . $info['path'] . '/', $build['#markup']);
+
+      // Change 'trans_path:' to the URL to the actual help directory.
+      $build['#markup'] = str_replace('&trans_path&', $base_path . $file_info['path'] . '/', $build['#markup']);
+
+      // Change 'base_url:' to the URL to the site.
+      $build['#markup'] = preg_replace('/&base_url&([^"]+)"/', $base_path . '$1' . '"', $build['#markup']);
+
+      // Run the line break filter if requested.
+      if (!empty($info['line break'])) {
+        // Remove the header since it adds an extra <br /> to the filter.
+        $build['#markup'] = preg_replace('/^<!--[^\n]*-->\n/', '', $build['#markup']);
+
+        $build['#markup'] = _filter_autop($build['#markup']);
+      }
+
+      if (!empty($info['navigation']) && !$is_modal) {
+        $topics = $this->advanced_help->getTopics();
+        $topics = $this->getTopicHierarchy($topics);
+        if (!empty($topics[$module][$topic]['children'])) {
+          $items = $this->getTree($topics, $topics[$module][$topic]['children']);
+          $links = [
+            '#theme' => 'item_list',
+            '#items' => $items
+          ];
+          $build['#markup'] .= \Drupal::service('renderer')->render($links, FALSE);
+        }
+
+        list($parent_module, $parent_topic) = $topics[$module][$topic]['_parent'];
+        if ($parent_topic) {
+          $parent = $topics[$module][$topic]['_parent'];
+          $up = new Url('advanced_help.help', ['module' => $parent[0], 'topic' => $parent[1]]);
+        }
+        else {
+          $up = new Url('advanced_help.module_index', ['module' => $module]);
+        }
+
+        $siblings = $topics[$parent_module][$parent_topic]['children'];
+        uasort($siblings, [$this, 'helpUasort']);
+        $prev = $next = NULL;
+        $found = FALSE;
+        foreach ($siblings as $sibling) {
+          list($sibling_module, $sibling_topic) = $sibling;
+          if ($found) {
+            $next = $sibling;
+            break;
+          }
+          if ($sibling_module == $module && $sibling_topic == $topic) {
+            $found = TRUE;
+            continue;
+          }
+          $prev = $sibling;
+        }
+
+        if ($prev || $up || $next) {
+          $navigation = '<div class="help-navigation clear-block">';
+
+          if ($prev) {
+            $navigation .= $this->l('«« ' . $topics[$prev[0]][$prev[1]]['title'], new Url('advanced_help.help', ['module' => $prev[0], 'topic' => $prev[1]], ['attributes' => ['class' => 'help-left']]));
+          }
+          if ($up) {
+            $navigation .= $this->l($this->t('Up'), $up->setOption('attributes', ['class' => ($prev) ? 'help-up' : 'help-up-noleft']));
+          }
+          if ($next) {
+            $navigation .= $this->l($topics[$next[0]][$next[1]]['title'] . ' »»', new Url('advanced_help.help', ['module' => $next[0], 'topic' => $next[1]], ['attributes' => ['class' => 'help-right']]));
+          }
+
+          $navigation .= '</div>';
+          $build['#markup'] .= $navigation;
+        }
+      }
+      $build['#markup'] = '<div class="advanced-help-topic">' . $build['#markup'] . '</div>';
+//      drupal_alter('advanced_help_topic', $output, $popup);
+      return $build;
+    }
+  }
+
+  /**
+   * Set the title of the topic.
+   * @param $module
+   * @param $topic
+   * @return string
+   */
+  public function topicPageTitle($module, $topic) {
+    $info = $this->advanced_help->getTopic($module, $topic);
+    if (!$info) {
+      throw new NotFoundHttpException();
+    }
+    return $info['title'];
+  }
+}
\ No newline at end of file
diff --git a/web/modules/contrib/advanced_help/src/HelpInterface.php b/web/modules/contrib/advanced_help/src/HelpInterface.php
new file mode 100644 (file)
index 0000000..1168749
--- /dev/null
@@ -0,0 +1,11 @@
+<?php
+
+namespace Drupal\advanced_help;
+
+use Drupal\Component\Plugin\PluginInspectionInterface;
+
+/**
+ * Defines an interface for ice cream flavor plugins.
+ */
+interface HelpInterface extends PluginInspectionInterface {
+}
\ No newline at end of file
diff --git a/web/modules/contrib/advanced_help/src/Plugin/Derivative/DynamicLocalTasks.php b/web/modules/contrib/advanced_help/src/Plugin/Derivative/DynamicLocalTasks.php
new file mode 100644 (file)
index 0000000..b4eea41
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+
+namespace Drupal\advanced_help\Plugin\Derivative;
+
+use Drupal\Component\Plugin\Derivative\DeriverBase;
+
+/**
+ * Defines dynamic local tasks.
+ */
+class DynamicLocalTasks extends DeriverBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinitions($base_plugin_definition) {
+
+    if (\Drupal::moduleHandler()->moduleExists('help')) {
+      $this->derivatives['advanced_help.help'] = $base_plugin_definition;
+      $this->derivatives['advanced_help.help']['title'] = "Help";
+      $this->derivatives['advanced_help.help']['route_name'] = 'help.main';
+      $this->derivatives['advanced_help.help']['base_route'] = 'help.main';
+
+      $this->derivatives['help.main'] = $base_plugin_definition;
+      $this->derivatives['help.main']['title'] = "Advanced Help";
+      $this->derivatives['help.main']['route_name'] = 'advanced_help.main';
+      $this->derivatives['help.main']['base_route'] = 'help.main';
+
+      return $this->derivatives;
+    }
+  }
+}
+?>
\ No newline at end of file
diff --git a/web/modules/contrib/advanced_help/src/Plugin/Search/AdvancedHelpSearch.php b/web/modules/contrib/advanced_help/src/Plugin/Search/AdvancedHelpSearch.php
new file mode 100644 (file)
index 0000000..3618df0
--- /dev/null
@@ -0,0 +1,267 @@
+<?php
+
+namespace Drupal\advanced_help\Plugin\Search;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Access\AccessibleInterface;
+use Drupal\search\Plugin\SearchPluginBase;
+use Drupal\advanced_help\AdvancedHelpManager;
+use Drupal\search\Plugin\SearchIndexingInterface;
+use Drupal\Core\Config\Config;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Executes a keyword search for Advanced Help against the {advanced_help} topic pages.
+ *
+ * @SearchPlugin(
+ *   id = "advanced_help_search",
+ *   title = @Translation("Advanced Help")
+ * )
+ */
+class AdvancedHelpSearch extends SearchPluginBase implements AccessibleInterface, SearchIndexingInterface {
+
+  /**
+   * The database connection.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $database;
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $currentUser;
+
+  /**
+   * Advanced Help Manager.
+   * @var \Drupal\advanced_help\AdvancedHelpManager
+   */
+  protected $advancedHelp;
+
+  /**
+   * A config object for 'search.settings'.
+   *
+   * @var \Drupal\Core\Config\Config
+   */
+  protected $searchSettings;
+
+
+  /**
+   * {@inheritdoc}
+   */
+  static public function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $container->get('database'),
+      $container->get('plugin.manager.advanced_help'),
+      $container->get('current_user'),
+      $container->get('config.factory')->get('search.settings'),
+      $configuration,
+      $plugin_id,
+      $plugin_definition
+    );
+  }
+
+  /**
+   * Creates a UserSearch object.
+   *
+   * @param Connection $database
+   *   The database connection.
+   * @param \Drupal\advanced_help\AdvancedHelpManager $advanced_help
+   *   The advanced Help manager.
+   * @param \Drupal\Core\Session\AccountInterface $current_user
+   *   The current user.
+   * @param array $configuration
+   * @param \Drupal\Core\Config\Config $search_settings
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   */
+  public function __construct(Connection $database, AdvancedHelpManager $advanced_help, AccountInterface $current_user, Config $search_settings, array $configuration, $plugin_id, $plugin_definition) {
+    $this->database = $database;
+    $this->advancedHelp = $advanced_help;
+    $this->currentUser = $current_user;
+    $this->searchSettings = $search_settings;
+
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+
+    $this->addCacheTags(['user_list']);
+  }
+
+  public function access($operation = 'view', AccountInterface $account = NULL, $return_as_object = FALSE) {
+    $result = AccessResult::allowedIf(!empty($account) && $account->hasPermission('access user profiles'))->cachePerPermissions();
+    return $return_as_object ? $result : $result->isAllowed();
+  }
+
+  /**
+   * Gets search id for each topic.
+   *
+   * Get or create an sid (search id) that correlates to each topic for
+   * the search system.
+   * @param array $topics
+   * @return array
+   */
+  private function getSids($topics) {
+    $language = \Drupal::languageManager()->getCurrentLanguage()->getId();
+    $result = $this->database->select('advanced_help_index', 'ahi')
+      ->fields('ahi', ['sid', 'module', 'topic', 'langcode'])
+      ->condition('langcode', $language)
+      ->execute();
+    foreach ($result as $sid) {
+      if (empty($topics[$sid->module][$sid->topic])) {
+        $this->database->query("DELETE FROM {advanced_help_index} WHERE sid = :sid", [':sid' => $sid->sid]);
+      }
+      else {
+        $topics[$sid->module][$sid->topic]['sid'] = $sid->sid;
+      }
+    }
+    return $topics;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute() {
+    if ($this->isSearchExecutable()) {
+      $keys = $this->keywords;
+
+      // Build matching conditions.
+      $query = $this->database
+        ->select('search_index', 'i', ['target' => 'replica'])
+        ->extend('Drupal\search\SearchQuery')
+        ->extend('Drupal\Core\Database\Query\PagerSelectExtender');
+      $query->join('advanced_help_index', 'ahi', 'ahi.sid = i.sid');
+      $query->join('search_dataset', 'sd', "ahi.sid = sd.sid AND sd.type = '{$this->pluginId}'");
+      $query->searchExpression($keys, $this->getPluginId());
+
+      $find = $query
+        ->fields('i', ['langcode'])
+        ->fields('ahi', ['module', 'topic'])
+        ->fields('sd', ['data'])
+        ->groupBy('i.langcode, ahi.module, ahi.topic, sd.data')
+        ->limit(10)
+        ->execute();
+
+      $results = [];
+      foreach ($find as $key => $item) {
+        $result = [
+          'link' => '/help/ah/' . $item->module . '/' . $item->topic,
+          'title' => $item->topic,
+          'score' => $item->calculated_score,
+          'snippet' => search_excerpt($keys, $item->data, $item->langcode),
+          'langcode' => $item->langcode,
+        ];
+        $results[] = $result;
+      }
+
+      return $results;
+    }
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function updateIndex() {
+    // Interpret the cron limit setting as the maximum number of nodes to index
+    // per cron run.
+    $limit = (int)$this->searchSettings->get('index.cron_limit');
+    $language = \Drupal::languageManager()->getCurrentLanguage()->getId();
+    $topics = $this->getSids($this->advancedHelp->getTopics());
+
+    // If we got interrupted by limit, this will contain the last module
+    // and topic we looked at.
+    $last = \Drupal::state()->get($this->getPluginId() . '.last_cron', ['time' => 0]);
+    $count = 0;
+    foreach ($topics as $module => $module_topics) {
+      // Fast forward if necessary.
+      if (!empty($last['module']) && $last['module'] != $module) {
+        continue;
+      }
+
+      foreach ($module_topics as $topic => $info) {
+        // Fast forward if necessary.
+        if (!empty($last['topic']) && $last['topic'] != $topic) {
+          continue;
+        }
+
+        //If we've been looking to catch up, and we have, reset so we
+        // stop fast forwarding.
+        if (!empty($last['module'])) {
+          unset($last['topic']);
+          unset($last['module']);
+        }
+
+        $file = $this->advancedHelp->getTopicFileName($module, $topic);
+        if ($file && (empty($info['sid']) || filemtime($file) > $last['time'])) {
+          if (empty($info['sid'])) {
+            $info['sid'] = $this->database->insert('advanced_help_index')
+              ->fields([
+                'module' => $module,
+                'topic' => $topic,
+                'langcode' => $language
+              ])
+              ->execute();
+          }
+        }
+
+        // Update index, using search index "type" equal to the plugin ID.
+        search_index($this->getPluginId(), $info['sid'], $language, file_get_contents($file));
+        $count++;
+        if ($count >= $limit) {
+          $last['module'] = $module;
+          $last['topic'] = $topic;
+          \Drupal::state()->set($this->getPluginId() . '.last_cron', $last);
+          return;
+        }
+      }
+    }
+    \Drupal::state()->set($this->getPluginId() . '.last_cron', ['time' => time()]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function indexClear() {
+    search_index_clear($this->getPluginId());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function markForReindex() {
+    search_mark_for_reindex($this->getPluginId());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function indexStatus() {
+    $topics = $this->advancedHelp->getTopics();
+    $total = 0;
+    foreach ($topics as $module => $module_topics) {
+      foreach ($module_topics as $topic => $info) {
+        $file = $this->advancedHelp->getTopicFileName($module, $topic);
+        if ($file) {
+          $total++;
+        }
+      }
+    }
+    $last_cron = \Drupal::state()->get($this->getPluginId() . '.last_cron', ['time' => 0]);
+    $indexed = 0;
+    if ($last_cron['time'] != 0) {
+      $indexed = $this->database->select('search_dataset', 'sd')
+        ->fields('sd', ['sid'])
+        ->condition('type', $this->getPluginId())
+        ->condition('reindex', 0)
+        ->countQuery()
+        ->execute()
+        ->fetchField();
+    }
+    return ['remaining' => $total - $indexed, 'total' => $total];
+  }
+}
\ No newline at end of file
diff --git a/web/modules/contrib/advanced_help/templates/advanced-help-topic.html.twig b/web/modules/contrib/advanced_help/templates/advanced-help-topic.html.twig
new file mode 100644 (file)
index 0000000..29c4d34
--- /dev/null
@@ -0,0 +1,3 @@
+{% if topic_exists %}
+    <a href="{{ path('advanced_help.help', {'module': module, 'topic' : topic}) }}" {{ attributes }} >{{ text|raw }}</a>
+{% endif %}
diff --git a/web/modules/contrib/better_formats b/web/modules/contrib/better_formats
deleted file mode 160000 (submodule)
index 9b3b5fa..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 9b3b5fa792c1dfe0d1121021a230086f3599b7e4
diff --git a/web/modules/contrib/better_formats/README.txt b/web/modules/contrib/better_formats/README.txt
new file mode 100644 (file)
index 0000000..3bd2bb9
--- /dev/null
@@ -0,0 +1,32 @@
+This very basic documentation for during development.
+Better docs will be generated closer to a full release.
+
+
+The only items currently implemented in the D8 version of Better Formats are:
+
+1. Display options: When BF is enabled you will have permissions at
+   admin/people/permissions to control per role display of:
+   1. format tips
+   2. format tips link
+   3. format selection for [entity]
+
+   #3 is actually several permissions. There is one for each entity in your site.
+
+2. Simple field level default format.
+   This allows you set a field level default format using the standard "Default Value"
+   setting of a field. This is only possibly normally if you enter something in the
+   text field for the field api to save the format too. BF gives you the ability
+   to set the format WITHOUT having to set a value in the field.
+
+   1. At admin/config/content/formats/settings enable "Use field default" option.
+   2. Create a field on one of your entities with one of the following types: Text, Long Text, Long text with summary.
+   3. Save the field.
+   4. Now go back and edit the field you just saved. This is required because of
+      how the field default value option works.
+   5. You will now see a "Text format" dropdown below your field in the
+      "Default Value" area. Set the default format in the dropdown.
+   6. Save the field. Default will now be used on all new content forms for that field.
+
+3. Allowed format selection for each text field.
+
+4. Ordering of formats in Formats select box for each text field.
diff --git a/web/modules/contrib/better_formats/better_formats.info.yml b/web/modules/contrib/better_formats/better_formats.info.yml
new file mode 100644 (file)
index 0000000..02abe2e
--- /dev/null
@@ -0,0 +1,5 @@
+name: Better Formats
+type: module
+description: 'Enhances the core input format system by managing input format defaults and settings.'
+core: 8.x
+configure: better_formats.settings
diff --git a/web/modules/contrib/better_formats/better_formats.install b/web/modules/contrib/better_formats/better_formats.install
new file mode 100644 (file)
index 0000000..36bfae6
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the better_formats module.
+ */
+
+/**
+ * Implements of hook_install().
+ */
+function better_formats_install() {
+  // Increase module weight to prevent compatibility issues.
+  module_set_weight('better_formats', 100);
+}
diff --git a/web/modules/contrib/better_formats/better_formats.links.task.yml b/web/modules/contrib/better_formats/better_formats.links.task.yml
new file mode 100644 (file)
index 0000000..1219f69
--- /dev/null
@@ -0,0 +1,4 @@
+better_formats.settings_tab:
+  route_name: better_formats.settings
+  title: Settings
+  base_route: filter.admin_overview
diff --git a/web/modules/contrib/better_formats/better_formats.module b/web/modules/contrib/better_formats/better_formats.module
new file mode 100644 (file)
index 0000000..5680144
--- /dev/null
@@ -0,0 +1,429 @@
+<?php
+
+/**
+ * @file
+ * Enhances the core input format system by managing input format defaults and settings.
+ */
+
+use Drupal\Core\Config\Entity\ThirdPartySettingsInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+
+/**
+ * Implements hook_element_info_alter().
+ */
+function better_formats_element_info_alter(array &$types) {
+  // Our process callback must run immediately after
+  // TextFormat::processFormat().
+  if (isset($types['text_format']) && isset($types['text_format']['#process'])) {
+    $search_value = ['Drupal\filter\Element\TextFormat', 'processFormat'];
+    $key = array_search($search_value, $types['text_format']['#process']);
+
+    if ($key !== FALSE) {
+      $key++;
+      array_splice($types['text_format']['#process'], $key, 0, 'better_formats_filter_process_format');
+    }
+    else {
+      $types['text_format']['#process'][] = 'better_formats_filter_process_format';
+    }
+  }
+}
+
+/**
+ * Process callback for form elements that have a text format selector attached.
+ *
+ * This callback runs after filter_process_format() and performs additional
+ * modifications to the form element.
+ *
+ * @see \Drupal\filter\Element\TextFormat::processFormat()
+ */
+function better_formats_filter_process_format(array &$element, FormStateInterface $form_state, array $complete_form) {
+  // Before we make any modifications to the element, record whether or not
+  // TextFormat::processFormat() has determined that (for security reasons) the
+  // user is not allowed to make any changes to this field. This will happen if
+  // the user does not have permission to use the currently-assigned text
+  // format.
+  $access_denied_for_security = isset($element['format']['format']['#access']) && !$element['format']['format']['#access'];
+
+  $form_object = $form_state->getFormObject();
+
+  if ($form_object instanceof Drupal\Core\Entity\ContentEntityForm) {
+    $entity = $form_state->getFormObject()->getEntity();
+    $entity_type = $entity->getEntityTypeId();
+    $field_name = reset($element['#parents']);
+    $field_definition = $entity->getFieldDefinition($field_name);
+
+    if ($field_definition instanceof ThirdPartySettingsInterface) {
+      $bf = $field_definition->getThirdPartySettings('better_formats');
+
+      if (Drupal::config('better_formats.settings')->get('per_field_core')) {
+        $field_defaults = $field_definition->getDefaultValue($entity);
+        $default_value = array_shift($field_defaults);
+        better_formats_set_default_format($element, $default_value['format']);
+      }
+    }
+  }
+  elseif ($form_object instanceof Drupal\field_ui\Form\FieldConfigEditForm) {
+    $entity = $form_state->getFormObject()->getEntity();
+    $entity_type = $entity->getTargetEntityTypeId();
+    $bf = $entity->getThirdPartySettings('better_formats');
+  }
+  else {
+    return $element;
+  }
+
+  // Now hide several parts of the element for cosmetic reasons (depending on
+  // the permissions of the current user).
+  $user = \Drupal::currentUser();
+  $user_is_admin = $user->hasPermission('administer filters');
+
+  // The selection should be shown unless proven otherwise.
+  $hide_selection = FALSE;
+
+  // If an entity is available then allow Better Formats permission to control
+  // visibility.
+  if ($entity_type != NULL) {
+    $hide_selection = $user->hasPermission('hide format selection for ' . $entity_type);
+  }
+
+  // Privileged users should still be able to change the format selection.
+  if ($hide_selection && !$user_is_admin) {
+    $element['format']['format']['#access'] = FALSE;
+  }
+
+  // Allow formats tips to be hidden.
+  $hide_tips = $user->hasPermission('hide format tips');
+
+  if ($hide_tips && !$user_is_admin) {
+    $element['format']['guidelines']['#access'] = FALSE;
+  }
+
+  // Allow format tips link to be hidden.
+  $hide_tips_link = $user->hasPermission('hide more format tips link');
+
+  if ($hide_tips_link && !$user_is_admin) {
+    $element['format']['help']['#access'] = FALSE;
+  }
+
+  // If the element represents a field attached to an entity, we may need to
+  // adjust the allowed text format options. However, we don't want to touch
+  // this if TextFormat::processFormat() has determined that (for security
+  // reasons) the user is not allowed to make any changes; in that case, Drupal
+  // core will hide the format selector and force the field to be saved with its
+  // current values, and we should not do anything to alter that process.
+  if ($entity_type != NULL && !$access_denied_for_security) {
+    $eid = $entity->id();
+
+    // Need to only do this on create forms.
+    if (empty($eid) && isset($bf) && !empty($bf['default_order_toggle']) && !empty($bf['default_order_wrapper']['formats'])) {
+      $order = $bf['default_order_wrapper']['formats'];
+      uasort($order, 'better_formats_text_format_sort');
+
+      $options = [];
+
+      foreach ($order as $id => $weight) {
+        if (isset($element['format']['format']['#options'][$id])) {
+          $options[$id] = $element['format']['format']['#options'][$id];
+        }
+      }
+
+      $element['format']['format']['#options'] = $options;
+      $options_keys = array_keys($options);
+
+      if (!Drupal::config('better_formats.settings')->get('per_field_core')) {
+        better_formats_set_default_format($element, array_shift($options_keys));
+      }
+    }
+    if (isset($bf) && !empty($bf['allowed_formats_toggle']) && !empty($bf['allowed_formats'])) {
+      // Filter the list of available formats to those allowed on this field.
+      $allowed_fields = array_filter($bf['allowed_formats']);
+      $options = &$element['format']['format']['#options'];
+      $options = array_intersect_key($options, $allowed_fields);
+
+      // If there is only one allowed format, deny access to the text format
+      // selector for cosmetic reasons, just like filter_process_format() does.
+      if (count($options) == 1) {
+        $element['format']['format']['#access'] = FALSE;
+        $hide_selection = TRUE;
+      }
+
+      // If there are no allowed formats, we need to deny access to the entire
+      // field, since it doesn't make sense to add or edit content that does
+      // not have a text format.
+      if (empty($options)) {
+        $element['#access'] = FALSE;
+      }
+      // Otherwise, if the current default format is no longer one of the
+      // allowed options, a new default format must be assigned.
+      elseif (!isset($options[$element['format']['format']['#default_value']])) {
+        // If there is no text in the field, it is safe to automatically assign
+        // a new default format. We pick the first available option to be
+        // consistent with what filter_default_format() does.
+        if (!isset($element['value']['#default_value']) || $element['value']['#default_value'] === '') {
+          $formats = array_keys($options);
+          better_formats_set_default_format($element, reset($formats));
+        }
+        // Otherwise, it is unsafe to automatically assign a new default format
+        // (since this will display the content in a way that was not
+        // originally intended and might be dangerous, e.g. if the content
+        // contains an attempted XSS attack). A human must explicitly decide
+        // which new format to assign, so we force the field to be required but
+        // with no default value, similar to what filter_process_format() does.
+        // Although filter_process_format() limits this functionality to users
+        // with the 'administer filters' permission, we can allow it for any
+        // user here since we know that the user already has permission to use
+        // the current format; thus, there is no danger of exposing unformatted
+        // text (for example, raw PHP code) that they are otherwise not allowed
+        // to see.
+        else {
+          $element['format']['format']['#required'] = TRUE;
+          better_formats_set_default_format($element, NULL);
+          // Force access to the format selector (it may have been denied
+          // previously for cosmetic reasons).
+          $element['format']['#access'] = TRUE;
+          $element['format']['format']['#access'] = TRUE;
+        }
+      }
+    }
+  }
+
+  // If the user is not supposed to see the text format selector, hide all
+  // guidelines except those associated with the default format. We need to do
+  // this at the end, since the above code may have altered the default format.
+  if ($hide_selection && isset($element['format']['format']['#default_value'])) {
+    foreach (Element::children($element['format']['guidelines']) as $format) {
+      if ($format != $element['format']['format']['#default_value']) {
+        $element['format']['guidelines'][$format]['#access'] = FALSE;
+      }
+    }
+  }
+
+  // Keep the format for validation and submit processing but don't sent it to
+  // the browser if the user is not supposed to see anything inside of it.
+  if ($hide_selection && $hide_tips && $hide_tips_link) {
+    unset($element['format']['#type']);
+  }
+
+  return $element;
+}
+
+/**
+ * Determine if text field type uses text formatter.
+ *
+ * @param string $type
+ *   The field type to check.
+ *
+ * @return bool
+ *   TRUE if input field type uses text formatter, FALSE if it does not.
+ */
+function better_formats_is_text_format($type) {
+  if (in_array($type, ['text', 'text_long', 'text_with_summary'], TRUE)) {
+    return TRUE;
+  }
+  else {
+    return FALSE;
+  }
+}
+
+/**
+ * Set the default format for the element.
+ *
+ * @param array $element
+ *    The form element to set the default format on.
+ * @param string $default_value
+ *    The id for the format to set as default.
+ */
+function better_formats_set_default_format(array &$element, $default_value) {
+  $element['#format'] = $default_value;
+  $element['format']['format']['#default_value'] = $default_value;
+}
+
+/**
+ * Sort text formats by weight.
+ *
+ * @param array $a
+ *   Array containing weight value to compare.
+ * @param array $b
+ *   Array containing weight value to compare.
+ *
+ * @return bool
+ *   TRUE if the weight of $a is greater than $b, FALSE if weight of $b is
+ *   greater than $a or equal to $a.
+ */
+function better_formats_text_format_sort($a, $b) {
+  return $a['weight'] > $b['weight'];
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function better_formats_form_field_config_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) {
+  $entity = $form_state->getFormObject()->getEntity();
+
+  // Only alter fields with text processing and if admin has chosen.
+  $text_processing = better_formats_is_text_format($entity->getType());
+  $config = Drupal::config('better_formats.settings');
+
+  if ($text_processing && $config->get('per_field_core')) {
+    // Add a submit handler to save default values on empty fields.
+    $form['actions']['submit']['#submit'][] = 'better_formats_field_config_edit_form_submit';
+  }
+
+  // If the field is a format-using text field, allow the admin to configure
+  // which formats are allowed here.
+  if ($text_processing) {
+    // We have to set an explicit weight here so that we can put the allowed
+    // formats list after it.
+    $bf_settings = $entity->getThirdPartySettings('better_formats') != NULL ? $entity->getThirdPartySettings('better_formats') : [];
+
+    // Add in the better formats table.
+    $form['third_party_settings'] += better_formats_field_settings_form($bf_settings);
+    $form['third_party_settings']['#weight'] = -4;
+  }
+}
+
+/**
+ * Build the settings form for Field API fields.
+ *
+ * @param array $bf_form
+ *   The existing better formats settings form from the form element.
+ *
+ * @return array
+ *   The array of better formats form items.
+ */
+function better_formats_field_settings_form($bf_form = []) {
+  $formats = filter_formats();
+  // Plain Text Format should not be an option, that is a separate field type.
+  unset($formats['plain_text']);
+
+  $form = [];
+  $form['better_formats'] = [
+    '#tree' => TRUE,
+    '#type' => 'fieldset',
+    '#title' => t('Text Formats'),
+    '#weight' => 0,
+  ];
+
+  $allowed_options = [];
+
+  foreach ($formats as $format) {
+    $allowed_options[$format->id()] = $format->label();
+  }
+
+  $allowed_toggle_default = isset($bf_form['allowed_formats_toggle']) ? $bf_form['allowed_formats_toggle'] : FALSE;
+  $allowed_defaults = isset($bf_form['allowed_formats']) ? $bf_form['allowed_formats'] : [];
+
+  if (empty($allowed_defaults)) {
+    $allowed_defaults = array_keys($allowed_options);
+  }
+
+  $form['better_formats']['allowed_formats_toggle'] = [
+    '#type' => 'checkbox',
+    '#title' => t('Limit allowed text formats'),
+    '#description' => t('Check the allowed formats below. If checked available text formats can be chosen.'),
+    '#weight' => 1,
+    '#default_value' => $allowed_toggle_default,
+  ];
+  $form['better_formats']['allowed_formats'] = [
+    '#type' => 'checkboxes',
+    '#title' => t('Allowed formats'),
+    '#options' => $allowed_options,
+    '#description' => t('Select the text formats allowed for this field. Note that not all of these may appear on the form if a user does not have permission to use them. <strong>Warning:</strong> This affects existing content which may leave you unable to edit some fields. If that happens you must allow the format that field was saved in here.'),
+    '#weight' => 2,
+    '#default_value' => $allowed_defaults,
+    '#states' => [
+      'visible' => [
+        ':input[name="third_party_settings[better_formats][allowed_formats_toggle]"]' => ['checked' => TRUE],
+      ],
+    ],
+  ];
+
+  $order_toggle_default = isset($bf_form['default_order_toggle']) ? $bf_form['default_order_toggle'] : FALSE;
+
+  $form['better_formats']['default_order_toggle'] = [
+    '#type' => 'checkbox',
+    '#title' => t('Override default order'),
+    '#description' => t('Override the gloabl order that will determine the default text format a user will get <strong>only on entity creation</strong>.'),
+    '#weight' => 3,
+    '#default_value' => $order_toggle_default,
+  ];
+  $form['better_formats']['default_order_wrapper'] = [
+    '#tree' => TRUE,
+    '#type' => 'container',
+    '#weight' => 4,
+    '#states' => [
+      'visible' => [
+        ':input[name="third_party_settings[better_formats][default_order_toggle]"]' => ['checked' => TRUE],
+      ],
+    ],
+  ];
+
+  // Formats ordering table.
+  $form['better_formats']['default_order_wrapper']['formats'] = [
+    '#type' => 'table',
+    '#header' => [t('Format'), t('Weight')],
+    '#empty' => t('There are no items yet.'),
+    '#tabledrag' => [
+      [
+        'action' => 'order',
+        'relationship' => 'sibling',
+        'group' => 'format-order-weight',
+      ],
+    ],
+  ];
+
+  foreach ($formats as $id => $format) {
+    $default = isset($bf_form['default_order_wrapper']['formats'][$id]) ? $bf_form['default_order_wrapper']['formats'][$id] : NULL;
+    $weight = isset($default['weight']) ? $default['weight'] : $format->get('weight');
+
+    // TableDrag: Mark the table row as draggable.
+    $form['better_formats']['default_order_wrapper']['formats'][$id]['#attributes']['class'][] = 'draggable';
+    // TableDrag: Sort the table row according to its existing/configured weight.
+    $form['better_formats']['default_order_wrapper']['formats'][$id]['#weight'] = $weight;
+
+    // Some table columns containing raw markup.
+    $form['better_formats']['default_order_wrapper']['formats'][$id]['label'] = [
+      '#markup' => $format->label(),
+    ];
+
+    // TableDrag: Weight column element.
+    $form['better_formats']['default_order_wrapper']['formats'][$id]['weight'] = [
+      '#type' => 'weight',
+      '#title' => t('Weight for @title', ['@title' => $format->label()]),
+      '#title_display' => 'invisible',
+      '#default_value' => $weight,
+      // Classify the weight element for #tabledrag.
+      '#attributes' => ['class' => ['format-order-weight']],
+    ];
+  }
+
+  // Sort formats according to weight.
+  Element::children($form['better_formats']['default_order_wrapper']['formats'], TRUE);
+
+  return $form;
+}
+
+/**
+ * Submit handler for field instance edit form.
+ *
+ * Copied and slightly modifed from FieldConfigEditForm::submitForm().
+ *
+ * @see \Drupal\field_ui\Form\FieldConfigEditForm::submitForm()
+ */
+function better_formats_field_config_edit_form_submit(array &$form, FormStateInterface $form_state) {
+  $entity = $form_state->getFormObject()->getEntity();
+  $text_processing = better_formats_is_text_format($entity->getType());
+
+  // Only act on fields that have text processing enabled.
+  if ($text_processing) {
+    // Handle the default value.
+    $default_value = [];
+    $default_input_value = $form_state->getValue(['default_value_input', $entity->getName()]);
+
+    if ($default_input_value != NULL) {
+      $default_value = $default_input_value;
+    }
+
+    $entity->setDefaultValue($default_value)->save();
+  }
+}
diff --git a/web/modules/contrib/better_formats/better_formats.permissions.yml b/web/modules/contrib/better_formats/better_formats.permissions.yml
new file mode 100644 (file)
index 0000000..75c2dd5
--- /dev/null
@@ -0,0 +1,10 @@
+hide format tips:
+  title: 'Hide format tips'
+  description: 'Toggle display of format description help.'
+
+hide more format tips link:
+  title:  'Hide more format tips link'
+  description: 'Toggle display of the "More information about text formats" link.'
+
+permission_callbacks:
+  - \Drupal\better_formats\BetterFormatsPermissions::permissions
diff --git a/web/modules/contrib/better_formats/better_formats.routing.yml b/web/modules/contrib/better_formats/better_formats.routing.yml
new file mode 100644 (file)
index 0000000..d2a20eb
--- /dev/null
@@ -0,0 +1,7 @@
+better_formats.settings:
+  path: '/admin/config/content/formats/settings'
+  defaults:
+    _form: '\Drupal\better_formats\Form\SettingsForm'
+    _title: 'Settings'
+  requirements:
+    _permission: 'administer filters'
diff --git a/web/modules/contrib/better_formats/composer.json b/web/modules/contrib/better_formats/composer.json
new file mode 100644 (file)
index 0000000..27f828e
--- /dev/null
@@ -0,0 +1,8 @@
+{
+  "name": "drupal/better_formats",
+  "description": "Enhances the core input format system by managing input format defaults and settings.",
+  "type": "drupal-module",
+  "license": "GPL-2.0+",
+  "minimum-stability": "dev",
+  "require": { }
+}
diff --git a/web/modules/contrib/better_formats/config/install/better_formats.settings.yml b/web/modules/contrib/better_formats/config/install/better_formats.settings.yml
new file mode 100644 (file)
index 0000000..8fbd024
--- /dev/null
@@ -0,0 +1 @@
+per_field_core: FALSE
diff --git a/web/modules/contrib/better_formats/config/schema/better_formats.schema.yml b/web/modules/contrib/better_formats/config/schema/better_formats.schema.yml
new file mode 100644 (file)
index 0000000..a767614
--- /dev/null
@@ -0,0 +1,6 @@
+better_formats.settings:
+  type: config_object
+  mapping:
+    per_field_core:
+      type: boolean
+      label: 'Use field default'
diff --git a/web/modules/contrib/better_formats/src/BetterFormatsPermissions.php b/web/modules/contrib/better_formats/src/BetterFormatsPermissions.php
new file mode 100644 (file)
index 0000000..a023808
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+
+namespace Drupal\better_formats;
+
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides dynamic permissions of the better formats module.
+ */
+class BetterFormatsPermissions implements ContainerInjectionInterface {
+
+  use StringTranslationTrait;
+
+  /**
+   * The entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * Constructs a new BetterFormatsPermissions instance.
+   *
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   */
+  public function __construct(EntityManagerInterface $entity_manager) {
+    $this->entityManager = $entity_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static($container->get('entity.manager'));
+  }
+
+  /**
+   * Returns an array of better formats permissions.
+   *
+   * @return array
+   */
+  public function permissions() {
+    $permissions = [];
+
+    // Generate permissions for each entity type.
+    foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
+      if ($entity_type->get('field_ui_base_route')) {
+        $permissions['hide format selection for ' . $entity_type_id] = [
+          'title' => $this->t('Hide format selection for @label', ['@label' => $entity_type->getLabel()]),
+        ];
+      }
+    }
+
+    return $permissions;
+  }
+
+}
diff --git a/web/modules/contrib/better_formats/src/Form/SettingsForm.php b/web/modules/contrib/better_formats/src/Form/SettingsForm.php
new file mode 100644 (file)
index 0000000..7e28e53
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+
+namespace Drupal\better_formats\Form;
+
+use Drupal\Core\Form\ConfigFormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Class SettingsForm.
+ *
+ * @package Drupal\better_formats\Form
+ */
+class SettingsForm extends ConfigFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'better_formats_settings_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditableConfigNames() {
+    return ['better_formats.settings'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $config = $this->config('better_formats.settings');
+
+    $form['control'] = [
+      '#type' => 'fieldset',
+      '#title' => t('Control'),
+    ];
+
+    $form['control']['per_field_core'] = [
+      '#type'  => 'checkbox',
+      '#title' => t('Use field default'),
+      '#description' => t('Use the core field module default value to set the default format. This will force the default format even when the default field value is empty. To set a default format you must re-edit a text field after saving it with the "Filtered text" option turned on.'),
+      '#default_value' => $config->get('per_field_core'),
+    ];
+
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $config = $this->config('better_formats.settings');
+    $form_state->cleanValues();
+
+    foreach ($form_state->getValues() as $key => $value) {
+      $config->set($key, $value);
+    }
+    $config->save();
+
+    parent::submitForm($form, $form_state);
+  }
+
+}
diff --git a/web/modules/contrib/better_formats/src/Tests/BetterFormatsFilterFormatAccessTest.php b/web/modules/contrib/better_formats/src/Tests/BetterFormatsFilterFormatAccessTest.php
new file mode 100644 (file)
index 0000000..8504515
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\better_formats\Tests;
+
+use Drupal\filter\Tests\FilterFormatAccessTest;
+
+/**
+ * Copy of FilterFormatAccessTest.
+ *
+ * @group better_formats
+ */
+class BetterFormatsFilterFormatAccessTest extends FilterFormatAccessTest {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['better_formats'];
+
+  /**
+   * {@inheritdoc}
+   */
+  function setUp() {
+    parent::setUp();
+  }
+}
diff --git a/web/modules/contrib/better_formats/src/Tests/BetterFormatsTermTest.php b/web/modules/contrib/better_formats/src/Tests/BetterFormatsTermTest.php
new file mode 100644 (file)
index 0000000..c57ec0f
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\better_formats\Tests;
+
+use Drupal\taxonomy\Tests\TermTest;
+
+/**
+ * Copy of TermTest.
+ *
+ * @group better_formats
+ */
+class BetterFormatsTermTest extends TermTest {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['better_formats'];
+
+  /**
+   * {@inheritdoc}
+   */
+  function setUp() {
+    parent::setUp();
+  }
+}
diff --git a/web/modules/contrib/ckeditor_widgets b/web/modules/contrib/ckeditor_widgets
deleted file mode 160000 (submodule)
index 2d46263..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 2d462637f8804b6d0b530604d0376e97a23a3b7f
diff --git a/web/modules/contrib/ckeditor_widgets/CHANGELOG.txt b/web/modules/contrib/ckeditor_widgets/CHANGELOG.txt
new file mode 100644 (file)
index 0000000..7dfc863
--- /dev/null
@@ -0,0 +1,7 @@
+2015-06-25: jlyon: Improved method that angular app is bound
+2015-06-25: jlyon: Moved angular app into main module code (rather than requiring a download to sites/all/libraries)
+2015-06-25: jlyon: Added CKEditor config steps to README
+2015-06-25: jlyon: Fixed issue where placeholder widget would flash at 2x height on page load
+2015-07-23: jlyon: Fixed issue with file status not getting changed to 1 after file upload, causing files to get deleted after 24 hrs
+2015-07-23: jlyon: Changed icons from glyphicons to fontawesome @todo: some way to choose icon set?
+2015-08-27: jlyon: Make ckeditor image browser work in content types other than Page
\ No newline at end of file
diff --git a/web/modules/contrib/ckeditor_widgets/README.md b/web/modules/contrib/ckeditor_widgets/README.md
new file mode 100644 (file)
index 0000000..060353f
--- /dev/null
@@ -0,0 +1,34 @@
+Angular Media
+=============
+
+Rethinking the media dialog interface with the help of Angular.
+
+###Highlights
+* A better user interface for selecting and editing files in Drupal
+* Easy multi-upload built-in to every selection method
+* Search for and easily add creative commons photos with proper attribution
+
+###Installation
+* Install the module dependencies (file_entity, media, views, features, views_data_export_json).
+* Download the Angular Media App from Github and copy it to sites/all/libraries (or similar): https://github.com/albatrossdigital/angular-media-app
+* Install the module in Drupal
+* Add a new File field and select the Angular Media Browser widget
+
+###CKEditor configuration steps
+In order to get the CKEditor integration to work, follow these steps:
+1. In CKEditor profile, under Editor Apperance, check Plugin for Angular Media Browser and add the insert image button.
+2. In CKEditor profile, under File Browser Settings, change File browser type to "CKFinder".
+3. In global CKEditor settings(`/admin/config/content/ckeditor/editg`), in Local path to CKFinder, enter any valid url (we use "modules" in Helm).
+4. On permissions (`admin/people/permissions`), give the appropriate role access to CKFinder access.
+
+###Status
+This module is currently in a MVP state and should not be used on production websites.  It is under active development and many features and improvements will be added in the coming weeks. If you are interested in helping, please post a note in the issue queue.
+
+###Roadmap
+* Make the Angular Media field work when there are multiple instances on one edit page
+* Remove the media dependency
+* Make the Angular Media field work when the form is loaded via the Drupal AJAX api (ctools modals)
+
+
+###Who?
+Conception and development to this point has been done by <a href="http://albatrossdigital.com">Albatross Digital</a>. We believe that every website should look beautiful, and having a user-friendly media experience is paramount to that vision.
\ No newline at end of file
diff --git a/web/modules/contrib/ckeditor_widgets/ckeditor_widgets.info.yml b/web/modules/contrib/ckeditor_widgets/ckeditor_widgets.info.yml
new file mode 100644 (file)
index 0000000..2edd26c
--- /dev/null
@@ -0,0 +1,10 @@
+name: CKEditor Widgets
+type: module
+description: Adds widgets and an Insert Template menu to CKEditor
+core: 8.x
+package: CKEditor
+version: 1.0
+dependencies:
+  - ckeditor
+  - editor
+
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/contents.css b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/contents.css
new file mode 100644 (file)
index 0000000..eeba9d3
--- /dev/null
@@ -0,0 +1,126 @@
+/* Bootstrap styles */
+.row {
+  margin-left: -15px;
+  margin-right: -15px;
+}
+.row:before, .row:after {
+  content: " ";
+  display: table;
+}
+.row:after {
+  clear: both;
+}
+
+.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {
+  position: relative;
+  min-height: 1px;
+  float: left;
+  padding: 15px;
+  box-sizing: border-box;
+  -moz-box-sizing: border-box;
+}
+.col-md-6 {
+  width: 50%;
+}
+.col-md-3 {
+  width: 25%;
+}
+.col-md-9 {
+  width: 75%;
+}
+.col-md-4 {
+  width: 33.3%;
+}
+
+/* Custom styles */
+.col-md-6 img,
+.col-md-3 img,
+.col-md-4 img,
+.col-md-9 img {
+  display: block;
+  max-width: 100%;
+  height: auto;
+}
+.two-col,
+.two-col-left,
+.two-col-right,
+.three-col,
+.accordion {
+       padding: 8px;
+       margin: 10px auto;
+       background: #eee;
+       border-radius: 8px;
+       border: 1px solid #ddd;
+       box-shadow: 0 1px 1px #fff inset, 0 -1px 0px #ccc inset;
+}
+.two-col .col-md-6,
+.two-col-left .col-md-3,
+.two-col-left .col-md-9,
+.two-col-right .col-md-3,
+.two-col-right .col-md-9,
+.three-col .col-md-4,
+.accordion dd .content,
+.accordion dd.accordion-navigation>a {
+       box-shadow: 0 1px 1px #ddd inset;
+       border: 1px solid #cccccc;
+       border-radius: 5px;
+       background: #fff;
+       min-height: 5em;
+}
+.two-col .col-md-6,
+.two-col-left .col-md-3,
+.two-col-left .col-md-9,
+.two-col-right .col-md-3,
+.two-col-right .col-md-9 {
+       margin: -3px;
+}
+.two-col-right .col-sidebar,
+.two-col-left .col-sidebar {
+       padding: 0;
+}
+.two-col .col-2 {
+  margin-left: 9px;
+}
+.three-col .col-1 {
+       margin-left: -3px;
+}
+.three-col .col-2 {
+       margin-left: 3px;
+}
+.three-col .col-3 {
+       margin-left: 3px;
+  margin-right: -3px;
+}
+.two-col-left .col-md-9{
+  margin-left: 8px;
+}
+.two-col-right .col-md-9{
+  margin-right: 8px;
+}
+
+.accordion {
+       padding-top: 2px;
+}
+.accordion:before {
+       content: 'Accordion';
+       text-transform: uppercase;
+       padding-bottom: 4px;
+       font-size: 10px;
+       color: #888;
+}
+.accordion dd.accordion-navigation > .content,
+.accordion dd > .content {
+       display: block !important;
+}
+.accordion dd.accordion-navigation {
+       margin-bottom: .7em !important;
+}
+.accordion dd.accordion-navigation>a {
+       padding: 5px;
+       background: #ddd;
+       min-height: 0;
+       margin-bottom: 1px;
+}
+.accordion dd.accordion-navigation>a p {
+       margin: 0;
+}
\ No newline at end of file
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/dialogs/widgetbootstrapAlert.js b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/dialogs/widgetbootstrapAlert.js
new file mode 100644 (file)
index 0000000..223d1c3
--- /dev/null
@@ -0,0 +1,67 @@
+CKEDITOR.dialog.add( 'widgetfoundationAlert', function( editor ) {
+    var clientHeight = document.documentElement.clientHeight,
+        alertTypes = CKEDITOR.config.widgetfoundationAlert_alertTypes,
+        alertTypesSelect = [],
+        alertName;
+
+    for ( alertName in alertTypes ) {
+        alertTypesSelect.push( [ alertTypes[ alertName ], alertName ] );
+    }
+
+
+    // Size adjustments.
+    /*var size = CKEDITOR.document.getWindow().getViewPaneSize(),
+        // Make it maximum 800px wide, but still fully visible in the viewport.
+        width = Math.min( size.width - 70, 800 ),
+        // Make it use 2/3 of the viewport height.
+        height = size.height / 1.5;
+        // Low resolution settings.
+        if ( clientHeight < 650 )
+            height = clientHeight - 220;*/
+
+    return {
+        title: 'Edit Alert Type',
+        minWidth: 200,
+        minHeight: 100,
+        contents: [
+            {
+                id: 'info',
+                elements: [
+                    {
+                        id: 'type',
+                        type: 'select',
+                        label: 'Alert Type',
+                        items: alertTypesSelect,
+                        required: true,
+                        validate: CKEDITOR.dialog.validate.notEmpty('Alert type required'),
+                        setup: function( widget ) {
+                            this.setValue( widget.data.type != undefined ? widget.data.type : 'alert');
+                        },
+                        commit: function( widget ) {
+                            widget.setData( 'type', this.getValue() );
+                        }
+                    }/*,
+                    {
+                        id: 'alertText',
+                        type: 'textarea',
+                        label: 'Alert Content',
+                        setup: function( widget ) {
+                            this.setValue( widget.data.alertText );
+                        },
+                        commit: function( widget ) {
+                            widget.setData( 'alertText', this.getValue() );
+                        },
+                        required: true,
+                        validate: CKEDITOR.dialog.validate.notEmpty('Content required'),
+                        inputStyle: 'cursor:auto;' +
+                            'width:' + width + 'px;' +
+                            'height:' + height + 'px;' +
+                            'tab-size:4;' +
+                            'text-align:left;',
+                            'class': 'cke_source'
+                    }*/
+                ]
+            }
+        ]
+    };
+} );
\ No newline at end of file
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/dialogs/widgetfoundationAccordion.js b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/dialogs/widgetfoundationAccordion.js
new file mode 100644 (file)
index 0000000..4ad7e2e
--- /dev/null
@@ -0,0 +1,61 @@
+CKEDITOR.dialog.add( 'widgetfoundationAccordion', function( editor ) {
+    return {
+        title: 'Edit Accordion Box',
+        minWidth: 200,
+        minHeight: 100,
+        contents: [
+            {
+                id: 'info',
+                elements: [
+                    {
+                        id: 'name',
+                        type: 'text',
+                        label: 'Accordion machine name',
+                        width: '200px',
+                        setup: function( widget ) {
+                            this.setValue( widget.data.name != undefined ? widget.data.name : 'accordion');
+                        },
+                        commit: function( widget ) {
+                            widget.setData( 'name', this.getValue() );
+                        }
+                    },
+                    {
+                        id: 'count',
+                        type: 'text',
+                        label: 'Number of panels',
+                        width: '50px',
+                        setup: function( widget ) {
+                            this.setValue( widget.data.count != undefined ? widget.data.count : 3);
+                        },
+                        commit: function( widget ) {
+                            widget.setData( 'count', this.getValue() );
+                        }
+                    },
+                    {
+                        id: 'activePanel',
+                        type: 'text',
+                        width: '50px',
+                        label: 'Active panel (leave blank for accordion to be initially collapsed, or enter the number of the panel you would like open, ex: 1)',
+                        setup: function( widget ) {
+                            this.setValue( widget.data.activePanel);
+                        },
+                        commit: function( widget ) {
+                            widget.setData( 'activePanel', this.getValue() );
+                        }
+                    },
+                    {
+                        id: 'multiExpand',
+                        type: 'checkbox',
+                        label: 'Allow multiple accordion panels to be expanded at the same time',
+                        setup: function( widget ) {
+                            this.setValue( widget.data.multiExpand );
+                        },
+                        commit: function( widget ) {
+                            widget.setData( 'multiExpand', this.getValue() );
+                        }
+                    }
+                ]
+            }
+        ]
+    };
+} );
\ No newline at end of file
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapAccordion.png b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapAccordion.png
new file mode 100644 (file)
index 0000000..6ae2f5b
Binary files /dev/null and b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapAccordion.png differ
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapAlert.png b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapAlert.png
new file mode 100644 (file)
index 0000000..26f29a2
Binary files /dev/null and b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapAlert.png differ
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapLeftCol.png b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapLeftCol.png
new file mode 100644 (file)
index 0000000..d1cde64
Binary files /dev/null and b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapLeftCol.png differ
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapRightCol.png b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapRightCol.png
new file mode 100644 (file)
index 0000000..432a142
Binary files /dev/null and b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapRightCol.png differ
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapThreeCol.png b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapThreeCol.png
new file mode 100644 (file)
index 0000000..58a3d2e
Binary files /dev/null and b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapThreeCol.png differ
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapTwoCol.png b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapTwoCol.png
new file mode 100644 (file)
index 0000000..9e8e9f1
Binary files /dev/null and b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapTwoCol.png differ
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/plugin.js b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/plugin.js
new file mode 100644 (file)
index 0000000..e670302
--- /dev/null
@@ -0,0 +1,326 @@
+// Init default alert classes
+
+CKEDITOR.config.widgetbootstrapAlert_alertTypes = {
+    'alert': 'Alert',
+    'info': 'Info',
+    'warning': 'Warning',
+    'success': 'Success'
+};
+
+
+CKEDITOR.plugins.add( 'widgetbootstrap', {
+    requires: 'widget',
+
+    icons: 'widgetbootstrapLeftCol,widgetbootstrapRightCol,widgetbootstrapTwoCol,widgetbootstrapThreeCol,widgetbootstrapAlert',
+
+    /*defaults : {
+        name: 'accordion',
+        count: 3,
+        activePanel: 1,
+        multiExpand: false
+    },*/
+
+    init: function( editor ) {
+        
+        // Configurable settings
+        //var allowedWidget = editor.config.widgetbootstrap_allowedWidget != undefined ? editor.config.widgetbootstrap_allowedFull :
+        //    'p h2 h3 h4 h5 h6 span br ul ol li strong em img[!src,alt,width,height]';
+        var allowedFull = editor.config.widgetbootstrap_allowedFull != undefined ? editor.config.widgetbootstrap_allowedFull :
+            'p a div span h2 h3 h4 h5 h6 section article iframe object embed strong b i em cite pre blockquote small sub sup code ul ol li dl dt dd table thead tbody th tr td img caption mediawrapper br[href,src,target,width,height,colspan,span,alt,name,title,class,id,data-options]{text-align,float,margin}(*);'
+        //var allowedText = editor.config.widgetbootstrap_allowedText != undefined ? editor.config.widgetbootstrap_allowedFull :
+        //    'p span br ul ol li strong em';
+
+
+        allowedWidget = allowedFull;
+        //allowedText = allowedWidget;
+
+        var showButtons = editor.config.widgetbootstrapShowButtons != undefined ? editor.config.widgetbootstrapShowButtons : true;
+
+        // Define the widgets
+        editor.widgets.add( 'widgetbootstrapLeftCol', {
+
+            button: showButtons ? 'Add left column box' : undefined,
+
+            template:
+                '<div class="row two-col-left">' +
+                    '<div class="col-md-3 col-sidebar"><p><img src="http://placehold.it/300x250&text=Image" /></p></div>' +
+                    '<div class="col-md-9 col-main"><p>Content</p></div>' +
+                '</div>',
+
+            editables: {
+                col1: {
+                    selector: '.col-sidebar',
+                    allowedContent: allowedWidget
+                },
+                col2: {
+                    selector: '.col-main',
+                    allowedContent: allowedWidget
+                }
+            },
+
+            allowedContent: allowedFull,
+
+            upcast: function( element ) {
+                return element.name == 'div' && element.hasClass( 'two-col-right-left' );
+            }
+            
+        } );
+
+        editor.widgets.add( 'widgetbootstrapRightCol', {
+
+            button: showButtons ? 'Add right column box' : undefined,
+
+            template:
+                '<div class="row two-col-right">' +
+                    '<div class="col-md-9 col-main"><p>Content</p></div>' +
+                    '<div class="col-md-3 col-sidebar"><p><img src="http://placehold.it/300x250&text=Image" /></p></div>' +
+                '</div>',
+
+            editables: {
+                col1: {
+                    selector: '.col-sidebar',
+                    allowedContent: allowedWidget
+                },
+                col2: {
+                    selector: '.col-main',
+                    allowedContent: allowedWidget
+                }
+            },
+
+            allowedContent: allowedFull,
+
+            upcast: function( element ) {
+                return element.name == 'div' && element.hasClass( 'two-col-right' );
+            }
+
+        } );
+
+        editor.widgets.add( 'widgetbootstrapTwoCol', {
+
+            button: showButtons ? 'Add two column box' : undefined,
+
+            template:
+                '<div class="row two-col">' +
+                    '<div class="col-md-6 col-1"><p><img src="http://placehold.it/500x280&text=Image" /></p><p>Content</p></div>' +
+                    '<div class="col-md-6 col-2"><p><img src="http://placehold.it/500x280&text=Image" /></p><p>Content</p></div>' +
+                '</div>',
+
+            editables: {
+                col1: {
+                    selector: '.col-1',
+                    allowedContent: allowedWidget
+                },
+                col2: {
+                    selector: '.col-2',
+                    allowedContent: allowedWidget
+                }
+            },
+
+            allowedContent: allowedFull,
+
+            upcast: function( element ) {
+                return element.name == 'div' && element.hasClass( 'two-col' );
+            }
+
+        } );
+
+        editor.widgets.add( 'widgetbootstrapThreeCol', {
+
+            button: showButtons ? 'Add three column box' : undefined,
+
+            template:
+                '<div class="row three-col">' +
+                    '<div class="col-md-4 col-1"><p><img src="http://placehold.it/400x225&text=Image" /></p><p>Text below</p></div>' +
+                    '<div class="col-md-4 col-2"><p><img src="http://placehold.it/400x225&text=Image" /></p><p>Text below</p></div>' +
+                    '<div class="col-md-4 col-3"><p><img src="http://placehold.it/400x225&text=Image" /></p><p>Text below</p></div>' +
+                '</div>',
+
+            editables: {
+                col1: {
+                    selector: '.col-1',
+                    allowedContent: allowedWidget
+                },
+                col2: {
+                    selector: '.col-2',
+                    allowedContent: allowedWidget
+                },
+                col3: {
+                    selector: '.col-3',
+                    allowedContent: allowedWidget
+                }
+            },
+
+            allowedContent: allowedFull,
+
+            upcast: function( element ) {
+                return element.name == 'div' && element.hasClass( 'three-col' );
+            }
+
+        } );
+
+        editor.addCommand( 'openwidgetbootstrapAlert', new CKEDITOR.dialogCommand( 'widgetbootstrapAlert' ) );
+        
+        // Add foundation alert button
+        // Textare decodes html entities
+        //var textarea = new CKEDITOR.dom.element( 'textarea' );
+
+        editor.widgets.add( 'widgetbootstrapAlert', {
+
+            button: showButtons ? 'Add alert box' : undefined,
+            dialog: 'widgetbootstrapAlert',
+
+            template: '<div class="alert-box"><div class="alert-text">Some Text</span></div>',
+
+            editables: {
+                alertBox: {
+                    selector: '.alert-text',
+                    allowedContent: allowedWidget
+                },
+            },
+
+            allowedContent: allowedFull,
+
+            data: function() {
+                var newData = this.data,
+                    oldData = this.oldData;
+
+                /*if( newData.alertText ) {
+                    this.element.getChild( 0 ).setHtml( CKEDITOR.tools.htmlEncode( newData.alertText ) );
+                }*/
+                
+                if ( oldData && newData.type != oldData.type )
+                    this.element.removeClass(oldData.type);
+
+                if ( newData.type )
+                    this.element.addClass(newData.type);
+
+                // Save oldData.
+                this.oldData = CKEDITOR.tools.copy( newData );
+            },
+
+            upcast: function( el, data ) {
+                if (el.name != 'div' || !el.hasClass( 'alert-box' ))
+                    return;
+
+                var childrenArray = el.children,
+                    alertText;
+
+                if ( childrenArray.length !== 1 || !( alertText = childrenArray[ 0 ] ).hasClass('alert-text'))
+                    return;
+
+                // Acceptable alert types
+                var alertTypes = CKEDITOR.config.widgetbootstrapAlert_alertTypes;
+                // Check alert types
+                for(var i = 0; i < el.classes.length; i++) {
+                    if(el.classes[i] != 'alert-box') {
+                        for ( alertName in alertTypes ) {
+                            if(el.classes[i] == alertName) {
+                                data.type = alertName;
+                            }
+                        }
+                    }
+                }
+
+                // Use textarea to decode HTML entities (#11926).
+                //textarea.setHtml( alertText.getHtml() );
+                //data.alertText = textarea.getValue();
+
+                return el;
+            },
+
+            downcast: function( el ) {
+                return el;
+            }
+
+        } );
+        // Alert dialog
+        CKEDITOR.dialog.add( 'widgetbootstrapAlert', this.path + 'dialogs/widgetbootstrapAlert.js' );
+
+        /*CKEDITOR.dialog.add( 'widgetbootstrapAccordion', this.path + 'dialogs/widgetbootstrapAccordion.js' );
+        editor.widgets.add( 'widgetbootstrapAccordion', {
+
+            button: showButtons ? 'Add accordion box' : undefined,
+
+            template:
+                '<dl class="accordion" data-accordion><div class="col-1"></div></dl>',
+     
+
+            allowedContent: allowedFull,
+
+            dialog: 'widgetbootstrapAccordion',
+
+            upcast: function( element ) {
+                return element.name == 'div' && element.hasClass( 'accordion' );
+            },
+
+            /*init: function() {
+                var width = this.element.getStyle( 'width' );
+                if ( width )
+                    this.setData( 'width', width );
+                if ( this.element.hasClass( 'align-left' ) )
+                    this.setData( 'align', 'left' );
+                if ( this.element.hasClass( 'align-right' ) )
+                    this.setData( 'align', 'right' );
+                if ( this.element.hasClass( 'align-center' ) )
+                    this.setData( 'align', 'center' );
+            },
+
+            data: function() {
+
+                var name = this.data.name != undefined ? this.data.name : 'accordion';
+                var count = this.data.count != undefined ? this.data.count : 0;
+                //@todo: var prevCount = this.data.prevCount != undefined ? this.data.prevCount : 
+
+                // Add rows
+                if (this.data.prevCount == undefined || this.data.prevCount < count) {
+                    for (var i=this.data.prevCount != undefined ? this.data.prevCount : 1; i<=count; i++) {
+                        var active = this.data.activePanel == i ? ' active' : '';
+                        var template = 
+                            '<dd class="accordion-navigation">' +
+                                '<a href="#'+ name+i +'"><div class="accordion-header-'+i+'">Heading '+i+'</div></a>' +
+                                '<div id="panel'+ name+i +'" class="content content-'+i+active+'">' +
+                                  '' +
+                                '</div>'
+                            '</dd>'
+                        var newPanel = CKEDITOR.dom.element.createFromHtml( template );
+                        this.element.append(newPanel);
+                    }
+
+                    // For some reason, the initEditable call needs to come in a separate for loop
+                    // the html code added wasn't in the DOM yet
+                    for (var i=this.data.prevCount != undefined ? this.data.prevCount : 1; i<=count; i++) {
+                        this.initEditable( 'heading'+i, {
+                            selector: '.accordion-header-'+i
+                        } );
+                        this.initEditable( 'content'+i, {
+                            selector: '.content-'+i
+                        } ); 
+                    }
+                }
+
+                // Remove rows
+                if (this.data.prevCount != undefined && this.data.prevCount > count) {
+                    // @todo
+                }
+                
+
+                this.data.prevCount = i;
+            }
+        } );*/
+
+        // Append the widget's styles when in the CKEditor edit page,
+        // added for better user experience.
+        // Assign or append the widget's styles depending on the existing setup.
+        if (typeof editor.config.contentsCss == 'object') {
+            editor.config.contentsCss.push(CKEDITOR.getUrl(this.path + 'contents.css'));
+        }
+
+        else {
+            editor.config.contentsCss = [editor.config.contentsCss, CKEDITOR.getUrl(this.path + 'contents.css')];
+        }
+
+    }
+
+
+} );
\ No newline at end of file
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/samples/index.html b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/samples/index.html
new file mode 100644 (file)
index 0000000..51d3fe6
--- /dev/null
@@ -0,0 +1,179 @@
+<!DOCTYPE html>
+<html lang="en">
+
+  <head>
+    <meta charset="utf-8" />
+    <title>Bootstrap, from Twitter</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <meta name="description" content="" />
+    <meta name="author" content="" />
+    <!-- Le styles -->
+    <link data-require="bootstrap-css" data-semver="3.3.1" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" />
+    <link data-require="bootstrap@*" data-semver="3.3.1" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" />
+    <style>
+      body {
+        padding-top: 60px;
+      }
+      @media (max-width: 979px) {
+
+        /* Remove any padding from the body */
+        body {
+          padding-top: 0;
+        }
+      }
+    </style>
+    <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
+    <!--[if lt IE 9]>
+      <script src="//html5shim.googlecode.com/svn/trunk/html5.js"></script>
+    <![endif]-->
+    <!-- Le fav and touch icons -->
+    <link rel="shortcut icon" href="images/favicon.ico" />
+    <link rel="apple-touch-icon" href="images/apple-touch-icon.png" />
+    <link rel="apple-touch-icon" sizes="72x72" href="images/apple-touch-icon-72x72.png" />
+    <link rel="apple-touch-icon" sizes="114x114" href="images/apple-touch-icon-114x114.png" />
+    <!-- Le javascript
+    ================================================== -->
+    <script data-require="jquery" data-semver="2.1.3" src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
+    <script data-require="bootstrap" data-semver="3.3.1" src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js"></script>
+    <script src="http://albatrossdigital.github.io/ckeditor-sandbox/ckeditor.js"></script>
+  </head>
+
+  <body>
+    <div class="container">
+      <h1>CKEditor Bootstrap Widgets</h1>
+      <p>
+        Give your content authors the ability to add responsive layout elements to their pages.  CKEditor widgets are provided for common 
+        <a href="http://getbootstrap.com/">Bootstrap</a> layouts:
+      </p>
+      <ul>
+        <li>Left sidebar image</li>
+        <li>Right sidebar image</li>
+        <li>Two columns</li>
+        <li>Three columns</li>
+        <li>Alert box</li>
+      </ul>
+      <p>Each widget can have its own button, or you can use the Widget Template Menu plugin to create a dropdown menu of templates.</p>
+      <p><strong>Watch a demo: <a href="http://albatrossdigital.com/node/41">http://albatrossdigital.com/node/41</a></strong></p>
+
+      <h2>Using</h2>
+      <p>Install the plugin using the standard <a href="http://docs.ckeditor.com/#!/guide/dev_plugins" target="_blank">CKEditor plugin installation instructions</a>.  
+      We recommend manually building your editor on ckeditor.com, or using <a href="https://github.com/albatrossdigital/ckeditor-sandbox" target="_blank">our base editor build</a>, which will include everything you need.</p>
+      <!--<pre>&lt;script src="http://albatrossdigital.github.io/ckeditor-sandbox/ckeditor.js"&gt;&lt;/script&gt;</pre>-->
+      <p>Once you have added the plugin, you can use our Widget Template Menu plugin or the standard Toolbar Groups.  For each, you must set extraPlugins and allowedContent.</p>
+      <ul>
+        <li><a href="#widgetTemplateMenu">Widget Template Menu</a></li>
+        <li><a href="#toolbarGroups">Toolbar Groups</a></li>
+        <!--<li><a href="#customToolbar">Custom Toolbar</a></li>-->
+      </ul>
+
+      <br/><a name="widgetTemplateMenu"></a><h3>Widget Template Menu</h3>
+
+      <pre>
+        CKEDITOR.replace( 'editor1', {
+          plugins: 'wysiwygarea,toolbar,basicstyles,menubutton,link,sourcearea,image2,widget',
+          extraPlugins: 'widgetbootstrap,widgettemplatemenu',
+          height: 400,
+          toolbar: [
+            [ 'Source', '-', 'NewPage', 'Preview', '-', 'Templates' ],
+            [ 'Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo' ],
+            [ 'Bold' ],
+            [ 'WidgetTemplateMenu' ]
+          ],
+          allowedContent: 'p a div span h2 h3 h4 h5 h6 section article iframe object embed strong b i em cite pre blockquote small,' +
+            'sub sup code ul ol li dl dt dd table thead tbody th tr td img caption mediawrapper br[href,src,target,width,height,colspan,' +
+            'span,alt,name,title,class,id,data-options]{text-align,float,margin}(*);'
+        } );
+      </pre>
+
+      <textarea id="editor1" cols="10" rows="10">
+        <div class="row two-col-right"><div class="col-md-9 col-main"><p>Content</p></div><div class="col-md-3 col-sidebar"><p><img alt="" src="http://placehold.it/300x250&amp;text=Image" /></p></div></div>
+        <p>&nbsp;</p>
+      </textarea>
+
+      <script>
+
+        CKEDITOR.replace( 'editor1', {
+          plugins: 'wysiwygarea,toolbar,basicstyles,menubutton,link,sourcearea,image2,widget',
+          extraPlugins: 'widgetbootstrap,widgettemplatemenu',
+          height: 400,
+          toolbar: [
+            [ 'Source', '-', 'NewPage', 'Preview', '-', 'Templates' ],
+            [ 'Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo' ],
+            [ 'Bold' ],
+            [ 'WidgetTemplateMenu' ]
+          ],
+          allowedContent: 'p a div span h2 h3 h4 h5 h6 section article iframe object embed strong b i em cite pre blockquote small,' +
+            'sub sup code ul ol li dl dt dd table thead tbody th tr td img caption mediawrapper br[href,src,target,width,height,colspan,' +
+            'span,alt,name,title,class,id,data-options]{text-align,float,margin}(*);'
+        } );
+
+      </script>
+
+
+      <br/><a name="toolbarGroups"></a><h3>Toolbar Groups</h3>
+      <pre>
+        CKEDITOR.replace( 'editor2', {
+          plugins: 'wysiwygarea,toolbar,basicstyles,menubutton,link,sourcearea',
+          extraPlugins: 'image2,widget,widgetbootstrap',
+          height: 400,
+          toolbarGroups : [
+            { name: 'clipboard',   groups: [ 'clipboard', 'undo', 'source' ] },
+            { name: 'editing',     groups: [ 'find', 'selection', 'spellchecker' ] },
+            { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] },
+            { name: 'paragraph',   groups: [ 'list', 'indent', 'blocks', 'align' ] },
+            { name: 'links' },
+            { name: 'insert' },
+            { name: 'about' }
+          ],
+          allowedContent: 'p a div span h2 h3 h4 h5 h6 section article iframe object embed strong b i em cite pre blockquote small,' +
+            'sub sup code ul ol li dl dt dd table thead tbody th tr td img caption mediawrapper br[href,src,target,width,height,colspan,' +
+            'span,alt,name,title,class,id,data-options]{text-align,float,margin}(*);'
+        } );
+      </pre>
+
+      <textarea id="editor2" cols="10" rows="10">
+        <div class="row two-col-right"><div class="col-md-9 col-main"><p>Content</p></div><div class="col-md-3 col-sidebar"><p><img alt="" src="http://placehold.it/300x250&amp;text=Image" /></p></div></div>
+        <p>&nbsp;</p>
+      </textarea>
+
+      <script>
+
+        CKEDITOR.replace( 'editor2', {
+          plugins: 'wysiwygarea,toolbar,basicstyles,menubutton,link,sourcearea',
+          extraPlugins: 'image2,widget,widgetbootstrap',
+          height: 400,
+          toolbarGroups : [
+              // On the basic preset, clipboard and undo is handled by keyboard.
+              // Uncomment the following line to enable them on the toolbar as well.
+              { name: 'clipboard',   groups: [ 'clipboard', 'undo', 'source' ] },
+              { name: 'editing',     groups: [ 'find', 'selection', 'spellchecker' ] },
+              { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] },
+              { name: 'paragraph',   groups: [ 'list', 'indent', 'blocks', 'align' ] },
+              { name: 'links' },
+              { name: 'insert' },
+              { name: 'about' }
+          ],
+          allowedContent: 'p a div span h2 h3 h4 h5 h6 section article iframe object embed strong b i em cite pre blockquote small,' +
+            'sub sup code ul ol li dl dt dd table thead tbody th tr td img caption mediawrapper br[href,src,target,width,height,colspan,' +
+            'span,alt,name,title,class,id,data-options]{text-align,float,margin}(*);'
+        } );
+
+      </script>
+
+      <br/><br/><h2>About the Creators</h2>
+      <div class="row two-col-left">
+        <div class="col-md-2 col-sidebar">
+          <p><a href="http://albatrossdigital.com" title="Albatross Digital" target="_blank"><img alt="Albatross Digital" src="http://albatrossdigital.com/images/logo.png" /></a></p>
+        </div>
+
+        <div class="col-md-10 col-main">
+          <p><a href="http://albatrossdigital.com" target="_blank">Albatross Digital</a> is a small group that builds websites, designs identities and solve problems.
+          We have spent a lot of time and resources thinking about better content authoring experiences and workflows.  If you're interested in learning 
+          more, <a href="http://albatrossdigital.com/projects">have a look at our work</a> or send us a note at <a href="mailto:hello@albatrossdigital.com">hello@albatrossdigital.com</a>.
+        </div>
+      </div>
+    </div>
+
+  </body>
+
+</html>
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/samples/simplebox.html b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/samples/simplebox.html
new file mode 100644 (file)
index 0000000..7fbfef2
--- /dev/null
@@ -0,0 +1,86 @@
+
+<!DOCTYPE html>
+<html>
+<head>
+       <title>Tutorial &mdash; Simple Box Widget, part 2</title>
+       <meta charset="utf-8">
+       <script src="../../../../ckeditor.js"></script>
+</head>
+<body>
+       <h1 class="samples">
+               Tutorial &mdash; Simple Box Widget, part 2
+       </h1>
+
+       <textarea id="editor1" cols="10" rows="10">
+               <h1>Simple Box Sample</h1>
+
+               <div class="simplebox align-right" style="width: 50%">
+                       <h2 class="simplebox-title">Title</h2>
+                       <div class="simplebox-content">
+                               <p>Foo <strong>bar</strong>.</p>
+                               <ul>
+                                       <li>Foo!</li>
+                                       <li>Bar!</li>
+                               </ul>
+                       </div>
+               </div>
+
+               <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sit amet orci ut nisi adipiscing ultrices. Sed pellentesque iaculis malesuada. Pellentesque scelerisque, purus non porta dictum, neque urna bibendum dolor, eget tristique ipsum metus fringilla dolor. Nullam sed accumsan sapien. Vestibulum in placerat magna. Sed justo lacus, volutpat rhoncus odio luctus, ornare adipiscing mauris. Vivamus erat sem, egestas et lectus eget, varius cursus odio. Duis posuere lacus sit amet urna bibendum, id iaculis eros ultrices. Vestibulum a ultrices ante.</p>
+
+               <p>Pellentesque vitae eleifend nisl, non accumsan tellus. Maecenas nec libero non tellus tincidunt mollis porttitor sed arcu. Donec ultricies nulla vitae eros lacinia, vel congue sem auctor. Vivamus convallis, urna ac tincidunt malesuada, lectus erat convallis metus, a hendrerit massa augue accumsan magna. Nulla mattis tellus elit, nec congue magna scelerisque eget. Aliquam posuere nisi augue, posuere sodales nisi iaculis eu. Donec fermentum urna id nibh sagittis fermentum sit amet sed enim. Aliquam neque elit, pretium elementum nunc a, faucibus accumsan lorem. Etiam pulvinar odio et hendrerit tincidunt. Suspendisse tempus eros lacus, in convallis velit mollis ut. Aenean congue, justo eleifend ultricies malesuada, nunc nunc molestie mauris, eget placerat libero eros vel nisi. Quisque diam arcu, mollis ac laoreet vitae, varius et sem. Interdum et malesuada fames ac ante ipsum primis in faucibus. Duis in vehicula sapien. Nunc feugiat porta elit nec volutpat.</p>
+
+               <div class="simplebox align-center" style="width: 750px">
+                       <h2 class="simplebox-title">Title</h2>
+                       <div class="simplebox-content">
+                               <p>Foo <strong>bar</strong>.</p>
+                               <ul>
+                                       <li>Foo!</li>
+                                       <li>Bar!</li>
+                               </ul>
+                       </div>
+               </div>
+
+               <p>Ut eget ipsum a sapien porta ultrices. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus mi lacus, pharetra eu bibendum blandit, tristique sit amet leo. Integer eu nulla nec magna vulputate blandit. Praesent mattis quis ante eget adipiscing. Nulla vel tempus risus, in placerat velit. Mauris sed nibh at elit posuere laoreet. Morbi non sapien sed nunc fringilla imperdiet.</p>
+       </textarea>
+
+       <script>
+
+CKEDITOR.replace( 'editor1', {
+    plugins: 'wysiwygarea,toolbar,basicstyles,menubutton,link,sourcearea',
+    extraPlugins: 'autosave,lineutils,image2,widget,leaflet,widgetfoundation,fontawesome,codesnippet', //,autogrow
+    contentsCss: [
+        '//cdnjs.cloudflare.com/ajax/libs/foundation/5.3.3/css/foundation.css',
+               '../../../../contents.css',
+               '../contents.css',
+        '//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css',
+       ],
+       height: 400,
+    toolbarGroups : [
+        // On the basic preset, clipboard and undo is handled by keyboard.
+        // Uncomment the following line to enable them on the toolbar as well.
+        { name: 'clipboard',   groups: [ 'clipboard', 'undo', 'source' ] },
+        { name: 'editing',     groups: [ 'find', 'selection', 'spellchecker' ] },
+        { name: 'forms' },
+        { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] },
+        { name: 'paragraph',   groups: [ 'list', 'indent', 'blocks', 'align' ] },
+        { name: 'links' },
+        { name: 'insert' },
+        { name: 'styles' },
+        { name: 'colors' },
+        { name: 'tools' },
+        { name: 'others' },
+        { name: 'about' }
+    ],
+    
+    toolbar: [
+           [ 'Source', '-', 'NewPage', 'Preview', '-', 'Templates' ],
+           [ 'Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo' ],
+           [ 'Bold', 'WidgetFoundation' ]
+       ], 
+
+} );
+
+       </script>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/contents.css b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/contents.css
new file mode 100644 (file)
index 0000000..7ff971c
--- /dev/null
@@ -0,0 +1,48 @@
+.box,
+.quotebox {
+       padding: 8px;
+       margin: 10px auto;
+       background: #eee;
+       border-radius: 8px;
+       border: 1px solid #ddd;
+       box-shadow: 0 1px 1px #fff inset, 0 -1px 0px #ccc inset;
+}
+.box .box-title,
+.box .box-content,
+.quotebox .quote,
+.quotebox .byline {
+       box-shadow: 0 1px 1px #ddd inset;
+       border: 1px solid #cccccc;
+       border-radius: 5px;
+       background: #fff;
+       margin-left: 0;
+       margin-right: 0;
+       padding: 2px 5px;
+}
+
+.box .box-content,
+.quotebox .quote {
+       min-height: 3em;
+}
+
+.box .box-title p,
+.quotebox .byline p {
+       margin-bottom: 0;
+}
+
+
+.box .box-title {
+       background: #ddd;
+}
+.box .row {
+       margin-left: 0;
+       margin-right: 0;
+}
+
+.quotebox {
+       background: #eee url(icons/widgetcommonQuotebox.png) 5px 2px no-repeat;
+}
+.quotebox .quote {
+       margin-left: 20px;
+       margin-bottom: 5px;
+}
\ No newline at end of file
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/dialogs/foundation2Col.js b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/dialogs/foundation2Col.js
new file mode 100644 (file)
index 0000000..82d683e
--- /dev/null
@@ -0,0 +1,43 @@
+CKEDITOR.dialog.add( 'foundation2Col', function( editor ) {
+    return {
+        title: 'Edit 2 Column Box',
+        minWidth: 200,
+        minHeight: 100,
+        contents: [
+            {
+                id: 'info',
+                elements: [
+                    {
+                        id: 'align',
+                        type: 'select',
+                        label: 'Align',
+                        items: [
+                            [ editor.lang.common.notSet, '' ],
+                            [ editor.lang.common.alignLeft, 'left' ],
+                            [ editor.lang.common.alignRight, 'right' ],
+                            [ editor.lang.common.alignCenter, 'center' ]
+                        ],
+                        setup: function( widget ) {
+                            this.setValue( widget.data.align );
+                        },
+                        commit: function( widget ) {
+                            widget.setData( 'align', this.getValue() );
+                        }
+                    },
+                    {
+                        id: 'width',
+                        type: 'text',
+                        label: 'Width',
+                        width: '50px',
+                        setup: function( widget ) {
+                            this.setValue( widget.data.width );
+                        },
+                        commit: function( widget ) {
+                            widget.setData( 'width', this.getValue() );
+                        }
+                    }
+                ]
+            }
+        ]
+    };
+} );
\ No newline at end of file
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/dialogs/widgetfoundationAccordion.js b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/dialogs/widgetfoundationAccordion.js
new file mode 100644 (file)
index 0000000..4ad7e2e
--- /dev/null
@@ -0,0 +1,61 @@
+CKEDITOR.dialog.add( 'widgetfoundationAccordion', function( editor ) {
+    return {
+        title: 'Edit Accordion Box',
+        minWidth: 200,
+        minHeight: 100,
+        contents: [
+            {
+                id: 'info',
+                elements: [
+                    {
+                        id: 'name',
+                        type: 'text',
+                        label: 'Accordion machine name',
+                        width: '200px',
+                        setup: function( widget ) {
+                            this.setValue( widget.data.name != undefined ? widget.data.name : 'accordion');
+                        },
+                        commit: function( widget ) {
+                            widget.setData( 'name', this.getValue() );
+                        }
+                    },
+                    {
+                        id: 'count',
+                        type: 'text',
+                        label: 'Number of panels',
+                        width: '50px',
+                        setup: function( widget ) {
+                            this.setValue( widget.data.count != undefined ? widget.data.count : 3);
+                        },
+                        commit: function( widget ) {
+                            widget.setData( 'count', this.getValue() );
+                        }
+                    },
+                    {
+                        id: 'activePanel',
+                        type: 'text',
+                        width: '50px',
+                        label: 'Active panel (leave blank for accordion to be initially collapsed, or enter the number of the panel you would like open, ex: 1)',
+                        setup: function( widget ) {
+                            this.setValue( widget.data.activePanel);
+                        },
+                        commit: function( widget ) {
+                            widget.setData( 'activePanel', this.getValue() );
+                        }
+                    },
+                    {
+                        id: 'multiExpand',
+                        type: 'checkbox',
+                        label: 'Allow multiple accordion panels to be expanded at the same time',
+                        setup: function( widget ) {
+                            this.setValue( widget.data.multiExpand );
+                        },
+                        commit: function( widget ) {
+                            widget.setData( 'multiExpand', this.getValue() );
+                        }
+                    }
+                ]
+            }
+        ]
+    };
+} );
\ No newline at end of file
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/icons/widgetcommonBox.png b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/icons/widgetcommonBox.png
new file mode 100644 (file)
index 0000000..6a5e313
Binary files /dev/null and b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/icons/widgetcommonBox.png differ
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/icons/widgetcommonQuotebox.png b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/icons/widgetcommonQuotebox.png
new file mode 100644 (file)
index 0000000..714bee1
Binary files /dev/null and b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/icons/widgetcommonQuotebox.png differ
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/plugin.js b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/plugin.js
new file mode 100644 (file)
index 0000000..a45b35a
--- /dev/null
@@ -0,0 +1,104 @@
+CKEDITOR.plugins.add( 'widgetcommon', {
+    requires: 'widget',
+
+    icons: 'widgetcommonQuotebox,widgetcommonBox',
+
+    defaults : {
+        name: 'accordion',
+        count: 3,
+        activePanel: 1,
+        multiExpand: false
+    },
+
+    init: function( editor ) {
+        
+        // Configurable settings
+        var allowedFull = editor.config.widgetcommon_allowedFull != undefined ? editor.config.widgetcommon_allowedFull :
+            'div(!row,two-col-left,two-col-right,accordion,two-col,three-col){width};' +
+            'div(!columns,col-xs-12,col-sm-3,col-sm-9,col-sidebar,col-main,col-1,col-2,col-3,panel,panel-default,panel-heading,panel-body)';
+        var allowedWidget = editor.config.widgetcommon_allowedWidget != undefined ? editor.config.widgetcommon_allowedFull :
+            'p br ul ol li a strong em img[!src,alt,width,height]';
+        var allowedText = editor.config.widgetcommon_allowedText != undefined ? editor.config.widgetcommon_allowedFull :
+            'p br ul ol li strong em';
+        var allowedTitle = editor.config.widgetcommon_allowedTitle != undefined ? editor.config.widgetcommon_allowedTitle :
+            'strong em';
+
+        //allowedWidget = 'img[!src,alt,width,height]';
+        //allowedText = allowedWidget;
+
+        var showButtons = editor.config.widgetcommonShowButtons != undefined ? editor.config.widgetcommonShowButtons : true;
+
+
+        editor.widgets.add( 'widgetcommonBox', {
+
+            button: showButtons ? 'Add box' : undefined,
+
+            template:
+                '<div class="panel panel-default">' +
+                    '<div class="panel-heading box-title">Title</h2></div>' +
+                    '<div class="panel-body box-content">Content</div>' +
+                '</div>',
+
+            editables: {
+                title: {
+                    selector: '.box-title',
+                    allowedContent: allowedTitle
+                },
+                content: {
+                    selector: '.box-content',
+                    allowedContent: allowedWidget
+                }
+            },
+
+            allowedContent: allowedFull,
+
+            upcast: function( element ) {
+                return element.name == 'div' && element.hasClass( 'two-col-right' );
+            }
+
+        } );
+
+        // Define the widgets
+        editor.widgets.add( 'widgetcommonQuotebox', {
+
+            button: showButtons ? 'Add Quotebox' : undefined,
+
+            template:
+                '<div class="row quotebox">' +
+                    '<div class="row quote">Quote</div>' +
+                    '<div class="row byline">&mdash; Person</div>' +
+                '</div>',
+
+            editables: {
+                quote: {
+                    selector: '.quote',
+                    allowedContent: allowedFull
+                },
+                byline: {
+                    selector: '.byline',
+                    allowedContent: allowedTitle
+                }
+            },
+
+            allowedContent: allowedFull,
+
+            upcast: function( element ) {
+                return element.name == 'div' && element.hasClass( 'quotebox' );
+            }
+            
+        } );
+
+        // Append the widget's styles when in the CKEditor edit page,
+        // added for better user experience.
+        // Assign or append the widget's styles depending on the existing setup.
+        if (typeof editor.config.contentsCss == 'object') {
+            editor.config.contentsCss.push(CKEDITOR.getUrl(this.path + 'contents.css'));
+        }
+
+        else {
+            editor.config.contentsCss = [editor.config.contentsCss, CKEDITOR.getUrl(this.path + 'contents.css')];
+        }
+    }
+
+
+} );
\ No newline at end of file
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/samples/contents.css b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/samples/contents.css
new file mode 100644 (file)
index 0000000..bd413ac
--- /dev/null
@@ -0,0 +1,35 @@
+.two-col,
+.three-col {
+       padding: 8px;
+       margin: 10px auto;
+       background: #eee;
+       border-radius: 8px;
+       border: 1px solid #ddd;
+       box-shadow: 0 1px 1px #fff inset, 0 -1px 0px #ccc inset;
+}
+.two-col .columns,
+.three-col .columns {
+       box-shadow: 0 1px 1px #ddd inset;
+       border: 1px solid #cccccc;
+       border-radius: 5px;
+       background: #fff;
+}
+.two-col .columns {
+       margin: -2px;
+}
+.two-col .col-sidebar {
+       padding: 0;
+}
+.three-col .col-1 {
+       margin-left: -3px;
+}
+.three-col .col-2 {
+       margin-left: 3px;
+}
+.three-col .col-3 {
+       margin-right: -3px;
+}
+
+.accordion .accordion-navigation > .content, .accordion dd > .content {
+       display: block !important;
+}
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/samples/simplebox.html b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/samples/simplebox.html
new file mode 100644 (file)
index 0000000..7fbfef2
--- /dev/null
@@ -0,0 +1,86 @@
+
+<!DOCTYPE html>
+<html>
+<head>
+       <title>Tutorial &mdash; Simple Box Widget, part 2</title>
+       <meta charset="utf-8">
+       <script src="../../../../ckeditor.js"></script>
+</head>
+<body>
+       <h1 class="samples">
+               Tutorial &mdash; Simple Box Widget, part 2
+       </h1>
+
+       <textarea id="editor1" cols="10" rows="10">
+               <h1>Simple Box Sample</h1>
+
+               <div class="simplebox align-right" style="width: 50%">
+                       <h2 class="simplebox-title">Title</h2>
+                       <div class="simplebox-content">
+                               <p>Foo <strong>bar</strong>.</p>
+                               <ul>
+                                       <li>Foo!</li>
+                                       <li>Bar!</li>
+                               </ul>
+                       </div>
+               </div>
+
+               <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sit amet orci ut nisi adipiscing ultrices. Sed pellentesque iaculis malesuada. Pellentesque scelerisque, purus non porta dictum, neque urna bibendum dolor, eget tristique ipsum metus fringilla dolor. Nullam sed accumsan sapien. Vestibulum in placerat magna. Sed justo lacus, volutpat rhoncus odio luctus, ornare adipiscing mauris. Vivamus erat sem, egestas et lectus eget, varius cursus odio. Duis posuere lacus sit amet urna bibendum, id iaculis eros ultrices. Vestibulum a ultrices ante.</p>
+
+               <p>Pellentesque vitae eleifend nisl, non accumsan tellus. Maecenas nec libero non tellus tincidunt mollis porttitor sed arcu. Donec ultricies nulla vitae eros lacinia, vel congue sem auctor. Vivamus convallis, urna ac tincidunt malesuada, lectus erat convallis metus, a hendrerit massa augue accumsan magna. Nulla mattis tellus elit, nec congue magna scelerisque eget. Aliquam posuere nisi augue, posuere sodales nisi iaculis eu. Donec fermentum urna id nibh sagittis fermentum sit amet sed enim. Aliquam neque elit, pretium elementum nunc a, faucibus accumsan lorem. Etiam pulvinar odio et hendrerit tincidunt. Suspendisse tempus eros lacus, in convallis velit mollis ut. Aenean congue, justo eleifend ultricies malesuada, nunc nunc molestie mauris, eget placerat libero eros vel nisi. Quisque diam arcu, mollis ac laoreet vitae, varius et sem. Interdum et malesuada fames ac ante ipsum primis in faucibus. Duis in vehicula sapien. Nunc feugiat porta elit nec volutpat.</p>
+
+               <div class="simplebox align-center" style="width: 750px">
+                       <h2 class="simplebox-title">Title</h2>
+                       <div class="simplebox-content">
+                               <p>Foo <strong>bar</strong>.</p>
+                               <ul>
+                                       <li>Foo!</li>
+                                       <li>Bar!</li>
+                               </ul>
+                       </div>
+               </div>
+
+               <p>Ut eget ipsum a sapien porta ultrices. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus mi lacus, pharetra eu bibendum blandit, tristique sit amet leo. Integer eu nulla nec magna vulputate blandit. Praesent mattis quis ante eget adipiscing. Nulla vel tempus risus, in placerat velit. Mauris sed nibh at elit posuere laoreet. Morbi non sapien sed nunc fringilla imperdiet.</p>
+       </textarea>
+
+       <script>
+
+CKEDITOR.replace( 'editor1', {
+    plugins: 'wysiwygarea,toolbar,basicstyles,menubutton,link,sourcearea',
+    extraPlugins: 'autosave,lineutils,image2,widget,leaflet,widgetfoundation,fontawesome,codesnippet', //,autogrow
+    contentsCss: [
+        '//cdnjs.cloudflare.com/ajax/libs/foundation/5.3.3/css/foundation.css',
+               '../../../../contents.css',
+               '../contents.css',
+        '//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css',
+       ],
+       height: 400,
+    toolbarGroups : [
+        // On the basic preset, clipboard and undo is handled by keyboard.
+        // Uncomment the following line to enable them on the toolbar as well.
+        { name: 'clipboard',   groups: [ 'clipboard', 'undo', 'source' ] },
+        { name: 'editing',     groups: [ 'find', 'selection', 'spellchecker' ] },
+        { name: 'forms' },
+        { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] },
+        { name: 'paragraph',   groups: [ 'list', 'indent', 'blocks', 'align' ] },
+        { name: 'links' },
+        { name: 'insert' },
+        { name: 'styles' },
+        { name: 'colors' },
+        { name: 'tools' },
+        { name: 'others' },
+        { name: 'about' }
+    ],
+    
+    toolbar: [
+           [ 'Source', '-', 'NewPage', 'Preview', '-', 'Templates' ],
+           [ 'Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo' ],
+           [ 'Bold', 'WidgetFoundation' ]
+       ], 
+
+} );
+
+       </script>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/extraIcons/codesnippet.png b/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/extraIcons/codesnippet.png
new file mode 100644 (file)
index 0000000..187d188
Binary files /dev/null and b/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/extraIcons/codesnippet.png differ
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/extraIcons/drupalbreak.png b/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/extraIcons/drupalbreak.png
new file mode 100644 (file)
index 0000000..16fd067
Binary files /dev/null and b/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/extraIcons/drupalbreak.png differ
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/extraIcons/fontawesome.png b/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/extraIcons/fontawesome.png
new file mode 100644 (file)
index 0000000..566825b
Binary files /dev/null and b/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/extraIcons/fontawesome.png differ
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/extraIcons/leaflet.png b/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/extraIcons/leaflet.png
new file mode 100644 (file)
index 0000000..803b31a
Binary files /dev/null and b/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/extraIcons/leaflet.png differ
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/extraIcons/oembed.png b/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/extraIcons/oembed.png
new file mode 100644 (file)
index 0000000..2ba7cce
Binary files /dev/null and b/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/extraIcons/oembed.png differ
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/icons/widgettemplatemenu.png b/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/icons/widgettemplatemenu.png
new file mode 100644 (file)
index 0000000..57c0268
Binary files /dev/null and b/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/icons/widgettemplatemenu.png differ
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/plugin.js b/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/plugin.js
new file mode 100644 (file)
index 0000000..b8b4374
--- /dev/null
@@ -0,0 +1,90 @@
+CKEDITOR.plugins.add( 'widgettemplatemenu', {
+    requires: 'menu',
+
+    defaults : {
+        name: 'accordion',
+        count: 3,
+        activePanel: 1,
+        multiExpand: false
+    },
+
+    init: function( editor ) {
+        
+        // Set the default button info based on installed plugins
+        var buttonData = {};
+        // @todo: make these if statement work
+        if (editor.plugins.widgetcommon != undefined) {
+            buttonData.widgetcommonBox = 'Insert box';
+            buttonData.widgetcommonQuotebox = 'Insert quote box';
+        }
+        if (editor.plugins.widgetbootstrap != undefined) {
+            buttonData.widgetbootstrapLeftCol = 'Insert left column template';
+            buttonData.widgetbootstrapRightCol = 'Insert right column template';
+            buttonData.widgetbootstrapTwoCol = 'Insert two column template';
+            buttonData.widgetbootstrapThreeCol = 'Insert three column template';
+            buttonData.widgetbootstrapAlert = 'Insert Alert box';
+        }
+        if (editor.commands.oembed != undefined) {
+            buttonData.oembed = 'Insert media';
+        }
+        if (editor.commands.codeSnippet != undefined) {
+            buttonData.codeSnippet = 'Insert code snippet';
+        }
+        if (editor.commands.leaflet != undefined) {
+            buttonData.leaflet = 'Insert map';
+        }
+
+        // Get the enabled menu items from editor.config
+        if (editor.config.widgettemplatemenuButtons != undefined) {
+            var config = editor.config.widgettemplatemenuButtons.split(',');
+            var buttons = {};
+            for (var i = 0; i < config.length; i++) {
+                buttons[config[i]] = buttonData[config[i]];
+            }
+        }
+        else {
+            var buttons = buttonData;
+        }
+        
+        // Build the list of menu items
+        var items =  {};
+        for(var key in buttons) {
+            items[key] = {
+                label: buttons[key],
+                command: key,
+                group: 'widgettemplatemenu',
+                icon: key
+            }
+        }
+
+        // Items must belong to a group.
+        editor.addMenuGroup( 'widgettemplatemenu' );
+        editor.addMenuItems( items );
+
+        editor.ui.add( 'WidgetTemplateMenu', CKEDITOR.UI_MENUBUTTON, {
+            label: 'Insert Template',
+            icon: this.path + 'icons/widgettemplatemenu.png' ,
+            onMenu: function() {
+                // You can control the state of your commands live, every time
+                // the menu is opened.
+                return {
+                    widgetcommonBox: editor.commands.widgetcommonBox == undefined ? false : editor.commands.widgetcommonBox.state,
+                    widgetcommonQuotebox: editor.commands.widgetcommonQuotebox == undefined ? false : editor.commands.widgetbootstrapLeftCol.state,
+                    widgetbootstrapLeftCol: editor.commands.widgetbootstrapLeftCol == undefined ? false : editor.commands.widgetbootstrapLeftCol.state,
+                    widgetbootstrapRightCol: editor.commands.widgetbootstrapRightCol == undefined ? false : editor.commands.widgetbootstrapRightCol.state,
+                    widgetbootstrapTwoCol: editor.commands.widgetbootstrapTwoCol == undefined ? false : editor.commands.widgetbootstrapTwoCol.state,
+                    widgetbootstrapThreeCol: editor.commands.widgetbootstrapThreeCol == undefined ? false : editor.commands.widgetbootstrapThreeCol.state,
+                    widgetbootstrapAlert: editor.commands.widgetbootstrapAlert == undefined ? false : editor.commands.widgetbootstrapAlert.state,
+                    widgetbootstrapAccordion: editor.commands.widgetbootstrapAccordion == undefined ? false : editor.commands.widgetbootstrapAccordion.state,
+                    oembed: editor.commands.oembed == undefined ? false : editor.commands.oembed.state,
+                    codeSnippet: editor.commands.codeSnippet == undefined ? false : editor.commands.codeSnippet.state,
+                    leaflet: editor.commands.leaflet == undefined ? false : editor.commands.leaflet.state,
+                    FontAwesome: editor.commands.FontAwesome == undefined ? false : editor.commands.FontAwesome.state
+                };
+            }
+        } );
+        
+    }
+
+
+} );
\ No newline at end of file
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/samples/contents.css b/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/samples/contents.css
new file mode 100644 (file)
index 0000000..bd413ac
--- /dev/null
@@ -0,0 +1,35 @@
+.two-col,
+.three-col {
+       padding: 8px;
+       margin: 10px auto;
+       background: #eee;
+       border-radius: 8px;
+       border: 1px solid #ddd;
+       box-shadow: 0 1px 1px #fff inset, 0 -1px 0px #ccc inset;
+}
+.two-col .columns,
+.three-col .columns {
+       box-shadow: 0 1px 1px #ddd inset;
+       border: 1px solid #cccccc;
+       border-radius: 5px;
+       background: #fff;
+}
+.two-col .columns {
+       margin: -2px;
+}
+.two-col .col-sidebar {
+       padding: 0;
+}
+.three-col .col-1 {
+       margin-left: -3px;
+}
+.three-col .col-2 {
+       margin-left: 3px;
+}
+.three-col .col-3 {
+       margin-right: -3px;
+}
+
+.accordion .accordion-navigation > .content, .accordion dd > .content {
+       display: block !important;
+}
diff --git a/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/samples/simplebox.html b/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/samples/simplebox.html
new file mode 100644 (file)
index 0000000..7fbfef2
--- /dev/null
@@ -0,0 +1,86 @@
+
+<!DOCTYPE html>
+<html>
+<head>
+       <title>Tutorial &mdash; Simple Box Widget, part 2</title>
+       <meta charset="utf-8">
+       <script src="../../../../ckeditor.js"></script>
+</head>
+<body>
+       <h1 class="samples">
+               Tutorial &mdash; Simple Box Widget, part 2
+       </h1>
+
+       <textarea id="editor1" cols="10" rows="10">
+               <h1>Simple Box Sample</h1>
+
+               <div class="simplebox align-right" style="width: 50%">
+                       <h2 class="simplebox-title">Title</h2>
+                       <div class="simplebox-content">
+                               <p>Foo <strong>bar</strong>.</p>
+                               <ul>
+                                       <li>Foo!</li>
+                                       <li>Bar!</li>
+                               </ul>
+                       </div>
+               </div>
+
+               <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sit amet orci ut nisi adipiscing ultrices. Sed pellentesque iaculis malesuada. Pellentesque scelerisque, purus non porta dictum, neque urna bibendum dolor, eget tristique ipsum metus fringilla dolor. Nullam sed accumsan sapien. Vestibulum in placerat magna. Sed justo lacus, volutpat rhoncus odio luctus, ornare adipiscing mauris. Vivamus erat sem, egestas et lectus eget, varius cursus odio. Duis posuere lacus sit amet urna bibendum, id iaculis eros ultrices. Vestibulum a ultrices ante.</p>
+
+               <p>Pellentesque vitae eleifend nisl, non accumsan tellus. Maecenas nec libero non tellus tincidunt mollis porttitor sed arcu. Donec ultricies nulla vitae eros lacinia, vel congue sem auctor. Vivamus convallis, urna ac tincidunt malesuada, lectus erat convallis metus, a hendrerit massa augue accumsan magna. Nulla mattis tellus elit, nec congue magna scelerisque eget. Aliquam posuere nisi augue, posuere sodales nisi iaculis eu. Donec fermentum urna id nibh sagittis fermentum sit amet sed enim. Aliquam neque elit, pretium elementum nunc a, faucibus accumsan lorem. Etiam pulvinar odio et hendrerit tincidunt. Suspendisse tempus eros lacus, in convallis velit mollis ut. Aenean congue, justo eleifend ultricies malesuada, nunc nunc molestie mauris, eget placerat libero eros vel nisi. Quisque diam arcu, mollis ac laoreet vitae, varius et sem. Interdum et malesuada fames ac ante ipsum primis in faucibus. Duis in vehicula sapien. Nunc feugiat porta elit nec volutpat.</p>
+
+               <div class="simplebox align-center" style="width: 750px">
+                       <h2 class="simplebox-title">Title</h2>
+                       <div class="simplebox-content">
+                               <p>Foo <strong>bar</strong>.</p>
+                               <ul>
+                                       <li>Foo!</li>
+                                       <li>Bar!</li>
+                               </ul>
+                       </div>
+               </div>
+
+               <p>Ut eget ipsum a sapien porta ultrices. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus mi lacus, pharetra eu bibendum blandit, tristique sit amet leo. Integer eu nulla nec magna vulputate blandit. Praesent mattis quis ante eget adipiscing. Nulla vel tempus risus, in placerat velit. Mauris sed nibh at elit posuere laoreet. Morbi non sapien sed nunc fringilla imperdiet.</p>
+       </textarea>
+
+       <script>
+
+CKEDITOR.replace( 'editor1', {
+    plugins: 'wysiwygarea,toolbar,basicstyles,menubutton,link,sourcearea',
+    extraPlugins: 'autosave,lineutils,image2,widget,leaflet,widgetfoundation,fontawesome,codesnippet', //,autogrow
+    contentsCss: [
+        '//cdnjs.cloudflare.com/ajax/libs/foundation/5.3.3/css/foundation.css',
+               '../../../../contents.css',
+               '../contents.css',
+        '//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css',
+       ],
+       height: 400,
+    toolbarGroups : [
+        // On the basic preset, clipboard and undo is handled by keyboard.
+        // Uncomment the following line to enable them on the toolbar as well.
+        { name: 'clipboard',   groups: [ 'clipboard', 'undo', 'source' ] },
+        { name: 'editing',     groups: [ 'find', 'selection', 'spellchecker' ] },
+        { name: 'forms' },
+        { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] },
+        { name: 'paragraph',   groups: [ 'list', 'indent', 'blocks', 'align' ] },
+        { name: 'links' },
+        { name: 'insert' },
+        { name: 'styles' },
+        { name: 'colors' },
+        { name: 'tools' },
+        { name: 'others' },
+        { name: 'about' }
+    ],
+    
+    toolbar: [
+           [ 'Source', '-', 'NewPage', 'Preview', '-', 'Templates' ],
+           [ 'Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo' ],
+           [ 'Bold', 'WidgetFoundation' ]
+       ], 
+
+} );
+
+       </script>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/web/modules/contrib/ckeditor_widgets/src/Plugin/CKEditorPlugin/WidgetBootstrap.php b/web/modules/contrib/ckeditor_widgets/src/Plugin/CKEditorPlugin/WidgetBootstrap.php
new file mode 100644 (file)
index 0000000..9e574cc
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * @file
+ * Definition of \Drupal\ckeditor_widgets\Plugin\CKEditorPlugin\AnchorLink.
+ */
+namespace Drupal\ckeditor_widgets\Plugin\CKEditorPlugin;
+
+use Drupal\editor\Entity\Editor;
+use Drupal\ckeditor\CKEditorPluginBase;
+
+/**
+ * Defines the "widgetbootstrap" plugin.
+ *
+ * @CKEditorPlugin(
+ *   id = "widgetbootstrap",
+ *   label = @Translation("CKEditor Bootstrap Widgets"),
+ *   module = "ckeditor_widgets"
+ * )
+ */
+class WidgetBootstrap extends CKEditorPluginBase {
+
+    /**
+     * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getFile().
+     */
+    function getFile() {
+        return drupal_get_path('module', 'ckeditor_widgets') . '/js/plugins/widgetbootstrap/plugin.js';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getDependencies(Editor $editor) {
+        return array();
+    }
+    /**
+     * {@inheritdoc}
+     */
+    public function getLibraries(Editor $editor) {
+        return array();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function isInternal() {
+        return FALSE;
+    }
+
+    /**
+     * Implements \Drupal\ckeditor\Plugin\CKEditorPluginButtonsInterface::getButtons().
+     */
+    function getButtons() {
+        return array(
+            'widgetbootstrapLeftCol' => array(
+                'label' => $this->t('Insert left column box'),
+                'image' => drupal_get_path('module', 'ckeditor_widgets') . '/js/plugins/widgetbootstrap/icons/widgetbootstrapLeftCol.png',
+            ),
+            'widgetbootstrapRightCol' => array(
+                'image' => drupal_get_path('module', 'ckeditor_widgets') . '/js/plugins/widgetbootstrap/icons/widgetbootstrapRightCol.png',
+                'label' => $this->t('Insert right column box'),
+            ),
+            'widgetbootstrapTwoCol' => array(
+                'image' => drupal_get_path('module', 'ckeditor_widgets') . '/js/plugins/widgetbootstrap/icons/widgetbootstrapTwoCol.png',
+                'label' => $this->t('Insert two column box'),
+            ),
+            'widgetbootstrapThreeCol' => array(
+                'image' => drupal_get_path('module', 'ckeditor_widgets') . '/js/plugins/widgetbootstrap/icons/widgetbootstrapThreeCol.png',
+                'label' => $this->t('Insert three column box'),
+            ),
+            'widgetbootstrapAlert' => array(
+                'image' => drupal_get_path('module', 'ckeditor_widgets') . '/js/plugins/widgetbootstrap/icons/widgetbootstrapAlert.png',
+                'label' => $this->t('Insert alert box'),
+            ),
+            'widgetbootstrapAccordion' => array(
+                'image' => drupal_get_path('module', 'ckeditor_widgets') . '/js/plugins/widgetbootstrap/icons/widgetbootstrapAccordion.png',
+                'label' => $this->t('Insert accordion box'),
+            ),
+        );
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getConfig(Editor $editor) {
+        return array();
+    }
+}
diff --git a/web/modules/contrib/ckeditor_widgets/src/Plugin/CKEditorPlugin/WidgetCommon.php b/web/modules/contrib/ckeditor_widgets/src/Plugin/CKEditorPlugin/WidgetCommon.php
new file mode 100644 (file)
index 0000000..a8021f4
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Definition of \Drupal\ckeditor_widgets\Plugin\CKEditorPlugin\AnchorLink.
+ */
+namespace Drupal\ckeditor_widgets\Plugin\CKEditorPlugin;
+
+use Drupal\editor\Entity\Editor;
+use Drupal\ckeditor\CKEditorPluginBase;
+
+/**
+ * Defines the "widgetcommon" plugin.
+ *
+ * @CKEditorPlugin(
+ *   id = "widgetcommon",
+ *   label = @Translation("CKEditor Common Widgets"),
+ *   module = "ckeditor_widgets"
+ * )
+ */
+class WidgetCommon extends CKEditorPluginBase {
+
+    /**
+     * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getFile().
+     */
+    function getFile() {
+        return drupal_get_path('module', 'ckeditor_widgets') . '/js/plugins/widgetcommon/plugin.js';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getDependencies(Editor $editor) {
+        return array();
+    }
+    /**
+     * {@inheritdoc}
+     */
+    public function getLibraries(Editor $editor) {
+        return array();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function isInternal() {
+        return FALSE;
+    }
+
+    /**
+     * Implements \Drupal\ckeditor\Plugin\CKEditorPluginButtonsInterface::getButtons().
+     */
+    function getButtons() {
+        return array(
+            'widgetcommonBox' => array(
+                'label' => $this->t('Insert box'),
+                'image' => drupal_get_path('module', 'ckeditor_widgets') . '/js/plugins/widgetcommon/icons/widgetcommonBox.png',
+            ),
+            'widgetcommonQuotebox' => array(
+                'image' => drupal_get_path('module', 'ckeditor_widgets') . '/js/plugins/widgetcommon/icons/widgetcommonQuotebox.png',
+                'label' => $this->t('Insert quote box'),
+            ),
+        );
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getConfig(Editor $editor) {
+        return array();
+    }
+}
diff --git a/web/modules/contrib/ckeditor_widgets/src/Plugin/CKEditorPlugin/WidgetTemplateMenu.php b/web/modules/contrib/ckeditor_widgets/src/Plugin/CKEditorPlugin/WidgetTemplateMenu.php
new file mode 100644 (file)
index 0000000..f5db7c1
--- /dev/null
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * @file
+ * Definition of \Drupal\ckeditor_widgets\Plugin\CKEditorPlugin\AnchorLink.
+ */
+namespace Drupal\ckeditor_widgets\Plugin\CKEditorPlugin;
+
+use Drupal\editor\Entity\Editor;
+use Drupal\ckeditor\CKEditorPluginBase;
+
+/**
+ * Defines the "widgettemplatemenu" plugin.
+ *
+ * @CKEditorPlugin(
+ *   id = "widgettemplatemenu",
+ *   label = @Translation("CKEditor Template Menu Widgets"),
+ *   module = "ckeditor_widgets"
+ * )
+ */
+class WidgetTemplateMenu extends CKEditorPluginBase {
+
+    /**
+     * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getFile().
+     */
+    function getFile() {
+        return drupal_get_path('module', 'ckeditor_widgets') . '/js/plugins/widgettemplatemenu/plugin.js';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getDependencies(Editor $editor) {
+        return array();
+    }
+    /**
+     * {@inheritdoc}
+     */
+    public function getLibraries(Editor $editor) {
+        return array();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function isInternal() {
+        return FALSE;
+    }
+
+    /**
+     * Implements \Drupal\ckeditor\Plugin\CKEditorPluginButtonsInterface::getButtons().
+     */
+    function getButtons() {
+        return array(
+            'oembed' => array(
+                'label' => $this->t('Insert media'),
+                'image' => drupal_get_path('module', 'ckeditor_widgets') . '/js/plugins/widgettemplatemenu/extraIcons/oembed.png',
+            ),
+            'codeSnippet' => array(
+                'image' => drupal_get_path('module', 'ckeditor_widgets') . '/js/plugins/widgettemplatemenu/extraIcons/codesnippet.png',
+                'label' => $this->t('Insert code snippet'),
+            ),
+            'leaflet' => array(
+                'image' => drupal_get_path('module', 'ckeditor_widgets') . '/js/plugins/widgettemplatemenu/extraIcons/leaflet.png',
+                'label' => $this->t('Insert leaflet map'),
+            ),
+            'FontAwesome' => array(
+                'image' => drupal_get_path('module', 'ckeditor_widgets') . '/js/plugins/widgettemplatemenu/extraIcons/fontawesome.png',
+                'label' => $this->t('Insert Font Awesome icon'),
+            ),
+            'WidgetTemplateMenu' => array(
+                'image' => drupal_get_path('module', 'ckeditor_widgets') . '/js/plugins/widgettemplatemenu/icons/widgettemplatemenu.png',
+                'label' => $this->t('Add Template Menu'),
+            ),
+        );
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getConfig(Editor $editor) {
+        return array();
+    }
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader b/web/modules/contrib/drupalmoduleupgrader
deleted file mode 160000 (submodule)
index 8bf2d9c..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 8bf2d9c140d4a315f267ff511fc69ea1595b62b8
diff --git a/web/modules/contrib/drupalmoduleupgrader/.gitignore b/web/modules/contrib/drupalmoduleupgrader/.gitignore
new file mode 100644 (file)
index 0000000..f350693
--- /dev/null
@@ -0,0 +1,7 @@
+composer.phar
+vendor
+bin
+.DS_Store
+checkout
+cache
+.idea
diff --git a/web/modules/contrib/drupalmoduleupgrader/.gitmodules b/web/modules/contrib/drupalmoduleupgrader/.gitmodules
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/web/modules/contrib/drupalmoduleupgrader/README.txt b/web/modules/contrib/drupalmoduleupgrader/README.txt
new file mode 100644 (file)
index 0000000..b44d7b6
--- /dev/null
@@ -0,0 +1,190 @@
+
+CONTENTS OF THIS FILE
+---------------------
+ * Introduction
+ * Usage
+ * Requirements
+ * Installation
+ * Troubleshooting
+ * FAQ
+ * Maintainers
+
+
+INTRODUCTION
+------------
+
+Drupal Module Upgrader is a script that scans the source of a Drupal 7 module,
+flags any code that requires updating to Drupal 8, points off to any relevant
+API change notices from https://www.drupal.org/list-changes/, and (where
+possible) will actually attempt to *convert* the Drupal 7 code automatically to
+the Drupal 8 version!
+
+ * For a full description of the module, visit the project page:
+   https://drupal.org/project/drupalmoduleupgrader
+ * To submit bug reports and feature suggestions, or to track changes:
+   https://drupal.org/project/issues/drupalmoduleupgrader
+
+
+USAGE
+-----
+
+1. Place the Drupal 7 module you wish to port into your Drupal 8 site's
+   /modules directory.
+
+2. To scan the code and get a report of code that needs updating and how, run
+   the following inside the Drupal 8 root directory:
+
+   drush dmu-analyze MODULE_NAME
+
+   This will print a report showing any relevant change notices where you can
+   read more.
+
+3. To attempt to upgrade your Drupal 7 module's code to Drupal 8 automatically,
+   run the following inside the Drupal 8 root directory:
+
+   drush dmu-upgrade MODULE_NAME
+
+   The script will output a few lines as it attempts various conversions. Go
+   into your modules/MODULE_NAME directory and check out all of your new YAML
+   files and such. ;)
+
+4. To clear out D7 code that has been converted, run the clean command:
+
+   drush dmu-clean MODULE_NAME
+
+   This will do things like delete old .info files and such, so you're closer to
+   your port being completed!
+
+REQUIREMENTS
+------------
+This project requires the following dependencies:
+
+ * Composer (https://getcomposer.org)
+ * Drush 7+ (https://github.com/drush-ops/drush)
+ * Pharborist (https://github.com/grom358/pharborist)
+ * Symfony Yaml Component (https://github.com/symfony/Yaml)
+
+Note that most dependencies are automatically downloaded by Composer during
+installation.
+
+
+INSTALLATION
+------------
+
+0. Download and install Composer:
+
+   https://getcomposer.org/doc/00-intro.md#system-requirements
+
+1. Download and install the latest version of Drush:
+
+   https://github.com/drush-ops/drush#installupdate---composer
+
+2. Download and install the latest Drupal 8:
+
+   git clone --branch 8.0.x http://git.drupal.org/project/drupal.git 8.x
+
+3. Download the latest release of drupalmoduleupgrader to your Drupal 8 site’s
+   /modules directory:
+
+   drush dl drupalmoduleupgrader
+
+4. Run `composer install` from the drupalmoduleupgrader directory:
+
+   cd drupalmoduleupgrader
+   composer install
+
+   You should see output as it downloads various dependencies (pharborist,
+   phpcs, yaml...)
+
+5. Finally, enable the module:
+
+   drush en drupalmoduleupgrader -y
+
+
+TROUBLESHOOTING
+---------------
+ * If you are getting any errors, check the following first:
+   - Are you using the very latest Drupal 8 code? From the 8.x root directory,
+     do:
+       git pull --rebase
+   - Are you using the very latest drupalmoduleupgrader code (and dependencies'
+     code)? From the drupalmoduleupgrader root directory:
+       git pull --rebase
+       composer update
+       drush pm-uninstall drupalmoduleupgrader -y
+       drush en drupalmoduleupgrader
+
+
+RUNNING TESTS
+-------------
+
+Drupal Module Upgrader uses Composer to install its dependencies inside the
+module folder. Since it operates as a standalone project this is perfectly
+fine. However if we want to run the tests we have to use a different strategy.
+Drupal core itself also uses Composer, and it manages its dependencies and
+namespaces inside the core/vendor/ folder. It is unaware of DMU's dependencies
+and the tests will fail.
+
+We can use Composer Manager [1] to generate a new composer.json file in the
+root of the Drupal site. This will combine the dependencies of Drupal core and
+all contributed and custom projects.
+
+
+0. Navigate to the root folder of your Drupal installation:
+
+   cd /path/to/drupal/site/
+
+1. Download Composer Manager:
+
+   drush dl composer_manager
+
+2. Initialize Composer Manager:
+
+   php modules/composer_manager/scripts/init.php
+
+3. Install the combined dependencies of Drupal core and modules:
+
+   composer drupal-install
+
+4. Run the tests:
+
+  ./vendor/bin/phpunit -c core --group=DMU
+
+
+FAQ
+---
+Q: Wow, this thing is awesome! How does it work under the hood?
+A: You're in luck! We have documentation describing DMU's overall architecture
+   and how to contribute:
+   https://www.drupal.org/documentation/modules/drupalmoduleupgrader/contributors
+
+MAINTAINERS
+-----------
+Current maintainers:
+ * Adam (phenaproxima) - https://www.drupal.org/u/phenaproxima
+ * Angela Byron (webchick) - https://www.drupal.org/u/webchick
+
+Past maintainers:
+ * Gábor Hojtsy - https://www.drupal.org/u/gábor-hojtsy
+ * Jakob Perry (japerry) - https://www.drupal.org/u/japerry
+ * Jess (xjm) - https://www.drupal.org/u/xjm
+ * Lisa Baker (eshta) - https://www.drupal.org/u/eshta
+ * Wim Leers - https://www.drupal.org/u/wim-leers
+
+Special thanks to:
+ * Cameron Zemek (grom358) - https://www.drupal.org/u/grom358 for all the
+   pharborist help!
+
+This project has been sponsored by:
+* Acquia
+  Dream It. Drupal It. https://www.acquia.com
+
+This project has been supported by:
+* PreviousNext
+  Australia’s premium Drupal website consulting, design and development firm.
+  http://www.previousnext.com.au/
+
+
+REFERENCES
+----------
+[1] Composer Manager: https://www.drupal.org/project/composer_manager
diff --git a/web/modules/contrib/drupalmoduleupgrader/composer.json b/web/modules/contrib/drupalmoduleupgrader/composer.json
new file mode 100644 (file)
index 0000000..0e09eb0
--- /dev/null
@@ -0,0 +1,33 @@
+{
+  "name": "drupal/drupalmoduleupgrader",
+  "description": "A Drush command to update Drupal 7 modules to Drupal 8.",
+  "type": "drupal-module",
+  "license": "GPL-2.0+",
+  "support": {
+    "issues": "https://drupal.org/project/issues/drupalmoduleupgrader",
+    "source": "https://drupal.org/project/drupalmoduleupgrader"
+  },
+  "require": {
+    "grom358/pharborist": "dev-master",
+    "symfony/finder": "^2.6.0",
+    "symfony/filesystem": "^2.6.0",
+    "cebe/markdown": "1.0.*@dev"
+  },
+  "keywords": [
+    "Drupal",
+    "upgrade",
+    "update",
+    "phpcs",
+    "PHP_CodeSniffer",
+    "standards"
+  ],
+  "require-dev": {
+    "mikey179/vfsStream": "^1.5",
+    "phpunit/phpunit": "^4.8"
+  },
+  "autoload": {
+    "psr-4": {
+        "Drupal\\drupalmoduleupgrader\\": "src/"
+    }
+  }
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/composer.lock b/web/modules/contrib/drupalmoduleupgrader/composer.lock
new file mode 100644 (file)
index 0000000..377a8d8
--- /dev/null
@@ -0,0 +1,1223 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+        "This file is @generated automatically"
+    ],
+    "hash": "f59cdad40a5a56c0723fad3bad091c72",
+    "content-hash": "5bd1f47c37890341b873152ed65e02c7",
+    "packages": [
+        {
+            "name": "cebe/markdown",
+            "version": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/cebe/markdown.git",
+                "reference": "f89dc1da1fc6823f0286d6cad736a642efd0f59e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/cebe/markdown/zipball/f89dc1da1fc6823f0286d6cad736a642efd0f59e",
+                "reference": "f89dc1da1fc6823f0286d6cad736a642efd0f59e",
+                "shasum": ""
+            },
+            "require": {
+                "lib-pcre": "*",
+                "php": ">=5.4.0"
+            },
+            "require-dev": {
+                "cebe/indent": "*",
+                "facebook/xhprof": "*@dev",
+                "phpunit/phpunit": "3.7.*"
+            },
+            "bin": [
+                "bin/markdown"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "cebe\\markdown\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Carsten Brandt",
+                    "email": "mail@cebe.cc",
+                    "homepage": "http://cebe.cc/",
+                    "role": "Creator"
+                }
+            ],
+            "description": "A super fast, highly extensible markdown parser for PHP",
+            "homepage": "https://github.com/cebe/markdown#readme",
+            "keywords": [
+                "extensible",
+                "fast",
+                "gfm",
+                "markdown",
+                "markdown-extra"
+            ],
+            "time": "2014-12-18 00:45:32"
+        },
+        {
+            "name": "grom358/pharborist",
+            "version": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/grom358/pharborist.git",
+                "reference": "0db9e51299a80e95b06857ed1809f59bbbab1af6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/grom358/pharborist/zipball/0db9e51299a80e95b06857ed1809f59bbbab1af6",
+                "reference": "0db9e51299a80e95b06857ed1809f59bbbab1af6",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4",
+                "phpdocumentor/reflection-docblock": "2.0.*"
+            },
+            "require-dev": {
+                "apigen/apigen": "2.8.*",
+                "phpunit/phpunit": "4.2.*"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Pharborist\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "GPL"
+            ],
+            "authors": [
+                {
+                    "name": "Cameron Zemek",
+                    "role": "lead"
+                }
+            ],
+            "description": "Pharborist builds a syntax tree for PHP that can be traversed and manipulated.",
+            "keywords": [
+                "standards",
+                "syntax"
+            ],
+            "time": "2015-09-20 22:14:29"
+        },
+        {
+            "name": "phpdocumentor/reflection-docblock",
+            "version": "2.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+                "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8",
+                "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.0"
+            },
+            "suggest": {
+                "dflydev/markdown": "~1.0",
+                "erusev/parsedown": "~1.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "phpDocumentor": [
+                        "src/"
+                    ]
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mike van Riel",
+                    "email": "mike.vanriel@naenius.com"
+                }
+            ],
+            "time": "2015-02-03 12:10:50"
+        },
+        {
+            "name": "symfony/filesystem",
+            "version": "v2.7.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/filesystem.git",
+                "reference": "a17f8a17c20e8614c15b8e116e2f4bcde102cfab"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/filesystem/zipball/a17f8a17c20e8614c15b8e116e2f4bcde102cfab",
+                "reference": "a17f8a17c20e8614c15b8e116e2f4bcde102cfab",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.9"
+            },
+            "require-dev": {
+                "symfony/phpunit-bridge": "~2.7"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.7-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Filesystem\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Filesystem Component",
+            "homepage": "https://symfony.com",
+            "time": "2015-09-09 17:42:36"
+        },
+        {
+            "name": "symfony/finder",
+            "version": "v2.7.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/finder.git",
+                "reference": "8262ab605973afbb3ef74b945daabf086f58366f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/finder/zipball/8262ab605973afbb3ef74b945daabf086f58366f",
+                "reference": "8262ab605973afbb3ef74b945daabf086f58366f",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.9"
+            },
+            "require-dev": {
+                "symfony/phpunit-bridge": "~2.7"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.7-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Finder\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Finder Component",
+            "homepage": "https://symfony.com",
+            "time": "2015-09-19 19:59:23"
+        }
+    ],
+    "packages-dev": [
+        {
+            "name": "doctrine/instantiator",
+            "version": "1.0.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/instantiator.git",
+                "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
+                "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3,<8.0-DEV"
+            },
+            "require-dev": {
+                "athletic/athletic": "~0.1.8",
+                "ext-pdo": "*",
+                "ext-phar": "*",
+                "phpunit/phpunit": "~4.0",
+                "squizlabs/php_codesniffer": "~2.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Marco Pivetta",
+                    "email": "ocramius@gmail.com",
+                    "homepage": "http://ocramius.github.com/"
+                }
+            ],
+            "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+            "homepage": "https://github.com/doctrine/instantiator",
+            "keywords": [
+                "constructor",
+                "instantiate"
+            ],
+            "time": "2015-06-14 21:17:01"
+        },
+        {
+            "name": "mikey179/vfsStream",
+            "version": "v1.6.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/mikey179/vfsStream.git",
+                "reference": "73bcb605b741a7d5044b47592338c633788b0eb7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/mikey179/vfsStream/zipball/73bcb605b741a7d5044b47592338c633788b0eb7",
+                "reference": "73bcb605b741a7d5044b47592338c633788b0eb7",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.5"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.6.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "org\\bovigo\\vfs\\": "src/main/php"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Frank Kleine",
+                    "homepage": "http://frankkleine.de/",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Virtual file system to mock the real file system in unit tests.",
+            "homepage": "http://vfs.bovigo.org/",
+            "time": "2015-10-06 16:59:57"
+        },
+        {
+            "name": "phpspec/prophecy",
+            "version": "v1.5.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpspec/prophecy.git",
+                "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4745ded9307786b730d7a60df5cb5a6c43cf95f7",
+                "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7",
+                "shasum": ""
+            },
+            "require": {
+                "doctrine/instantiator": "^1.0.2",
+                "phpdocumentor/reflection-docblock": "~2.0",
+                "sebastian/comparator": "~1.1"
+            },
+            "require-dev": {
+                "phpspec/phpspec": "~2.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.4.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Prophecy\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Konstantin Kudryashov",
+                    "email": "ever.zet@gmail.com",
+                    "homepage": "http://everzet.com"
+                },
+                {
+                    "name": "Marcello Duarte",
+                    "email": "marcello.duarte@gmail.com"
+                }
+            ],
+            "description": "Highly opinionated mocking framework for PHP 5.3+",
+            "homepage": "https://github.com/phpspec/prophecy",
+            "keywords": [
+                "Double",
+                "Dummy",
+                "fake",
+                "mock",
+                "spy",
+                "stub"
+            ],
+            "time": "2015-08-13 10:07:40"
+        },
+        {
+            "name": "phpunit/php-code-coverage",
+            "version": "2.2.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+                "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979",
+                "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "phpunit/php-file-iterator": "~1.3",
+                "phpunit/php-text-template": "~1.2",
+                "phpunit/php-token-stream": "~1.3",
+                "sebastian/environment": "^1.3.2",
+                "sebastian/version": "~1.0"
+            },
+            "require-dev": {
+                "ext-xdebug": ">=2.1.4",
+                "phpunit/phpunit": "~4"
+            },
+            "suggest": {
+                "ext-dom": "*",
+                "ext-xdebug": ">=2.2.1",
+                "ext-xmlwriter": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.2.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sb@sebastian-bergmann.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+            "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+            "keywords": [
+                "coverage",
+                "testing",
+                "xunit"
+            ],
+            "time": "2015-10-06 15:47:00"
+        },
+        {
+            "name": "phpunit/php-file-iterator",
+            "version": "1.4.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+                "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
+                "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.4.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sb@sebastian-bergmann.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+            "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+            "keywords": [
+                "filesystem",
+                "iterator"
+            ],
+            "time": "2015-06-21 13:08:43"
+        },
+        {
+            "name": "phpunit/php-text-template",
+            "version": "1.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-text-template.git",
+                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Simple template engine.",
+            "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+            "keywords": [
+                "template"
+            ],
+            "time": "2015-06-21 13:50:34"
+        },
+        {
+            "name": "phpunit/php-timer",
+            "version": "1.0.7",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-timer.git",
+                "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b",
+                "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sb@sebastian-bergmann.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Utility class for timing",
+            "homepage": "https://github.com/sebastianbergmann/php-timer/",
+            "keywords": [
+                "timer"
+            ],
+            "time": "2015-06-21 08:01:12"
+        },
+        {
+            "name": "phpunit/php-token-stream",
+            "version": "1.4.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-token-stream.git",
+                "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da",
+                "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da",
+                "shasum": ""
+            },
+            "require": {
+                "ext-tokenizer": "*",
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.2"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.4-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Wrapper around PHP's tokenizer extension.",
+            "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
+            "keywords": [
+                "tokenizer"
+            ],
+            "time": "2015-09-15 10:49:45"
+        },
+        {
+            "name": "phpunit/phpunit",
+            "version": "4.8.12",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/phpunit.git",
+                "reference": "00194eb95989190a73198390ceca081ad3441a7f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/00194eb95989190a73198390ceca081ad3441a7f",
+                "reference": "00194eb95989190a73198390ceca081ad3441a7f",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-json": "*",
+                "ext-pcre": "*",
+                "ext-reflection": "*",
+                "ext-spl": "*",
+                "php": ">=5.3.3",
+                "phpspec/prophecy": "^1.3.1",
+                "phpunit/php-code-coverage": "~2.1",
+                "phpunit/php-file-iterator": "~1.4",
+                "phpunit/php-text-template": "~1.2",
+                "phpunit/php-timer": ">=1.0.6",
+                "phpunit/phpunit-mock-objects": "~2.3",
+                "sebastian/comparator": "~1.1",
+                "sebastian/diff": "~1.2",
+                "sebastian/environment": "~1.3",
+                "sebastian/exporter": "~1.2",
+                "sebastian/global-state": "~1.0",
+                "sebastian/version": "~1.0",
+                "symfony/yaml": "~2.1|~3.0"
+            },
+            "suggest": {
+                "phpunit/php-invoker": "~1.1"
+            },
+            "bin": [
+                "phpunit"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.8.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "The PHP Unit Testing framework.",
+            "homepage": "https://phpunit.de/",
+            "keywords": [
+                "phpunit",
+                "testing",
+                "xunit"
+            ],
+            "time": "2015-10-12 03:36:47"
+        },
+        {
+            "name": "phpunit/phpunit-mock-objects",
+            "version": "2.3.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
+                "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983",
+                "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983",
+                "shasum": ""
+            },
+            "require": {
+                "doctrine/instantiator": "^1.0.2",
+                "php": ">=5.3.3",
+                "phpunit/php-text-template": "~1.2",
+                "sebastian/exporter": "~1.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "suggest": {
+                "ext-soap": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.3.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sb@sebastian-bergmann.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Mock Object library for PHPUnit",
+            "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
+            "keywords": [
+                "mock",
+                "xunit"
+            ],
+            "time": "2015-10-02 06:51:40"
+        },
+        {
+            "name": "sebastian/comparator",
+            "version": "1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/comparator.git",
+                "reference": "937efb279bd37a375bcadf584dec0726f84dbf22"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22",
+                "reference": "937efb279bd37a375bcadf584dec0726f84dbf22",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "sebastian/diff": "~1.2",
+                "sebastian/exporter": "~1.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.2.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@2bepublished.at"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides the functionality to compare PHP values for equality",
+            "homepage": "http://www.github.com/sebastianbergmann/comparator",
+            "keywords": [
+                "comparator",
+                "compare",
+                "equality"
+            ],
+            "time": "2015-07-26 15:48:44"
+        },
+        {
+            "name": "sebastian/diff",
+            "version": "1.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/diff.git",
+                "reference": "863df9687835c62aa423a22412d26fa2ebde3fd3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/863df9687835c62aa423a22412d26fa2ebde3fd3",
+                "reference": "863df9687835c62aa423a22412d26fa2ebde3fd3",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.2"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.3-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Kore Nordmann",
+                    "email": "mail@kore-nordmann.de"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Diff implementation",
+            "homepage": "http://www.github.com/sebastianbergmann/diff",
+            "keywords": [
+                "diff"
+            ],
+            "time": "2015-02-22 15:13:53"
+        },
+        {
+            "name": "sebastian/environment",
+            "version": "1.3.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/environment.git",
+                "reference": "6324c907ce7a52478eeeaede764f48733ef5ae44"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6324c907ce7a52478eeeaede764f48733ef5ae44",
+                "reference": "6324c907ce7a52478eeeaede764f48733ef5ae44",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.3.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides functionality to handle HHVM/PHP environments",
+            "homepage": "http://www.github.com/sebastianbergmann/environment",
+            "keywords": [
+                "Xdebug",
+                "environment",
+                "hhvm"
+            ],
+            "time": "2015-08-03 06:14:51"
+        },
+        {
+            "name": "sebastian/exporter",
+            "version": "1.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/exporter.git",
+                "reference": "7ae5513327cb536431847bcc0c10edba2701064e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e",
+                "reference": "7ae5513327cb536431847bcc0c10edba2701064e",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "sebastian/recursion-context": "~1.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.2.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@2bepublished.at"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Adam Harvey",
+                    "email": "aharvey@php.net"
+                }
+            ],
+            "description": "Provides the functionality to export PHP variables for visualization",
+            "homepage": "http://www.github.com/sebastianbergmann/exporter",
+            "keywords": [
+                "export",
+                "exporter"
+            ],
+            "time": "2015-06-21 07:55:53"
+        },
+        {
+            "name": "sebastian/global-state",
+            "version": "1.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/global-state.git",
+                "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
+                "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.2"
+            },
+            "suggest": {
+                "ext-uopz": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Snapshotting of global state",
+            "homepage": "http://www.github.com/sebastianbergmann/global-state",
+            "keywords": [
+                "global state"
+            ],
+            "time": "2015-10-12 03:26:01"
+        },
+        {
+            "name": "sebastian/recursion-context",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/recursion-context.git",
+                "reference": "994d4a811bafe801fb06dccbee797863ba2792ba"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/994d4a811bafe801fb06dccbee797863ba2792ba",
+                "reference": "994d4a811bafe801fb06dccbee797863ba2792ba",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Adam Harvey",
+                    "email": "aharvey@php.net"
+                }
+            ],
+            "description": "Provides functionality to recursively process PHP variables",
+            "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
+            "time": "2015-06-21 08:04:50"
+        },
+        {
+            "name": "sebastian/version",
+            "version": "1.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/version.git",
+                "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
+                "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
+                "shasum": ""
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+            "homepage": "https://github.com/sebastianbergmann/version",
+            "time": "2015-06-21 13:59:46"
+        },
+        {
+            "name": "symfony/yaml",
+            "version": "v2.7.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/yaml.git",
+                "reference": "31cb2ad0155c95b88ee55fe12bc7ff92232c1770"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/yaml/zipball/31cb2ad0155c95b88ee55fe12bc7ff92232c1770",
+                "reference": "31cb2ad0155c95b88ee55fe12bc7ff92232c1770",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.9"
+            },
+            "require-dev": {
+                "symfony/phpunit-bridge": "~2.7"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.7-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Yaml\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Yaml Component",
+            "homepage": "https://symfony.com",
+            "time": "2015-09-14 14:14:09"
+        }
+    ],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": {
+        "grom358/pharborist": 20,
+        "cebe/markdown": 20
+    },
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": [],
+    "platform-dev": []
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.entity_operations.yml b/web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.entity_operations.yml
new file mode 100644 (file)
index 0000000..93e55d4
--- /dev/null
@@ -0,0 +1,19 @@
+# This file controls the behavior of EntityOperationDeriver, which derives the
+# EntityOperation function rewriter, which converts calls to things like user_save()
+# to $user->save().
+definitions:
+  entity:
+    - save
+    - delete
+    - label
+  node:
+    - save
+    - delete
+  user:
+    - delete
+  comment:
+    - save
+    - delete
+  taxonomy_term:
+    - save
+    - delete
diff --git a/web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.functions.yml b/web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.functions.yml
new file mode 100644 (file)
index 0000000..a7243c5
--- /dev/null
@@ -0,0 +1,1000 @@
+# This file controls the behavior of the FunctionCall analyzer and the Disable
+# function call modifier.
+#
+# Each item in this file is either information about a single function, or a
+# group of functions. Groups will have the 'functions' key, listing the affected
+# functions.
+#
+# If 'disable' is true, the Disable plugin will unconditionally comment out
+# calls to the function, and leave a FIXME notice above it.
+
+definitions:
+  assets:
+    message: '`@function` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2169605'
+        title: '`drupal_add_css()`, `drupal_add_js()` and `drupal_add_library()` removed in favor of `#attached`'
+      -
+        url: 'https://www.drupal.org/node/2408597'
+        title: '`AssetResolverInterface` and `AttachedAssetsInterface` replace internal Asset API functions'
+    tags:
+      category:
+        - render
+        - ui
+    functions:
+      - drupal_add_css
+      - _drupal_add_css
+      - drupal_add_js
+      - _drupal_add_js
+      - drupal_add_library
+      - _drupal_add_library
+      - drupal_get_css
+      - drupal_get_js
+      - drupal_sort_css_js
+    fixme: |
+      The Assets API has totally changed. CSS, JavaScript, and libraries are now
+      attached directly to render arrays using the #attached property.
+    disable: true
+
+  cache:
+    message: 'The caching system has been rewritten.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/1884796'
+        title: 'Drupal 8 Cache API'
+      -
+        url: 'https://www.drupal.org/node/1272696'
+        title: 'New cache API'
+    tags:
+      category:
+        - cache
+    functions:
+      - cache_clear_all
+      - cache_get
+      - cache_get_multiple
+      - _cache_get_object
+      - cache_is_empty
+      - cache_set
+
+  conf_path:
+    message: '`@function` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2275139'
+        title: '`@function` moved into `DrupalKernel`'
+    tags:
+      category:
+        - system
+
+  confirm_form:
+    message: '`@function` is now `\Drupal\Core\Form\ConfirmFormBase`'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/1945416'
+        title: '`@function` removed'
+    tags:
+      category:
+        - form
+
+  crypt:
+    message: '`@function` has moved into the Crypt component.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/1984806'
+        title: '`@function` moved into `\Drupal\Component\Utility\Crypt`'
+    tags:
+      category:
+        - utility
+    functions:
+      - drupal_hash_base64
+      - drupal_hmac_base64
+      - drupal_random_bytes
+
+  ctools_get_plugins:
+    message: 'The CTools plugin system has moved into core.'
+    documentation:
+      -
+        url: 'https://api.drupal.org/api/drupal/core%21core.api.php/group/plugin_api/8'
+        title: 'Drupal 8 Plugin API'
+    tags:
+      category:
+        - ctools
+
+  ctools_include:
+    message: 'Most CTools APIs have moved into core.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2164623'
+        title: 'Many common dependencies added to core'
+    tags:
+      category:
+        - ctools
+    disable: true
+    fixme: 'Most CTools APIs have been moved into core.'
+
+  ctools_export:
+    message: 'The CTools Export API has moved into core.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/developing/api/entity'
+        title: 'Drupal 8 Entity API'
+    tags:
+      category:
+        - ctools
+        - entity
+    functions:
+      - ctools_export_crud_new
+      - ctools_export_crud_load
+      - ctools_export_crud_load_multiple
+      - ctools_export_crud_load_all
+      - ctools_export_crud_save
+      - ctools_export_crud_delete
+      - ctools_export_crud_export
+      - ctools_export_crud_import
+      - ctools_export_crud_set_status
+      - ctools_export_crud_enable
+      - ctools_export_crud_disable
+      - ctools_export_load_object
+      - ctools_export_load_object_reset
+      - ctools_get_default_object
+      - ctools_export_unpack_object
+      - ctools_var_export
+      - ctools_export_object
+      - ctools_export_get_schema
+      - ctools_export_get_schemas
+      - ctools_export_get_schemas_by_module
+      - ctools_export_set_status
+      - ctools_export_set_object_status
+      - ctools_export_form
+      - ctools_export_new_object
+      - ctools_export_to_hook_code
+      - ctools_export_default_to_hook_code
+      - ctools_export_default_list
+    disable: true
+    fixme: 'The CTools Export API has been merged with the core entity API.'
+
+  ctools_object_cache:
+    message: 'The CTools object caching system has moved into core.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/1805940'
+        title: 'TempStore API added to core'
+      -
+        url: 'https://www.drupal.org/node/2164623'
+        title: 'Modules added to Drupal 8 core'
+    tags:
+      category:
+        - ctools
+        - cache
+    functions:
+      - ctools_object_cache_get
+      - ctools_object_cache_set
+
+  current_path:
+    message: '`@function` has been replaced by the <current> route.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2382211'
+        title: '`current_path()` replaced by the <current> route'
+    tags:
+      category:
+        - menu
+        - system
+
+  drupal_add_tabledrag:
+    message: '`@function` is now the `#tabledrag` property of a render array.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2160571'
+        title: '`@function` replaced by render array attachment'
+    tags:
+      category:
+        - render
+        - ui
+    fixme: |
+      TableDrag is now attached with the #tabledrag property of certain render
+      arrays. @function is now internal and should never be called directly.
+    disable: true
+
+  drupal_array:
+    message: '`@function` has been moved into the NestedArray utility class.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/1870678'
+        title: '`drupal_array_*` functions replaced by static methods of `NestedArray` utility'
+    tags:
+      category:
+        - utility
+    functions:
+      - drupal_array_merge_deep
+      - drupal_array_merge_deep_array
+      - drupal_array_get_nested_value
+      - drupal_array_set_nested_value
+      - drupal_array_unset_nested_value
+      - drupal_array_nested_key_exists
+
+  drupal_cron_run:
+    message: '`@function` was moved into the `cron` service.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2181921'
+        title: '`@function` replaced by cron service'
+    tags:
+      category:
+        - system
+
+  drupal_exit:
+    message: '`@function` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2017339'
+        title: '`@function` removed'
+    tags:
+      category:
+        - system
+
+  drupal_get_query_array:
+    message: '`@function` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2079005'
+        title: '`@function` replaced by native `parse_str()` function'
+    tags:
+      category:
+        - utility
+    fixme: |
+      @function has been removed in favor of PHP's native parse_str(). You should
+      use that function instead and pass a destination array by reference.
+      For more information, see:
+      https://www.drupal.org/node/2079005
+      http://www.php.net/parse_str
+    disable: true
+
+  drupal_get_title:
+    message: '`@function` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2067859'
+        title: '`@function` removed'
+    tags:
+      category:
+        - system
+        - ui
+
+  drupal_goto:
+    message: '`@function` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2023537'
+        title: '`@function` removed'
+    tags:
+      category:
+        - system
+
+  drupal_http_request:
+    message: '`@function` has been replaced by Guzzle.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/1862446'
+        title: 'Guzzle HTTP client in Drupal core'
+      -
+        url: 'http://docs.guzzlephp.org/en/latest'
+        title: 'Guzzle documentation'
+    tags:
+      category:
+        - system
+    fixme: |
+      @function has been replaced by the Guzzle HTTP client, which is bundled
+      with Drupal core.
+    disable: true
+
+  drupal_is_cli:
+    message: '`@function` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2295037'
+        title: '`@function` removed'
+    tags:
+      category:
+        - system
+
+  drupal_map_assoc:
+    message: '`@function` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2207453'
+        title: '`@function` removed'
+    tags:
+      category:
+        - form
+        - utility
+
+  drupal_render:
+    message: '`@function` has been removed.'
+    tags:
+      category:
+        - render
+
+  drupal_set_title:
+    message: '`@function` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2067859'
+        title: '`@function` removed'
+    tags:
+      category:
+        - system
+        - ui
+    fixme: |
+      @function has been removed. There are now a few ways to set the title
+      dynamically, depending on the situation.
+    disable: true
+
+  drupal_site_offline:
+    message: '`@function` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/1628046'
+        title: '`@function` removed in favor of exceptions'
+    tags:
+      category:
+        - system
+    fixme: |
+      @function has been removed. If your code needs to fail, it should throw
+      a meaningful exception instead.
+    disable: true
+
+  drupal_valid_path:
+    message: '`@function` has been moved into the PathValidator service.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2302541'
+        title: '`@function` moved into PathValidator service'
+    tags:
+      category:
+        - system
+
+  drupal_var_export:
+    message: '`@function` is now part of the Variable utility.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2368411'
+        title: '`@function` moved into Variable utility'
+    tags:
+      category:
+        - system
+
+  drupal_write_record:
+    message: '`@function` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2340291'
+        title: '`@function` removed'
+    tags:
+      category:
+        - db
+        - system
+
+  element:
+    message: '`@function` is now a method of the `Element` class.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2173683'
+        title: '`element_*` functions moved into `Element` class'
+    tags:
+      category:
+        - render
+        - utility
+    functions:
+      - element_child
+      - element_children
+      - element_get_visible_children
+      - element_properties
+      - element_property
+      - element_set_attributes
+
+  entity_create:
+    message: '`@function` is now a method of `EntityInterface`.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2266845'
+        title: '`@function` replaced by `EntityInterface::create()`'
+    tags:
+      category:
+        - entity
+
+  entity_extract_ids:
+    message: '`@function` replaced by methods of `EntityInterface`'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/1724986'
+        title: '`@function` removed'
+    tags:
+      category:
+        - entity
+
+  entity_get_info:
+    message: '`@function` is now a method of the EntityManager service.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/1929006'
+        title: '`@function` is deprecated'
+    tags:
+      category:
+        - entity
+
+  entity_crud:
+    message: '`@function` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2266845'
+        title: 'Entities are now classed objects implementing `EntityInterface`'
+    tags:
+      category:
+        - entity
+    functions:
+      - entity_load
+      - entity_load_multiple
+      - entity_save
+      - entity_delete
+      - entity_label
+      - node_load
+      - node_load_multiple
+      - node_save
+      - node_delete
+      - user_load
+      - user_load_multiple
+      - user_save
+      - user_delete
+      - comment_load
+      - comment_load_multiple
+      - comment_save
+      - comment_delete
+      - taxonomy_term_load
+      - taxonomy_term_load_multiple
+      - taxonomy_term_save
+      - taxonomy_term_delete
+
+  field:
+    message: 'The Field API CRUD functions have been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2012896'
+        title: 'Fields and field instances are now entities'
+    tags:
+      category:
+        - field
+    functions:
+      - field_create_field
+      - field_create_instance
+      - field_delete_field
+      - field_delete_instance
+      - field_info_fields
+    fixme: |
+      Fields and field instances are now exportable configuration entities, and
+      the Field Info API has been removed.
+    disable: true
+
+  field_formatter:
+    message: '`@function` has been replaced with a field formatter plugin manager.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/1805846'
+        title: 'Field formatters are now plugins'
+    tags:
+      category:
+        - field
+    functions:
+      - field_info_formatter_types
+      - field_info_formatter_settings
+
+  field_type:
+    message: '`@function` has been replaced with a field type plugin manager.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2064123'
+        title: 'Field types are now plugins'
+    tags:
+      category:
+        - field
+    functions:
+      - field_info_field_types
+      - field_info_field_settings
+
+  field_update_field:
+    message: '`@function` is now `FieldStorageConfig::save()`.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2012896'
+        title: 'Field CRUD API replaced by Entity API'
+    tags:
+      category:
+        - field
+
+  field_update_instance:
+    message: '`@function` is now `FieldConfig::save()`.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2012896'
+        title: 'Field CRUD API replaced by Entity API'
+    tags:
+      category:
+        - field
+
+  field_view_field:
+    message: '`@function` is now a method of `FieldItemInterface`.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2208327'
+        title: '`@function` moved into `FieldItemInterface`'
+    tags:
+      category:
+        - field
+
+  field_view_value:
+    message: '`@function` is now a method of `FieldItemListInterface`.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2208327'
+        title: '`@function` moved into `FieldItemListInterface`'
+    tags:
+      category:
+        - field
+
+  field_widget:
+    message: '`@function` is now a method of `WidgetInterface`.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/1796000'
+        title: 'Field widgets are now plugins'
+    tags:
+      category:
+        - field
+    functions:
+      - field_default_extract_form_values
+      - field_default_form
+      - field_default_form_errors
+      - field_default_submit
+      - field_info_widget_types
+
+  file_system:
+    message: '`@function` is now a method of the `file_system` service.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2418133'
+        title: 'File system functions moved into `file_system` service'
+    tags:
+      category:
+        - system
+    functions:
+      - drupal_basename
+      - drupal_chmod
+      - drupal_dirname
+      - drupal_mkdir
+      - drupal_move_uploaded_file
+      - drupal_realpath
+      - drupal_rmdir
+      - drupal_tempnam
+      - drupal_unlink
+      - file_stream_wrapper_valid_scheme
+      - file_uri_scheme
+
+  form:
+    message: '`@function` has moved into the FormBuilder service.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2121003'
+        title: 'Form generation functions moved into FormBuilder service'
+    tags:
+      category:
+        - form
+    functions:
+      - drupal_build_form
+      - drupal_get_form
+      - drupal_form_submit
+      - drupal_prepare_form
+      - drupal_process_form
+      - drupal_rebuild_form
+      - drupal_redirect_form
+      - drupal_retrieve_form
+      - drupal_validate_form
+      - form_execute_handlers
+      - form_get_cache
+      - form_load_include
+      - form_set_cache
+
+  form_state:
+    message: '`@function` is now a method of `FormStateInterface`.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2121003'
+        title: 'Form functions moved into `FormStateInterface`'
+    tags:
+      category:
+        - form
+    functions:
+      - form_clear_error
+      - form_error
+      - form_get_errors
+      - form_set_error
+      - form_set_value
+      - form_state_defaults
+      - form_state_values_clean
+
+  format_interval:
+    message: '`@function` was moved into the `date.formatter` service.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2173787'
+        title: '`@function` moved to date formatter service'
+    tags:
+      category:
+        - render
+        - ui
+        - utility
+
+  format_plural:
+    message: '`@function` was moved into the translation service.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2173787'
+        title: '`@function` moved to translation service'
+    tags:
+      category:
+        - render
+        - ui
+        - utility
+
+  html:
+    message: '`@function` has been moved into the HTML component.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2388737'
+        title: 'HTML functions moved to a component'
+    functions:
+      - drupal_clean_css_identifer
+      - drupal_html_class
+      - drupal_html_id
+    tags:
+      category:
+        - render
+
+  ip_address:
+    message: '`@function` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/1969794'
+        title: '`@function` removed'
+    tags:
+      category:
+        - system
+        - utility
+
+  json:
+    message: '`@function` has moved into the Serialization component.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2219113'
+        title: '`@function` moved into Serialization component'
+    functions:
+      - drupal_json_decode
+      - drupal_json_encode
+    tags:
+      category:
+        - system
+        - utility
+
+  l:
+    message: '`@function` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2346779'
+        title: '`@function` replaced by URL generation API'
+    tags:
+      category:
+        - system
+
+  menu_active_trail:
+    message: '`@function` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2240003'
+        title: 'Active trail functions replaced by `menu.active_trail` service.'
+    tags:
+      category:
+        - menu
+    functions:
+      - menu_get_active_trail
+      - menu_link_get_preferred
+      - menu_set_active_item
+      - menu_set_active_trail
+    fixme: |
+      The active trail system has been removed in Drupal 8 because the routing and
+      linking systems have been completely rewritten. You will need to rewrite this
+      code to use the menu.active_trail service, or override the service if you need
+      to alter the active trail.
+    disable: true
+
+  menu_item:
+    message: '`@function` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2203305'
+        title: '`@function` removed'
+    tags:
+      category:
+        - menu
+    functions:
+      - menu_get_item
+      - menu_set_item
+    fixme: |
+      @function has been removed. To retrieve route information, use the
+      RouteMatch object, which you can retrieve by calling \Drupal::routeMatch().
+    disable: true
+
+  menu_tree:
+    message: '`@function` has been replaced by `menu.link_tree` service.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2226481'
+        title: 'Menu tree building is now a service'
+      -
+        url: 'https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Menu%21MenuLinkTree.php/class/MenuLinkTree/8'
+        title: '`MenuLinkTree` documentation'
+    tags:
+      category:
+        - menu
+    functions:
+      - menu_build_tree
+      - menu_parent_options
+      - menu_tree
+      - menu_tree_all_data
+      - menu_tree_check_access
+      - menu_tree_collect_node_links
+      - menu_tree_data
+      - menu_tree_page_data
+      - menu_tree_get_path
+      - menu_tree_set_path
+    fixme: |
+      @function is gone in Drupal 8. To generate or work with menu trees, you'll need to
+      use the menu.link_tree service.
+    disable: true
+
+  module_invoke:
+    message: '`@function` is now a method of the `module_handler` service.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/1894902'
+        title: '`@function` replaced by `module_handler` service'
+    tags:
+      category:
+        - system
+
+  module_invoke_all:
+    message: '`@function` is now a method of the `module_handler` service.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/1894902'
+        title: '`@function` replaced by `module_handler` service'
+    tags:
+      category:
+        - system
+
+  _node_revision_access:
+    message: '`@function` was moved into an access-checking service.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2328179'
+        title: '`@function` replaced'
+    tags:
+      category:
+        - entity
+        - node
+
+  stream_wrappers:
+    message: '`@function` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2393323'
+        title: '`@function` moved into stream wrapper manager service.'
+    tags:
+      category:
+        - system
+    functions:
+      - file_get_stream_wrappers
+      - file_stream_wrapper_get_class
+      - file_stream_wrapper_get_instance_by_uri
+      - file_stream_wrapper_get_instance_by_scheme
+
+  t_meta:
+    message: '`@function` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2021435'
+        title: '`@function` replaced by `t()`'
+    tags:
+      category:
+        - system
+    functions:
+      - get_t
+      - st
+
+  taxonomy:
+    message: '`@function` is now a method of the taxonomy term storage controller.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2328205'
+        title: 'Certain Taxonomy API functions have moved into `TermStorage` class'
+    tags:
+      category:
+        - entity
+        - taxonomy
+    functions:
+      - taxonomy_get_tree
+      - taxonomy_term_load_children
+      - taxonomy_term_load_parents
+      - taxonomy_term_load_parents_all
+
+  theme:
+    message: '`@function` has been renamed to `_theme()`, and should never be called directly.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2195739'
+        title: '`@function` renamed to `_theme()` and should not be called directly'
+    tags:
+      category:
+        - theme
+    fixme: |
+      @function has been renamed to _theme() and should NEVER be called directly.
+      Calling _theme() directly can alter the expected output and potentially
+      introduce security issues (see https://www.drupal.org/node/2195739). You
+      should use renderable arrays instead.
+    disable: true
+
+  theme_get_registry:
+    message: 'The theme registry is now a service.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2137545'
+        title: '`@function` is now two different methods of the `theme.registry` service'
+    tags:
+      category:
+        - theme
+
+  theme_registry:
+    message: 'Several low-level theme registry functions have moved.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2137545'
+        title: 'Theme registry moved to `theme.registry` service'
+    tags:
+      category:
+        - theme
+    functions:
+      - _theme_load_registry
+      - _theme_save_registry
+      - _theme_process_registry
+      - _theme_build_registry
+      - _theme_load_offline_registry
+    fixme: |
+      Several low-level theme system functions have been moved into the
+      theme.registry service.
+    disable: true
+
+  token:
+    message: 'The core token API is now a service.'
+    documentation:
+      -
+        url: https://www.drupal.org/node/1973488
+        title: 'Token API is now a service'
+    tags:
+      category:
+        - misc
+    functions:
+      - token_find_with_prefix
+      - token_generate
+      - token_info
+      - token_replace
+      - token_scan
+
+  unicode:
+    message: '`@function` has been moved into the Unicode utility class.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/1992584'
+        title: '`@function` moved into Unicode component'
+    tags:
+      category:
+        - utility
+    functions:
+      - decode_entities
+      - drupal_convert_to_utf8
+      - drupal_strlen
+      - drupal_strtolower
+      - drupal_strtoupper
+      - drupal_substr
+      - drupal_truncate_bytes
+      - drupal_ucfirst
+      - drupal_validate_utf8
+      - mime_header_encode
+      - mime_header_decode
+      - truncate_utf8
+      - unicode_check
+
+  url:
+    message: '`@function` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2346779'
+        title: '`@function` replaced by URL generation API'
+    tags:
+      category:
+        - system
+
+  url_utility:
+    message: '`@function` has been replaced by the `UrlHelper` utility.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2079005'
+        title: '`@function` is now a method of `\Drupal\Component\Utility\UrlHelper`'
+    tags:
+      category:
+        - utility
+    functions:
+      - drupal_encode_path
+      - drupal_get_query_parameters
+      - drupal_parse_url
+      - filter_xss_bad_protocol
+      - url_is_external
+
+  user_access:
+    message: '`@function` is now `AccountInterface::hasPermission()`.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2049309'
+        title: '`@function` got converted to a method on the user/account interface'
+    tags:
+      category:
+        - system
+        - user
+
+  variable_del:
+    message: '`@function` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2183531'
+        title: 'The Variable API has been removed'
+    tags:
+      category:
+        - config
+
+  variable_get:
+    message: '`@function` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2183531'
+        title: 'The Variable API has been removed'
+    tags:
+      category:
+        - config
+
+  variable_set:
+    message: '`@function` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2183531'
+        title: 'The Variable API has been removed'
+    tags:
+      category:
+        - config
+
+  watchdog:
+    message: '`@function` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2270941'
+        title: '`@function` is deprecated'
+    tags:
+      category:
+        - system
diff --git a/web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.grep.yml b/web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.grep.yml
new file mode 100644 (file)
index 0000000..9de89bf
--- /dev/null
@@ -0,0 +1,166 @@
+# This file controls the behavior of the Grep converter
+# (Plugin/DMU/Converter/Grep.php). Grep is basically a dumb search
+# and replace.
+
+definitions:
+  # Certain common global variables were turned into service methods in
+  # Drupal 8. Any globals listed here will be expanded into three forms:
+  # global $var, $GLOBALS['var'], and $GlOBALS["var"], and each form will
+  # be replaced with the replacement string. For example, global $user
+  # will become \Drupal::currentUser().
+  globals:
+    language: '\Drupal::languageManager()->getCurrentLanguage()'
+    theme: '\Drupal::theme()->getActiveTheme()->getName()'
+    theme_key: '\Drupal::theme()->getActiveTheme()->getName()'
+    user: '\Drupal::currentUser()'
+
+  # Many Drupal 7 API functions were moved into service objects, but otherwise
+  # left untouched. Those functions are listed here. Each function name will
+  # be expanded into function(), then replaced with its replacement. You should
+  # NOT include the parentheses in the replacement string. For example,
+  # drupal_alter('baz', $my_baz) will be replaced with:
+  # \Drupal::moduleHandler()->alter('baz', $my_baz)
+  function_calls:
+    check_plain: '\Drupal\Component\Utility\Html::escape'
+    conf_path: '\Drupal\Core\DrupalKernel::findSitePath'
+    current_path: '\Drupal\Core\Url::fromRoute("<current>")->toString'
+    decode_entities: '\Drupal\Component\Utility\Html::decodeEntities'
+    drupal_alter: '\Drupal::moduleHandler()->alter'
+    drupal_array_merge_deep: '\Drupal\Component\Utility\NestedArray::mergeDeep'
+    drupal_array_merge_deep_array: '\Drupal\Component\Utility\NestedArray::mergeDeepArray'
+    drupal_array_get_nested_value: '\Drupal\Component\Utility\NestedArray::getValue'
+    drupal_array_set_nested_value: '\Drupal\Component\Utility\NestedArray::setValue'
+    drupal_array_unset_nested_value: '\Drupal\Component\Utility\NestedArray::unsetValue'
+    drupal_array_nested_key_exists: '\Drupal\Component\Utility\NestedArray::keyExists'
+    drupal_basename: '\Drupal::service("file_system")->basename'
+    drupal_build_form: '\Drupal::formBuilder()->buildForm'
+    drupal_chmod: '\Drupal::service("file_system")->chmod'
+    drupal_clean_css_identifier: '\Drupal\Component\Utility\Html::cleanCssIdentifier'
+    drupal_clear_css_cache: '\Drupal::service("asset.css.collection_optimizer")->deleteAll'
+    drupal_clear_js_cache: '\Drupal::service("asset.js.collection.optimizer")->deleteAll'
+    drupal_convert_to_utf8: '\Drupal\Component\Utility\Unicode::convertToUtf8'
+    drupal_cron_run: '\Drupal::service("cron")->run'
+    drupal_dirname: '\Drupal::service("file_system")->dirname'
+    drupal_encode_path: '\Drupal\Component\Utility\UrlHelper::encodePath'
+    drupal_form_submit: '\Drupal::formBuilder()->submitForm'
+    drupal_get_form: '\Drupal::formBuilder()->getForm'
+    drupal_get_query_parameters: '\Drupal\Component\Utility\UrlHelper::filterQueryParameters'
+    drupal_hash_base64: '\Drupal\Component\Utility\Crypt::hashBase64'
+    drupal_hmac_base64: '\Drupal\Component\Utility\Crypt::hmacBase64'
+    drupal_html_class: '\Drupal\Component\Utility\Html::getClass'
+    drupal_html_id: '\Drupal\Component\Utility\Html::getId'
+    drupal_json_encode: '\Drupal\Component\Serialization\Json::encode'
+    drupal_json_decode: '\Drupal\Component\Serialization\Json::decode'
+    drupal_mkdir: '\Drupal::service("file_system")->mkdir'
+    drupal_move_uploaded_file: '\Drupal::service("file_system")->moveUploadedFile'
+    drupal_parse_dependency: '\Drupal\Core\Extension\ModuleHandler::parseDependency'
+    drupal_parse_url: '\Drupal\Component\Utility\UrlHelper::parse'
+    drupal_prepare_form: '\Drupal::formBuilder()->prepareForm'
+    drupal_process_form: '\Drupal::formBuilder()->processForm'
+    drupal_random_bytes: '\Drupal\Component\Utility\Crypt::randomBytes'
+    drupal_realpath: '\Drupal::service("file_system")->realpath'
+    drupal_rebuild_form: '\Drupal::formBuilder()->rebuildForm'
+    drupal_redirect_form: '\Drupal::formBuilder()->redirectForm'
+    drupal_render: '\Drupal::service("renderer")->render'
+    drupal_retrieve_form: '\Drupal::formBuilder()->retrieveForm'
+    drupal_rmdir: '\Drupal::service("file_system")->rmdir'
+    drupal_strlen: '\Drupal\Component\Utility\Unicode::strlen'
+    drupal_strtolower: '\Drupal\Component\Utility\Unicode::strtolower'
+    drupal_strtoupper: '\Drupal\Component\Utility\Unicode::strtoupper'
+    drupal_substr: '\Drupal\Component\Utility\Unicode::substr'
+    drupal_tempnam: '\Drupal::service("file_system")->tempnam'
+    drupal_truncate_bytes: '\Drupal\Component\Utility\Unicode::truncateBytes'
+    drupal_ucfirst: '\Drupal\Component\Utility\Unicode::ucfirst'
+    drupal_unlink: '\Drupal::service("file_system")->unlink'
+    drupal_valid_path: '\Drupal::service("path.validator")->isValid'
+    drupal_validate_form: '\Drupal::service("form_validator")->validateForm'
+    drupal_validate_utf8: '\Drupal\Component\Utility\Unicode::validateUtf8'
+    drupal_var_export: '\Drupal\Component\Utility\Variable::export'
+    element_child: '\Drupal\Core\Render\Element::child'
+    element_children: '\Drupal\Core\Render\Element::children'
+    element_get_visible_children: '\Drupal\Core\Render\Element::getVisibleChildren'
+    element_properties: '\Drupal\Core\Render\Element::properties'
+    element_property: '\Drupal\Core\Render\Element::property'
+    element_set_attributes: '\Drupal\Core\Render\Element::setAttributes'
+    entity_info_cache_clear: '\Drupal::entityManager()->clearCachedDefinitions'
+    field_attach_create_bundle: '\Drupal::entityManager()->onBundleCreate'
+    field_attach_delete_bundle: '\Drupal::entityManager()->onBundleDelete'
+    field_attach_rename_bundle: '\Drupal::entityManager()->onBundleRename'
+    field_cache_clear: '\Drupal::entityManager()->clearCachedFieldDefinitions'
+    field_info_cache_clear: '\Drupal::entityManager()->clearCachedBundles'
+    field_info_field_settings: '\Drupal::service("plugin.manager.field.field_type")->getDefaultSettings'
+    field_info_formatter_settings: '\Drupal::service("plugin.manager.field.formatter")->getDefaultSettings'
+    field_info_instance_settings: '\Drupal::service("plugin.manager.field.field_type")->getDefaultInstanceSettings'
+    field_info_widget_settings: '\Drupal::service("plugin.manager.field.widget")->getDefaultSettings'
+    file_get_stream_wrappers: '\Drupal::service("stream_wrapper_manager")->getWrappers'
+    file_stream_wrapper_get_class: '\Drupal::service("stream_wrapper_manager")->getClass'
+    file_stream_wrapper_get_instance_by_uri: '\Drupal::service("stream_wrapper_manager")->getViaUri'
+    file_stream_wrapper_get_instance_by_scheme: '\Drupal::service("stream_wrapper_manager")->getViaScheme'
+    file_stream_wrapper_valid_scheme: '\Drupal::service("file_system")->validScheme'
+    file_uri_scheme: '\Drupal::service("file_system")->uriScheme'
+    filter_xss: '\Drupal\Component\Utility\Xss::filter'
+    filter_xss_admin: '\Drupal\Component\Utility\Xss::filterAdmin'
+    filter_xss_bad_protocol: '\Drupal\Component\Utility\UrlHelper::filterBadProtocol'
+    form_get_cache: '\Drupal::formBuilder()->getCache'
+    form_set_cache: '\Drupal::formBuilder()->setCache'
+    format_interval: '\Drupal::service("date.formatter")->formatInterval'
+    format_plural: '\Drupal::translation()->formatPlural'
+    image_dimensions_scale: '\Drupal\Component\Image\Image::scaleDimensions'
+    ip_address: '\Drupal::request()->getClientIp'
+    lock_acquire: '\Drupal::lock()->acquire'
+    lock_release: '\Drupal::lock()->release'
+    lock_release_all: '\Drupal::lock()->releaseAll'
+    lock_wait: '\Drupal::lock()->wait'
+    menu_get_object: '\Drupal::routeMatch()->getParameter'
+    menu_tree_output: '\Drupal::menuTree()->build'
+    mime_header_decode: '\Drupal\Component\Utility\Unicode::mimeHeaderDecode'
+    mime_header_encode: '\Drupal\Component\Utility\Unicode::mimeHeaderEncode'
+    _module_build_dependencies: '\Drupal::moduleHandler()->buildModuleDependencies'
+    module_exists: '\Drupal::moduleHandler()->moduleExists'
+    module_hook: '\Drupal::moduleHandler()->implementsHook'
+    module_implements: '\Drupal::moduleHandler()->getImplementations'
+    module_implements_reset: '\Drupal::moduleHandler()->resetImplementations'
+    module_install: '\Drupal::moduleHandler()->install'
+    module_load_all_includes: '\Drupal::moduleHandler()->loadAllIncludes'
+    module_uninstall: '\Drupal::moduleHandler()->uninstall'
+    path_to_theme: '\Drupal::theme()->getActiveTheme()->getPath'
+    taxonomy_get_tree: '\Drupal::entityManager()->getStorage("taxonomy_term")->loadTree'
+    taxonomy_get_vocabularies: '\Drupal\taxonomy\Entity\Vocabulary::loadMultiple'
+    taxonomy_term_load: '\Drupal::entityManager()->getStorage("taxonomy_term")->load'
+    taxonomy_term_load_children: '\Drupal::entityManager()->getStorage("taxonomy_term")->loadChildren'
+    taxonomy_term_load_parents: '\Drupal::entityManager()->getStorage("taxonomy_term")->loadParents'
+    taxonomy_term_load_parents_all: '\Drupal::entityManager()->getStorage("taxonomy_term")->loadParentsAll'
+    token_find_with_prefix: '\Drupal::token()->findWithPrefix'
+    token_info: '\Drupal::token()->getInfo'
+    token_generate: '\Drupal::token()->generate'
+    token_replace: '\Drupal::token()->replace'
+    token_scan: '\Drupal::token()->scan'
+    truncate_utf8: '\Drupal\Component\Utility\Unicode::truncate'
+    unicode_check: '\Drupal\Component\Utility\Unicode::check'
+    url_is_external: '\Drupal\Component\Utility\UrlHelper::isExternal'
+    user_authenticate: '\Drupal::service("user.auth")->authenticate'
+    user_is_anonymous: '\Drupal::currentUser()->isAnonymous'
+    user_is_logged_in: '\Drupal::currentUser()->isAuthenticated'
+
+  # Common global constants were generally moved into classes and interfaces.
+  # Each listed constant is replaced literally (no pre-processing) with the
+  # replacement string. So for example, $node->foo[LANGUAGE_NONE] will be
+  # replaced with:
+  # $node->foo[\Drupal\Core\Language\Language::LANGCODE_NOT_SPECIFIED]
+  constants:
+    CACHE_PERMANENT: '\Drupal\Core\Cache\Cache::PERMANENT'
+    COMMENT_FORM_BELOW: '\Drupal\comment\Plugin\Field\FieldType\CommentItem::FORM_BELOW'
+    COMMENT_FORM_SEPARATE_PAGE: '\Drupal\comment\Plugin\Field\FieldType\CommentItem::FORM_SEPARATE_PAGE'
+    COMMENT_MODE_FLAT: '\Drupal\comment\CommentManagerInterface::COMMENT_MODE_FLAT'
+    DRUPAL_ANONYMOUS_RID: '\Drupal\Core\Session\AccountInterface::ANONYMOUS_ROLE'
+    DRUPAL_AUTHENTICATED_RID: '\Drupal\Core\Session\AccountInterface::AUTHENTICATED_RID'
+    DRUPAL_ROOT: '\Drupal::root()'
+    FILE_CHMOD_DIRECTORY: '\Drupal\Core\File\FileSystem::CHMOD_DIRECTORY'
+    FILE_CHMOD_FILE: '\Drupal\Core\File\FileSystem::CHMOD_FILE'
+    LANGUAGE_LTR: '\Drupal\Core\Language\Language::LANGUAGE_LTR'
+    LANGUAGE_NONE: '\Drupal\Core\Language\Language::LANGCODE_NOT_SPECIFIED'
+    LANGUAGE_RTL: '\Drupal\Core\Language\Language::LANGUAGE_RTL'
+    PREG_CLASS_UNICODE_WORD_BOUNDARY: '\Drupal\Component\Utility\Unicode::PREG_CLASS_WORD_BOUNDARY'
+    UNICODE_ERROR: '\Drupal\Component\Utility\Unicode::STATUS_ERROR'
+    UNICODE_MULTIBYTE: '\Drupal\Component\Utility\Unicode::STATUS_MULTIBYTE'
+    UNICODE_SINGLEBYTE: '\Drupal\Component\Utility\Unicode::STATUS_SINGLEBYTE'
diff --git a/web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.hooks.yml b/web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.hooks.yml
new file mode 100644 (file)
index 0000000..5be9926
--- /dev/null
@@ -0,0 +1,269 @@
+definitions:
+  blocks:
+    message: 'Blocks are now plugins in the `MODULE\Plugin\Block` namespace.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/1880620'
+        title: 'Blocks are now plugins'
+      -
+        url: 'https://api.drupal.org/api/drupal/core%21modules%21block%21block.api.php/group/block_api/8'
+        title: 'Drupal 8 Block API'
+    tags:
+      category:
+        - block
+    hook:
+      - block_info
+      - block_configure
+      - block_save
+      - block_view
+
+  boot:
+    message: '`@hook` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/1909596'
+        title: '`@hook` replaced by event subscriber'
+    tags:
+      category:
+        - system
+    delete: true
+
+  comment_status:
+    message: '`@hook` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2296867'
+        title: '`@hook` removed.'
+    tags:
+      category:
+        - entity
+    hook:
+      - comment_publish
+      - comment_unpublish
+    delete: true
+
+  drupal_goto_alter:
+    message: '`@hook` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2023537'
+        title: '`@hook` replaced by event subscriber'
+    tags:
+      category:
+        - system
+    delete: true
+
+  entity_info:
+    message: '`@hook` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/1827470'
+        title: 'Entity types are now annotated plugins'
+    tags:
+      category:
+        - entity
+    delete: true
+
+  exit:
+    message: '`@hook` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/1911186'
+        title: '`@hook` replaced by event subscriber'
+    tags:
+      category:
+        - system
+    delete: true
+
+  field_attach_bundle_crud:
+    message: '`@hook` has been renamed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/1964766'
+        title: 'Bundle CRUD API moved to Entity API'
+    tags:
+      category:
+        - field
+    hook:
+      - field_attach_create_bundle
+      - field_attach_delete_bundle
+      - field_attach_rename_bundle
+
+  field_formatter:
+    message: '`@hook` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/1805846'
+        title: 'Field formatters are now plugins'
+    tags:
+      category:
+        - field
+    hook:
+      - field_formatter_info
+      - field_formatter_prepare_view
+      - field_formatter_settings_form
+      - field_formatter_settings_summary
+      - field_formatter_view
+    delete: true
+
+  field_type:
+    message: '`@hook` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2064123'
+        title: 'Field types are now plugins'
+    tags:
+      category:
+        - field
+    hook:
+      - field_delete
+      - field_delete_revision
+      - field_info
+      - field_insert
+      - field_instance_settings_form
+      - field_is_empty
+      - field_load
+      - field_prepare_translation
+      - field_prepare_translation_alter
+      - field_prepare_view
+      - field_presave
+      - field_schema
+      - field_settings_form
+      - field_update
+      - field_validate
+    delete: true
+
+  field_widget:
+    message: '`@hook` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/1796000'
+        title: 'Field widgets are now plugins'
+    tags:
+      category:
+        - field
+    hook:
+      - field_widget_error
+      - field_widget_form
+      - field_widget_info
+      - field_widget_settings_form
+    delete: true
+
+  init:
+    message: '`@hook` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2013014'
+        title: '`@hook` removed'
+    tags:
+      category:
+        - system
+    delete: true
+
+  library:
+    message: '`@hook` is now `MODULE.libraries.yml`.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2201089'
+        title: '`@hook` replaced by `.libraries.yml` file'
+    tags:
+      category:
+        - system
+    delete: true
+
+  menu:
+    message: '`@hook` has been removed from Drupal 8.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/1800686'
+        title: 'All functionality of `@hook` replaced'
+      -
+        url: 'https://www.drupal.org/node/2177901'
+        title: 'Dynamic routes are now defined in `MODULE.routing.yml`'
+      -
+        url: 'https://www.drupal.org/node/2119699'
+        title: 'Page callbacks have been converted to controller classes'
+      -
+        url: 'https://www.drupal.org/node/2089605'
+        title: 'Route naming convention'
+      -
+        url: 'https://www.drupal.org/node/2165243'
+        title: 'Contextual links are now plugins defined in `MODULE.links.contextual.yml`'
+      -
+        url: 'https://www.drupal.org/node/2007444'
+        title: 'Local actions are now plugins defined in `MODULE.links.action.yml`'
+      -
+        url: 'https://www.drupal.org/node/2044515'
+        title: 'Local tasks and now plugins defined in `MODULE.links.task.yml`'
+      -
+        url: 'https://www.drupal.org/node/2228089'
+        title: 'Menu links are now defined in `MODULE.links.menu.yml`'
+    tags:
+      category:
+        - menu
+    delete: true
+
+  menu_alter:
+    message: '`@hook` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2118147#alter'
+        title: 'Replacements for `@hook`'
+    tags:
+      category:
+        - menu
+    delete: true
+
+  menu_site_status_alter:
+    message: '`@hook` has been removed.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2020005'
+        title: '`@hook` is now an event subscriber'
+    tags:
+      category:
+        - system
+    delete: true
+
+  page_alter:
+    message: '`@hook` is deprecated and may only be used to alter #attached assets.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2357755'
+        title: '@hook deprecated'
+    tags:
+      category:
+        - render
+
+  page_build:
+    message: '`@hook` is deprecated and may only be used to alter #attached assets.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2357755'
+        title: '`@hook` deprecated'
+    tags:
+      category:
+        - render
+
+  url_outbound_alter:
+    message: '`@hook` has been replaced by path processors.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2238759'
+        title: '`@hook` replaced by `OutboundPathProcessorInterface`'
+    tags:
+      category:
+        - render
+        - system
+    delete: true
+
+  watchdog:
+    message: '@hook has been replaced by a PSR-3 compatible logging system.'
+    documentation:
+      -
+        url: 'https://www.drupal.org/node/2270941'
+        title: '`@hook` removed; `watchdog()` deprecated and replaced by PSR-3 compliant logging service'
+    tags:
+      category:
+        - system
+    delete: true
diff --git a/web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.rewriters.yml b/web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.rewriters.yml
new file mode 100644 (file)
index 0000000..6c74cb0
--- /dev/null
@@ -0,0 +1,141 @@
+# This file controls the behavior of parametric rewriters, which are in
+# DMU's Utility\ParametricRewriter namespace.
+#
+# A parametric rewriter is essentially an intelligent search and replace
+# that acts on a function body and bases its changes on one of the
+# function's parameters. The type of parameter MUST be known ahead of time,
+# and it must be one of the types described in this file. A rewriter
+# configured to rewrite for a node, for example, will change $node->nid to
+# $node->id(), $node->title to $node->getTitle(), and so forth.
+
+definitions:
+  account:
+    type_hint: \Drupal\Core\Session\AccountInterface
+  comment:
+    type_hint: \Drupal\comment\CommentInterface
+    properties:
+      cid:
+        get: id
+      changed:
+        get: getChangedTime
+      created:
+        get: getCreatedTime
+        set: setCreatedTime
+      homepage:
+        get: getHomepage
+        set: setHomepage
+      hostname:
+        get: getHostname
+        set: setHostname
+      mail:
+        get: getAuthorEmail
+      name:
+        get: getAuthorName
+        set: setAuthorName
+      status:
+        get: isPublished
+        set: setPublished
+      subject:
+        get: getSubject
+        set: setSubject
+      thread:
+        get: getThread
+        set: setThread
+  field:
+    type_hint: \Drupal\Core\Field\FieldStorageDefinitionInterface
+    properties:
+      cardinality:
+        get: getCardinality
+      field_name:
+        get: getName
+      module:
+        get: getProvider
+      settings:
+        get: getSettings
+      translatable:
+        get: isTranslatable
+        set: setTranslatable
+      type:
+        get: getType
+  field_instance:
+    type_hint: \Drupal\Core\Field\FieldDefinitionInterface
+    properties:
+      bundle:
+        get: getTargetBundle
+      entity_type:
+        get: getTargetEntityTypeId
+      field_name:
+        get: getName
+      required:
+        get: isRequired
+      type:
+        get: getType
+  node:
+    type_hint: \Drupal\node\NodeInterface
+    properties:
+      nid:
+        get: id
+      sticky:
+        get: isSticky
+        set: setSticky
+      status:
+        get: isPublished
+        set: setPublished
+      promoted:
+        get: isPromoted
+        set: setPromoted
+      title:
+        get: getTitle
+        set: setTitle
+      uid:
+        get: getOwnerId
+        set: setOwnerId
+      created:
+        get: getCreatedTime
+        set: setCreatedTime
+      type:
+        get: getType
+      is_new:
+        get: isNew
+  user:
+    type_hint: \Drupal\user\UserInterface
+    properties:
+      uid:
+        get: id
+      access:
+        get: getLastAccessTime
+        set: setLastAccessTime
+      created:
+        get: getCreatedTime
+      is_new:
+        get: isNew
+      login:
+        get: getLastLoginTime
+        set: setLastLoginTime
+      mail:
+        get: getEmail
+        set: setEmail
+      name:
+        get: getUsername
+        set: setUsername
+      pass:
+        get: getPassword
+        set: setPassword
+  taxonomy_term:
+    type_hint: \Drupal\taxonomy\TermInterface
+    properties:
+      tid:
+        get: id
+      name:
+        get: getName
+        set: setName
+      description:
+        get: getDescription
+        set: setDescription
+      weight:
+        get: getWeight
+        set: setWeight
+      vid:
+        get: getVocabularyId
+      is_new:
+        get: isNew
diff --git a/web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.tags.yml b/web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.tags.yml
new file mode 100644 (file)
index 0000000..e8faf6e
--- /dev/null
@@ -0,0 +1,38 @@
+# This file defines the tags available to analyzers when creating issues.
+# An issue can have any number of tags, and each tag consists of one or
+# more arbitrary values. The tags don't affect the analyzers internally,
+# but they can affect the output of the report generated by dmu-analyze
+# so that developers can peruse their modules' issues in various ways.
+#
+# See hasTag(), getTag(), setTag(), and clearTag() in IssueInterface.
+
+definitions:
+  # Categories for issues. An issue can be in any number of categories.
+  # Categories are tagged by their machine name, so that the human-readable
+  # title is editable in one place (namely, here).
+  category:
+    block: 'Blocks'
+    cache: 'Caching'
+    config: 'Configuration'
+    ctools: 'CTools'
+    db: 'Database'
+    entity: 'Entity API'
+    field: 'Field API'
+    form: 'Form API'
+    info: 'Info File'
+    menu: 'Menu/Routing'
+    misc: 'Miscellaneous'
+    node: 'Node API'
+    render: 'Rendering'
+    system: 'System'
+    taxonomy: 'Taxonomy'
+    theme: 'Theme System'
+    ui: 'User Interface'
+    user: 'Users'
+    utility: 'Utilities'
+
+  # Issue error levels. At the time of this writing, all this affects is
+  # the CSS class of the issue as rendered in the report.
+  error_level:
+    - error
+    - warning
diff --git a/web/modules/contrib/drupalmoduleupgrader/drupalmoduleupgrader.drush.inc b/web/modules/contrib/drupalmoduleupgrader/drupalmoduleupgrader.drush.inc
new file mode 100644 (file)
index 0000000..ee17fe5
--- /dev/null
@@ -0,0 +1,322 @@
+<?php
+
+use Drupal\drupalmoduleupgrader\Report;
+use Drupal\drupalmoduleupgrader\Target;
+use Symfony\Component\Filesystem\Filesystem;
+
+/**
+ * Implements hook_drush_command().
+ */
+function drupalmoduleupgrader_drush_command() {
+  $items = [];
+
+  $items['dmu-list'] = [
+    'description' => 'Lists available plugins.',
+    'arguments' => [
+      'plugin_type' => 'The plugin type to query. Can be one of: indexer, analyzer, converter, cleaner.',
+    ],
+    'required-arguments' => TRUE,
+    'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_ROOT,
+  ];
+
+  $items['dmu-index'] = [
+    'description' => 'Indexes a target module.',
+    'arguments' => [
+      'module' => 'The name of a Drupal 7 module.',
+    ],
+    'required-arguments' => TRUE,
+    'examples' => [
+      'drush dmu-index pants' => 'Indexes the pants module.',
+    ],
+    'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_ROOT,
+  ];
+
+  $items['dmu-analyze'] = [
+    'description' => "Analyzes a Drupal 7 module and reports the changes needed to port it to Drupal 8.",
+    'arguments' => [
+      'module' => 'The machine name of a Drupal 7 module.',
+    ],
+    'required-arguments' => TRUE,
+    'options' => [
+      'only' => [
+        'description' => 'A comma-separated list of analyzers to run, excluding all others.',
+        'example-value' => 'HookMenu,VariableAPI,BlockInfo',
+      ],
+      'skip' => [
+        'description' => 'A comma-separated list of analyzers to skip.',
+        'example-value' => 'HookInit,HookExit',
+      ],
+      'path' => [
+        'description' => 'Optional path to the target module.',
+        'example-value' => 'drupal/modules/foobaz',
+      ],
+      'output' => [
+        'description' => 'Optional path to output the report.',
+        'example-value' => 'path/to/module/analyze.html',
+      ],
+    ],
+    'examples' => [
+      'drush dmu-analyze pants' => 'Analyze what needs to be changed in order to port the pants module.',
+    ],
+    'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_ROOT,
+  ];
+
+  $items['dmu-upgrade'] = [
+    'description' => "Upgrades a Drupal 7 module to Drupal 8.",
+    'arguments' => [
+      'module' => 'The machine name of a Drupal 7 module.',
+    ],
+    'required-arguments' => TRUE,
+    'options' => [
+      'backup' => [
+        'description' => 'If set, creates a backup copy of the module before conversion.',
+      ],
+      'only' => [
+        'description' => 'A comma-separated list of converters to run, excluding all others.',
+        'example-value' => 'HookMenu,VariableAPI,BlockInfo',
+      ],
+      'skip' => [
+        'description' => 'A comma-separated list of converters to skip.',
+        'example-value' => 'HookInit,HookExit',
+      ],
+      'path' => [
+        'description' => 'Optional path to the target module. Will be determined automatically if omitted.',
+        'example-value' => 'drupal/modules/foobaz',
+      ],
+    ],
+    'examples' => [
+      'drush dmu-upgrade pants' => 'Upgrade whatever can be automatically upgraded in the pants module.',
+    ],
+    'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_ROOT,
+  ];
+
+  return $items;
+}
+
+/**
+ * Returns a list of plugin IDs of a given type, filtered by the --only
+ * and --skip options.
+ *
+ * @param string $plugin_type
+ *  The plugin type. Can be one of indexer, analyzer, converter, cleaner.
+ *
+ * @return string[]
+ */
+function _dmu_plugin_list($plugin_type) {
+  // Instantiate the plugin manager and get all available plugin IDs.
+  $manager = \Drupal::service('plugin.manager.drupalmoduleupgrader.' . $plugin_type);
+  $plugin_IDs = array_keys($manager->getDefinitions());
+
+  // Filter by the --only and --skip options, if set.
+  if ($only = drush_get_option('only', FALSE)) {
+    $plugin_IDs = array_intersect($plugin_IDs, explode(',', $only));
+  }
+  elseif ($skip = drush_get_option('skip', FALSE)) {
+    $plugin_IDs = array_diff($plugin_IDs, explode(',', $skip));
+  }
+
+  return $plugin_IDs;
+}
+
+/**
+ * Checks for autoload.php, and includes it if it exists or sets an error
+ * if it doesn't.
+ */
+function _dmu_ensure_autoload() {
+  $locations = [
+    __DIR__ . '/vendor/autoload.php',
+    './vendor/autoload.php',
+  ];
+  foreach ($locations as $location) {
+    if (file_exists($location)) {
+      require_once $location;
+      return;
+    }
+  }
+
+  drush_set_error('no_autoload', 'autoload.php not found! Did you remember to run composer install from the drupalmoduleupgrader directory?');
+}
+
+/**
+ * Determines the path to a module.
+ *
+ * @param string $module
+ *  The module's machine name.
+ *
+ * @return string|NULL
+ */
+function _dmu_get_directory($module) {
+  if ($path = drush_get_option('path', NULL)) {
+    return $path;
+  }
+  else {
+    $search_directories = [
+      DRUPAL_ROOT . '/modules/' . $module,
+      __DIR__ . '/'. $module,
+    ];
+
+    $directories = array_filter($search_directories, 'is_dir');
+    if ($directories) {
+      return reset($directories);
+    }
+  }
+}
+
+/**
+ * Checks possible locations of a target module, and ensures that at least
+ * one exists. If none do, sets an error.
+ *
+ * @param string $module
+ *  The target module's machine name.
+ */
+function _dmu_ensure_directory($module) {
+  $directory = _dmu_get_directory($module);
+
+  if (empty($directory)) {
+    if ($path = drush_get_option('path', NULL)) {
+      drush_set_error('invalid_dir', 'Invalid path: ' . $path);
+    }
+    else {
+      drush_set_error('no_directory', "Cannot determine base directory of module $module. Try passing --path=modules/foobar");
+    }
+  }
+}
+
+/**
+ * Validates any of the DMU commands.
+ */
+function _dmu_validate_command($module) {
+  _dmu_ensure_autoload();
+  _dmu_ensure_directory($module);
+}
+
+function _dmu_build_target($module) {
+  $target = new Target(_dmu_get_directory($module), \Drupal::getContainer());
+
+  drush_print(\Drupal::translation()->translate('Indexing...'), 0, NULL, FALSE);
+  $target->buildIndex();
+  drush_print(\Drupal::translation()->translate('done.'));
+
+  return $target;
+}
+
+/**
+ * ----- dmu-list -----
+ */
+
+/**
+ * Lists all the available module-wide plugins.
+ */
+function drush_drupalmoduleupgrader_dmu_list($plugin_type) {
+  $manager = \Drupal::service('plugin.manager.drupalmoduleupgrader.' . $plugin_type);
+
+  $list = [];
+  foreach ($manager->getDefinitions() as $id => $definition) {
+    $list[$id] = $definition['description'];
+  }
+  drush_print_table(drush_key_value_to_array_table($list));
+}
+
+/**
+ * ----- dmu-index -----
+ */
+
+function drush_drupalmoduleupgrader_dmu_index_validate($module) {
+  _dmu_validate_command($module);
+}
+
+/**
+ * ----- dmu-analyze -----
+ */
+
+function drush_drupalmoduleupgrader_dmu_analyze_validate($module) {
+  _dmu_validate_command($module);
+}
+
+/**
+ * Analyzes what needs changing in a module to port it to Drupal 8.
+ *
+ * @param string $module
+ *  The machine name of the module to analyze.
+ */
+function drush_drupalmoduleupgrader_dmu_analyze($module) {
+  $target = _dmu_build_target($module);
+
+  $total_issues = 0;
+  $report = new Report();
+
+  $analyzers = \Drupal::service('plugin.manager.drupalmoduleupgrader.analyzer');
+  foreach (_dmu_plugin_list('analyzer') as $id) {
+    drush_log(\Drupal::translation()->translate('Executing plugin: @plugin_id', ['@plugin_id' => $id]), 'notice');
+    $issues = $analyzers->createInstance($id)->analyze($target);
+
+    if ($issues) {
+      if (! is_array($issues)) {
+        $issues = array($issues);
+      }
+      foreach ($issues as $issue) {
+        $report->addIssue($issue);
+      }
+      $total_issues += sizeof($issues);
+    }
+  }
+
+  if ($total_issues) {
+    $render = [
+      '#theme' => 'dmu_report',
+      '#report' => $report,
+      '#group_by' => 'category',
+    ];
+
+    $destination = drush_get_option('output', $target->getPath('upgrade-info.html'));
+    $output = \Drupal::service('renderer')->renderRoot($render);
+    file_put_contents($destination, $output);
+    drush_log(\Drupal::translation()->translate('Generated a report at @path', ['@path' => $destination]), 'success');
+  }
+  else {
+    drush_log(\Drupal::translation()->translate('Wow...no issues found! You get a cookie :)', 'success'));
+  }
+}
+
+/**
+ * ----- dmu-upgrade -----
+ */
+
+function drush_drupalmoduleupgrader_dmu_upgrade_validate($module) {
+  _dmu_validate_command($module);
+}
+
+/**
+ * Tries to automatically convert a Drupal 7 module to Drupal 8.
+ *
+ * @param string $module
+ *  The module to upgrade.
+ */
+function drush_drupalmoduleupgrader_dmu_upgrade($module) {
+  $target = _dmu_build_target($module);
+
+  if (drush_get_option('backup', FALSE)) {
+    $fs = new Filesystem();
+    $backup_at = $target->getBasePath() . '.bak';
+    $fs->mirror($target->getBasePath(), $backup_at);
+    drush_log(\Drupal::translation()->translate('Created backup at @path', [ '@path' => $backup_at ]), 'success');
+  }
+
+  $converters = \Drupal::service('plugin.manager.drupalmoduleupgrader.converter');
+  foreach (_dmu_plugin_list('converter') as $id) {
+    /** @var \Drupal\drupalmoduleupgrader\ConverterInterface $converter */
+    $converter = $converters->createInstance($id);
+
+    if ($converter->isExecutable($target)) {
+      drush_log(\Drupal::translation()->translate('Executing plugin: @plugin_id', ['@plugin_id' => $id]), 'notice');
+      try {
+        $converter->convert($target);
+      }
+      catch (Exception $e) {
+        drush_log($e->getMessage(), 'error');
+        // Being a notice, the stack trace will only appear in verbose mode.
+        drush_log($e->getTraceAsString(), 'notice');
+      }
+    }
+  }
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/drupalmoduleupgrader.info.yml b/web/modules/contrib/drupalmoduleupgrader/drupalmoduleupgrader.info.yml
new file mode 100644 (file)
index 0000000..d7f0c6a
--- /dev/null
@@ -0,0 +1,12 @@
+name: Drupal Module Upgrader
+description: Provides tools for upgrading Drupal 7 modules to Drupal 8.
+core: 8.x
+type: module
+package: Development
+# These dependencies are used ONLY by the test suite. If you want to run
+# the tests, install the -->DRUPAL 7<-- versions of these modules, alongside
+# DMU itself.
+# dependencies:
+#  - diff
+#  - examples
+#  - pants
diff --git a/web/modules/contrib/drupalmoduleupgrader/drupalmoduleupgrader.module b/web/modules/contrib/drupalmoduleupgrader/drupalmoduleupgrader.module
new file mode 100644 (file)
index 0000000..b01d5e5
--- /dev/null
@@ -0,0 +1,124 @@
+<?php
+
+/**
+ * Implements hook_theme().
+ */
+function drupalmoduleupgrader_theme() {
+  return [
+    'dmu_block' => [
+      'variables' => [
+        'module' => 'MYMODULE',
+        'class' => 'MyBlock',
+        'block_id' => 'my_block_id',
+        'block_label' => \Drupal::translation()->translate('Untitled'),
+        'configurable' => FALSE,
+      ],
+      'template' => 'Block',
+    ],
+    'dmu_controller' => [
+      'variables' => [
+        'module' => 'MYMODULE',
+      ],
+      'template' => 'Controller',
+    ],
+    'dmu_entity_type' => [
+      'variables' => [
+        'module' => 'MYMODULE',
+        'class' => 'MyEntityType',
+        // This will be pretty much pulled directly out of hook_entity_info().
+        'info' => [],
+      ],
+      'template' => 'EntityType',
+    ],
+    'dmu_event_subscriber' => [
+      'variables' => [
+        'module' => 'MYMODULE',
+        'class' => 'MyConfigForm',
+        'event' => 0,
+        'priority' => 0,
+      ],
+      'template' => 'EventSubscriber',
+    ],
+    'dmu_form' => [
+      'variables' => [
+        'module' => 'MYMODULE',
+        'class' => 'MyForm',
+        'form_id' => 'my_form_id',
+        'config' => FALSE,
+      ],
+      'template' => 'Form',
+    ],
+    'dmu_formatter' => [
+      'variables' => [
+        'module' => 'MYMODULE',
+        'class' => 'MyFormatter',
+        'info' => [
+          'id' => 'formatter_id',
+          'label' => 'My Formatter',
+          'description' => 'Formatter description.',
+          'field_types' => [],
+        ],
+      ],
+      'template' => 'Formatter',
+    ],
+    'dmu_issue' => [
+      'variables' => [
+        'issue' => NULL,
+      ],
+      'template' => 'Issue',
+    ],
+    'dmu_outbound_path_processor' => [
+      'variables' => [
+        'module' => 'MYMODULE',
+      ],
+      'template' => 'OutboundPathProcessor',
+    ],
+    'dmu_logger' => [
+      'variables' => [
+        'module' => 'MYMODULE',
+      ],
+      'template' => 'Logger',
+    ],
+    'dmu_route_subscriber' => [
+      'variables' => [
+        'module' => 'MYMODULE',
+      ],
+      'template' => 'RouteSubscriber',
+    ],
+    'dmu_report' => [
+      'variables' => [
+        'report' => NULL,
+        'issues' => [],
+      ],
+      'template' => 'Report',
+    ],
+    'dmu_widget' => [
+      'variables' => [
+        'module' => 'MYMODULE',
+        'class' => 'MyWidget',
+        'info' => [
+          'id' => 'widget_id',
+          'label' => 'My Widget',
+          'description' => 'Widget description goes here.',
+          'field_types' => [],
+        ],
+      ],
+      'template' => 'Widget',
+    ],
+  ];
+}
+
+function template_preprocess_dmu_report(array &$variables) {
+  $categories = \Drupal::config('drupalmoduleupgrader.tags')->get('category');
+
+  /** @var \Drupal\drupalmoduleupgrader\IssueInterface $issue */
+  foreach ($variables['report']->getIssues() as $issue) {
+    $category = $categories[ $issue->hasTag('category') ? $issue->getTag('category')[0] : 'misc' ];
+
+    $variables['issues'][$category][] = [
+      '#theme' => 'dmu_issue',
+      '#issue' => $issue,
+    ];
+  }
+  ksort($variables['issues']);
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/drupalmoduleupgrader.services.yml b/web/modules/contrib/drupalmoduleupgrader/drupalmoduleupgrader.services.yml
new file mode 100644 (file)
index 0000000..045bdaa
--- /dev/null
@@ -0,0 +1,95 @@
+services:
+  # Indexers are plugins responsible for gathering information about a target
+  # module. Things like:
+  #
+  # - Which classes are defined, and which files they reside in
+  # - Which functions are defined, and where
+  # - The tests, if any, and what type of tests they are
+  # - Which hooks the module implements
+  # - ...etc.
+  #
+  # Indexers can index any information about a target module, and they store it
+  # in an indexer (provided by the module_indexer service defined above).
+  plugin.manager.drupalmoduleupgrader.indexer:
+    class: Drupal\Core\Plugin\DefaultPluginManager
+    arguments:
+      - Plugin/DMU/Indexer
+      - '@container.namespaces'
+      - '@module_handler'
+      - Drupal\drupalmoduleupgrader\IndexerInterface
+      - Drupal\drupalmoduleupgrader\Annotation\Indexer
+
+  # Analyzers scan a module, determine what problems exist, then flag issues
+  # containing a summary, a list of problem points (actual line numbers and
+  # file names in the scanned module), with links with documentation explaining
+  # what needs to be changed. As the name implies, analyzers are read-only and
+  # do not modify the target module in any way, shape, or form.
+  #
+  # Analyzers are invoked after all indexers have been run, so they can and should
+  # use the information in the index as needed.
+  plugin.manager.drupalmoduleupgrader.analyzer:
+    class: Drupal\Core\Plugin\DefaultPluginManager
+    arguments:
+      - Plugin/DMU/Analyzer
+      - '@container.namespaces'
+      - '@module_handler'
+      - Drupal\drupalmoduleupgrader\AnalyzerInterface
+      - Drupal\drupalmoduleupgrader\Annotation\Analyzer
+
+  # Converters are DEPRECATED by fixers and should not be used or extended.
+  plugin.manager.drupalmoduleupgrader.converter:
+    class: Drupal\Core\Plugin\DefaultPluginManager
+    arguments:
+      - Plugin/DMU/Converter
+      - '@container.namespaces'
+      - '@module_handler'
+      - Drupal\drupalmoduleupgrader\ConverterInterface
+      - Drupal\drupalmoduleupgrader\Annotation\Converter
+
+  # Fixers perform small, isolated changes to PHP code, using Pharborist.
+  # They're the same idea as PHP_CodeSniffer fixer classes, except that they
+  # have the full power of Drupal, DMU, and Pharborist behind them. Kind of
+  # a "you and what army?" situation, except fixers are on the side of the
+  # mighty! :)
+  plugin.manager.drupalmoduleupgrader.fixer:
+    class: Drupal\Core\Plugin\DefaultPluginManager
+    arguments:
+      - Plugin/DMU/Fixer
+      - '@container.namespaces'
+      - '@module_handler'
+      - Drupal\drupalmoduleupgrader\FixerInterface
+      - Drupal\drupalmoduleupgrader\Annotation\Fixer
+
+  # Parametric rewriters are intelligent search-and-replace plugins that act
+  # on complete functions. Given one of the function's parameters and its type
+  # (which must be known ahead of time), the rewriter will alter the function
+  # so that it's calling the parameter's correct getters and setters for its
+  # various properties. Essentially, they're a type-aware search and replace.
+  plugin.manager.drupalmoduleupgrader.rewriter:
+    class: Drupal\Core\Plugin\DefaultPluginManager
+    arguments:
+      - Plugin/DMU/Rewriter
+      - '@container.namespaces'
+      - '@module_handler'
+      - Drupal\drupalmoduleupgrader\RewriterInterface
+      - Drupal\drupalmoduleupgrader\Annotation\Rewriter
+
+  plugin.manager.drupalmoduleupgrader.route:
+    class: Drupal\Core\Plugin\DefaultPluginManager
+    arguments:
+      - Plugin/DMU/Routing
+      - '@container.namespaces'
+      - '@module_handler'
+      - Drupal\drupalmoduleupgrader\Routing\RouteConverterInterface
+      - Drupal\drupalmoduleupgrader\Annotation\Converter
+
+  drupalmoduleupgrader.link_binding:
+    class: Drupal\drupalmoduleupgrader\Routing\LinkBinding\LinkBindingFactory
+    arguments:
+      - '@plugin.manager.menu.link'
+
+  drupalmoduleupgrader.form_converter:
+    class: Drupal\drupalmoduleupgrader\Utility\FormConverterFactory
+    arguments:
+      - '@string_translation'
+      - '@plugin.manager.drupalmoduleupgrader.rewriter'
diff --git a/web/modules/contrib/drupalmoduleupgrader/phpunit.xml.dist b/web/modules/contrib/drupalmoduleupgrader/phpunit.xml.dist
new file mode 100644 (file)
index 0000000..011f984
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit bootstrap="tests/bootstrap.php" colors="true">
+  <php>
+    <!-- Set error reporting to E_ALL. -->
+    <ini name="error_reporting" value="32767"/>
+    <!-- Do not limit the amount of memory tests take to run. -->
+    <ini name="memory_limit" value="-1"/>
+  </php>
+  <testsuites>
+    <testsuite name="Drupal Module Upgrader">
+      <directory>./tests</directory>
+    </testsuite>
+  </testsuites>
+</phpunit>
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/AnalyzerBase.php b/web/modules/contrib/drupalmoduleupgrader/src/AnalyzerBase.php
new file mode 100644 (file)
index 0000000..d2e4834
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader;
+
+/**
+ * Base class for analyzers.
+ */
+abstract class AnalyzerBase extends PluginBase implements AnalyzerInterface {
+
+  /**
+   * Creates an issue with title, summary, documentation and tags pulled from
+   * the plugin definition.
+   *
+   * @param TargetInterface $target
+   *  The target module.
+   *
+   * @return IssueInterface
+   */
+  protected function buildIssue(TargetInterface $target) {
+    $issue = new Issue($target, $this->pluginDefinition['message'], $this->pluginDefinition['summary']);
+
+    foreach ($this->pluginDefinition['documentation'] as $doc) {
+      $issue->addDocumentation($doc['url'], $doc['title']);
+    }
+
+    foreach ($this->pluginDefinition['tags'] as $group => $tag) {
+      $issue->setTag($group, $tag);
+    }
+
+    // If the plugin definition didn't supply an error_level tag, mark this
+    // one as an error.
+    if (empty($this->pluginDefinition['tags']['error_level'])) {
+      $issue->setTag('error_level', 'error');
+    }
+
+    return $issue;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/AnalyzerInterface.php b/web/modules/contrib/drupalmoduleupgrader/src/AnalyzerInterface.php
new file mode 100644 (file)
index 0000000..d315bdd
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader;
+
+/**
+ * Interface for plugins which can analyze a target module and flag potential
+ * or existing issues.
+ */
+interface AnalyzerInterface {
+
+  /**
+   * Analyzes a target module and flags any issues found.
+   *
+   * @param TargetInterface $target
+   *  The target module.
+   *
+   * @return \Drupal\drupalmoduleupgrader\IssueInterface[]
+   */
+  public function analyze(TargetInterface $target);
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Annotation/Analyzer.php b/web/modules/contrib/drupalmoduleupgrader/src/Annotation/Analyzer.php
new file mode 100644 (file)
index 0000000..90d8677
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Annotation;
+
+use Drupal\Component\Annotation\Plugin;
+
+/**
+ * Defines a Plugin annotation object for DMU analyzer plugins.
+ *
+ * Analyzers scan a target module's code to determine if any problems exist. If
+ * any do exist, it's the analyzer's job to file an issue detailing the nature
+ * of the problem -- summarizing the issue, pointing out where the problem is
+ * found, and referring the developer to documentation on drupal.org explaining
+ * how to fix the problem.
+ *
+ * Plugin Namespace: Plugin\DMU\Analyzer
+ *
+ * @Annotation
+ */
+class Analyzer extends Plugin {
+
+  /**
+   * The plugin ID.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * A short description of the analysis the plugin performs.
+   *
+   * @var string
+   */
+  public $description;
+
+  /**
+   * Documentation describing the changes covered by the plugin. Each item
+   * in the array should be an array with 'url' and 'title' keys.
+   *
+   * @var array[]
+   */
+  public $documentation = [];
+
+  /**
+   * The issue title. Markdown and HTML are allowed.
+   *
+   * @var string
+   */
+  public $title;
+
+  /**
+   * An optional detailed summary of the issue. Markdown and HTML are allowed.
+   *
+   * @var string
+   */
+  public $summary;
+  
+  /**
+   * The default tags to be applied to flagged issues. Tags are fairly arbitrary and
+   * can be any value. Tags are divided into groups (i.e., the keys of this here
+   * array).
+   *
+   * @var array
+   */
+  public $tags = [];
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Annotation/Converter.php b/web/modules/contrib/drupalmoduleupgrader/src/Annotation/Converter.php
new file mode 100644 (file)
index 0000000..0a4af4f
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Annotation;
+
+use Drupal\Component\Annotation\Plugin;
+
+/**
+ * Defines a Plugin annotation object for DMU converter plugins.
+ *
+ * Converters take Drupal 7 code and do what they can to rewrite it for Drupal 8.
+ * When a converter cannot convert something, it can leave a FIXME notice at the
+ * affected code informing the developer what still needs to be done. Converters
+ * may generate ugly code, but refactoring is not their job. Converts modify the
+ * target module in place.
+ *
+ * Plugin Namespace: Plugin\DMU\Converter
+ *
+ * @Annotation
+ */
+class Converter extends Plugin {
+
+  /**
+   * The plugin ID.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * A short description of the conversion the plugin performs.
+   *
+   * @var string
+   */
+  public $description;
+
+  /**
+   * If the plugin converts a hook (or several hooks), the hook(s) it converts
+   * (without the hook_ prefix).
+   *
+   * @var string|string[]
+   */
+  public $hook;
+
+  /**
+   * Optional FIXME notice the converter should leave at code that it cannot convert.
+   *
+   * @var string
+   */
+  public $fixme;
+
+  /**
+   * Optional documentation links to be included in the FIXME notice.
+   *
+   * @var string[]
+   */
+  public $documentation = [];
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Annotation/Fixer.php b/web/modules/contrib/drupalmoduleupgrader/src/Annotation/Fixer.php
new file mode 100644 (file)
index 0000000..e9616c7
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\drupalmoduleupgrader\Annotation\Fixer.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Annotation;
+
+use Drupal\Component\Annotation\Plugin;
+
+/**
+ * Defines a Plugin annotation object for DMU fixer plugins.
+ *
+ * Fixers are similar in nature to the fixer classes used by PHP_CodeSniffer,
+ * in the sense that their job is to perform particular, isolated changes
+ * to code. But DMU fixers are a lot more powerful than PHPCS's because a)
+ * they're Drupal plugins, and b) they're using Pharborist.
+ *
+ * Plugin Namespace: Plugin\DMU\Fixer
+ *
+ * @Annotation
+ */
+class Fixer extends Plugin {
+
+  /**
+   * The plugin ID.
+   *
+   * @var string
+   */
+  public $id;
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Annotation/Indexer.php b/web/modules/contrib/drupalmoduleupgrader/src/Annotation/Indexer.php
new file mode 100644 (file)
index 0000000..63ea18b
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Annotation;
+
+use Drupal\Component\Annotation\Plugin;
+
+/**
+ * Defines a Plugin annotation object for DMU indexer plugins.
+ *
+ * Indexers scan a target module to determine what's in it so that other plugins
+ * can use that information. All available indexers are always run before any
+ * other plugin type. Indexers are responsible for cataloguing things like:
+ *
+ * - What hooks a module implements, and where those implementations reside (i.e.,
+ *   which files)
+ * - Classes defined by a module
+ * - Functions defined by a module
+ * - Tests defined by a module, and what kind of tests they are
+ * - Which functions are called by the module, and when
+ *
+ * Any information gathered by an indexer is available to other plugin types.
+ * Essentially, indexers build a "map" of a target module, which is stored in
+ * an index backend (by default, an SQLite database that lives only in memory).
+ *
+ * Plugin Namespace: Plugin\DMU\Indexer
+ *
+ * @Annotation
+ */
+class Indexer extends Plugin {
+
+  /**
+   * The plugin ID.
+   *
+   * @var string
+   */
+  public $id;
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Annotation/Rewriter.php b/web/modules/contrib/drupalmoduleupgrader/src/Annotation/Rewriter.php
new file mode 100644 (file)
index 0000000..b07a5fd
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Annotation;
+
+use Drupal\Component\Annotation\Plugin;
+
+/**
+ * Defines a Plugin annotation object for parametric rewriters.
+ *
+ * Parametric rewriters are intelligent search-and-replace plugins which act
+ * on a function body based on one of the function's parameters. The parameter
+ * type must be known ahead of time.
+ *
+ * Plugin Namespace: Plugin\DMU\Rewriter
+ *
+ * @Annotation
+ */
+class Rewriter extends Plugin {
+
+  /**
+   * The plugin ID.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * Optional type hint to set on the parameter.
+   *
+   * @var
+   */
+  public $type_hint;
+
+  /**
+   * Properties known to the rewriter, keyed by property. Each property can
+   * have 'get' and 'set' keys, which are the corresponding getter and setter
+   * methods to replace the property with. The 'get' key is required; the
+   * setter is only needed if it's possible to set the property at all (for
+   * example, an entity ID property would not have a setter).
+   *
+   * @var array
+   */
+  public $properties = [];
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/ArrayIndexer.php b/web/modules/contrib/drupalmoduleupgrader/src/ArrayIndexer.php
new file mode 100644 (file)
index 0000000..a08e0e1
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader;
+
+abstract class ArrayIndexer extends IndexerBase {
+
+  protected $elements = [];
+
+  /**
+   * {@inheritdoc}
+   */
+  final public function hasAny(array $keys) {
+    foreach ($keys as $key) {
+      if ($this->count($key)) {
+        return TRUE;
+      }
+    }
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  final public function hasAll(array $keys) {
+    foreach ($keys as $key) {
+      if ($this->count($key) == 0) {
+        return FALSE;
+      }
+    }
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  final public function get($key) {
+    return $this->elements[$key];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  final public function getMultiple(array $keys) {
+    $values = array();
+
+    foreach ($keys as $key) {
+      if (array_key_exists($key, $this->elements)) {
+        $values[$key] = $this->get($key);
+      }
+    }
+
+    return $values;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  final public function getAll() {
+    return $this->elements;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/ConverterBase.php b/web/modules/contrib/drupalmoduleupgrader/src/ConverterBase.php
new file mode 100644 (file)
index 0000000..8b2c4e4
--- /dev/null
@@ -0,0 +1,285 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader;
+
+use Drupal\Component\Serialization\Yaml;
+use Drupal\drupalmoduleupgrader\Utility\Filter\ContainsLogicFilter;
+use Drupal\drupalmoduleupgrader\Utility\Filter\FunctionCallArgumentFilter;
+use Pharborist\DocCommentNode;
+use Pharborist\Filter;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Functions\FunctionDeclarationNode;
+use Pharborist\Functions\ParameterNode;
+use Pharborist\LineCommentBlockNode;
+use Pharborist\Objects\ClassNode;
+use Pharborist\Parser;
+use Pharborist\Variables\VariableNode;
+use Pharborist\WhitespaceNode;
+use Symfony\Component\Filesystem\Filesystem;
+
+/**
+ * Base class for converters.
+ */
+abstract class ConverterBase extends PluginBase implements ConverterInterface {
+
+  // Used by buildFixMe() to determine the comment style of the generated
+  // FIXME notice.
+  const LINE_COMMENT = '//';
+  const DOC_COMMENT = '/**/';
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isExecutable(TargetInterface $target) {
+    // If the plugin applies to particular hook(s), only return TRUE if the
+    // target module implements any of the hooks. Otherwise, return TRUE
+    // unconditionally.
+    if (isset($this->pluginDefinition['hook'])) {
+      return (boolean) array_filter((array) $this->pluginDefinition['hook'], [ $target->getIndexer('function'), 'has' ]);
+    }
+    else {
+      return TRUE;
+    }
+  }
+
+  /**
+   * Executes the target module's implementation of the specified hook, and
+   * returns the result.
+   *
+   * @return mixed
+   *
+   * @throws \LogicException if the target module doesn't implement the
+   * specified hook, or if the implementation contains logic.
+   *
+   * @deprecated
+   */
+  protected function executeHook(TargetInterface $target, $hook) {
+    $indexer = $target->getIndexer('function');
+
+    if ($indexer->has($hook)) {
+      // Configure the ContainsLogicFilter so that certain "safe" functions
+      // will pass it.
+      $has_logic = new ContainsLogicFilter();
+      $has_logic->whitelist('t');
+      $has_logic->whitelist('drupal_get_path');
+
+      $function = $indexer->get($hook);
+      if ($function->is($has_logic)) {
+        throw new \LogicException('{target}_{hook} cannot be executed because it contains logic.');
+      }
+      else {
+        $function_name = $function->getName()->getText();
+        if (! function_exists($function_name)) {
+          eval($function->getText());
+        }
+        return call_user_func($function_name);
+      }
+    }
+    else {
+      throw new \LogicException('{target} does not implement hook_{hook}.');
+    }
+  }
+
+  /**
+   * Creates an empty implementation of a hook.
+   *
+   * @param TargetInterface $target
+   *  The target module.
+   * @param string $hook
+   *  The hook to implement, without the hook_ prefix.
+   *
+   * @return \Pharborist\Functions\FunctionDeclarationNode
+   *  The hook implementation, appended to the main module file.
+   */
+  protected function implement(TargetInterface $target, $hook) {
+    $function = FunctionDeclarationNode::create($target->id() . '_' . $hook);
+    $function->setDocComment(DocCommentNode::create('Implements hook_' . $hook . '().'));
+
+    $module_file = $target->getPath('.module');
+    $target->open($module_file)->append($function);
+
+    WhitespaceNode::create("\n")->insertBefore($function);
+    WhitespaceNode::create("\n")->insertAfter($function);
+
+    return $function;
+  }
+
+  /**
+   * Writes a file to the target module's directory.
+   *
+   * @param TargetInterface $target
+   *  The target module.
+   * @param string $path
+   *  The path of the file to write, relative to the module root.
+   * @param string $data
+   *  The file contents.
+   *
+   * @return string
+   *  The path of the file, including the target's base path.
+   */
+  public function write(TargetInterface $target, $path, $data) {
+    static $fs;
+    if (empty($fs)) {
+      $fs = new Filesystem();
+    }
+
+    $destination_path = $target->getPath($path);
+    $fs->dumpFile($destination_path, (string) $data);
+
+    return $destination_path;
+  }
+
+  /**
+   * Writes a class to the target module's PSR-4 root.
+   *
+   * @param TargetInterface $target
+   *  The target module.
+   * @param ClassNode $class
+   *  The class to write. The path will be determined from the class'
+   *  fully qualified name.
+   *
+   * @return string
+   *  The generated path to the class.
+   */
+  public function writeClass(TargetInterface $target, ClassNode $class) {
+    $class_path = ltrim($class->getName()->getAbsolutePath(), '\\');
+    $path = str_replace([ 'Drupal\\' . $target->id(), '\\', ], [ 'src', '/' ], $class_path) . '.php';
+
+    return $this->write($target, $path, $class->parents()->get(0));
+  }
+
+  /**
+   * Writes out arbitrary data in YAML format.
+   *
+   * @param TargetInterface $target
+   *  The target module.
+   * @param string $group
+   *  The name of the YAML file. It will be prefixed with the module's machine
+   *  name and suffixed with .yml. For example, a group value of 'routing'
+   *  will write MODULE.routing.yml.
+   * @param array $data
+   *  The data to write.
+   *
+   * @todo This should be writeYAML, not writeInfo.
+   */
+  protected function writeInfo(TargetInterface $target, $group, array $data) {
+    $destination = $target->getPath('.' . $group . '.yml');
+    file_put_contents($destination, Yaml::encode($data));
+  }
+
+  /**
+   * Writes a service definition to the target module's services.yml file.
+   *
+   * @param TargetInterface $target
+   *  The target module.
+   * @param string $service_id
+   *  The service ID. If an existing one with the same ID already exists,
+   *  it will be overwritten.
+   * @param array $service_definition
+   */
+  protected function writeService(TargetInterface $target, $service_id, array $service_definition) {
+    $services = $target->getServices();
+    $services->set($service_id, $service_definition);
+    $this->writeInfo($target, 'services', [ 'services' => $services->toArray() ]);
+  }
+
+  /**
+   * Parses a generated class into a syntax tree.
+   *
+   * @param string|array $class
+   *  The class to parse, either as a string of PHP code or a renderable array.
+   *
+   * @return \Pharborist\Objects\ClassNode
+   */
+  protected function parse($class) {
+    if (is_array($class)) {
+      $class = \Drupal::service('renderer')->renderPlain($class);
+    }
+    return Parser::parseSnippet($class)->find(Filter::isInstanceOf('Pharborist\Objects\ClassNode'))[0];
+  }
+
+  /**
+   * Builds a FIXME notice using either the text in the plugin definition,
+   * or passed-in text.
+   *
+   * @param string|NULL $text
+   *  The FIXME notice's text, with variable placeholders and no translation.
+   * @param array $variables
+   *  Optional variables to use in translation. If empty, the FIXME will not
+   *  be translated.
+   * @param string|NULL $style
+   *  The comment style. Returns a LineCommentBlockNode if this is set to
+   *  self::LINE_COMMENT, a DocCommentNode if self::DOC_COMMENT, or the FIXME
+   *  as a string if set to anything else.
+   *
+   * @return mixed
+   */
+  protected function buildFixMe($text = NULL, array $variables = [], $style = self::LINE_COMMENT) {
+    $fixMe = "@FIXME\n" . ($text ?: $this->pluginDefinition['fixme']);
+
+    if (isset($this->pluginDefinition['documentation'])) {
+      $fixMe .= "\n";
+      foreach ($this->pluginDefinition['documentation'] as $doc) {
+        $fixMe .= "\n@see ";
+        $fixMe .= (isset($doc['url']) ? $doc['url'] : (string) $doc);
+      }
+    }
+
+    if ($variables) {
+      $fixMe = $this->t($fixMe, $variables);
+    }
+
+    switch ($style) {
+      case self::LINE_COMMENT:
+        return LineCommentBlockNode::create($fixMe);
+
+      case self::DOC_COMMENT:
+        return DocCommentNode::create($fixMe);
+
+      default:
+        return $fixMe;
+    }
+  }
+
+  /**
+   * Parametrically rewrites a function.
+   *
+   * @param \Drupal\drupalmoduleupgrader\RewriterInterface $rewriter
+   *  A fully configured parametric rewriter.
+   * @param \Pharborist\Functions\ParameterNode $parameter
+   *  The parameter upon which to base the rewrite.
+   * @param TargetInterface $target
+   *  The target module.
+   * @param boolean $recursive
+   *  If TRUE, rewriting will recurse into called functions which are passed
+   *  the rewritten parameter as an argument.
+   */
+  protected function rewriteFunction(RewriterInterface $rewriter, ParameterNode $parameter, TargetInterface $target, $recursive = TRUE) {
+    $rewriter->rewrite($parameter);
+    $target->save($parameter);
+
+    // Find function calls within the rewritten function which are called
+    // with the rewritten parameter.
+    $indexer = $target->getIndexer('function');
+    $next = $parameter
+      ->getFunction()
+      ->find(new FunctionCallArgumentFilter($parameter->getName()))
+      ->filter(function(FunctionCallNode $call) use ($indexer) {
+        return $indexer->has($call->getName()->getText());
+      });
+
+    /** @var \Pharborist\Functions\FunctionCallNode $call */
+    foreach ($next as $call) {
+      /** @var \Pharborist\Functions\FunctionDeclarationNode $function */
+      $function = $indexer->get($call->getName()->getText());
+
+      foreach ($call->getArguments() as $index => $argument) {
+        if ($argument instanceof VariableNode && $argument->getName() == $parameter->getName()) {
+          $this->rewriteFunction($rewriter, $function->getParameterAtIndex($index), $target, $recursive);
+          break;
+        }
+      }
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/ConverterInterface.php b/web/modules/contrib/drupalmoduleupgrader/src/ConverterInterface.php
new file mode 100644 (file)
index 0000000..59484b8
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader;
+
+/**
+ * Interface implemented by all plugins which can modify a Drupal 7 module and
+ * convert part of it to Drupal 8.
+ */
+interface ConverterInterface {
+
+  /**
+   * Returns if this conversion applies to the target module. If FALSE,
+   * the convert() method will not be called.
+   *
+   * @param TargetInterface $target
+   *  The target module.
+   *
+   * @return boolean
+   */
+  public function isExecutable(TargetInterface $target);
+
+  /**
+   * Performs required conversions.
+   *
+   * @param TargetInterface $target
+   *  The target module to convert.
+   */
+  public function convert(TargetInterface $target);
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/DependencyCollectorTrait.php b/web/modules/contrib/drupalmoduleupgrader/src/DependencyCollectorTrait.php
new file mode 100644 (file)
index 0000000..98a50b0
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a default implementation of ContainerFactoryPluginInterface which
+ * will pull any dependencies declared in the plugin definition out of the
+ * container.
+ */
+trait DependencyCollectorTrait {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    $arguments = array_slice(func_get_args(), 1);
+    $arguments += array_map([ $container, 'get' ], @($plugin_definition['dependencies'] ? : []));
+    return (new \ReflectionClass(get_called_class()))->newInstanceArgs($arguments);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/DeriverBase.php b/web/modules/contrib/drupalmoduleupgrader/src/DeriverBase.php
new file mode 100644 (file)
index 0000000..e8b251a
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader;
+
+use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Base class for DMU's plugin derivers. Sets up the translation service and
+ * provides a basic implementation of DeriverInterface::getDerivativeDefinition().
+ */
+abstract class DeriverBase implements ContainerDeriverInterface {
+
+  use StringTranslationTrait;
+
+  public function __construct(TranslationInterface $translator) {
+    $this->stringTranslation = $translator;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, $base_plugin_id) {
+    return new static($container->get('string_translation'));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinition($derivative_id, $base_definition) {
+    $derivatives = $this->getDerivativeDefinitions($base_definition);
+
+    if (isset($derivatives[$derivative_id])) {
+      return $derivatives[$derivative_id];
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/FixerBase.php b/web/modules/contrib/drupalmoduleupgrader/src/FixerBase.php
new file mode 100644 (file)
index 0000000..52ebe51
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\FixerBase.
+ */
+
+namespace Drupal\drupalmoduleupgrader;
+
+use Drupal\Core\Plugin\PluginBase as CorePluginBase;
+use Pharborist\Constants\ConstantNode;
+use Pharborist\Functions\ParameterNode;
+use Pharborist\Node;
+use Pharborist\NodeInterface;
+
+/**
+ * Base class for fixers, containing a lot of helpful utilities.
+ */
+abstract class FixerBase extends CorePluginBase implements FixerInterface {
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\TargetInterface
+   */
+  protected $target;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setTarget(TargetInterface $target) {
+    $this->target = $target;
+  }
+
+  protected function getUnaliasedPath($path) {
+    return preg_replace('/^~/', $this->target->getBasePath(), $path);
+  }
+
+  /**
+   * Returns if a node uses a specific trait anywhere in its lineage.
+   *
+   * @param \Pharborist\NodeInterface $node
+   *
+   * @return boolean
+   */
+  protected function usesTrait($trait, NodeInterface $node) {
+    $hierarchy = class_parents($node);
+    array_unshift($hierarchy, get_class($node));
+
+    $traits = [];
+    foreach ($hierarchy as $parent) {
+      $this->collectTraits($parent, $traits);
+    }
+
+    return in_array($trait, $traits);
+  }
+
+  private function collectTraits($class, array &$all_traits = []) {
+    $traits = class_uses($class);
+
+    foreach ($traits as $trait) {
+      $this->collectTraits($trait, $traits);
+    }
+
+    $all_traits += $traits;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/FixerInterface.php b/web/modules/contrib/drupalmoduleupgrader/src/FixerInterface.php
new file mode 100644 (file)
index 0000000..16427f5
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\FixerInterface.
+ */
+
+namespace Drupal\drupalmoduleupgrader;
+
+use Drupal\Core\Executable\ExecutableInterface;
+
+/**
+ * Interface implemented by all fixer plugins, which do small, isolated
+ * modifications to a code base. They're basically PHP_CodeSniffer fixers
+ * on steroids.
+ */
+interface FixerInterface extends ExecutableInterface {
+
+  /**
+   * Sets the target module to operate on.
+   *
+   * @param \Drupal\drupalmoduleupgrader\TargetInterface $target
+   */
+  public function setTarget(TargetInterface $target);
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/IOException.php b/web/modules/contrib/drupalmoduleupgrader/src/IOException.php
new file mode 100644 (file)
index 0000000..4fca723
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader;
+
+/**
+ * Exception thrown when an unexpected condition is encountered during an I/O
+ * operation (opening or saving a source file).
+ */
+class IOException extends \Exception {}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/IndexerBase.php b/web/modules/contrib/drupalmoduleupgrader/src/IndexerBase.php
new file mode 100644 (file)
index 0000000..24cb4d4
--- /dev/null
@@ -0,0 +1,199 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader;
+
+use Drupal\Core\Database\Connection as DatabaseConnection;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Plugin\PluginBase as CorePluginBase;
+use Pharborist\NodeCollection;
+use Pharborist\NodeInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Base class for indexers.
+ */
+abstract class IndexerBase extends CorePluginBase implements IndexerInterface, ContainerFactoryPluginInterface {
+
+  /**
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $db;
+
+  /**
+   * @var TargetInterface
+   */
+  protected $target;
+
+  /**
+   * @var string
+   */
+  protected $table;
+
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, DatabaseConnection $db, TargetInterface $target = NULL) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->db = $db;
+
+    if ($target) {
+      $this->bind($target);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('database')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function bind(TargetInterface $module) {
+    $this->target = $module;
+    $this->table = $module->id() . '__' . $this->getPluginId();
+
+    $schema = $this->db->schema();
+    if ($schema->tableExists($this->table)) {
+      $this->clear();
+    }
+    else {
+      $schema->createTable($this->table, [ 'fields' => $this->getFields() ]);
+    }
+    $this->build();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    /** @var \Symfony\Component\Finder\SplFileInfo $file */
+    foreach ($this->target->getFinder() as $file) {
+      $this->addFile($file->getPathname());
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function clear() {
+    $this->db->truncate($this->table)->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function destroy() {
+    $this->db->schema()->dropTable($this->table);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function has($identifier) {
+    return (boolean) $this->getQuery()
+      ->condition('id', $identifier)
+      ->countQuery()
+      ->execute()
+      ->fetchField();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasAny(array $identifiers) {
+    return $this->has($identifiers);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasAll(array $identifiers) {
+    $count = $this->getQuery()
+      ->condition('id', $identifiers)
+      ->countQuery()
+      ->execute()
+      ->fetchField();
+
+    return ($count == sizeof(array_unique($identifiers)));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function add(NodeInterface $node) {
+    $this->db
+      ->insert($this->table)
+      ->fields([
+        'id' => (string) $node->getName(),
+        'file' => $node->getFilename(),
+      ])
+      ->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteFile($path) {
+    $this->db
+      ->delete($this->table)
+      ->condition('file', $path)
+      ->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function delete($identifier) {
+    $this->db
+      ->delete($this->table)
+      ->condition('id', $identifier)
+      ->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMultiple(array $identifiers) {
+    return new NodeCollection(array_filter(array_map([ $this, 'get' ], $identifiers)));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAll() {
+    return $this->getMultiple($this->getQuery(['id'])->distinct()->execute()->fetchCol());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFields() {
+    return [
+      'id' => [
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+      ],
+      'file' => [
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+      ],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQuery(array $fields = []) {
+    return $this->db
+      ->select($this->table)
+      ->fields($this->table, $fields);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/IndexerExecutionInterface.php b/web/modules/contrib/drupalmoduleupgrader/src/IndexerExecutionInterface.php
new file mode 100644 (file)
index 0000000..224c5e0
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader;
+
+interface IndexerExecutionInterface {
+
+  /**
+   * Returns if the specified index object can be evaluated and executed safely.
+   *
+   * @param string $id
+   *  The object identifier.
+   *
+   * @return boolean
+   */
+  public function hasExecutable($id);
+
+  public function execute($id, array $arguments = []);
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/IndexerInterface.php b/web/modules/contrib/drupalmoduleupgrader/src/IndexerInterface.php
new file mode 100644 (file)
index 0000000..93ce474
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader;
+
+use Pharborist\NodeInterface;
+
+/**
+ * Interface for plugins which can scan a target module to collect information
+ * about what it contains. Indexers are always run before other plugin types,
+ * and all available indexers are always run. All information collected by
+ * indexers is available to the other plugin types via TargetInterface's
+ * getIndexer() method.
+ */
+interface IndexerInterface {
+
+  public function bind(TargetInterface $module);
+
+  public function build();
+
+  public function clear();
+
+  public function destroy();
+
+  public function has($identifier);
+
+  public function hasAny(array $identifiers);
+
+  public function hasAll(array $identifiers);
+
+  public function addFile($path);
+
+  public function add(NodeInterface $node);
+
+  public function deleteFile($path);
+
+  public function delete($identifier);
+
+  public function get($identifier);
+
+  public function getMultiple(array $identifiers);
+
+  public function getAll();
+
+  public function getFields();
+
+  public function getQuery(array $fields = []);
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/IndexerUsageInterface.php b/web/modules/contrib/drupalmoduleupgrader/src/IndexerUsageInterface.php
new file mode 100644 (file)
index 0000000..ca4a7be
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader;
+
+interface IndexerUsageInterface {
+
+  public function getUsages($identifier);
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Issue.php b/web/modules/contrib/drupalmoduleupgrader/src/Issue.php
new file mode 100644 (file)
index 0000000..77c41a4
--- /dev/null
@@ -0,0 +1,231 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader;
+
+use cebe\markdown\Markdown;
+use Drupal\Component\Plugin\PluginInspectionInterface;
+use Pharborist\Node;
+
+class Issue implements IssueInterface {
+
+  /**
+   * @var TargetInterface
+   */
+  protected $target;
+
+  /**
+   * @var string
+   */
+  protected $title;
+
+  /**
+   * @var string
+   */
+  protected $summary;
+
+  /**
+   * @var array
+   */
+  protected $documentation = [];
+
+  /**
+   * @var array
+   */
+  protected $violations = [];
+
+  /**
+   * @var AnalyzerInterface[]
+   */
+  protected $detectors = [];
+
+  /**
+   * @var mixed[]
+   */
+  protected $tags = [];
+
+  /**
+   * @var array[]
+   */
+  protected $fixes = [];
+
+  /**
+   * @var \cebe\markdown\Markdown
+   */
+  protected $parser;
+
+  public function __construct(Target $target, $title, $summary = NULL) {
+    $this->target = $target;
+    $this->setTitle($title);
+
+    if (isset($summary)) {
+      $this->setSummary($summary);
+    }
+
+    $this->parser = new Markdown();
+    $this->parser->html5 = TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTitle() {
+    return $this->parser->parseParagraph($this->title);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setTitle($title) {
+    $this->title = (string) $title;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSummary() {
+    return $this->parser->parse($this->summary);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setSummary($summary) {
+    $this->summary = (string) $summary;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addDocumentation($url, $title) {
+    $this->documentation[] = [
+      'url' => $url,
+      'title' => $this->parser->parseParagraph($title),
+    ];
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDocumentation() {
+    return $this->documentation;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addAffectedFile($file, AnalyzerInterface $detector) {
+    if (empty($this->violations[$file])) {
+      $this->violations[$file] = [];
+    }
+    $this->addDetector($detector);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addViolation(Node $node, AnalyzerInterface $detector) {
+    $file = $node->getFilename();
+    if ($file) {
+      $this->violations[$file][] = [
+        'line_number' => $node->getLineNumber(),
+      ];
+    }
+    else {
+      throw new \DomainException('Cannot record an issue violation from a detached node.');
+    }
+    $this->addDetector($detector);
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getViolations() {
+    $return_violations = [];
+
+    foreach ($this->violations as $file => $file_violations) {
+      if ($file_violations) {
+        foreach ($file_violations as $violation) {
+          $violation['file'] = $file;
+          $return_violations[] = $violation;
+        }
+      }
+      else {
+        $return_violations[] = ['file' => $file];
+      }
+    }
+
+    return $return_violations;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDetectors() {
+    return array_unique($this->detectors);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasTag($tag) {
+    return array_key_exists($tag, $this->tags);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTag($tag) {
+    return $this->tags[$tag];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setTag($tag, $value) {
+    $this->tags[$tag] = $value;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function clearTag($tag) {
+    unset($this->tags[$tag]);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFixes() {
+    return $this->fixes;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addFix($fixer_id, array $configuration = []) {
+    $this->fixes[] = array_merge($configuration, ['_plugin_id' => $fixer_id]);
+    $this->setTag('fixable', TRUE);
+    return $this;
+  }
+
+  /**
+   * Stores a reference to an issue detector, if we don't already know about it,
+   * for use by getDetectors().
+   *
+   * @param AnalyzerInterface $detector
+   */
+  protected function addDetector(AnalyzerInterface $detector) {
+    if ($detector instanceof PluginInspectionInterface) {
+      $this->detectors[] = $detector->getPluginId();
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/IssueInterface.php b/web/modules/contrib/drupalmoduleupgrader/src/IssueInterface.php
new file mode 100644 (file)
index 0000000..1661b1e
--- /dev/null
@@ -0,0 +1,166 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader;
+
+use Pharborist\Node;
+
+interface IssueInterface {
+
+  /**
+   * Returns the title of the issue.
+   *
+   * @return string
+   */
+  public function getTitle();
+
+  /**
+   * Sets the title of the issue.
+   *
+   * @param string $title
+   *
+   * @return $this
+   */
+  public function setTitle($title);
+
+  /**
+   * Returns the issue summary.
+   *
+   * @return string
+   */
+  public function getSummary();
+
+  /**
+   * Sets the issue summary.
+   *
+   * @param string $summary
+   *
+   * @return $this
+   */
+  public function setSummary($summary);
+
+  /**
+   * Adds a piece of documentation relevant to the issue.
+   *
+   * @param string $url
+   *  The documentation's full URL.
+   * @param string $title
+   *  The documentation's displayed title.
+   *
+   * @return $this
+   */
+  public function addDocumentation($url, $title);
+
+  /**
+   * Returns all documentation as an array of arrays, each containing 'url'
+   * and 'title' keys.
+   *
+   * @return array
+   */
+  public function getDocumentation();
+
+  /**
+   * Marks a particular file as being affected by this issue.
+   *
+   * @param string $file
+   *  The path of the affected file.
+   * @param \Drupal\drupalmoduleupgrader\AnalyzerInterface $detector
+   *  The plugin which detected the problem.
+   *
+   * @return $this
+   */
+  public function addAffectedFile($file, AnalyzerInterface $detector);
+
+  /**
+   * Flags a single violation of this issue in a particular syntax node.
+   *
+   * @param \Pharborist\Node $node
+   *  The offending syntax tree node.
+   * @param \Drupal\drupalmoduleupgrader\AnalyzerInterface $detector
+   *  The plugin which detected the violation.
+   *
+   * @return $this
+   */
+  public function addViolation(Node $node, AnalyzerInterface $detector);
+
+  /**
+   * Returns all violations as an array of arrays, each of which has a 'file' key
+   * (required), and an optional 'line_number' key.
+   *
+   * @return array
+   */
+  public function getViolations();
+
+  /**
+   * Returns the fully qualified names of every plugin which detected violations,
+   * as set by addAffectedFile() and addViolation().
+   *
+   * @return string[]
+   */
+  public function getDetectors();
+
+  /**
+   * Returns if a tag is set on the issue.
+   *
+   * @param string $tag
+   *  The tag's name.
+   *
+   * @return boolean
+   */
+  public function hasTag($tag);
+
+  /**
+   * Returns the value set for a tag. The tag value can be anything; the
+   * meaning of the value depends on the tag.
+   *
+   * @param string $tag
+   *  The tag's name.
+   *
+   * @return mixed
+   */
+  public function getTag($tag);
+
+  /**
+   * Sets the value for a tag. Any existing value for the tag will be
+   * blown away.
+   *
+   * @param string $tag
+   *  The tag's name.
+   * @param mixed $value
+   *  The tag value. Can be anything.
+   *
+   * @return $this
+   */
+  public function setTag($tag, $value);
+
+  /**
+   * Clears all values for a tag.
+   *
+   * @param string $tag
+   *  The tag's name.
+   *
+   * @return $this
+   */
+  public function clearTag($tag);
+
+  /**
+   * Gets all fixes queued for this issue. Each fix will be an array with at
+   * least a _plugin_id element, containing the plugin ID of the fixer to use.
+   * Everything else will be given to the fixer as configuration.
+   *
+   * @return array[]
+   */
+  public function getFixes();
+
+  /**
+   * Adds a fix for this issue.
+   *
+   * @param string $fixer_id
+   *  The plugin ID of the fixer to use.
+   * @param array $configuration
+   *  Optional configuration for the fixer.
+   *
+   * @return $this
+   */
+  public function addFix($fixer_id, array $configuration = []);
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/DB.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/DB.php
new file mode 100644 (file)
index 0000000..349be46
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Analyzer;
+
+use Drupal\drupalmoduleupgrader\AnalyzerBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Types\StringNode;
+
+/**
+ * @Analyzer(
+ *  id = "_db",
+ *  message = @Translation("Certain database tables have been removed."),
+ *  tags = {
+ *    "category" = { "db" }
+    },
+ *  deriver = "\Drupal\drupalmoduleupgrader\Plugin\DMU\Analyzer\DBDeriver"
+ * )
+ */
+class DB extends AnalyzerBase {
+
+  /**
+   * Tables which will cause the function call to be commented out.
+   *
+   * @var string[]
+   */
+  protected static $forbiddenTables = ['variable'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function analyze(TargetInterface $target) {
+    $function_calls = $target
+      ->getIndexer('function_call')
+      ->get($this->pluginDefinition['function'] ?: $this->getPluginId())
+      ->filter(function(FunctionCallNode $function_call) {
+        $arguments = $function_call->getArguments();
+        return $arguments[0] instanceof StringNode && in_array($arguments[0]->toValue(), self::$forbiddenTables);
+      });
+
+    $issues = [];
+    if ($function_calls->count() > 0) {
+      $issue = $this->buildIssue($target);
+      $function_calls->each(function(FunctionCallNode $function_call) use ($issue) {
+        $issue->addViolation($function_call, $this);
+      });
+      $issues[] = $issue;
+    }
+
+    return $issues;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/DBDeriver.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/DBDeriver.php
new file mode 100644 (file)
index 0000000..03401cd
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Analyzer;
+
+use Drupal\drupalmoduleupgrader\DeriverBase;
+
+/**
+ * Builds derivative definitions for the _db plugin.
+ */
+class DBDeriver extends DeriverBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinitions($base_definition) {
+    $derivatives = [];
+
+    $functions = [
+      'db_select', 'db_insert', 'db_update', 'db_merge', 'db_truncate',
+    ];
+
+    foreach ($functions as $function) {
+      $derivative = $base_definition;
+
+      $derivative['function'] = $function;
+      $derivative['description'] = $this->t('Checks for calls to @function() that refer to legacy tables.', [
+        '@function' => $function,
+      ]);
+
+      $derivatives[$function] = $derivative;
+    }
+
+    return $derivatives;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/FlagHook.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/FlagHook.php
new file mode 100644 (file)
index 0000000..08882c7
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Analyzer;
+
+use Drupal\drupalmoduleupgrader\AnalyzerBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+
+/**
+ * @Analyzer(
+ *  id = "_flag_hook",
+ *  deriver = "\Drupal\drupalmoduleupgrader\Plugin\DMU\Analyzer\FlagHookDeriver"
+ * )
+ */
+class FlagHook extends AnalyzerBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function analyze(TargetInterface $target) {
+    $hook = 'hook_' . $this->pluginDefinition['hook'];
+    $indexer = $target->getIndexer('function');
+
+    if ($indexer->has($hook)) {
+      return [$this->buildIssue($target)->addViolation($indexer->get($hook), $this)];
+    }
+    else {
+      return [];
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/FlagHookDeriver.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/FlagHookDeriver.php
new file mode 100644 (file)
index 0000000..09a6716
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Analyzer;
+
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\drupalmoduleupgrader\DeriverBase;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+class FlagHookDeriver extends DeriverBase {
+
+  /**
+   * @var array
+   */
+  protected $config;
+
+  public function __construct(TranslationInterface $translator, array $config) {
+    parent::__construct($translator);
+    $this->config = $config;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, $base_plugin_id) {
+    return new static(
+      $container->get('string_translation'),
+      $container->get('config.factory')->get('drupalmoduleupgrader.hooks')->get('definitions')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinitions($base_definition) {
+    $derivatives = [];
+
+    foreach ($this->config as $key => $info) {
+      if (empty($info['hook'])) {
+        $info['hook'] = [$key];
+      }
+
+      foreach ($info['hook'] as $hook) {
+        $variables = ['@hook' => 'hook_' . $hook . '()'];
+
+        $derivative = array_merge($base_definition, $info);
+        $derivative['hook'] = $hook;
+        $derivative['message'] = $this->t($info['message'], $variables);
+        $derivative['description'] = $this->t('Analyzes implementations of @hook.', $variables);
+        foreach ($derivative['documentation'] as &$doc) {
+          $doc['title'] = $this->t($doc['title'], $variables);
+        }
+
+        $derivatives[$hook] = $derivative;
+      }
+    }
+
+    return $derivatives;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/FunctionCall.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/FunctionCall.php
new file mode 100644 (file)
index 0000000..c022d9b
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Analyzer;
+
+use Drupal\drupalmoduleupgrader\AnalyzerBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+
+/**
+ * @Analyzer(
+ *  id = "_function_call",
+ *  deriver = "Drupal\drupalmoduleupgrader\Plugin\DMU\Analyzer\FunctionCallDeriver"
+ * )
+ */
+class FunctionCall extends AnalyzerBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function analyze(TargetInterface $target) {
+    $indexer = $target->getIndexer('function_call');
+    $issues = [];
+
+    if ($indexer->has($this->pluginDefinition['function'])) {
+      $issue = $this->buildIssue($target);
+
+      $indexer
+        ->get($this->pluginDefinition['function'])
+        ->each(function(FunctionCallNode $function_call) use ($issue) {
+          $issue->addViolation($function_call, $this);
+        });
+
+      $issues[] = $issue;
+    }
+
+    return $issues;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/FunctionCallDeriver.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/FunctionCallDeriver.php
new file mode 100644 (file)
index 0000000..77400c0
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Analyzer;
+
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\drupalmoduleupgrader\DeriverBase;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+class FunctionCallDeriver extends DeriverBase {
+
+  /**
+   * @var array
+   */
+  protected $config;
+
+  public function __construct(TranslationInterface $translator, array $config) {
+    parent::__construct($translator);
+    $this->config = $config;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, $base_plugin_id) {
+    return new static(
+      $container->get('string_translation'),
+      $container->get('config.factory')->get('drupalmoduleupgrader.functions')->get('definitions')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinitions($base_definition) {
+    $derivatives = [];
+
+    foreach ($this->config as $key => $info) {
+      // $key can either be the name of a single function, or an arbitrary string
+      // identifying a group of functions to handle.
+      if (empty($info['functions'])) {
+        $info['functions'] = [$key];
+      }
+
+      foreach ($info['functions'] as $function) {
+        $variables = ['@function' => $function . '()'];
+
+        $derivative = array_merge($base_definition, $info);
+        $derivative['function'] = $function;
+        $derivative['message'] = $this->t($derivative['message'], $variables);
+        $derivative['description'] = $this->t('Analyzes calls to @function.', $variables);
+        foreach ($derivative['documentation'] as &$doc) {
+          $doc['title'] = $this->t($doc['title'], $variables);
+        }
+        unset($derivative['functions']);
+
+        $derivatives[$function] = $derivative;
+      }
+    }
+
+    return $derivatives;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/Grep.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/Grep.php
new file mode 100644 (file)
index 0000000..ce065e1
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Analyzer;
+
+use Drupal\drupalmoduleupgrader\AnalyzerBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+
+/**
+ * @Analyzer(
+ *  id = "grep",
+ *  description = @Translation("Searches for and replaces commonly-used code that has changed in Drupal 8."),
+ *  documentation = {
+ *    {
+ *      "url" = "https://www.drupal.org/node/2324935",
+ *      "title" = @Translation("The global theme variables have been replaced by an ActiveTheme object")
+ *    }
+ *  },
+ *  tags = {
+ *    "category" = { "misc" },
+ *    "error_level" = "warning"
+ *  },
+ *  message = @Translation("Many common functions, shared variables, and constants have been renamed.")
+ * )
+ */
+class Grep extends AnalyzerBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function analyze(TargetInterface $target) {
+    // It's too odious to try and grep through the entire module for the various
+    // targets. So we'll just unconditionally flag the issue.
+    return [$this->buildIssue($target)];
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/HookFormAlter.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/HookFormAlter.php
new file mode 100644 (file)
index 0000000..bfcea8a
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Analyzer;
+
+use Drupal\drupalmoduleupgrader\AnalyzerBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Filter;
+use Pharborist\Functions\FunctionDeclarationNode;
+
+/**
+ * @Analyzer(
+ *  id = "hook_form_alter",
+ *  description = @Translation("Checks for outdated hook_form_alter() implementations."),
+ *  documentation = {
+ *    {
+ *      "url" = "https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Form%21form.api.php/function/hook_form_alter/8",
+ *      "title" = @Translation("`hook_form_alter()` documentation")
+ *    }
+ *  },
+ *  tags = {
+ *    "category" = { "form" }
+ *  },
+ *  message = @Translation("The signature of hook_form_alter() has changed in Drupal 8.")
+ * )
+ */
+class HookFormAlter extends AnalyzerBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function analyze(TargetInterface $target) {
+    $violations = [];
+
+    $indexer = $target->getIndexer('function');
+    if ($indexer->has('hook_form_alter')) {
+      $violations[] = $indexer->get('hook_form_alter');
+    }
+
+    $id = $target->id() . '_form_%_alter';
+    // Until kernel tests are run in PHPUnit, we need to check for
+    // the existence of db_like().
+    if (function_exists('db_like')) {
+      $id = db_like($id);
+    }
+    $alter_hooks = $target
+      ->getIndexer('function')
+      ->getQuery()
+      ->condition('id', $id, 'LIKE')
+      ->execute();
+
+    foreach ($alter_hooks as $alter_hook) {
+      $violations[] = $target
+        ->open($alter_hook->file)
+        ->find(Filter::isFunction($alter_hook->id));
+    }
+
+    $issues = [];
+
+    if ($violations) {
+      $issue = $this->buildIssue($target);
+      array_walk($violations, function(FunctionDeclarationNode $function) use ($issue) {
+        $issue->addViolation($function, $this);
+      });
+      $issues[] = $issue;
+    }
+
+    return $issues;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/HookPermission.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/HookPermission.php
new file mode 100644 (file)
index 0000000..c012587
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Analyzer;
+
+use Drupal\drupalmoduleupgrader\AnalyzerBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+
+/**
+ * @Analyzer(
+ *  id = "hook_permission",
+ *  description = @Translation("Analyzes implementations of hook_permission()."),
+ *  documentation = {
+ *    {
+ *      "url" = "https://www.drupal.org/node/2311427",
+ *      "title" = @Translation("Defining permissions in `MODULE.permissions.yml`")
+ *    }
+ *  },
+ *  tags = {
+ *    "category" = { "system", "user" },
+ *    "error_level" = "warning"
+ *  },
+ *  hook = "hook_permission",
+ *  message = @Translation("Static permissions are now defined in `MODULE.permissions.yml`.")
+ * )
+ */
+class HookPermission extends AnalyzerBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function analyze(TargetInterface $target) {
+    $issues = [];
+    $indexer = $target->getIndexer('function');
+
+    if ($indexer->hasExecutable('hook_permission')) {
+      $issues[] = $this
+        ->buildIssue($target)
+        ->addViolation($indexer->get('hook_permission'), $this)
+        ->addFix('hook_to_YAML', [
+          'hook' => 'permission',
+          'destination' => '~/' . $target->id() . '.permissions.yml',
+        ]);
+    }
+
+    return $issues;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/HookUninstall.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/HookUninstall.php
new file mode 100644 (file)
index 0000000..a6ff120
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Analyzer;
+
+use Drupal\drupalmoduleupgrader\AnalyzerBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Filter;
+use Pharborist\Functions\FunctionCallNode;
+
+/**
+ * @Analyzer(
+ *  id = "hook_uninstall",
+ *  description = @Translation("Removes variable_del() calls from hook_uninstall()."),
+ *  message = @Translation("Default configuration is deleted automatically."),
+ *  tags = {
+ *    "category" = { "config" },
+ *    "error_level" = "warning"
+ *  },
+ *  hook = "hook_uninstall"
+ * )
+ */
+class HookUninstall extends AnalyzerBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function analyze(TargetInterface $target) {
+    $indexer = $target->getIndexer('function');
+    $issues = [];
+
+    if ($indexer->has('hook_uninstall')) {
+      /** @var \Pharborist\NodeCollection $variable_del */
+      $variable_del = $indexer->get('hook_uninstall')->find(Filter::isFunctionCall('variable_del'));
+
+      if (sizeof($variable_del) > 0) {
+        $issue = $this->buildIssue($target);
+        $variable_del->each(function(FunctionCallNode $function_call) use ($issue) {
+          $issue->addViolation($function_call, $this);
+        });
+        $issues[] = $issue;
+      }
+    }
+
+    return $issues;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/InfoFile.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/InfoFile.php
new file mode 100644 (file)
index 0000000..1e21e95
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Analyzer;
+
+use Drupal\drupalmoduleupgrader\AnalyzerBase;
+use Drupal\drupalmoduleupgrader\Issue;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+
+/**
+ * @Analyzer(
+ *  id = "info",
+ *  description = @Translation("Analyzes Drupal 7 info files."),
+ *  documentation = {
+ *    {
+ *      "url" = "https://www.drupal.org/node/1935708",
+ *      "title" = @Translation("`.info` files are now `.info.yml` files")
+ *    }
+ *  }
+ * )
+ */
+class InfoFile extends AnalyzerBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function analyze(TargetInterface $target) {
+    $issues = [];
+    $info_file = $target->getPath('.info');
+    if (! file_exists($info_file)) {
+      return $issues;
+    }
+
+    $info = \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\InfoToYAML::parseInfo($info_file);
+    if (empty($info)) {
+      throw new \RuntimeException('Cannot parse info file ' . $info_file);
+    }
+
+    $doc = $this->pluginDefinition['documentation'][0];
+    if ($info['core'] != '8.x') {
+      $issues['core'] = new Issue($target, $this->t("Module info files' `core` key must have a value of `8.x`."));
+      $issues['core']->addDocumentation($doc['url'], $doc['title']);
+    }
+    if (empty($info['type'])) {
+      $issues['type'] = new Issue($target, $this->t('Info files must contain a `type` key.'));
+      $issues['type']->addDocumentation($doc['url'] . '#type', $doc['title']);
+    }
+    if (isset($info['dependencies'])) {
+      $issues['dependencies'] = new Issue($target, $this->t('Many common dependencies have moved into core.'));
+      $issues['dependencies']->addDocumentation($doc['url'], $doc['title']);
+    }
+    if (isset($info['files'])) {
+      $issues['files'] = new Issue($target, $this->t('Modules no longer declare classes in their info file.'));
+      $issues['files']->addDocumentation($doc['url'] . '#files', $doc['title']);
+    }
+    if (isset($info['configure'])) {
+      $issues['configure'] = new Issue($target, $this->t("Module info files' `configure` key must be a route name, not a path."));
+      $issues['configure']->addDocumentation($doc['url'] . '#configure', $doc['title']);
+    }
+
+    /** @var \Drupal\drupalmoduleupgrader\IssueInterface $issue */
+    foreach ($issues as $key => $issue) {
+      $issue->setTag('error_level', 'error');
+      $issue->setTag('category', ['info']);
+      $issue->addAffectedFile($info_file, $this);
+    }
+
+    return $issues;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/PSR4.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/PSR4.php
new file mode 100644 (file)
index 0000000..d18a929
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Analyzer;
+
+use Drupal\drupalmoduleupgrader\AnalyzerBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+
+/**
+ * @Analyzer(
+ *  id = "PSR4",
+ *  description = @Translation("Checks if the module defines any classes that need to be moved into a PSR-4 structure."),
+ *  documentation = {
+ *    {
+ *      "url" = "https://www.drupal.org/node/2246699",
+ *      "title" = @Translation("PSR-4 compatible class loader in Drupal core")
+ *    }
+ *  },
+ *  tags = {
+ *    "category" = { "misc", "system" }
+ *  },
+ *  message = @Translation("Classes must be PSR-4 compliant.")
+ * )
+ */
+class PSR4 extends AnalyzerBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function analyze(TargetInterface $target) {
+    $issues = [];
+    $class_count = $target
+      ->getIndexer('class')
+      ->getQuery()
+      ->condition('type', 'Pharborist\Objects\ClassNode')
+      ->countQuery()
+      ->execute()
+      ->fetchField();
+
+    if ($class_count > 0) {
+      $issues[] = $this->buildIssue($target);
+    }
+
+    return $issues;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/Tests.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/Tests.php
new file mode 100644 (file)
index 0000000..611c445
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Analyzer;
+
+use Drupal\drupalmoduleupgrader\AnalyzerBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+
+/**
+ * @Analyzer(
+ *  id = "tests",
+ *  description = @Translation("Checks for test classes that need to be rejiggered for Drupal 8."),
+ *  documentation = {
+ *    {
+ *      "url" = "https://www.drupal.org/node/1543796",
+ *      "title" = @Translation("Namespacing of automated tests has changed")
+ *    },
+ *    {
+ *      "url" = "https://www.drupal.org/node/2301125",
+ *      "title" = @Translation("<code>getInfo()</code> in test classes replaced by doc comments")
+ *    },
+ *    {
+ *      "url" = "https://www.drupal.org/node/1710766",
+ *      "title" = @Translation("Test classes should define a <code>$modules</code> property declaring dependencies")
+ *    },
+ *    {
+ *      "url" = "https://www.drupal.org/node/1911318",
+ *      "title" = @Translation("SimpleTest tests now use empty &quot;testing&quot; profile by default")
+ *    },
+ *    {
+ *      "url" = "https://www.drupal.org/node/1829160",
+ *      "title" = @Translation("New <code>KernelTestBase</code> class for API-level integration tests")
+ *    },
+ *    {
+ *      "url" = "https://www.drupal.org/node/2012184",
+ *      "title" = @Translation("PHPUnit added to Drupal core")
+ *    }
+ *  },
+ *  tags = {
+ *    "category" = { "misc", "system" }
+ *  },
+ *  message = @Translation("Automated web tests must be in a PSR-4 namespace, and unit tests must be converted to PHPUnit.")
+ * )
+ */
+class Tests extends AnalyzerBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function analyze(TargetInterface $target) {
+    $issues = [];
+    $total = 0;
+    $total += $target->getIndexer('class')->getQuery()->condition('parent', 'DrupalWebTestCase')->countQuery()->execute();
+    $total += $target->getIndexer('class')->getQuery()->condition('parent', 'DrupalUnitTestCase')->countQuery()->execute();
+    $total += $target->getIndexer('class')->getQuery()->condition('parent', 'DrupalTestBase')->countQuery()->execute();
+
+    if ($total) {
+      $issues[] = $this->buildIssue($target);
+    }
+
+    return $issues;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Blocks.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Blocks.php
new file mode 100644 (file)
index 0000000..8edaba5
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Drupal\drupalmoduleupgrader\Utility\StringTransformTrait;
+
+/**
+ * @Converter(
+ *  id = "blocks",
+ *  description = @Translation("Converts Drupal 7 blocks to plugins."),
+ *  hook = {
+ *    "hook_block_configure",
+ *    "hook_block_info",
+ *    "hook_block_save",
+ *    "hook_block_view"
+ *  },
+ *  fixme = @Translation("hook_!hook is gone in Drupal 8.
+
+It has been left here by the Drupal Module Upgrader so that you can move its
+logic into the appropriate block plugins, which should be in the
+src/Plugin/Block directory. Once all logic is moved into the plugins, delete
+this hook."),
+ *  documentation = {
+ *    "https://www.drupal.org/node/1880620"
+ *  }
+ * )
+ */
+class Blocks extends ConverterBase {
+
+  use StringTransformTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    try {
+      $blocks = $this->executeHook($target, 'block_info');
+    }
+    catch (\LogicException $e) {
+      $this->log->warning($e->getMessage(), [
+        'target' => $target->id(),
+        'hook' => $this->pluginDefinition['hook'],
+      ]);
+      return;
+    }
+
+    $indexer = $target->getIndexer('function');
+
+    foreach ($blocks as $id => $info) {
+      // Render the block plugin's shell.
+      $render = [
+        '#theme' => 'dmu_block',
+        '#module' => $target->id(),
+        '#class' => $this->toTitleCase(preg_replace('/[^a-zA-Z0-9_]+/', '_', $id)),
+        '#block_id' => $id,
+        '#block_label' => $info['info'],
+        '#configurable' => $indexer->has('block_configure'),
+      ];
+      $this->writeClass($target, $this->parse($render));
+    }
+
+    // Slap a FIXME on hook_block_info(), and on other block hooks which
+    // may or may not exist.
+    $this->addFixMe($target, 'block_info');
+
+    if ($indexer->has('hook_block_view')) {
+      $this->addFixMe($target, 'block_view');
+    }
+    if ($indexer->has('hook_block_save')) {
+      $this->addFixMe($target, 'block_save');
+    }
+    if ($indexer->has('hook_block_configure')) {
+      $this->addFixMe($target, 'block_configure');
+    }
+  }
+
+  /**
+   * Slaps a translated FIXME notice above a block-related hook.
+   *
+   * @param TargetInterface $target
+   *  The target module.
+   * @param string $hook
+   *  The hook to put the FIXME on. It's up to the calling code to ensure
+   *  that the hook actually exists.
+   */
+  private function addFixMe(TargetInterface $target, $hook) {
+    $variables = ['!hook' => $hook];
+
+    $function = $target
+      ->getIndexer('function')
+      ->get('hook_' . $hook)
+      ->setDocComment($this->buildFixMe(NULL, $variables, self::DOC_COMMENT));
+
+    $target->save($function);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/EntityHooks.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/EntityHooks.php
new file mode 100644 (file)
index 0000000..ee8a6f6
--- /dev/null
@@ -0,0 +1,104 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Psr\Log\LoggerInterface;
+
+/**
+ * @Converter(
+ *  id = "entity_hooks",
+ *  description = @Translation("Rewrites various entity-related hooks."),
+ *  hook = {
+ *    "hook_comment_delete",
+ *    "hook_comment_insert",
+ *    "hook_comment_presave",
+ *    "hook_comment_update",
+ *    "hook_node_access",
+ *    "hook_node_access_records",
+ *    "hook_node_access_records_alter",
+ *    "hook_node_delete",
+ *    "hook_node_grants",
+ *    "hook_node_grants_alter",
+ *    "hook_node_insert",
+ *    "hook_node_presave",
+ *    "hook_node_revision_delete",
+ *    "hook_node_search_result",
+ *    "hook_node_submit",
+ *    "hook_node_update",
+ *    "hook_node_update_index",
+ *    "hook_node_validate",
+ *    "hook_taxonomy_term_delete",
+ *    "hook_taxonomy_term_insert",
+ *    "hook_taxonomy_term_presave",
+ *    "hook_taxonomy_term_update",
+ *    "hook_user_delete",
+ *    "hook_user_logout"
+ *  },
+ *  dependencies = { "plugin.manager.drupalmoduleupgrader.rewriter" }
+ * )
+ */
+class EntityHooks extends ConverterBase {
+
+  /**
+   * @var \Drupal\Component\Plugin\PluginManagerInterface
+   */
+  protected $rewriters;
+
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, TranslationInterface $translator, LoggerInterface $log, PluginManagerInterface $rewriters) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $translator, $log);
+    $this->rewriters = $rewriters;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target, $hook = NULL, $index = 0, $rewriter_id = NULL) {
+    $indexer = $target->getIndexer('function');
+
+    if (isset($hook)) {
+      if ($indexer->has($hook)) {
+        if (empty($rewriter_id)) {
+          // Extract the entity type from the hook (e.g. 'hook_node_delete').
+          preg_match('/^hook_(.+)_[a-z]+$/', $hook, $matches);
+          $rewriter_id = '_rewriter:' . $matches[1];
+        }
+        $rewriter = $this->rewriters->createInstance($rewriter_id);
+        $this->rewriteFunction($rewriter, $indexer->get($hook)->getParameterAtIndex($index), $target);
+      }
+    }
+    else {
+      $this->convert($target, 'hook_comment_delete');
+      $this->convert($target, 'hook_comment_insert');
+      $this->convert($target, 'hook_comment_presave');
+      $this->convert($target, 'hook_comment_update');
+      $this->convert($target, 'hook_node_access');
+      $this->convert($target, 'hook_node_access', 2, '_rewriter:account');
+      $this->convert($target, 'hook_node_access_records', 0, '_rewriter:node');
+      $this->convert($target, 'hook_node_access_records_alter', 1, '_rewriter:node');
+      $this->convert($target, 'hook_node_delete');
+      $this->convert($target, 'hook_node_grants', 0, '_rewriter:account');
+      $this->convert($target, 'hook_node_grants_alter', 1, '_rewriter:account');
+      $this->convert($target, 'hook_node_insert');
+      $this->convert($target, 'hook_node_presave');
+      $this->convert($target, 'hook_node_revision_delete');
+      $this->convert($target, 'hook_node_search_result');
+      $this->convert($target, 'hook_node_submit');
+      $this->convert($target, 'hook_node_submit', 2, 'form_state');
+      $this->convert($target, 'hook_node_update');
+      $this->convert($target, 'hook_node_update_index');
+      $this->convert($target, 'hook_node_validate');
+      $this->convert($target, 'hook_node_validate', 2, 'form_state');
+      $this->convert($target, 'hook_taxonomy_term_delete');
+      $this->convert($target, 'hook_taxonomy_term_insert');
+      $this->convert($target, 'hook_taxonomy_term_presave');
+      $this->convert($target, 'hook_taxonomy_term_update');
+      $this->convert($target, 'hook_user_delete');
+      $this->convert($target, 'hook_user_logout');
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CToolsGetPlugins.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CToolsGetPlugins.php
new file mode 100644 (file)
index 0000000..baf5196
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+use Pharborist\Types\StringNode;
+
+/**
+ * @Converter(
+ *  id = "ctools_get_plugins",
+ *  description = @Translation("Rewrites calls to ctools_get_plugins().")
+ * )
+ */
+class CToolsGetPlugins extends FunctionCallModifier {
+
+  /**
+   * Tests if the function call can be rewritten at all, which it will be
+   * only if both arguments are strings, and the first argument is the machine
+   * name of the target module.
+   *
+   * @param \Pharborist\Functions\FunctionCallNode $call
+   *  The function call to test.
+   * @param \Drupal\drupalmoduleupgrader\TargetInterface $target
+   *  The target module.
+   *
+   * @return boolean
+   */
+  public function canRewrite(FunctionCallNode $call, TargetInterface $target) {
+    $arguments = $call->getArguments();
+    return ($arguments[0] instanceof StringNode && $arguments[0]->toValue() == $target->id() && $arguments[1] instanceof StringNode);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    if (! $this->canRewrite($call, $target)) {
+      return NULL;
+    }
+
+    $arguments = $call->getArguments();
+    $plugin_owner = $arguments[0]->toValue();
+    $plugin_type = $arguments[1]->toValue();
+
+    $services = $target->getServices();
+    $service_id = 'plugin.manager.' . $plugin_owner . '.' . $plugin_type;
+    $services->set($service_id, [
+      'class' => 'Drupal\Core\Plugin\DefaultPluginManager',
+      'arguments' => [
+        'Plugin/' . $plugin_owner . '/' . $plugin_type,
+        '@container.namespaces',
+        '@module_handler',
+        'Drupal\Component\Plugin\PluginBase',
+        'Drupal\Component\Annotation\Plugin',
+      ],
+    ]);
+    $this->writeInfo($target, 'services', [ 'services' => $services->toArray() ]);
+
+    return ClassMethodCallNode::create('\Drupal', 'service')
+      ->appendArgument($service_id)
+      ->appendMethodCall('getDefinitions');
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CToolsObjectCacheGet.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CToolsObjectCacheGet.php
new file mode 100644 (file)
index 0000000..be7762a
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+
+/**
+ * @Converter(
+ *  id = "ctools_object_cache_get",
+ *  description = @Translation("Rewrites calls to ctools_object_cache_get().")
+ * )
+ */
+class CToolsObjectCacheGet extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    return ClassMethodCallNode::create('\Drupal', 'service')
+      ->appendArgument('user.tempstore')
+      ->appendMethodCall('get')
+      ->appendArgument(clone $call->getArgumentList()->getItem(1));
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CToolsObjectCacheSet.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CToolsObjectCacheSet.php
new file mode 100644 (file)
index 0000000..b122060
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+
+/**
+ * @Converter(
+ *  id = "ctools_object_cache_set",
+ *  description = @Translation("Rewrites calls to ctools_object_cache_set().")
+ * )
+ */
+class CToolsObjectCacheSet extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $arguments = $call->getArguments()->toArray();
+    array_shift($arguments);
+
+    if (sizeof($arguments) == 3) {
+      array_pop($arguments);
+    }
+
+    return ClassMethodCallNode::create('\Drupal', 'service')
+      ->appendArgument('user.tempstore')
+      ->appendMethodCall('set')
+      ->appendArgument($arguments[0])
+      ->appendArgument($arguments[1]);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CacheGet.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CacheGet.php
new file mode 100644 (file)
index 0000000..786d465
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+
+/**
+ * @Converter(
+ *  id = "cache_get",
+ *  description = @Translation("Rewrites calls to cache_get().")
+ * )
+ */
+class CacheGet extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $arguments = $call->getArguments();
+
+    $get = ClassMethodCallNode::create('\Drupal', 'cache');
+    if (sizeof($arguments) == 2) {
+      $get->appendArgument(clone $arguments[1]);
+    }
+
+    return $get->appendMethodCall('get')->appendArgument(clone $arguments[0]);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CacheSet.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CacheSet.php
new file mode 100644 (file)
index 0000000..d539682
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+
+/**
+ * @Converter(
+ *  id = "cache_set",
+ *  description = @Translation("Rewrites calls to cache_set().")
+ * )
+ */
+class CacheSet extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $arguments = $call->getArguments();
+
+    $cache = ClassMethodCallNode::create('\Drupal', 'cache');
+    if (sizeof($arguments) > 2) {
+      $cache->appendArgument(clone $arguments[2]);
+    }
+
+    $set = $cache->appendMethodCall('set')
+      ->appendArgument(clone $arguments[0])
+      ->appendArgument(clone $arguments[1]);
+
+    // Include the expiration time, if given.
+    if (sizeof($arguments) == 4) {
+      $set->appendArgument(clone $arguments[3]);
+    }
+
+    return $set;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CommentLoad.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CommentLoad.php
new file mode 100644 (file)
index 0000000..d56484a
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+
+/**
+ * @Converter(
+ *  id = "comment_load",
+ *  description = @Translation("Rewrites calls to comment_load()."),
+ *  fixme = @Translation("comment_load() is now EntityStorageInterface::load().")
+ * )
+ */
+class CommentLoad extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $arguments = $call->getArguments();
+
+    // If there were three arguments, the call is affecting the internal
+    // comment_load() cache. Unfortunately, it's pretty much impossible to
+    // reliably determine whether or not they wanted to reset the cache,
+    // so let's just leave a FIXME.
+    if (sizeof($arguments) == 2) {
+      $this->buildFixMe('To reset the comment cache, use EntityStorageInterface::resetCache().')->insertBefore($call);
+    }
+
+    return ClassMethodCallNode::create('\Drupal', 'entityManager')
+      ->appendMethodCall('getStorage')
+      ->appendArgument('comment')
+      ->appendMethodCall('load')
+      ->appendArgument(clone $arguments[0]);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DB.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DB.php
new file mode 100644 (file)
index 0000000..3ce82a4
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Types\StringNode;
+
+/**
+ * @Converter(
+ *  id = "_db",
+ *  deriver = "\Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\DBDeriver"
+ * )
+ */
+class DB extends FunctionCallModifier {
+
+  /**
+   * Tables which will cause the function call to be commented out.
+   *
+   * @var string[]
+   */
+  protected static $forbiddenTables = ['system', 'variable'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $table = $call->getArgumentList()->getItem(0);
+    return ($table instanceof StringNode && in_array($table->toValue(), self::$forbiddenTables)) ? NULL : $call;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DBDeriver.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DBDeriver.php
new file mode 100644 (file)
index 0000000..c2bc861
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\DeriverBase;
+
+/**
+ * Builds derivative definitions for the _db plugin.
+ */
+class DBDeriver extends DeriverBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinitions($base_definition) {
+    $derivatives = [];
+
+    $functions = [
+      'db_select', 'db_insert', 'db_update', 'db_merge', 'db_delete', 'db_truncate',
+    ];
+
+    foreach ($functions as $function) {
+      $variables = [
+        '@function' => $function,
+      ];
+      $derivative = $base_definition;
+      $derivative['function'] = $function;
+      $derivative['description'] = $this->t('Disables calls to @function() which refer to legacy tables.', $variables);
+
+      $derivatives[$function] = $derivative;
+    }
+
+    return $derivatives;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/Disable.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/Disable.php
new file mode 100644 (file)
index 0000000..32a4207
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+
+/**
+ * @Converter(
+ *  id = "_disable",
+ *  deriver = "Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\DisableDeriver"
+ * )
+ */
+final class Disable extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    return NULL;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DisableDeriver.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DisableDeriver.php
new file mode 100644 (file)
index 0000000..73aa6e9
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\DeriverBase;
+
+/**
+ * Builds derivative definitions for the _disable plugin, based on a bundled configuration
+ * file. This allows us (plugin authors) to easily define which function calls can be
+ * commented out.
+ */
+class DisableDeriver extends DeriverBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinitions($base_definition) {
+    $derivatives = [];
+
+    $config = \Drupal::config('drupalmoduleupgrader.functions')->get('definitions');
+    foreach ($config as $key => $info) {
+      // Only disable functions that have been explicitly marked for disabling.
+      if (empty($info['disable'])) {
+        continue;
+      }
+
+      // $key can either be the name of a single function, or an arbitrary string
+      // identifying a group of functions to handle.
+      if (empty($info['functions'])) {
+        $info['functions'] = [$key];
+      }
+
+      foreach ($info['functions'] as $function) {
+        $derivative = $base_definition;
+        $variables = ['@function' => $function . '()'];
+
+        $derivative['function'] = $function;
+        $derivative['description'] = $this->t('Disables calls to @function().', $variables);
+        if (isset($info['fixme'])) {
+          $derivative['fixme'] = $this->t($info['fixme'], $variables);
+        }
+        $derivative['documentation'] = $info['documentation'];
+
+        $derivatives[$function] = $derivative;
+      }
+    }
+
+    return $derivatives;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DrupalGetTitle.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DrupalGetTitle.php
new file mode 100644 (file)
index 0000000..0b9b81c
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+
+/**
+ * @Converter(
+ *  id = "drupal_get_title",
+ *  description = @Translation("Rewrites calls to drupal_get_title().")
+ * )
+ */
+class DrupalGetTitle extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    return ClassMethodCallNode::create('\Drupal', 'service')
+      ->appendArgument('title_resolver')
+      ->appendMethodCall('getTitle')
+      ->appendArgument(ClassMethodCallNode::create('\Drupal', 'request'))
+      ->appendArgument(ClassMethodCallNode::create('\Drupal', 'routeMatch')->appendMethodCall('getRouteObject'));
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DrupalIsCLI.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DrupalIsCLI.php
new file mode 100644 (file)
index 0000000..6f4165a
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Parser;
+
+/**
+ * @Converter(
+ *  id = "drupal_is_cli",
+ *  description = @Translation("Rewrites calls to drupal_is_cli().")
+ * )
+ */
+class DrupalIsCLI extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    return Parser::parseExpression('(PHP_SAPI === "cli")');
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DrupalMapAssoc.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DrupalMapAssoc.php
new file mode 100644 (file)
index 0000000..157ba98
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+
+/**
+ * @Converter(
+ *  id = "drupal_map_assoc",
+ *  description = @Translation("Rewrites calls to drupal_map_assoc().")
+ * )
+ */
+class DrupalMapAssoc extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    // Change function name to array_combine().
+    $call->setName('array_combine');
+
+    // Duplicate the first $array argument twice (silly, but true).
+    // Need to clone the argument to make a copy of it, since Pharborist works
+    // on original tree elements.
+    $arguments = $call->getArguments();
+    return $call->appendArgument(clone $arguments[0]);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DrupalWriteRecord.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DrupalWriteRecord.php
new file mode 100644 (file)
index 0000000..c836a89
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+use Pharborist\Types\ArrayNode;
+use Pharborist\Types\StringNode;
+
+/**
+ * @Converter(
+ *  id = "drupal_write_record",
+ *  description = @Translation("Rewrites calls to drupal_write_record().")
+ * )
+ */
+class DrupalWriteRecord extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $rewritten = ClassMethodCallNode::create('\Drupal', 'database');
+
+    $arguments = $call->getArguments();
+    if (sizeof($arguments) == 3) {
+      $key = $arguments[2] instanceof StringNode ? ArrayNode::create([ clone $arguments[2] ]) : clone $arguments[2];
+
+      return $rewritten
+        ->appendMethodCall('merge')
+        ->appendArgument(clone $arguments[0])
+        ->appendMethodCall('fields')
+        ->appendArgument(clone $arguments[1])
+        ->appendMethodCall('key')
+        ->appendArgument($key)
+        ->appendMethodCall('execute');
+    }
+    else {
+      return $rewritten
+        ->appendMethodCall('insert')
+        ->appendArgument(clone $arguments[0])
+        ->appendMethodCall('fields')
+        ->appendArgument(clone $arguments[1])
+        ->appendMethodCall('execute');
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/EntityCreate.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/EntityCreate.php
new file mode 100644 (file)
index 0000000..4d4df13
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+
+/**
+ * @Converter(
+ *  id = "entity_create",
+ *  description = @Translation("Rewrites calls to entity_create().")
+ * )
+ */
+class EntityCreate extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $arguments = $call->getArguments();
+
+    return ClassMethodCallNode::create('\Drupal', 'entityManager')
+      ->appendMethodCall('getStorage')
+      ->appendArgument(clone $arguments[0])
+      ->appendMethodCall('create')
+      ->appendArgument(clone $arguments[1]);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/EntityGetInfo.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/EntityGetInfo.php
new file mode 100644 (file)
index 0000000..f61db9c
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+
+/**
+ * @Converter(
+ *  id = "entity_get_info",
+ *  description = @Translation("Rewrites calls to entity_get_info().")
+ * )
+ */
+class EntityGetInfo extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $manager = ClassMethodCallNode::create('\Drupal', 'entityManager');
+
+    $arguments = $call->getArguments();
+    if ($arguments->isEmpty()) {
+      return $manager->appendMethodCall('getDefinitions');
+    }
+    elseif (sizeof($arguments) == 1) {
+      return $manager
+        ->appendMethodCall('getDefinition')
+        ->appendArgument(clone $arguments[0]);
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/EntityLoad.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/EntityLoad.php
new file mode 100644 (file)
index 0000000..f58c951
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+use Pharborist\Types\ArrayNode;
+
+/**
+ * @Converter(
+ *  id = "entity_load",
+ *  description = @Translation("Rewrites calls to entity_load().")
+ * )
+ */
+class EntityLoad extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $arguments = $call->getArguments();
+
+    // If there were three arguments, the call is affecting the internal
+    // entity cache. Unfortunately, it's pretty much impossible to reliably
+    // determine whether or not they wanted to reset the cache, so let's just
+    // leave a FIXME.
+    if (sizeof($arguments) == 3) {
+      $this->buildFixMe('To reset the entity cache, use EntityStorageInterface::resetCache().')->insertBefore($call);
+    }
+
+    $rewritten = ClassMethodCallNode::create('\Drupal', 'entityManager')
+      ->appendMethodCall('getStorage')
+      ->appendArgument(clone $arguments[0]);
+
+    // If there's a third argument, conditions were passed. Not a recommended
+    // practice, but certain modules might have done it anyway. If we detect
+    // conditions, use loadByProperties().
+    if (sizeof($arguments) > 2) {
+      return $rewritten
+        ->appendMethodCall('loadByProperties')
+        ->appendArgument(clone $arguments[2]);
+    }
+    else {
+      $rewritten->appendMethodCall('load');
+
+      if (sizeof($arguments) > 1 && $arguments[1] instanceof ArrayNode) {
+        $rewritten->appendArgument(clone $arguments[1]);
+      }
+
+      return $rewritten;
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/EntityOperation.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/EntityOperation.php
new file mode 100644 (file)
index 0000000..73e9ab2
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ObjectMethodCallNode;
+
+/**
+ * @Converter(
+ *  id = "_entity_operation",
+ *  deriver = "Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\EntityOperationDeriver"
+ * )
+ */
+class EntityOperation extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $arguments = $call->getArguments();
+    $object = (strPos($call->getName()->getText(), 'entity_') === 0 ? $arguments[1] : $arguments[0]);
+
+    return ObjectMethodCallNode::create(clone $object, $this->pluginDefinition['method']);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/EntityOperationDeriver.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/EntityOperationDeriver.php
new file mode 100644 (file)
index 0000000..efe7570
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\drupalmoduleupgrader\DeriverBase;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Builds derivative definitions for the _entity_operation plugin, allowing us to
+ * rewrite calls to things like entity_save(), node_delete(), entity_label(), etc.
+ */
+class EntityOperationDeriver extends DeriverBase {
+
+  /**
+   * @var array
+   */
+  protected $config;
+
+  public function __construct(TranslationInterface $translator, array $config) {
+    parent::__construct($translator);
+    $this->config = $config;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, $base_plugin_id) {
+    return new static(
+      $container->get('string_translation'),
+      $container->get('config.factory')->get('drupalmoduleupgrader.entity_operations')->get('definitions')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinitions($base_definition) {
+    $derivatives = [];
+
+    foreach ($this->config as $entity_type => $operations) {
+      foreach ($operations as $operation) {
+        $function = $entity_type . '_' . $operation;
+        $variables = [
+          '@function' => $function,
+          '@operation' => $operation,
+        ];
+        $derivative = $base_definition;
+        $derivative['function'] = $function;
+        $derivative['method'] = $operation;
+        $derivative['message'] = $this->t('`@function` is now `EntityInterface::@operation`.', $variables);
+        $derivative['description'] = $this->t('Rewrites calls to @function().', $variables);
+        $derivatives[$function] = $derivative;
+      }
+    }
+
+    return $derivatives;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldInfoFieldTypes.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldInfoFieldTypes.php
new file mode 100644 (file)
index 0000000..2eb734a
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+
+/**
+ * @Converter(
+ *  id = "field_info_field_types",
+ *  description = @Translation("Rewrites calls to field_info_field_types().")
+ * )
+ */
+class FieldInfoFieldTypes extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $replacement = ClassMethodCallNode::create('\Drupal', 'service')
+      ->appendArgument('plugin.manager.field.field_type');
+
+    $arguments = $call->getArguments();
+    if ($arguments->isEmpty()) {
+      return $replacement->appendMethodCall('getDefinitions');
+    }
+    elseif (sizeof($arguments) == 1) {
+      return $replacement
+        ->appendMethodCall('getDefinition')
+        ->appendArgument(clone $arguments[0]);
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldInfoFormatterTypes.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldInfoFormatterTypes.php
new file mode 100644 (file)
index 0000000..76fab59
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+
+/**
+ * @Converter(
+ *  id = "field_info_formatter_types",
+ *  description = @Translation("Rewrites calls to field_info_formatter_types().")
+ * )
+ */
+class FieldInfoFormatterTypes extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $replacement = ClassMethodCallNode::create('\Drupal', 'service')
+      ->appendArgument('plugin.manager.field.formatter');
+
+    $arguments = $call->getArguments();
+    if ($arguments->isEmpty()) {
+      return $replacement->appendMethodCall('getDefinitions');
+    }
+    elseif (sizeof($arguments) == 1) {
+      return $replacement
+        ->appendMethodCall('getDefinition')
+        ->appendArgument(clone $arguments[0]);
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldInfoWidgetTypes.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldInfoWidgetTypes.php
new file mode 100644 (file)
index 0000000..c132184
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+
+/**
+ * @Converter(
+ *  id = "field_info_widget_types",
+ *  description = @Translation("Rewrites calls to field_info_widget_types().")
+ * )
+ */
+class FieldInfoWidgetTypes extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $replacement = ClassMethodCallNode::create('\Drupal', 'service')
+      ->appendArgument('plugin.manager.field.widget');
+
+    $arguments = $call->getArguments();
+    if ($arguments->isEmpty()) {
+      return $replacement->appendMethodCall('getDefinitions');
+    }
+    elseif (sizeof($arguments) == 1) {
+      return $replacement
+        ->appendMethodCall('getDefinition')
+        ->appendArgument(clone $arguments[0]);
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldUpdateField.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldUpdateField.php
new file mode 100644 (file)
index 0000000..96408a1
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ObjectMethodCallNode;
+
+/**
+ * @Converter(
+ *  id = "field_update_field",
+ *  description = @Translation("Rewrites calls to field_update_field().")
+ * )
+ */
+class FieldUpdateField extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    return ObjectMethodCallNode::create(clone $call->getArgumentList()->getItem(0), 'save');
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldUpdateInstance.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldUpdateInstance.php
new file mode 100644 (file)
index 0000000..79bdf04
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ObjectMethodCallNode;
+
+/**
+ * @Converter(
+ *  id = "field_update_instance",
+ *  description = @Translation("Rewrites calls to field_update_instance().")
+ * )
+ */
+class FieldUpdateInstance extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    return ObjectMethodCallNode::create(clone $call->getArgumentList()->getItem(0), 'save');
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldViewField.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldViewField.php
new file mode 100644 (file)
index 0000000..0a0ac2d
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ObjectMethodCallNode;
+use Pharborist\Parser;
+use Pharborist\Types\StringNode;
+
+/**
+ * @Converter(
+ *  id = "field_view_field",
+ *  description = @Translation("Rewrites calls to field_view_field().")
+ * )
+ */
+class FieldViewField extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $arguments = $call->getArguments();
+
+    $property = $arguments[2] instanceof StringNode ? $arguments[2]->toValue() : clone $arguments[2];
+    $rewritten = ObjectMethodCallNode::create(Parser::parseExpression($arguments[1] . '->' . $property), 'view');
+
+    if (sizeof($arguments) >= 4) {
+      $rewritten->appendArgument(clone $arguments[3]);
+    }
+
+    return $rewritten;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldViewValue.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldViewValue.php
new file mode 100644 (file)
index 0000000..4eadf35
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ObjectMethodCallNode;
+
+/**
+ * @Converter(
+ *  id = "field_view_value",
+ *  description = @Translation("Rewrites calls to field_view_value().")
+ * )
+ */
+class FieldViewValue extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $arguments = $call->getArguments();
+
+    $rewritten = ObjectMethodCallNode::create(clone $arguments[3], 'view');
+    if (sizeof($arguments) >= 5) {
+      $rewritten->appendArgument(clone $arguments[4]);
+    }
+
+    return $rewritten;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FormExecuteHandlers.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FormExecuteHandlers.php
new file mode 100644 (file)
index 0000000..7514269
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+use Pharborist\Types\StringNode;
+
+/**
+ * @Converter(
+ *  id = "form_execute_handlers",
+ *  description = @Translation("Rewrites calls to form_execute_handlers()."),
+ *  fixme = @Translation("form_execute_handlers() has been split into the executeValidateHandlers() and executeSubmitHandlers() methods of the form builder service.")
+ * )
+ */
+class FormExecuteHandlers extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $arguments = $call->getArguments();
+
+    if ($arguments[0] instanceof StringNode) {
+      $handler_type = $arguments[0]->toValue();
+
+      if ($handler_type == 'validate' || $handler_type == 'submit') {
+        return ClassMethodCallNode::create('\Drupal', 'formBuilder')
+          ->appendMethodCall('execute' . ucFirst($handler_type) . 'Handlers')
+          ->appendArgument(clone $arguments[1])
+          ->appendArgument(clone $arguments[2]);
+      }
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FormLoadInclude.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FormLoadInclude.php
new file mode 100644 (file)
index 0000000..0c5332c
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ObjectMethodCallNode;
+
+/**
+ * @Converter(
+ *  id = "form_load_include",
+ *  description = @Translation("Rewrites calls to form_load_include().")
+ * )
+ */
+class FormLoadInclude extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $arguments = $call->getArguments();
+
+    $rewritten = ObjectMethodCallNode::create(clone $arguments[0], 'loadInclude')
+      ->appendArgument(clone $arguments[2])
+      ->appendArgument(clone $arguments[1]);
+
+    if (sizeof($arguments) == 4) {
+      $rewritten->appendArgument(clone $arguments[3]);
+    }
+
+    return $rewritten;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FormSetValue.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FormSetValue.php
new file mode 100644 (file)
index 0000000..1d3aa84
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ObjectMethodCallNode;
+
+/**
+ * @Converter(
+ *  id = "form_set_value",
+ *  description = @Translation("Rewrites calls to form_set_value().")
+ * )
+ */
+class FormSetValue extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $arguments = $call->getArguments();
+
+    return ObjectMethodCallNode::create($arguments[2]->remove(), 'setValueForElement')
+      ->appendArgument(clone $arguments[0])
+      ->appendArgument(clone $arguments[1]);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FormStateDefaults.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FormStateDefaults.php
new file mode 100644 (file)
index 0000000..191cb5d
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+
+/**
+ * @Converter(
+ *  id = "form_state_defaults",
+ *  description = @Translation("Rewrites calls to form_state_defaults().")
+ * )
+ */
+class FormStateDefaults extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    // @todo
+    // There are two possibilities here. If the call is part of a += operation
+    // or an array_merge() call, the entire thing needs to be commented out.
+    // Otherwise, it should be changed to 'new FormState()', which requires an
+    // upstream change in Pharborist.
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FormStateValuesClean.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FormStateValuesClean.php
new file mode 100644 (file)
index 0000000..ee3ca8e
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ObjectMethodCallNode;
+
+/**
+ * @Converter(
+ *  id = "form_state_values_clean",
+ *  description = @Translation("Rewrites calls to form_state_values_clean().")
+ * )
+ */
+class FormStateValuesClean extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    return ObjectMethodCallNode::create(clone $call->getArgumentList()->getItem(0), 'cleanValues');
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FunctionCallModifier.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FunctionCallModifier.php
new file mode 100644 (file)
index 0000000..b53e0b2
--- /dev/null
@@ -0,0 +1,76 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+
+/**
+ * Base class for converters which modify individual function calls.
+ */
+abstract class FunctionCallModifier extends ConverterBase {
+
+  /**
+   * Tries to rewrite the original function call.
+   *
+   * @param \Pharborist\Functions\FunctionCallNode $call
+   *  The original function call.
+   * @param \Drupal\drupalmoduleupgrader\TargetInterface $target
+   *  The target module.
+   *
+   * @return \Pharborist\Node|NULL
+   *  If the original function call is returned (determined by object identity),
+   *  the function call is not replaced. If a different node is returned, it
+   *  will replace the original call. And if nothing is returned, the original
+   *  call is commented out with a FIXME.
+   */
+  abstract public function rewrite(FunctionCallNode $call, TargetInterface $target);
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isExecutable(TargetInterface $target) {
+    // Silence 'undefined index' notices if the 'function' key doesn't exist in
+    // the plugin definition.
+    $function = @($this->pluginDefinition['function'] ?: $this->getPluginId());
+    return $target->getIndexer('function_call')->has($function);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    // Prevent stupid effing 'undefined index' notices.
+    $function = @($this->pluginDefinition['function'] ?: $this->getPluginId());
+
+    $function_calls = $target
+      ->getIndexer('function_call')
+      ->get($function);
+
+    foreach ($function_calls as $function_call) {
+      // If the function call is no longer attached to a tree, don't even
+      // try to rewrite it. This could happen when there are two calls to
+      // the same function in a single statement, and the first one has
+      // been commented out -- the second one will be attached to an orphaned
+      // sub-tree, and this will result in fatal errors.
+      if (! $function_call->hasRoot()) {
+        continue;
+      }
+
+      $rewritten = $this->rewrite($function_call, $target);
+      if (empty($rewritten)) {
+        $statement = $function_call->getStatement();
+        $rewritten = $statement->toComment();
+        $statement->replaceWith($rewritten);
+        $this->buildFixMe()->insertBefore($rewritten);
+      }
+      elseif ($rewritten !== $function_call) {
+        $function_call->replaceWith($rewritten);
+      }
+
+      $target->save($rewritten);
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/GetT.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/GetT.php
new file mode 100644 (file)
index 0000000..76bcd71
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Types\StringNode;
+
+/**
+ * @Converter(
+ *  id = "get_t",
+ *  description = @Translation("Rewrites calls to get_t().")
+ * )
+ */
+class GetT extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    return StringNode::fromValue('t');
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/L.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/L.php
new file mode 100644 (file)
index 0000000..7c3ff71
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+use Pharborist\Parser;
+use Pharborist\Types\StringNode;
+
+/**
+ * @Converter(
+ *  id = "l",
+ *  description = @Translation("Rewrites calls to l()."),
+ *  fixme = @Translation("l() expects a Url object, created from a route name or external URI."),
+ *  dependencies = { "router.route_provider" }
+ * )
+ */
+class L extends URL {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $arguments = $call->getArguments();
+    if ($arguments[1] instanceof StringNode) {
+      // Create a call to url() and let the parent class rewrite it like normal,
+      // so we don't have to duplicate that code.
+      $url = Parser::parseSnippet('url(' . $arguments[1] . ');')->firstChild();
+      $url_rewritten = parent::rewrite($url, $target);
+      if ($url_rewritten) {
+        return ClassMethodCallNode::create('\Drupal', 'l')
+          ->appendArgument($arguments[0])
+          ->appendArgument($url_rewritten);
+      }
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/LoadMultiple.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/LoadMultiple.php
new file mode 100644 (file)
index 0000000..57d2915
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+
+/**
+ * @Converter(
+ *  id = "_load_multiple",
+ *  deriver = "\Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\LoadMultipleDeriver"
+ * )
+ */
+class LoadMultiple extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $arguments = $call->getArguments();
+
+    // If there were three arguments, the call is affecting the internal
+    // entity cache. Unfortunately, it's pretty much impossible to reliably
+    // determine whether or not they wanted to reset the cache, so let's just
+    // leave a FIXME.
+    if (sizeof($arguments) == 3) {
+      $variables = [
+        '!entity_type' => $this->pluginDefinition['entity_type'],
+      ];
+      $this->buildFixMe('To reset the !entity_type cache, use EntityStorageInterface::resetCache().', $variables)->insertBefore($call);
+    }
+
+    $rewritten = ClassMethodCallNode::create('\Drupal', 'entityManager')
+      ->appendMethodCall('getStorage')
+      ->appendArgument($this->pluginDefinition['entity_type']);
+
+    // If there's more than one argument, conditions were passed (not a
+    // recommended practice, but modules might have done it anyway), in which
+    // case we need to use loadByProperties(). Otherwise, loadMultiple().
+    if (sizeof($arguments) > 1) {
+      return $rewritten
+        ->appendMethodCall('loadByProperties')
+        ->appendArgument(clone $arguments[1]);
+    }
+    else {
+      return $rewritten
+        ->appendMethodCall('loadMultiple')
+        ->appendArgument(clone $arguments[0]);
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/LoadMultipleDeriver.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/LoadMultipleDeriver.php
new file mode 100644 (file)
index 0000000..6bb7bc3
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\DeriverBase;
+
+/**
+ * Builds derivative definitions for the _load_multiple plugin.
+ */
+class LoadMultipleDeriver extends DeriverBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinitions($base_definition) {
+    $derivatives = [];
+
+    foreach (['node', 'user', 'comment', 'taxonomy_term'] as $entity_type) {
+      $function = $entity_type . '_load_multiple';
+      $variables = ['@function' => $function];
+
+      $derivative = $base_definition;
+      $derivative['function'] = $function;
+      $derivative['entity_type'] = $entity_type;
+      $derivative['message'] = $this->t('`@function` is now `EntityStorageInterface::loadMultiple()`.', $variables);
+      $derivative['description'] = $this->t('Rewrites calls to @function().', $variables);
+
+      $derivatives[$entity_type] = $derivative;
+    }
+
+    return $derivatives;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/ModuleInvoke.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/ModuleInvoke.php
new file mode 100644 (file)
index 0000000..39acaa3
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+use Pharborist\Types\ArrayNode;
+
+/**
+ * @Converter(
+ *  id = "module_invoke",
+ *  description = @Translation("Rewrites calls to module_invoke().")
+ * )
+ */
+class ModuleInvoke extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $arguments = $call->getArguments()->toArray();
+
+    $invoke = ClassMethodCallNode::create('\Drupal', 'moduleHandler')
+      ->appendMethodCall('invoke')
+      ->appendArgument(array_shift($arguments)->remove())
+      ->appendArgument(array_shift($arguments)->remove());
+
+    if ($arguments) {
+      $invoke->appendArgument(ArrayNode::create($arguments));
+    }
+
+    return $invoke;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/ModuleInvokeAll.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/ModuleInvokeAll.php
new file mode 100644 (file)
index 0000000..e1a60c0
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+use Pharborist\Types\ArrayNode;
+
+/**
+ * @Converter(
+ *  id = "module_invoke_all",
+ *  description = @Translation("Rewrites calls to module_invoke_all().")
+ * )
+ */
+class ModuleInvokeAll extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $arguments = $call->getArguments()->toArray();
+
+    $rewritten = ClassMethodCallNode::create('\Drupal', 'moduleHandler')
+      ->appendMethodCall('invokeAll')
+      ->appendArgument(array_shift($arguments));
+
+    if ($arguments) {
+      $rewritten->appendArgument(ArrayNode::create($arguments));
+    }
+
+    return $rewritten;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/NodeLoad.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/NodeLoad.php
new file mode 100644 (file)
index 0000000..405b0ea
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+
+/**
+ * @Converter(
+ *  id = "node_load",
+ *  description = @Translation("Rewrites calls to node_load()."),
+ *  fixme = @Translation("node_load() is now EntityStorageInterface::load().")
+ * )
+ */
+class NodeLoad extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $arguments = $call->getArguments();
+
+    // If there were three arguments, the call is affecting the internal
+    // node_load() cache. Unfortunately, it's pretty much impossible to
+    // reliably determine whether or not they wanted to reset the cache,
+    // so let's just leave a FIXME.
+    if (sizeof($arguments) == 3) {
+      $this->buildFixMe('To reset the node cache, use EntityStorageInterface::resetCache().')->insertBefore($call);
+    }
+
+    $rewritten = ClassMethodCallNode::create('\Drupal', 'entityManager')
+      ->appendMethodCall('getStorage')
+      ->appendArgument('node');
+
+    // If there's more than one argument, a revision ID was passed, which
+    // means we call loadRevision($nid). Otherwise, call load($nid).
+    if (sizeof($arguments) > 1) {
+      return $rewritten
+        ->appendMethodCall('loadRevision')
+        ->appendArgument(clone $arguments[1]);
+    }
+    else {
+      return $rewritten
+        ->appendMethodCall('load')
+        ->appendArgument(clone $arguments[0]);
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/St.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/St.php
new file mode 100644 (file)
index 0000000..e1b2677
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+
+/**
+ * @Converter(
+ *  id = "st",
+ *  description = @Translation("Rewrites calls to st().")
+ * )
+ */
+class St extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    return $call->setName('t');
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/ThemeGetRegistry.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/ThemeGetRegistry.php
new file mode 100644 (file)
index 0000000..7319996
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+use Pharborist\Types\FalseNode;
+use Pharborist\Types\StringNode;
+
+/**
+ * @Converter(
+ *  id = "theme_get_registry",
+ *  description = @Translation("Rewrites calls to theme_get_registry().")
+ * )
+ */
+class ThemeGetRegistry extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $arguments = $call->getArguments()->toArray();
+
+    return ClassMethodCallNode::create('\Drupal', 'service')
+      ->appendArgument(StringNode::fromValue('theme.registry'))
+      ->appendMethodCall(($arguments && $arguments[0] instanceof FalseNode) ? 'getRuntime' : 'get');
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/URL.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/URL.php
new file mode 100644 (file)
index 0000000..b196688
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Routing\RouteProviderInterface;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+use Pharborist\Types\StringNode;
+use Psr\Log\LoggerInterface;
+
+/**
+ * @Converter(
+ *  id = "url",
+ *  description = @Translation("Rewrites calls to url()."),
+ *  fixme = @Translation("url() expects a route name or an external URI."),
+ *  dependencies = { "router.route_provider" }
+ * )
+ */
+class URL extends FunctionCallModifier implements ContainerFactoryPluginInterface {
+
+  /**
+   * @var \Drupal\Core\Routing\RouteProviderInterface
+   */
+  protected $routeProvider;
+
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, TranslationInterface $translator, LoggerInterface $log, RouteProviderInterface $route_provider) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $translator, $log);
+    $this->routeProvider = $route_provider;
+  }
+
+  /**
+   * Looks up routes by path, and returns TRUE if at least one was found.
+   *
+   * @param string $path
+   *  The path to search for, not including the leading slash. Can be an
+   *  external URL.
+   *
+   * @return boolean
+   *  TRUE if the path matches a route, FALSE otherwise. External URLs will
+   *  always return FALSE.
+   */
+  protected function routeExists($path) {
+    // If there's a scheme in the URL, consider this an external URL and don't even
+    // try to rewrite it.
+    $scheme = parse_url($path, PHP_URL_SCHEME);
+    if (isset($scheme)) {
+      return FALSE;
+    }
+    else {
+      $routes = $this->routeProvider->getRoutesByPattern('/' . $path);
+      return (sizeof($routes) > 0);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $arguments = $call->getArguments();
+    if ($arguments[0] instanceof StringNode) {
+      $path = $arguments[0]->toValue();
+
+      // If the URL has a scheme (e.g., http://), it's external.
+      if (parse_url($path, PHP_URL_SCHEME)) {
+        return ClassMethodCallNode::create('\Drupal\Core\Url', 'fromUri')
+          ->appendArgument(clone $arguments[0]);
+      }
+      elseif ($this->routeExists($path)) {
+        $route = $this->routeProvider->getRoutesByPattern('/' . $path)->getIterator()->key();
+        return ClassMethodCallNode::create('\Drupal\Core\Url', 'fromRoute')
+          ->appendArgument(StringNode::fromValue($route));
+      }
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/UserAccess.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/UserAccess.php
new file mode 100644 (file)
index 0000000..8bed401
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+use Pharborist\Objects\ObjectMethodCallNode;
+use Pharborist\Variables\VariableNode;
+
+/**
+ * @Converter(
+ *  id = "user_access",
+ *  description = @Translation("Rewrites calls to user_access().")
+ * )
+ */
+class UserAccess extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $arguments = $call->getArguments();
+
+    if (isset($arguments[1]) && $arguments[1] instanceof VariableNode) {
+      $rewritten = ObjectMethodCallNode::create(clone $arguments[1], 'hasPermission');
+    }
+    else {
+      $rewritten = ClassMethodCallNode::create('\Drupal', 'currentUser')->appendMethodCall('hasPermission');
+    }
+
+    return $rewritten->appendArgument(clone $arguments[0]);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/UserLoad.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/UserLoad.php
new file mode 100644 (file)
index 0000000..7ff0973
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+
+/**
+ * @Converter(
+ *  id = "user_load",
+ *  description = @Translation("Rewrites calls to user_load()."),
+ *  fixme = @Translation("user_load() is now EntityStorageInterface::load().")
+ * )
+ */
+class UserLoad extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $arguments = $call->getArguments();
+
+    // If there were three arguments, the call is affecting the internal
+    // user_load() cache. Unfortunately, it's pretty much impossible to
+    // reliably determine whether or not they wanted to reset the cache,
+    // so let's just leave a FIXME.
+    if (sizeof($arguments) == 2) {
+      $this->buildFixMe('To reset the user cache, use EntityStorageInterface::resetCache().')->insertBefore($call);
+    }
+
+    return ClassMethodCallNode::create('\Drupal', 'entityManager')
+      ->appendMethodCall('getStorage')
+      ->appendArgument('user')
+      ->appendMethodCall('load')
+      ->appendArgument(clone $arguments[0]);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/UserSave.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/UserSave.php
new file mode 100644 (file)
index 0000000..0a76940
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ObjectMethodCallNode;
+
+/**
+ * @Converter(
+ *  id = "user_save",
+ *  description = @Translation("Rewrites calls to user_save()."),
+ *  fixme = @Translation("user_save() is now a method of the user entity.")
+ * )
+ */
+class UserSave extends FunctionCallModifier {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $arguments = $call->getArguments();
+
+    if (sizeof($arguments) == 1) {
+      return ObjectMethodCallNode::create(clone $arguments[0], 'save');
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/VariableAPI.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/VariableAPI.php
new file mode 100644 (file)
index 0000000..b60dab5
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Types\StringNode;
+
+/**
+ * Parent class of the VariableGet, VariableSet, and VariableDel plugins.
+ */
+abstract class VariableAPI extends FunctionCallModifier {
+
+  /**
+   * Helper for subclasses' rewrite() methods. This checks if the call can
+   * be rewritten at all and leaves a FIXME if it can't. If the variable's
+   * key is not a string starting with MODULE_, the call will not be
+   * considered rewritable.
+   *
+   * @return boolean
+   */
+  protected function tryRewrite(FunctionCallNode $call, TargetInterface $target) {
+    $statement = $call->getStatement();
+    $arguments = $call->getArguments();
+
+    if ($arguments[0] instanceof StringNode) {
+      $key = $arguments[0]->toValue();
+
+      if (strPos($key, $target->id() . '_') === 0) {
+        return TRUE;
+      }
+      else {
+        $comment = <<<END
+This looks like another module's variable. You'll need to rewrite this call
+to ensure that it uses the correct configuration object.
+END;
+        $this->buildFixMe($comment)->prependTo($statement);
+        return FALSE;
+      }
+    }
+    else {
+      $comment = <<<END
+The correct configuration object could not be determined. You'll need to
+rewrite this call manually.
+END;
+      $this->buildFixMe($comment)->prependTo($statement);
+      return FALSE;
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/VariableDel.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/VariableDel.php
new file mode 100644 (file)
index 0000000..965ca73
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+
+/**
+ * @Converter(
+ *  id = "variable_del",
+ *  description = @Translation("Replaces variable_del() calls with Configuration API.")
+ * )
+ */
+class VariableDel extends VariableAPI {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    if ($this->tryRewrite($call, $target)) {
+      return ClassMethodCallNode::create('\Drupal', 'config')
+        ->appendArgument($target->id() . '.settings')
+        ->appendMethodCall('clear')
+        ->appendArgument(clone $call->getArguments()->get(0))
+        ->appendMethodCall('save');
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/VariableGet.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/VariableGet.php
new file mode 100644 (file)
index 0000000..915edb3
--- /dev/null
@@ -0,0 +1,91 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\Component\Serialization\Yaml;
+use Drupal\Core\Config\InstallStorage;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+use Pharborist\Types\ScalarNode;
+
+/**
+ * @Converter(
+ *  id = "variable_get",
+ *  description = @Translation("Replaces variable_get() calls with Configuration API.")
+ * )
+ */
+class VariableGet extends VariableAPI {
+
+  /**
+   * Default configuration values extracted from rewritten calls to
+   * variable_get().
+   *
+   * @var mixed[]
+   */
+  private $defaults = [];
+
+  /**
+   * The schema accompanying any extracted default values.
+   *
+   * @var array
+   */
+  private $schema = [];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    if ($this->tryRewrite($call, $target)) {
+      $arguments = $call->getArguments();
+      $key = $arguments[0]->toValue();
+
+      if ($arguments[1] instanceof ScalarNode) {
+        // @TODO Couldn't convert() derive the schema from $this->defaults?
+        // That'd be preferable to having yet another state property ($schema)
+        // on this class.
+        $this->defaults[$key] = $arguments[1]->toValue();
+        $this->schema[$key]['type'] = getType($this->defaults[$key]);
+      }
+      else {
+        $comment = <<<END
+Could not extract the default value because it is either indeterminate, or
+not scalar. You'll need to provide a default value in
+config/install/@module.settings.yml and config/schema/@module.schema.yml.
+END;
+        $variables = [ '@module' => $target->id() ];
+        $this->buildFixMe($comment, $variables)->prependTo($call->getStatement());
+      }
+
+      return ClassMethodCallNode::create('\Drupal', 'config')
+        ->appendArgument($target->id() . '.settings')
+        ->appendMethodCall('get')
+        ->appendArgument(clone $arguments[0]);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    parent::convert($target);
+
+    if ($this->defaults && $this->schema) {
+      $group = $target->id() . '.settings';
+
+      $this->write($target, InstallStorage::CONFIG_INSTALL_DIRECTORY . '/' . $group . '.yml', Yaml::encode($this->defaults));
+      $this->defaults = [];
+
+      $schema = [
+        $group => [
+          'type' => 'mapping',
+          'label' => (string) $this->t('Settings'),
+          'mapping' => $this->schema,
+        ],
+      ];
+      $this->write($target, InstallStorage::CONFIG_SCHEMA_DIRECTORY . '/' . $target->id() . '.schema.yml', Yaml::encode($schema));
+      $this->schema = [];
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/VariableSet.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/VariableSet.php
new file mode 100644 (file)
index 0000000..682e10c
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+
+/**
+ * @Converter(
+ *  id = "variable_set",
+ *  description = @Translation("Replaces variable_set() calls with Configuration API.")
+ * )
+ */
+class VariableSet extends VariableAPI {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    if ($this->tryRewrite($call, $target)) {
+      $arguments = $call->getArguments();
+
+      return ClassMethodCallNode::create('\Drupal', 'configFactory')
+        ->appendMethodCall('getEditable')
+        ->appendArgument($target->id() . '.settings')
+        ->appendMethodCall('set')
+        ->appendArgument(clone $arguments[0])
+        ->appendArgument(clone $arguments[1])
+        ->appendMethodCall('save');
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/Watchdog.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/Watchdog.php
new file mode 100644 (file)
index 0000000..91a84f9
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Constants\ConstantNode;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Objects\ClassMethodCallNode;
+use Pharborist\Types\ArrayNode;
+
+/**
+ * @Converter(
+ *  id = "watchdog",
+ *  description = @Translation("Converts calls to watchdog() to \Drupal::logger().")
+ * )
+ */
+class Watchdog extends FunctionCallModifier {
+
+  protected static $severityConstants = [
+    'WATCHDOG_EMERGENCY',
+    'WATCHDOG_ALERT',
+    'WATCHDOG_CRITICAL',
+    'WATCHDOG_ERROR',
+    'WATCHDOG_WARNING',
+    'WATCHDOG_NOTICE',
+    'WATCHDOG_INFO',
+    'WATCHDOG_DEBUG',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(FunctionCallNode $call, TargetInterface $target) {
+    $arguments = $call->getArguments();
+
+    // We'll call a specific method on the logger object, depending on the
+    // severity passed in the original function call (if any). If there are
+    // at least four arguments, a severity was passed. We check $arguments[3]
+    // to ensure it's a valid severity constant, and if it's not, we default
+    // to the notice() severity.
+    //
+    // @TODO Leave a FIXME for an invalid severity, since changing it to a
+    // notice alters the intent of the original code.
+    //
+    if (sizeof($arguments) > 3 && $arguments[3] instanceof ConstantNode && in_array($arguments[3]->getConstantName()->getText(), static::$severityConstants)) {
+      $method = strtolower(subStr($arguments[3], 9));
+    }
+    else {
+      $method = 'notice';
+    }
+
+    // If there is a third argument, and it's an array, a context array
+    // was passed.
+    $context = (sizeof($arguments) > 2 && $arguments[2] instanceof ArrayNode) ? clone $arguments[2] : ArrayNode::create([]);
+
+    return ClassMethodCallNode::create('\Drupal', 'logger')
+      ->appendArgument(clone $arguments[0])
+      ->appendMethodCall($method)
+      ->appendArgument(clone $arguments[1])
+      ->appendArgument($context);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Grep.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Grep.php
new file mode 100644 (file)
index 0000000..7fbf063
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Parser;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * @Converter(
+ *  id = "grep",
+ *  description = @Translation("Searches for and replaces commonly-used code that has changed in Drupal 8.")
+ * )
+ */
+class Grep extends ConverterBase {
+
+  /**
+   * @var string[]
+   */
+  private $targets = [];
+
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, TranslationInterface $translator, LoggerInterface $log) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $translator, $log);
+
+    foreach ($configuration['globals'] as $variable => $replacement) {
+      $this->targets['global $' . $variable . ';'] = '$' . $variable . ' = ' . $replacement . ';';
+      $this->targets['$GLOBALS[\'' . $variable . '\']'] = $replacement;
+      $this->targets['$GLOBALS["' . $variable . '"]'] = $replacement;
+    }
+    foreach ($configuration['constants'] as $constant => $replacement) {
+      $this->targets[$constant] = $replacement;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $container->get('config.factory')->get('drupalmoduleupgrader.grep')->get('definitions'),
+      $plugin_id,
+      $plugin_definition,
+      $container->get('string_translation'),
+      $container->get('logger.factory')->get('drupalmoduleupgrader')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    foreach ($this->configuration['function_calls'] as $function => $replace_with) {
+      $function_calls = $target->getIndexer('function_call')->get($function);
+      foreach ($function_calls as $function_call) {
+        $rewritten = str_ireplace($function, $replace_with, $function_call->getText());
+        $node = Parser::parseExpression($rewritten);
+        $function_call->replaceWith($node);
+        $target->save($node);
+      }
+    }
+
+    // Flush other open syntax trees to ensure that other plugins don't clobber
+    // our changes later.
+    $target->flush();
+
+    foreach ($target->getFinder() as $file) {
+      // Load in the entire contents of the module. This is criminally inefficient
+      // and wasteful of memory and should eventually be refactored into something
+      // a little more...I dunno, sustainable.
+      /** @var \Symfony\Component\Finder\SplFileInfo $file */
+      $search = array_keys($this->targets);
+      $replace = array_values($this->targets);
+      file_put_contents($file->getPathname(), str_replace($search, $replace, $file->getContents()));
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookBoot.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookBoot.php
new file mode 100644 (file)
index 0000000..6c687b1
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\ParameterNode;
+
+/**
+ * @Converter(
+ *  id = "hook_boot",
+ *  description = @Translation("Converts Drupal 7's hook_boot() to an EventSubscriber."),
+ *  hook = "hook_boot"
+ * )
+ */
+class HookBoot extends ConverterBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    $this->writeService($target, 'boot_subscriber', [
+      'class' => 'Drupal\\' . $target->id() . '\\EventSubscriber\\BootSubscriber',
+      'tags' => [
+        [ 'name' => 'event_subscriber' ],
+      ],
+    ]);
+
+    $render = [
+      '#theme' => 'dmu_event_subscriber',
+      '#module' => $target->id(),
+      '#class' => 'BootSubscriber',
+      '#event' => 'KernelEvents::REQUEST',
+    ];
+    $subscriber = $this->parse($render);
+
+    $target
+      ->getIndexer('function')
+      ->get('hook_boot')
+      ->cloneAsMethodOf($subscriber)
+      ->setName('onEvent')
+      ->appendParameter(ParameterNode::create('event')
+        ->setTypeHint('\Symfony\Component\HttpKernel\Event\GetResponseEvent')
+      );
+
+    $this->writeClass($target, $subscriber);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookEntityInfo.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookEntityInfo.php
new file mode 100644 (file)
index 0000000..60f7601
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\Component\Utility\SafeMarkup;
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Drupal\drupalmoduleupgrader\Utility\StringTransformTrait;
+
+/**
+ * @Converter(
+ *  id = "hook_entity_info",
+ *  description = @Translation("Creates entity class boilerplate from hook_entity_info()."),
+ *  hook = "hook_entity_info"
+ * )
+ */
+class HookEntityInfo extends ConverterBase {
+
+  use StringTransformTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    try {
+      $entity_types = $this->executeHook($target, 'entity_info');
+    }
+    catch (\LogicException $e) {
+      $this->log->warning($e->getMessage(), [
+        'target' => $target->id(),
+        'hook' => $this->pluginDefinition['hook'],
+      ]);
+      return;
+    }
+
+    foreach ($entity_types as $id => $entity_type) {
+      $entity_type['id'] = $id;
+
+      $entity_type['base_table'] = $entity_type['base table'];
+      unset($entity_type['base table']);
+
+      $entity_type['keys'] = $entity_type['entity keys'];
+      unset($entity_type['entity keys']);
+
+      if (isset($entity_type['controller class'])) {
+        /** @var \Pharborist\Objects\ClassNode $controller */
+        $indexer = $target->getIndexer('class');
+        if ($indexer->has($entity_type['controller class'])) {
+          $controller = $indexer->get($entity_type['controller class']);
+
+          $parent = $controller->getExtends();
+          if ($parent) {
+            if ($parent->getText() == 'DrupalDefaultEntityController' || $parent->getText() == 'EntityAPIController') {
+              $controller->setExtends('Drupal\Core\Entity\Sql\SqlContentEntityStorage');
+            }
+            else {
+              // @todo Not entirely sure what to do here. It's not a huge problem
+              // if the controller extends another class defined by the target
+              // (which is, admittedly, an edge case), but if it extends a
+              // controller defined by *another* module that isn't Entity API?
+            }
+          }
+
+          // @todo Handle interfaces implemented by the entity controller.
+
+          $this->writeClass($target, PSR4::toPSR4($target, $controller));
+          $entity_type['controllers']['storage'] = $controller->getName()->getAbsolutePath();
+        }
+        else {
+          throw new \LogicException(SafeMarkup::format('Cannot get ahold of the controller class for @entity_type entity type.', ['@entity_type' => $id]));
+        }
+      }
+      else {
+        $entity_type['controllers']['storage'] = 'Drupal\Core\Entity\Sql\SqlContentEntityStorage';
+      }
+
+      $render = [
+        '#module' => $target->id(),
+        '#class' => $this->toTitleCase($id),
+        '#theme' => 'dmu_entity_type',
+        '#info' => $entity_type,
+      ];
+      $this->writeClass($target, $this->parse($render));
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookEntityTypeView.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookEntityTypeView.php
new file mode 100644 (file)
index 0000000..8bfb9b3
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\ParameterNode;
+use Psr\Log\LoggerInterface;
+
+/**
+ * @Converter(
+ *  id = "hook_ENTITY_TYPE_view",
+ *  description = @Translation("Converts implementations of hook_ENTITY_TYPE_view()."),
+ *  hook = {
+ *    "hook_comment_view",
+ *    "hook_node_view",
+ *    "hook_taxonomy_term_view",
+ *    "hook_user_view"
+ *  },
+ *  dependencies = { "plugin.manager.drupalmoduleupgrader.rewriter" }
+ * )
+ */
+class HookEntityTypeView extends ConverterBase {
+
+  /**
+   * @var \Drupal\Component\Plugin\PluginManagerInterface
+   */
+  protected $rewriters;
+
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, TranslationInterface $translator, LoggerInterface $log, PluginManagerInterface $rewriters) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $translator, $log);
+    $this->rewriters = $rewriters;
+  }
+
+  public function convert(TargetInterface $target) {
+    $indexer = $target->getIndexer('function');
+
+    $hooks = array_filter($this->pluginDefinition['hook'], [$indexer, 'has']);
+    foreach ($hooks as $hook) {
+      /** @var \Pharborist\Functions\FunctionDeclarationNode $function */
+      $function = $indexer->get($hook);
+      $function->prependParameter(ParameterNode::create('build')->setTypeHint('array')->setReference(TRUE));
+
+      // Extract the entity type from the hook name (e.g. 'hook_node_view').
+      preg_match('/^hook_(.+)_view$/', $hook, $matches);
+      $entity_type = $matches[1];
+      $rewriter = $this->rewriters->createInstance('_rewriter:' . $entity_type);
+      $this->rewriteFunction($rewriter, $function->getParameterAtIndex(1), $target);
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookExit.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookExit.php
new file mode 100644 (file)
index 0000000..25196f2
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+
+/**
+ * @Converter(
+ *  id = "hook_exit",
+ *  description = @Translation("Converts Drupal 7's hook_exit() to an EventSubscriber."),
+ *  hook = "hook_exit"
+ * )
+ */
+class HookExit extends ConverterBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    $this->writeService($target, 'exit_subscriber', [
+      'class' => 'Drupal\\' . $target->id() . '\\EventSubscriber\\ExitSubscriber',
+      'tags' => [
+        [ 'name' => 'event_subscriber' ],
+      ],
+    ]);
+
+    $render = [
+      '#theme' => 'dmu_event_subscriber',
+      '#module' => $target->id(),
+      '#class' => 'ExitSubscriber',
+      '#event' => 'KernelEvents::TERMINATE',
+    ];
+    $subscriber = $this->parse($render);
+    $target
+      ->getIndexer('function')
+      ->get('hook_exit')
+      ->cloneAsMethodOf($subscriber)
+      ->setName('onEvent');
+    $this->writeClass($target, $subscriber);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFieldAttachCreateBundle.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFieldAttachCreateBundle.php
new file mode 100644 (file)
index 0000000..95cecd7
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+
+/**
+ * @Converter(
+ *  id = "hook_field_attach_create_bundle",
+ *  description = @Translation("Renames hook_field_attach_create_bundle()."),
+ *  hook = "hook_field_attach_create_bundle"
+ * )
+ */
+class HookFieldAttachCreateBundle extends ConverterBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    $hook = $target
+      ->getIndexer('function')
+      ->get($this->pluginDefinition['hook'])
+      ->setName($target->id() . '_entity_bundle_create');
+
+    $target->save($hook);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFieldAttachDeleteBundle.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFieldAttachDeleteBundle.php
new file mode 100644 (file)
index 0000000..e8f567f
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\DocCommentNode;
+
+/**
+ * @Converter(
+ *  id = "hook_field_attach_delete_bundle",
+ *  description = @Translation("Adds a FIXME notice to hook_field_attach_delete_bundle()."),
+ *  hook = "hook_field_attach_delete_bundle",
+ *  fixme = @Translation("@FIXME
+hook_field_attach_delete_bundle() has been renamed to hook_entity_bundle_delete(),
+and it no longer accepts an $instances argument. This cannot be converted
+automatically because it's likely to affect the hook's logic, so you'll need to
+modify this function manually.
+
+For more information, see https://www.drupal.org/node/1964766.
+")
+ * )
+ */
+class HookFieldAttachDeleteBundle extends ConverterBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    $hook = $target
+      ->getIndexer('function')
+      ->get($this->pluginDefinition['hook'])
+      ->before(DocCommentNode::create($this->pluginDefinition['fixme']));
+
+    $target->save($hook);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFieldAttachRenameBundle.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFieldAttachRenameBundle.php
new file mode 100644 (file)
index 0000000..291c052
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+
+/**
+ * @Converter(
+ *  id = "hook_field_attach_rename_bundle",
+ *  description = @Translation("Renames hook_field_attach_rename_bundle()."),
+ *  hook = "hook_field_attach_rename_bundle"
+ * )
+ */
+class HookFieldAttachRenameBundle extends ConverterBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    $hook = $target
+      ->getIndexer('function')
+      ->get($this->pluginDefinition['hook'])
+      ->setName($target->id() . '_entity_bundle_rename');
+
+    $target->save($hook);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFieldFormatterInfo.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFieldFormatterInfo.php
new file mode 100644 (file)
index 0000000..0eebc62
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Drupal\drupalmoduleupgrader\Utility\StringTransformTrait;
+
+/**
+ * @Converter(
+ *  id = "hook_field_formatter_info",
+ *  description = @Translation("Creates formatter class templates from hook_field_formatter_info()."),
+ *  hook = "hook_field_formatter_info"
+ * )
+ */
+class HookFieldFormatterInfo extends ConverterBase {
+
+  use StringTransformTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    try {
+      $formatters = $this->executeHook($target, $this->pluginDefinition['hook']);
+    }
+    catch (\LogicException $e) {
+      $this->log->warning($e->getMessage(), [
+        'target' => $target->id(),
+        'hook' => $this->pluginDefinition['hook'],
+      ]);
+      return;
+    }
+
+    foreach ($formatters as $id => $formatter) {
+      $render = [
+        '#module' => $target->id(),
+        '#class' => $this->toTitleCase($id),
+        '#theme' => 'dmu_formatter',
+        '#info' => [
+          'id' => $id,
+          'label' => $formatter['label'],
+          'description' => $formatter['description'] ?: NULL,
+          'field_types' => $formatter['field types'],
+        ],
+      ];
+      $this->writeClass($target, $this->parse($render));
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFieldWidgetInfo.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFieldWidgetInfo.php
new file mode 100644 (file)
index 0000000..f69565e
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Drupal\drupalmoduleupgrader\Utility\StringTransformTrait;
+
+/**
+ * @Converter(
+ *  id = "hook_field_widget_info",
+ *  description = @Translation("Creates formatter class templates from hook_field_widget_info()."),
+ *  hook = "hook_field_widget_info"
+ * )
+ */
+class HookFieldWidgetInfo extends ConverterBase {
+
+  use StringTransformTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    try {
+      $widgets = $this->executeHook($target, $this->pluginDefinition['hook']);
+    }
+    catch (\LogicException $e) {
+      $this->logger->warning($e->getMessage(), [
+        'target' => $target->id(),
+        'hook' => $this->pluginDefinition['hook'],
+      ]);
+      return;
+    }
+
+    foreach ($widgets as $id => $widget) {
+      $render = [
+        '#module' => $target->id(),
+        '#class' => $this->toTitleCase($id),
+        '#theme' => 'dmu_widget',
+        '#info' => [
+          'id' => $id,
+          'label' => $widget['label'],
+          'description' => $widget['description'] ?: NULL,
+          'field_types' => $widget['field types'],
+        ],
+      ];
+      $this->writeClass($target, $this->parse($render));
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFormAlter.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFormAlter.php
new file mode 100644 (file)
index 0000000..0ee54c2
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+
+/**
+ * @Converter(
+ *  id = "hook_form_alter",
+ *  description = @Translation("Corrects hook_form_alter() function signatures.")
+ * )
+ */
+class HookFormAlter extends ConverterBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    $indexer = $target->getIndexer('function');
+
+    // @FIXME This is not working (returns empty result set)...don't know why.
+    $alter_hooks = $indexer
+      ->getQuery()
+      ->condition(db_or()
+        ->condition('id', $target->id() . '_form_alter')
+        ->condition('id', db_like($target->id() . '_form_%_alter'), 'LIKE')
+      )
+      ->execute();
+
+    foreach ($alter_hooks as $alter_hook) {
+      /** @var \Pharborist\Functions\FunctionDeclarationNode $function */
+      $function = $indexer->get($alter_hook->id);
+
+      $parameters = $function->getParameters();
+      if (sizeof($parameters) > 1) {
+        $parameters[1]->setTypeHint('\Drupal\Core\Form\FormStateInterface');
+        $target->save($function);
+      }
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookInit.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookInit.php
new file mode 100644 (file)
index 0000000..0d13efd
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+
+/**
+ * @Converter(
+ *  id = "hook_init",
+ *  description = @Translation("Converts Drupal 7's hook_init() to an EventSubscriber."),
+ *  hook = "hook_init"
+ * )
+ */
+class HookInit extends ConverterBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    $this->writeService($target, 'init_subscriber', [
+      'class' => 'Drupal\\' . $target->id() . '\\EventSubscriber\\InitSubscriber',
+      'tags' => [
+        [ 'name' => 'event_subscriber' ],
+      ],
+    ]);
+
+    $render = [
+      '#theme' => 'dmu_event_subscriber',
+      '#module' => $target->id(),
+      '#class' => 'InitSubscriber',
+      '#event' => 'KernelEvents::REQUEST',
+    ];
+    $subscriber = $this->parse($render);
+    $target
+      ->getIndexer('function')
+      ->get('hook_init')
+      ->cloneAsMethodOf($subscriber)
+      ->setName('onEvent');
+    $this->writeClass($target, $subscriber);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookLibrary.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookLibrary.php
new file mode 100644 (file)
index 0000000..d1a8e5f
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+
+/**
+ * @Converter(
+ *  id = "hook_library",
+ *  description = @Translation("Converts Drupal 7's hook_library() to YAML."),
+ *  hook = "hook_library"
+ * )
+ */
+class HookLibrary extends ConverterBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    try {
+      $libraries = $this->executeHook($target, $this->pluginDefinition['hook']);
+    }
+    catch (\LogicException $e) {
+      return;
+    }
+
+    foreach ($libraries as $id => &$lib) {
+      if (isset($lib['website'])) {
+        $lib['remote'] = $lib['website'];
+        unset($lib['website']);
+      }
+
+      if (isset($lib['dependencies'])) {
+        $lib['dependencies'] = array_map(function(array $dependency) {
+          if ($dependency[0] == 'system') {
+            $dependency[0] == 'core';
+          }
+          return implode('/', $dependency);
+        }, $lib['dependencies']);
+      }
+    }
+
+    $this->writeInfo($target, 'libraries', $libraries);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookMenuAlter.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookMenuAlter.php
new file mode 100644 (file)
index 0000000..7a3c665
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\ParameterNode;
+
+/**
+ * @Converter(
+ *  id = "hook_menu_alter",
+ *  description = @Translation("Creates boilerplate for logic that formerly belonged in hook_menu_alter()."),
+ *  hook = "hook_menu_alter",
+ *  fixme = @Translation("hook_menu_alter() is gone in Drupal 8. You will have to port its
+functionality manually. The are several mechanisms for this:
+
+To alter routes, you must implement a route subscriber class. An empty one
+has been generated for you in src/Routing/RouteSubscriber.php.
+
+To alter menu link definitions, see hook_menu_links_discovered_alter(). An
+empty implementation has been created at the end of this file.
+
+To alter local task definitions, see hook_menu_local_tasks_alter(). An
+empty implementation has been created for you at the end of this file.
+
+To alter local actions, see hook_menu_local_actions_alter(). An
+empty implementation has been created for you at the end of this file.
+
+Contextual links are altered during rendering only. See
+hook_contextual_links_view_alter(). An empty implementation has been
+created for you at the end of this file."),
+ *  documentation = {
+ *    "https://www.drupal.org/node/2118147#alter",
+ *    "https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Menu%21menu.api.php/function/hook_menu_links_discovered_alter/8",
+ *    "https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Menu%21menu.api.php/function/hook_menu_local_tasks_alter/8",
+ *    "https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Menu%21menu.api.php/function/hook_menu_local_actions_alter/8",
+ *    "https://api.drupal.org/api/drupal/core%21modules%21contextual%21contextual.api.php/function/hook_contextual_links_view_alter/8",
+ *  }
+ * )
+ */
+class HookMenuAlter extends ConverterBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    $target
+      ->getIndexer('function')
+      ->get($this->pluginDefinition['hook'])
+      ->setDocComment($this->buildFixMe(NULL, [], self::DOC_COMMENT));
+
+    $render = [
+      '#theme' => 'dmu_route_subscriber',
+      '#module' => $target->id(),
+    ];
+    $this->writeClass($target, $this->parse($render));
+
+    $alterable = ParameterNode::create('data');
+    $alterable->setTypeHint('array')->setReference(TRUE);
+
+    $parameter = clone $alterable;
+    $this
+      ->implement($target, 'menu_links_discovered_alter')
+      ->appendParameter($parameter->setName('links'));
+
+    $parameter = clone $alterable;
+    $this
+      ->implement($target, 'menu_local_tasks_alter')
+      ->appendParameter($parameter->setName('data'))
+      ->appendParameter(ParameterNode::create('route_name'));
+
+    $parameter = clone $alterable;
+    $this
+      ->implement($target, 'menu_local_actions_alter')
+      ->appendParameter($parameter->setName('local_actions'));
+
+    $parameter = clone $alterable;
+    $items = clone $alterable;
+    $function = $this
+      ->implement($target, 'contextual_links_view_alter')
+      ->appendParameter($parameter->setName('element'))
+      ->appendParameter($items->setName('items')->setReference(FALSE));
+
+    $target->save($function);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookNodePrepare.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookNodePrepare.php
new file mode 100644 (file)
index 0000000..44a9a53
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Functions\ParameterNode;
+use Psr\Log\LoggerInterface;
+
+/**
+ * @Converter(
+ *  id = "hook_node_prepare",
+ *  description = @Translation("Converts hook_node_prepare() into hook_ENTITY_TYPE_prepare_form()."),
+ *  hook = "hook_node_prepare",
+ *  dependencies = { "plugin.manager.drupalmoduleupgrader.rewriter" }
+ * )
+ */
+class HookNodePrepare extends ConverterBase {
+
+  /**
+   * @var \Drupal\Component\Plugin\PluginManagerInterface
+   */
+  protected $rewriters;
+
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, TranslationInterface $translator, LoggerInterface $log, PluginManagerInterface $rewriters) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $translator, $log);
+    $this->rewriters = $rewriters;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    /** @var \Pharborist\Functions\FunctionDeclarationNode $function */
+    $function = $target->getIndexer('function')->get('hook_node_prepare');
+
+    // foo_node_prepare() --> foo_node_prepare_form().
+    $function->setName($function->getName() . '_form');
+
+    // The first parameter is a node, so rewrite the function accordingly.
+    $this->rewriters
+      ->createInstance('_entity:node')
+      ->rewrite($function->getParameterAtIndex(0));
+
+    // Create the $operation parameter.
+    $function->appendParameter(ParameterNode::create('operation'));
+
+    // Create the $form_state parameter.
+    $form_state = ParameterNode::create('form_state')->setTypeHint('\Drupal\Core\Form\FormStateInterface');
+    $function->appendParameter($form_state);
+
+    $target->save($function);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookPermission.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookPermission.php
new file mode 100644 (file)
index 0000000..6949f90
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\Component\Render\MarkupInterface;
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+
+/**
+ * @Converter(
+ *  id = "hook_permission",
+ *  description = @Translation("Converts static implementations of hook_permission() to YAML."),
+ *  hook = "hook_permission"
+ * )
+ */
+class HookPermission extends ConverterBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    $permissions = $this->executeHook($target, $this->pluginDefinition['hook']);
+    $this->writeInfo($target, 'permissions', $this->castTranslatables($permissions));
+  }
+
+  /**
+   * Casts translatable string objects in a permissions array to strings.
+   *
+   * @param array $permissions
+   *   An array of permissions, as returned by hook_permission().
+   *
+   * @return array
+   *   The permissions array, with all TranslatableString objects casted to
+   *   strings.
+   */
+  protected function castTranslatables($permissions) {
+    array_walk_recursive($permissions, function (&$value) {
+      if ($value instanceof MarkupInterface) {
+        $value = (string) $value;
+      }
+    });
+
+    return $permissions;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookURLOutboundAlter.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookURLOutboundAlter.php
new file mode 100644 (file)
index 0000000..09a4e07
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+
+/**
+ * @Converter(
+ *  id = "hook_url_outbound_alter",
+ *  description = @Translation("Converts hook_url_outbound_alter() to a service."),
+ *  hook = "hook_url_outbound_alter"
+ * )
+ */
+class HookURLOutboundAlter extends ConverterBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    $this->writeService($target, 'outbound_path_processor', [
+      'class' => 'Drupal\\' . $target->id() . '\\OutboundPathProcessor',
+      'tags' => [
+        [ 'name' => 'path_processor_outbound' ],
+      ],
+    ]);
+
+    $render = [
+      '#theme' => 'dmu_outbound_path_processor',
+      '#module' => $target->id(),
+    ];
+    $processor = $this->parse($render);
+    $target
+      ->getIndexer('function')
+      ->get('hook_url_outbound_alter')
+      ->cloneAsMethodOf($processor)
+      ->setName('processOutbound');
+    $this->writeClass($target, $processor);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookUserLogin.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookUserLogin.php
new file mode 100644 (file)
index 0000000..70c4782
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\DocCommentNode;
+use Pharborist\Types\ArrayNode;
+use Psr\Log\LoggerInterface;
+
+/**
+ * @Converter(
+ *  id = "hook_user_login",
+ *  description = @Translation("Alters signatures of hook_user_login() implementations."),
+ *  hook = "hook_user_login",
+ *  dependencies = { "plugin.manager.drupalmoduleupgrader.rewriter" }
+ * )
+ */
+class HookUserLogin extends ConverterBase {
+
+  /**
+   * @var \Drupal\Component\Plugin\PluginManagerInterface
+   */
+  protected $rewriters;
+
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, TranslationInterface $translator, LoggerInterface $log, PluginManagerInterface $rewriters) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $translator, $log);
+    $this->rewriters = $rewriters;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    /** @var \Pharborist\Functions\FunctionDeclarationNode $function */
+    $function = $target->getIndexer('function')->get('hook_user_login');
+    // The $edit parameter is defunct in Drupal 8, but we'll leave it in
+    // there as an empty array to prevent errors, and move it to the back
+    // of the line.
+    /** @var \Pharborist\Functions\ParameterNode $edit */
+    $edit = $function->getParameterList()->shift()->setReference(FALSE)->setValue(ArrayNode::create([]));
+    $function->appendParameter($edit);
+
+    // Slap a FIXME on the hook implementation, informing the developer that
+    // $edit and $category are dead.
+    $comment = $function->getDocComment();
+    $comment_text = $comment ? $comment->getCommentText() : '';
+    if ($comment_text) {
+      $comment_text .= "\n\n";
+    }
+    $comment_text .= <<<'END'
+@FIXME
+The $edit parameter is gone in Drupal 8. It has been left here in order to
+prevent 'undefined variable' errors, but it will never actually be passed to
+this hook. You'll need to modify this function and remove every reference to it.
+END;
+    $function->setDocComment(DocCommentNode::create($comment_text));
+
+    $rewriter = $this->rewriters->createInstance('_rewriter:user');
+    $this->rewriteFunction($rewriter, $function->getParameterAtIndex(0), $target);
+    $target->save($function);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookWatchdog.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookWatchdog.php
new file mode 100644 (file)
index 0000000..aff15a2
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+
+/**
+ * @Converter(
+ *  id = "hook_watchdog",
+ *  description = @Translation("Converts hook_watchdog() to an implementation of \\Psr\\Log\\LoggerInterface."),
+ *  hook = "hook_watchdog"
+ * )
+ */
+class HookWatchdog extends ConverterBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    $this->writeService($target, 'default_logger', [
+      'class' => 'Drupal\\' . $target->id() . '\\Logger\\DefaultLogger',
+      'tags' => [
+        [ 'name' => 'logger' ],
+      ],
+    ]);
+
+    $render = [
+      '#theme' => 'dmu_logger',
+      '#module' => $target->id(),
+    ];
+    $this->writeClass($target, $this->parse($render));
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/InfoToYAML.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/InfoToYAML.php
new file mode 100644 (file)
index 0000000..2d0dca4
--- /dev/null
@@ -0,0 +1,97 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+
+/**
+ * @Converter(
+ *  id = "info",
+ *  description = @Translation("Converts Drupal 7 info files to Drupal 8.")
+ * )
+ */
+class InfoToYAML extends ConverterBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    $info_file = $target->getPath('.info');
+
+    $info = self::parseInfo($info_file);
+    $info['core'] = '8.x';
+    $info['type'] = 'module';
+
+    if (isset($info['dependencies'])) {
+      // array_values() is called in order to reindex the array. Issue #2340207
+      $info['dependencies'] = array_values(array_diff($info['dependencies'], ['ctools', 'list']));
+    }
+
+    unset($info['files'], $info['configure'], $info['datestamp'], $info['version'], $info['project']);
+    $this->writeInfo($target, 'info', $info);
+  }
+
+  /**
+   * Parses a D7 info file. This is copied straight outta the D7 function 
+   * drupal_parse_info_format().
+   */
+  public static function parseInfo($file) {
+    $info = [];
+    $constants = get_defined_constants();
+    $data = file_get_contents($file);
+
+    if (preg_match_all('
+      @^\s*                           # Start at the beginning of a line, ignoring leading whitespace
+      ((?:
+        [^=;\[\]]|                    # Key names cannot contain equal signs, semi-colons or square brackets,
+        \[[^\[\]]*\]                  # unless they are balanced and not nested
+      )+?)
+      \s*=\s*                         # Key/value pairs are separated by equal signs (ignoring white-space)
+      (?:
+        ("(?:[^"]|(?<=\\\\)")*")|     # Double-quoted string, which may contain slash-escaped quotes/slashes
+        (\'(?:[^\']|(?<=\\\\)\')*\')| # Single-quoted string, which may contain slash-escaped quotes/slashes
+        ([^\r\n]*?)                   # Non-quoted string
+      )\s*$                           # Stop at the next end of a line, ignoring trailing whitespace
+      @msx', $data, $matches, PREG_SET_ORDER)) {
+      foreach ($matches as $match) {
+        // Fetch the key and value string.
+        $i = 0;
+        foreach (array('key', 'value1', 'value2', 'value3') as $var) {
+          $$var = isset($match[++$i]) ? $match[$i] : '';
+        }
+        $value = stripslashes(substr($value1, 1, -1)) . stripslashes(substr($value2, 1, -1)) . $value3;
+
+        // Parse array syntax.
+        $keys = preg_split('/\]?\[/', rtrim($key, ']'));
+        $last = array_pop($keys);
+        $parent = &$info;
+
+        // Create nested arrays.
+        foreach ($keys as $key) {
+          if ($key == '') {
+            $key = count($parent);
+          }
+          if (!isset($parent[$key]) || !is_array($parent[$key])) {
+            $parent[$key] = array();
+          }
+          $parent = &$parent[$key];
+        }
+
+        // Handle PHP constants.
+        if (isset($constants[$value])) {
+          $value = $constants[$value];
+        }
+
+        // Insert actual value.
+        if ($last == '') {
+          $last = count($parent);
+        }
+        $parent[$last] = $value;
+      }
+    }
+
+    return $info;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Links.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Links.php
new file mode 100644 (file)
index 0000000..386fed4
--- /dev/null
@@ -0,0 +1,128 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\Routing\HookMenu;
+use Drupal\drupalmoduleupgrader\Routing\LinkBinding\LinkBinding;
+use Drupal\drupalmoduleupgrader\Routing\LinkBinding\LinkBindingFactory;
+use Drupal\drupalmoduleupgrader\Routing\LinkBinding\LocalActionLinkBinding;
+use Drupal\drupalmoduleupgrader\Routing\LinkBinding\LocalTaskLinkBinding;
+use Drupal\drupalmoduleupgrader\Routing\LinkBinding\MenuLinkBinding;
+use Drupal\drupalmoduleupgrader\Routing\LinkIndex;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Drupal\drupalmoduleupgrader\Utility\Filter\ContainsLogicFilter;
+use Pharborist\DocCommentNode;
+use Psr\Log\LoggerInterface;
+
+/**
+ * @Converter(
+ *  id = "links",
+ *  description = @Translation("Converts Drupal 7's hook_menu() links to plugin definitions."),
+ *  hook = "hook_menu",
+ *  fixme = @Translation("@FIXME
+This implementation of hook_menu() cannot be automatically converted because
+it contains logic (i.e., branching statements, function calls, object
+instantiation, etc.) You will need to convert it manually. Sorry!
+
+For more information on how to convert hook_menu() to Drupal 8's new routing
+and linking systems, see https://api.drupal.org/api/drupal/core%21includes%21menu.inc/group/menu/8"),
+ *  dependencies = { "plugin.manager.drupalmoduleupgrader.route", "drupalmoduleupgrader.link_binding" }
+ * )
+ */
+class Links extends ConverterBase {
+
+  /**
+   * @var PluginManagerInterface
+   */
+  protected $routeConverters;
+
+  /**
+   * @var LinkBindingFactory
+   */
+  protected $linkBinding;
+
+  /**
+   * Constructs a Links object.
+   *
+   * @param array $configuration
+   *   Additional configuration for the plugin.
+   * @param string $plugin_id
+   *   The plugin ID, will be "Links".
+   * @param string $plugin_definition
+   *   The plugin definition as derived from the annotations.
+   * @param \Drupal\Component\Plugin\PluginManagerInterface $route_converters
+   *  The plugin manager for route converters, used by HookMenu.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, TranslationInterface $translator, LoggerInterface $log, PluginManagerInterface $route_converters, LinkBindingFactory $link_binding) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $translator, $log);
+    $this->routeConverters = $route_converters;
+    $this->linkBinding = $link_binding;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    // If the hook implementation contains logic, we cannot convert it and
+    // that's that. So we'll leave a FIXME and bail out.
+    /** @var \Pharborist\Functions\FunctionDeclarationNode $hook */
+    $hook = $target->getIndexer('function')->get('hook_menu');
+    if ($hook->is(new ContainsLogicFilter)) {
+      $hook->setDocComment(DocCommentNode::create($this->pluginDefinition['fixme']));
+      $target->save($hook);
+      return;
+    }
+
+    // Links are split out by group because there are separate config files
+    // for each link type.
+    $links = [
+      'menu' => new LinkIndex(),
+      'task' => new LinkIndex(),
+      'action' => new LinkIndex(),
+      'contextual' => new LinkIndex(),
+    ];
+
+    $hook_menu = new HookMenu($target, $this->routeConverters);
+    foreach ($hook_menu->getSourceRoutes()->getAllLinks() as $path => $source) {
+      /** @var LinkBinding $binding */
+      $binding = $this->linkBinding->create($source, $hook_menu->getDestinationRoute($path));
+
+      // Skip if the converter wasn't able to find a destination.
+      $destination = $binding->getDestination();
+      if (empty($destination)) {
+        continue;
+      }
+
+      if ($binding instanceof MenuLinkBinding) {
+        $links['menu']->addBinding($binding);
+      }
+      elseif ($binding instanceof LocalTaskLinkBinding) {
+        $links['task']->addBinding($binding);
+      }
+      elseif ($binding instanceof LocalActionLinkBinding) {
+        $links['action']->addBinding($binding);
+      }
+      elseif ($source->isContextualLink()) {
+        $links['contextual']->addBinding($binding);
+      }
+    }
+
+    $links = array_map(function(LinkIndex $index) {
+      return $index->build();
+    }, $links);
+
+    foreach ($links['contextual'] as $link) {
+      $link['group'] = $target->id();
+    }
+
+    foreach ($links as $group => $data) {
+      if ($data) {
+        $this->writeInfo($target, 'links.' . $group, $data);
+      }
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/PSR4.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/PSR4.php
new file mode 100644 (file)
index 0000000..67b621b
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Objects\ClassNode;
+use Pharborist\RootNode;
+use Pharborist\WhitespaceNode;
+
+/**
+ * @Converter(
+ *  id = "PSR4",
+ *  description = @Translation("Moves classes into PSR-4 directory structure.")
+ * )
+ */
+class PSR4 extends ConverterBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isExecutable(TargetInterface $target) {
+    return (boolean) $target->getIndexer('class')->getQuery()->countQuery()->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    $target
+      ->getIndexer('class')
+      ->getAll()
+      ->each(function(ClassNode $class) use ($target) {
+        $this->writeClass($target, self::toPSR4($target, $class));
+      });
+  }
+
+  /**
+   * Utility method to PSR4-ify a class. It'll move the class into its own file
+   * in the given module's namespace. The class is modified in-place, so you
+   * should clone it before calling this function if you want to make a PSR-4
+   * *copy* of it.
+   *
+   * @param \Drupal\drupalmoduleupgrader\TargetInterface $target
+   *  The module which will own the class.
+   * @param \Pharborist\ClassNode $class
+   *  The class to modify.
+   *
+   * @return \Pharborist\ClassNode
+   *  The modified class, returned for convenience.
+   */
+  public static function toPSR4(TargetInterface $target, ClassNode $class) {
+    $ns = 'Drupal\\' . $target->id();
+    RootNode::create($ns)->getNamespace($ns)->append($class->remove());
+    WhitespaceNode::create("\n\n")->insertBefore($class);
+
+    return $class;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Routing.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Routing.php
new file mode 100644 (file)
index 0000000..8b88019
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\Routing\HookMenu;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Drupal\drupalmoduleupgrader\Utility\Filter\ContainsLogicFilter;
+use Pharborist\DocCommentNode;
+use Psr\Log\LoggerInterface;
+
+/**
+ * @Converter(
+ *  id = "routing",
+ *  description = @Translation("Converts parts of hook_menu() to the Drupal 8 routing system."),
+ *  hook = "hook_menu",
+ *  fixme = @Translation("@FIXME
+This implementation of hook_menu() cannot be automatically converted because
+it contains logic (i.e., branching statements, function calls, object
+instantiation, etc.) You will need to convert it manually. Sorry!
+
+For more information on how to convert hook_menu() to Drupal 8's new routing
+and linking systems, see https://api.drupal.org/api/drupal/core%21includes%21menu.inc/group/menu/8"),
+ *  dependencies = { "plugin.manager.drupalmoduleupgrader.route" }
+ * )
+ */
+class Routing extends ConverterBase {
+
+  /**
+   * The route converters' plugin manager.
+   *
+   * @var \Drupal\Component\Plugin\PluginManagerInterface
+   */
+  protected $routeConverters;
+
+  /**
+   * Constructs a Routing object.
+   *
+   * @param array $configuration
+   *   Additional configuration for the plugin.
+   * @param string $plugin_id
+   *   The plugin ID, will be "Links".
+   * @param mixed $plugin_definition
+   *   The plugin definition as derived from the annotations.
+   *
+   * @param PluginManagerInterface $route_converters
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, TranslationInterface $translator, LoggerInterface $log, PluginManagerInterface $route_converters) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $translator, $log);
+    $this->routeConverters = $route_converters;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    // If the hook implementation contains logic, we cannot convert it and
+    // that's that. So we'll leave a FIXME and bail out.
+    /** @var \Pharborist\Functions\FunctionDeclarationNode $hook */
+    $hook = $target->getIndexer('function')->get('hook_menu');
+    if ($hook->is(new ContainsLogicFilter)) {
+      $hook->setDocComment(DocCommentNode::create($this->pluginDefinition['fixme']));
+      $target->save($hook);
+      return;
+    }
+
+    $hook_menu = new HookMenu($target, $this->routeConverters);
+    foreach ($hook_menu->getSourceRoutes() as $path => $route) {
+      /** @var \Drupal\drupalmoduleupgrader\Routing\Drupal7\RouteWrapper $route */
+      if ($route->containsKey('page callback')) {
+        $plugin_id = $this->routeConverters->hasDefinition($route['page callback']) ? $route['page callback'] : 'default';
+        /** @var \Drupal\drupalmoduleupgrader\Routing\RouteConverterInterface $converter */
+        $this->routeConverters->createInstance($plugin_id)->buildRoute($target, $route);
+      }
+    }
+
+    $routing = [];
+    foreach ($hook_menu->getDestinationRoutes() as $name => $route) {
+      $routing[$name] = [
+        'path' => $route->getPath()->__toString(),
+        'defaults' => $route->getDefaults(),
+        'requirements' => $route->getRequirements(),
+      ];
+    }
+    $this->writeInfo($target, 'routing', $routing);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Tests.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Tests.php
new file mode 100644 (file)
index 0000000..d3e8b66
--- /dev/null
@@ -0,0 +1,201 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Drupal\drupalmoduleupgrader\Utility\Filter\ContainsLogicFilter;
+use Pharborist\DocCommentNode;
+use Pharborist\Filter;
+use Pharborist\Objects\ClassMemberNode;
+use Pharborist\Objects\ClassMethodCallNode;
+use Pharborist\Objects\ClassNode;
+use Pharborist\Parser;
+use Pharborist\RootNode;
+use Pharborist\Types\StringNode;
+use Pharborist\WhitespaceNode;
+
+/**
+ * @Converter(
+ *  id = "tests",
+ *  description = @Translation("Modifies test classes.")
+ * )
+ */
+class Tests extends ConverterBase {
+
+  private $target;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isExecutable(TargetInterface $target) {
+    foreach (['DrupalTestCase', 'DrupalWebTestCase'] as $parent_class) {
+      if ($target->getIndexer('class')->getQuery()->condition('parent', $parent_class)->countQuery()->execute()) {
+        return TRUE;
+      }
+    }
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    $this->target = $target;
+
+    $mapping = [
+      'DrupalWebTestCase' => 'convertWeb',
+      'AJAXTestCase' => 'convertAjax',
+    ];
+    foreach ($mapping as $parent_class => $convert_method) {
+      $test_files = $target->getIndexer('class')->getQuery(['file'])->condition('parent', $parent_class)->execute()->fetchCol();
+      foreach ($test_files as $test_file) {
+        /** @var \Pharborist\Objects\Classnode[] $tests */
+        $tests = $target->open($test_file)->find(Filter::isInstanceOf('\Pharborist\Objects\SingleInheritanceNode'))->toArray();
+        foreach ($tests as $test) {
+          if ((string) $test->getExtends() === $parent_class) {
+            $this->$convert_method($test);
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Converts a single web test.
+   *
+   * @param \Pharborist\Objects\ClassNode $test
+   */
+  public function convertWeb(ClassNode $test) {
+    $test->setExtends('\Drupal\simpletest\WebTestBase');
+    $this->convertInfo($test);
+    $this->setModules($test);
+    $this->setProfile($test);
+    $this->move($test);
+  }
+
+  /**
+   * Converts the test's getInfo() method to an annotation.
+   *
+   * @param \Pharborist\Objects\ClassNode $test
+   */
+  private function convertInfo(ClassNode $test) {
+    $info = $this->extractInfo($test);
+
+    if ($info) {
+      $comment = '';
+      $comment .= $info['description'] . "\n\n";
+      $comment .= '@group ' . $this->target->id();
+
+      if (isset($info['dependencies'])) {
+        $comment .= "\n";
+        foreach ($info['dependencies'] as $module) {
+          $comment .= '@requires module . ' . $module . "\n";
+        }
+      }
+
+      $test->setDocComment(DocCommentNode::create($comment));
+    }
+    else {
+      $this->log->error('Could not get info for test {class}.', [
+        'class' => $test->getName(),
+      ]);
+    }
+  }
+
+  /**
+   * Extracts the return value of the test's getInfo() method, if there's no
+   * logic in the method.
+   *
+   * @param \Pharborist\Objects\ClassNode $test
+   *
+   * @return array|NULL
+   */
+  private function extractInfo(ClassNode $test) {
+    if ($test->hasMethod('getInfo')) {
+      $info = $test->getMethod('getInfo');
+
+      if (! $info->is(new ContainsLogicFilter())) {
+        return eval($info->getBody()->getText());
+      }
+    }
+  }
+
+  /**
+   * Sets the test's $modules property.
+   *
+   * @param \Pharborist\Objects\ClassNode $test
+   */
+  private function setModules(ClassNode $test) {
+    $modules = $this->extractModules($test);
+    if ($modules) {
+      // @todo Use ClassNode::createProperty() when #124 lands in Pharborist
+      $property = Parser::parseSnippet('class Foo { public static $modules = ["' . implode('", "', $modules) . '"]; }')
+        ->getBody()
+        ->firstChild()
+        ->remove();
+      $test->appendProperty($property);
+    }
+  }
+
+  /**
+   * Extracts every module required by a web test by scanning its calls
+   * to parent::setUp().
+   *
+   * @param \Pharborist\Objects\ClassNode $test
+   *
+   * @return string[]
+   *  Array of modules set up by this module.
+   */
+  private function extractModules(ClassNode $test) {
+    $modules = [];
+
+    $test
+      ->find(Filter::isClassMethodCall('parent', 'setUp'))
+      ->filter(function(ClassMethodCallNode $call) {
+        return (sizeof($call->getArguments()) > 0);
+      })
+      ->each(function(ClassMethodCallNode $call) use (&$modules) {
+        foreach ($call->getArguments() as $argument) {
+          if ($argument instanceof StringNode) {
+            $modules[] = $argument->toValue();
+          }
+        }
+
+        $call->clearArguments();
+      });
+
+    return array_unique($modules);
+  }
+
+  /**
+   * Sets the test's $profile property.
+   *
+   * @param \Pharborist\Objects\ClassNode $test
+   */
+  private function setProfile(ClassNode $test) {
+    if (! $test->hasProperty('profile')) {
+      $test->appendProperty(ClassMemberNode::create('profile', StringNode::create("'standard'"), 'protected'));
+    }
+  }
+
+  public function move(ClassNode $test) {
+    $ns = 'Drupal\\' . $this->target->id() . '\\Tests';
+    RootNode::create($ns)->getNamespace($ns)->append($test->remove());
+    WhitespaceNode::create("\n\n")->insertBefore($test);
+
+    $this->writeClass($this->target, $test);
+  }
+
+  /**
+   * Converts a single Ajax test.
+   *
+   * @param \Pharborist\Objects\ClassNode $test
+   */
+  public function convertAjax(ClassNode $test) {
+    $test->setExtends('\Drupal\system\Tests\Ajax\AjaxTestBase');
+    $this->setModules($test);
+    $this->move($test);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/UnitTests.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/UnitTests.php
new file mode 100644 (file)
index 0000000..f6e0fb8
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\DocCommentNode;
+use Pharborist\RootNode;
+use Pharborist\WhitespaceNode;
+
+/**
+ * @Converter(
+ *  id = "unit_tests",
+ *  description = @Translation("Modifies unit test classes.")
+ * )
+ */
+class UnitTests extends ConverterBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isExecutable(TargetInterface $target) {
+    return $target->getIndexer('class')->getQuery()->condition('parent', 'DrupalUnitTestCase')->countQuery()->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    $unit_tests = [];
+    $test_files = $target->getIndexer('class')->getQuery(['file'])->condition('parent', 'DrupalUnitTestCase')->execute()->fetchCol();
+    foreach ($test_files as $test_file) {
+      /** @var \Pharborist\Objects\Classnode[] $tests */
+      $tests = $target->open($test_file)->find(Filter::isInstanceOf('\Pharborist\Objects\SingleInheritanceNode'))->toArray();
+      foreach ($tests as $test) {
+        if ((string) $test->getExtends() === 'DrupalUnitTestCase') {
+          $unit_tests[] = $test;
+        }
+      }
+    }
+
+    /** @var \Pharborist\Objects\ClassNode $unit_test */
+    foreach ($unit_tests as $unit_test) {
+      $unit_test->setExtends('\Drupal\Tests\UnitTestCase');
+
+      $comment_text = <<<END
+@FIXME
+Unit tests are now written for the PHPUnit framework. You will need to refactor
+this test in order for it to work properly.
+END;
+      $comment = DocCommentNode::create($comment_text);
+      $unit_test->setDocComment($comment);
+
+      $ns = 'Drupal\Tests\\' . $target->id() . '\Unit';
+      $doc = RootNode::create($ns)->getNamespace($ns)->append($unit_test->remove());
+      WhitespaceNode::create("\n\n")->insertBefore($unit_test);
+
+      $this->write($target, 'tests/src/Unit/' . $unit_test->getName() . '.php', "<?php\n\n$doc");
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/UserHooks.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/UserHooks.php
new file mode 100644 (file)
index 0000000..98425dd
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;
+
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\RewriterInterface;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\DocCommentNode;
+use Pharborist\Types\NullNode;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * @Converter(
+ *  id = "user_hooks",
+ *  description = @Translation("Alters implementations of hook_user_insert(), hook_user_presave(), and hook_user_update()."),
+ *  hook = {
+ *    "hook_user_insert",
+ *    "hook_user_presave",
+ *    "hook_user_update"
+ *  }
+ * )
+ */
+class UserHooks extends ConverterBase {
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\RewriterInterface
+   */
+  protected $rewriter;
+
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, TranslationInterface $translator, LoggerInterface $log, RewriterInterface $rewriter) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $translator, $log);
+    $this->rewriter = $rewriter;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('string_translation'),
+      $container->get('logger.factory')->get('drupalmoduleupgrader'),
+      $container->get('plugin.manager.drupalmoduleupgrader.rewriter')->createInstance('_rewriter:user')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert(TargetInterface $target) {
+    $indexer = $target->getIndexer('function');
+
+    $hooks = array_filter($this->pluginDefinition['hook'], [$indexer, 'has']);
+    foreach ($hooks as $hook) {
+      /** @var \Pharborist\Functions\FunctionDeclarationNode $function */
+      $function = $indexer->get($hook);
+      // The $edit parameter is defunct in Drupal 8, but we'll leave it in
+      // there as an empty array to prevent errors, and move it to the back
+      // of the line.
+      /** @var \Pharborist\Functions\ParameterNode $edit */
+      $edit = $function->getParameterList()->shift()->setReference(FALSE)->setValue(NullNode::create());
+      $function->appendParameter($edit);
+
+      // Slap a FIXME on the hook implementation, informing the developer that
+      // $edit and $category are dead.
+      $comment = $function->getDocComment();
+      $comment_text = $comment ? $comment->getCommentText() : '';
+      if ($comment_text) {
+        $comment_text .= "\n\n";
+      }
+      $comment_text .= <<<'END'
+@FIXME
+The $edit and $category parameters are gone in Drupal 8. They have been left
+here in order to prevent 'undefined variable' errors, but they will never
+actually be passed to this hook. You'll need to modify this function and
+remove every reference to them.
+END;
+      $function->setDocComment(DocCommentNode::create($comment_text));
+
+      $this->rewriteFunction($this->rewriter, $function->getParameterAtIndex(0), $target);
+      $target->save($function);
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/CreateClass.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/CreateClass.php
new file mode 100644 (file)
index 0000000..45d4734
--- /dev/null
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer\CreateClass.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer;
+
+use Drupal\drupalmoduleupgrader\FixerBase;
+use Pharborist\DocCommentNode;
+use Pharborist\Objects\ClassNode;
+use Pharborist\Parser;
+use Pharborist\RootNode;
+use Pharborist\Token;
+use Symfony\Component\Filesystem\Filesystem;
+
+/**
+ * @Fixer(
+ *  id = "create_class"
+ * )
+ */
+class CreateClass extends FixerBase {
+
+  /**
+   * @var \Symfony\Component\Filesystem\Filesystem
+   */
+  protected $fs;
+
+  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->fs = new Filesystem();
+  }
+
+  public function execute() {
+    $ns = $this->extractNS($this->configuration['class']);
+    $class = $this->extractLocal($this->configuration['class']);
+
+    $doc = RootNode::create($ns);
+    $ns = $doc->getNamespace($ns);
+    Token::newline()->insertBefore($ns);
+    Token::newline()->appendTo($ns);
+    $class = ClassNode::create($class);
+
+    if ($parent = $this->configuration['parent']) {
+      Parser::parseSnippet('use ' . ltrim($parent, '\\') . ';')
+        ->appendTo($ns)
+        ->after(Token::newline());
+      $class->setExtends($this->extractLocal($parent));
+    }
+
+    $interfaces = (array) $this->configuration['interfaces'];
+    foreach ($interfaces as $interface) {
+      Parser::parseSnippet('use ' . ltrim($interface, '\\') . ';')
+        ->appendTo($ns)
+        ->after(Token::newline());
+    }
+    $class->setImplements(array_map([ $this, 'extractLocal' ], $interfaces));
+
+    if (isset($this->configuration['doc'])) {
+      $class->setDocComment(DocCommentNode::create($this->configuration['doc']));
+    }
+
+    $class->appendTo($ns)->before(Token::newline());
+
+    $destination = $this->getUnaliasedPath($this->configuration['destination']);
+    $dir = subStr($destination, 0, strrPos($destination, '/'));
+    $this->fs->mkdir($dir);
+    file_put_contents($destination, $doc->getText());
+    // Need to store the class' local name as its index identifier because
+    // \Pharborist\Filter::isClass() doesn't support lookup by qualified path.
+    $this->target->getIndexer('class')->addFile($destination);
+  }
+
+  protected function extractLocal($path) {
+    return subStr($path, strrPos($path, '\\') + 1);
+  }
+
+  protected function extractNS($path) {
+    $path = ltrim($path, '\\');
+    return subStr($path, 0, strrPos($path, '\\'));
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/Define.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/Define.php
new file mode 100644 (file)
index 0000000..8a2bd1d
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer\Define.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer;
+
+use Drupal\Component\Serialization\Yaml as YAML;
+use Drupal\Component\Utility\NestedArray;
+use Drupal\drupalmoduleupgrader\FixerBase;
+
+/**
+ * @Fixer(
+ *  id = "define"
+ * )
+ */
+class Define extends FixerBase {
+
+  public function execute() {
+    $file = $this->getUnaliasedPath($this->configuration['in']);
+    $data = file_exists($file) ? YAML::decode(file_get_contents($file)) : [];
+    $keys = explode('/', $this->configuration['key']);
+    NestedArray::setValue($data, $keys, $this->configuration['value']);
+    file_put_contents($file, YAML::encode($data));
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/Delete.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/Delete.php
new file mode 100644 (file)
index 0000000..389ee24
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer\Delete.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer;
+
+use Drupal\drupalmoduleupgrader\FixerBase;
+
+/**
+ * @Fixer(
+ *  id = "delete"
+ * )
+ */
+class Delete extends FixerBase {
+
+  use NodeCollectorTrait;
+
+  public function execute() {
+    foreach ($this->getObjects() as $node) {
+      $node->remove();
+    }
+    $this->target->save();
+
+    // Rebuild the index so it won't contain non-existent crap.
+    $indexer = $this->target->getIndexer($this->configuration['type']);
+    $indexer->clear();
+    $indexer->build();
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/Disable.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/Disable.php
new file mode 100644 (file)
index 0000000..8fc1fa0
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer\Disable.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer;
+
+/**
+ * @Fixer(
+ *  id = "disable"
+ * )
+ */
+class Disable extends Notify {
+
+  use NodeCollectorTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute() {
+    parent::execute();
+
+    foreach ($this->getObjects() as $node) {
+      if ($node->hasRoot()) {
+        $statement = $node->getStatement();
+        $statement->replaceWith($statement->toComment());
+      }
+    }
+
+    $this->target->save();
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/FormCallbackToMethod.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/FormCallbackToMethod.php
new file mode 100644 (file)
index 0000000..a19c2a1
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer\FormCallbackToMethod.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer;
+
+use Drupal\drupalmoduleupgrader\FixerBase;
+
+/**
+ * @Fixer(
+ *  id = "form_callback_to_method"
+ * )
+ */
+class FormCallbackToMethod extends FixerBase {
+
+  public function execute() {
+    /** @var \Pharborist\Functions\FunctionDeclarationNode $callback */
+    $callback = $this
+      ->target
+      ->getIndexer('function')
+      ->get($this->configuration['callback']);
+
+    list ($class, $method_name) = explode('::', $this->configuration['destination']);
+    /** @var \Pharborist\Objects\ClassNode $class */
+    $class = $this
+      ->target
+      ->getIndexer('class')
+      ->get($class);
+
+    $method = $callback->cloneAsMethodOf($class)->setName($method_name);
+
+    $form_interface = new \ReflectionClass('\Drupal\Core\Form\FormInterface');
+    if ($form_interface->hasMethod($method_name)) {
+      $method->matchReflector($form_interface->getMethod($method_name));
+    }
+
+    $this->target->save($method);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/HookToYAML.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/HookToYAML.php
new file mode 100644 (file)
index 0000000..96938d3
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer\HookToYAML.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer;
+
+use Drupal\Component\Serialization\Yaml as YAML;
+use Drupal\drupalmoduleupgrader\FixerBase;
+
+/**
+ * @Fixer(
+ *  id = "hook_to_YAML"
+ * )
+ */
+class HookToYAML extends FixerBase {
+
+  public function execute() {
+    $destination = $this->getUnaliasedPath($this->configuration['destination']);
+    $data = $this->target->executeHook($this->configuration['hook']);
+    file_put_contents($destination, YAML::encode($data));
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/Implement.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/Implement.php
new file mode 100644 (file)
index 0000000..7498ab1
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer\Implement.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer;
+
+use Drupal\drupalmoduleupgrader\FixerBase;
+use Pharborist\DocCommentNode;
+use Pharborist\Objects\ClassMethodNode;
+
+/**
+ * @Fixer(
+ *  id = "implement"
+ * )
+ */
+class Implement extends FixerBase {
+
+  public function execute() {
+    /** @var \Pharborist\Objects\ClassNode $class */
+    $class = $this
+      ->target
+      ->getIndexer('class')
+      ->get($this->configuration['target']);
+
+    // Use reflection to get the method definition.
+    list ($interface, $method) = explode('::', $this->configuration['definition']);
+    $interface = new \ReflectionClass($interface);
+    $method = $interface->getMethod($method);
+
+    $node = ClassMethodNode::create($method->getName());
+    $node->setDocComment(DocCommentNode::create('@inheritdoc'));
+    $class->appendMethod($node);
+    $node->matchReflector($method);
+
+    // @TODO There needs to be a way to implement the method body!
+
+    $this->target->save($class);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/ImplementHook.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/ImplementHook.php
new file mode 100644 (file)
index 0000000..165bf08
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer\ImplementHook.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer;
+
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\drupalmoduleupgrader\FixerBase;
+use Pharborist\DocCommentNode;
+use Pharborist\Functions\FunctionDeclarationNode;
+use Pharborist\Functions\ParameterNode;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * @Fixer(
+ *  id = "implement_hook"
+ * )
+ */
+class ImplementHook extends FixerBase implements ContainerFactoryPluginInterface {
+
+  protected $moduleHandler;
+
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, ModuleHandlerInterface $module_handler) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->moduleHandler = $module_handler;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('module_handler')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute() {
+    $this->moduleHandler->loadInclude($this->configuration['module'], 'php', 'api');
+
+    $hook = $this->configuration['hook'];
+    $function = FunctionDeclarationNode::create($this->target->id() . '_' . $hook);
+    $function->setDocComment(DocCommentNode::create('Implements hook_' . $hook));
+
+    $reflector = new \ReflectionFunction('hook_' . $hook);
+    $function->matchReflector($reflector);
+
+    $module = $this->target->getPath('.module');
+    $doc = $this->target->open($module)->append($function);
+    $this->target->save($doc);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/NodeCollectorTrait.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/NodeCollectorTrait.php
new file mode 100644 (file)
index 0000000..7ae15b0
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer\NodeCollectorTrait.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer;
+
+/**
+ * Trait used by fixers which loop through existing indexer objects and do
+ * things with them.
+ */
+trait NodeCollectorTrait {
+
+  protected function getObjects() {
+    /** @var \Pharborist\NodeCollection $objects */
+    $objects = $this->target->getIndexer($this->configuration['type'])->get($this->configuration['id']);
+
+    if (isset($this->configuration['where'])) {
+      $where = $this->configuration['where'];
+      // If the first character of the filter is an exclamation point, negate it.
+      return ($where{0} == '!' ? $objects->not(subStr($where, 1)) : $objects->filter($where));
+    }
+    else {
+      return $objects;
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/Notify.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/Notify.php
new file mode 100644 (file)
index 0000000..44f8db4
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer\Notify.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer;
+
+use Drupal\drupalmoduleupgrader\FixerBase;
+use Pharborist\DocCommentNode;
+use Pharborist\LineCommentBlockNode;
+use Pharborist\NodeInterface;
+
+/**
+ * @Fixer(
+ *  id = "notify"
+ * )
+ */
+class Notify extends FixerBase {
+
+  use NodeCollectorTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute() {
+    foreach ($this->getObjects() as $node) {
+      $comment = $this->getComment($node);
+      if ($comment) {
+        $comment .= "\n\n";
+      }
+      $this->setComment($node, $comment . $this->configuration['note']);
+    }
+
+    $this->target->save();
+  }
+
+  protected function getComment(NodeInterface $node) {
+    if ($this->supportsDocComments($node)) {
+      /** @var \Pharborist\DocCommentTrait $node */
+      $comment = $node->getDocComment() ?: DocCommentNode::create('');
+      return $comment->getCommentText();
+    }
+    else {
+      return '';
+    }
+  }
+
+  protected function setComment(NodeInterface $node, $comment_text) {
+    if ($this->supportsDocComments($node)) {
+      /** @var \Pharborist\DocCommentTrait $node */
+      $node->setDocComment(DocCommentNode::create($comment_text));
+    }
+    else {
+      LineCommentBlockNode::create($comment_text)->insertBefore($node->getStatement());
+    }
+  }
+
+  /**
+   * Returns if a node supports doc comments by importing DocCommentTrait
+   * anywhere in its lineage.
+   *
+   * @param \Pharborist\NodeInterface $node
+   *
+   * @return boolean
+   */
+  protected function supportsDocComments(NodeInterface $node) {
+    return $this->usesTrait('Pharborist\DocCommentTrait', $node);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/PSR4.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/PSR4.php
new file mode 100644 (file)
index 0000000..984b5fb
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer\PSR4.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer;
+
+use Drupal\drupalmoduleupgrader\FixerBase;
+use Pharborist\Namespaces\NameNode;
+use Pharborist\Parser;
+use Pharborist\RootNode;
+use Pharborist\WhitespaceNode;
+
+/**
+ * @Fixer(
+ *  id = "psr4ify"
+ * )
+ */
+class PSR4 extends FixerBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute() {
+    /** @var \Pharborist\Objects\ClassNode $class */
+    $class = $this
+      ->target
+      ->getIndexer('class')
+      ->get($this->configuration['source']);
+
+    $ns = substr($this->configuration['destination'], 0, strrpos($this->configuration['destination'], '\\'));
+    $doc = RootNode::create($ns);
+    $ns = $doc->getNamespace($ns);
+    WhitespaceNode::create("\n")->appendTo($ns);
+
+    $import = [];
+
+    if ($parent = $class->getExtends()) {
+      $import[] = $parent->getAbsolutePath();
+    }
+
+    $interfaces = $class->getImplementList();
+    if ($interfaces) {
+      foreach ($interfaces->getItems() as $interface) {
+        $import[] = $interface->getAbsolutePath();
+      }
+    }
+
+    foreach ($class->getMethods() as $method) {
+      foreach ($method->getParameters() as $parameter) {
+        $type_hint = $parameter->getTypeHint();
+        if ($type_hint instanceof NameNode) {
+          $import[] = $type_hint->getAbsolutePath();
+        }
+      }
+    }
+
+    foreach (array_unique($import) as $i) {
+      Parser::parseSnippet('use ' . ltrim($i, '\\') . ';')->appendTo($ns);
+      WhitespaceNode::create("\n")->appendTo($ns);
+    }
+
+    WhitespaceNode::create("\n")->appendTo($ns);
+    $class->remove()->appendTo($ns);
+
+    $search_for = ['Drupal\\' . $this->target->id(), '\\'];
+    $replace_with = ['src', '/'];
+    $path = str_replace($search_for, $replace_with, $this->configuration['destination']) . '.php';
+    file_put_contents($this->target->getPath($path), $doc->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Indexer/Classes.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Indexer/Classes.php
new file mode 100644 (file)
index 0000000..e5d9624
--- /dev/null
@@ -0,0 +1,118 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Indexer;
+
+use Drupal\drupalmoduleupgrader\IndexerBase;
+use Drupal\drupalmoduleupgrader\IndexerUsageInterface;
+use Pharborist\Filter;
+use Pharborist\NodeCollection;
+use Pharborist\NodeInterface;
+use Pharborist\Objects\ClassNode;
+use Pharborist\Objects\NewNode;
+use Pharborist\Parser;
+
+/**
+ * @Indexer(
+ *  id = "class"
+ * )
+ */
+class Classes extends IndexerBase implements IndexerUsageInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addFile($path) {
+    $doc = Parser::parseFile($path);
+
+    $doc
+      ->find(Filter::isInstanceOf('\Pharborist\Objects\ClassNode'))
+      ->each([ $this, 'add' ]);
+
+    $doc
+      ->find(Filter::isInstanceOf('\Pharborist\Objects\NewNode'))
+      ->each([ $this, 'add' ]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function add(NodeInterface $node) {
+    $fields = [
+      'file' => $node->getFilename(),
+      'type' => get_class($node),
+    ];
+
+    if ($node instanceof ClassNode) {
+      $fields['id'] = (string) $node->getName();
+      $fields['parent'] = (string) $node->getExtends();
+    }
+    elseif ($node instanceof NewNode) {
+      $fields['id'] = (string) $node->getClassName();
+    }
+    else {
+      throw new \InvalidArgumentException('Unexpected node type (expected ClassNode or NewNode).');
+    }
+
+    $this->db->insert($this->table)->fields($fields)->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function get($identifier) {
+    $file = $this->getQuery(['file'])
+      ->condition('id', $identifier)
+      ->execute()
+      ->fetchField();
+
+    return $this->target
+      ->open($file)
+      ->find(Filter::isClass($identifier))
+      ->get(0);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFields() {
+    $fields = parent::getFields();
+
+    $fields['type'] = array(
+      'type' => 'varchar',
+      'length' => 255,
+      'not null' => TRUE,
+    );
+    $fields['parent'] = array(
+      'type' => 'varchar',
+      'length' => 255,
+    );
+
+    return $fields;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getUsages($identifier) {
+    $files = $this->getQuery(['file'])
+      ->distinct()
+      ->condition('id', $identifier)
+      ->condition('type', 'Pharborist\Objects\NewNode')
+      ->execute()
+      ->fetchCol();
+
+    $usages = new NodeCollection();
+    foreach ($files as $file) {
+      $this->target
+        ->open($file)
+        ->find(Filter::isInstanceOf('\Pharborist\Objects\NewNode'))
+        ->filter(function(NewNode $node) use ($identifier) {
+          return $node->getClassName() == $identifier;
+        })
+        ->addTo($usages);
+    }
+
+    return $usages;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Indexer/Constants.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Indexer/Constants.php
new file mode 100644 (file)
index 0000000..e4b8049
--- /dev/null
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Plugin\DMU\Indexer\Constants.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Indexer;
+
+use Drupal\drupalmoduleupgrader\ArrayIndexer;
+use Drupal\drupalmoduleupgrader\IndexerUsageInterface;
+use Pharborist\Constants\ConstantDeclarationNode;
+use Pharborist\Constants\ConstantNode;
+use Pharborist\Filter;
+use Pharborist\Functions\DefineNode;
+use Pharborist\NodeCollection;
+use Pharborist\NodeInterface;
+use Pharborist\Parser;
+use Pharborist\Types\ScalarNode;
+use Pharborist\Types\StringNode;
+
+/**
+ * @Indexer(
+ *  id = "constant"
+ * )
+ */
+class Constants extends ArrayIndexer implements IndexerUsageInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addFile($path) {
+    Parser::parseFile($path)
+      ->find(Filter::isInstanceOf('\Pharborist\Constants\ConstantNode', '\Pharborist\Functions\DefineNode', '\Pharborist\Constants\ConstantDeclarationNode'))
+      ->each([ $this, 'add' ]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function add(NodeInterface $node) {
+    if ($node instanceof DefineNode) {
+      list ($key, $value) = $node->getArguments();
+      if ($key instanceof StringNode && $value instanceof ScalarNode) {
+        $this->elements[ $key->toValue() ] = $value->toValue();
+      }
+    }
+    elseif ($node instanceof ConstantDeclarationNode) {
+      $value = $node->getValue();
+      if ($value instanceof ScalarNode) {
+        $this->elements[ (string) $node->getName() ] = $value->toValue();
+      }
+    }
+    elseif ($node instanceof ConstantNode) {
+      $this->db
+        ->insert($this->table)
+        ->fields([
+          'id' => (string) $node->getConstantName(),
+          // Hardcoding file name, as file name resolution is unavailable due
+          // to changes upstream in Pharborist.
+          'file' => 'foo.module',
+        ])
+        ->execute();
+    }
+    else {
+      throw new \InvalidArgumentException('Unexpected node type (expected DefineNode, ConstantDeclarationNode, or ConstantNode).');
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getUsages($identifier) {
+    $files = $this->getQuery(['file'])
+      ->distinct()
+      ->condition('id', $identifier)
+      ->execute()
+      ->fetchCol();
+
+    $usages = new NodeCollection();
+    foreach ($files as $file) {
+      $this->target
+        ->open($file)
+        ->find(Filter::isInstanceOf('\Pharborist\Constants\ConstantNode'))
+        ->filter(function(ConstantNode $node) use ($identifier) {
+          return $node->getConstantName() == $identifier;
+        })
+        ->addTo($usages);
+    }
+
+    return $usages;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Indexer/FunctionCalls.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Indexer/FunctionCalls.php
new file mode 100644 (file)
index 0000000..cc5ac78
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Indexer;
+
+use Drupal\drupalmoduleupgrader\IndexerBase;
+use Pharborist\Filter;
+use Pharborist\Parser;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\NodeCollection;
+
+/**
+ * @Indexer(
+ *  id = "function_call",
+ *  description = @Translation("Indexes all function calls in a target module."),
+ *  exclude = { "t" }
+ * )
+ */
+class FunctionCalls extends IndexerBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    /** @var \Symfony\Component\Finder\SplFileInfo $file */
+    foreach ($this->target->getFinder() as $file) {
+      $path = $file->getPathname();
+
+      $this->target
+        ->open($path)
+        ->find(Filter::isInstanceOf('\Pharborist\Functions\FunctionCallNode'))
+        ->not(function(FunctionCallNode $function_call) {
+          return in_array($function_call->getName()->getText(), $this->pluginDefinition['exclude']);
+        })
+        ->each([ $this, 'add' ]);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function get($id) {
+    $all = new NodeCollection([]);
+
+    $files = $this
+      ->getQuery(['file'])
+      ->distinct(TRUE)
+      ->condition('id', $id)
+      ->execute()
+      ->fetchCol();
+
+    array_walk($files, function($file) use ($all, $id) {
+      $all->add($this->target->open($file)->find(Filter::isFunctionCall($id)));
+    });
+
+    return $all;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addFile($path) {
+    $doc = Parser::parseFile($path);
+
+    $doc
+      ->find(Filter::isInstanceOf('\Pharborist\Functions\FunctionCallNode'))
+      ->each([ $this, 'add' ]);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Indexer/Functions.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Indexer/Functions.php
new file mode 100644 (file)
index 0000000..ad57585
--- /dev/null
@@ -0,0 +1,212 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Indexer;
+
+use Drupal\Component\Utility\SafeMarkup;
+use Drupal\drupalmoduleupgrader\IndexerBase;
+use Drupal\drupalmoduleupgrader\IndexerExecutionInterface;
+use Drupal\drupalmoduleupgrader\IndexerUsageInterface;
+use Drupal\drupalmoduleupgrader\Utility\Filter\ContainsLogicFilter;
+use Pharborist\Filter;
+use Pharborist\Functions\FunctionDeclarationNode;
+use Pharborist\NodeCollection;
+use Pharborist\NodeInterface;
+use Pharborist\Parser;
+
+/**
+ * @Indexer(
+ *  id = "function"
+ * )
+ */
+class Functions extends IndexerBase implements IndexerExecutionInterface, IndexerUsageInterface {
+
+  protected function prepareID($id) {
+    return preg_replace('/^hook_/', $this->target->id() . '_', $id);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function has($identifier) {
+    return parent::has($this->prepareID($identifier));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasAny(array $identifiers) {
+    return parent::hasAny(array_map([ $this, 'prepareID' ], $identifiers));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasAll(array $identifiers) {
+    return parent::hasAll(array_map([ $this, 'prepareID' ], $identifiers));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addFile($path) {
+    $doc = Parser::parseFile($path);
+
+    $doc
+      ->children(Filter::isInstanceOf('\Pharborist\Functions\FunctionDeclarationNode'))
+      ->each([ $this, 'add' ]);
+
+    $doc
+      ->find(Filter::isInstanceOf('\Pharborist\Functions\FunctionCallNode'))
+      ->each([ $this, 'add' ]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function add(NodeInterface $node) {
+    /** @var \Pharborist\Functions\FunctionDeclarationNode|\Pharborist\Functions\FunctionCallNode $node */
+    $fields = [
+      'id' => (string) $node->getName(),
+      'file' => $node->getFilename(),
+      'type' => get_class($node),
+    ];
+
+    if ($node instanceof FunctionDeclarationNode) {
+      $logical = new ContainsLogicFilter();
+      $logical->whitelist('t');
+      $logical->whitelist('drupal_get_path');
+      $fields['has_logic'] = (int) $node->is($logical);
+    }
+
+    $this->db
+      ->insert($this->table)
+      ->fields($fields)
+      ->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function delete($id) {
+    parent::delete($this->prepareID($id));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function get($identifier) {
+    $identifier = $this->prepareID($identifier);
+
+    $file = $this->getQuery(['file'])
+      ->condition('id', $identifier)
+      ->execute()
+      ->fetchField();
+
+    return $this->target
+      ->open($file)
+      ->children(Filter::isFunction($identifier))
+      ->get(0);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMultiple(array $identifiers) {
+    return parent::getMultiple(array_map([ $this, 'prepareID' ], $identifiers));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFields() {
+    $fields = parent::getFields();
+
+    $fields['type'] = array(
+      'type' => 'varchar',
+      'length' => 255,
+      'not null' => TRUE,
+    );
+    $fields['has_logic'] = array(
+      'type' => 'int',
+      'size' => 'tiny',
+      'unsigned' => TRUE,
+    );
+
+    return $fields;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQuery(array $fields = []) {
+    return parent::getQuery($fields)->condition('type', 'Pharborist\Functions\FunctionDeclarationNode');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasExecutable($identifier) {
+    if ($this->has($identifier)) {
+      $ret = $this->getQuery()
+        ->condition('id', $this->prepareID($identifier))
+        ->condition('has_logic', 0)
+        ->countQuery()
+        ->execute()
+        ->fetchField();
+      return $ret;
+    }
+    else {
+      return FALSE;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute($identifier, array $arguments = []) {
+    $function = $this->prepareID($identifier);
+
+    // If the function already exists, we can safely assume that it's already
+    // been scanned for dangerous logic and evaluated into existence.
+    if (function_exists($function)) {
+      return call_user_func_array($function, $arguments);
+    }
+    else {
+      if ($this->hasExecutable($function)) {
+        eval($this->get($function)->get(0)->getText());
+        return $this->execute($function, $arguments);
+      }
+      else {
+        $variables = [
+          '@function' => $function,
+        ];
+        throw new \LogicException(SafeMarkup::format('Cowardly refusing to execute @function.', $variables));
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getUsages($identifier) {
+    $function = $this->prepareID($identifier);
+
+    $files = $this->getQuery(['file'])
+      ->distinct()
+      ->condition('id', $function)
+      ->condition('type', 'Pharborist\Functions\FunctionCallNode')
+      ->execute()
+      ->fetchCol();
+
+    $usages = new NodeCollection();
+    foreach ($files as $file) {
+      $this->target
+        ->open($file)
+        ->find(Filter::isFunctionCall($function))
+        ->addTo($usages);
+    }
+
+    return $usages;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Rewriter/FormState.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Rewriter/FormState.php
new file mode 100644 (file)
index 0000000..b710df9
--- /dev/null
@@ -0,0 +1,214 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Rewriter;
+
+use Pharborist\ExpressionNode;
+use Pharborist\Filter;
+use Pharborist\Functions\ParameterNode;
+use Pharborist\Objects\ObjectMethodCallNode;
+use Pharborist\Operators\AssignNode;
+use Pharborist\Token;
+use Pharborist\Types\ArrayNode;
+
+/**
+ * @Rewriter(
+ *  id = "form_state",
+ *  type_hint = "\Drupal\Core\Form\FormStateInterface",
+ *  properties = {
+ *    "always_process" = {
+ *      "get" = "getAlwaysProcess",
+ *      "set" = "setAlwaysProcess"
+ *    },
+ *    "build_info" = {
+ *      "get" = "getBuildInfo",
+ *      "set" = "setBuildInfo"
+ *    },
+ *    "buttons" = {
+ *      "get" = "getButtons",
+ *      "set" = "setButtons"
+ *    },
+ *    "cache" = {
+ *      "get" = "isCached",
+ *      "set" = "setCached"
+ *    },
+ *    "complete_form" = {
+ *      "get" = "getCompleteForm"
+ *    },
+ *    "executed" = {
+ *      "get" = "isExecuted",
+ *      "set" = "setExecuted"
+ *    },
+ *    "groups" = {
+ *      "get" = "getGroups",
+ *      "set" = "setGroups"
+ *    },
+ *    "has_file_element" = {
+ *      "get" = "hasFileElement",
+ *      "set" = "setHasFileElement"
+ *    },
+ *    "input" = {
+ *      "get" = "getUserInput",
+ *      "set" = "setUserInput"
+ *    },
+ *    "limit_validation_errors" = {
+ *      "get" = "getLimitValidationErrors",
+ *      "set" = "setLimitValidationErrors"
+ *    },
+ *    "must_validate" = {
+ *      "get" = "isValidationEnforced",
+ *      "set" = "setValidationEnforced"
+ *    },
+ *    "process_input" = {
+ *      "get" = "isProcessingInput",
+ *      "set" = "setProcessInput"
+ *    },
+ *    "programmed" = {
+ *      "get" = "isProgrammed",
+ *      "set" = "setProgrammed"
+ *    },
+ *    "programmed_bypass_access_check" = {
+ *      "get" = "isBypassingProgrammedAccessChecks",
+ *      "set" = "setProgrammedBypassAccessCheck"
+ *    },
+ *    "rebuild" = {
+ *      "get" = "isRebuilding",
+ *      "set" = "setRebuild"
+ *    },
+ *    "response" = {
+ *      "get" = "getResponse",
+ *      "set" = "setResponse"
+ *    },
+ *    "storage" = {
+ *      "get" = "getStorage",
+ *      "set" = "setStorage"
+ *    },
+ *    "submit_handlers" = {
+ *      "get" = "getSubmitHandlers",
+ *      "set" = "setSubmitHandlers"
+ *    },
+ *    "submitted" = {
+ *      "get" = "isSubmitted",
+ *      "set" = "getSubmitted"
+ *    },
+ *    "temporary" = {
+ *      "get" = "getTemporary",
+ *      "set" = "setTemporary"
+ *    },
+ *    "triggering_element" = {
+ *      "get" = "getTriggeringElement",
+ *      "set" = "setTriggeringElement"
+ *    },
+ *    "validate_handlers" = {
+ *      "get" = "getValidateHandlers",
+ *      "set" = "setValidateHandlers"
+ *    },
+ *    "validation_complete" = {
+ *      "get" = "isValidationComplete",
+ *      "set" = "setValidationComplete"
+ *    }
+ *  }
+ * )
+ */
+class FormState extends Generic {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(ParameterNode $parameter) {
+    parent::rewrite($parameter);
+
+    $function = $parameter->getFunction();
+    $form_state = Token::variable('$' . $parameter->getName());
+
+    $set_errors = $function->find(Filter::isFunctionCall('form_set_error', 'form_error'));
+    /** @var \Pharborist\Functions\FunctionCallNode $set_error */
+    foreach ($set_errors as $set_error) {
+      $arguments = $set_error->getArguments();
+      $method = $set_error->getName()->getText() == 'form_set_error' ? 'setErrorByName' : 'setError';
+
+      $rewrite = ObjectMethodCallNode::create(clone $form_state, $method)
+        ->appendArgument(clone $arguments[0])
+        ->appendArgument(clone $arguments[1]);
+
+      $set_error->replaceWith($rewrite);
+    }
+
+    // form_clear_error() --> $form_state->clearErrors().
+    $clear_errors = $function->find(Filter::isFunctionCall('form_clear_error'));
+    foreach ($clear_errors as $clear_error) {
+      $clear_error->replaceWith(ObjectMethodCallNode::create(clone $form_state, 'clearErrors'));
+    }
+
+    // form_get_errors() --> $form_state->getErrors()
+    $get_errors = $function->find(Filter::isFunctionCall('form_get_errors'));
+    foreach ($get_errors as $get_error) {
+      $get_error->replaceWith(ObjectMethodCallNode::create(clone $form_state, 'getErrors'));
+    }
+
+    // form_get_error() --> $form_state->getError()
+    $get_errors = $function->find(Filter::isFunctionCall('form_get_error'));
+    /** @var \Pharborist\Functions\FunctionCallNode $get_error */
+    foreach ($get_errors as $get_error) {
+      $rewrite = ObjectMethodCallNode::create(clone $form_state, 'getError')
+        ->appendArgument($get_error->getArguments()->get(0));
+      $get_error->replaceWith($rewrite);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewriteAsGetter(ExpressionNode $expr, $property) {
+    /** @var \Pharborist\ArrayLookupNode $expr */
+    $object = clone $expr->getRootArray();
+    $keys = $expr->getKeys();
+
+    // $foo = $form_state['values'] --> $foo = $form_state->getValues()
+    // $foo = $form_state['values']['baz'] --> $form_state->getValue(['baz'])
+    if ($property == 'values') {
+      if (sizeof($keys) == 1) {
+        return ObjectMethodCallNode::create($object, 'getValues');
+      }
+      else {
+        array_shift($keys);
+        return ObjectMethodCallNode::create($object, 'getValue')->appendArgument(ArrayNode::create($keys));
+      }
+    }
+    elseif (isset($this->pluginDefinition['properties'][$property]['get'])) {
+      return parent::rewriteAsGetter($expr, $property);
+    }
+    // $foo = $form_state['arbitrary_key'] --> $foo = $form_state->get(['arbitrary_key'])
+    else {
+      return ObjectMethodCallNode::create($object, 'get')
+        ->appendArgument(ArrayNode::create($keys));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewriteAsSetter(ExpressionNode $expr, $property, AssignNode $assignment) {
+    /** @var \Pharborist\ArrayLookupNode $expr */
+    $object = clone $expr->getRootArray();
+    $keys = $expr->getKeys();
+    $value = clone $assignment->getRightOperand();
+
+    // $form_state['values']['baz'] = 'foo' --> $form_state->setValue(['baz'], 'foo')
+    if ($property == 'values') {
+      array_shift($keys);
+      return ObjectMethodCallNode::create($object, 'setValue')
+        ->appendArgument(ArrayNode::create($keys))
+        ->appendArgument($value);
+    }
+    elseif (isset($this->pluginDefinition['properties'][$property]['set'])) {
+      return parent::rewriteAsSetter($expr, $property, $assignment);
+    }
+    // $form_state['arbitrary_key'] = 'baz' --> $form_state->set(['arbitrary_key'], 'baz')
+    else {
+      return ObjectMethodCallNode::create($object, 'set')
+        ->appendArgument(ArrayNode::create($keys))
+        ->appendArgument($value);
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Rewriter/Generic.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Rewriter/Generic.php
new file mode 100644 (file)
index 0000000..e3f07b4
--- /dev/null
@@ -0,0 +1,306 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Rewriter;
+
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\drupalmoduleupgrader\PluginBase;
+use Drupal\drupalmoduleupgrader\RewriterInterface;
+use Drupal\drupalmoduleupgrader\Utility\Filter\FieldValueFilter;
+use Drupal\drupalmoduleupgrader\Utility\Filter\NodeAssignmentFilter;
+use Pharborist\ArrayLookupNode;
+use Pharborist\Constants\ConstantNode;
+use Pharborist\ExpressionNode;
+use Pharborist\Filter;
+use Pharborist\Functions\CallNode;
+use Pharborist\Functions\EmptyNode;
+use Pharborist\Functions\IssetNode;
+use Pharborist\Functions\ParameterNode;
+use Pharborist\Node;
+use Pharborist\NodeCollection;
+use Pharborist\Objects\ClassConstantLookupNode;
+use Pharborist\Objects\ObjectMethodCallNode;
+use Pharborist\Objects\ObjectPropertyNode;
+use Pharborist\Operators\AssignNode;
+use Pharborist\Operators\BooleanNotNode;
+use Pharborist\Parser;
+use Pharborist\Types\StringNode;
+use Pharborist\Variables\VariableNode;
+use Psr\Log\LoggerInterface;
+
+/**
+ * @Rewriter(
+ *  id = "_rewriter",
+ *  deriver = "\Drupal\drupalmoduleupgrader\Plugin\DMU\Rewriter\GenericDeriver"
+ * )
+ */
+class Generic extends PluginBase implements RewriterInterface {
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\Utility\Filter\NodeAssignmentFilter
+   */
+  protected $isAssigned;
+
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, TranslationInterface $translator, LoggerInterface $log) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $translator, $log);
+    $this->isAssigned = new NodeAssignmentFilter();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(ParameterNode $parameter) {
+    // Don't even try to rewrite the function if the parameter is reassigned.
+    if ($this->isReassigned($parameter)) {
+      $error = $this->t('@function() cannot be parametrically rewritten because @parameter is reassigned.', [
+        '@parameter' => $parameter->getName(),
+        '@function' => $parameter->getFunction()->getName()->getText(),
+      ]);
+      throw new \LogicException($error);
+    }
+
+    foreach ($this->getExpressions($parameter)->not($this->isAssigned) as $expr) {
+      $property = $this->getProperty($expr);
+      if (empty($property)) {
+        continue;
+      }
+
+      $getter = $this->rewriteAsGetter($expr, $property);
+      if ($getter) {
+        $empty = $expr->closest(Filter::isFunctionCall('empty', 'isset'));
+
+        // If the original expression was wrapped by a call to isset() or
+        // empty(), we need to replace it entirely.
+        if ($getter instanceof CallNode && $empty instanceof CallNode) {
+          // If the isset() or empty() call was negated, reverse that logic.
+          $parent = $empty->parent();
+          if ($parent instanceof BooleanNotNode) {
+            $parent->replaceWith($getter);
+          }
+          else {
+            $empty->replaceWith(BooleanNotNode::fromExpression($getter));
+          }
+        }
+        else {
+          $expr->replaceWith($getter);
+        }
+      }
+    }
+
+    foreach ($this->getExpressions($parameter)->filter($this->isAssigned) as $expr) {
+      // If the property cannot be determined, don't even try to rewrite the
+      // expression.
+      $property = $this->getProperty($expr);
+      if (empty($property)) {
+        continue;
+      }
+
+      $assignment = $expr->closest(Filter::isInstanceOf('\Pharborist\Operators\AssignNode'));
+
+      $setter = $this->rewriteAsSetter($expr, $property, $assignment);
+      if ($setter) {
+        $assignment->replaceWith($setter);
+      }
+    }
+
+    // Set the type hint, if one is defined.
+    if (isset($this->pluginDefinition['type_hint'])) {
+      $parameter->setTypeHint($this->pluginDefinition['type_hint']);
+
+      // If the type hint extends FieldableEntityInterface, rewrite any field
+      // lookups (e.g. $node->body[LANGUAGE_NONE][0]['value']).
+      if (in_array('Drupal\Core\Entity\FieldableEntityInterface', class_implements($this->pluginDefinition['type_hint']))) {
+        $filter = new FieldValueFilter($parameter->getName());
+
+        foreach ($parameter->getFunction()->find($filter) as $lookup) {
+          $lookup->replaceWith(self::rewriteFieldLookup($lookup));
+        }
+      }
+    }
+  }
+
+  /**
+   * Finds every rewritable expression in the function body.
+   *
+   * @param \Pharborist\Functions\ParameterNode $parameter
+   *  The parameter on which the rewrite is based.
+   *
+   * @return \Pharborist\NodeCollection
+   */
+  protected function getExpressions(ParameterNode $parameter) {
+    $filter = Filter::isInstanceOf('\Pharborist\ArrayLookupNode', '\Pharborist\Objects\ObjectPropertyNode');
+    $expressions = new NodeCollection();
+
+    $parameter
+      ->getFunction()
+      ->find(Filter::isInstanceOf('\Pharborist\Variables\VariableNode'))
+      ->filter(function(VariableNode $variable) use ($parameter) {
+        return $variable->getName() == $parameter->getName();
+      })
+      ->each(function(VariableNode $variable) use ($filter, $expressions) {
+        $root = $variable->furthest($filter);
+        if ($root) {
+          $expressions->add($root);
+        }
+      });
+
+    return $expressions;
+  }
+
+  /**
+   * Returns the property used by a rewritable expression, or NULL if the
+   * property cannot be determined.
+   *
+   * @param \Pharborist\ExpressionNode $expr
+   *  The rewritable expression.
+   *
+   * @return string|NULL
+   */
+  protected function getProperty(ExpressionNode $expr) {
+    if ($expr instanceof ObjectPropertyNode) {
+      return $expr->getPropertyName();
+    }
+    elseif ($expr instanceof ArrayLookupNode) {
+      $key = $expr->getKey(0);
+
+      if ($key instanceof StringNode) {
+        return $key->toValue();
+      }
+    }
+  }
+
+  /**
+   * Rewrites the given expression as a property getter. Returns NULL if the
+   * expression cannot be rewritten.
+   *
+   * @param \Pharborist\ExpressionNode $expr
+   *  The expression to rewrite.
+   * @param string $property
+   *  The property being used in the expression.
+   *
+   * @return \Pharborist\ExpressionNode|NULL
+   */
+  public function rewriteAsGetter(ExpressionNode $expr, $property) {
+    if ($expr instanceof ObjectPropertyNode) {
+      // Should be getRootObject() or getLookupRoot().
+      // @see Pharborist issue #191
+      $object = clone $expr->getObject();
+    }
+    elseif ($expr instanceof ArrayLookupNode) {
+      $object = clone $expr->getRootArray();
+    }
+
+    if (isset($object) && isset($this->pluginDefinition['properties'][$property]['get'])) {
+      return ObjectMethodCallNode::create($object, $this->pluginDefinition['properties'][$property]['get']);
+    }
+  }
+
+  /**
+   * Rewrites an assignment expression as a property setter. Returns NULL if
+   * the expression cannot be rewritten.
+   *
+   * @param \Pharborist\ExpressionNode $expr
+   *  The expression to rewrite.
+   * @param string $property
+   *  The property being used in the expression.
+   * @param \Pharborist\Operators\AssignNode $assignment
+   *  The entire assignment expression being rewritten.
+   *
+   * @return \Pharborist\ExpressionNode|NULL
+   */
+  public function rewriteAsSetter(ExpressionNode $expr, $property, AssignNode $assignment) {
+    if ($expr instanceof ObjectPropertyNode) {
+      // Should be getRootObject() or getLookupRoot().
+      // @see Pharborist issue #191
+      $object = clone $expr->getObject();
+    }
+    elseif ($expr instanceof ArrayLookupNode) {
+      $object = clone $expr->getRootArray();
+    }
+
+    if (isset($object) && isset($this->pluginDefinition['properties'][$property]['set'])) {
+      return ObjectMethodCallNode::create($object, $this->pluginDefinition['properties'][$property]['set'])
+        ->appendArgument(clone $assignment->getRightOperand());
+    }
+  }
+
+  /**
+   * Returns if the parameter is fully reassigned anywhere in the function.
+   *
+   * @param \Pharborist\Functions\ParameterNode $parameter
+   *  The parameter to check.
+   *
+   * @return boolean
+   */
+  protected function isReassigned(ParameterNode $parameter) {
+    return (boolean) $parameter
+      ->getFunction()
+      ->find(Filter::isInstanceOf('\Pharborist\Variables\VariableNode'))
+      ->filter(function(VariableNode $variable) use ($parameter) {
+        return $variable->getName() == $parameter->getName();
+      })
+      ->filter($this->isAssigned)
+      ->count();
+  }
+
+  /**
+   * Rewrites a Drupal 7 field lookup like so:
+   *
+   * $node->body[LANGUAGE_NONE][0]['value'] --> $node->body[0]->value
+   * $node->body['fr'][0]['value'] --> $node->getTranslation('fr')->body[0]->value
+   *
+   * @param \Pharborist\ArrayLookupNode $node
+   *  The original field lookup.
+   *
+   * @return \Pharborist\ExpressionNode
+   */
+  public static function rewriteFieldLookup(ArrayLookupNode $node) {
+    $keys = $node->getKeys();
+    /** @var \Pharborist\Objects\ObjectPropertyNode $root */
+    $root = $node->getRootArray();
+    $expr = $root->getObject()->getText();
+
+    if (self::isTranslation($keys[0])) {
+      $expr .= '->getTranslation(' . $keys[0] . ')';
+    }
+    $expr .= '->' . $root->getPropertyName() . '[' . $keys[1] . ']';
+
+    /** @var \Pharborist\Types\StringNode|\Pharborist\Node $column */
+    foreach (array_slice($keys, 2) as $column) {
+      $expr .= '->';
+      $expr .= $column instanceof StringNode ? $column->toValue() : $column->getText();
+    }
+
+    return Parser::parseExpression($expr);
+  }
+
+  /**
+   * Checks if a field lookup key is translated. This will be TRUE unless one
+   * of the following conditions applies:
+   *
+   * - The key is the Drupal\Core\Language\Language::LANGCODE_NOT_SPECIFIED
+   *   constant.
+   * - The key is the LANGUAGE_NONE constant from Drupal 7.
+   * - The key is the string 'und'.
+   *
+   * @param Node $key
+   *  The key to check.
+   *
+   * @return boolean
+   */
+  public static function isTranslation(Node $key) {
+    if ($key instanceof ClassConstantLookupNode) {
+      $constant = $key->getClassName() . '::' . $key->getConstantName();
+      return $constant != '\Drupal\Core\Language\Language::LANGCODE_NOT_SPECIFIED';
+    }
+    elseif ($key instanceof ConstantNode) {
+      return $key->getConstantName() != 'LANGUAGE_NONE';
+    }
+    elseif ($key instanceof StringNode) {
+      return $key->toValue() != 'und';
+    }
+    else {
+      return TRUE;
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Rewriter/GenericDeriver.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Rewriter/GenericDeriver.php
new file mode 100644 (file)
index 0000000..9ca0960
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Rewriter;
+
+use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Builds derivative definitions for the generic rewriter, based on the
+ * drupalmoduleupgrader.rewriters configuration object.
+ */
+class GenericDeriver implements ContainerDeriverInterface {
+
+  /**
+   * @var array
+   */
+  protected $config;
+
+  public function __construct(array $config) {
+    $this->config = $config;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, $base_plugin_id) {
+    return new static(
+      $container->get('config.factory')->get('drupalmoduleupgrader.rewriters')->get('definitions')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinition($derivative_id, $base_definition) {
+    $derivatives = $this->getDerivativeDefinitions($base_definition);
+
+    if (isset($derivatives[$derivative_id])) {
+      return $derivatives[$derivative_id];
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinitions($base_definition) {
+    $derivatives = [];
+
+    foreach ($this->config as $data_type => $definition) {
+      $derivatives[$data_type] = $definition + $base_definition;
+    }
+
+    return $derivatives;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Routing/ContentRoute.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Routing/ContentRoute.php
new file mode 100644 (file)
index 0000000..77bb9c5
--- /dev/null
@@ -0,0 +1,241 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Routing;
+
+use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Routing\RouteProviderInterface;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\drupalmoduleupgrader\ConverterBase;
+use Drupal\drupalmoduleupgrader\Routing\Drupal7\RouteWrapper as Drupal7Route;
+use Drupal\drupalmoduleupgrader\Routing\Drupal8\RouteWrapper as Drupal8Route;
+use Drupal\drupalmoduleupgrader\Routing\ParameterMap;
+use Drupal\drupalmoduleupgrader\Routing\RouteConverterInterface;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Drupal\drupalmoduleupgrader\Utility\StringTransformTrait;
+use Pharborist\ControlStructures\ReturnStatementNode;
+use Pharborist\Filter;
+use Pharborist\Functions\ParameterNode;
+use Pharborist\Objects\ClassMethodCallNode;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Routing\Route as CoreRoute;
+
+/**
+ * @Converter(
+ *  id = "default",
+ *  description = @Translation("Converts a menu item to a _controller route."),
+ *  dependencies = { "router.route_provider", "plugin.manager.drupalmoduleupgrader.rewriter" }
+ * )
+ */
+class ContentRoute extends ConverterBase implements RouteConverterInterface, ContainerFactoryPluginInterface {
+
+  use StringTransformTrait;
+
+  /**
+   * @var RouteProviderInterface
+   */
+  protected $routeProvider;
+
+  /**
+   * @var PluginManagerInterface
+   */
+  protected $rewriters;
+
+  /**
+   * Constructs a RouteConverterBase object.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, TranslationInterface $translator, LoggerInterface $log, RouteProviderInterface $route_provider, PluginManagerInterface $rewriters) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $translator, $log);
+    $this->routeProvider = $route_provider;
+    $this->rewriters = $rewriters;
+  }
+
+  /**
+   * Conform with ConverterInterface, which we implement through ConverterBase.
+   * Because route conversion is so complex, the Routing plugin never calls
+   * this method. It relies instead on the other methods defined in
+   * RouteConverterInterface.
+   */
+  final public function convert(TargetInterface $target) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName(TargetInterface $target, Drupal7Route $route) {
+    $name = $target->id() . '.' . $this->unPrefix($route['page callback'], $target->id());
+
+    $arguments = array_filter($route['page arguments'], 'is_string');
+    if ($arguments) {
+      $name .= '_' . implode('_', $arguments);
+    }
+
+    return $name;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildPath(TargetInterface $target, Drupal7Route $route) {
+    // The parameter map modifies the path in-place, so we'll clone it in order
+    // to keep this method non-destructive.
+    $path = clone $route->getPath();
+    $this->buildParameterMap($target, $route)->applyPath($path);
+    return $path;
+  }
+
+  /**
+   * Builds a parameter map from the aggregated arguments of the title,
+   * access, and page callbacks.
+   *
+   * @return \Drupal\drupalmoduleupgrader\Routing\ParameterMap
+   */
+  protected function buildParameterMap(TargetInterface $target, Drupal7Route $route) {
+    $map = new ParameterMap(clone $route->getPath(), []);
+
+    $indexer = $target->getIndexer('function');
+
+    if ($indexer->has($route['title callback'])) {
+      $map->merge(new ParameterMap(
+        $route->getPath(),
+        $indexer->get($route['title callback'])->getParameters()->toArray(),
+        $route['title arguments']
+      ));
+    }
+
+    if ($indexer->has($route['access callback'])) {
+      $map->merge(new ParameterMap(
+        $route->getPath(),
+        $indexer->get($route['access callback'])->getParameters()->toArray(),
+        $route['access arguments']
+      ));
+    }
+
+    if ($indexer->has($route['page callback'])) {
+      $map->merge(new ParameterMap(
+        $route->getPath(),
+        $indexer->get($route['page callback'])->getParameters()->toArray(),
+        $route['page arguments']
+      ));
+    }
+
+    return $map;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildRouteDefinition(TargetInterface $target, Drupal7Route $route) {
+    $indexer = $target->getIndexer('function');
+
+    $definition = new CoreRoute('');
+    $this->buildParameterMap($target, $route)->applyRoute($definition);
+
+    $controller = $this->getController($target, $route)->getName()->getAbsolutePath();
+
+    if ($route->containsKey('title')) {
+      $definition->setDefault('_title', $route['title']);
+    }
+    elseif ($indexer->has($route['title callback'])) {
+      $definition->setDefault('_title_callback', $controller . '::' . $route['title callback']);
+    }
+
+    if ($route->isAbsoluteAccess()) {
+      $definition->setRequirement('_access', $route['access callback'] ? 'true' : 'false');
+    }
+    elseif ($route->isPermissionBased()) {
+      $definition->setRequirement('_permission', $route['access arguments'][0]);
+    }
+    elseif ($indexer->has($route['access callback'])) {
+      $definition->setRequirement('_custom_access', $controller . '::' . $route['access callback']);
+    }
+
+    if ($indexer->has($route['page callback'])) {
+      $definition->setDefault('_controller', $controller . '::' . $route['page callback']);
+    }
+
+    return new Drupal8Route($this->getName($target, $route), $definition, $this->routeProvider);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildRoute(TargetInterface $target, Drupal7Route $route) {
+    $definition = $this->buildRouteDefinition($target, $route);
+
+    $map = $this->buildParameterMap($target, $route);
+    $map->applyRoute($definition->unwrap());
+
+    $indexer = $target->getIndexer('function');
+
+    foreach ($map->toArray() as $function_name => $parameters) {
+      if ($parameters && $indexer->has($function_name)) {
+        /** @var \Pharborist\Functions\FunctionDeclarationNode $function */
+        $function = $indexer->get($function_name);
+        foreach ($parameters as $parameter_name => $info) {
+          $parameter = $function->getParameterByName($parameter_name)->setName($info['name'], TRUE);
+          if (isset($info['type'])) {
+            $plugin_id = '_rewriter:' . $info['type'];
+            if ($this->rewriters->hasDefinition($plugin_id)) {
+              $this->rewriters->createInstance($plugin_id)->rewrite($parameter);
+            }
+          }
+        }
+      }
+    }
+
+    $class_indexer = $target->getIndexer('class');
+    if ($class_indexer->has('DefaultController')) {
+      $controller = $class_indexer->get('DefaultController');
+    }
+    else {
+      $controller = $this->getController($target, $route);
+      $class_indexer->addFile($this->writeClass($target, $controller));
+    }
+
+    if ($indexer->has($route['title callback'])) {
+      if (! $controller->hasMethod($route['title callback'])) {
+        $indexer->get($route['title callback'])->cloneAsMethodOf($controller);
+      }
+    }
+
+    if ($indexer->has($route['access callback'])) {
+      $func = $indexer->get($route['access callback']);
+
+      $returns = $func->find(Filter::isInstanceOf('\Pharborist\ReturnStatementNode'));
+      foreach ($returns as $ret) {
+        $call = ClassMethodCallNode::create('\Drupal\Core\Access\AccessResult', 'allowedIf')->appendArgument($ret->getExpression());
+        $ret->replaceWith(ReturnStatementNode::create($call));
+      }
+
+      // The access callback always receives an $account parameter.
+      if ($func->hasParameter('account')) {
+        $func->getParameter('account')->setTypeHint('Drupal\Core\Session\AccountInterface');
+      }
+      else {
+        $account = ParameterNode::create('account')->setTypeHint('Drupal\Core\Session\AccountInterface');
+        $func->appendParameter($account);
+      }
+
+      if (! $controller->hasMethod($route['access callback'])) {
+        $func->cloneAsMethodOf($controller);
+      }
+    }
+
+    if ($indexer->has($route['page callback'])) {
+      if (! $controller->hasMethod($route['page callback'])) {
+        $indexer->get($route['page callback'])->cloneAsMethodOf($controller);
+      }
+    }
+
+    $this->writeClass($target, $controller);
+  }
+
+  protected function getController(TargetInterface $target, Drupal7Route $route) {
+    $render = [
+      '#theme' => 'dmu_controller',
+      '#module' => $target->id(),
+    ];
+    return $this->parse($render);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Routing/FormRoute.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Routing/FormRoute.php
new file mode 100644 (file)
index 0000000..b60727c
--- /dev/null
@@ -0,0 +1,109 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Routing;
+
+use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\Routing\RouteProviderInterface;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\drupalmoduleupgrader\Routing\Drupal7\RouteWrapper;
+use Drupal\drupalmoduleupgrader\Routing\ParameterMap;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Drupal\drupalmoduleupgrader\Utility\FormConverterFactory;
+use Psr\Log\LoggerInterface;
+
+/**
+ * @Converter(
+ *  id = "drupal_get_form",
+ *  description = @Translation("Converts a drupal_get_form() menu item to a _form route."),
+ *  dependencies = { "router.route_provider", "plugin.manager.drupalmoduleupgrader.rewriter", "drupalmoduleupgrader.form_converter" }
+ * )
+ */
+class FormRoute extends ContentRoute {
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\Utility\FormConverter
+   */
+  protected $formConverter;
+
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, TranslationInterface $translator, LoggerInterface $log, RouteProviderInterface $route_provider, PluginManagerInterface $rewriters, FormConverterFactory $form_converter) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $translator, $log, $route_provider, $rewriters);
+    $this->formConverter = $form_converter;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName(TargetInterface $target, RouteWrapper $route) {
+    $name = $target->id() . '.' . $this->unPrefix($route['page arguments'][0], $target->id());
+
+    $arguments = array_filter(array_slice($route['page arguments'], 1), 'is_string');
+    if ($arguments) {
+      $name .= '_' . implode('_', $arguments);
+    }
+
+    return $name;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function buildParameterMap(TargetInterface $target, RouteWrapper $route) {
+    $map = parent::buildParameterMap($target, $route);
+
+    $indexer = $target->getIndexer('function');
+    if ($indexer->has($route['page arguments'][0])) {
+      $builder = $indexer->get($route['page arguments'][0]);
+      $parameters = $this->bumpKeys(array_slice($builder->getParameters()->toArray(), 2), 2);
+      $arguments = $this->bumpKeys(array_slice($route['page arguments'], 1), 2);
+      $map->merge(new ParameterMap($route->getPath(), $parameters, $arguments));
+    }
+
+    return $map;
+  }
+
+  /**
+   * Returns a copy of the input array with the keys increased by $offset. This
+   * only works on numerically indexed arrays; I don't know what it does to
+   * associative arrays, but probably nothing good.
+   *
+   * @param array $input
+   *  The input array.
+   *
+   * @param int $offset
+   *  The offset to add to the keys.
+   *
+   * @return array
+   */
+  private function bumpKeys(array $input, $offset = 0) {
+    $output = [];
+
+    foreach ($input as $key => $value) {
+      $output[ $key + $offset ] = $value;
+    }
+
+    return $output;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildRoute(TargetInterface $target, RouteWrapper $route) {
+    $controller = $this->formConverter->get($target, $route['page arguments'][0])->build();
+    $target->getIndexer('class')->addFile($this->writeClass($target, $controller));
+  }
+
+  protected function getController(TargetInterface $target, RouteWrapper $route) {
+    return $this->formConverter->get($target, $route['page arguments'][0])->render();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildRouteDefinition(TargetInterface $target, RouteWrapper $route) {
+    $definition = parent::buildRouteDefinition($target, $route);
+    $definition->setDefault('_form', $this->getController($target, $route)->getName()->getAbsolutePath());
+
+    return $definition;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/PluginBase.php b/web/modules/contrib/drupalmoduleupgrader/src/PluginBase.php
new file mode 100644 (file)
index 0000000..506a3a1
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader;
+
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Plugin\PluginBase as CorePluginBase;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Base class for all DMU plugin types, pulling string translation and logging
+ * services from the container by default.
+ *
+ * @deprecated
+ */
+abstract class PluginBase extends CorePluginBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * @var LoggerInterface
+   */
+  protected $log;
+
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, TranslationInterface $translator, LoggerInterface $log) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->stringTranslation = $translator;
+    $this->log = $log;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    $arguments = [
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      // Always include the string translation and logging services.
+      $container->get('string_translation'),
+      $container->get('logger.factory')->get('drupalmoduleupgrader'),
+    ];
+
+    // Pull any declared dependencies out of the container.
+    if (isset($plugin_definition['dependencies'])) {
+      foreach ($plugin_definition['dependencies'] as $dependency) {
+        $arguments[] = $container->get($dependency);
+      }
+    }
+
+    return (new \ReflectionClass(get_called_class()))->newInstanceArgs($arguments);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Report.php b/web/modules/contrib/drupalmoduleupgrader/src/Report.php
new file mode 100644 (file)
index 0000000..995a1f4
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader;
+
+/**
+ * Basic implementation of an analyzer report.
+ */
+class Report implements ReportInterface {
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\IssueInterface[]
+   */
+  protected $issues = [];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addIssue(IssueInterface $issue) {
+    $id = spl_object_hash($issue);
+    $this->issues[$id] = $issue;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIssues($tag = NULL) {
+    // We call array_values() here to reset the keys.
+    $issues = array_values($this->issues);
+
+    if ($tag) {
+      $issues = array_filter($issues, function(IssueInterface $issue) use ($tag) {
+        return $issue->hasTag($tag);
+      });
+    }
+
+    return $issues;
+  }
+
+  public function enumerateTag($tag) {
+    $enum = array_map(function(IssueInterface $issue) use ($tag) { return $issue->getTag($tag); }, $this->getIssues($tag));
+    return array_unique($enum);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/ReportInterface.php b/web/modules/contrib/drupalmoduleupgrader/src/ReportInterface.php
new file mode 100644 (file)
index 0000000..cb5e73c
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader;
+
+/**
+ * Defines a report generated by the dmu-analyze command. Issues returned by
+ * analyzers are added to this report, then it's handed off to the theme system.
+ */
+interface ReportInterface {
+
+  /**
+   * Adds an issue to this module.
+   *
+   * @param IssueInterface $issue
+   *  The issue to add.
+   *
+   * @return $this
+   */
+  public function addIssue(IssueInterface $issue);
+
+  /**
+   * Returns all issues collected so far, optionally filtered by a tag.
+   *
+   * @param string|NULL $tag
+   *  (optional) A tag name. If set, only issues which have this tag will
+   *  be returned (regardless of the tag's value in each issue -- it's up to
+   *  the calling code to do any further filtering).
+   *
+   * @return IssueInterface[]
+   */
+  public function getIssues($tag = NULL);
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/RewriterInterface.php b/web/modules/contrib/drupalmoduleupgrader/src/RewriterInterface.php
new file mode 100644 (file)
index 0000000..5f59d41
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader;
+
+use Pharborist\Functions\ParameterNode;
+
+/**
+ * Defines a parametric rewriter.
+ *
+ * Parametric rewriters are utility plugins which can alter a function body
+ * in the context of a specific parameter. If a parameter is explicitly defined
+ * as a node, for example, the rewriter can alter the function body so that
+ * $node->nid becomes $node->id(). Rewriters work from property maps defined
+ * in the plugin definition.
+ */
+interface RewriterInterface {
+
+  /**
+   * Parametrically rewrites the function containing the given parameter.
+   *
+   * @param ParameterNode $parameter
+   *  The parameter upon which to base the rewrite. The parameter must be
+   *  attached to a function or method declaration node, or fatal errors will
+   *  likely result.
+   */
+  public function rewrite(ParameterNode $parameter);
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Routing/Drupal7/RouteWrapper.php b/web/modules/contrib/drupalmoduleupgrader/src/Routing/Drupal7/RouteWrapper.php
new file mode 100644 (file)
index 0000000..655420e
--- /dev/null
@@ -0,0 +1,257 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Converter\Routing\Drupal7\Route.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Routing\Drupal7;
+
+use Doctrine\Common\Collections\ArrayCollection;
+use Drupal\drupalmoduleupgrader\Routing\RouterBuiltEvent;
+use Drupal\drupalmoduleupgrader\Routing\RouteWrapperInterface;
+use Drupal\drupalmoduleupgrader\Utility\Path\Drupal7\PathUtility;
+
+/**
+ * Encapsulates a Drupal 7 route (including the link, if any).
+ */
+class RouteWrapper extends ArrayCollection implements RouteWrapperInterface {
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\Utility\Path\Drupal7\PathUtility
+   */
+  protected $path;
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\Routing\RouterInterface
+   */
+  protected $router;
+
+  /**
+   * @var static|NULL
+   */
+  protected $parent;
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\Routing\Drupal7\Router
+   */
+  protected $children;
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\Routing\Drupal7\Router
+   */
+  protected $siblings;
+
+  /**
+   * Constructs a Route object.
+   */
+  public function __construct($path, array $item) {
+    $this->path = new PathUtility($path);
+
+    // Merge in hook_menu() defaults to normalize things.
+    $item += [
+      'title callback' => 't',
+      'title arguments' => [],
+      'access callback' => 'user_access',
+      'access arguments' => [],
+      'page arguments' => [],
+      'type' => 'MENU_NORMAL_ITEM',
+    ];
+    parent::__construct($item);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIdentifier() {
+    return $this->getPath()->__toString();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPath() {
+    return $this->path;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasParent() {
+    return isset($this->parent);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getParent() {
+    return $this->parent;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function unwrap() {
+    return $this->toArray();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onRouterBuilt(RouterBuiltEvent $event) {
+    $this->router = $event->getRouter();
+
+    $my_path = $this->getPath();
+    $my_length = sizeof($my_path);
+    $my_path = (string) $my_path;
+
+    // If trying to get the parent raises an exception, we're going to
+    // bail out. But we don't need the parent in order to find our own
+    // children, so search for them before searching for the parent.
+    $this->children = $this->router
+      ->filter(function(RouteWrapper $route) use ($my_path, $my_length) {
+        $path = $route->getPath();
+        // <WTF>$path needs to be explicitly cast to a string, 'cause strPos() won't do
+        // it, even though trim() and similar functions will.</WTF>
+        return (sizeof($path) == ($my_length + 1) && strPos((string) $path, $my_path) === 0);
+      })
+      ->ofType('MENU_LOCAL_TASK, MENU_DEFAULT_LOCAL_TASK, MENU_LOCAL_ACTION');
+
+    try {
+      $parent = $this->getPath()->getParent();
+      $this->parent = $this->router->get($parent->__toString());
+    }
+    catch (\LengthException $e) {
+      // Because there's no parent path, we can't effectively search for siblings.
+      // Time to die.
+      return;
+    }
+
+    $this->siblings = $this->router
+      ->filter(function(RouteWrapper $route) use ($parent, $my_path, $my_length) {
+        $path = $route->getPath();
+        // <WTF>strPos(), <sarcasm>in its wisdom</sarcasm>, won't cast to string.</WTF>
+        return ($path !== $my_path && sizeof($path) == $my_length && strPos((string) $path, (string) $parent) === 0);
+      });
+  }
+
+  /**
+   * Returns if this route has an absolute access flag (TRUE or FALSE).
+   *
+   * @return boolean
+   */
+  public function isAbsoluteAccess() {
+    return is_bool($this->get('access callback'));
+  }
+
+  /**
+   * Returns if this route has permission-based access.
+   *
+   * @return boolean
+   */
+  public function isPermissionBased() {
+    return ($this->get('access callback') == 'user_access');
+  }
+
+  /**
+   * Returns if this route exposes a link of any kind.
+   *
+   * @return boolean
+   */
+  public function hasLink() {
+    return ($this->isLink() || $this->isLocalTask() || $this->isDefaultLocalTask() || $this->isLocalAction());
+  }
+
+  /**
+   * Returns if this route is a normal link.
+   *
+   * @return boolean
+   */
+  public function isLink() {
+    return $this->get('type') == 'MENU_NORMAL_ITEM';
+  }
+
+  /**
+   * Returns if this route is a local task (NOT a default local task).
+   *
+   * @return boolean
+   */
+  public function isLocalTask() {
+    return $this->get('type') == 'MENU_LOCAL_TASK';
+  }
+
+  /**
+   * Gets the closest default local task, if there is one.
+   *
+   * @return static|NULL
+   */
+  public function getDefaultTask() {
+    if ($this->hasSiblings()) {
+      return $this->getSiblings()->ofType('MENU_DEFAULT_LOCAL_TASK')->first();
+    }
+  }
+
+  /**
+   * Returns if this route is a default local task.
+   *
+   * @return boolean
+   */
+  public function isDefaultLocalTask() {
+    return $this->get('type') == 'MENU_DEFAULT_LOCAL_TASK';
+  }
+
+  /**
+   * Returns if this route is a local action.
+   *
+   * @return boolean
+   */
+  public function isLocalAction() {
+    return $this->get('type') == 'MENU_LOCAL_ACTION';
+  }
+
+  /**
+   * Returns if this route is a contextual link.
+   *
+   * @return boolean
+   */
+  public function isContextualLink() {
+    return ($this->isLocalAction() && $this->containsKey('context') && $this->get('context') == 'MENU_CONTEXT_INLINE');
+  }
+
+  /**
+   * Returns if this route has children.
+   *
+   * @return boolean
+   */
+  public function hasChildren() {
+    return $this->getChildren()->count() > 0;
+  }
+
+  /**
+   * Returns the immediate children of this route.
+   *
+   * @return \Drupal\drupalmoduleupgrader\Routing\Drupal7\Router
+   */
+  public function getChildren() {
+    return $this->children;
+  }
+
+  /**
+   * Returns if this route has siblings.
+   *
+   * @return boolean
+   */
+  public function hasSiblings() {
+    return $this->getSiblings()->count() > 0;
+  }
+
+  /**
+   * Gets the siblings of this route.
+   *
+   * @return \Drupal\drupalmoduleupgrader\Routing\Drupal7\Router
+   */
+  public function getSiblings() {
+    return $this->siblings;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Routing/Drupal7/Router.php b/web/modules/contrib/drupalmoduleupgrader/src/Routing/Drupal7/Router.php
new file mode 100644 (file)
index 0000000..38e0a57
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Converter\Routing\Drupal7\Router.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Routing\Drupal7;
+
+use Drupal\drupalmoduleupgrader\Routing\RouterBase;
+
+/**
+ * Represents a collection of Drupal 7 routes, i.e., the result of hook_menu().
+ */
+class Router extends RouterBase {
+
+  /**
+   * Gets all items of a specific type.
+   *
+   * @param string $link_types
+   *  The link type(s), separated by commas (e.g., 'MENU_NORMAL_ITEM, MENU_LOCAL_TASK').
+   *
+   * @return static
+   */
+  public function ofType($link_types) {
+    $link_types = array_map('trim', explode(', ', $link_types));
+
+    return $this->filter(function(RouteWrapper $route) use ($link_types) {
+      return in_array($route['type'], $link_types);
+    });
+  }
+
+  /**
+   * Gets all items which expose a link of any kind.
+   *
+   * @return static
+   */
+  public function getAllLinks() {
+    return $this->filter(function(RouteWrapper $route) {
+      return $route->hasLink();
+    });
+  }
+
+  /**
+   * Gets all normal links.
+   *
+   * @return static
+   */
+  public function getLinks() {
+    return $this->filter(function(RouteWrapper $route) {
+      return $route->isLink();
+    });
+  }
+
+  /**
+   * Gets all local tasks.
+   *
+   * @return static
+   */
+  public function getLocalTasks() {
+    return $this->filter(function(RouteWrapper $route) {
+      return $route->isLocalTask();
+    });
+  }
+
+  /**
+   * Gets all default local tasks.
+   *
+   * @return static
+   */
+  public function getDefaultLocalTasks() {
+    return $this->filter(function(RouteWrapper $route) {
+      return $route->isDefaultLocalTask();
+    });
+  }
+
+  /**
+   * Gets all local actions.
+   *
+   * @return static
+   */
+  public function getLocalActions() {
+    return $this->filter(function(RouteWrapper $route) {
+      return $route->isLocalAction();
+    });
+  }
+
+  /**
+   * Gets all contextual links.
+   *
+   * @return static
+   */
+  public function getContextualLinks() {
+    return $this->filter(function(RouteWrapper $route) {
+      return $route->isContextualLink();
+    });
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Routing/Drupal8/RouteWrapper.php b/web/modules/contrib/drupalmoduleupgrader/src/Routing/Drupal8/RouteWrapper.php
new file mode 100644 (file)
index 0000000..5266b4d
--- /dev/null
@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Converter\Routing\Drupal8\Route.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Routing\Drupal8;
+
+use Drupal\Core\Routing\RouteProviderInterface;
+use Drupal\drupalmoduleupgrader\Routing\RouterBuiltEvent;
+use Drupal\drupalmoduleupgrader\Routing\RouteWrapperInterface;
+use Drupal\drupalmoduleupgrader\Utility\Path\Drupal8\PathUtility;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Wraps around a Symfony Route object, providing helper methods.
+ */
+class RouteWrapper implements RouteWrapperInterface {
+
+  /**
+   * @var string
+   */
+  protected $name;
+
+  /**
+   * @var \Symfony\Component\Routing\Route
+   */
+  protected $route;
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\Utility\Path\Drupal8\PathUtility
+   */
+  protected $path;
+
+  /**
+   * @var \Drupal\Core\Routing\RouteProviderInterface
+   */
+  protected $routeProvider;
+
+  /**
+   * @var \Symfony\Component\Routing\RouteCollection
+   */
+  protected $router;
+
+  /**
+   * @var static
+   */
+  protected $parent;
+
+  /**
+   * Constructs a Route object.
+   */
+  public function __construct($name, Route $route, RouteProviderInterface $route_provider) {
+    $this->name = $name;
+    $this->route = $route;
+    $this->routeProvider = $route_provider ? $route_provider: \Drupal::service('router.route_provider');
+    $this->path = new PathUtility($route->getPath());
+  }
+
+  /**
+   * Forwards unknown function calls to the wrapped Route.
+   */
+  public function __call($method, array $arguments) {
+    return call_user_func_array([ $this->route, $method ], $arguments);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIdentifier() {
+    return $this->name;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPath() {
+    return $this->path;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasParent() {
+    return isset($this->parent);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getParent() {
+    return $this->parent;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function unwrap() {
+    return $this->route;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onRouterBuilt(RouterBuiltEvent $event) {
+    $this->router = $event->getRouter();
+
+    try {
+      $parent = $this->getPath()->getParent()->__toString();
+    }
+    catch (\LengthException $e) {
+      return;
+    }
+
+    // First, search the injected router for the parent route.
+    foreach ($this->router as $route) {
+      if ($route->getPath() == $parent) {
+        $this->parent = $route;
+      }
+    }
+
+    // Next, search the core route provider if no parent was found.
+    if (empty($this->parent)) {
+      $parents = $this->routeProvider->getRoutesByPattern($parent)->getIterator();
+
+      if (sizeof($parents) > 0) {
+        $this->parent = new static($parents->key(), $parents->current(), $this->routeProvider);
+      }
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Routing/HookMenu.php b/web/modules/contrib/drupalmoduleupgrader/src/Routing/HookMenu.php
new file mode 100644 (file)
index 0000000..d5666d8
--- /dev/null
@@ -0,0 +1,169 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Routing;
+
+use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\drupalmoduleupgrader\Routing\Drupal7\Router as Drupal7Router;
+use Drupal\drupalmoduleupgrader\Routing\Drupal7\RouteWrapper as Drupal7Route;
+use Drupal\drupalmoduleupgrader\Routing\RouterBase as Drupal8Router;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+
+/**
+ * This class is a conversion map for hook_menu().
+ *
+ * It will compile a router object for hook_menu(), and resolve the inherent
+ * hierarchies where possible. It will also build the corresponding Drupal 8
+ * routes by invoking the appropriate route converters.
+ *
+ * All this is absolutely READ-ONLY. Nothing in the target module is changed.
+ */
+class HookMenu {
+
+  /**
+   * The source routes (from Drupal 7).
+   *
+   * @var RouterInterface
+   */
+  protected $sourceRoutes;
+
+  /**
+   * The destination routes (as in a routing.yml file).
+   *
+   * @var RouterInterface
+   */
+  protected $destinationRoutes;
+
+  /**
+   * Maps Drupal 7 paths to Drupal 8 route names.
+   *
+   * @var string[]
+   */
+  protected $routeMap = [];
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\TargetInterface
+   */
+  protected $target;
+
+  /**
+   * The route converters' plugin manager.
+   *
+   * @var \Drupal\Component\Plugin\PluginManagerInterface
+   */
+  protected $routeConverters;
+
+  /**
+   * Constructs a HookMenu object.
+   *
+   * @param \Drupal\drupalmoduleupgrader\TargetInterface $target
+   *  The target module.
+   * @param \Drupal\Component\Plugin\PluginManagerInterface $route_converters
+   *   The route converters.
+   */
+  public function __construct(TargetInterface $target, PluginManagerInterface $route_converters) {
+    $this->target = $target;
+    $this->routeConverters = $route_converters;
+
+    // If the hook_menu() implementation doesn't exist, get the implementation
+    // from the indexer and eval it into existence. It's the calling code's
+    // responsibility to ensure that the implementation doesn't contain anything
+    // which will blow up on execution.
+    $hook = $target->id() . '_menu';
+    if (! function_exists($hook)) {
+      eval($target->getIndexer('function')->get('hook_menu')->getText());
+    }
+  }
+
+  /**
+   * Returns the collection of routes in the source.
+   *
+   * @return RouterInterface
+   *   The requested link collection.
+   */
+  public function getSourceRoutes() {
+    if (empty($this->sourceRoutes)) {
+      $this->sourceRoutes = new Drupal7Router();
+
+      $items = call_user_func($this->target->id() . '_menu');
+      foreach ($items as $path => $item) {
+        $this->sourceRoutes->addRoute(new Drupal7Route($path, $item));
+      }
+
+      // Now that all routes have been loaded, tell them to resolve their
+      // hierarchical relationships.
+      $this->sourceRoutes->finalize();
+    }
+    return $this->sourceRoutes;
+  }
+
+  /**
+   * Returns the collection of routes in the destination.
+   *
+   * @return RouterInterface
+   *   The requested route collection.
+   */
+  public function getDestinationRoutes() {
+    if (empty($this->destinationRoutes)) {
+      $this->destinationRoutes = $this->buildDestinationRoutes();
+    }
+    return $this->destinationRoutes;
+  }
+
+  /**
+   * Returns the destination route for the given source path.
+   *
+   * @param string $path
+   *   The source path, as defined in hook_menu().
+   *
+   * @return \Drupal\drupalmoduleupgrader\Routing\Drupal8\RouteWrapper|NULL
+   *   The destination route.
+   */
+  public function getDestinationRoute($path) {
+    return $this->getDestinationRoutes()->get($this->routeMap[$path]);
+  }
+
+  /**
+   * Builds the Drupal 8 router by running the Drupal 7 router items through
+   * the appropriate route converters.
+   *
+   * @return RouterInterface
+   */
+  private function buildDestinationRoutes() {
+    // @todo These are currently hardcoded on the D7 -> D8 conversion. Make this
+    //   configurable.
+    $router = new Drupal8Router();
+    $this->routeMap = [];
+
+    foreach ($this->getSourceRoutes() as $path => $route) {
+      /** @var Drupal7\RouteWrapper $route */
+      // If the route hasn't got a page callback...don't even try.
+      if (!$route->containsKey('page callback')) {
+        continue;
+      }
+
+      // Get the appropriate route converter, which will build the route
+      // definition.
+      $plugin_id = $route['page callback'];
+      if (!$this->routeConverters->hasDefinition($plugin_id)) {
+        $plugin_id = 'default';
+      }
+
+      /** @var Drupal8\RouteWrapper $d8_route */
+      $d8_route = $this->routeConverters->createInstance($plugin_id)->buildRouteDefinition($this->target, $route);
+      $router->addRoute($d8_route);
+      $this->routeMap[$path] = $d8_route->getIdentifier();
+    }
+    $router->finalize();
+
+    foreach ($this->getSourceRoutes()->getDefaultLocalTasks() as $path => $route) {
+      /** @var Drupal7\RouteWrapper $route */
+      if ($route->hasParent()) {
+        $parent = (string) $route->getParent()->getPath();
+        $this->routeMap[$path] = $this->routeMap[$parent];
+      }
+    }
+
+    return $router;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkBinding/LinkBinding.php b/web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkBinding/LinkBinding.php
new file mode 100644 (file)
index 0000000..217d8d7
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Routing\LinkBinding;
+
+use Drupal\drupalmoduleupgrader\Routing\Drupal7\RouteWrapper as Drupal7Route;
+use Drupal\drupalmoduleupgrader\Routing\Drupal8\RouteWrapper as Drupal8Route;
+use Drupal\drupalmoduleupgrader\Routing\LinkIndex;
+
+/**
+ * Represents a binding between a Drupal 7 route and a Drupal 8 one.
+ */
+class LinkBinding {
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\Routing\Drupal7\RouteWrapper
+   */
+  protected $source;
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\Routing\Drupal8\RouteWrapper
+   */
+  protected $destination;
+
+  /**
+   * The link ID.
+   *
+   * @var string
+   */
+  protected $id;
+
+  /**
+   * Index of all other links of this type.
+   *
+   * @var LinkIndex
+   */
+  protected $index;
+
+  /**
+   * Constructs a LinkBinding object.
+   */
+  public function __construct(Drupal7Route $source, Drupal8Route $destination) {
+    $this->source = $source;
+    $this->destination = $destination;
+  }
+
+  /**
+   * Returns the Drupal 7 route in this binding.
+   *
+   * @return \Drupal\drupalmoduleupgrader\Routing\Drupal7\RouteWrapper
+   */
+  public function getSource() {
+    return $this->source;
+  }
+
+  /**
+   * Returns the Drupal 8 route in this binding.
+   *
+   * @return Drupal7Route
+   */
+  public function getDestination() {
+    return $this->destination;
+  }
+
+  /**
+   * Returns the link's plugin ID.
+   *
+   * @return string
+   */
+  public function getIdentifier() {
+    return $this->id ?: $this->getDestination()->getIdentifier();
+  }
+
+  /**
+   * React when the binding is added to an index.
+   *
+   * @param string $id
+   *  The link's plugin ID, sanitized to prevent collisions.
+   * @param LinkIndex $index
+   *  The link index.
+   */
+  public function onIndexed($id, LinkIndex $index) {
+    $this->id = $id;
+    $this->index = $index;
+  }
+
+  /**
+   * Builds the link definition.
+   *
+   * @return array
+   */
+  public function build() {
+    $link = [
+      'route_name' => $this->getDestination()->getIdentifier(),
+    ];
+
+    $source = $this->getSource();
+    if ($source->containsKey('title')) {
+      $link['title'] = $source['title'];
+    }
+    if ($source->containsKey('weight')) {
+      $link['weight'] = $source['weight'];
+    }
+
+    return $link;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkBinding/LinkBindingFactory.php b/web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkBinding/LinkBindingFactory.php
new file mode 100644 (file)
index 0000000..1168bde
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Routing\LinkBinding;
+
+use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\drupalmoduleupgrader\Routing\Drupal7\RouteWrapper as Drupal7Route;
+use Drupal\drupalmoduleupgrader\Routing\Drupal8\RouteWrapper as Drupal8Route;
+
+/**
+ * Factory class to create link bindings, depending on the source route's type.
+ */
+class LinkBindingFactory {
+
+  /**
+   * @var PluginManagerInterface
+   */
+  private $linkManager;
+
+  public function __construct(PluginManagerInterface $link_manager) {
+    $this->linkManager = $link_manager;
+  }
+
+  /**
+   * Factory method. Returns a link binding object appropriate for the source link type.
+   *
+   * @param Drupal7Route $source
+   *  The source (Drupal 7) route.
+   * @param Drupal8Route $destination
+   *  The destination (Drupal 8) route.
+   *
+   * @return mixed
+   *  A link binding object; either an instance of this class or a subclass thereof.
+   */
+  public function create(Drupal7Route $source, Drupal8Route $destination) {
+    if ($source->isLink()) {
+      return new MenuLinkBinding($source, $destination);
+    }
+    elseif ($source->isLocalTask() || $source->isDefaultLocalTask()) {
+      return new LocalTaskLinkBinding($source, $destination, $this->linkManager);
+    }
+    elseif ($source->isLocalAction()) {
+      if ($source->isContextualLink()) {
+        return new LinkBinding($source, $destination);
+      }
+      else {
+        return new LocalActionLinkBinding($source, $destination);
+      }
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkBinding/LocalActionLinkBinding.php b/web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkBinding/LocalActionLinkBinding.php
new file mode 100644 (file)
index 0000000..4aedc69
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Routing\LinkBinding;
+
+/**
+ * Represents a local action.
+ */
+class LocalActionLinkBinding extends LinkBinding {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    $link = parent::build();
+    $link['appears_on'][] = $this->getDestination()->getIdentifier();
+
+    return $link;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkBinding/LocalTaskLinkBinding.php b/web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkBinding/LocalTaskLinkBinding.php
new file mode 100644 (file)
index 0000000..39ae82b
--- /dev/null
@@ -0,0 +1,86 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Routing\LinkBinding;
+
+use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\drupalmoduleupgrader\Routing\Drupal7\RouteWrapper as Drupal7Route;
+use Drupal\drupalmoduleupgrader\Routing\Drupal8\RouteWrapper as Drupal8Route;
+
+/**
+ * Represents a local task or default local task.
+ */
+class LocalTaskLinkBinding extends LinkBinding {
+
+  /**
+   * @var PluginManagerInterface
+   */
+  private $linkManager;
+
+  /**
+   * Constructs a LinkBinding object.
+   */
+  public function __construct(Drupal7Route $source, Drupal8Route $destination, PluginManagerInterface $link_manager) {
+    parent::__construct($source, $destination);
+    $this->linkManager = $link_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    $link = parent::build();
+
+    $source = $this->getSource();
+
+    if ($source->isDefaultLocalTask()) {
+      $link['base_route'] = $link['route_name'];
+    }
+    elseif ($source->isLocalTask()) {
+      $default_task = $source->getDefaultTask();
+      if ($default_task) {
+        $path = $default_task->getPath()->__toString();
+
+        if ($this->index->containsKey($path)) {
+          $link['base_route'] = $this->index[$path]->getDestination()->getIdentifier();
+        }
+      }
+    }
+
+    if ($source->hasParent()) {
+      $parent = $source->getParent();
+
+      if ($parent->isLocalTask() || $parent->isDefaultLocalTask()) {
+        $parent_id = $this->getParentID();
+
+        if ($parent_id) {
+          unset($link['base_route']);
+          $link['parent_id'] = $parent_id;
+        }
+      }
+    }
+
+    return $link;
+  }
+
+  /**
+   * Gets the parent task's link ID, if any.
+   *
+   * @return string|NULL
+   */
+  public function getParentID() {
+    $path = $this->getSource()->getParent()->getPath()->__toString();
+
+    if ($this->index->containsKey($path)) {
+      return $this->index[$path]->getIdentifier();
+    }
+
+    $parent = $this->getDestination()->getParent()->getIdentifier();
+
+    foreach ($this->linkManager->getDefinitions() as $id => $link) {
+      if ($link['route_name'] == $parent) {
+        return $id;
+      }
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkBinding/MenuLinkBinding.php b/web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkBinding/MenuLinkBinding.php
new file mode 100644 (file)
index 0000000..854718b
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Routing\LinkBinding;
+
+/**
+ * Represents a standard menu link.
+ */
+class MenuLinkBinding extends LinkBinding {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    $link = parent::build();
+
+    $source = $this->getSource();
+    if ($source->containsKey('description')) {
+      $link['description'] = $source['description'];
+    }
+
+    $destination = $this->getDestination();
+    if ($destination->hasParent()) {
+      $link['parent'] = $destination->getParent()->getIdentifier();
+    }
+
+    return $link;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkIndex.php b/web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkIndex.php
new file mode 100644 (file)
index 0000000..71a0846
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Converter\Routing\LinkIndex.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Routing;
+
+use Doctrine\Common\Collections\ArrayCollection;
+use Drupal\drupalmoduleupgrader\Routing\LinkBinding\LinkBinding;
+
+/**
+ * Represents a set of link bindings of a single type (i.e., menu links, local tasks, etc.)
+ */
+class LinkIndex extends ArrayCollection {
+
+  /**
+   * Tracks link IDs to prevent collisions.
+   *
+   * @var string[]
+   */
+  protected $idiotBox = [];
+
+  /**
+   * Adds a binding to this index.
+   *
+   * @param \Drupal\drupalmoduleupgrader\Routing\LinkBinding\LinkBinding $binding
+   */
+  public function addBinding(LinkBinding $binding) {
+    $id = $binding->getIdentifier();
+
+    if (isset($this->idiotBox[$id])) {
+      $id .= '_' . $this->idiotBox[$id]++;
+    }
+    else {
+      $this->idiotBox[$id] = 0;
+    }
+
+    $this->set($binding->getSource()->getPath()->__toString(), $binding);
+    $binding->onIndexed($id, $this);
+  }
+
+  /**
+   * Builds all the links in this index and returns them as an array of arrays,
+   * keyed by link ID.
+   *
+   * @return array
+   */
+  public function build() {
+    $build = [];
+
+    foreach ($this as $binding) {
+      $build[ $binding->getIdentifier() ] = $binding->build();
+    }
+
+    return $build;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Routing/ParameterBinding.php b/web/modules/contrib/drupalmoduleupgrader/src/Routing/ParameterBinding.php
new file mode 100644 (file)
index 0000000..d43ef40
--- /dev/null
@@ -0,0 +1,122 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Routing;
+
+use Drupal\drupalmoduleupgrader\Utility\Path\Drupal7\PathComponent;
+use Drupal\drupalmoduleupgrader\Utility\Path\PathUtilityInterface;
+use Pharborist\Functions\ParameterNode;
+use Pharborist\Types\ScalarNode;
+
+/**
+ * Represents a binding between a single callback parameter and a single
+ * path component in a Drupal 8 route path, possibly affected by an argument.
+ */
+class ParameterBinding {
+
+  /**
+   * @var PathUtilityInterface
+   */
+  protected $path;
+
+  /**
+   * @var ParameterNode
+   */
+  protected $parameter;
+
+  /**
+   * @var mixed
+   */
+  protected $argument;
+
+  /**
+   * The trouble with Drupal 7 callback arguments is that virtually any value
+   * could be explicitly passed, including NULL and FALSE. -1 is an illegal
+   * value because it's an integer, but not a valid path position. So we'll
+   * use it here as a signal that no argument is explicitly bound to the
+   * parameter.
+   */
+  const NO_ARGUMENT = -1;
+
+  public function __construct(PathUtilityInterface $path, ParameterNode $parameter, $argument = self::NO_ARGUMENT) {
+    // Clone $path so that we have our own copy to look at. The original $path
+    // is (probably) modified by upstream code.
+    $this->path = clone $path;
+    $this->parameter = $parameter;
+    $this->argument = $argument;
+  }
+
+  /**
+   * The original parameter node.
+   *
+   * @return \Pharborist\Functions\ParameterNode
+   */
+  public function getParameter() {
+    return $this->parameter;
+  }
+
+  /**
+   * Returns if the parameter is explicitly represented in the path.
+   *
+   * @return boolean
+   */
+  public function inPath() {
+    return ($this->isPathPosition() && sizeof($this->path) > $this->getArgument());
+  }
+
+  /**
+   * Returns if this binding has an explicit argument.
+   *
+   * @return boolean
+   */
+  public function hasArgument() {
+    return ($this->getArgument() !== self::NO_ARGUMENT);
+  }
+
+  /**
+   * Returns the argument.
+   *
+   * @return mixed
+   */
+  public function getArgument() {
+    return $this->argument;
+  }
+
+  /**
+   * Whether or not the argument is a path position (integer greater
+   * than or equal to 0).
+   *
+   * @return boolean
+   */
+  public function isPathPosition() {
+    return ($this->hasArgument() && is_integer($this->getArgument()));
+  }
+
+  /**
+   * Returns the value of the binding. If the value is an instance of
+   * \Drupal\drupalmoduleupgrader\Utility\Path\PathComponentInterface,
+   * the binding expects to be physically represented in the path, although
+   * it may not yet be (this can be ascertained by the inPath() method). Any
+   * other value is used verbatim.
+   *
+   * @return mixed
+   */
+  public function getValue() {
+    if ($this->hasArgument()) {
+      if ($this->isPathPosition()) {
+        $position = $this->getArgument();
+        return $this->path->containsKey($position) ? $this->path[$position] : new PathComponent('%');
+      }
+      else {
+        return $this->getArgument();
+      }
+    }
+    else {
+      $value = $this->getParameter()->getValue();
+
+      if ($value instanceof ScalarNode) {
+        return $value->toValue();
+      }
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Routing/ParameterMap.php b/web/modules/contrib/drupalmoduleupgrader/src/Routing/ParameterMap.php
new file mode 100644 (file)
index 0000000..65eb3af
--- /dev/null
@@ -0,0 +1,156 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Routing;
+
+use Doctrine\Common\Collections\ArrayCollection;
+use Drupal\drupalmoduleupgrader\Utility\Path\Drupal7\PathComponent;
+use Drupal\drupalmoduleupgrader\Utility\Path\Drupal8\PathComponent as PathComponent8x;
+use Drupal\drupalmoduleupgrader\Utility\Path\PathUtilityInterface;
+use Symfony\Component\Routing\Route as Drupal8Route;
+
+/**
+ * Represents a set of parameter bindings for a particular path, callback,
+ * and set of arguments.
+ */
+class ParameterMap extends ArrayCollection {
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\Utility\Path\PathUtilityInterface
+   */
+  protected $path;
+
+  /**
+   * @var integer
+   */
+  protected $_length = 0;
+
+  protected $bindings = [];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(PathUtilityInterface $path, array $parameters, array $arguments = []) {
+    parent::__construct();
+    $this->path = $path;
+    $this->_length = sizeof($path);
+
+    while ($parameters) {
+      $argument = $arguments ? array_shift($arguments) : ParameterBinding::NO_ARGUMENT;
+      $this->addBinding(new ParameterBinding($path, array_shift($parameters), $argument));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function toArray() {
+    $output = [];
+
+    foreach ($this->bindings as $key => $bindings) {
+      if (is_integer($key)) {
+        /** @var ParameterBinding[] $bindings */
+        foreach ($bindings as $binding) {
+          $parameter = $binding->getParameter()->getName();
+          $function = $binding->getParameter()->getFunction()->getName()->getText();
+          $output[$function][$parameter]['name'] = $bindings[0]->getParameter()->getName();
+
+          $value = $bindings[0]->getValue();
+          if ($value instanceof PathComponent && $value->isWildcard()) {
+            $output[$function][$parameter]['type'] = ltrim($value, '%');
+          }
+        }
+      }
+    }
+
+    return $output;
+  }
+
+  /**
+   * Merge another parameter map into this one. Bindings from the incoming map
+   * should 'win', although the specifics are up to the implementing classes.
+   *
+   * @param ParameterMap $map
+   *  The parameter map to merge.
+   */
+  public function merge(ParameterMap $map) {
+    foreach ($map as $binding) {
+      $this->addBinding($binding);
+    }
+  }
+
+  /**
+   * Adds a binding to this map, overwriting the existing one if there is a
+   * conflict.
+   *
+   * @param ParameterBinding $binding
+   *  The binding to add.
+   */
+  public function addBinding(ParameterBinding $binding) {
+    $value = $binding->getValue();
+    // The binding will return a PathComponent if it expects to be physically
+    // represented in the path, whether or not it already is.
+    if ($value instanceof PathComponent) {
+      if ($binding->inPath()) {
+        $key = $binding->getArgument();
+      }
+      else {
+        $key = $this->path->indexOf($value);
+        if ($key === FALSE) {
+          $key = $this->_length++;
+        }
+      }
+    }
+    else {
+      $key = $binding->getParameter()->getName();
+    }
+
+    $this->set($key, $binding);
+
+    if (! isset($this->bindings[$key])) {
+      $this->bindings[$key] = [];
+    }
+    array_unshift($this->bindings[$key], $binding);
+  }
+
+  /**
+   * Applies the parameter map to a path, modifying it as needed.
+   *
+   * @param \Drupal\drupalmoduleupgrader\Utility\Path\PathUtilityInterface $path
+   *  The path to modify (in-place).
+   */
+  public function applyPath(PathUtilityInterface $path) {
+    foreach ($this as $key => $binding) {
+      if (is_integer($key)) {
+        $path[$key] = new PathComponent8x('{' . $binding->getParameter()->getName() . '}');
+      }
+    }
+  }
+
+  /**
+   * Apply the parameter map to a Drupal 8 route, modifying it as needed.
+   *
+   * @param \Symfony\Component\Routing\Route $route
+   *  The route to process.
+   */
+  public function applyRoute(Drupal8Route $route) {
+    $this->applyPath($this->path);
+
+    foreach ($this as $key => $binding) {
+      $parameter = $binding->getParameter();
+
+      /** @var ParameterBinding $binding */
+      if (is_integer($key)) {
+        if ($parameter->isOptional()) {
+          // @todo Don't use eval().
+          $value = eval('return ' . $parameter->getValue() . ';');
+          $route->setDefault($parameter->getName(), $value);
+        }
+      }
+      elseif ($binding->hasArgument()) {
+        $route->setDefault($parameter->getName(), $binding->getValue());
+      }
+    }
+    $route->setPath($this->path->__toString());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Routing/RouteConverterInterface.php b/web/modules/contrib/drupalmoduleupgrader/src/Routing/RouteConverterInterface.php
new file mode 100644 (file)
index 0000000..22b1c77
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Routing;
+
+use Drupal\drupalmoduleupgrader\Routing\Drupal7\RouteWrapper;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+
+/**
+ * Defines a route converter, which converts a Drupal 7 router item to a
+ * Drupal 8 Symfony route. These plugins are NOT responsible for converting
+ * *links* (including tabs or local actions), only the actual route.
+ *
+ * Every method in this interface takes two arguments. First, a TargetInterface
+ * representing the target module. Second, the original Drupal 7 route (i.e.,
+ * hook_menu() item), wrapped in a RouteWrapperInterface.
+ */
+interface RouteConverterInterface {
+
+  /**
+   * Generates the route's machine-readable name.
+   *
+   * @return string
+   */
+  public function getName(TargetInterface $target, RouteWrapper $route);
+
+  /**
+   * Builds the Drupal 8 path for the route.
+   *
+   * The path should be prefixed by a slash, and contain {slugs} corresponding
+   * to parameters of the callback method which can accept input from the path.
+   * Parameters are matched to slugs by name and type hint.
+   *
+   * @return \Drupal\drupalmoduleupgrader\Utility\Path\PathUtilityInterface
+   */
+  public function buildPath(TargetInterface $target, RouteWrapper $route);
+
+  /**
+   * Builds the Drupal 8 definition for the route, without making any changes
+   * to the original module or callback.
+   *
+   * @return Drupal8\RouteWrapper
+   */
+  public function buildRouteDefinition(TargetInterface $target, RouteWrapper $route);
+
+  /**
+   * Builds the Drupal 8 route, making any needed changes to the original module
+   * and/or callback.
+   */
+  public function buildRoute(TargetInterface $target, RouteWrapper $route);
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Routing/RouteWrapperInterface.php b/web/modules/contrib/drupalmoduleupgrader/src/Routing/RouteWrapperInterface.php
new file mode 100644 (file)
index 0000000..50e83be
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Routing;
+
+/**
+ * Common interface implemented by classes which wrap around Drupal 7 or
+ * Drupal 8 routes.
+ */
+interface RouteWrapperInterface {
+
+  /**
+   * Returns an identifier for this route.
+   *
+   * @return string
+   */
+  public function getIdentifier();
+
+  /**
+   * Returns a PathUtilityInterface implementation for the route.
+   *
+   * @return \Drupal\drupalmoduleupgrader\Utility\Path\PathUtilityInterface
+   */
+  public function getPath();
+
+  /**
+   * Returns if this route has a parent.
+   *
+   * @return boolean
+   */
+  public function hasParent();
+
+  /**
+   * Gets the parent route, if there is one. The parent should also be wrapped.
+   *
+   * @return static|NULL
+   */
+  public function getParent();
+
+  /**
+   * Returns the original, unwrapped route.
+   *
+   * @return mixed
+   */
+  public function unwrap();
+
+  /**
+   * React to the router (i.e., the collection of routes defined by the
+   * module) being completely built.
+   *
+   * @param RouterBuiltEvent $event
+   *  The event object.
+   */
+  public function onRouterBuilt(RouterBuiltEvent $event);
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Routing/RouterBase.php b/web/modules/contrib/drupalmoduleupgrader/src/Routing/RouterBase.php
new file mode 100644 (file)
index 0000000..ac057dc
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Converter\Routing\RouterBase.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Routing;
+
+use Doctrine\Common\Collections\ArrayCollection;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+
+/**
+ * Base class for RouterInterface implementations.
+ */
+class RouterBase extends ArrayCollection implements RouterInterface {
+
+  /**
+   * @var \Symfony\Component\EventDispatcher\EventDispatcher
+   */
+  protected $dispatcher;
+
+  /**
+   * Constructs a RouterBase.
+   */
+  public function __construct(array $elements = []) {
+    parent::__construct($elements);
+    $this->dispatcher = new EventDispatcher();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addRoute(RouteWrapperInterface $route) {
+    $this->set($route->getIdentifier(), $route);
+    $this->dispatcher->addListener('router.built', [ $route, 'onRouterBuilt' ]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function finalize() {
+    $this->dispatcher->dispatch('router.built', new RouterBuiltEvent($this));
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Routing/RouterBuiltEvent.php b/web/modules/contrib/drupalmoduleupgrader/src/Routing/RouterBuiltEvent.php
new file mode 100644 (file)
index 0000000..c4f2307
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Converter\Routing\RouterBuiltEvent.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Routing;
+
+use Symfony\Component\EventDispatcher\Event;
+
+/**
+ * Event object fired when all routes have been added to a RouterInterface
+ * implementation.
+ */
+class RouterBuiltEvent extends Event {
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\Converter\Routing\RouterInterface
+   */
+  protected $router;
+
+  /**
+   * Constructs a RouterBuiltEvent object.
+   */
+  public function __construct(RouterInterface $router) {
+    $this->router = $router;
+  }
+
+  /**
+   * Returns the router object.
+   *
+   * @return \Drupal\drupalmoduleupgrader\Converter\Routing\RouterInterface
+   */
+  public function getRouter() {
+    return $this->router;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Routing/RouterInterface.php b/web/modules/contrib/drupalmoduleupgrader/src/Routing/RouterInterface.php
new file mode 100644 (file)
index 0000000..0266033
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Converter\Routing\RouterInterface.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Routing;
+
+/**
+ * Defines a collection of routes, each wrapped by an implementation of
+ * RouteWrapperInterface in order to normalize the gaping differences
+ * between Drupal 7 and Drupal 8 routes.
+ */
+interface RouterInterface {
+
+  /**
+   * Adds a wrapped route definition to this router.
+   *
+   * @param RouteWrapperInterface $route
+   *  The wrapped route definition.
+   */
+  public function addRoute(RouteWrapperInterface $route);
+
+  /**
+   * Completes the 'build' of this router, dispatching the 'router.built'
+   * event to all added routes.
+   */
+  public function finalize();
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Target.php b/web/modules/contrib/drupalmoduleupgrader/src/Target.php
new file mode 100644 (file)
index 0000000..30a6b26
--- /dev/null
@@ -0,0 +1,279 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader;
+
+use Doctrine\Common\Collections\ArrayCollection;
+use Drupal\Component\Utility\SafeMarkup;
+use Pharborist\Node;
+use Pharborist\Parser;
+use Pharborist\RootNode;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\Finder\Finder;
+
+/**
+ * Default implementation of TargetInterface.
+ */
+class Target implements TargetInterface {
+
+  /**
+   * The target module's machine name.
+   *
+   * @var string
+   */
+  protected $id;
+
+  /**
+   * @var \Drupal\Component\Plugin\PluginManagerInterface
+   */
+  protected $indexerManager;
+
+  /**
+   * The target module's base path.
+   *
+   * @var string
+   */
+  protected $basePath;
+
+  /**
+   * @var IndexerInterface[]
+   */
+  protected $indexers = [];
+
+  /**
+   * @var \Doctrine\Common\Collections\ArrayCollection
+   */
+  protected $services;
+
+  /**
+   * All open documents.
+   *
+   * @var \Pharborist\RootNode[]
+   */
+  protected $documents = [];
+
+  /**
+   * Constructs a Target.
+   *
+   * @param string $path
+   *  The base path of the target module.
+   * @param ContainerInterface $container
+   *  The current container, to pull any dependencies out of.
+   */
+  public function __construct($path, ContainerInterface $container) {
+    $this->indexerManager = $container->get('plugin.manager.drupalmoduleupgrader.indexer');
+
+    if (is_dir($path)) {
+      $this->basePath = $path;
+    }
+    else {
+      throw new \RuntimeException(SafeMarkup::format('Invalid base path: @path', ['@path' => $path]));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function id() {
+    if (empty($this->id)) {
+      $dir = $this->getBasePath();
+      $info = (new Finder)->in($dir)->depth('== 0')->name('*.info')->getIterator();
+      $info->rewind();
+
+      if ($info_file = $info->current()) {
+        $this->id = subStr($info_file->getFilename(), 0, -5);
+      }
+      else {
+        throw new \RuntimeException(SafeMarkup::format('Could not find info file in @dir', ['@dir' => $dir]));
+      }
+    }
+    return $this->id;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getBasePath() {
+    return $this->basePath;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPath($file) {
+    if ($file{0} == '.') {
+      $file = $this->id() . $file;
+    }
+    return $this->getBasePath() . '/' . ltrim($file, '/');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFinder() {
+      // We do NOT want to include submodules. We can detect one by the presence
+      // of an info file -- if there is one, its directory is a submodule.
+      $directories = (new Finder)
+        ->directories()
+        ->in($this->getBasePath())
+        ->filter(function(\SplFileInfo $dir) {
+          return (new Finder)->files()->in($dir->getPathname())->depth('== 0')->name('*.info')->count() === 0;
+        });
+
+      $directories = array_keys(iterator_to_array($directories));
+      $directories[] = $this->getBasePath();
+
+      return (new Finder)
+        ->files()
+        ->in($directories)
+        // We don't need to recurse, because we've already determined which
+        // directories to search.
+        ->depth('== 0')
+        ->name('*.module')
+        ->name('*.install')
+        ->name('*.inc')
+        ->name('*.php')
+        ->name('*.test');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIndexer($which) {
+    if (empty($this->indexers[$which])) {
+      /** @var IndexerInterface $indexer */
+      $indexer = $this->indexerManager->createInstance($which);
+      $indexer->bind($this);
+      $this->indexers[$which] = $indexer;
+    }
+    return $this->indexers[$which];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getServices() {
+    if (empty($this->services)) {
+      $this->services = new ArrayCollection();
+    }
+    return $this->services;
+  }
+
+  /**
+   * Runs all available indexers on this target.
+   */
+  public function buildIndex() {
+    $indexers = array_keys($this->indexerManager->getDefinitions());
+    foreach ($indexers as $id) {
+      $this->getIndexer($id)->build();
+    }
+    // Release syntax trees that were opened during indexing.
+    $this->flush();
+  }
+
+  /**
+   * Destroys all index data for this target.
+   */
+  public function destroyIndex() {
+    $indexers = array_keys($this->indexerManager->getDefinitions());
+    foreach ($indexers as $id) {
+      $this->getIndexer($id)->destroy();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function implementsHook($hook) {
+    return $this->getIndexer('function')->has('hook_' . $hook);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function executeHook($hook, array $arguments = []) {
+    if ($this->implementsHook($hook)) {
+      return $this->getIndexer('function')->execute('hook_' . $hook, $arguments);
+    }
+    else {
+      $variables = [
+        '@module' => $this->id(),
+        '@hook' => $hook,
+      ];
+      throw new \InvalidArgumentException(SafeMarkup::format('@module does not implement hook_@hook.', $variables));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function open($file) {
+    if (empty($this->documents[$file])) {
+      $this->documents[$file] = Parser::parseFile($file);
+    }
+    return $this->documents[$file];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save(Node $node = NULL) {
+    if ($node) {
+      $file = $this->getFileOf($node);
+      if ($file) {
+        $doc = $node instanceof RootNode ? $node : $node->parents()->get(0);
+
+        $victory = file_put_contents($file, $doc->getText());
+        if ($victory === FALSE) {
+          throw new IOException(SafeMarkup::format('Failed to save @file.', [ '@file' => $file ]));
+        }
+      }
+      else {
+        throw new IOException('Cannot save a node that is not attached to an open document.');
+      }
+    }
+    else {
+      array_walk($this->documents, [ $this, 'save' ]);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function create($file, $ns = NULL) {
+    $this->documents[$file] = RootNode::create($ns);
+    return $this->documents[$file];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function flush() {
+    $this->documents = [];
+  }
+
+  /**
+   * Determines which currently-open file a node belongs to, if any. Nodes
+   * which are not part of any open syntax tree will return NULL.
+   *
+   * @return string|NULL
+   */
+  public function getFileOf(Node $node) {
+    if ($node instanceof RootNode) {
+      $root = $node;
+    }
+    else {
+      $parents = $node->parents();
+      if ($parents->isEmpty()) {
+        return NULL;
+      }
+      $root = $parents->get(0);
+    }
+
+    foreach ($this->documents as $file => $doc) {
+      if ($root === $doc) {
+        return $file;
+      }
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/TargetInterface.php b/web/modules/contrib/drupalmoduleupgrader/src/TargetInterface.php
new file mode 100644 (file)
index 0000000..9e98bae
--- /dev/null
@@ -0,0 +1,126 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader;
+
+use Pharborist\Node;
+
+/**
+ * Represents a Drupal 7 module being run through the DMU.
+ */
+interface TargetInterface {
+
+  /**
+   * Returns the machine name of the target module.
+   *
+   * @return string
+   */
+  public function id();
+
+  /**
+   * Returns the base path of the target module.
+   *
+   * @return string
+   */
+  public function getBasePath();
+
+  /**
+   * Returns the path to a particular file, relative to the CWD.
+   *
+   * @param string $file
+   *  The file, relative to the module root. If $file begins with a period,
+   *  it will be prefixed with the module name (.module --> MODULE.module)
+   *
+   * @return string
+   */
+  public function getPath($file);
+
+  /**
+   * Returns a fully configured Finder which can iterate over the target
+   * module's code files. Any file type which doesn't contain PHP code
+   * should be ignored.
+   *
+   * @return \Symfony\Component\Finder\Finder
+   */
+  public function getFinder();
+
+  /**
+   * Returns an indexer for this target.
+   *
+   * @param string $which
+   *  The type of indexer to get. Should be the ID of an indexer plugin.
+   *
+   * @return IndexerInterface
+   */
+  public function getIndexer($which);
+
+  /**
+   * Returns services defined by the target module.
+   *
+   * @return \Doctrine\Common\Collections\ArrayCollection
+   */
+  public function getServices();
+
+  /**
+   * Returns if the target module implements a particular hook.
+   *
+   * @param string $hook
+   *  The hook to look for, without the hook_ prefix.
+   *
+   * @return boolean
+   */
+  public function implementsHook($hook);
+
+  /**
+   * Executes a hook implementation and returns the result.
+   *
+   * @param string $hook
+   *  The hook to execute, without the hook_ prefix.
+   * @param array $arguments
+   *  Additional parameters to pass to the hook implementation.
+   *
+   * @return mixed
+   *
+   * @throws
+   *  \InvalidArgumentException if the module doesn't implement the hook.
+   *  \LogicException if the hook contains non-executable logic.
+   */
+  public function executeHook($hook, array $arguments = []);
+
+  /**
+   * Parses a file into a syntax tree, keeping a reference to it, and
+   * returns it.
+   *
+   * @param string $file
+   *  The path of the file to open, relative to the CWD.
+   *
+   * @return \Pharborist\RootNode|NULL
+   */
+  public function open($file);
+
+  /**
+   * Saves the file in which a particular node appears.
+   *
+   * @param \Pharborist\Node|NULL $node
+   *  The node to save. This can be positioned anywhere in the
+   *  syntax tree. If NULL, all open files will be saved.
+   *
+   * @throws \Drupal\drupalmoduleupgrader\IOException
+   */
+  public function save(Node $node = NULL);
+
+  /**
+   * Creates a new, empty document.
+   *
+   * @param string $file
+   *  The path of the file to create, relative to the CWD.
+   *
+   * @return \Pharborist\RootNode
+   */
+  public function create($file);
+
+  /**
+   * Clears internal references to all open documents, discarding changes.
+   */
+  public function flush();
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Utility/Filter/ContainsLogicFilter.php b/web/modules/contrib/drupalmoduleupgrader/src/Utility/Filter/ContainsLogicFilter.php
new file mode 100644 (file)
index 0000000..82535df
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Utility\Filter;
+
+use Pharborist\Filter;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\ParentNode;
+
+class ContainsLogicFilter {
+
+  /**
+   * Function calls which should not be considered logic.
+   *
+   * @var string[]
+   */
+  protected $whitelist = [];
+
+  /**
+   * Pharborist node types which are considered logic.
+   *
+   * @var string[]
+   */
+  protected static $logic = [
+    '\Pharborist\ControlStructures\IfNode',
+    '\Pharborist\ControlStructures\SwitchNode',
+    '\Pharborist\Objects\ClassMethodCallNode',
+    '\Pharborist\Objects\ObjectMethodCallNode',
+    '\Pharborist\Objects\NewNode',
+    '\Pharborist\Objects\ClassConstantLookupNode',
+  ];
+
+  /**
+   * Specify a function to be whitelisted so that it will not be considered
+   * logic.
+   *
+   * @param string ... $function
+   *  At least one function to add to the whitelist.
+   */
+  public function whitelist() {
+    $this->whitelist = array_unique(array_merge($this->whitelist, func_get_args()));
+  }
+
+  /**
+   * Tests if a function contains logic: any branching operator, function
+   * call, or object instantiation.
+   *
+   * @param \Pharborist\ParentNode $node
+   *  The node to test.
+   *
+   * @return boolean
+   */
+  public function __invoke(ParentNode $node) {
+    $function_calls = $node
+      ->find(Filter::isInstanceOf('\Pharborist\Functions\FunctionCallNode'))
+      ->not(function(FunctionCallNode $call) {
+        return in_array($call->getName()->getText(), $this->whitelist);
+      });
+
+    if ($function_calls->isEmpty()) {
+      $filter = call_user_func_array('\Pharborist\Filter::isInstanceOf', static::$logic);
+      return (boolean) $node->find($filter)->count();
+    }
+    else {
+      return TRUE;
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Utility/Filter/FieldValueFilter.php b/web/modules/contrib/drupalmoduleupgrader/src/Utility/Filter/FieldValueFilter.php
new file mode 100644 (file)
index 0000000..63d9af9
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Utility\Filter;
+
+use Pharborist\ArrayLookupNode;
+use Pharborist\Node;
+use Pharborist\Objects\ObjectPropertyNode;
+use Pharborist\Variables\VariableNode;
+
+/**
+ * Filters for things that *look like* field accesses, e.g.
+ * $foo->bar[LANGUAGE_NONE][0]['value']. This filter doesn't guarantee that
+ * matched nodes actually ARE field accesses -- just that they have the proper
+ * formation (S-foils in attack formation!...what, you don't like Star Wars?)
+ */
+class FieldValueFilter {
+
+  /**
+   * @var string
+   */
+  protected $variable;
+
+  public function __construct($variable) {
+    $this->variable = $variable;
+  }
+
+  /**
+   * @return boolean
+   */
+  public function __invoke(Node $node) {
+    if ($node instanceof ArrayLookupNode) {
+      $root = $node->getRootArray();
+
+      if ($root instanceof ObjectPropertyNode) {
+        $object = $root->getObject();
+
+        if ($object instanceof VariableNode && $object->getName() == $this->variable) {
+          return (sizeof($node->getKeys()) >= 3);
+        }
+      }
+    }
+    return FALSE;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Utility/Filter/FunctionCallArgumentFilter.php b/web/modules/contrib/drupalmoduleupgrader/src/Utility/Filter/FunctionCallArgumentFilter.php
new file mode 100644 (file)
index 0000000..97623ab
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Utility\Filter;
+
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Node;
+use Pharborist\Variables\VariableNode;
+
+/**
+ * Filters for function calls which are passed a particular argument.
+ */
+class FunctionCallArgumentFilter {
+
+  /**
+   * @var string
+   */
+  protected $variable;
+
+  public function __construct($variable) {
+    $this->variable = $variable;
+  }
+
+  /**
+   * @return boolean
+   */
+  public function __invoke(Node $node) {
+    if ($node instanceof FunctionCallNode) {
+      return (boolean) $node->getArgumentList()->children([$this, 'hasArgument'])->count();
+    }
+    return FALSE;
+  }
+
+  /**
+   * @return boolean
+   */
+  public function hasArgument(Node $argument) {
+    return ($argument instanceof VariableNode && $argument->getName() == $this->variable);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Utility/Filter/NodeAssignmentFilter.php b/web/modules/contrib/drupalmoduleupgrader/src/Utility/Filter/NodeAssignmentFilter.php
new file mode 100644 (file)
index 0000000..dd83849
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Utility\Filter;
+
+use Pharborist\Filter;
+use Pharborist\Node;
+
+class NodeAssignmentFilter {
+
+  /**
+   * Tests if the given node is on the left side of an assignment.
+   *
+   * @param \Pharborist\Node $node
+   *  The node to test.
+   *
+   * @return boolean
+   */
+  public function __invoke(Node $node) {
+    /** @var \Pharborist\Operators\AssignNode $assignment */
+    $assignment = $node->closest(Filter::isInstanceOf('\Pharborist\Operators\AssignNode'));
+    return ($assignment ? $assignment->getLeftOperand() === $node : FALSE);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Utility/FormConverter.php b/web/modules/contrib/drupalmoduleupgrader/src/Utility/FormConverter.php
new file mode 100644 (file)
index 0000000..9baf2ac
--- /dev/null
@@ -0,0 +1,178 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Utility;
+
+use Drupal\drupalmoduleupgrader\RewriterInterface;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+use Pharborist\Filter;
+use Pharborist\Functions\FunctionCallNode;
+use Pharborist\Functions\FunctionDeclarationNode;
+use Pharborist\Functions\ParameterNode;
+use Pharborist\Objects\ClassMethodNode;
+use Pharborist\Objects\ClassNode;
+use Pharborist\Parser;
+use Pharborist\Token;
+use Pharborist\TokenNode;
+
+/**
+ * Converts a form from a set of callback functions to a class implementing
+ * \Drupal\Core\Form\FormInterface.
+ */
+class FormConverter {
+
+  use StringTransformTrait;
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\TargetInterface
+   */
+  protected $target;
+
+  /**
+   * @var string
+   */
+  protected $formID;
+
+  /**
+   * @var \Pharborist\Functions\FunctionDeclarationNode
+   */
+  protected $builder;
+
+  /**
+   * @var \Pharborist\Functions\FunctionDeclarationNode
+   */
+  protected $validator;
+
+  /**
+   * @var \Pharborist\Functions\FunctionDeclarationNode
+   */
+  protected $submitHandler;
+
+  /**
+   * @var boolean
+   */
+  protected $isConfig;
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\RewriterInterface
+   */
+  protected $formStateRewriter;
+
+  /**
+   * @var \Pharborist\Objects\ClassNode
+   */
+  protected $controller;
+
+  public function __construct(TargetInterface $target, $form_id, RewriterInterface $rewriter) {
+    $indexer = $target->getIndexer('function');
+
+    $this->target = $target;
+    $this->formID = $form_id;
+
+    $this->builder = $indexer->get($form_id);
+
+    $validator = $form_id . '_validate';
+    if ($indexer->has($validator)) {
+      $this->validator = $indexer->get($validator);
+    }
+    $submit_handler = $form_id . '_submit';
+    if ($indexer->has($submit_handler)) {
+      $this->submitHandler = $indexer->get($submit_handler);
+    }
+
+    $this->isConfig = $this->builder->has(Filter::isFunctionCall('system_settings_form'));
+    $this->formStateRewriter = $rewriter;
+  }
+
+  /**
+   * @return \Pharborist\Objects\ClassNode
+   */
+  public function render() {
+    if (empty($this->controller)) {
+      $render = [
+        '#theme' => 'dmu_form',
+        '#module' => $this->target->id(),
+        '#form_id' => $this->formID,
+        '#class' => $this->toTitleCase($this->formID),
+        '#config' => $this->isConfig,
+      ];
+      $source = \Drupal::service('renderer')->renderPlain($render);
+      $this->controller = Parser::parseSource($source)
+        ->find(Filter::isClass($render['#class']))->get(0);
+    }
+    return $this->controller;
+  }
+
+  /**
+   * @return \Pharborist\Objects\ClassNode
+   */
+  public function build() {
+    $controller = $this->render();
+
+    $builder = $this->addMethod($this->builder, $controller, 'buildForm');
+    if ($this->isConfig) {
+      $builder
+        ->find(Filter::isFunctionCall('system_settings_form'))
+        ->each(function(FunctionCallNode $call) {
+          $call
+            ->setName('parent::buildForm')
+            ->appendArgument(Token::variable('$form_state'));
+        });
+    }
+
+    if ($this->validator) {
+      $this
+        ->addMethod($this->validator, $controller, 'validateForm')
+        ->getParameterAtIndex(0)
+        ->setReference(TRUE)
+        ->setTypeHint('array');
+    }
+    if ($this->submitHandler) {
+      $this
+        ->addMethod($this->submitHandler, $controller, ($this->isConfig ? '_submitForm' : 'submitForm'))
+        ->getParameterAtIndex(0)
+        ->setReference(TRUE)
+        ->setTypeHint('array');
+    }
+
+    return $controller;
+  }
+
+  /**
+   * @return \Pharborist\Objects\ClassMethodNode
+   */
+  protected function addMethod(FunctionDeclarationNode $function, ClassNode $class, $alias = NULL) {
+    $method = ClassMethodNode::fromFunction($function);
+    if ($alias) {
+      $method->setName($alias);
+    }
+    $class->appendMethod($method);
+
+    // Add the parameters required for FormInterface conformance.
+    $parameters = $method->getParameters()->toArray();
+    if (empty($parameters)) {
+      $parameters[0] = ParameterNode::create('$form');
+      $method->appendParameter($parameters[0]);
+    }
+    if (sizeof($parameters) == 1) {
+      $parameters[1] = ParameterNode::create('$form_state');
+      $method->appendParameter($parameters[1]);
+    }
+
+    // The $form parameter must have the array type hint.
+    $parameters[0]->setTypeHint('array');
+
+    // The form state is never passed by reference.
+    $parameters[1]->setReference(FALSE);
+
+    // Additional parameters MUST have a default value of NULL in order to conform
+    // to FormInterface.
+    for ($i = 2; $i < sizeof($parameters); $i++) {
+      $parameters[$i]->setValue(new TokenNode(T_STRING, 'NULL'));
+    }
+
+    $this->formStateRewriter->rewrite($parameters[1]);
+
+    return $method;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Utility/FormConverterFactory.php b/web/modules/contrib/drupalmoduleupgrader/src/Utility/FormConverterFactory.php
new file mode 100644 (file)
index 0000000..de35ebf
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Utility;
+
+use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\drupalmoduleupgrader\TargetInterface;
+
+class FormConverterFactory {
+
+  use StringTranslationTrait;
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\RewriterInterface
+   */
+  protected $rewriter;
+
+  public function __construct(TranslationInterface $translator, PluginManagerInterface $rewriters) {
+    $this->stringTranslation = $translator;
+    $this->rewriter = $rewriters->createInstance('form_state');
+  }
+
+  /**
+   * Creates a FormConverter for a specific form.
+   *
+   * @param TargetInterface $target
+   *  The module which defines the form.
+   * @param string $form_id
+   *  The original form ID.
+   *
+   * @return FormConverter
+   *
+   * @throws \BadMethodCallException if the target module doesn't define
+   * the given form.
+   */
+  public function get(TargetInterface $target, $form_id) {
+    $indexer = $target->getIndexer('function');
+
+    if ($indexer->has($form_id)) {
+      return new FormConverter($target, $form_id, $this->rewriter);
+    }
+    else {
+      $message = $this->t('@target does not define form @form_id.', [
+        '@target' => $target->id(),
+        '@form_id' => $form_id,
+      ]);
+      throw new \BadMethodCallException($message);
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/Drupal7/PathComponent.php b/web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/Drupal7/PathComponent.php
new file mode 100644 (file)
index 0000000..f29b2e1
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Utility\Path\Drupal7\PathComponent.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Utility\Path\Drupal7;
+
+use Drupal\drupalmoduleupgrader\Utility\Path\PathComponentBase;
+
+/**
+ * Represents a single component in a Drupal 7 route path.
+ */
+class PathComponent extends PathComponentBase {
+
+  /**
+   * Returns if this component is a generic placeholder (%).
+   *
+   * @return boolean
+   */
+  public function isPlaceholder() {
+    return $this->value == '%';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isWildcard() {
+    return (boolean) preg_match('/%[a-zA-Z0-9_]+/', $this->value);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/Drupal7/PathUtility.php b/web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/Drupal7/PathUtility.php
new file mode 100644 (file)
index 0000000..2adf5a9
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Utility\Path\Drupal7\PathUtility.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Utility\Path\Drupal7;
+
+use Drupal\drupalmoduleupgrader\Utility\Path\PathUtilityBase;
+
+/**
+ * Represents a Drupal 7 route path.
+ */
+class PathUtility extends PathUtilityBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getComponent($value) {
+    return new PathComponent($value);
+  }
+
+  /**
+   * Returns if the path has %wildcards or placeholders (%) in it.
+   *
+   * @return boolean
+   */
+  public function isDynamic() {
+    return ($this->hasWildcards() || $this->hasPlaceholders());
+  }
+
+  /**
+   * Returns if there are placeholders in the path.
+   *
+   * @return boolean
+   */
+  public function hasPlaceholders() {
+    return ($this->getPlaceholders()->count() > 0);
+  }
+
+  /**
+   * Returns every placeholder in the path, keyed by position.
+   *
+   * @return static
+   */
+  public function getPlaceholders() {
+    return $this->filter(function(PathComponent $component) {
+      return $component->isPlaceholder();
+    });
+  }
+
+  /**
+   * Returns a copy of the collection with all placeholders removed.
+   *
+   * @return static
+   */
+  public function deletePlaceholders() {
+    return $this->filter(function(PathComponent $component) {
+      return (! $component->isPlaceholder());
+    });
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/Drupal8/PathComponent.php b/web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/Drupal8/PathComponent.php
new file mode 100644 (file)
index 0000000..07d1661
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Utility\Path\Drupal8\PathComponent.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Utility\Path\Drupal8;
+
+use Drupal\drupalmoduleupgrader\Utility\Path\PathComponentBase;
+
+/**
+ * Represents a single component in a Drupal 8 route path.
+ */
+class PathComponent extends PathComponentBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isWildcard() {
+    return (boolean) preg_match('/\{[a-zA-Z0-9_]+\}/', $this->value);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/Drupal8/PathUtility.php b/web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/Drupal8/PathUtility.php
new file mode 100644 (file)
index 0000000..8246367
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Utility\Path\PathUtilityBase.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Utility\Path\Drupal8;
+
+use Drupal\drupalmoduleupgrader\Utility\Path\PathUtilityBase;
+
+/**
+ * Represents a Drupal 8 route path.
+ */
+class PathUtility extends PathUtilityBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getComponent($value) {
+    return new PathComponent($value);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/PathComponentBase.php b/web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/PathComponentBase.php
new file mode 100644 (file)
index 0000000..a286867
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Utility\Path\PathComponentBase.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Utility\Path;
+
+/**
+ * Represents a single component in a route path.
+ */
+abstract class PathComponentBase implements PathComponentInterface {
+
+  /**
+   * @var string
+   */
+  protected $value;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct($value) {
+    $this->value = $value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __toString() {
+    return $this->value;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/PathComponentInterface.php b/web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/PathComponentInterface.php
new file mode 100644 (file)
index 0000000..5b3cf6c
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Utility\Path\PathComponentInterface.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Utility\Path;
+
+/**
+ * Represents a single component of a route path.
+ */
+interface PathComponentInterface {
+
+  /**
+   * Constructs the path component.
+   *
+   * @param mixed $value
+   */
+  public function __construct($value);
+
+  /**
+   * @return string
+   */
+  public function __toString();
+
+  /**
+   * Returns if this component is considered a wildcard.
+   *
+   * @return boolean
+   */
+  public function isWildcard();
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/PathUtilityBase.php b/web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/PathUtilityBase.php
new file mode 100644 (file)
index 0000000..b2ca3a8
--- /dev/null
@@ -0,0 +1,140 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Utility\Path\PathUtilityBase.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Utility\Path;
+
+use Doctrine\Common\Collections\ArrayCollection;
+
+/**
+ * Base class for PathUtilityInterface implementations.
+ */
+abstract class PathUtilityBase extends ArrayCollection implements PathUtilityInterface {
+
+  /**
+   * The next index for getNextWildcard() to slice on.
+   *
+   * @var integer
+   */
+  protected $_wildcard = 0;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct($path) {
+    if (is_array($path)) {
+      foreach ($path as $component) {
+        $this->add($component);
+      }
+    }
+    elseif (is_string($path)) {
+      $this->__construct(explode('/', $path));
+    }
+    else {
+      throw new \InvalidArgumentException();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function add($value) {
+    if ($value instanceof PathComponentInterface) {
+      parent::add($value);
+    }
+    elseif (is_scalar($value)) {
+      $this->add(static::getComponent($value));
+    }
+    else {
+      throw new \InvalidArgumentException();
+    }
+  }
+
+  /**
+   * Filters the path by a string. The filtered path will only contain
+   * components whose string representation is identical to $element.
+   *
+   * @param string $element
+   *  The string to search for.
+   *
+   * @return static
+   */
+  public function find($element) {
+    return $this
+      ->filter(function(PathComponentInterface $component) use ($element) {
+        return ($element === $component->__toString());
+      });
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function contains($element) {
+    return (boolean) $this->find($element)->count();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasWildcards() {
+    return ($this->getWildcards()->count() > 0);
+  }
+
+  /**
+   * Returns every {wildcard} in the path, keyed by position.
+   *
+   * @return static
+   */
+  public function getWildcards() {
+    return $this->filter(function(PathComponentInterface $component) {
+      return $component->isWildcard();
+    });
+  }
+
+  /**
+   * Returns the next wildcard, if any.
+   *
+   * @return \Drupal\drupalmoduleupgrader\Utility\Path\PathComponentInterface|NULL
+   */
+  public function getNextWildcard() {
+    $wildcards = $this->getWildcards()->slice($this->_wildcard, 1);
+
+    if (isset($wildcards[$this->_wildcard])) {
+      return $wildcards[$this->_wildcard++];
+    }
+  }
+
+  /**
+   * Returns a copy of the collection with wildcards removed.
+   *
+   * @return static
+   */
+  public function deleteWildcards() {
+    return $this->filter(function(PathComponentInterface $component) {
+      return (! $component->isWildcard());
+    });
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getParent() {
+    if ($this->count() > 1) {
+      return new static($this->slice(0, -1));
+    }
+    else {
+      throw new \LengthException('Cannot get parent a path with one component.');
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __toString() {
+    return implode('/', $this->toArray());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/PathUtilityInterface.php b/web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/PathUtilityInterface.php
new file mode 100644 (file)
index 0000000..f9a65aa
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\drupalmoduleupgrader\Utility\Path\PathUtilityInterface.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Utility\Path;
+
+use Doctrine\Common\Collections\Collection as CollectionInterface;
+
+/**
+ * Represents a route path.
+ */
+interface PathUtilityInterface extends CollectionInterface {
+
+  /**
+   * Constructs a path utility.
+   *
+   * @param mixed $path
+   *  The path to wrap, either as a string or an array.
+   *
+   * @throws \InvalidArgumentException
+   */
+  public function __construct($path);
+
+  /**
+   * Returns a new path component wrapping a value.
+   *
+   * @param mixed $value
+   *  The value to wrap.
+   *
+   * @return \Drupal\drupalmoduleupgrader\Utility\Path\PathComponentInterface
+   */
+  public static function getComponent($value);
+
+  /**
+   * Returns if there are wildcards in the path.
+   *
+   * @return boolean
+   */
+  public function hasWildcards();
+
+  /**
+   * Returns a PathUtilityInterface for the parent path.
+   *
+   * @return static
+   *
+   * @throws \LengthException if the path cannot have a parent (i.e.,
+   * the path only has one component).
+   */
+  public function getParent();
+
+  /**
+   * Collapses the path into a string.
+   *
+   * @return string
+   */
+  public function __toString();
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Utility/StringTransformTrait.php b/web/modules/contrib/drupalmoduleupgrader/src/Utility/StringTransformTrait.php
new file mode 100644 (file)
index 0000000..c340b47
--- /dev/null
@@ -0,0 +1,148 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\drupalmoduleupgrader\Utility\StringTransformTrait.
+ */
+
+namespace Drupal\drupalmoduleupgrader\Utility;
+
+/**
+ * Contains methods for transforming strings in various helpful ways.
+ */
+trait StringTransformTrait {
+
+  /**
+   * Converts a string toCamelCase :)
+   *
+   * @param string $string
+   *  The string to convert.
+   *
+   * @return string
+   */
+  public function toCamelCase($string) {
+    return preg_replace_callback('/_[a-z]/', function (array $match) { return strToUpper($match[0]{1}); }, $string);
+  }
+
+  /**
+   * Converts a string ToTitleCase.
+   *
+   * @param string $string
+   *  The string to convert.
+   *
+   * @return string
+   */
+  public function toTitleCase($string) {
+    $string = $this->toCamelCase($string);
+    $string{0} = strToUpper($string{0});
+
+    return $string;
+  }
+
+  /**
+   * Trims a prefix (as well as leading or trailing underscore, if any) from a
+   * string.
+   *
+   * @param string $string
+   *  The string to process.
+   * @param string $prefix
+   *  The prefix to trim off, without leading or trailing underscores.
+   *
+   * @return string
+   */
+  public function unPrefix($string, $prefix) {
+    return preg_replace('/^_?' . $prefix . '_/', NULL, $string);
+  }
+
+  /**
+   * Trims a suffix (as well as leading underscore, if any) from a string.
+   *
+   * @param string $string
+   *  The string to process.
+   * @param string $suffix
+   *  The suffix to trim off, without leading underscore.
+   *
+   * @return string
+   */
+  public function unSuffix($string, $suffix) {
+    return preg_replace('/^_?' . $suffix . '$/', NULL, $string);
+  }
+
+  /**
+   * Deletes {wildcards} from a route path.
+   *
+   * @param string $path
+   *
+   * @return string
+   */
+  public function deleteWildcards($path) {
+    return preg_replace('/\/?\{([a-zA-Z0-9_]+)\}/', NULL, $path);
+  }
+
+  /**
+   * Deletes %wildcards from a route path.
+   *
+   * @param string $path
+   *
+   * @return string
+   */
+  public function deleteLegacyWildcards($path) {
+    return preg_replace('/\/?%[a-zA-Z0-9_]+/', NULL, $path);
+  }
+
+  /**
+   * Generates an identifier from a Drupal 7 path.
+   *
+   * @param string $path
+   *  The input path, including any %wildcards.
+   *
+   * @return string
+   *  The identifier
+   */
+  public function getIdentifierFromLegacyPath($path) {
+    return $this->getIdentifierFromPath($this->deleteLegacyWildcards($path));
+  }
+
+  /**
+   * Generates an identifier from a path.
+   *
+   * @param string $path
+   *  The input path, including any {wildcards}.
+   *
+   * @return string
+   *  The identifier.
+   */
+  public function getIdentifierFromPath($path) {
+    return $this->getIdentifier($this->deleteWildcards($path));
+  }
+
+  /**
+   * Generates an identifier (prefixed with the module name, if $this->module exists)
+   * from an arbitrary string.
+   *
+   * @param $string
+   *  The input string.
+   *
+   * @return string
+   *  The identifier.
+   */
+  public function getIdentifier($string) {
+    // Replace all non-alphanumeric character sequences with an underscore.
+    $id = preg_replace('/[^a-zA-Z0-9_]+/', '_', $string);
+
+    if (isset($this->module)) {
+      // If the name begins with MODULE_, replace that underscore with a period. Otherwise,
+      // prefix the key with the module's machine name. We want all routes to look like
+      // MODULE.route.
+      if (strIPos($id, $this->module->getMachineName() . '_') === 0) {
+        $id = preg_replace('/_/', '.', $id, 1);
+      }
+      else {
+        $id = $this->module->getMachineName() . '.' . $id;
+      }
+    }
+
+    return $id;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/templates/Block.html.twig b/web/modules/contrib/drupalmoduleupgrader/templates/Block.html.twig
new file mode 100644 (file)
index 0000000..5b8da6e
--- /dev/null
@@ -0,0 +1,64 @@
+/**
+ * @file
+ * Contains \Drupal\{{ module }}\Plugin\Block\{{ class }}.
+ */
+
+namespace Drupal\{{ module }}\Plugin\Block;
+
+use Drupal\Core\Block\BlockBase;
+{% if configurable %}
+use Drupal\Core\Form\FormStateInterface;
+{% endif %}
+
+/**
+ * Provides the {{ class }} block.
+ *
+ * @Block(
+ *   id = "{{ module }}_{{ block_id }}",
+ *   admin_label = @Translation("{{ block_label }}")
+ * )
+ */
+class {{ class }} extends BlockBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    /**
+     * @FIXME
+     * hook_block_view() has been removed in Drupal 8. You should move your
+     * block's view logic into this method and delete {{ module }}_block_view()
+     * as soon as possible!
+     */
+    return {{ module }}_block_view('{{ block_id }}');
+  }
+
+  {% if configurable %}
+  /**
+   * {@inheritdoc}
+   */
+  public function blockForm($form, FormStateInterface $form_state) {
+    /**
+     * @FIXME
+     * hook_block_configure() is gone in Drupal 8. You should move your block's
+     * configuration logic into this method and delete {{ module }}_block_configure()
+     * as soon as possible!
+     */
+    return {{ module }}_block_configure('{{ block_id }}');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function blockSubmit($form, FormStateInterface $form_state) {
+    /**
+     * @FIXME
+     * hook_block_save() is gone in Drupal 8. You should move your block's save
+     * logic into this method and delete {{ module }}_block_save() as soon as
+     * possible!
+     */
+    return {{ module }}_block_save('{{ block_id }}', $form_state->getValues());
+  }
+  {% endif %}
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/templates/Controller.html.twig b/web/modules/contrib/drupalmoduleupgrader/templates/Controller.html.twig
new file mode 100644 (file)
index 0000000..fc045dd
--- /dev/null
@@ -0,0 +1,15 @@
+/**
+ * @file
+ * Contains \Drupal\{{ module }}\Controller\DefaultController.
+ */
+
+namespace Drupal\{{ module }}\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+
+/**
+ * Default controller for the {{ module }} module.
+ */
+class DefaultController extends ControllerBase {
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/templates/EntityType.html.twig b/web/modules/contrib/drupalmoduleupgrader/templates/EntityType.html.twig
new file mode 100644 (file)
index 0000000..a3b9417
--- /dev/null
@@ -0,0 +1,34 @@
+/**
+ * @file
+ * Contains \Drupal\{{ module }}\Entity\{{ class }}.
+ */
+
+namespace Drupal\{{ module }}\Entity;
+
+use Drupal\Core\Entity\ContentEntityBase;
+
+/**
+ * @EntityType(
+ *  id = "{{ info.id }}",
+ *  label = @Translation("{{ info.label }}"),
+ *  controllers = {
+ *    "storage" = "{{ info.controllers.storage }}
+ *  },
+ *  base_table = "{{ info.base_table }}",
+ *  entity_keys = {
+ *    "id" = "{{ info.keys.id }}",
+{% if info.keys.label %}
+ *    "label" = "{{ info.keys.label }}"
+{% endif %}
+ *  }
+ * )
+ */
+class {{ class }} extends ContentEntityBase {
+
+  /**
+   * @FIXME
+   * Move all logic relating to the {{ info.id }} entity type into this
+   * class. For more information, see https://www.drupal.org/node/1827470.
+   */
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/templates/EventSubscriber.html.twig b/web/modules/contrib/drupalmoduleupgrader/templates/EventSubscriber.html.twig
new file mode 100644 (file)
index 0000000..7c7c421
--- /dev/null
@@ -0,0 +1,20 @@
+/**
+ * @file
+ * Contains \Drupal\{{ module }}\EventSubscriber\{{ class }}.
+ */
+
+namespace Drupal\{{ module }}\EventSubscriber;
+
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+class {{ class }} implements EventSubscriberInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    return [ {{ event }} => ['onEvent', {{ priority }}]];
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/templates/Form.html.twig b/web/modules/contrib/drupalmoduleupgrader/templates/Form.html.twig
new file mode 100644 (file)
index 0000000..86746eb
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\{{ module }}\Form\{{ class }}.
+ */
+
+namespace Drupal\{{ module }}\Form;
+
+use Drupal\Core\Form\{% if config %}Config{% endif %}FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+
+class {{ class }} extends {% if config %}Config{% endif %}FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return '{{ form_id }}';
+  }
+
+  {% if config %}
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $config = $this->config('{{ module }}.settings');
+
+    foreach (Element::children($form) as $variable) {
+      $config->set($variable, $form_state->getValue($form[$variable]['#parents']));
+    }
+    $config->save();
+
+    if (method_exists($this, '_submitForm')) {
+      $this->_submitForm($form, $form_state);
+    }
+
+    parent::submitForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditableConfigNames() {
+    return ['{{ module }}.settings'];
+  }
+  {% endif %}
+
+}
+?>
diff --git a/web/modules/contrib/drupalmoduleupgrader/templates/Formatter.html.twig b/web/modules/contrib/drupalmoduleupgrader/templates/Formatter.html.twig
new file mode 100644 (file)
index 0000000..e55cc58
--- /dev/null
@@ -0,0 +1,32 @@
+/**
+ * @file
+ * Contains \Drupal\{{ module }}\Plugin\Field\FieldFormatter\{{ class }}.
+ */
+
+namespace Drupal\{{ module }}\Plugin\Field\FieldFormatter;
+
+use Drupal\Core\Field\FormatterBase;
+
+/**
+ * @FieldFormatter(
+ *  id = "{{ info.id }}",
+ *  label = @Translation("{{ info.label }}"),
+{% if info.description %}
+ *  description = @Translation("{{ info.description }}"),
+{% endif %}
+ *  field_types = {{ '{' }}"{{ info.field_types|join('", "') }}"{{ '}' }}
+ * )
+ */
+class {{ class }} extends FormatterBase {
+
+  /**
+   * @FIXME
+   * Move all logic relating to the {{ info.id }} formatter into this
+   * class. For more information, see:
+   *
+   * https://www.drupal.org/node/1805846
+   * https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Field%21FormatterInterface.php/interface/FormatterInterface/8
+   * https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Field%21FormatterBase.php/class/FormatterBase/8
+   */
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/templates/Issue.html.twig b/web/modules/contrib/drupalmoduleupgrader/templates/Issue.html.twig
new file mode 100644 (file)
index 0000000..320d3fe
--- /dev/null
@@ -0,0 +1,27 @@
+{% autoescape false %}
+    <details class="{{ issue.Tag('error_level') }}">
+      <summary>{{ issue.Title() }}</summary>
+
+      {% if issue.Summary is not empty %}<div>{{ issue.Summary }}</div>{% endif %}
+
+      {% if issue.Documentation is not empty %}
+      <h5>Documentation</h5>
+      <ul>
+        {% for doc in issue.Documentation %}
+        <li><a target="_blank" href="{{ doc.url }}">{{ doc.title }}</a></li>
+        {% endfor %}
+      </ul>
+      {% endif %}
+
+      {% if issue.Violations is not empty %}
+      <h5>Files Affected</h5>
+      <ul>
+      {% for violation in issue.Violations %}
+        <li>{{ violation.file }}{% if violation.line_number %}, line {{ violation.line_number }}{% endif %}</li>
+      {% endfor %}
+      </ul>
+      {% endif %}
+      {% if issue.hasTag('fixable') %}<p class="fixable">This issue can be fixed automatically.</p>{% endif %}
+      {% if issue.Detectors is not empty %}<aside>Flagged by {{ issue.Detectors|join(', ') }}</aside>{% endif %}
+    </details>
+{% endautoescape %}
diff --git a/web/modules/contrib/drupalmoduleupgrader/templates/Logger.html.twig b/web/modules/contrib/drupalmoduleupgrader/templates/Logger.html.twig
new file mode 100644 (file)
index 0000000..34af48f
--- /dev/null
@@ -0,0 +1,25 @@
+/**
+ * @file
+ * Contains \Drupal\{{ module }}\Logger\DefaultLogger.
+ */
+
+namespace Drupal\{{ module }}\Logger;
+
+use Psr\Log\LoggerInterface;
+use Psr\Log\LoggerTrait;
+
+class DefaultLogger implements LoggerInterface {
+
+  use LoggerTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function log($level, $message, array $context = array()) {
+    /**
+     * @FIXME
+     * Port your hook_watchdog() logic here.
+     */
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/templates/OutboundPathProcessor.html.twig b/web/modules/contrib/drupalmoduleupgrader/templates/OutboundPathProcessor.html.twig
new file mode 100644 (file)
index 0000000..d8978f0
--- /dev/null
@@ -0,0 +1,14 @@
+/**
+ * @file
+ * Contains \Drupal\{{ module }}\OutboundPathProcessor.
+ */
+
+namespace Drupal\{{ module }};
+
+use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
+
+use Symfony\Component\HttpFoundation\Request;
+
+class OutboundPathProcessor implements OutboundPathProcessorInterface {
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/templates/Report.html.twig b/web/modules/contrib/drupalmoduleupgrader/templates/Report.html.twig
new file mode 100644 (file)
index 0000000..8d9e8d7
--- /dev/null
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <link href='http://fonts.googleapis.com/css?family=Open+Sans:600italic,400,700,600' rel='stylesheet' type='text/css' />
+    <style type="text/css">
+    <!--
+
+body {
+  width: 80%;
+  color: #343434;
+  margin: 1em auto;
+  font-family: 'Open Sans', Verdana, sans-serif;
+}
+
+details {
+  font-size: 18px;
+  line-height: 25px;
+  margin-bottom: 2em;
+  display: block;
+}
+
+details summary {
+  padding: 1em;
+  margin-bottom: 1em;
+  display: block;
+}
+
+details.error summary {
+  background-color: #ffd5d5;
+}
+
+details.warning summary {
+  background-color: #fff3bb;
+}
+
+a {
+  color: #095cb1;
+  font-weight: bold;
+  text-decoration: none;
+}
+
+h5 {
+  font-size: 1em;
+}
+
+aside {
+  font-style: italic;
+  line-height: 20px;
+  font-size: 15px;
+}
+
+.group {
+  border: 1px solid #ececec;
+  padding: 1em;
+  display: block;
+}
+
+.group > summary {
+  padding: 0;
+  color: #c8c8c8;
+  margin-bottom: 0;;
+  font-weight: bold;
+  letter-spacing: .1em;
+  text-transform: uppercase;
+  display: block;
+}
+
+.group[open] > summary {
+  margin-bottom: 1em;
+}
+
+.group > details:last-child {
+  margin-bottom: 0;
+}
+
+    -->
+    </style>
+  </head>
+  <body>
+  {% for tag, tagged_issues in issues %}
+    <details open="true" class="group">
+      <summary>{{ tag }}</summary>
+      {{ tagged_issues }}
+    </details>
+  {% endfor %}
+  </body>
+</html>
diff --git a/web/modules/contrib/drupalmoduleupgrader/templates/RouteSubscriber.html.twig b/web/modules/contrib/drupalmoduleupgrader/templates/RouteSubscriber.html.twig
new file mode 100644 (file)
index 0000000..32429bd
--- /dev/null
@@ -0,0 +1,29 @@
+/**
+ * @file
+ * Contains \Drupal\{{ module }}\Routing\RouteSubscriber.
+ */
+
+namespace Drupal\{{ module }}\Routing;
+
+use Drupal\Core\Routing\RouteSubscriberBase;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Listens to dynamic route events.
+ */
+class RouteSubscriber extends RouteSubscriberBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function alterRoutes(RouteCollection $collection) {
+    /**
+     * @FIXME
+     * Parts of your hook_menu_alter() logic should be moved in here. You should NOT
+     * use this method to define new routes -- read the documentation at
+     * https://www.drupal.org/node/2122201 to learn how to define dynamic routes --
+     * but to alter existing ones.
+     */
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/templates/Widget.html.twig b/web/modules/contrib/drupalmoduleupgrader/templates/Widget.html.twig
new file mode 100644 (file)
index 0000000..18c1fbe
--- /dev/null
@@ -0,0 +1,32 @@
+/**
+ * @file
+ * Contains \Drupal\{{ module }}\Plugin\Field\FieldWidget\{{ class }}.
+ */
+
+namespace Drupal\{{ module }}\Plugin\Field\FieldWidget;
+
+use Drupal\Core\Field\WidgetBase;
+
+/**
+ * @FieldWidget(
+ *  id = "{{ info.id }}",
+ *  label = @Translation("{{ info.label }}"),
+{% if info.description %}
+ *  description = @Translation("{{ info.description }}"),
+{% endif %}
+ *  field_types = {{ '{' }}"{{ info.field_types|join('", "') }}"{{ '}' }}
+ * )
+ */
+class {{ class }} extends WidgetBase {
+
+  /**
+   * @FIXME
+   * Move all logic relating to the {{ info.id }} widget into this class.
+   * For more information, see:
+   *
+   * https://www.drupal.org/node/1796000
+   * https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Field%21WidgetInterface.php/interface/WidgetInterface/8
+   * https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Field%21WidgetBase.php/class/WidgetBase/8
+   */
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/bootstrap.php b/web/modules/contrib/drupalmoduleupgrader/tests/bootstrap.php
new file mode 100644 (file)
index 0000000..4e074a1
--- /dev/null
@@ -0,0 +1,4 @@
+<?php
+
+require __DIR__ . '/../../../core/tests/bootstrap.php';
+require __DIR__ . '/../vendor/autoload.php';
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/ContainerMockTrait.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/ContainerMockTrait.php
new file mode 100644 (file)
index 0000000..0e115f3
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit;
+
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+
+/**
+ * A trait for tests that need a mock container; contains (deprecated) methods
+ * to mock basic translation and logging services as well.
+ */
+trait ContainerMockTrait {
+
+  /**
+   * @var \Symfony\Component\DependencyInjection\ContainerInterface
+   */
+  protected $container;
+
+  protected function mockContainer() {
+    if (empty($this->container)) {
+      // Using a ContainerBuilder lets us simply stick services into the
+      // container, which is a whole lot easier than mocking it!
+      $this->container = new ContainerBuilder();
+    }
+  }
+
+  protected function mockTranslator() {
+    $this->mockContainer();
+
+    // Mock the string_translation service; calling its translate()
+    // method will return the original, unprocessed string.
+    $translator = $this->getMock('\Drupal\Core\StringTranslation\TranslationInterface');
+    $translator->method('translate')->willReturnArgument(0);
+    $this->container->set('string_translation', $translator);
+  }
+
+  protected function mockLogger() {
+    $this->mockContainer();
+
+    // Mock the logger.factory service and a logger channel.
+    $factory = $this->getMock('\Drupal\Core\Logger\LoggerChannelFactoryInterface');
+    $channel = $this->getMock('\Drupal\Core\Logger\LoggerChannelInterface');
+    $factory->method('get')->willReturn($channel);
+    $this->container->set('logger.factory', $factory);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/IssueTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/IssueTest.php
new file mode 100644 (file)
index 0000000..e4813dc
--- /dev/null
@@ -0,0 +1,94 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit;
+
+use Drupal\drupalmoduleupgrader\Issue;
+use Pharborist\Filter;
+
+/**
+ * @group DMU
+ */
+class IssueTest extends TestBase {
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\IssueInterface
+   */
+  private $issue;
+
+  public function setUp() {
+    parent::setUp();
+    $this->issue = new Issue($this->target, 'Foobaz');
+  }
+
+  public function testTitle() {
+    $this->issue->setTitle('Foobar');
+    $this->assertEquals('Foobar', $this->issue->getTitle());
+  }
+
+  public function testSummary() {
+    $this->issue->setSummary('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.');
+    $this->assertEquals("<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</p>\n", $this->issue->getSummary());
+  }
+
+  public function testDocumentation() {
+    $this->issue->addDocumentation('http://www.google.com', 'Just Google it, baby!');
+    $documentation = $this->issue->getDocumentation();
+    $this->assertInternalType('array', $documentation);
+    $this->assertCount(1, $documentation);
+    $this->assertArrayHasKey('url', $documentation[0]);
+    $this->assertArrayHasKey('title', $documentation[0]);
+    $this->assertEquals('http://www.google.com', $documentation[0]['url']);
+    $this->assertEquals('Just Google it, baby!', $documentation[0]['title']);
+  }
+
+  public function testViolationsAndDetectors() {
+    $analyzer = $this->getMockBuilder('\Drupal\drupalmoduleupgrader\AnalyzerBase')->disableOriginalConstructor()->getMock();
+    $analyzer->method('getPluginId')->willReturn('blarg');
+    $this->issue->addAffectedFile($this->dir->getChild('foo.info')->url(), $analyzer);
+
+    $code = <<<'END'
+<?php
+
+/**
+ * Implements hook_permission().
+ */
+function foo_permission() {
+  return array();
+}
+END;
+    $this->dir->getChild('foo.module')->setContent($code);
+
+    $node = $this->target
+      ->open($this->dir->getChild('foo.module')->url())
+      ->children(Filter::isFunction('foo_permission'))
+      ->get(0);
+    $this->issue->addViolation($node, $analyzer);
+
+    $violations = $this->issue->getViolations();
+    $this->assertInternalType('array', $violations);
+    $this->assertCount(2, $violations);
+    $this->assertArrayHasKey('file', $violations[0]);
+    $this->assertArrayNotHasKey('line_number', $violations[0]);
+    $this->assertEquals($this->dir->getChild('foo.info')->url(), $violations[0]['file']);
+    $this->assertArrayHasKey('file', $violations[1]);
+    $this->assertArrayHasKey('line_number', $violations[1]);
+    $this->assertEquals($this->dir->getChild('foo.module')->url(), $violations[1]['file']);
+
+    $detectors = $this->issue->getDetectors();
+    $this->assertInternalType('array', $detectors);
+    $this->assertCount(1, $detectors);
+    $this->assertEquals($analyzer->getPluginId(), $detectors[0]);
+  }
+
+  public function testFixes() {
+    $this->issue->addFix('foo');
+    $this->issue->addFix('baz', ['bar' => 'wambooli']);
+
+    $fixes = $this->issue->getFixes();
+    $this->assertInternalType('array', $fixes);
+    $this->assertCount(2, $fixes);
+    $this->assertEquals(['_plugin_id' => 'foo'], $fixes[0]);
+    $this->assertEquals(['_plugin_id' => 'baz', 'bar' => 'wambooli'], $fixes[1]);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/ModuleMockerTrait.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/ModuleMockerTrait.php
new file mode 100644 (file)
index 0000000..1115939
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit;
+
+use org\bovigo\vfs\vfsStream;
+
+/**
+ * A trait for tests that need a mock module to work on.
+ */
+trait ModuleMockerTrait {
+
+  protected function mockModule($id) {
+    // Create a virtual (in-memory) directory for the module, and touch
+    // a few empty files. Tests should fill in the code of the module
+    // according to their own needs.
+    $dir = vfsStream::setup($id);
+    vfsStream::newFile($id . '.module')->at($dir);
+    vfsStream::newFile($id . '.info')->at($dir);
+    vfsStream::newFile($id . '.install')->at($dir);
+    vfsStream::newFile($id . '.test')->at($dir);
+    vfsStream::newDirectory('src')->at($dir);
+
+    $config_dir = vfsStream::newDirectory('config')->at($dir);
+    vfsStream::newDirectory('install')->at($config_dir);
+    vfsStream::newDirectory('optional')->at($config_dir);
+    vfsStream::newDirectory('schema')->at($config_dir);
+
+    return $dir;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/AnalyzerTestBase.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/AnalyzerTestBase.php
new file mode 100644 (file)
index 0000000..b855360
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Analyzer;
+
+use Drupal\Tests\drupalmoduleupgrader\Unit\TestBase;
+
+abstract class AnalyzerTestBase extends TestBase {
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\AnalyzerInterface
+   */
+  protected $analyzer;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getPlugin(array $configuration = [], $plugin_definition = []) {
+    $plugin_definition += [
+      'message' => $this->getRandomGenerator()->sentences(4),
+      'summary' => NULL,
+      'documentation' => [],
+      'tags' => [],
+    ];
+    return parent::getPlugin($configuration, $plugin_definition);
+  }
+
+  /**
+   * Tests an issue generated by an analyzer to ensure that it has all the
+   * default values pulled from the plugin definition.
+   *
+   * @param $issue
+   *  The issue to test. Will be checked for IssueInterface conformance.
+   */
+  protected function assertIssueDefaults($issue) {
+    $this->assertInstanceOf('\Drupal\drupalmoduleupgrader\IssueInterface', $issue);
+
+    $plugin_definition = $this->analyzer->getPluginDefinition();
+    $this->assertEquals($plugin_definition['message'], $issue->getTitle());
+    $this->assertEquals($plugin_definition['summary'], $issue->getSummary());
+    $this->assertSame($issue->getDocumentation(), $plugin_definition['documentation']);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/DBTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/DBTest.php
new file mode 100644 (file)
index 0000000..0f96372
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Analyzer;
+
+use Drupal\drupalmoduleupgrader\Plugin\DMU\Indexer\FunctionCalls;
+
+/**
+ * @group DMU.Analyzer
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Analyzer\DB
+ */
+class DBTest extends AnalyzerTestBase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $code = <<<'END'
+<?php
+
+/**
+ * Implements hook_uninstall().
+ */
+function foo_uninstall() {
+  db_delete('variable')->condition('name', 'foo_baz')->execute();
+}
+END;
+    $this->dir->getChild('foo.install')->setContent($code);
+
+    $indexer = new FunctionCalls([], 'function', [], $this->db, $this->target);
+    $indexer->build();
+    $this->container
+      ->get('plugin.manager.drupalmoduleupgrader.indexer')
+      ->method('createInstance')
+      ->with('function_call')
+      ->willReturn($indexer);
+
+    $this->analyzer = $this->getPlugin([], ['function' => 'db_delete']);
+  }
+
+  public function test() {
+    $issues = $this->analyzer->analyze($this->target);
+    $this->assertInternalType('array', $issues);
+    $this->assertNotEmpty($issues);
+    $this->assertIssueDefaults($issues[0]);
+    $this->assertCount(1, $issues[0]->getViolations());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/FlagHookTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/FlagHookTest.php
new file mode 100644 (file)
index 0000000..338d66f
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Analyzer;
+
+use Drupal\drupalmoduleupgrader\Plugin\DMU\Indexer\Functions;
+
+/**
+ * @group DMU.Analyzer
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Analyzer\FlagHook
+ */
+class FlagHookTest extends AnalyzerTestBase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $code = <<<'END'
+<?php
+
+/**
+ * Implements hook_block_info().
+ */
+function foo_block_info() {
+  return array();
+}
+END;
+    $this->dir->getChild('foo.module')->setContent($code);
+
+    $indexer = new Functions([], 'function', [], $this->db, $this->target);
+    $indexer->build();
+    $this->container
+      ->get('plugin.manager.drupalmoduleupgrader.indexer')
+      ->method('createInstance')
+      ->with('function')
+      ->willReturn($indexer);
+
+    $this->analyzer = $this->getPlugin([], ['hook' => 'block_info']);
+  }
+
+  public function test() {
+    $issues = $this->analyzer->analyze($this->target);
+    $this->assertInternalType('array', $issues);
+    $this->assertNotEmpty($issues);
+    $this->assertIssueDefaults($issues[0]);
+    $this->assertCount(1, $issues[0]->getViolations());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/FunctionCallTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/FunctionCallTest.php
new file mode 100644 (file)
index 0000000..e245b9c
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Analyzer;
+
+use Drupal\drupalmoduleupgrader\Plugin\DMU\Indexer\FunctionCalls;
+
+/**
+ * @group DMU.Analyzer
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Analyzer\FunctionCall
+ */
+class FunctionCallTest extends AnalyzerTestBase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $code = <<<'END'
+<?php
+
+function foo_blorf() {
+  $data = array();
+  drupal_write_record($data, 'id');
+}
+END;
+    $this->dir->getChild('foo.module')->setContent($code);
+
+    $indexer = new FunctionCalls([], 'function', [], $this->db, $this->target);
+    $indexer->build();
+    $this->container
+      ->get('plugin.manager.drupalmoduleupgrader.indexer')
+      ->method('createInstance')
+      ->with('function_call')
+      ->willReturn($indexer);
+
+    $this->analyzer = $this->getPlugin([], ['function' => 'drupal_write_record']);
+  }
+
+  public function test() {
+    $issues = $this->analyzer->analyze($this->target);
+    $this->assertInternalType('array', $issues);
+    $this->assertNotEmpty($issues);
+    $this->assertIssueDefaults($issues[0]);
+    $this->assertCount(1, $issues[0]->getViolations());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/HookFormAlterTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/HookFormAlterTest.php
new file mode 100644 (file)
index 0000000..5dd474d
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Analyzer;
+
+use Drupal\drupalmoduleupgrader\Plugin\DMU\Indexer\Functions;
+
+/**
+ * @group DMU.Analyzer
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Analyzer\HookFormAlter
+ */
+class HookFormAlterTest extends AnalyzerTestBase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $code = <<<'END'
+<?php
+
+/**
+ * Implements hook_form_alter().
+ */
+function foo_form_alter(array &$form, array &$form_state, $form_id) {
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function foo_form_blarg_alter(array &$form, array &$form_state) {
+}
+END;
+    $this->dir->getChild('foo.module')->setContent($code);
+
+    $function_indexer = new Functions([], 'function', [], $this->db, $this->target);
+    $function_indexer->build();
+
+    $this->container
+      ->get('plugin.manager.drupalmoduleupgrader.indexer')
+      ->method('createInstance')
+      ->with('function')
+      ->willReturn($function_indexer);
+
+    $this->analyzer = $this->getPlugin();
+  }
+
+  public function testHookFormAlter() {
+    $issues = $this->analyzer->analyze($this->target);
+    $this->assertInternalType('array', $issues);
+    $this->assertNotEmpty($issues);
+    $this->assertIssueDefaults($issues[0]);
+    $this->assertCount(2, $issues[0]->getViolations());
+  }
+
+  public function testDerivedFormAlter() {
+    $issues = $this->analyzer->analyze($this->target);
+    $this->assertInternalType('array', $issues);
+    $this->assertNotEmpty($issues);
+    $this->assertIssueDefaults($issues[0]);
+    $this->assertCount(2, $issues[0]->getViolations());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/HookPermissionTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/HookPermissionTest.php
new file mode 100644 (file)
index 0000000..3ce57df
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Analyzer;
+
+use Drupal\drupalmoduleupgrader\Plugin\DMU\Indexer\Functions;
+
+/**
+ * @group DMU.Analyzer
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Analyzer\HookPermission
+ *
+ * @todo Add a test for dynamic permissions. Drupal 8 still uses
+ * hook_permission() for this, so dynamic permissions should not result in
+ * an issue being flagged.
+ */
+class HookPermissionTest extends AnalyzerTestBase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $code = <<<'END'
+<?php
+
+/**
+ * Implements hook_permission().
+ */
+function foo_permission() {
+  return array();
+}
+END;
+    $this->dir->getChild('foo.module')->setContent($code);
+
+    $indexer = new Functions([], 'function', [], $this->db, $this->target);
+    $indexer->build();
+    $this->container
+      ->get('plugin.manager.drupalmoduleupgrader.indexer')
+      ->method('createInstance')
+      ->with('function')
+      ->willReturn($indexer);
+
+    $this->analyzer = $this->getPlugin();
+  }
+
+  public function test() {
+    $issues = $this->analyzer->analyze($this->target);
+    $this->assertInternalType('array', $issues);
+    $this->assertNotEmpty($issues);
+    $this->assertIssueDefaults($issues[0]);
+    $this->assertCount(1, $issues[0]->getViolations());
+    $fixes = $issues[0]->getFixes();
+    $this->assertNotEmpty($fixes);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/HookUninstallTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/HookUninstallTest.php
new file mode 100644 (file)
index 0000000..59f04e8
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Analyzer;
+
+use Drupal\drupalmoduleupgrader\Plugin\DMU\Indexer\Functions;
+
+/**
+ * @group DMU.Analyzer
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Analyzer\HookUninstall
+ */
+class HookUninstalltest extends AnalyzerTestBase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $code = <<<'END'
+<?php
+
+/**
+ * Implements hook_uninstall().
+ */
+function foo_uninstall() {
+  variable_del('foo_baz');
+}
+END;
+    $this->dir->getChild('foo.install')->setContent($code);
+
+    $indexer = new Functions([], 'function', [], $this->db, $this->target);
+    $indexer->build();
+    $this->container
+      ->get('plugin.manager.drupalmoduleupgrader.indexer')
+      ->method('createInstance')
+      ->with('function')
+      ->willReturn($indexer);
+
+    $this->analyzer = $this->getPlugin();
+  }
+
+  public function test() {
+    $issues = $this->analyzer->analyze($this->target);
+    $this->assertInternalType('array', $issues);
+    $this->assertNotEmpty($issues);
+    $this->assertIssueDefaults($issues[0]);
+    $this->assertCount(1, $issues[0]->getViolations());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/InfoFileTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/InfoFileTest.php
new file mode 100644 (file)
index 0000000..edd054c
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Analyzer;
+
+/**
+ * @group DMU.Analyzer
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Analyzer\InfoFile
+ */
+class InfoFileTest extends AnalyzerTestBase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $info = <<<'END'
+name = "Foobar"
+core = "7.x"
+files[] = foo.test
+END;
+    $this->dir->getChild('foo.info')->setContent($info);
+
+    $this->analyzer = $this->getPlugin([], [
+      'documentation' => [
+        [ 'url' => 'http://www.google.com', 'title' => 'Google it, baby.' ],
+      ],
+    ]);
+  }
+
+  public function test() {
+    $issues = $this->analyzer->analyze($this->target);
+    $this->assertInternalType('array', $issues);
+    $this->assertArrayHasKey('core', $issues);
+    $this->assertArrayHasKey('type', $issues);
+    $this->assertArrayNotHasKey('dependencies', $issues);
+    $this->assertArrayHasKey('files', $issues);
+    $this->assertArrayNotHasKey('configure', $issues);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/PSR4Test.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/PSR4Test.php
new file mode 100644 (file)
index 0000000..ccd0ae3
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Analyzer;
+
+use Drupal\drupalmoduleupgrader\Plugin\DMU\Indexer\Classes;
+
+/**
+ * @group DMU.Analyzer
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Analyzer\PSR4
+ */
+class PSR4Test extends AnalyzerTestBase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $code = <<<'END'
+<?php
+
+class FooBaz {}
+END;
+    $this->dir->getChild('foo.module')->setContent($code);
+
+    $indexer = new Classes([], 'class', [], $this->db, $this->target);
+    $indexer->build();
+    $this->container
+      ->get('plugin.manager.drupalmoduleupgrader.indexer')
+      ->method('createInstance')
+      ->with('class')
+      ->willReturn($indexer);
+
+    $this->analyzer = $this->getPlugin();
+  }
+
+  public function test() {
+    $issues = $this->analyzer->analyze($this->target);
+    $this->assertInternalType('array', $issues);
+    $this->assertNotEmpty($issues);
+    $this->assertIssueDefaults($issues[0]);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/TestsTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/TestsTest.php
new file mode 100644 (file)
index 0000000..7880e3b
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Analyzer;
+
+use Drupal\drupalmoduleupgrader\Plugin\DMU\Indexer\Classes;
+
+/**
+ * @group DMU.Analyzer
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Analyzer\Tests
+ */
+class TestsTest extends AnalyzerTestBase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $code = <<<'END'
+<?php
+
+class FooTestCase extends DrupalWebTestCase {}
+END;
+    $this->dir->getChild('foo.test')->setContent($code);
+
+    $indexer = new Classes([], 'class', [], $this->db, $this->target);
+    $indexer->build();
+
+    $this->container
+      ->get('plugin.manager.drupalmoduleupgrader.indexer')
+      ->method('createInstance')
+      ->with('class')
+      ->willReturn($indexer);
+
+    $this->analyzer = $this->getPlugin();
+  }
+
+  public function test() {
+    $issues = $this->analyzer->analyze($this->target);
+    $this->assertInternalType('array', $issues);
+    $this->assertNotEmpty($issues);
+    $this->assertIssueDefaults($issues[0]);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CToolsGetPluginsTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CToolsGetPluginsTest.php
new file mode 100644 (file)
index 0000000..0c5858b
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\CToolsGetPlugins;
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\CToolsGetPlugins
+ */
+class CToolsGetPluginsTest extends FunctionCallModifierTestBase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->plugin = CToolsGetPlugins::create($this->container, [], 'ctools_get_plugins', []);
+  }
+
+  public function testCanRewriteValidFunctionCall() {
+    $function_call = Parser::parseExpression('ctools_get_plugins("foo", "foobaz")');
+    $this->assertTrue($this->plugin->canRewrite($function_call, $this->target));
+  }
+
+  public function testCanRewriteInvalidFunctionCall() {
+    $function_call = Parser::parseExpression('ctools_get_plugins($module_name, "foobaz")');
+    $this->assertFalse($this->plugin->canRewrite($function_call, $this->target));
+  }
+
+  public function testRewriteValidFunctionCall() {
+    $function_call = Parser::parseExpression('ctools_get_plugins("foo", "foobaz")');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::service(\'plugin.manager.foo.foobaz\')->getDefinitions()', $rewritten->getText());
+  }
+
+  public function testRewriteInvalidFunctionCall() {
+    $function_call = Parser::parseExpression('ctools_get_plugins($module_name, "foobaz")');
+    $this->assertNull($this->plugin->rewrite($function_call, $this->target));
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CToolsObjectCacheGetTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CToolsObjectCacheGetTest.php
new file mode 100644 (file)
index 0000000..7d3075f
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\CToolsObjectCacheGet
+ */
+class CToolsObjectCacheGetTest extends FunctionCallModifierTestBase {
+
+  public function testRewrite() {
+    $function_call = Parser::parseExpression('ctools_object_cache_get("foo", "baz")');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::service(\'user.tempstore\')->get("baz")', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CToolsObjectCacheSetTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CToolsObjectCacheSetTest.php
new file mode 100644 (file)
index 0000000..5dff008
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\CToolsObjectCacheSet
+ */
+class CToolsObjectCacheSetTest extends FunctionCallModifierTestBase {
+
+  public function testRewriteNoSessionID() {
+    $function_call = Parser::parseExpression('ctools_object_cache_set("foo", "baz", array())');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::service(\'user.tempstore\')->set("baz", array())', $rewritten->getText());
+  }
+
+  public function testRewriteSessionID() {
+    $function_call = Parser::parseExpression('ctools_object_cache_set("foo", "baz", array(), "SESSION_ID")');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::service(\'user.tempstore\')->set("baz", array())', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CacheGetTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CacheGetTest.php
new file mode 100644 (file)
index 0000000..508392a
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\CacheGet
+ */
+class CacheGetTest extends FunctionCallModifierTestBase {
+
+  public function testRewriteDefaultBin() {
+    $function_call = Parser::parseExpression('cache_get("foo")');
+    $rewritten = $this->getPlugin()->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::cache()->get("foo")', $rewritten->getText());
+  }
+
+  public function testRewriteSpecificBin() {
+    $function_call = Parser::parseExpression('cache_get("baz", "foo")');
+    $rewritten = $this->getPlugin()->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::cache("foo")->get("baz")', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CacheSetTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CacheSetTest.php
new file mode 100644 (file)
index 0000000..19f3d81
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\CacheSet
+ */
+class CacheSetTest extends FunctionCallModifierTestBase {
+
+  public function testRewriteDefaultBin() {
+    $function_call = Parser::parseExpression('cache_set("foo", array())');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::cache()->set("foo", array())', $rewritten->getText());
+  }
+
+  public function testRewriteSpecificBinNoExpiration() {
+    $function_call = Parser::parseExpression('cache_set("foo", array(), "baz")');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::cache("baz")->set("foo", array())', $rewritten->getText());
+  }
+
+  public function testRewriteSpecificBinWithExpiration() {
+    $function_call = Parser::parseExpression('cache_set("foo", array(), "bar", 67890)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::cache("bar")->set("foo", array(), 67890)', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CommentLoadTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CommentLoadTest.php
new file mode 100644 (file)
index 0000000..9ff3503
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Filter;
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\CommentLoad
+ */
+class CommentLoadTest extends FunctionCallModifierTestBase {
+
+  public function testRewriteWithoutCacheReset() {
+    $function_call = Parser::parseExpression('comment_load(30)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::entityManager()->getStorage(\'comment\')->load(30)', $rewritten->getText());
+  }
+
+  public function testRewriteWithCacheReset() {
+    $original = <<<'END'
+comment_load(30, TRUE);
+END;
+    $expected = <<<'END'
+// @FIXME
+// To reset the comment cache, use EntityStorageInterface::resetCache().
+\Drupal::entityManager()->getStorage('comment')->load(30);
+END;
+    $snippet = Parser::parseSnippet($original);
+    $function_call = $snippet->children(Filter::isFunctionCall('comment_load'))->get(0);
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $function_call->replaceWith($rewritten);
+    $this->assertEquals($expected, $snippet->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DBTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DBTest.php
new file mode 100644 (file)
index 0000000..398df54
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\DB
+ *
+ * Currently, the DB plugin behaves identically for every function it handles,
+ * so I'm only bothering to test db_select().
+ */
+class DBTest extends FunctionCallModifierTestBase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->plugin = $this->getPlugin([], [ 'function' => 'db_select' ]);
+  }
+
+  public function testRewriteDBSelectAllowedTable() {
+    $function_call = Parser::parseExpression('db_select("session")');
+    $this->assertSame($function_call, $this->plugin->rewrite($function_call, $this->target));
+  }
+
+  public function testRewriteDBSelectForbiddenTable() {
+    $function_call = Parser::parseExpression('db_select("variable")');
+    $this->assertNull($this->plugin->rewrite($function_call, $this->target));
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DisableTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DisableTest.php
new file mode 100644 (file)
index 0000000..01f9c79
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\Disable
+ *
+ * Currently, the Disable plugin behaves identically for every function it
+ * handles (unconditionally returns NULL), so I'm only testing one function.
+ */
+class DisableTest extends FunctionCallModifierTestBase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->plugin = $this->getPlugin([], [ 'function' => 'field_create_field' ]);
+  }
+
+  public function testRewrite() {
+    $function_call = Parser::parseExpression('field_create_field($field)');
+    $this->assertNull($this->plugin->rewrite($function_call, $this->target));
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DrupalGetTitleTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DrupalGetTitleTest.php
new file mode 100644 (file)
index 0000000..85530e0
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\DrupalGetTitle
+ */
+class DrupalGetTitleTest extends FunctionCallModifierTestBase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->plugin = $this->getPlugin();
+  }
+
+  public function testRewrite() {
+    $function_call = Parser::parseExpression('drupal_get_title()');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::service(\'title_resolver\')->getTitle(\Drupal::request(), \Drupal::routeMatch()->getRouteObject())', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DrupalIsCLITest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DrupalIsCLITest.php
new file mode 100644 (file)
index 0000000..7ec9224
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\DrupalIsCLI
+ */
+class DrupalIsCLITest extends FunctionCallModifierTestBase {
+
+  public function testRewrite() {
+    $function_call = Parser::parseExpression('drupal_is_cli()');
+    $rewritten = $this->getPlugin()->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\ExpressionNode', $rewritten);
+    $this->assertEquals('(PHP_SAPI === "cli")', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DrupalMapAssocTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DrupalMapAssocTest.php
new file mode 100644 (file)
index 0000000..7c93a23
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\DrupalMapAssoc
+ */
+class DrupalMapAssocTest extends FunctionCallModifierTestBase {
+
+  public function testRewrite() {
+    $function_call = Parser::parseExpression('drupal_map_assoc(array(0, 1, 2, 3))');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Functions\FunctionCallNode', $rewritten);
+    $this->assertSame($rewritten, $function_call);
+    $this->assertEquals('array_combine(array(0, 1, 2, 3), array(0, 1, 2, 3))', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DrupalWriteRecordTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DrupalWriteRecordTest.php
new file mode 100644 (file)
index 0000000..5dd812f
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\DrupalWriteRecord
+ */
+class DrupalWriteRecordTest extends FunctionCallModifierTestBase {
+
+  public function testRewriteUpdateArrayKey() {
+    $function_call = Parser::parseExpression('drupal_write_record("foobar", $record, array("id"));');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::database()->merge("foobar")->fields($record)->key(array("id"))->execute()', $rewritten->getText());
+  }
+
+  public function testRewriteUpdateStringKey() {
+    $function_call = Parser::parseExpression('drupal_write_record("foobar", $record, "baz")');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::database()->merge("foobar")->fields($record)->key(["baz"])->execute()', $rewritten->getText());
+  }
+
+  public function testRewriteInsert() {
+    $function_call = Parser::parseExpression('drupal_write_record("foobar", $record)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::database()->insert("foobar")->fields($record)->execute()', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/EntityCreateTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/EntityCreateTest.php
new file mode 100644 (file)
index 0000000..d8bb3a5
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\EntityCreate
+ */
+class EntityCreateTest extends FunctionCallModifierTestBase {
+
+  public function testRewrite() {
+    $function_call = Parser::parseExpression('entity_create("node", $node_values)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::entityManager()->getStorage("node")->create($node_values)', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/EntityGetInfoTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/EntityGetInfoTest.php
new file mode 100644 (file)
index 0000000..7f67253
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\EntityGetInfo
+ */
+class EntityGetInfoTest extends FunctionCallModifierTestBase {
+
+  public function testRewriteNoArguments() {
+    $function_call = Parser::parseExpression('entity_get_info()');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::entityManager()->getDefinitions()', $rewritten->getText());
+  }
+
+  public function testRewriteEntityType() {
+    $function_call = Parser::parseExpression('entity_get_info("node")');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::entityManager()->getDefinition("node")', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldInfoFieldTypesTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldInfoFieldTypesTest.php
new file mode 100644 (file)
index 0000000..f3bee13
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\FieldInfoFieldTypes
+ */
+class FieldInfoFieldTypesTest extends FunctionCallModifierTestBase {
+
+  public function testRewriteNoArguments() {
+    $function_call = Parser::parseExpression('field_info_field_types()');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::service(\'plugin.manager.field.field_type\')->getDefinitions()', $rewritten->getText());
+  }
+
+  public function testRewriteFieldType() {
+    $function_call = Parser::parseExpression('field_info_field_types("text")');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::service(\'plugin.manager.field.field_type\')->getDefinition("text")', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldInfoFormatterTypesTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldInfoFormatterTypesTest.php
new file mode 100644 (file)
index 0000000..45c4775
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\FieldInfoFormatterTypes
+ */
+class FieldInfoFormatterTypesTest extends FunctionCallModifierTestBase {
+
+  public function testRewriteNoArguments() {
+    $function_call = Parser::parseExpression('field_info_formatter_types()');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::service(\'plugin.manager.field.formatter\')->getDefinitions()', $rewritten->getText());
+  }
+
+  public function testRewriteFieldType() {
+    $function_call = Parser::parseExpression('field_info_formatter_types("text_default")');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::service(\'plugin.manager.field.formatter\')->getDefinition("text_default")', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldInfoWidgetTypesTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldInfoWidgetTypesTest.php
new file mode 100644 (file)
index 0000000..8330797
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\FieldInfoWidgetTypes
+ */
+class FieldInfoWidgetTypesTest extends FunctionCallModifierTestBase {
+
+  public function testRewriteNoArguments() {
+    $function_call = Parser::parseExpression('field_info_widget_types()');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::service(\'plugin.manager.field.widget\')->getDefinitions()', $rewritten->getText());
+  }
+
+  public function testRewriteFieldType() {
+    $function_call = Parser::parseExpression('field_info_widget_types("text_textfield")');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::service(\'plugin.manager.field.widget\')->getDefinition("text_textfield")', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldUpdateFieldTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldUpdateFieldTest.php
new file mode 100644 (file)
index 0000000..1722eeb
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\FieldUpdateField
+ */
+class FieldUpdateFieldTest extends FunctionCallModifierTestBase {
+
+  public function testRewrite() {
+    $function_call = Parser::parseExpression('field_update_field($field)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('$field->save()', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldUpdateInstanceTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldUpdateInstanceTest.php
new file mode 100644 (file)
index 0000000..aa8ccf3
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\FieldUpdateInstance
+ */
+class FieldUpdateInstanceTest extends FunctionCallModifierTestBase {
+
+  public function testRewrite() {
+    $function_call = Parser::parseExpression('field_update_instance($instance)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('$instance->save()', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldViewFieldTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldViewFieldTest.php
new file mode 100644 (file)
index 0000000..2ec55c9
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\FieldViewField
+ */
+class FieldViewFieldTest extends FunctionCallModifierTestBase {
+
+  public function testRewriteViewMode() {
+    $function_call = Parser::parseExpression('field_view_field("node", $node, "field_foo", $view_mode, $langcode)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('$node->field_foo->view($view_mode)', $rewritten->getText());
+  }
+
+  public function testRewriteDisplayOptions() {
+    $function_call = Parser::parseExpression('field_view_field("node", $node, "field_foo", array("type" => "some_formatter"), $langcode)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('$node->field_foo->view(array("type" => "some_formatter"))', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldViewValueTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldViewValueTest.php
new file mode 100644 (file)
index 0000000..4f6ac6e
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\FieldViewValue
+ */
+class FieldViewValueTest extends FunctionCallModifierTestBase {
+
+  public function testRewriteViewMode() {
+    $function_call = Parser::parseExpression('field_view_value("node", $node, "field_foo", $item, $view_mode, $langcode)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('$item->view($view_mode)', $rewritten->getText());
+  }
+
+  public function testRewriteDisplayOptions() {
+    $function_call = Parser::parseExpression('field_view_value("node", $node, "field_foo", $item, array("type" => "some_formatter"), $langcode)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('$item->view(array("type" => "some_formatter"))', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FormExecuteHandlersTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FormExecuteHandlersTest.php
new file mode 100644 (file)
index 0000000..9a0bded
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\FormExecuteHandlers
+ */
+class FormExecuteHandlersTest extends FunctionCallModifierTestBase {
+
+  public function testRewriteValidate() {
+    $function_call = Parser::parseExpression('form_execute_handlers("validate", $form, $form_state)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::formBuilder()->executeValidateHandlers($form, $form_state)', $rewritten->getText());
+  }
+
+  public function testRewriteSubmit() {
+    $function_call = Parser::parseExpression('form_execute_handlers("submit", $form, $form_state)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::formBuilder()->executeSubmitHandlers($form, $form_state)', $rewritten->getText());
+  }
+
+  public function testRewriteInvalidHandlerType() {
+    $function_call = Parser::parseExpression('form_execute_handlers("blorfable", $form, $form_state)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertNull($rewritten);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FormLoadIncludeTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FormLoadIncludeTest.php
new file mode 100644 (file)
index 0000000..406c6b3
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\FormLoadInclude
+ */
+class FormLoadIncludeTest extends FunctionCallModifierTestBase {
+
+  public function testRewriteWithoutName() {
+    $function_call = Parser::parseExpression('form_load_include($form_state, "inc", "mod_foo")');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('$form_state->loadInclude("mod_foo", "inc")', $rewritten->getText());
+  }
+
+  public function testRewriteWithName() {
+    $function_call = Parser::parseExpression('form_load_include($form_state, "inc", "mod_foo", "bazzle")');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('$form_state->loadInclude("mod_foo", "inc", "bazzle")', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FormSetValueTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FormSetValueTest.php
new file mode 100644 (file)
index 0000000..e8d91d5
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\FormSetValue
+ */
+class FormSetValueTest extends FunctionCallModifierTestBase {
+
+  public function testRewrite() {
+    $function_call = Parser::parseExpression('form_set_value($element, $value, $form_state)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('$form_state->setValueForElement($element, $value)', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FormStateValuesCleanTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FormStateValuesCleanTest.php
new file mode 100644 (file)
index 0000000..5337e38
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\FormStateValuesClean
+ */
+class FormStateValuesCleanTest extends FunctionCallModifierTestBase {
+
+  public function testRewrite() {
+    $function_call = Parser::parseExpression('form_state_values_clean($form_state)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('$form_state->cleanValues()', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FunctionCallModifierTestBase.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FunctionCallModifierTestBase.php
new file mode 100644 (file)
index 0000000..1947b83
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions\FunctionCallModifierTestBase.
+ */
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Drupal\Tests\drupalmoduleupgrader\Unit\TestBase;
+
+/**
+ * Base class for testing function call modifiers.
+ *
+ * @package Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions
+ */
+abstract class FunctionCallModifierTestBase extends TestBase {
+
+  /**
+   * The plugin object under test.
+   *
+   * @var \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\FunctionCallModifier
+   */
+  protected $plugin;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+    $this->plugin = $this->getPlugin();
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/GetTTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/GetTTest.php
new file mode 100644 (file)
index 0000000..6ab4d67
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\GetT
+ */
+class GetTTest extends FunctionCallModifierTestBase {
+
+  public function testRewrite() {
+    $function_call = Parser::parseExpression('get_t()');
+    /** @var \Pharborist\Types\StringNode $rewritten */
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Types\StringNode', $rewritten);
+    $this->assertEquals('t', $rewritten->toValue());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/ModuleInvokeAllTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/ModuleInvokeAllTest.php
new file mode 100644 (file)
index 0000000..a31e5b9
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\ModuleInvokeAll
+ */
+class ModuleInvokeAllTest extends FunctionCallModifierTestBase {
+
+  public function testRewriteWithoutArguments() {
+    $function_call = Parser::parseExpression('module_invoke_all("cer_fields")');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::moduleHandler()->invokeAll("cer_fields")', $rewritten->getText());
+  }
+
+  public function testRewriteWithArguments() {
+    $function_call = Parser::parseExpression('module_invoke_all("menu_alter", $menu)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::moduleHandler()->invokeAll("menu_alter", [$menu])', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/ModuleInvokeTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/ModuleInvokeTest.php
new file mode 100644 (file)
index 0000000..c592619
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\ModuleInvoke
+ */
+class ModuleInvokeTest extends FunctionCallModifierTestBase {
+
+  public function testRewriteNoArguments() {
+    $function_call = Parser::parseExpression('module_invoke_all("foo", "menu")');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::moduleHandler()->invoke("foo", "menu")', $rewritten->getText());
+  }
+
+  public function testRewriteArguments() {
+    $function_call = Parser::parseExpression('module_invoke_all("foo", "menu_alter", $menu)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::moduleHandler()->invoke("foo", "menu_alter", [$menu])', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/NodeLoadTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/NodeLoadTest.php
new file mode 100644 (file)
index 0000000..342c9c0
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Filter;
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\NodeLoad
+ */
+class NodeLoadTest extends FunctionCallModifierTestBase {
+
+  public function testRewriteWithNidOnly() {
+    $function_call = Parser::parseExpression('node_load(30)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::entityManager()->getStorage(\'node\')->load(30)', $rewritten->getText());
+  }
+
+  public function testRewriteWithVid() {
+    $function_call = Parser::parseExpression('node_load(30, 32)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::entityManager()->getStorage(\'node\')->loadRevision(32)', $rewritten->getText());
+  }
+
+  /**
+   * This test is failing at the moment because for whatever reason,
+   * $snippet->children() is only fetching the first call to node_load().
+   */
+  public function _testRewriteWithCacheReset() {
+    $original = <<<'END'
+node_load(30);
+node_load(30, TRUE);
+node_load(30, 32);
+node_load(30, 32, TRUE);
+END;
+    $expected = <<<'END'
+\Drupal::entityManager()->getStorage('user')->load(30);
+// FIXME: To reset the node cache, use EntityStorageInterface::resetCache().
+\Drupal::entityManager()->getStorage('user')->load(30);
+\Drupal::entityManager()->getStorage('user')->loadRevision(32);
+// FIXME: To reset the node cache, use EntityStorageInterface::resetCache().
+\Drupal::entityManager()->getStorage('user')->loadRevision(32);
+END;
+    $snippet = Parser::parseSnippet($original);
+    $function_calls = $snippet->children(Filter::isFunctionCall('node_load'));
+    foreach ($function_calls as $function_call) {
+      $rewritten = $this->plugin->rewrite($function_call, $this->target);
+      $function_call->replaceWith($rewritten);
+    }
+    $this->assertEquals($expected, $snippet->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/StTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/StTest.php
new file mode 100644 (file)
index 0000000..3bb9457
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\St
+ */
+class StTest extends FunctionCallModifierTestBase {
+
+  public function testRewrite() {
+    $function_call = Parser::parseExpression('st("I translate thee!")');
+    /** @var \Pharborist\Functions\FunctionCallNode $rewritten */
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertSame($function_call, $rewritten);
+    $this->assertEquals('t("I translate thee!")', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/ThemeGetRegistryTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/ThemeGetRegistryTest.php
new file mode 100644 (file)
index 0000000..820c54e
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\ThemeGetRegistry
+ */
+class ThemeGetRegistryTest extends FunctionCallModifierTestBase {
+
+  public function testRewriteNoArgument() {
+    $function_call = Parser::parseExpression('theme_get_registry()');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::service(\'theme.registry\')->get()', $rewritten->getText());
+  }
+
+  public function testRewriteArgument() {
+    $function_call = Parser::parseExpression('theme_get_registry(FALSE)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::service(\'theme.registry\')->getRuntime()', $rewritten->getText());
+
+    $function_call = Parser::parseExpression('theme_get_registry("foo")');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::service(\'theme.registry\')->get()', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/UserAccessTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/UserAccessTest.php
new file mode 100644 (file)
index 0000000..0dbf92f
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\UserAccess
+ */
+class UserAccessTest extends FunctionCallModifierTestBase {
+
+  public function testRewriteCurrentUser() {
+    $function_call = Parser::parseExpression('user_access("kick ass and take names")');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::currentUser()->hasPermission("kick ass and take names")', $rewritten->getText());
+  }
+
+  public function testRewriteAccount() {
+    $function_call = Parser::parseExpression('user_access("be exceptional", $account)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('$account->hasPermission("be exceptional")', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/UserLoadTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/UserLoadTest.php
new file mode 100644 (file)
index 0000000..01ae76b
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Filter;
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\UserLoad
+ */
+class UserLoadTest extends FunctionCallModifierTestBase {
+
+  public function testRewriteWithoutCacheReset() {
+    $function_call = Parser::parseExpression('user_load(30)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::entityManager()->getStorage(\'user\')->load(30)', $rewritten->getText());
+  }
+
+  public function testRewriteWithCacheReset() {
+    $original = <<<'END'
+user_load(30, TRUE);
+END;
+    $expected = <<<'END'
+// @FIXME
+// To reset the user cache, use EntityStorageInterface::resetCache().
+\Drupal::entityManager()->getStorage('user')->load(30);
+END;
+    $snippet = Parser::parseSnippet($original);
+    $function_call = $snippet->children(Filter::isFunctionCall('user_load'))->get(0);
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $function_call->replaceWith($rewritten);
+    $this->assertEquals($expected, $snippet->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/UserSaveTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/UserSaveTest.php
new file mode 100644 (file)
index 0000000..4c76947
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\UserSave
+ */
+class UserSaveTest extends FunctionCallModifierTestBase {
+
+  public function testRewriteWithoutEditArray() {
+    $function_call = Parser::parseExpression('user_save($account)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('$account->save()', $rewritten->getText());
+  }
+
+  public function testRewriteWithEditArray() {
+    $function_call = Parser::parseExpression('user_save($account, array())');
+    $this->assertNull($this->plugin->rewrite($function_call, $this->target));
+  }
+
+  public function testRewriteWithEditArrayAndCategory() {
+    $function_call = Parser::parseExpression('user_save($account, array(), "Foo")');
+    $this->assertNull($this->plugin->rewrite($function_call, $this->target));
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/VariableDelTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/VariableDelTest.php
new file mode 100644 (file)
index 0000000..248376a
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Filter;
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\VariableDel
+ */
+class VariableDelTest extends FunctionCallModifierTestBase {
+
+  public function testNonStringKey() {
+    $original = <<<'END'
+<?php
+variable_del($my_var);
+END;
+    $expected = <<<'END'
+<?php
+// @FIXME
+// The correct configuration object could not be determined. You'll need to
+// rewrite this call manually.
+variable_del($my_var);
+END;
+
+    $snippet = Parser::parseSource($original);
+    $function_call = $snippet->find(Filter::isFunctionCall('variable_del'))->get(0);
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertNull($rewritten);
+    $this->assertSame($expected, $snippet->getText());
+  }
+
+  public function testForeignStringKey() {
+    $original = <<<'END'
+<?php
+variable_del('bar_wambooli');
+END;
+    $expected = <<<'END'
+<?php
+// @FIXME
+// This looks like another module's variable. You'll need to rewrite this call
+// to ensure that it uses the correct configuration object.
+variable_del('bar_wambooli');
+END;
+
+    $snippet = Parser::parseSource($original);
+    $function_call = $snippet->find(Filter::isFunctionCall('variable_del'))->get(0);
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertNull($rewritten);
+    $this->assertSame($expected, $snippet->getText());
+  }
+
+  public function testStringKey() {
+    $function_call = Parser::parseExpression('variable_del("foo_wambooli")');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::config(\'foo.settings\')->clear("foo_wambooli")->save()', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/VariableGetTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/VariableGetTest.php
new file mode 100644 (file)
index 0000000..2c5f690
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Filter;
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\VariableGet
+ */
+class VariableGetTest extends FunctionCallModifierTestBase {
+
+  public function testNonStringKey() {
+    $original = <<<'END'
+<?php
+variable_get($my_var, TRUE);
+END;
+    $expected = <<<'END'
+<?php
+// @FIXME
+// The correct configuration object could not be determined. You'll need to
+// rewrite this call manually.
+variable_get($my_var, TRUE);
+END;
+
+    $snippet = Parser::parseSource($original);
+    $function_call = $snippet->find(Filter::isFunctionCall('variable_get'))->get(0);
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertNull($rewritten);
+    $this->assertSame($expected, $snippet->getText());
+  }
+
+  public function testForeignStringKey() {
+    $original = <<<'END'
+<?php
+variable_get('bar_wambooli', TRUE);
+END;
+    $expected = <<<'END'
+<?php
+// @FIXME
+// This looks like another module's variable. You'll need to rewrite this call
+// to ensure that it uses the correct configuration object.
+variable_get('bar_wambooli', TRUE);
+END;
+
+    $snippet = Parser::parseSource($original);
+    $function_call = $snippet->find(Filter::isFunctionCall('variable_get'))->get(0);
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertNull($rewritten);
+    $this->assertSame($expected, $snippet->getText());
+  }
+
+  public function testStringKeyAndUnextractableDefaultValue() {
+    $original = <<<'END'
+<?php
+variable_get('foo_wambooli', array());
+END;
+    $expected = <<<'END'
+<?php
+// @FIXME
+// Could not extract the default value because it is either indeterminate, or
+// not scalar. You'll need to provide a default value in
+// config/install/@module.settings.yml and config/schema/@module.schema.yml.
+variable_get('foo_wambooli', array());
+END;
+
+    $snippet = Parser::parseSource($original);
+    $function_call = $snippet->find(Filter::isFunctionCall('variable_get'))->get(0);
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::config(\'foo.settings\')->get(\'foo_wambooli\')', $rewritten->getText());
+    $this->assertSame($expected, $snippet->getText());
+  }
+
+  public function testStringKeyAndExtractableDefaultValue() {
+    $function_call = Parser::parseExpression('variable_get("foo_wambooli", 30)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::config(\'foo.settings\')->get("foo_wambooli")', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/VariableSetTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/VariableSetTest.php
new file mode 100644 (file)
index 0000000..22bc278
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Filter;
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\VariableSet
+ */
+class VariableSetTest extends FunctionCallModifierTestBase {
+
+  public function testNonStringKey() {
+    $original = <<<'END'
+<?php
+variable_set($my_var, TRUE);
+END;
+    $expected = <<<'END'
+<?php
+// @FIXME
+// The correct configuration object could not be determined. You'll need to
+// rewrite this call manually.
+variable_set($my_var, TRUE);
+END;
+
+    $snippet = Parser::parseSource($original);
+    $function_call = $snippet->find(Filter::isFunctionCall('variable_set'))->get(0);
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertNull($rewritten);
+    $this->assertSame($expected, $snippet->getText());
+  }
+
+  public function testForeignStringKey() {
+    $original = <<<'END'
+<?php
+variable_set('bar_wambooli', TRUE);
+END;
+    $expected = <<<'END'
+<?php
+// @FIXME
+// This looks like another module's variable. You'll need to rewrite this call
+// to ensure that it uses the correct configuration object.
+variable_set('bar_wambooli', TRUE);
+END;
+
+    $snippet = Parser::parseSource($original);
+    $function_call = $snippet->find(Filter::isFunctionCall('variable_set'))->get(0);
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertNull($rewritten);
+    $this->assertSame($expected, $snippet->getText());
+  }
+
+  public function testStringKey() {
+    $function_call = Parser::parseExpression('variable_set("foo_wambooli", 30)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::configFactory()->getEditable(\'foo.settings\')->set("foo_wambooli", 30)->save()', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/WatchdogTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/WatchdogTest.php
new file mode 100644 (file)
index 0000000..1e7c89e
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Converter\Functions;
+
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Converter.Functions
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Converter\Functions\Watchdog
+ */
+class WatchdogTest extends FunctionCallModifierTestBase {
+
+  public function testRewriteNoVariablesDefaultSeverity() {
+    $function_call = Parser::parseExpression('watchdog("foo", "Hi!")');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::logger("foo")->notice("Hi!", [])', $rewritten->getText());
+  }
+
+  public function testRewriteVariablesDefaultSeverity() {
+    $function_call = Parser::parseExpression('watchdog("foo", "Hej", array("baz"))');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::logger("foo")->notice("Hej", array("baz"))', $rewritten->getText());
+  }
+
+  public function testRewriteNoVariablesSeverity() {
+    $function_call = Parser::parseExpression('watchdog("foo", "Harrr", NULL, WATCHDOG_WARNING)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::logger("foo")->warning("Harrr", [])', $rewritten->getText());
+  }
+
+  public function testRewriteVariablesSeverity() {
+    $function_call = Parser::parseExpression('watchdog("foo", "Hurrr", array("baz"), WATCHDOG_ERROR)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::logger("foo")->error("Hurrr", array("baz"))', $rewritten->getText());
+  }
+
+  public function testRewriteNoVariablesDynamicSeverity() {
+    $function_call = Parser::parseExpression('watchdog("foo", "Barrr", NULL, get_severity())');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::logger("foo")->notice("Barrr", [])', $rewritten->getText());
+  }
+
+  public function testRewriteVariablesTernarySeverity() {
+    $function_call = Parser::parseExpression('watchdog("foo", "Yarrr", array(0), $bipolar ? WATCHDOG_NOTICE : WATCHDOG_CRITICAL)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::logger("foo")->notice("Yarrr", array(0))', $rewritten->getText());
+  }
+
+  public function testRewriteNoVariablesUnknownSeverity() {
+    $function_call = Parser::parseExpression('watchdog("foo", "Ba-zing!", NULL, WATCHDOG_FOO)');
+    $rewritten = $this->plugin->rewrite($function_call, $this->target);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('\Drupal::logger("foo")->notice("Ba-zing!", [])', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/CreateClassTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/CreateClassTest.php
new file mode 100644 (file)
index 0000000..ab1cdc7
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Fixer;
+
+use Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer\CreateClass;
+use Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer\Delete;
+use Drupal\drupalmoduleupgrader\Plugin\DMU\Indexer\Classes;
+use Drupal\Tests\drupalmoduleupgrader\Unit\TestBase;
+
+/**
+ * @group DMU.Fixer
+ */
+class CreateClassTest extends TestBase {
+
+  public function test() {
+    $indexer = new Classes([], 'class', [], $this->db, $this->target);
+    $indexer->build();
+
+    $this
+      ->container
+      ->get('plugin.manager.drupalmoduleupgrader.indexer')
+      ->method('createInstance')
+      ->with('class')
+      ->willReturn($indexer);
+
+    $config = [
+      'class' => '\Drupal\foo\MyBaz',
+      'destination' => '~/src/MyBaz.php',
+      'parent' => '\Drupal\Core\Baz\BazBase',
+      'interfaces' => [
+        '\Drupal\Core\Baz\BazInterface',
+        '\Drupal\Core\Executable\ExecutableInterface',
+      ],
+      'doc' => 'This is my bazzifier. There are many like it, but this one is mine.',
+    ];
+    $plugin = new CreateClass($config, uniqID(), []);
+    $plugin->setTarget($this->target);
+    $plugin->execute();
+
+    $this->assertTrue($indexer->has('MyBaz'));
+    $classes = $indexer->get('MyBaz');
+    $this->assertCount(1, $classes);
+    /** @var \Pharborist\Objects\ClassNode $class */
+    $class = $classes->get(0);
+    $this->assertInstanceOf('\Pharborist\Objects\ClassNode', $class);
+    $this->assertEquals('\Drupal\foo\MyBaz', $class->getName()->getAbsolutePath());
+    $this->assertEquals('MyBaz', $class->getName()->getText());
+    $parent = $class->getExtends();
+    $this->assertInstanceOf('\Pharborist\Namespaces\NameNode', $parent);
+    $this->assertEquals('BazBase', $parent->getText());
+    return;
+    $interfaces = $class->getImplementList();
+    $this->assertCount(2, $interfaces->getItems());
+    $this->assertEquals('BazInterface', $interfaces->get(0)->getText());
+    $this->assertEquals('ExecutableInterface', $interfaces->get(1)->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/DefineTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/DefineTest.php
new file mode 100644 (file)
index 0000000..86dcfe8
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Fixer;
+
+use Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer\Define;
+use Drupal\Tests\drupalmoduleupgrader\Unit\TestBase;
+
+/**
+ * @group DMU.Fixer
+ */
+class DefineTest extends TestBase {
+
+  public function test() {
+    $config = [
+      'key' => 'foo.settings/baz',
+      'value' => 'wambooli',
+      'in' => '~/foo.settings.yml',
+    ];
+    $plugin = new Define($config, uniqID(), []);
+    $plugin->setTarget($this->target);
+    $plugin->execute();
+
+    $url = $this->dir->getChild('foo.settings.yml')->url();
+    $this->assertFileExists($url);
+    $expected = <<<END
+foo.settings:
+  baz: wambooli
+
+END;
+    $this->assertEquals($expected, file_get_contents($url));
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/DeleteTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/DeleteTest.php
new file mode 100644 (file)
index 0000000..c8be99e
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Fixer;
+
+use Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer\Delete;
+use Drupal\drupalmoduleupgrader\Plugin\DMU\Indexer\Functions;
+use Drupal\Tests\drupalmoduleupgrader\Unit\TestBase;
+
+/**
+ * @group DMU.Fixer
+ *
+ * @TODO Add a test of the 'where' configuration option.
+ */
+class
+DeleteTest extends TestBase {
+
+  public function test() {
+    $code = <<<'END'
+<?php
+
+function foo_permission() {
+  return array(
+    'bazify' => array(
+      'title' => 'Do snazzy bazzy things',
+    ),
+  );
+}
+END;
+    $this->dir->getChild('foo.module')->setContent($code);
+
+    $indexer = new Functions([], 'function', [], $this->db, $this->target);
+    $indexer->build();
+
+    $this
+      ->container
+      ->get('plugin.manager.drupalmoduleupgrader.indexer')
+      ->method('createInstance')
+      ->with('function')
+      ->willReturn($indexer);
+
+    $config = [
+      'type' => 'function',
+      'id' => 'hook_permission',
+    ];
+    $plugin = new Delete($config, uniqid(), []);
+    $plugin->setTarget($this->target);
+    $plugin->execute();
+
+    $this->assertFalse($indexer->has('permission'));
+    $this->assertEquals("<?php\n\n", $this->dir->getChild('foo.module')->getContent());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/DisableTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/DisableTest.php
new file mode 100644 (file)
index 0000000..13e8c60
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Fixer;
+
+use Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer\Disable;
+use Drupal\drupalmoduleupgrader\Plugin\DMU\Indexer\FunctionCalls;
+use Drupal\Tests\drupalmoduleupgrader\Unit\TestBase;
+
+/**
+ * @group DMU.Fixer
+ *
+ * @TODO Add a test of the 'where' configuration option.
+ */
+class DisableTest extends TestBase {
+
+  public function test() {
+    $code = <<<'END'
+<?php
+
+variable_get('foo');
+END;
+    $this->dir->getChild('foo.module')->setContent(rtrim($code));
+
+    $indexer = new FunctionCalls([], 'function', ['exclude' => []], $this->db, $this->target);
+    $indexer->build();
+
+    $this
+      ->container
+      ->get('plugin.manager.drupalmoduleupgrader.indexer')
+      ->method('createInstance')
+      ->with('function_call')
+      ->willReturn($indexer);
+
+    $config = [
+      'type' => 'function_call',
+      'id' => 'variable_get',
+      'note' => 'This is no longer kosher!',
+    ];
+    $plugin = new Disable($config, uniqID(), []);
+    $plugin->setTarget($this->target);
+    $plugin->execute();
+
+    $expected = <<<END
+<?php
+
+// This is no longer kosher!
+// variable_get('foo');
+END;
+
+    // trim() makes the test pass. Go figure.
+    $this->assertEquals($expected, trim($this->dir->getChild('foo.module')->getContent()));
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/FormCallbackToMethodTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/FormCallbackToMethodTest.php
new file mode 100644 (file)
index 0000000..b205e7e
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Fixer;
+
+use Drupal\drupalmoduleupgrader\IOException;
+use Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer\FormCallbackToMethod;
+use Drupal\Tests\drupalmoduleupgrader\Unit\TestBase;
+use Pharborist\NodeCollection;
+use Pharborist\Objects\ClassNode;
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Fixer
+ */
+class FormCallbackToMethodTest extends TestBase {
+
+  public function test() {
+    $callback = Parser::parseSnippet('function foo_submit(&$form, &$form_state) {}');
+    $function_indexer = $this->getMock('\Drupal\drupalmoduleupgrader\IndexerInterface');
+    $function_indexer->method('get')->with('foo_submit')->willReturn(new NodeCollection([ $callback ]));
+
+    $class = ClassNode::create('FooForm');
+    $class_indexer = $this->getMock('\Drupal\drupalmoduleupgrader\IndexerInterface');
+    $class_indexer->method('get')->with('FooForm')->willReturn(new NodeCollection([ $class ]));
+
+    $this
+      ->container
+      ->get('plugin.manager.drupalmoduleupgrader.indexer')
+      ->method('createInstance')
+      ->willReturnCallback(function($which) use ($class_indexer, $function_indexer) {
+        switch ($which) {
+          case 'class':
+            return $class_indexer;
+          case 'function':
+            return $function_indexer;
+          default:
+            break;
+        }
+      });
+
+    $config = [
+      'callback' => 'foo_submit',
+      'destination' => 'FooForm::submitForm',
+    ];
+    $plugin = new FormCallbackToMethod($config, uniqID(), []);
+    $plugin->setTarget($this->target);
+    try {
+      // We expect a CodeManagerIOException because we're implementing the
+      // method on a class that is not officially part of the target's code.
+      // That's OK, though.
+      $plugin->execute();
+    }
+    catch (IOException $e) {}
+
+    $this->assertTrue($class->hasMethod('submitForm'));
+    $parameters = $class->getMethod('submitForm')->getParameters();
+    $this->assertCount(2, $parameters);
+    $this->assertEquals('form', $parameters[0]->getName());
+    $this->assertInstanceOf('\Pharborist\TokenNode', $parameters[0]->getTypeHint());
+    $this->assertSame(T_ARRAY, $parameters[0]->getTypeHint()->getType());
+    $this->assertInstanceOf('\Pharborist\TokenNode', $parameters[0]->getReference());
+    $this->assertEquals('form_state', $parameters[1]->getName());
+    $this->assertInstanceOf('\Pharborist\Namespaces\NameNode', $parameters[1]->getTypeHint());
+    $this->assertEquals('Drupal\Core\Form\FormStateInterface', $parameters[1]->getTypeHint()->getText());
+    $this->assertNull($parameters[1]->getReference());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/HookToYAMLTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/HookToYAMLTest.php
new file mode 100644 (file)
index 0000000..300bcaf
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Fixer;
+
+use Drupal\Component\Serialization\Yaml as YAML;
+use Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer\HookToYAML;
+use Drupal\Tests\drupalmoduleupgrader\Unit\TestBase;
+
+/**
+ * @group DMU.Fixer
+ */
+class HookToYAMLTest extends TestBase {
+
+  public function test() {
+    $permissions = [
+      'bazify' => [
+        'title' => 'Do snazzy bazzy things',
+      ],
+    ];
+
+    $indexer = $this->getMockBuilder('\Drupal\drupalmoduleupgrader\Plugin\DMU\Indexer\Functions')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $indexer->method('has')->with('hook_permission')->willReturn(TRUE);
+    $indexer->method('hasExecutable')->with('hook_permission')->willReturn(TRUE);
+    $indexer->method('execute')->with('hook_permission')->willReturn($permissions);
+    $this
+      ->container
+      ->get('plugin.manager.drupalmoduleupgrader.indexer')
+      ->method('createInstance')
+      ->with('function')
+      ->willReturn($indexer);
+
+    $config = [
+      'hook' => 'permission',
+      'destination' => '~/foo.permissions.yml',
+    ];
+    $plugin = new HookToYAML($config, uniqID(), []);
+    $plugin->setTarget($this->target);
+    $plugin->execute();
+
+    $url = $this->dir->getChild('foo.permissions.yml')->url();
+    $this->assertFileExists($url);
+    $this->assertSame(YAML::encode($permissions), file_get_contents($url));
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/ImplementHookTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/ImplementHookTest.php
new file mode 100644 (file)
index 0000000..f91687a
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Fixer;
+
+use Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer\ImplementHook;
+use Drupal\Tests\drupalmoduleupgrader\Unit\TestBase;
+use Pharborist\Filter;
+
+/**
+ * @group DMU.Fixer
+ */
+class ImplementHookTest extends TestBase {
+
+  public function test() {
+    eval('function hook_tokens($type, $tokens, array $data = array(), array $options = array()) {}');
+
+    $config = [
+      'hook' => 'tokens',
+      'module' => 'system',
+    ];
+    $module_handler = $this->getMock('\Drupal\Core\Extension\ModuleHandlerInterface');
+    $plugin = new ImplementHook($config, uniqID(), [], $module_handler);
+    $plugin->setTarget($this->target);
+    $plugin->execute();
+
+    $module = $this->target->getPath('.module');
+    $function = $this->target->open($module)->children(Filter::isFunction('foo_tokens'))->get(0);
+    $this->assertInstanceOf('\Pharborist\Functions\FunctionDeclarationNode', $function);
+    $this->assertEquals('foo_tokens', $function->getName()->getText());
+
+    $parameters = $function->getParameters();
+    $this->assertCount(4, $parameters);
+
+    $this->assertNull($parameters[0]->getTypeHint());
+    $this->assertEquals('type', $parameters[0]->getName());
+    $this->assertNull($parameters[0]->getValue());
+
+    $this->assertNull($parameters[1]->getTypeHint());
+    $this->assertEquals('tokens', $parameters[1]->getName());
+    $this->assertNull($parameters[1]->getValue());
+
+    $this->assertInstanceOf('\Pharborist\TokenNode', $parameters[2]->getTypeHint());
+    $this->assertSame(T_ARRAY, $parameters[2]->getTypeHint()->getType());
+    $this->assertEquals('data', $parameters[2]->getName());
+    $this->assertInstanceOf('\Pharborist\Types\ArrayNode', $parameters[2]->getValue());
+
+    $this->assertInstanceOf('\Pharborist\TokenNode', $parameters[3]->getTypeHint());
+    $this->assertSame(T_ARRAY, $parameters[3]->getTypeHint()->getType());
+    $this->assertEquals('options', $parameters[3]->getName());
+    $this->assertInstanceOf('\Pharborist\Types\ArrayNode', $parameters[3]->getValue());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/ImplementTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/ImplementTest.php
new file mode 100644 (file)
index 0000000..6e04517
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Fixer;
+
+use Drupal\drupalmoduleupgrader\IOException;
+use Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer\Implement;
+use Drupal\Tests\drupalmoduleupgrader\Unit\TestBase;
+use Pharborist\NodeCollection;
+use Pharborist\Objects\ClassNode;
+
+/**
+ * @group DMU.Fixer
+ */
+class ImplementTest extends TestBase {
+
+  public function test() {
+    $class = ClassNode::create('Foobaz');
+    $indexer = $this->getMock('\Drupal\drupalmoduleupgrader\IndexerInterface');
+    $indexer->method('get')->with('Foobaz')->willReturn(new NodeCollection([$class]));
+
+    $this
+      ->container
+      ->get('plugin.manager.drupalmoduleupgrader.indexer')
+      ->method('createInstance')
+      ->with('class')
+      ->willReturn($indexer);
+
+    $config = [
+      'definition' => '\Drupal\Core\Block\BlockPluginInterface::blockForm',
+      'target' => 'Foobaz',
+    ];
+    $plugin = new Implement($config, uniqID(), []);
+    $plugin->setTarget($this->target);
+    try {
+      // We expect a CodeManagerIOException because we're implementing the
+      // method on a class that is not officially part of the target's code.
+      // That's OK, though.
+      $plugin->execute();
+    }
+    catch (IOException $e) {}
+
+    $this->assertTrue($class->hasMethod('blockForm'));
+    $method = $class->getMethod('blockForm');
+    $this->assertInstanceOf('\Pharborist\Objects\ClassMethodNode', $method);
+    $parameters = $method->getParameters();
+    $this->assertCount(2, $parameters);
+    $this->assertEquals($parameters[0]->getName(), 'form');
+    $this->assertNull($parameters[0]->getTypeHint());
+    $this->assertEquals($parameters[1]->getName(), 'form_state');
+    $type = $parameters[1]->getTypeHint();
+    $this->assertInstanceOf('\Pharborist\Namespaces\NameNode', $type);
+    $this->assertEquals('Drupal\Core\Form\FormStateInterface', $type->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/NotifyTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/NotifyTest.php
new file mode 100644 (file)
index 0000000..0ba10b0
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Fixer;
+
+use Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer\Notify;
+use Drupal\Tests\drupalmoduleupgrader\Unit\TestBase;
+use Pharborist\DocCommentNode;
+use Pharborist\NodeCollection;
+use Pharborist\Objects\ClassNode;
+
+/**
+ * @group DMU.Fixer
+ *
+ * @TODO Add a test of the 'where' configuration option.
+ */
+class NotifyTest extends TestBase {
+
+  public function testDocComment() {
+    $class = ClassNode::create('Wambooli');
+    $class->setDocComment(DocCommentNode::create('Double wambooli!'));
+    $this->assertInstanceOf('\Pharborist\DocCommentNode', $class->getDocComment());
+    $indexer = $this->getMock('\Drupal\drupalmoduleupgrader\IndexerInterface');
+    $indexer->method('get')->with('Wambooli')->willReturn(new NodeCollection([ $class ]));
+
+    $this
+      ->container
+      ->get('plugin.manager.drupalmoduleupgrader.indexer')
+      ->method('createInstance')
+      ->with('class')
+      ->willReturn($indexer);
+
+    $config = [
+      'type' => 'class',
+      'id' => 'Wambooli',
+      'note' => 'You need to rewrite this thing because I said so!',
+    ];
+    $plugin = new Notify($config, uniqID(), []);
+    $plugin->setTarget($this->target);
+    $plugin->execute();
+
+    $comment = $class->getDocComment();
+    $this->assertInstanceOf('\Pharborist\DocCommentNode', $comment);
+    $expected = <<<END
+Double wambooli!
+
+You need to rewrite this thing because I said so!
+END;
+    $this->assertEquals($expected, $comment->getCommentText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/PSR4Test.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/PSR4Test.php
new file mode 100644 (file)
index 0000000..54c42bc
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Fixer;
+
+use Drupal\drupalmoduleupgrader\Plugin\DMU\Fixer\PSR4;
+use Drupal\Tests\drupalmoduleupgrader\Unit\TestBase;
+use org\bovigo\vfs\vfsStream;
+use Pharborist\NodeCollection;
+use Pharborist\Objects\ClassNode;
+
+/**
+ * @group DMU.Fixer
+ */
+class PSR4Test extends TestBase {
+
+  public function test() {
+    $class = ClassNode::create('Wambooli');
+    $indexer = $this->getMock('\Drupal\drupalmoduleupgrader\IndexerInterface');
+    $indexer->method('get')->with('Wambooli')->willReturn(new NodeCollection([ $class ]));
+
+    $this
+      ->container
+      ->get('plugin.manager.drupalmoduleupgrader.indexer')
+      ->method('createInstance')
+      ->with('class')
+      ->willReturn($indexer);
+
+    $config = [
+      'source' => 'Wambooli',
+      'destination' => 'Drupal\foo\Wambooli',
+    ];
+    $plugin = new PSR4($config, uniqID(), []);
+    $plugin->setTarget($this->target);
+    $plugin->execute();
+
+    $url = $this->target->getPath('src/Wambooli.php');
+    $this->assertFileExists($url);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Indexer/ClassesTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Indexer/ClassesTest.php
new file mode 100644 (file)
index 0000000..1d2e837
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Indexer;
+use Drupal\drupalmoduleupgrader\Plugin\DMU\Indexer\Classes;
+
+/**
+ * @group DMU.Indexer
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Indexer\Classes
+ *
+ * @expectID Foobaz
+ * @expectType \Pharborist\Objects\ClassNode
+ */
+class ClassesTest extends IndexerTestBase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $code = <<<'END'
+<?php
+
+class Foobaz {}
+END;
+    $this->dir->getChild('foo.module')->setContent($code);
+
+    $this->indexer = new Classes([], 'class', [], $this->db, $this->target);
+    $this->indexer->build();
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Indexer/FunctionsTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Indexer/FunctionsTest.php
new file mode 100644 (file)
index 0000000..d75d4c3
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Indexer;
+use Drupal\drupalmoduleupgrader\Plugin\DMU\Indexer\Functions;
+
+/**
+ * @group DMU.Indexer
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Indexer\Functions
+ *
+ * @expectID foo_blarg
+ * @expectType \Pharborist\Functions\FunctionDeclarationNode
+ */
+class FunctionsTest extends IndexerTestBase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $code = <<<'END'
+<?php
+
+function foo_blarg() {}
+END;
+    $this->dir->getChild('foo.module')->setContent($code);
+
+    $this->indexer = new Functions([], 'function', [], $this->db, $this->target);
+    $this->indexer->build();
+  }
+
+  public function testQuery() {
+    $this->assertInstanceOf('\Drupal\Core\Database\Query\Select', $this->indexer->getQuery());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Indexer/IndexerTestBase.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Indexer/IndexerTestBase.php
new file mode 100644 (file)
index 0000000..97c6956
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Indexer;
+
+use Drupal\Tests\drupalmoduleupgrader\Unit\TestBase;
+
+/**
+ * Base class for tests of DMU's indexer plugins. Because the indexers'
+ * behavior is so standard, this class reflects that by implementing a lot
+ * of standard assertions.
+ */
+abstract class IndexerTestBase extends TestBase {
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\IndexerInterface
+   */
+  protected $indexer;
+
+  public function testClear() {
+    $this->indexer->clear();
+    $this->assertCount(0, $this->indexer);
+  }
+
+  public function testHas() {
+    $this->assertTrue($this->indexer->has($this->info['class']['expectID'][0]));
+    $this->assertFalse($this->indexer->has(uniqID()));
+  }
+
+  public function testGet() {
+    $node = $this->indexer->get($this->info['class']['expectID'][0]);
+
+    $this->assertFalse($collection->isEmpty());
+
+    $this->assertInstanceOf($this->info['class']['expectType'][0], $node);
+  }
+
+  /**
+   * @depends testHas
+   */
+  public function testDelete() {
+    $id = $this->info['class']['expectID'][0];
+    $this->indexer->delete($id);
+    $this->assertFalse($this->indexer->has($id));
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Rewriter/FormStateTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Rewriter/FormStateTest.php
new file mode 100644 (file)
index 0000000..ef15773
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Rewriter;
+
+use Drupal\Tests\drupalmoduleupgrader\Unit\TestBase;
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Rewriter
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Rewriter\FormState
+ */
+class FormStateTest extends TestBase {
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\RewriterInterface
+   */
+  protected $plugin;
+
+  public function setUp() {
+    parent::setUp();
+
+    $definition = [
+      'properties' => [
+        'input' => [
+          'get' => 'getUserInput',
+          'set' => 'setUserInput',
+        ],
+      ],
+    ];
+    $this->plugin = $this->getPlugin([], $definition);
+  }
+
+  public function testRewriteValuesAsGetter() {
+    $expr = Parser::parseExpression('$form_state["values"]');
+    $rewritten = $this->plugin->rewriteAsGetter($expr, 'values');
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('$form_state->getValues()', $rewritten->getText());
+
+    $expr = Parser::parseExpression('$form_state["values"]["foo"]');
+    $rewritten = $this->plugin->rewriteAsGetter($expr, 'values');
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('$form_state->getValue(["foo"])', $rewritten->getText());
+
+    $expr = Parser::parseExpression('$form_state["values"]["foo"][0]');
+    $rewritten = $this->plugin->rewriteAsGetter($expr, 'values');
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('$form_state->getValue(["foo", 0])', $rewritten->getText());
+  }
+
+  public function testRewriteKnownPropertyAsGetter() {
+    $expr = Parser::parseExpression('$form_state["input"]');
+    $rewritten = $this->plugin->rewriteAsGetter($expr, 'input');
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('$form_state->getUserInput()', $rewritten->getText());
+  }
+
+  public function testRewriteArbitraryKeyAsGetter() {
+    $expr = Parser::parseExpression('$form_state["foo"]["baz"]');
+    $rewritten = $this->plugin->rewriteAsGetter($expr, 'foo');
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('$form_state->get(["foo", "baz"])', $rewritten->getText());
+  }
+
+  public function testRewriteValuesAsSetter() {
+    /** @var \Pharborist\Operators\AssignNode $expr */
+    $expr = Parser::parseExpression('$form_state["values"]["foo"] = "baz"');
+    $rewritten = $this->plugin->rewriteAsSetter($expr->getLeftOperand(), 'values', $expr);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('$form_state->setValue(["foo"], "baz")', $rewritten->getText());
+
+    $expr = Parser::parseExpression('$form_state["values"]["foo"][1] = "bar"');
+    $rewritten = $this->plugin->rewriteAsSetter($expr->getLeftOperand(), 'values', $expr);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('$form_state->setValue(["foo", 1], "bar")', $rewritten->getText());
+  }
+
+  public function testRewriteKnownPropertyAsSetter() {
+    /** @var \Pharborist\Operators\AssignNode $expr */
+    $expr = Parser::parseExpression('$form_state["input"] = array()');
+    $rewritten = $this->plugin->rewriteAsSetter($expr->getLeftOperand(), 'input', $expr);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('$form_state->setUserInput(array())', $rewritten->getText());
+  }
+
+  public function testRewriteArbitraryKeyAsSetter() {
+    /** @var \Pharborist\Operators\AssignNode $expr */
+    $expr = Parser::parseExpression('$form_state["foo"]["baz"] = "bar"');
+    $rewritten = $this->plugin->rewriteAsSetter($expr->getLeftOperand(), 'foo', $expr);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('$form_state->set(["foo", "baz"], "bar")', $rewritten->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Rewriter/GenericTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Rewriter/GenericTest.php
new file mode 100644 (file)
index 0000000..fdf5aaf
--- /dev/null
@@ -0,0 +1,140 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Plugin\DMU\Rewriter;
+
+use Drupal\drupalmoduleupgrader\Plugin\DMU\Rewriter\Generic as GenericRewriter;
+use Drupal\Tests\drupalmoduleupgrader\Unit\TestBase;
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Rewriter
+ * @covers \Drupal\drupalmoduleupgrader\Plugin\DMU\Rewriter\Generic
+ */
+class GenericTest extends TestBase {
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\RewriterInterface
+   */
+  protected $plugin;
+
+  public function setUp() {
+    parent::setUp();
+
+    $definition = [
+      'properties' => [
+        'nid' => [
+          'get' => 'id',
+        ],
+        'title' => [
+          'get' => 'getTitle',
+          'set' => 'setTitle',
+        ],
+      ],
+    ];
+    $this->plugin = $this->getPlugin([], $definition);
+  }
+
+  public function testRewriteValidPropertyAsGetter() {
+    /** @var \Pharborist\Objects\ObjectPropertyNode $expr */
+    $expr = Parser::parseExpression('$node->nid');
+    $rewritten = $this->plugin->rewriteAsGetter($expr, 'nid');
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('$node->id()', $rewritten->getText());
+  }
+
+  public function testRewriteInvalidPropertyAsGetter() {
+    /** @var \Pharborist\Objects\ObjectPropertyNode $expr */
+    $expr = Parser::parseExpression('$node->baz');
+    $rewritten = $this->plugin->rewriteAsGetter($expr, 'baz');
+    $this->assertNull($rewritten);
+  }
+
+  public function testRewriteValidPropertyAsSetter() {
+    /** @var \Pharborist\Operators\AssignNode $expr */
+    $expr = Parser::parseExpression('$node->title = "Foobaz"');
+    $rewritten = $this->plugin->rewriteAsSetter($expr->getLeftOperand(), 'title', $expr);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $rewritten);
+    $this->assertEquals('$node->setTitle("Foobaz")', $rewritten->getText());
+  }
+
+  public function testRewriteInvalidPropertyAsSetter() {
+    /** @var \Pharborist\Operators\AssignNode $expr */
+    $expr = Parser::parseExpression('$node->baz = "Blorf!"');
+    $rewritten = $this->plugin->rewriteAsSetter($expr->getLeftOperand(), 'baz', $expr);
+    $this->assertNull($rewritten);
+
+    /** @var \Pharborist\Operators\AssignNode $expr */
+    $expr = Parser::parseExpression('$node->nid = 30');
+    $rewritten = $this->plugin->rewriteAsSetter($expr->getLeftOperand(), 'nid', $expr);
+    $this->assertNull($rewritten);
+  }
+
+  public function testRewriteFieldLookup() {
+    /** @var \Pharborist\ArrayLookupNode $lookup */
+    $lookup = Parser::parseExpression('$node->field_foo[LANGUAGE_NONE][0]["value"]');
+    $rewritten = GenericRewriter::rewriteFieldLookup($lookup);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectPropertyNode', $rewritten);
+    $this->assertEquals('$node->field_foo[0]->value', $rewritten->getText());
+
+    $lookup = Parser::parseExpression('$node->field_foo[\Drupal\Core\Language\Language::LANGCODE_NOT_SPECIFIED][0]["value"]');
+    $rewritten = GenericRewriter::rewriteFieldLookup($lookup);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectPropertyNode', $rewritten);
+    $this->assertEquals('$node->field_foo[0]->value', $rewritten->getText());
+
+    $lookup = Parser::parseExpression('$node->field_foo["und"][0]["value"]');
+    $rewritten = GenericRewriter::rewriteFieldLookup($lookup);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectPropertyNode', $rewritten);
+    $this->assertEquals('$node->field_foo[0]->value', $rewritten->getText());
+
+    $lookup = Parser::parseExpression('$node->field_foo["en"][0]["value"]');
+    $rewritten = GenericRewriter::rewriteFieldLookup($lookup);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectPropertyNode', $rewritten);
+    $this->assertEquals('$node->getTranslation("en")->field_foo[0]->value', $rewritten->getText());
+
+    $lookup = Parser::parseExpression('$node->field_foo["und"][2]["wambooli"]');
+    $rewritten = GenericRewriter::rewriteFieldLookup($lookup);
+    $this->assertInstanceOf('\Pharborist\Objects\ObjectPropertyNode', $rewritten);
+    $this->assertEquals('$node->field_foo[2]->wambooli', $rewritten->getText());
+  }
+
+  public function testRewriteEmpty() {
+    $code = <<<'END'
+function foo($baz) {
+  if (empty($baz->nid)) {
+  }
+}
+END;
+    /** @var \Pharborist\Functions\FunctionDeclarationNode $func */
+    $func = Parser::parseSnippet($code);
+    $this->plugin->rewrite($func->getParameterAtIndex(0));
+
+    $expected = <<<'END'
+function foo($baz) {
+  if (!$baz->id()) {
+  }
+}
+END;
+    $this->assertEquals($expected, $func->getText());
+  }
+
+  public function testRewriteIsset() {
+    $code = <<<'END'
+function foo($baz) {
+  if (isset($baz->title)) {
+  }
+}
+END;
+    /** @var \Pharborist\Functions\FunctionDeclarationNode $func */
+    $func = Parser::parseSnippet($code);
+    $this->plugin->rewrite($func->getParameterAtIndex(0));
+
+    $expected = <<<'END'
+function foo($baz) {
+  if (!$baz->getTitle()) {
+  }
+}
+END;
+    $this->assertEquals($expected, $func->getText());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/ReportTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/ReportTest.php
new file mode 100644 (file)
index 0000000..68b5800
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit;
+
+use Drupal\drupalmoduleupgrader\Issue;
+use Drupal\drupalmoduleupgrader\Report;
+
+/**
+ * @group DMU
+ */
+class ReportTest extends TestBase {
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\ReportInterface
+   */
+  private $report;
+
+  public function setUp() {
+    parent::setUp();
+    $this->report = new Report();
+  }
+
+  public function test() {
+    $issue = new Issue($this->target, 'Foo');
+    $this->report->addIssue($issue);
+
+    $issue = new Issue($this->target, 'Baz');
+    $this->report->addIssue($issue);
+
+    $issues = $this->report->getIssues();
+    $this->assertTrue(is_array($issues));
+    $this->assertCount(2, $issues);
+    $this->assertInstanceOf('\Drupal\drupalmoduleupgrader\IssueInterface', $issues[0]);
+    $this->assertEquals('Foo', $issues[0]->getTitle());
+    $this->assertInstanceOf('\Drupal\drupalmoduleupgrader\IssueInterface', $issues[1]);
+    $this->assertEquals('Baz', $issues[1]->getTitle());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/Drupal7/RouteWrapperTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/Drupal7/RouteWrapperTest.php
new file mode 100644 (file)
index 0000000..19b8bbc
--- /dev/null
@@ -0,0 +1,128 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\drupalmoduleupgrader\Unit\Converter\Routing\Drupal7\RouteWrapperTest.
+ */
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Routing\Drupal7;
+
+use Drupal\drupalmoduleupgrader\Routing\Drupal7\RouteWrapper;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @group DMU.Routing
+ */
+class RouteWrapperTest extends UnitTestCase {
+
+  private function getMockRouteWrapper() {
+    $route = [
+      'title' => 'List revisions',
+      'page callback' => 'diff_diffs_overview',
+      'type' => 'MENU_DEFAULT_LOCAL_TASK',
+      'access callback' => 'diff_node_revision_access',
+      'access arguments' => [1],
+      'file' => 'diff.pages.inc',
+    ];
+    return new RouteWrapper('node/%node/revisions/list', $route);
+  }
+
+  public function testGetIdentifier() {
+    $this->assertEquals('node/%node/revisions/list', $this->getMockRouteWrapper()->getIdentifier());
+  }
+
+  public function testGetPath() {
+    $wrapper = $this->getMockRouteWrapper();
+    $this->assertInstanceOf('\Drupal\drupalmoduleupgrader\Utility\Path\Drupal7\PathUtility', $wrapper->getPath());
+    $this->assertEquals('node/%node/revisions/list', $wrapper->getPath());
+  }
+
+  public function testUnwrap() {
+    $route = [
+      'title' => 'List revisions',
+      'page callback' => 'diff_diffs_overview',
+      'type' => 'MENU_DEFAULT_LOCAL_TASK',
+      'access callback' => 'diff_node_revision_access',
+      'access arguments' => [1],
+      'file' => 'diff.pages.inc',
+    ];
+
+    $unwrapped_route = $this->getMockRouteWrapper()->unwrap();
+    $this->assertTrue(is_array($unwrapped_route));
+
+    foreach ($route as $key => $value) {
+      $this->assertArrayHasKey($key, $unwrapped_route);
+      $this->assertEquals($value, $unwrapped_route[$key]);
+    }
+  }
+
+  public function testIsAbsoluteAccess() {
+    $wrapper = $this->getMockRouteWrapper();
+
+    $this->assertFalse($wrapper->isAbsoluteAccess());
+    $wrapper['access callback'] = TRUE;
+    $this->assertTrue($wrapper->isAbsoluteAccess());
+    $wrapper['access callback'] = FALSE;
+    $this->assertTrue($wrapper->isAbsoluteAccess());
+  }
+
+  public function testIsPermissionBased() {
+    $wrapper = $this->getMockRouteWrapper();
+
+    $this->assertFalse($wrapper->isPermissionBased());
+    $wrapper['access callback'] = 'user_access';
+    $this->assertTrue($wrapper->isPermissionBased());
+  }
+
+  public function testHasLink() {
+    $this->assertTrue($this->getMockRouteWrapper()->hasLink());
+  }
+
+  public function testIsLink() {
+    $wrapper = $this->getMockRouteWrapper();
+    $this->assertFalse($wrapper->isLink());
+
+    $wrapper['type'] = 'MENU_NORMAL_ITEM';
+    $this->assertTrue($wrapper->isLink());
+  }
+
+  public function testIsLocalTask() {
+    $wrapper = $this->getMockRouteWrapper();
+    $this->assertFalse($wrapper->isLocalTask());
+
+    $wrapper['type'] = 'MENU_LOCAL_TASK';
+    $this->assertTrue($wrapper->isLocalTask());
+  }
+
+  public function testIsDefaultLocalTask() {
+    $wrapper = $this->getMockRouteWrapper();
+    $this->assertTrue($wrapper->isDefaultLocalTask());
+
+    $wrapper['type'] = 'MENU_NORMAL_ITEM';
+    $this->assertFalse($wrapper->isDefaultLocalTask());
+  }
+
+  public function testIsLocalAction() {
+    $wrapper = $this->getMockRouteWrapper();
+    $this->assertFalse($wrapper->isLocalAction());
+
+    $wrapper['type'] = 'MENU_LOCAL_ACTION';
+    $this->assertTrue($wrapper->isLocalAction());
+  }
+
+  public function testIsContextualLink() {
+    $wrapper = $this->getMockRouteWrapper();
+    $this->assertFalse($wrapper->isContextualLink());
+
+    $wrapper['type'] = 'MENU_LOCAL_ACTION';
+    $this->assertTrue($wrapper->isLocalAction());
+    $this->assertFalse($wrapper->isContextualLink());
+
+    $wrapper['context'] = 'MENU_CONTEXT_INLINE';
+    $this->assertTrue($wrapper->isContextualLink());
+
+    $wrapper['type'] = 'MENU_NORMAL_ITEM';
+    $this->assertFalse($wrapper->isContextualLink());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/Drupal7/RouterTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/Drupal7/RouterTest.php
new file mode 100644 (file)
index 0000000..f65ccd0
--- /dev/null
@@ -0,0 +1,171 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\drupalmoduleupgrader\Unit\Converter\Routing\Drupal7\RouterTest.
+ */
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Routing\Drupal7;
+
+use Drupal\drupalmoduleupgrader\Routing\Drupal7\Router;
+use Drupal\drupalmoduleupgrader\Routing\Drupal7\RouteWrapper;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @group DMU.Routing
+ */
+class RouterTest extends UnitTestCase {
+
+  private $router;
+
+  public function __construct() {
+    $this->router = new Router();
+
+    foreach ($this->hookMenu() as $path => $item) {
+      $route = new RouteWrapper($path, $item);
+      $this->router->addRoute($route);
+    }
+  }
+
+  public function testOfType() {
+    $this->assertCount(8, $this->router->ofType('MENU_LOCAL_TASK, MENU_DEFAULT_LOCAL_TASK'));
+  }
+
+  public function testGetAllLinks() {
+    $this->assertCount(9, $this->router->getAllLinks());
+  }
+
+  public function testGetLinks() {
+    $this->assertCount(1, $this->router->getLinks());
+  }
+
+  public function testGetLocalTasks() {
+    $this->assertCount(5, $this->router->getLocalTasks());
+  }
+
+  public function testGetDefaultLocalTasks() {
+    $this->assertCount(3, $this->router->getDefaultLocalTasks());
+  }
+
+  public function testGetLocalActions() {
+    $this->assertCount(0, $this->router->getLocalActions());
+  }
+
+  public function testGetContextualLinks() {
+    $this->assertCount(0, $this->router->getContextualLinks());
+  }
+
+  public function testFinalize() {
+    $this->router->finalize();
+
+    $list_revisions = $this->router['node/%node/revisions/list'];
+    $this->assertFalse($list_revisions->hasParent());
+    $this->assertFalse($list_revisions->hasChildren());
+    $this->assertTrue($list_revisions->hasSiblings());
+    $this->assertTrue($list_revisions->getSiblings()->containsKey('node/%node/revisions/view'));
+
+    $view_revisions = $this->router['node/%node/revisions/view'];
+    $this->assertFalse($view_revisions->hasParent());
+    $this->assertTrue($view_revisions->hasChildren());
+    $this->assertTrue($view_revisions->getChildren()->containsKey('node/%node/revisions/view/latest'));
+    $this->assertTrue($view_revisions->hasSiblings());
+    $this->assertTrue($view_revisions->getSiblings()->containsKey('node/%node/revisions/list'));
+
+    $diff_fields = $this->router['admin/config/content/diff/fields'];
+    $this->assertTrue($diff_fields->hasParent());
+    $this->assertEquals('admin/config/content/diff', $diff_fields->getParent()->getPath());
+  }
+
+  /**
+   * The Diff module's hook_menu() implementation. It's a nice mix of things
+   * to test on.
+   *
+   * @return array
+   */
+  private function hookMenu() {
+    $items = [];
+    $items['node/%node/revisions/list'] = array(
+      'title' => 'List revisions',
+      'page callback' => 'diff_diffs_overview',
+      'type' => 'MENU_DEFAULT_LOCAL_TASK',
+      'access callback' => 'diff_node_revision_access',
+      'access arguments' => array(1),
+      'file' => 'diff.pages.inc',
+    );
+    $items['node/%node/revisions/view'] = array(
+      'title' => 'Compare revisions',
+      'page callback' => 'diff_diffs_show',
+      'page arguments' => array(1, 4, 5, 6),
+      'type' => 'MENU_LOCAL_TASK',
+      'access callback' => 'diff_node_revision_access',
+      'access arguments' => array(1),
+      'tab_parent' => 'node/%/revisions/list',
+      'file' => 'diff.pages.inc',
+    );
+    $items['node/%node/revisions/view/latest'] = array(
+      'title' => 'Show latest difference',
+      'page callback' => 'diff_latest',
+      'page arguments' => array(1),
+      'type' => 'MENU_LOCAL_TASK',
+      'access arguments' => array('access content'),
+      'tab_parent' => 'node/%/revisions/view',
+      'file' => 'diff.pages.inc',
+    );
+    $items['admin/config/content/diff'] = array(
+      'title' => 'Diff',
+      'description' => 'Diff settings.',
+      'file' => 'diff.admin.inc',
+      'page callback' => 'drupal_get_form',
+      'page arguments' => array('diff_admin_settings'),
+      'access arguments' => array('administer site configuration'),
+    );
+    $items['admin/config/content/diff/settings'] = array(
+      'title' => 'Settings',
+      'type' => 'MENU_DEFAULT_LOCAL_TASK',
+      'weight' => -10,
+    );
+    $items['admin/config/content/diff/fields'] = array(
+      'title' => 'Fields',
+      'description' => 'Field support and settings overview.',
+      'file' => 'diff.admin.inc',
+      'page callback' => 'diff_admin_field_overview',
+      'access arguments' => array('administer site configuration'),
+      'type' => 'MENU_LOCAL_TASK',
+    );
+    $items['admin/config/content/diff/fields/%'] = array(
+      'title' => 'Global field settings',
+      'page callback' => 'drupal_get_form',
+      'page arguments' => array('diff_admin_global_field_settings', 5),
+      'access arguments' => array('administer site configuration'),
+      'type' => 'MENU_VISIBLE_IN_BREADCRUMB',
+      'file' => 'diff.admin.inc',
+    );
+    $items['admin/config/content/diff/entities'] = array(
+      'title' => 'Entities',
+      'description' => 'Entity settings.',
+      'file' => 'diff.admin.inc',
+      'page callback' => 'drupal_get_form',
+      'page arguments' => array('diff_admin_global_entity_settings', 'node'),
+      'access arguments' => array('administer site configuration'),
+      'type' => 'MENU_LOCAL_TASK',
+    );
+    $items['admin/config/content/diff/entities/node'] = array(
+      'title' => 'Nodes',
+      'description' => 'Node comparison settings.',
+      'type' => 'MENU_DEFAULT_LOCAL_TASK',
+      'weight' => -10,
+    );
+    $items['admin/config/content/diff/entities/user'] = array(
+      'title' => 'Users',
+      'description' => 'User diff settings.',
+      'file' => 'diff.admin.inc',
+      'page callback' => 'drupal_get_form',
+      'page arguments' => array('diff_admin_global_entity_settings', 'user'),
+      'access arguments' => array('administer site configuration'),
+      'type' => 'MENU_LOCAL_TASK',
+    );
+
+    return $items;
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/Drupal8/RouteWrapperTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/Drupal8/RouteWrapperTest.php
new file mode 100644 (file)
index 0000000..db74e58
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\drupalmoduleupgrader\Unit\Converter\Routing\Drupal8\RouteWrapperTest.
+ */
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Routing\Drupal8;
+
+use Drupal\drupalmoduleupgrader\Routing\Drupal8\RouteWrapper;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\Routing\Route;
+
+/**
+ * @group DMU.Routing
+ */
+class RouteWrapperTest extends UnitTestCase {
+
+  private $route, $wrapper;
+
+  public function __construct() {
+    $this->route = new Route('user/{user}/edit');
+    $this->wrapper = new RouteWrapper('user.edit', $this->route, $this->getMock('\Drupal\Core\Routing\RouteProviderInterface'));
+  }
+
+  public function testGetIdentifier() {
+    $this->assertEquals('user.edit', $this->wrapper->getIdentifier());
+  }
+
+  public function testGetPath() {
+    $this->assertInstanceOf('\Drupal\drupalmoduleupgrader\Utility\Path\Drupal8\PathUtility', $this->wrapper->getPath());
+    $this->assertEquals('/user/{user}/edit', $this->wrapper->getPath());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/LinkBinding/LinkBindingTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/LinkBinding/LinkBindingTest.php
new file mode 100644 (file)
index 0000000..7b97198
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\drupalmoduleupgrader\Unit\Converter\Routing\LinkBinding\LinkBindingTest.
+ */
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Routing\LinkBinding;
+
+use Drupal\drupalmoduleupgrader\Routing\Drupal7\RouteWrapper as Drupal7Route;
+use Drupal\drupalmoduleupgrader\Routing\Drupal8\RouteWrapper as Drupal8Route;
+use Drupal\drupalmoduleupgrader\Routing\LinkBinding\LinkBinding;
+use Drupal\drupalmoduleupgrader\Routing\LinkIndex;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\Routing\Route;
+
+/**
+ * @group DMU.Routing
+ */
+class LinkBindingTest extends UnitTestCase {
+
+  private $source, $destination;
+
+  public function __construct() {
+    $item = array(
+      'title' => 'Diff',
+      'description' => 'Diff settings.',
+      'file' => 'diff.admin.inc',
+      'page callback' => 'drupal_get_form',
+      'page arguments' => array('diff_admin_settings'),
+      'access arguments' => array('administer site configuration'),
+    );
+    $this->source = new Drupal7Route('admin/config/content/diff', $item);
+    $this->destination = new Drupal8Route('diff.settings', new Route('/admin/config/content/diff'), $this->getMock('\Drupal\Core\Routing\RouteProviderInterface'));
+  }
+
+  private function getMockBinding() {
+    return new LinkBinding($this->source, $this->destination);
+  }
+
+  public function testGetSource() {
+    $this->assertSame($this->source, $this->getMockBinding()->getSource());
+  }
+
+  public function testGetDestination() {
+    $this->assertSame($this->destination, $this->getMockBinding()->getDestination());
+  }
+
+  public function testGetIdentifier() {
+    $this->assertSame('diff.settings', $this->getMockBinding()->getIdentifier());
+  }
+
+  public function testOnIndexed() {
+    $binding = $this->getMockBinding();
+    $index = new LinkIndex();
+    $index->addBinding($binding);
+    $this->assertSame('diff.settings', $binding->getIdentifier());
+
+    // If a binding is added with the same identifier (which could easily happen,
+    // depending on how the binding computes its identifier), _0, _1, etc. should
+    // be appended to it by the index.
+    $clone = clone $binding;
+    $index->addBinding($clone);
+    $this->assertSame('diff.settings_0', $clone->getIdentifier());
+  }
+
+  public function testBuild() {
+    $link = $this->getMockBinding()->build();
+    $this->assertEquals('diff.settings', $link['route_name']);
+    $this->assertEquals('Diff', $link['title']);
+    $this->assertArrayNotHasKey('weight', $link);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/ParameterBindingTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/ParameterBindingTest.php
new file mode 100644 (file)
index 0000000..6d4d5bc
--- /dev/null
@@ -0,0 +1,117 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Routing;
+
+use Drupal\drupalmoduleupgrader\Routing\ParameterBinding;
+use Drupal\drupalmoduleupgrader\Utility\Path\Drupal7\PathUtility;
+use Drupal\Tests\UnitTestCase;
+use Pharborist\Functions\ParameterNode;
+use Pharborist\Types\StringNode;
+
+/**
+ * @group DMU.Routing
+ */
+class ParameterBindingTest extends UnitTestCase {
+
+  /**
+   * @var ParameterNode
+   */
+  private $parameter;
+
+  public function setUp() {
+    // ParameterNode supports variadic parameters, which use the T_ELLIPSIS
+    // token. Which will be undefined on any PHP older than 5.6. So this kludges
+    // around that.
+    if (! defined('T_ELLIPSIS')) {
+      define('T_ELLIPSIS', 0);
+    }
+
+    $this->parameter = ParameterNode::create('foo');
+  }
+
+  public function testGetParameter() {
+    $path = new PathUtility('foo/baz');
+    $binding = new ParameterBinding($path, $this->parameter);
+    $this->assertSame($this->parameter, $binding->getParameter());
+  }
+
+  public function testInPath() {
+    $path = new PathUtility('foo/baz');
+    $binding = new ParameterBinding($path, $this->parameter);
+    $this->assertFalse($binding->inPath());
+
+    $path = new PathUtility('foo/%node');
+    $binding = new ParameterBinding($path, $this->parameter, 1);
+    $this->assertTrue($binding->inPath());
+  }
+
+  public function testHasArgument() {
+    $path = new PathUtility('foo/baz');
+    $binding = new ParameterBinding($path, $this->parameter);
+    $this->assertFalse($binding->hasArgument());
+
+    $path = new PathUtility('foo/%node');
+    $binding = new ParameterBinding($path, $this->parameter, 1);
+    $this->assertTrue($binding->hasArgument());
+
+    $path = new PathUtility('foo/%');
+    $binding = new ParameterBinding($path, $this->parameter, 'baz');
+    $this->assertTrue($binding->hasArgument());
+  }
+
+  public function testGetArgument() {
+    $path = new PathUtility('foo/%node');
+    $binding = new ParameterBinding($path, $this->parameter, 1);
+    $this->assertSame(1, $binding->getArgument());
+
+    $path = new PathUtility('foo/%');
+    $binding = new ParameterBinding($path, $this->parameter, 'baz');
+    $this->assertEquals('baz', $binding->getArgument());
+  }
+
+  public function testIsPathPosition() {
+    $path = new PathUtility('foo/%node');
+    $binding = new ParameterBinding($path, $this->parameter, 1);
+    $this->assertTrue($binding->isPathPosition());
+
+    $path = new PathUtility('foo/%');
+    $binding = new ParameterBinding($path, $this->parameter, 'baz');
+    $this->assertFalse($binding->isPathPosition());
+  }
+
+  public function testGetValuePathPositionInPath() {
+    $path = new PathUtility('foo/%node');
+    $binding = new ParameterBinding($path, $this->parameter, 1);
+    $value = $binding->getValue();
+    $this->assertInstanceOf('\Drupal\drupalmoduleupgrader\Utility\Path\PathComponentInterface', $value);
+    $this->assertEquals('%node', $value);
+  }
+
+  public function testGetValuePathPositionNotInPath() {
+    $path = new PathUtility('foo/%node');
+    $binding = new ParameterBinding($path, $this->parameter, 2);
+    $value = $binding->getValue();
+    $this->assertInstanceOf('\Drupal\drupalmoduleupgrader\Utility\Path\PathComponentInterface', $value);
+    $this->assertEquals('%', $value);
+  }
+
+  public function testGetValueStaticArgument() {
+    $path = new PathUtility('foo/%node');
+    $binding = new ParameterBinding($path, $this->parameter, 'baz');
+    $this->assertEquals('baz', $binding->getValue());
+  }
+
+  public function testGetValueNoArgument() {
+    $this->parameter->setValue(StringNode::fromValue('har'));
+    $path = new PathUtility('foo/%node');
+    $binding = new ParameterBinding($path, $this->parameter);
+    $this->assertEquals('har', $binding->getValue());
+  }
+
+  public function testGetValueNoArgumentNoDefaultvalue() {
+    $path = new PathUtility('foo/%node');
+    $binding = new ParameterBinding($path, $this->parameter);
+    $this->assertNull($binding->getValue());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/RouterBaseTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/RouterBaseTest.php
new file mode 100644 (file)
index 0000000..ead1d4e
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\drupalmoduleupgrader\Unit\Converter\Routing\RouterBaseTest.
+ */
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Routing;
+
+use Drupal\drupalmoduleupgrader\Routing\Drupal8\RouteWrapper;
+use Drupal\drupalmoduleupgrader\Routing\RouterBase;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * @group DMU.Routing
+ */
+class RouterBaseTest extends UnitTestCase {
+
+  private $userEdit, $userView, $userRoot, $routeProvider;
+
+  public function __construct() {
+    $this->userEdit = new Route('/user/{user}/edit');
+    $this->userView = new Route('/user/{user}');
+    $this->userRoot = new Route('/user');
+
+    $route_collection = new RouteCollection();
+    $route_collection->add('user', $this->userRoot);
+
+    $this->routeProvider = $this->getMock('\Drupal\Core\Routing\RouteProviderInterface');
+    $this->routeProvider
+      ->expects($this->any())
+      ->method('getRoutesByPattern')
+      ->with('/user')
+      ->will($this->returnValue($route_collection));
+  }
+
+  public function testAddRoute() {
+    $router = new RouterBase();
+    $this->assertCount(0, $router);
+
+    $route = new RouteWrapper('user.edit', $this->userEdit, $this->routeProvider);
+    $router->addRoute($route);
+    $this->assertCount(1, $router);
+  }
+
+  /**
+   * @depends testAddRoute
+   */
+  public function testFinalize() {
+    $router = new RouterBase();
+
+    $user_edit = new RouteWrapper('user.edit', $this->userEdit, $this->routeProvider);
+    $router->addRoute($user_edit);
+
+    $user_view = new RouteWrapper('user.view', $this->userView, $this->routeProvider);
+    $router->addRoute($user_view);
+
+    $router->finalize();
+
+    $this->assertTrue($user_edit->hasParent());
+    $this->assertSame($user_view, $user_edit->getParent());
+    $this->assertTrue($user_view->hasParent());
+    $this->assertEquals('/user', $user_view->getParent()->getPath());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/SQLiteDatabaseTrait.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/SQLiteDatabaseTrait.php
new file mode 100644 (file)
index 0000000..4a9b692
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit;
+
+use Drupal\Core\Database\Driver\sqlite\Connection;
+
+/**
+ * A trait for tests that need a database.
+ */
+trait SQLiteDatabaseTrait {
+
+  /**
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $db;
+
+  protected function initDB() {
+    if (empty($this->db)) {
+      // In-memory databases will cease to exist as soon as the connection
+      // is closed, which is...convenient as hell!
+      $db = new \PDO('sqlite::memory:');
+      // Throw exceptions when things go awry.
+      $db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
+      $this->db = new Connection($db, []);
+    }
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/TargetTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/TargetTest.php
new file mode 100644 (file)
index 0000000..b48043b
--- /dev/null
@@ -0,0 +1,115 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit;
+
+use Drupal\drupalmoduleupgrader\Target;
+use Pharborist\NodeCollection;
+use Pharborist\Parser;
+
+/**
+ * @group DMU
+ */
+class TargetTest extends TestBase {
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\IndexerInterface
+   */
+  protected $indexer;
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->indexer = $this->getMockBuilder('\Drupal\drupalmoduleupgrader\Plugin\DMU\Indexer\Functions')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $this->container
+      ->get('plugin.manager.drupalmoduleupgrader.indexer')
+      ->method('createInstance')
+      ->with('function')
+      ->willReturn($this->indexer);
+  }
+
+  /**
+   * @expectedException \RuntimeException
+   */
+  public function testInvalidBasePath() {
+    // Trying to create a target with an invalid path should instantly
+    // throw an exception.
+    new Target('foobar', $this->container);
+  }
+
+  public function testID() {
+    $this->assertEquals('foo', $this->target->id());
+  }
+
+  public function testGetBasePath() {
+    $this->assertEquals($this->dir->url(), $this->target->getBasePath());
+  }
+
+  public function testGetPath() {
+    $this->assertEquals($this->dir->getChild('foo.module')->url(), $this->target->getPath('.module'));
+    $this->assertEquals($this->dir->getChild('foo.install')->url(), $this->target->getPath('.install'));
+  }
+
+  public function testGetFinder() {
+    $this->assertInstanceOf('\Symfony\Component\Finder\Finder', $this->target->getFinder());
+  }
+
+  /**
+   * @depends testGetFinder
+   */
+  public function testFinder() {
+    $expected = $this->target->getFinder()
+      ->exclude('menu_example')
+      ->name('*.module')
+      ->name('*.install')
+      ->name('*.inc')
+      ->name('*.test')
+      ->name('*.php');
+    $this->assertEquals(array_keys(iterator_to_array($expected)), array_keys(iterator_to_array($this->target->getFinder())));
+  }
+
+  public function testGetIndexer() {
+    $this->assertSame($this->indexer, $this->target->getIndexer('function'));
+  }
+
+  public function testGetServices() {
+    $this->assertInstanceOf('\Doctrine\Common\Collections\ArrayCollection', $this->target->getServices());
+  }
+
+  public function testImplementsHook() {
+    $this->indexer->method('has')->willReturnMap([
+      ['hook_permission', TRUE],
+      ['hook_menu_alter', FALSE],
+    ]);
+
+    $this->assertTrue($this->target->implementsHook('permission'));
+    $this->assertFalse($this->target->implementsHook('menu_alter'));
+  }
+
+  /**
+   * @expectedException \InvalidArgumentException
+   */
+  public function testExecuteUnimplementedHook() {
+    $this->indexer->method('has')->with('hook_menu')->willReturn(FALSE);
+    $this->target->executeHook('menu');
+  }
+
+  public function testExecuteHook() {
+    $expected = [
+      'foo/baz' => [
+        'title' => 'It worked!',
+      ],
+    ];
+
+    $this->indexer->method('has')->with('hook_menu')->willReturn(TRUE);
+    $this->indexer->method('hasExecutable')->with('hook_menu')->willReturn(TRUE);
+    $this->indexer->method('execute')->with('hook_menu')->willReturn($expected);
+
+    $actual = $this->target->executeHook('menu');
+    $this->assertInternalType('array', $actual);
+    $this->assertSame($expected, $actual);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/TestBase.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/TestBase.php
new file mode 100644 (file)
index 0000000..d3c324f
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit;
+
+use Drupal\drupalmoduleupgrader\Target;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Base class for all DMU tests, providing a useful environment:
+ *
+ * - A module called foo, mocked in memory using vfsStream. The actual
+ *   module files are empty and should be filled in by subclasses.
+ * - A TargetInterface instance for the foo module.
+ * - A Drupal database connection to an empty in-memory SQLite database.
+ * - A container with mocked string_translation and logger.factory services.
+ */
+abstract class TestBase extends UnitTestCase {
+
+  use ContainerMockTrait;
+  use SQLiteDatabaseTrait;
+  use ModuleMockerTrait;
+
+  /**
+   * The parsed annotations for the test class and method being executed.
+   *
+   * @var array
+   */
+  protected $info;
+
+  /**
+   * @var \org\bovigo\vfs\vfsStreamDirectory
+   */
+  protected $dir;
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\TargetInterface
+   */
+  protected $target;
+
+  /**
+   * Mocks an entire module, called foo, in a virtual file system.
+   */
+  public function setUp() {
+    $this->info = $this->getAnnotations();
+
+    $this->dir = $this->mockModule('foo');
+
+    $this->mockContainer();
+    $this->mockTranslator();
+    $this->mockLogger();
+    $this->initDB();
+
+    // At the time of this writing, Target will pull the indexer manager out
+    // of the container right away, so let's mock it.
+    $indexers = $this->getMock('\Drupal\Component\Plugin\PluginManagerInterface');
+    $this->container->set('plugin.manager.drupalmoduleupgrader.indexer', $indexers);
+
+    $this->target = new Target($this->dir->url(), $this->container);
+  }
+
+  /**
+   * Instantiates the plugin class covered by this test (as indicated by the
+   * @covers annotation). The plugin instance is given a randomly generated
+   * ID and description. Dependencies will be pulled from $this->container,
+   * so this should only be called once the mock container is ready.
+   *
+   * @param array $configuration
+   *  Additional configuration to pass to the instance.
+   * @param array $plugin_definition
+   *  Additional definition info to pass to the instance.
+   *
+   * @return object
+   *  A plugin instance.
+   */
+  protected function getPlugin(array $configuration = [], $plugin_definition = []) {
+    $plugin_definition['description'] = $this->getRandomGenerator()->sentences(4);
+
+    $class = $this->info['class']['covers'][0];
+    return $class::create($this->container, $configuration, $this->randomMachineName(), $plugin_definition);
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Filter/ContainsLogicFilterTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Filter/ContainsLogicFilterTest.php
new file mode 100644 (file)
index 0000000..44d32e9
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Utility\Filter;
+
+use Drupal\drupalmoduleupgrader\Utility\Filter\ContainsLogicFilter;
+use Drupal\Tests\UnitTestCase;
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Utility.Filter
+ */
+class ContainsLogicFilterTest extends UnitTestCase {
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\Utility\Filter\ContainsLogicFilter
+   */
+  protected $filter;
+
+  public function setUp() {
+    $this->filter = new ContainsLogicFilter();
+  }
+
+  public function testFunctionCallIsLogic() {
+    $this->assertTrue(Parser::parseSnippet('function foo() { bar(); }')->is($this->filter));
+  }
+
+  public function testWhiteListedFunctionCallIsNotLogic() {
+    $this->filter->whitelist('bar');
+    $this->assertFalse(Parser::parseSnippet('function foo() { bar(); }')->is($this->filter));
+  }
+
+  public function testIfIsLogic() {
+    $this->assertTrue(Parser::parseSnippet('function foo() { if (true) return TRUE; }')->is($this->filter));
+  }
+
+  public function testSwitchIsLogic() {
+    $function = <<<'END'
+function foo() {
+  switch ($baz) {
+    case 'a':
+    case 'b':
+    default:
+      break;
+  }
+}
+END;
+    $this->assertTrue(Parser::parseSnippet($function)->is($this->filter));
+  }
+
+  public function testClassMethodCallIsLogic() {
+    $this->assertTrue(Parser::parseSnippet('function foo() { return \Drupal::config(); }')->is($this->filter));
+  }
+
+  public function testObjectMethodCallIsLogic() {
+    $this->assertTrue(Parser::parseSnippet('function foo() { return \Drupal::config()->get("foo.settings"); }')->is($this->filter));
+  }
+
+  public function testObjectInstantiationIsLogic() {
+    $this->assertTrue(Parser::parseSnippet('function foo() { return new Entity(); }')->is($this->filter));
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Filter/FieldValueFilterTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Filter/FieldValueFilterTest.php
new file mode 100644 (file)
index 0000000..44e3cd8
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Utility\Filter;
+
+use Drupal\drupalmoduleupgrader\Utility\Filter\FieldValueFilter;
+use Drupal\Tests\UnitTestCase;
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Utility.Filter
+ */
+class FieldValueFilterTest extends UnitTestCase {
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\Utility\Filter\FieldValueFilter
+   */
+  protected $filter;
+
+  public function setUp() {
+    $this->filter = new FieldValueFilter('foo');
+  }
+
+  public function testFailIfNotArrayLookupNode() {
+    $this->assertFalse(Parser::parseExpression('$foo->baz')->is($this->filter));
+  }
+
+  public function testFailIfLookupRootIsNotObjectPropertyNode() {
+    $this->assertFalse(Parser::parseExpression('$foo["bar"]["baz"]')->is($this->filter));
+  }
+
+  public function testFailOnVariableNameMismatch() {
+    $this->assertFalse(Parser::parseExpression('$baz->foo["und"][0]["value"]')->is($this->filter));
+  }
+
+  public function testPass() {
+    $this->assertTrue(Parser::parseExpression('$foo->field_baz["und"][0]["value"]')->is($this->filter));
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Filter/FunctionCallArgumentFilterTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Filter/FunctionCallArgumentFilterTest.php
new file mode 100644 (file)
index 0000000..a967436
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Utility\Filter;
+
+use Drupal\drupalmoduleupgrader\Utility\Filter\FunctionCallArgumentFilter;
+use Drupal\Tests\UnitTestCase;
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Utility.Filter
+ */
+class FunctionCallArgumentFilterTest extends UnitTestCase {
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\Utility\Filter\FunctionCallArgumentFilter
+   */
+  protected $filter;
+
+  public function setUp() {
+    $this->filter = new FunctionCallArgumentFilter('foo');
+  }
+
+  public function testFailIfNotCallNode() {
+    $this->assertFalse(Parser::parseExpression('$foo[0]')->is($this->filter));
+  }
+
+  public function testFailIfCallNotHasArgument() {
+    $this->assertFalse(Parser::parseExpression('baz(0, "foo", bar())')->is($this->filter));
+  }
+
+  public function testFailIfVariableIsChild() {
+    $this->assertFalse(Parser::parseExpression('baz($foo[0])')->is($this->filter));
+  }
+
+  public function testPass() {
+    $this->assertTrue(Parser::parseExpression('baz($foo, 1, 2, "bar")')->is($this->filter));
+    $this->assertTrue(Parser::parseExpression('baz(1, 2, $foo, "bar")')->is($this->filter));
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Filter/NodeAssignmentFilterTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Filter/NodeAssignmentFilterTest.php
new file mode 100644 (file)
index 0000000..e5806d0
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Utility\Filter;
+
+use Drupal\drupalmoduleupgrader\Utility\Filter\NodeAssignmentFilter;
+use Drupal\Tests\UnitTestCase;
+use Pharborist\Parser;
+
+/**
+ * @group DMU.Utility.Filter
+ */
+class NodeAssignmentFilterTest extends UnitTestCase {
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\Utility\Filter\NodeAssignmentFilter
+   */
+  protected $filter;
+
+  public function setUp() {
+    $this->filter = new NodeAssignmentFilter();
+  }
+
+  public function testLeftOperand() {
+    /** @var \Pharborist\Operators\AssignNode $expr */
+    $expr = Parser::parseExpression('$foo = "bazzz"');
+    $this->assertTrue($expr->getLeftOperand()->is($this->filter));
+  }
+
+  public function testRightOperand() {
+    /** @var \Pharborist\Operators\AssignNode $expr */
+    $expr = Parser::parseExpression('$baz = $foo');
+    $this->assertFalse($expr->getRightOperand()->is($this->filter));
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Path/Drupal7/PathComponentTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Path/Drupal7/PathComponentTest.php
new file mode 100644 (file)
index 0000000..253b445
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Utility\Path\Drupal7;
+
+use Drupal\drupalmoduleupgrader\Utility\Path\Drupal7\PathComponent;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @group DMU.Utility.Path
+ */
+class PathComponentTest extends UnitTestCase {
+
+  public function testPathComponent() {
+    $placeholder = new PathComponent('%');
+    $this->assertTrue($placeholder->isPlaceholder());
+    $this->assertFalse($placeholder->isWildcard());
+    $this->assertEquals('%', $placeholder->__toString());
+
+    $wildcard = new PathComponent('%node');
+    $this->assertTrue($wildcard->isWildcard());
+    $this->assertFalse($wildcard->isPlaceholder());
+    $this->assertEquals('%node', $wildcard->__toString('%node'));
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Path/Drupal7/PathUtilityTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Path/Drupal7/PathUtilityTest.php
new file mode 100644 (file)
index 0000000..2f97110
--- /dev/null
@@ -0,0 +1,113 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\drupalmoduleupgrader\Unit\Utility\Path\Drupal7\PathUtilityTest.
+ */
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Utility\Path\Drupal7;
+
+use Drupal\drupalmoduleupgrader\Utility\Path\Drupal7\PathComponent;
+use Drupal\drupalmoduleupgrader\Utility\Path\Drupal7\PathUtility;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @group DMU.Utility.Path
+ */
+class PathUtilityTest extends UnitTestCase {
+
+  public function __construct() {
+    $this->path = new PathUtility('node/%node/foo/%');
+  }
+
+  public function testCount() {
+    $this->assertCount(4, $this->path);
+  }
+
+  public function testAdd() {
+    $path = clone $this->path;
+
+    $path->add('baz');
+    $this->assertCount(5, $path);
+    $this->assertInstanceOf('Drupal\\drupalmoduleupgrader\\Utility\\Path\\Drupal7\\PathComponent', $path->last());
+    $this->assertEquals('baz', $path->last()->__toString());
+
+    $path->add(new PathComponent('wambooli'));
+    $this->assertCount(6, $path);
+    $this->assertEquals('wambooli', $path->last()->__toString());
+  }
+
+  /**
+   * @expectedException \InvalidArgumentException
+   */
+  public function testAddArray() {
+    $this->path->add([]);
+  }
+
+  /**
+   * @expectedException \InvalidArgumentException
+   */
+  public function testAddObject() {
+    $this->path->add(new \StdClass());
+  }
+
+  public function testFind() {
+    $result = $this->path->find('foo');
+    $this->assertCount(1, $result);
+    $this->assertInstanceOf('Drupal\\drupalmoduleupgrader\\Utility\\Path\\Drupal7\\PathComponent', $result->first());
+    $this->assertEquals('foo', $result->first()->__toString());
+  }
+
+  public function testContains() {
+    $this->assertTrue($this->path->contains('%node'));
+    $this->assertFalse($this->path->contains('fruit'));
+  }
+
+  public function testHasWildcards() {
+    $this->assertTrue($this->path->hasWildcards());
+  }
+
+  public function testGetWildcards() {
+    $this->assertEquals('%node', $this->path->getWildcards()->__toString());
+  }
+
+  public function testGetNextWildcard() {
+    $wildcard = $this->path->getNextWildcard();
+    $this->assertInstanceOf('Drupal\\drupalmoduleupgrader\\Utility\\Path\\Drupal7\\PathComponent', $wildcard);
+    $this->assertEquals('%node', $wildcard->__toString());
+
+    $wildcard = $this->path->getNextWildcard();
+    $this->assertNull($wildcard);
+  }
+
+  public function testDeleteWildcards() {
+    $this->assertEquals('node/foo/%', $this->path->deleteWildcards()->__toString());
+  }
+
+  public function testGetParent() {
+    $this->assertEquals('node/%node/foo', $this->path->getParent()->__toString());
+  }
+
+  public function testIsDynamic() {
+    $this->assertTrue($this->path->isDynamic());
+  }
+
+  public function testHasPlaceholders() {
+    $this->assertTrue($this->path->hasPlaceholders());
+  }
+
+  public function testGetPlaceholders() {
+    $placeholders = $this->path->getPlaceholders();
+    $this->assertCount(1, $placeholders);
+    $this->assertInstanceOf('Drupal\\drupalmoduleupgrader\\Utility\\Path\\Drupal7\\PathComponent', $placeholders->first());
+    $this->assertEquals('%', $placeholders->first()->__toString());
+  }
+
+  /**
+   * @depends testHasPlaceholders
+   */
+  public function testDeletePlaceholders() {
+    $this->assertFalse($this->path->deletePlaceholders()->hasPlaceholders());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Path/Drupal8/PathComponentTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Path/Drupal8/PathComponentTest.php
new file mode 100644 (file)
index 0000000..9fba1ff
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Utility\Path\Drupal8;
+
+use Drupal\drupalmoduleupgrader\Utility\Path\Drupal8\PathComponent;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @group DMU.Utility.Path
+ */
+class PathComponentTest extends UnitTestCase {
+
+  public function testPathComponent() {
+    $wildcard = new PathComponent('{node}');
+    $this->assertTrue($wildcard->isWildcard());
+    $this->assertEquals('{node}', $wildcard->__toString());
+  }
+
+}
diff --git a/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Path/Drupal8/PathUtilityTest.php b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Path/Drupal8/PathUtilityTest.php
new file mode 100644 (file)
index 0000000..b3475cb
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\drupalmoduleupgrader\Unit\Utility\Path\Drupal8\PathUtilityTest.
+ */
+
+namespace Drupal\Tests\drupalmoduleupgrader\Unit\Utility\Path\Drupal8;
+
+use Drupal\drupalmoduleupgrader\Utility\Path\Drupal8\PathComponent;
+use Drupal\drupalmoduleupgrader\Utility\Path\Drupal8\PathUtility;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @group DMU.Utility.Path
+ */
+class PathUtilityTest extends UnitTestCase {
+
+  public function __construct() {
+    $this->path = new PathUtility('node/{node}/foo/{bar}');
+  }
+
+  public function testCount() {
+    $this->assertCount(4, $this->path);
+  }
+
+  public function testAdd() {
+    $path = clone $this->path;
+
+    $path->add('baz');
+    $this->assertCount(5, $path);
+    $this->assertInstanceOf('Drupal\\drupalmoduleupgrader\\Utility\\Path\\Drupal8\\PathComponent', $path->last());
+    $this->assertEquals('baz', $path->last()->__toString());
+
+    $path->add(new PathComponent('wambooli'));
+    $this->assertCount(6, $path);
+    $this->assertEquals('wambooli', $path->last()->__toString());
+  }
+
+  /**
+   * @expectedException \InvalidArgumentException
+   */
+  public function testAddArray() {
+    $this->path->add([]);
+  }
+
+  /**
+   * @expectedException \InvalidArgumentException
+   */
+  public function testAddObject() {
+    $this->path->add(new \StdClass());
+  }
+
+  public function testFind() {
+    $result = $this->path->find('foo');
+    $this->assertCount(1, $result);
+    $this->assertInstanceOf('Drupal\\drupalmoduleupgrader\\Utility\\Path\\Drupal8\\PathComponent', $result->first());
+    $this->assertEquals('foo', $result->first()->__toString());
+  }
+
+  public function testContains() {
+    $this->assertTrue($this->path->contains('{node}'));
+    $this->assertFalse($this->path->contains('fruit'));
+  }
+
+  public function testHasWildcards() {
+    $this->assertTrue($this->path->hasWildcards());
+  }
+
+  public function testGetWildcards() {
+    $this->assertEquals('{node}/{bar}', $this->path->getWildcards()->__toString());
+  }
+
+  public function testGetNextWildcard() {
+    $wildcard = $this->path->getNextWildcard();
+    $this->assertInstanceOf('Drupal\\drupalmoduleupgrader\\Utility\\Path\\Drupal8\\PathComponent', $wildcard);
+    $this->assertEquals('{node}', $wildcard->__toString());
+
+    $wildcard = $this->path->getNextWildcard();
+    $this->assertInstanceOf('Drupal\\drupalmoduleupgrader\\Utility\\Path\\Drupal8\\PathComponent', $wildcard);
+    $this->assertEquals('{bar}', $wildcard->__toString());
+
+    $wildcard = $this->path->getNextWildcard();
+    $this->assertNull($wildcard);
+  }
+
+  public function testDeleteWildcards() {
+    $this->assertEquals('node/foo', $this->path->deleteWildcards()->__toString());
+  }
+
+  public function testGetParent() {
+    $this->assertEquals('node/{node}/foo', $this->path->getParent()->__toString());
+  }
+
+}
diff --git a/web/modules/contrib/filefield_sources b/web/modules/contrib/filefield_sources
deleted file mode 160000 (submodule)
index b19c6a8..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit b19c6a839804f47587828d4a50e29e0720fa4c08
diff --git a/web/modules/contrib/filefield_sources/README.txt b/web/modules/contrib/filefield_sources/README.txt
new file mode 100644 (file)
index 0000000..c0dd03a
--- /dev/null
@@ -0,0 +1,43 @@
+-----------------
+FileField Sources
+-----------------
+
+Description
+-----------
+FileField Sources is a module that enhances the generic and image upload fields
+in Drupal. Typically such fields only allow you to upload a file from your
+desktop. FileField Sources makes it so that you can populate any file field
+from a variety of sources, such as entering remote URLs directly, re-use
+existing uploaded files, pull from a server directory, or a variety of other
+possibilities.
+
+This module built by Robots: http://www.lullabot.com
+Author: Nathan Haug (quicksketch)
+
+Requirements
+------------
+1) Drupal 7.36 or greater is required beginning in FileField Sources 7.x-1.10.
+
+Installation
+------------
+1) Place this module directory in your modules folder (this will usually be
+   "modules/").
+
+2) Enable the module within your Drupal site.
+
+3) Add or configure an existing file or image field. To configure a typical node
+   field, visit Manage -> Structure -> Content types and click
+   "Manage form display" on a type you'd like to modify. Add a new file field or
+   edit an existing one.
+
+   While editing the file or image field, you'll have new options available
+   under a "File sources" details. You can enable the desired sources for that
+   particular field.
+
+4) Create a piece of content that uses your file and try it out.
+
+Support
+-------
+Please file bug reports in the FileField Sources issue queue. Do not use the
+Drupal.org forums or send bug reports via e-mail.
+http://drupal.org/project/issues/filefield_sources?categories=All
diff --git a/web/modules/contrib/filefield_sources/config/schema/filefield_sources.data_types.schema.yml b/web/modules/contrib/filefield_sources/config/schema/filefield_sources.data_types.schema.yml
new file mode 100644 (file)
index 0000000..1d0e67d
--- /dev/null
@@ -0,0 +1,5 @@
+# Basic data types for FileField Sources.
+
+filefield_sources_source:
+  type: boolean
+  label: 'Plugin'
diff --git a/web/modules/contrib/filefield_sources/config/schema/filefield_sources.schema.yml b/web/modules/contrib/filefield_sources/config/schema/filefield_sources.schema.yml
new file mode 100644 (file)
index 0000000..a4bb0e7
--- /dev/null
@@ -0,0 +1,9 @@
+field.widget.third_party.filefield_sources:
+  type: mapping
+  label: 'filefield_sources entity form display settings'
+  mapping:
+    filefield_sources:
+      type: sequence
+      label: 'filefield_sources settings'
+      sequence:
+        - type: filefield_sources.setting.[%key]
diff --git a/web/modules/contrib/filefield_sources/config/schema/filefield_sources.setting.source_attach.schema.yml b/web/modules/contrib/filefield_sources/config/schema/filefield_sources.setting.source_attach.schema.yml
new file mode 100644 (file)
index 0000000..1616856
--- /dev/null
@@ -0,0 +1,13 @@
+filefield_sources.setting.source_attach:
+  type: mapping
+  label: 'File attach settings'
+  mapping:
+    path:
+      type: string
+      label: 'File attach path'
+    absolute:
+      type: integer
+      label: 'File attach location'
+    attach_mode:
+      type: string
+      label: 'Attach method'
diff --git a/web/modules/contrib/filefield_sources/config/schema/filefield_sources.setting.source_imce.schema.yml b/web/modules/contrib/filefield_sources/config/schema/filefield_sources.setting.source_imce.schema.yml
new file mode 100644 (file)
index 0000000..cd51b21
--- /dev/null
@@ -0,0 +1,7 @@
+filefield_sources.setting.source_imce:
+  type: mapping
+  label: 'IMCE file browser settings'
+  mapping:
+    imce_mode:
+      type: integer
+      label: 'File browser mode'
diff --git a/web/modules/contrib/filefield_sources/config/schema/filefield_sources.setting.source_reference.schema.yml b/web/modules/contrib/filefield_sources/config/schema/filefield_sources.setting.source_reference.schema.yml
new file mode 100644 (file)
index 0000000..444e9ac
--- /dev/null
@@ -0,0 +1,10 @@
+filefield_sources.setting.source_reference:
+  type: mapping
+  label: 'Autocomplete reference options'
+  mapping:
+    autocomplete:
+      type: string
+      label: 'Match file name'
+    search_all_fields:
+      type: string
+      label: 'Search all file fields'
diff --git a/web/modules/contrib/filefield_sources/config/schema/filefield_sources.setting.sources.schema.yml b/web/modules/contrib/filefield_sources/config/schema/filefield_sources.setting.sources.schema.yml
new file mode 100644 (file)
index 0000000..cd50a51
--- /dev/null
@@ -0,0 +1,5 @@
+filefield_sources.setting.sources:
+  type: sequence
+  label: 'filefield_sources source'
+  sequence:
+    - type: filefield_sources.source.[%key]
diff --git a/web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.attach.schema.yml b/web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.attach.schema.yml
new file mode 100644 (file)
index 0000000..ab7a53f
--- /dev/null
@@ -0,0 +1,3 @@
+filefield_sources.source.attach:
+  type: filefield_sources_source
+  label: 'File attach'
diff --git a/web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.clipboard.schema.yml b/web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.clipboard.schema.yml
new file mode 100644 (file)
index 0000000..e400679
--- /dev/null
@@ -0,0 +1,3 @@
+filefield_sources.source.clipboard:
+  type: filefield_sources_source
+  label: 'Clipboard'
diff --git a/web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.imce.schema.yml b/web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.imce.schema.yml
new file mode 100644 (file)
index 0000000..ec3b5a3
--- /dev/null
@@ -0,0 +1,3 @@
+filefield_sources.source.imce:
+  type: filefield_sources_source
+  label: 'File browser'
diff --git a/web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.reference.schema.yml b/web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.reference.schema.yml
new file mode 100644 (file)
index 0000000..4f77b13
--- /dev/null
@@ -0,0 +1,3 @@
+filefield_sources.source.reference:
+  type: filefield_sources_source
+  label: 'Reference existing'
diff --git a/web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.remote.schema.yml b/web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.remote.schema.yml
new file mode 100644 (file)
index 0000000..a26f8dc
--- /dev/null
@@ -0,0 +1,3 @@
+filefield_sources.source.remote:
+  type: filefield_sources_source
+  label: 'Remote URL'
diff --git a/web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.upload.schema.yml b/web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.upload.schema.yml
new file mode 100644 (file)
index 0000000..35208c7
--- /dev/null
@@ -0,0 +1,5 @@
+# Schema for the filefield_sources source plugins.
+
+filefield_sources.source.upload:
+  type: filefield_sources_source
+  label: 'Upload'
diff --git a/web/modules/contrib/filefield_sources/css/filefield_sources.css b/web/modules/contrib/filefield_sources/css/filefield_sources.css
new file mode 100644 (file)
index 0000000..4754fde
--- /dev/null
@@ -0,0 +1,44 @@
+/* Generic display for all sources. */
+
+div.filefield-source input.form-text,
+div.filefield-source select.form-select {
+  display: inline;
+  width: 20em;
+}
+
+div.filefield-source .form-item {
+  white-space: normal;
+}
+
+div.filefield-source .hint {
+  color: #999;
+}
+
+div.filefield-sources-list a.active {
+  font-weight: bold;
+}
+
+/* Clipboard source. */
+div.filefield-source-clipboard-capture {
+  border: 1px solid #ccc;
+  width: 20em;
+  height: 1.4em;
+  padding: 2px;
+  display: inline-block;
+  vertical-align: top;
+  overflow: hidden;
+}
+div.filefield-source-clipboard-capture img {
+  display: none;
+}
+
+/* Reference source. */
+div.filefield-source-reference-item {
+  font-size: 90%;
+}
+
+/* Remote source. */
+div.filefield-source-remote input.form-text {
+  /* Helps with display consistency since references has a background. */
+  background-image: inherit;
+}
diff --git a/web/modules/contrib/filefield_sources/filefield_sources.api.php b/web/modules/contrib/filefield_sources/filefield_sources.api.php
new file mode 100644 (file)
index 0000000..ecb9a1c
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+/**
+ * @file
+ * This file documents hooks provided by the FileField Sources module.
+ *
+ * Note that none of this code is executed by using FileField Sources module,
+ * it is provided here for reference as an example how to implement these hooks
+ * in your own module.
+ */
+
+/**
+ * Returns a list of widgets that are compatible with FileField Sources.
+ *
+ * FileField Sources works with the most common widgets used with Drupal (the
+ * standard Image and File widgets). Any module that provides another widget
+ * for uploading files may add compatibility with FileField Sources by
+ * implementing this hook and returning the widgets that their module supports.
+ */
+function hook_filefield_sources_widgets() {
+  // Add any widgets that your module supports here.
+  return array('mymodule_file_widgetname');
+}
+
+/**
+ * Allows altering the sources available on a field.
+ *
+ * This hook allows other modules to modify the sources available to a user.
+ *
+ * @param array $sources
+ *   List of filefiled sources plugins.
+ *
+ * @param mixed $context
+ *   Contains 'enabled_sources', 'element', 'form_state'.
+ */
+function hook_filefield_sources_sources_alter(&$sources, $context) {
+  // This example will exclude sources the user doesn't have access to.
+  foreach (array_keys($sources) as $type) {
+    if (!user_access("use $type filefield source")) {
+      unset($sources[$type]);
+    }
+  }
+}
diff --git a/web/modules/contrib/filefield_sources/filefield_sources.info.yml b/web/modules/contrib/filefield_sources/filefield_sources.info.yml
new file mode 100644 (file)
index 0000000..c9a52b5
--- /dev/null
@@ -0,0 +1,8 @@
+name: File Field Sources
+type: module
+description: 'Extends File fields to allow referencing of existing files, remote files, and server files.'
+package: Fields
+core: 8.x
+dependencies:
+  - file
+  - system
diff --git a/web/modules/contrib/filefield_sources/filefield_sources.install b/web/modules/contrib/filefield_sources/filefield_sources.install
new file mode 100644 (file)
index 0000000..8dfb84c
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @file
+ * Update and install functions for FileField Sources.
+ */
+
+/**
+ * Implements hook_install().
+ */
+function filefield_sources_install() {
+  // FileField Sources needs to load after both ImageField and FileField.
+  try {
+    $file_weight = module_get_weight('file');
+    $image_weight = module_get_weight('image');
+    $weight = max(array($file_weight, $image_weight));
+    $weight++;
+  }
+  catch (Exception $e) {
+    $weight = 5;
+  }
+  module_set_weight('filefield_sources', $weight);
+}
diff --git a/web/modules/contrib/filefield_sources/filefield_sources.libraries.yml b/web/modules/contrib/filefield_sources/filefield_sources.libraries.yml
new file mode 100644 (file)
index 0000000..7a0c7eb
--- /dev/null
@@ -0,0 +1,9 @@
+drupal.filefield_sources:
+  version: VERSION
+  js:
+    js/filefield_sources.js: {}
+  css:
+    theme:
+      css/filefield_sources.css: {}
+  dependencies:
+    - file/drupal.file
diff --git a/web/modules/contrib/filefield_sources/filefield_sources.module b/web/modules/contrib/filefield_sources/filefield_sources.module
new file mode 100644 (file)
index 0000000..5562a12
--- /dev/null
@@ -0,0 +1,771 @@
+<?php
+
+/**
+ * @file
+ * Extend FileField to allow files from multiple sources.
+ */
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Field\WidgetBase;
+use Drupal\Core\Field\WidgetInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Render\Element;
+use Drupal\imce\Imce;
+
+const FILEFIELD_SOURCE_ATTACH_DEFAULT_PATH = 'file_attach';
+const FILEFIELD_SOURCE_ATTACH_RELATIVE = 0;
+const FILEFIELD_SOURCE_ATTACH_ABSOLUTE = 1;
+const FILEFIELD_SOURCE_ATTACH_MODE_MOVE = 'move';
+const FILEFIELD_SOURCE_ATTACH_MODE_COPY = 'copy';
+
+const FILEFIELD_SOURCE_REFERENCE_HINT_TEXT = 'example.png [fid:123]';
+const FILEFIELD_SOURCE_REMOTE_HINT_TEXT = 'http://example.com/files/file.png';
+
+const FILEFIELD_SOURCE_REFERENCE_MATCH_STARTS_WITH = '0';
+const FILEFIELD_SOURCE_REFERENCE_MATCH_CONTAINS = '1';
+const FILEFIELD_SOURCE_REFERENCE_SEARCH_ALL_NO = '0';
+const FILEFIELD_SOURCE_REFERENCE_SEARCH_ALL_YES = '1';
+
+/**
+ * Implements hook_element_info_alter().
+ */
+function filefield_sources_element_info_alter(&$type) {
+  if (isset($type['managed_file'])) {
+    $type['managed_file']['#process'][] = 'filefield_sources_field_process';
+    $type['managed_file']['#pre_render'][] = 'filefield_sources_field_pre_render';
+    $type['managed_file']['#element_validate'][] = 'filefield_sources_field_validate';
+    $type['managed_file']['#file_value_callbacks'][] = 'filefield_sources_field_value';
+  }
+}
+
+/**
+ * Implements hook_theme().
+ */
+function filefield_sources_theme() {
+  $theme = array();
+
+  $theme['filefield_sources_element'] = array(
+    'render element' => 'element',
+    'function' => 'theme_filefield_sources_element',
+  );
+
+  $theme['filefield_sources_list'] = array(
+    'variables' => array('element' => NULL, 'sources' => NULL),
+    'function' => 'theme_filefield_sources_list',
+  );
+
+  return $theme;
+}
+
+/**
+ * Implements hook_field_widget_third_party_settings_form().
+ *
+ * Add file field sources settings form to supported field widget forms.
+ *
+ * @see \Drupal\field_ui\FormDisplayOverview
+ */
+function filefield_sources_field_widget_third_party_settings_form(WidgetInterface $plugin, FieldDefinitionInterface $field_definition, $form_mode, $form, FormStateInterface $form_state) {
+  $element = array();
+  if (in_array($plugin->getPluginId(), \Drupal::moduleHandler()->invokeAll('filefield_sources_widgets'))) {
+    $element = filefield_sources_form($plugin, $form_state);
+  }
+  return $element;
+}
+
+/**
+ * Implements hook_field_widget_settings_summary_alter().
+ *
+ * Add file field sources information to the field widget settings summary.
+ *
+ * @see \Drupal\field_ui\FormDisplayOverview
+ */
+function filefield_sources_field_widget_settings_summary_alter(&$summary, $context) {
+  $plugin = $context['widget'];
+  if (in_array($plugin->getPluginId(), \Drupal::moduleHandler()->invokeAll('filefield_sources_widgets'))) {
+    $settings = $plugin->getThirdPartySetting('filefield_sources', 'filefield_sources');
+    $enabled_sources = _filefield_sources_enabled($settings);
+    $summary[] = t('File field sources:') . ' ' . implode(', ', array_keys($enabled_sources));
+  }
+}
+
+/**
+ * Implements hook_field_widget_form_alter().
+ *
+ * Add file field sources widget's settings to element.
+ */
+function filefield_sources_field_widget_form_alter(&$element, FormStateInterface $form_state, $context) {
+  $plugin = $context['widget'];
+  if (in_array($plugin->getPluginId(), \Drupal::moduleHandler()->invokeAll('filefield_sources_widgets'))) {
+    $element['#filefield_sources_settings'] = $plugin->getThirdPartySetting('filefield_sources', 'filefield_sources');
+
+    // Bundle is missing in element.
+    $items = $context['items'];
+    $element['#bundle'] = $items->getEntity()->bundle();
+  }
+}
+
+/**
+ * Implements hook_filefield_sources_widgets().
+ *
+ * This returns a list of widgets that are compatible with FileField Sources.
+ */
+function filefield_sources_filefield_sources_widgets() {
+  return array('file_generic', 'image_image');
+}
+
+/**
+ * Configuration form for editing FileField Sources settings for a widget.
+ */
+function filefield_sources_form($plugin, FormStateInterface $form_state) {
+  $settings = $plugin->getThirdPartySetting('filefield_sources', 'filefield_sources');
+
+  // Backward compatibility: auto-enable 'upload'.
+  $enabled = _filefield_sources_enabled($settings);
+
+  $form['filefield_sources'] = array(
+    '#type' => 'details',
+    '#title' => t('File sources'),
+    '#weight' => 20,
+  );
+
+  $sources = filefield_sources_list();
+  $form['filefield_sources']['sources'] = array(
+    '#type' => 'checkboxes',
+    '#title' => t('Enabled sources'),
+    '#options' => $sources,
+    '#default_value' => $enabled,
+    '#description' => t('Select the available locations from which this widget may select files.'),
+  );
+
+  $params = array($plugin);
+  $form['filefield_sources'] = array_merge($form['filefield_sources'], filefield_sources_invoke_all('settings', $params));
+
+  return $form;
+}
+
+/**
+ * A #process callback to extend the filefield_widget element type.
+ *
+ * Add the central JavaScript and CSS files that allow switching between
+ * different sources. Third-party modules can also add to the list of sources
+ * by implementing hook_filefield_sources_info().
+ */
+function filefield_sources_field_process(&$element, FormStateInterface $form_state, &$complete_form) {
+  static $js_added;
+
+  // Check if we are processing file field sources.
+  if (!isset($element['#filefield_sources_settings'])) {
+    return $element;
+  }
+
+  // Do all processing as needed by each source.
+  $sources = filefield_sources_info();
+  $settings = $element['#filefield_sources_settings'];
+  $enabled_sources = _filefield_sources_enabled($settings);
+
+  $context = array(
+    'enabled_sources' => &$enabled_sources,
+    'element'         => $element,
+    'form_state'      => $form_state,
+  );
+
+  // Allow other modules to alter the sources.
+  \Drupal::moduleHandler()->alter('filefield_sources_sources', $sources, $context);
+
+  foreach ($sources as $source_name => $source) {
+    if (empty($enabled_sources[$source_name])) {
+      unset($sources[$source_name]);
+    }
+    // Default upload plugin does not have class.
+    elseif (isset($source['class'])) {
+      $callback = array($source['class'], 'process');
+      if (is_callable($callback)) {
+        $element = call_user_func_array($callback, array(
+          &$element,
+          $form_state,
+          &$complete_form,
+        ));
+      }
+    }
+  }
+  $element['#filefield_sources'] = $sources;
+
+  // Exit out if not adding any sources.
+  if (empty($sources)) {
+    return $element;
+  }
+
+  // Hide default 'upload' type?
+  if (!isset($enabled_sources['upload'])) {
+    foreach (array('upload_button', 'upload') as $field) {
+      if (isset($element[$field])) {
+        $element[$field]['#access'] = FALSE;
+      }
+    }
+  }
+
+  // Add class to upload button.
+  $element['upload_button']['#attributes']['class'][] = 'upload-button';
+
+  $element['#attached']['library'][] = 'filefield_sources/drupal.filefield_sources';
+
+  // Check the element for hint text that might need to be added.
+  foreach (Element::children($element) as $key) {
+    if (isset($element[$key]['#filefield_sources_hint_text']) && !isset($js_added[$key])) {
+      $type = str_replace('filefield_', '', $key);
+
+      $element['#attached']['drupalSettings']['fileFieldSources'][$type] = array(
+        'hintText' => $element[$key]['#filefield_sources_hint_text'],
+      );
+
+      $js_added[$key] = TRUE;
+    }
+  }
+
+  // Adjust the Ajax settings so that on upload and remove of any individual
+  // file, the entire group of file fields is updated together.
+  // Clone of Drupal\file\Plugin\Field\FieldWidget\FileWidget::process().
+  if ($element['#cardinality'] != 1) {
+    $parents = array_slice($element['#array_parents'], 0, -1);
+    $new_options = array(
+      'query' => array(
+        'element_parents' => implode('/', $parents),
+      ),
+    );
+    $field_element = NestedArray::getValue($complete_form, $parents);
+    $new_wrapper = $field_element['#id'] . '-ajax-wrapper';
+    foreach (Element::children($element) as $key) {
+      foreach (Element::children($element[$key]) as $subkey) {
+        if (isset($element[$key][$subkey]['#ajax'])) {
+          $element[$key][$subkey]['#ajax']['options'] = $new_options;
+          $element[$key][$subkey]['#ajax']['wrapper'] = $new_wrapper;
+          $element[$key][$subkey]['#limit_validation_errors'] = array(
+            array_slice($element['#array_parents'], 0, -2),
+          );
+        }
+      }
+    }
+    unset($element['#prefix'], $element['#suffix']);
+  }
+
+  // Add the list of sources to the element for toggling between sources.
+  if (empty($element['fids']['#value'])) {
+    if (count($enabled_sources) > 1) {
+      $element['filefield_sources_list'] = array(
+        '#theme' => 'filefield_sources_list',
+        '#element' => $element,
+        '#sources' => $sources,
+        '#weight' => -20,
+      );
+    }
+  }
+
+  return $element;
+}
+
+/**
+ * A #pre_render function to hide sources if a file is currently uploaded.
+ */
+function filefield_sources_field_pre_render($element) {
+  // If we already have a file, we don't want to show the upload controls.
+  if (!empty($element['#value']['fids'])) {
+    foreach (Element::children($element) as $key) {
+      if (!empty($element[$key]['#filefield_source'])) {
+        $element[$key]['#access'] = FALSE;
+      }
+    }
+  }
+  return $element;
+}
+
+/**
+ * An #element_validate function to run source validations.
+ */
+function filefield_sources_field_validate(&$element, FormStateInterface $form_state, &$complete_form) {
+  // Do all processing as needed by each source.
+  $sources = filefield_sources_info();
+  foreach ($sources as $source) {
+    if (!isset($source['class'])) {
+      continue;
+    }
+
+    $callback = array($source['class'], 'validate');
+    if (is_callable($callback)) {
+      call_user_func_array($callback, array(
+        $element,
+        $form_state,
+        $complete_form,
+      ));
+    }
+  }
+}
+
+/**
+ * Form submission handler for all FileField Source buttons.
+ *
+ * Clone of \Drupal\file\Plugin\Field\FieldWidget\FileWidget::submit(), with
+ * a few changes:
+ *   - Submit button is one level down compare to 'Upload' source's submit
+ *     button.
+ *   - Replace static in static::getWidgetState and static::setWidgetState by
+ *     WidgetBase.
+ *   - Rebuild the form after all.
+ */
+function filefield_sources_field_submit(&$form, FormStateInterface $form_state) {
+  // During the form rebuild, formElement() will create field item widget
+  // elements using re-indexed deltas, so clear out FormState::$input to
+  // avoid a mismatch between old and new deltas. The rebuilt elements will
+  // have #default_value set appropriately for the current state of the field,
+  // so nothing is lost in doing this.
+  $button = $form_state->getTriggeringElement();
+  $parents = array_slice($button['#parents'], 0, -3);
+  NestedArray::setValue($form_state->getUserInput(), $parents, NULL);
+
+  // Go one level up in the form, to the widgets container.
+  $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -2));
+  $field_name = $element['#field_name'];
+  $parents = $element['#field_parents'];
+
+  $submitted_values = NestedArray::getValue($form_state->getValues(), array_slice($button['#parents'], 0, -3));
+  foreach ($submitted_values as $delta => $submitted_value) {
+    if (empty($submitted_value['fids'])) {
+      unset($submitted_values[$delta]);
+    }
+  }
+
+  // If there are more files uploaded via the same widget, we have to separate
+  // them, as we display each file in it's own widget.
+  $new_values = array();
+  foreach ($submitted_values as $delta => $submitted_value) {
+    if (is_array($submitted_value['fids'])) {
+      foreach ($submitted_value['fids'] as $fid) {
+        $new_value = $submitted_value;
+        $new_value['fids'] = array($fid);
+        $new_values[] = $new_value;
+      }
+    }
+    else {
+      $new_value = $submitted_value;
+    }
+  }
+
+  // Re-index deltas after removing empty items.
+  $submitted_values = array_values($new_values);
+
+  // Update form_state values.
+  NestedArray::setValue($form_state->getValues(), array_slice($button['#parents'], 0, -3), $submitted_values);
+
+  // Update items.
+  $field_state = WidgetBase::getWidgetState($parents, $field_name, $form_state);
+  $field_state['items'] = $submitted_values;
+  WidgetBase::setWidgetState($parents, $field_name, $form_state, $field_state);
+
+  // We need to rebuild the form, so that uploaded file can be displayed.
+  $form_state->setRebuild();
+}
+
+/**
+ * A #filefield_value_callback to run source value callbacks.
+ */
+function filefield_sources_field_value(&$element, &$input, FormStateInterface $form_state) {
+  // Do all processing as needed by each source.
+  $sources = filefield_sources_info();
+  foreach ($sources as $source) {
+    if (isset($source['class'])) {
+      $callback = array($source['class'], 'value');
+      if (is_callable($callback)) {
+        call_user_func_array($callback, array(&$element, &$input, $form_state));
+      }
+    }
+  }
+}
+
+/**
+ * Call all FileField Source hooks stored in the available include files.
+ */
+function filefield_sources_invoke_all($method, &$params) {
+  $return = array();
+  foreach (\Drupal::service('filefield_sources')->getDefinitions() as $definition) {
+    if (!isset($definition['class'])) {
+      continue;
+    }
+    // Get routes defined by each plugin.
+    $callback = array($definition['class'], $method);
+    if (is_callable($callback)) {
+      $result = call_user_func_array($callback, $params);
+      if (isset($result) && is_array($result)) {
+        $return = array_merge_recursive($return, $result);
+      }
+      elseif (isset($result)) {
+        $return[] = $result;
+      }
+    }
+  }
+  return $return;
+}
+
+/**
+ * Load hook_filefield_sources_info() data from all modules.
+ */
+function filefield_sources_info($include_default = TRUE) {
+  $info = \Drupal::service('filefield_sources')->getDefinitions();
+  if (isset($info['imce']) && !Imce::access()) {
+    unset($info['imce']);
+  }
+  if ($include_default) {
+    $info['upload'] = array(
+      'name' => t('Upload (default)'),
+      'label' => t('Upload'),
+      'description' => t('Upload a file from your computer.'),
+      'weight' => -10,
+    );
+  }
+
+  uasort($info, '_filefield_sources_sort');
+
+  return $info;
+}
+
+/**
+ * Create a list of FileField Sources by name, suitable for a select list.
+ */
+function filefield_sources_list($include_default = TRUE) {
+  $info = filefield_sources_info($include_default);
+  $list = array();
+
+  foreach ($info as $key => $source) {
+    $list[$key] = $source['name'];
+  }
+
+  return $list;
+}
+
+/**
+ * Save a file into the database after validating it.
+ *
+ * This function is identical to the core function file_save_upload() except
+ * that it accepts an input file path instead of an input file source name.
+ *
+ * @see file_save_upload()
+ */
+function filefield_sources_save_file($filepath, $validators = array(), $destination = FALSE, $replace = FILE_EXISTS_RENAME) {
+  $user = \Drupal::currentUser();
+
+  // Begin building file object.
+  $file = entity_create('file', array(
+    'uri' => $filepath,
+    'uid' => $user->id(),
+    'status' => FILE_EXISTS_RENAME,
+  ));
+  $file->setFilename(trim(basename($filepath), '.'));
+  $file->setMimeType(\Drupal::service('file.mime_type.guesser')->guess($file->getFilename()));
+  $file->setSize(filesize($filepath));
+
+  $extensions = '';
+  if (isset($validators['file_validate_extensions'])) {
+    if (isset($validators['file_validate_extensions'][0])) {
+      // Build the list of non-munged extensions if the caller provided them.
+      $extensions = $validators['file_validate_extensions'][0];
+    }
+    else {
+      // If 'file_validate_extensions' is set and the list is empty then the
+      // caller wants to allow any extension. In this case we have to remove the
+      // validator or else it will reject all extensions.
+      unset($validators['file_validate_extensions']);
+    }
+  }
+  else {
+    // No validator was provided, so add one using the default list.
+    // Build a default non-munged safe list for file_munge_filename().
+    $extensions = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp';
+    $validators['file_validate_extensions'] = array();
+    $validators['file_validate_extensions'][0] = $extensions;
+  }
+
+  if (!empty($extensions)) {
+    // Munge the filename to protect against possible malicious extension hiding
+    // within an unknown file type (ie: filename.html.foo).
+    $file->setFilename(file_munge_filename($file->getFilename(), $extensions));
+  }
+
+  // Rename potentially executable files, to help prevent exploits (i.e. will
+  // rename filename.php.foo and filename.php to filename.php.foo.txt and
+  // filename.php.txt, respectively). Don't rename if 'allow_insecure_uploads'
+  // evaluates to TRUE.
+  if (!\Drupal::config('system.file')->get('allow_insecure_uploads') && preg_match('/\.(php|pl|py|cgi|asp|js)(\.|$)/i', $file->getFilename()) && (substr($file->getFilename(), -4) != '.txt')) {
+    $file->setMimeType('text/plain');
+    $file->setFileUri($file->getFileUri() . '.txt');
+    $file->setFilename($file->getFilename() . '.txt');
+    // The .txt extension may not be in the allowed list of extensions. We have
+    // to add it here or else the file upload will fail.
+    if (!empty($extensions)) {
+      $validators['file_validate_extensions'][0] .= ' txt';
+      drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $file->getFilename())));
+    }
+  }
+
+  // If the destination is not provided, use the temporary directory.
+  if (empty($destination)) {
+    $destination = 'temporary://';
+  }
+
+  // Assert that the destination contains a valid stream.
+  $destination_scheme = file_uri_scheme($destination);
+  if (!$destination_scheme || !file_stream_wrapper_valid_scheme($destination_scheme)) {
+    drupal_set_message(t('The file could not be uploaded, because the destination %destination is invalid.', array('%destination' => $destination)), 'error');
+    return FALSE;
+  }
+
+  // A URI may already have a trailing slash or look like "public://".
+  if (substr($destination, -1) != '/') {
+    $destination .= '/';
+  }
+
+  // Ensure the destination is writable.
+  file_prepare_directory($destination, FILE_CREATE_DIRECTORY);
+
+  // Check if this is actually the same file being "attached" to a file record.
+  // If so, it acts as a file replace, except no file is actually moved.
+  $reuse_file = ($destination . $file->getFilename() === $file->getFileUri());
+  if ($reuse_file) {
+    $replace = FILE_EXISTS_REPLACE;
+  }
+
+  $file->destination = file_destination($destination . $file->getFilename(), $replace);
+  // If file_destination() returns FALSE then $replace == FILE_EXISTS_ERROR and
+  // there's an existing file so we need to bail.
+  if ($file->destination === FALSE) {
+    drupal_set_message(t('The file %source could not be uploaded because a file by that name already exists in the destination %directory.', array('%source' => $file->getFilename(), '%directory' => $destination)), 'error');
+    return FALSE;
+  }
+
+  // Add in our check of the the file name length.
+  $validators['file_validate_name_length'] = array();
+
+  // Call the validation functions specified by this function's caller.
+  $errors = file_validate($file, $validators);
+
+  // Check for errors.
+  if (!empty($errors)) {
+    $message = t('The specified file %name could not be uploaded.', array('%name' => $file->getFilename()));
+    if (count($errors) > 1) {
+      $message .= theme('item_list', array('items' => $errors));
+    }
+    else {
+      $message .= ' ' . array_pop($errors);
+    }
+    drupal_set_message($message, 'error');
+    return FALSE;
+  }
+
+  // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary
+  // directory. This overcomes open_basedir restrictions for future file
+  // operations.
+  $file->setFileUri($file->destination);
+  if (!$reuse_file && !file_unmanaged_copy($filepath, $file->getFileUri(), $replace)) {
+    drupal_set_message(t('File upload error. Could not move uploaded file.'), 'error');
+    \Drupal::logger('filefield_sources')->log(E_NOTICE, 'Upload error. Could not move uploaded file %file to destination %destination.', array('%file' => $file->getFilename(), '%destination' => $file->getFileUri()));
+    return FALSE;
+  }
+
+  // Set the permissions on the new file.
+  drupal_chmod($file->getFileUri());
+
+  // If we are replacing an existing file re-use its database record.
+  if ($replace == FILE_EXISTS_REPLACE) {
+    $existing_files = file_load_multiple(array(), array('uri' => $file->getFileUri()));
+    if (count($existing_files)) {
+      $existing = reset($existing_files);
+      $file->setOriginalId($existing->id());
+    }
+  }
+
+  // If we made it this far it's safe to record this file in the database.
+  $file->save();
+  return $file;
+}
+
+/**
+ * Clean up the file name, munging extensions and transliterating.
+ *
+ * @param string $filepath
+ *   A string containing a file name or full path. Only the file name will
+ *   actually be modified.
+ *
+ * @return string
+ *   A file path with a cleaned-up file name.
+ */
+function filefield_sources_clean_filename($filepath, $extensions) {
+  $filename = basename($filepath);
+
+  if (\Drupal::moduleHandler()->moduleExists('transliteration')) {
+    module_load_include('inc', 'transliteration');
+
+    $langcode = NULL;
+    if (!empty($_POST['language'])) {
+      $languages = language_list();
+      $langcode = isset($languages[$_POST['language']]) ? $_POST['language'] : NULL;
+    }
+    $filename = transliteration_clean_filename($filename, $langcode);
+  }
+
+  // Because this transfer mechanism does not use file_save_upload(), we need
+  // to manually munge the filename to prevent dangerous extensions.
+  // See file_save_upload().
+  if (empty($extensions)) {
+    $extensions = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp';
+  }
+  $filename = file_munge_filename($filename, $extensions);
+  $directory = drupal_dirname($filepath);
+  return ($directory != '.' ? $directory . '/' : '') . $filename;
+}
+
+/**
+ * Theme the display of the source element.
+ */
+function theme_filefield_sources_element($variables) {
+  $element = $variables['element'];
+  $source_id = $element['#source_id'];
+  $method = isset($element['#method']) ? $element['#method'] : 'element';
+  $extra_variables = isset($element['#variables']) ? $element['#variables'] : array();
+
+  $sources = filefield_sources_info();
+  if (isset($sources[$source_id]['class'])) {
+    $callback = array($sources[$source_id]['class'], $method);
+    if (is_callable($callback)) {
+      $variables = array_merge($variables, $extra_variables);
+      return call_user_func_array($callback, array($variables));
+    }
+  }
+
+  return '';
+}
+
+/**
+ * Theme the display of the sources list.
+ */
+function theme_filefield_sources_list($variables) {
+  $element = $variables['element'];
+  $sources = $variables['sources'];
+
+  $links = array();
+
+  foreach ($sources as $name => $source) {
+    $links[] = '<a href="#" onclick="return false;" title="' . $source['description'] . '" id="' . $element['#id'] . '-' . $name . '-source" class="filefield-source filefield-source-' . $name . '">' . $source['label'] . '</a>';
+  }
+  return '<div class="filefield-sources-list">' . implode(' | ', $links) . '</div>';
+}
+
+/**
+ * Validate a file based on the $element['#upload_validators'] property.
+ */
+function filefield_sources_element_validate($element, $file, FormStateInterface $form_state) {
+  $validators = $element['#upload_validators'];
+  $errors = array();
+
+  // Since this frequently is used to reference existing files, check that
+  // they exist first in addition to the normal validations.
+  if (!file_exists($file->getFileUri())) {
+    $errors[] = t('The file does not exist.');
+  }
+  // Call the validation functions.
+  else {
+    foreach ($validators as $function => $args) {
+      // Add the $file variable to the list of arguments and pass it by
+      // reference (required for PHP 5.3 and higher).
+      array_unshift($args, NULL);
+      $args[0] = &$file;
+      $errors = array_merge($errors, call_user_func_array($function, $args));
+    }
+  }
+
+  // Check for validation errors.
+  if (!empty($errors)) {
+    $message = t('The selected file %name could not be referenced.', array('%name' => $file->filename));
+    if (count($errors) > 1) {
+      $message .= '<ul><li>' . implode('</li><li>', $errors) . '</li></ul>';
+    }
+    else {
+      $message .= ' ' . array_pop($errors);
+    }
+    $form_state->setError($element, $message);
+    return 0;
+  }
+
+  return 1;
+}
+
+/**
+ * Generate help text based on the $element['#upload_validators'] property.
+ */
+function filefield_sources_element_validation_help($validators) {
+  $desc = array();
+  foreach ($validators as $callback => $arguments) {
+    $help_func = $callback . '_help';
+    if (function_exists($help_func)) {
+      $desc[] = call_user_func_array($help_func, $arguments);
+    }
+  }
+  return empty($desc) ? '' : implode('<br />', $desc);
+}
+
+/**
+ * Custom sort function for ordering sources.
+ */
+function _filefield_sources_sort($a, $b) {
+  $a = (array) $a + array('weight' => 0, 'label' => '');
+  $b = (array) $b + array('weight' => 0, 'label' => '');
+  return $a['weight'] < $b['weight'] ? -1 : ($a['weight'] > $b['weight'] ? 1 : strnatcasecmp($a['label'], $b['label']));
+}
+
+/**
+ * Helper to return enabled sources for a field.
+ *
+ * This provides backward compatibility for 'upload' type.
+ *
+ * @see http://drupal.org/node/932994
+ */
+function _filefield_sources_enabled($settings) {
+  if (!isset($settings['sources']['upload'])) {
+    $settings['sources']['upload'] = 'upload';
+  }
+
+  $enabled = array_keys(array_filter($settings['sources']));
+  asort($enabled);
+  return array_combine($enabled, $enabled);
+}
+
+// @todo Remove once https://www.drupal.org/node/1808132 is finished.
+if (!function_exists('module_get_weight')) {
+  /**
+   * Gets weight of a particular module.
+   *
+   * @param string $module
+   *   The name of the module (without the .module extension).
+   *
+   * @return int
+   *   The configured weight of the module.
+   *
+   * @throws InvalidArgumentException
+   *   Thrown in case the given module is not installed in the system.
+   */
+  function module_get_weight($module) {
+    $weight = \Drupal::config('core.extension')->get("module.$module");
+    if ($weight !== NULL) {
+      return (int) $weight;
+    }
+    $weight = \Drupal::config('core.extension')->get("disabled.module.$module");
+    if ($weight !== NULL) {
+      return (int) $weight;
+    }
+    throw new InvalidArgumentException(format_string('The module %module is not installed.', array('%module' => $module)));
+  }
+}
+
+/**
+ * Check for CURL extension enabled.
+ */
+function filefield_sources_curl_enabled() {
+  return function_exists('curl_version');
+}
diff --git a/web/modules/contrib/filefield_sources/filefield_sources.routing.yml b/web/modules/contrib/filefield_sources/filefield_sources.routing.yml
new file mode 100644 (file)
index 0000000..002d37c
--- /dev/null
@@ -0,0 +1,2 @@
+route_callbacks:
+  - '\Drupal\filefield_sources\Routing\FilefieldSourcesRoutes::routes'
diff --git a/web/modules/contrib/filefield_sources/filefield_sources.services.yml b/web/modules/contrib/filefield_sources/filefield_sources.services.yml
new file mode 100644 (file)
index 0000000..354207b
--- /dev/null
@@ -0,0 +1,12 @@
+services:
+  plugin.manager.filefield_sources:
+    class: Drupal\filefield_sources\FilefieldSourceManager
+    parent: default_plugin_manager
+  filefield_sources:
+    alias: plugin.manager.filefield_sources
+  access_check.filefield_sources.field:
+    class: Drupal\filefield_sources\Access\FieldAccessCheck
+    tags:
+      - { name: access_check, applies_to: _access_filefield_sources_field }
+  filefield_sources.imce_scanner:
+    class: Drupal\filefield_sources\ImceScanner
diff --git a/web/modules/contrib/filefield_sources/js/filefield_sources.js b/web/modules/contrib/filefield_sources/js/filefield_sources.js
new file mode 100644 (file)
index 0000000..1f66a37
--- /dev/null
@@ -0,0 +1,262 @@
+/**
+ * @file
+ * Defines Javascript behaviors for the filefield_sources module.
+ */
+
+(function ($, Drupal) {
+
+"use strict";
+
+// Behavior to add source options to configured fields.
+Drupal.behaviors.fileFieldSources = {};
+Drupal.behaviors.fileFieldSources.attach = function(context, settings) {
+  $('div.filefield-sources-list:not(.filefield-sources-processed)', context).each(function() {
+    $(this).addClass('filefield-sources-processed');
+    var $fileFieldElement = $(this).parents('div.form-managed-file:first');
+    $(this).find('a').click(function() {
+      // Remove the active class.
+      $(this).parents('div.filefield-sources-list').find('a.active').removeClass('active');
+
+      // Find the unique FileField Source class name.
+      var fileFieldSourceClass = this.className.match(/filefield-source-[0-9a-z_]+/i)[0];
+
+      // The default upload element is a special case.
+      if ($(this).is('.filefield-source-upload')) {
+        $fileFieldElement.find('div.filefield-sources-list').siblings('.form-file, .form-submit').css('display', '');
+        $fileFieldElement.find('div.filefield-source').css('display', 'none');
+      }
+      else {
+        $fileFieldElement.find('div.filefield-sources-list').siblings('.form-file, .form-submit').css('display', 'none');
+        $fileFieldElement.find('div.filefield-source').not('div.' + fileFieldSourceClass).css('display', 'none');
+        $fileFieldElement.find('div.' + fileFieldSourceClass).css('display', '');
+      }
+
+      // Add the active class.
+      $(this).addClass('active');
+      Drupal.fileFieldSources.updateHintText($fileFieldElement.get(0));
+    }).first().triggerHandler('click');
+
+    // Clipboard support.
+    $fileFieldElement.find('.filefield-source-clipboard-capture')
+      .bind('paste', Drupal.fileFieldSources.pasteEvent)
+      .bind('focus', Drupal.fileFieldSources.pasteFocus)
+      .bind('blur', Drupal.fileFieldSources.pasteBlur);
+
+    // Imce support.
+    $fileFieldElement.find('.filefield-sources-imce-browse')
+      .bind('click', Drupal.fileFieldSources.imceBrowse);
+  });
+
+  if (context === document) {
+    $('form').submit(function() {
+      Drupal.fileFieldSources.removeHintText();
+    });
+  }
+};
+
+// Helper functions used by FileField Sources.
+Drupal.fileFieldSources = {
+  /**
+   * Update the hint text when clicking between source types.
+   */
+  updateHintText: function(fileFieldElement) {
+    // Add default value hint text to text fields.
+    $(fileFieldElement).find('div.filefield-source').each(function() {
+      var matches = this.className.match(/filefield-source-([a-z]+)/);
+      var sourceType = matches[1];
+      var textfield = $(this).find('input.form-text:first').get(0);
+      var defaultText = (drupalSettings.fileFieldSources && drupalSettings.fileFieldSources[sourceType]) ? drupalSettings.fileFieldSources[sourceType].hintText : '';
+
+      // If the field doesn't exist, just return.
+      if (!textfield) {
+        return;
+      }
+
+      // If this field is not shown, remove its value and be done.
+      if (!$(this).is(':visible') && textfield.value == defaultText) {
+        textfield.value = '';
+        return;
+      }
+
+      // Set a default value:
+      if (textfield.value == '') {
+        textfield.value = defaultText;
+      }
+
+      // Set a default class.
+      if (textfield.value == defaultText) {
+        $(textfield).addClass('hint');
+      }
+
+      $(textfield).focus(hideHintText);
+      $(textfield).blur(showHintText);
+
+      function showHintText() {
+        if (this.value == '') {
+          this.value = defaultText;
+          $(this).addClass('hint');
+        }
+      }
+
+      function hideHintText() {
+        if (this.value == defaultText) {
+          this.value = '';
+          $(this).removeClass('hint');
+        }
+      }
+    });
+  },
+
+  /**
+   * Delete all hint text from a form before submit.
+   */
+  removeHintText: function() {
+    $('div.filefield-source input.hint').val('').removeClass('hint');
+  },
+
+  /**
+   * Clean up the default value on focus.
+   */
+  pasteFocus: function(e) {
+    // Set default text.
+    if (!this.defaultText) {
+      this.defaultText = this.innerHTML;
+      this.innerHTML = '';
+    }
+    // Remove non-text nodes.
+    $(this).children().remove();
+  },
+
+  /**
+   * Restore default value on blur.
+   */
+  pasteBlur: function(e) {
+    if (this.defaultText && !this.innerHTML) {
+      this.innerHTML = this.defaultText;
+    }
+  },
+
+  pasteEvent: function(e) {
+    var clipboardData = null;
+    var targetElement = this;
+    var userAgent = navigator.userAgent.toLowerCase();
+
+    // Chrome.
+    if (window.event && window.event.clipboardData && window.event.clipboardData.items) {
+      clipboardData = window.event.clipboardData;
+    }
+    // All browsers in the future (hopefully).
+    else if (e.originalEvent && e.originalEvent.clipboardData && e.originalEvent.clipboardData.items) {
+      clipboardData = e.originalEvent.clipboardData;
+    }
+    // Firefox with content editable pastes as img tag with data href.
+    else if (userAgent.match(/mozilla/) && !userAgent.match(/webkit/)) {
+      Drupal.fileFieldSources.waitForPaste(targetElement);
+      return true;
+    }
+    else {
+      Drupal.fileFieldSources.pasteError(targetElement, Drupal.t('Paste from clipboard not supported in this browser.'));
+      return false;
+    }
+
+    var items = clipboardData.items;
+    var types = clipboardData.types;
+    var filename = targetElement.firstChild ? targetElement.firstChild.textContent : '';
+
+    // Handle files and image content directly in the clipboard.
+    var fileFound = false;
+    for (var n = 0; n < items.length; n++) {
+      if (items[n] && items[n].kind === 'file') {
+        var fileBlob = items[n].getAsFile();
+        var fileReader = new FileReader();
+        // Define events to fire after the file is read into memory.
+        fileReader.onload = function() {
+          Drupal.fileFieldSources.pasteSubmit(targetElement, filename, this.result);
+        };
+        fileReader.onerror = function() {
+          Drupal.fileFieldSources.pasteError(targetElement, Drupal.t('Error reading file from clipboard.'));
+        };
+        // Read in the file to fire the above events.
+        fileReader.readAsDataURL(fileBlob);
+        fileFound = true;
+        break;
+      }
+      // Handle files that a copy/pasted as a file reference.
+      /* if (types[n] && types[n] === 'Files') {
+          TODO: Figure out how to capture copy/paste of entire files from desktop.
+       }*/
+    }
+    if (!fileFound) {
+      Drupal.fileFieldSources.pasteError(targetElement, Drupal.t('No file in clipboard.'));
+    }
+    return false;
+  },
+
+  /**
+   * For browsers that don't support native clipboardData attributes.
+   */
+  waitForPaste: function(targetElement) {
+    if (targetElement.children && targetElement.children.length > 0) {
+      var filename = targetElement.firstChild ? targetElement.firstChild.textContent : '';
+      var tagFound = false;
+      $(targetElement).find('img[src^="data:image"]').each(function(n, element) {
+        Drupal.fileFieldSources.pasteSubmit(targetElement, filename, element.src);
+        tagFound = true;
+      });
+      $(targetElement).html(filename);
+      if (!tagFound) {
+        Drupal.fileFieldSources.pasteError(targetElement, Drupal.t('No file in clipboard.'));
+      }
+    }
+    else {
+      setTimeout(function() {
+        Drupal.fileFieldSources.waitForPaste(targetElement);
+      }, 200);
+    }
+  },
+
+  /**
+   * Set an error on the paste field temporarily then clear it.
+   */
+  pasteError: function(domElement, errorMessage) {
+    var $description = $(domElement).parents('.filefield-source-clipboard:first').find('.description');
+    if (!$description.data('originalDescription')) {
+      $description.data('originalDescription', $description.html())
+    }
+    $description.html(errorMessage);
+    var errorTimeout = setTimeout(function() {
+      $description.html($description.data('originalDescription'));
+      $(this).unbind('click.pasteError');
+    }, 3000);
+    $(domElement).bind('click.pasteError', function() {
+      clearTimeout(errorTimeout);
+      $description.html($description.data('originalDescription'));
+      $(this).unbind('click.pasteError');
+    });
+  },
+
+  /**
+   * After retreiving a clipboard, post the results to the server.
+   */
+  pasteSubmit: function(targetElement, filename, contents) {
+    var $wrapper = $(targetElement).parents('.filefield-source-clipboard');
+    $wrapper.find('.filefield-source-clipboard-filename').val(filename);
+    $wrapper.find('.filefield-source-clipboard-contents').val(contents);
+    $wrapper.find('input.form-submit').trigger('mousedown');
+  },
+
+  /**
+   * Click event for the imce browse link.
+   */
+  imceBrowse: function (e) {
+    window.open(this.href, '', 'width=760,height=560,resizable=1');
+    e.preventDefault();
+  }
+};
+
+// Override triggerUploadButton method from file.js.
+Drupal.file.triggerUploadButton = function (event) {
+  $(event.target).closest('.form-managed-file').find('.form-submit.upload-button').trigger('mousedown');
+}
+
+})(jQuery, Drupal);
diff --git a/web/modules/contrib/filefield_sources/src/Access/FieldAccessCheck.php b/web/modules/contrib/filefield_sources/src/Access/FieldAccessCheck.php
new file mode 100644 (file)
index 0000000..b70f535
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\filefield_sources\Access\FieldAccessCheck.
+ */
+
+namespace Drupal\filefield_sources\Access;
+
+use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface;
+use Drupal\Core\Session\AccountInterface;
+
+/**
+ * Access check for file field source routes.
+ */
+class FieldAccessCheck implements RoutingAccessInterface {
+
+  /**
+   * Checks access.
+   *
+   * @param string $entity_type
+   *   Entity type.
+   * @param string $bundle_name
+   *   Bundle name.
+   * @param string $field_name
+   *   Field name.
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The currently logged in account.
+   *
+   * @return string
+   *   A \Drupal\Core\Access\AccessInterface constant value.
+   */
+  public function access($entity_type, $bundle_name, $field_name, AccountInterface $account) {
+    $field = entity_load('field_config', $entity_type . '.' . $bundle_name . '.' . $field_name);
+    return $field->access('edit', $account, TRUE);
+  }
+
+}
diff --git a/web/modules/contrib/filefield_sources/src/Annotation/FilefieldSource.php b/web/modules/contrib/filefield_sources/src/Annotation/FilefieldSource.php
new file mode 100644 (file)
index 0000000..3a873fe
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\filefield_sources\Annotation\FilefieldSource.
+ */
+
+namespace Drupal\filefield_sources\Annotation;
+
+use Drupal\Component\Annotation\Plugin;
+
+/**
+ * Defines a Search API processor annotation object.
+ *
+ * @Annotation
+ */
+class FilefieldSource extends Plugin {
+
+  /**
+   * The file field source plugin ID.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The name of the file field source plugin.
+   *
+   * It will be displayed in a select list.
+   *
+   * @ingroup plugin_translatable
+   *
+   * @var \Drupal\Core\Annotation\Translation
+   */
+  public $name;
+
+  /**
+   * The human-readable name of the file field source plugin.
+   *
+   * @ingroup plugin_translatable
+   *
+   * @var \Drupal\Core\Annotation\Translation
+   */
+  public $label;
+
+  /**
+   * The description of the file field source plugin.
+   *
+   * @ingroup plugin_translatable
+   *
+   * @var \Drupal\Core\Annotation\Translation
+   */
+  public $description;
+
+  /**
+   * The weight of file field source plugin.
+   *
+   * @var integer
+   */
+  public $weight;
+
+}
diff --git a/web/modules/contrib/filefield_sources/src/Controller/ImceController.php b/web/modules/contrib/filefield_sources/src/Controller/ImceController.php
new file mode 100644 (file)
index 0000000..4f8fcb6
--- /dev/null
@@ -0,0 +1,111 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\imce\Controller\ImceController.
+ */
+
+namespace Drupal\filefield_sources\Controller;
+
+use Symfony\Component\HttpFoundation\Request;
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\imce\Imce;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Drupal\Core\Render\BubbleableMetadata;
+
+/**
+ * Controller routines for imce routes.
+ */
+class ImceController extends ControllerBase {
+
+  /**
+   * Outputs the IMCE browser for FileField.
+   */
+  public function page($entity_type, $bundle_name, $field_name, Request $request) {
+    // Check access.
+    if (!\Drupal::moduleHandler()->moduleExists('imce') || !Imce::access() || !$instance = entity_load('field_config', $entity_type . '.' . $bundle_name . '.' . $field_name)) {
+      throw new AccessDeniedHttpException();
+    }
+
+    $settings = $instance->getSettings();
+    $imceFM = Imce::userFM(\Drupal::currentUser(), $settings['uri_scheme'], $request);
+
+    // Override scanner.
+    if (!empty($imceFM)) {
+      $scanner = \Drupal::service('filefield_sources.imce_scanner');
+      $widget = entity_get_form_display($entity_type, $bundle_name, 'default')->getComponent($field_name);
+      // Full mode.
+      if (!empty($widget['third_party_settings']['filefield_sources']['filefield_sources']['source_imce']['imce_mode'])) {
+        $imceFM->setConf('scanner', array($scanner, 'customScanFull'));
+        // Set context.
+        $scanner->setContext(array(
+          'scheme' => $imceFM->getConf('scheme'),
+        ));
+      }
+      // Restricted mode.
+      else {
+        $imceFM->setConf('scanner', array($scanner, 'customScanRestricted'));
+
+        // Make field directory the only accessible one.
+        $field_uri = static::getUploadLocation($settings);
+        static::disablePerms($imceFM, $field_uri, array('browse_files'));
+
+        // Set context.
+        $scanner->setContext(array(
+          'entity_type' => $entity_type,
+          'field_name' => $field_name,
+          'uri' => $field_uri,
+          'is_rool' => $is_root,
+        ));
+      }
+
+      // Disable absolute URLs.
+      \Drupal::configFactory()->getEditable('imce.settings')->set('abs_urls', FALSE);
+
+      return $imceFM->pageResponse();
+    }
+  }
+
+  /**
+   * Determines the URI for a file field.
+   *
+   * @param array $data
+   *   An array of token objects to pass to token_replace().
+   *
+   * @return string
+   *   A file directory URI with tokens replaced.
+   *
+   * @see token_replace()
+   */
+  protected static function getUploadLocation($settings, $data = array()) {
+    $destination = trim($settings['file_directory'], '/');
+
+    // Replace tokens. To ensure that render context is empty, pass a bubbleable
+    // metadata object to the replace method.
+    $bubbleable_metadata = new BubbleableMetadata();
+    $destination = \Drupal::token()->replace($destination, $data, [], $bubbleable_metadata);
+
+    return $settings['uri_scheme'] . '://' . $destination;
+  }
+
+  /**
+   * Disable IMCE profile permissions.
+   */
+  protected static function disablePerms($imceFM, $field_uri, $exceptions = array()) {
+    $scheme = $imceFM->getConf('scheme');
+    $root = $scheme . '://';
+    $is_root = $field_uri == $root;
+    $path = $is_root ? '.' : substr($field_uri, strlen($root));
+
+    $folders = $imceFM->getConf('folders');
+    $perms = \Drupal::service('plugin.manager.imce.plugin')->permissionInfo();
+    $folders['.']['permissions']['all'] = FALSE;
+    $folders[$path]['permissions']['all'] = FALSE;
+    foreach ($perms as $perm => $title) {
+      $folders['.']['permissions'][$perm] = FALSE;
+      $folders[$path]['permissions'][$perm] = in_array($perm, array('browse_files')) ? TRUE : FALSE;
+    }
+    $imceFM->setConf('folders', $folders);
+  }
+
+}
diff --git a/web/modules/contrib/filefield_sources/src/File/MimeType/ExtensionMimeTypeGuesser.php b/web/modules/contrib/filefield_sources/src/File/MimeType/ExtensionMimeTypeGuesser.php
new file mode 100644 (file)
index 0000000..2c44e97
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\filefield_sources\File\MimeType\ExtensionMimeTypeGuesser.
+ */
+
+namespace Drupal\filefield_sources\File\MimeType;
+
+use Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser as CoreExtensionMimeTypeGuesser;
+
+/**
+ * Add methods to core guesser.
+ */
+class ExtensionMimeTypeGuesser extends CoreExtensionMimeTypeGuesser {
+
+  /**
+   * Convert mime type to extension.
+   *
+   * @param string $mimetype
+   *   Mime type.
+   *
+   * @return string|bool
+   *   Return extension if found, FALSE otherwise.
+   */
+  public function convertMimeTypeToExtension($mimetype) {
+    $this->checkDefaultMapping();
+
+    $mime_key = array_search($mimetype, $this->mapping['mimetypes']);
+    $extension = array_search($mime_key, $this->mapping['extensions']);
+
+    return $extension;
+  }
+
+  /**
+   * Convert mime type to most common extension.
+   *
+   * @param string $mimetype
+   *   Mime type.
+   *
+   * @return string|bool
+   *   Return extension if found, FALSE otherwise.
+   */
+  public function convertMimeTypeToMostCommonExtension($mimetype) {
+    $this->checkDefaultMapping();
+
+    $extension = FALSE;
+    if (isset($mimetype)) {
+      // See if this matches a known MIME type.
+      $mime_key = array_search($mimetype, $this->mapping['mimetypes']);
+      if ($mime_key !== FALSE) {
+        // If we have a match, get this list of likely extensions. For some
+        // reason Drupal lists the "most common" extension last for most file
+        // types including php, jpg, and doc.
+        if ($extensions = array_keys($this->mapping['extensions'], $mime_key)) {
+          $extension = end($extensions);
+        }
+      }
+    }
+    return $extension;
+  }
+
+  /**
+   * Check for default mapping.
+   */
+  private function checkDefaultMapping() {
+    if ($this->mapping === NULL) {
+      $mapping = $this->defaultMapping;
+      // Allow modules to alter the default mapping.
+      $this->moduleHandler->alter('file_mimetype_mapping', $mapping);
+      $this->mapping = $mapping;
+    }
+  }
+
+}
diff --git a/web/modules/contrib/filefield_sources/src/FilefieldSourceInterface.php b/web/modules/contrib/filefield_sources/src/FilefieldSourceInterface.php
new file mode 100644 (file)
index 0000000..8ea45ca
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\filefield_sources\FilefieldSourceInterface.
+ */
+
+namespace Drupal\filefield_sources;
+
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Provides an interface for file field source plugins.
+ *
+ * @see \Drupal\filefield_sources\FilefieldSourceManager
+ * @see \Drupal\filefield_sources\Annotation\FilefieldSource
+ * @see plugin_api
+ *
+ * @ingroup filefield_sources
+ */
+interface FilefieldSourceInterface {
+
+  /**
+   * Value callback for file field source plugin.
+   *
+   * @param array $element
+   *   An associative array containing the properties of the element.
+   * @param mixed $input
+   *   The incoming input to populate the form element. If this is FALSE,
+   *   the element's default value should be returned.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @return mixed
+   *   The value to assign to the element.
+   */
+  public static function value(array &$element, &$input, FormStateInterface $form_state);
+
+  /**
+   * Process callback for file field source plugin.
+   *
+   * @param array $element
+   *   An associative array containing the properties and children of the
+   *   generic input element.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   * @param array $complete_form
+   *   The complete form structure.
+   *
+   * @return array
+   *   The processed element.
+   */
+  public static function process(array &$element, FormStateInterface $form_state, array &$complete_form);
+
+}
diff --git a/web/modules/contrib/filefield_sources/src/FilefieldSourceManager.php b/web/modules/contrib/filefield_sources/src/FilefieldSourceManager.php
new file mode 100644 (file)
index 0000000..5c320d1
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\filefield_sources\FilefieldSourceManager.
+ */
+
+namespace Drupal\filefield_sources;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\DefaultPluginManager;
+
+/**
+ * Provides a plugin manager for file field source.
+ *
+ * @see \Drupal\filefield_sources\Annotation\FilefieldSource
+ * @see \Drupal\filefield_sources\FilefieldSourceInterface
+ * @see plugin_api
+ */
+class FilefieldSourceManager extends DefaultPluginManager {
+
+  /**
+   * Constructs a FilefieldSourceManager object.
+   *
+   * @param \Traversable $namespaces
+   *   An object that implements \Traversable which contains the root paths
+   *   keyed by the corresponding namespace to look for plugin implementations.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   Cache backend instance to use.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler to invoke the alter hook with.
+   */
+  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
+    $this->setCacheBackend($cache_backend, 'filefield_sources');
+
+    parent::__construct('Plugin/FilefieldSource', $namespaces, $module_handler, 'Drupal\filefield_sources\FilefieldSourceInterface', 'Drupal\filefield_sources\Annotation\FilefieldSource');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDefinitions() {
+    $definitions = parent::getDefinitions();
+    if (!\Drupal::moduleHandler()->moduleExists('imce')) {
+      unset($definitions['imce']);
+    }
+    if (!filefield_sources_curl_enabled()) {
+      unset($definitions['remote']);
+    }
+    return $definitions;
+  }
+
+}
diff --git a/web/modules/contrib/filefield_sources/src/FilefieldSourcesServiceProvider.php b/web/modules/contrib/filefield_sources/src/FilefieldSourcesServiceProvider.php
new file mode 100644 (file)
index 0000000..8f2a65f
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\filefield_sources\FilefieldSourcesServiceProvider.
+ */
+
+namespace Drupal\filefield_sources;
+
+use Drupal\Core\DependencyInjection\ServiceModifierInterface;
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+
+/**
+ * Alter file.mime_type.guesser.extension service.
+ */
+class FilefieldSourcesServiceProvider implements ServiceModifierInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function alter(ContainerBuilder $container) {
+    $definition = $container->getDefinition('file.mime_type.guesser.extension');
+    $definition->setClass('Drupal\filefield_sources\File\MimeType\ExtensionMimeTypeGuesser');
+  }
+
+}
diff --git a/web/modules/contrib/filefield_sources/src/ImceScanner.php b/web/modules/contrib/filefield_sources/src/ImceScanner.php
new file mode 100644 (file)
index 0000000..1993c0e
--- /dev/null
@@ -0,0 +1,102 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\filefield_sources\ImceScanner.
+ */
+
+namespace Drupal\filefield_sources;
+
+use Drupal\imce\Imce;
+
+/**
+ * Imce scanner service.
+ */
+class ImceScanner {
+
+  /**
+   * Scanner context.
+   *
+   * @var mixed
+   */
+  private $context = NULL;
+
+  /**
+   * Sets scanner context.
+   */
+  public function setContext($context) {
+    $this->context = $context;
+  }
+
+  /**
+   * Scan and return files, subdirectories.
+   */
+  public function customScanFull($dirname, $options) {
+    // Get a list of files in the database for this directory.
+    $scheme = $this->context['scheme'];
+    $sql_uri_name = $dirname == '.' ? $scheme . '://' : $dirname . '/';
+
+    $result = db_select('file_managed', 'f')
+      ->fields('f', array('uri'))
+      ->condition('f.uri', $sql_uri_name . '%', 'LIKE')
+      ->condition('f.uri', $sql_uri_name . '_%/%', 'NOT LIKE')
+      ->execute();
+
+    $db_files = array();
+    foreach ($result as $row) {
+      $db_files[basename($row->uri)] = 1;
+    }
+
+    // Get the default IMCE directory scan, then filter down to database files.
+    $content = Imce::scanDir($dirname, $options);
+    foreach ($content['files'] as $filename => $file) {
+      if (!isset($db_files[$filename])) {
+        unset($content['files'][$filename]);
+      }
+    }
+
+    return $content;
+  }
+
+  /**
+   * Scan directory and return file list.
+   *
+   * This only work on Restricted Mode.
+   */
+  public function customScanRestricted($dirname, $options) {
+    $content = array('files' => array(), 'subfolders' => array());
+    $field_uri = $this->context['uri'];
+    $is_root = $this->context['is_root'];
+
+    if ($dirname !== $field_uri) {
+      return $content;
+    }
+
+    $entity_type = $this->context['entity_type'];
+    $field_name = $this->context['field_name'];
+    $field_storage = entity_load('field_storage_config', $entity_type . '.' . $field_name);
+    $entity_manager = \Drupal::entityManager();
+    if ($entity_manager->hasDefinition($entity_type)) {
+      $storage = $entity_manager->getStorage($entity_type);
+      $table_mapping = $storage->getTableMapping();
+      $field_table = $table_mapping->getDedicatedDataTableName($field_storage);
+      $field_column_name = $table_mapping->getFieldColumnName($field_storage, 'target_id');
+
+      $sql_uri = $field_uri . ($is_root ? '' : '/');
+      $query = db_select($field_table, 'cf');
+      $query->innerJoin('file_managed', 'f', 'f.fid = cf.' . $field_column_name);
+      $result = $query->fields('f')
+        ->condition('f.status', 1)
+        ->condition('f.uri', $sql_uri . '%', 'LIKE')
+        ->condition('f.uri', $sql_uri . '%/%', 'NOT LIKE')
+        ->execute();
+      foreach ($result as $file) {
+        // Get real name.
+        $name = basename($file->uri);
+        $content['files'][$name] = $file->uri;
+      }
+    }
+
+    return $content;
+  }
+}
diff --git a/web/modules/contrib/filefield_sources/src/Plugin/FilefieldSource/Attach.php b/web/modules/contrib/filefield_sources/src/Plugin/FilefieldSource/Attach.php
new file mode 100644 (file)
index 0000000..ca965f0
--- /dev/null
@@ -0,0 +1,356 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\filefield_sources\Plugin\FilefieldSource\Attach.
+ */
+
+namespace Drupal\filefield_sources\Plugin\FilefieldSource;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\filefield_sources\FilefieldSourceInterface;
+use Drupal\Core\Field\WidgetInterface;
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Site\Settings;
+use Drupal\Core\Template\Attribute;
+
+/**
+ * A FileField source plugin to allow use of files within a server directory.
+ *
+ * @FilefieldSource(
+ *   id = "attach",
+ *   name = @Translation("File attach from server directory"),
+ *   label = @Translation("File attach"),
+ *   description = @Translation("Select a file from a directory on the server."),
+ *   weight = 3
+ * )
+ */
+class Attach implements FilefieldSourceInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function value(array &$element, &$input, FormStateInterface $form_state) {
+    if (!empty($input['filefield_attach']['filename'])) {
+      $instance = entity_load('field_config', $element['#entity_type'] . '.' . $element['#bundle'] . '.' . $element['#field_name']);
+      $filepath = $input['filefield_attach']['filename'];
+
+      // Check that the destination is writable.
+      $directory = $element['#upload_location'];
+      $mode = Settings::get('file_chmod_directory', FILE_CHMOD_DIRECTORY);
+
+      // This first chmod check is for other systems such as S3, which don't
+      // work with file_prepare_directory().
+      if (!drupal_chmod($directory, $mode) && !file_prepare_directory($directory, FILE_CREATE_DIRECTORY)) {
+        \Drupal::logger('filefield_sources')->log(E_NOTICE, 'File %file could not be copied, because the destination directory %destination is not configured correctly.', array(
+          '%file' => $filepath,
+          '%destination' => drupal_realpath($directory),
+        ));
+        drupal_set_message(t('The specified file %file could not be copied, because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions. More information is available in the system log.', array('%file' => $filepath)), 'error');
+        return;
+      }
+
+      // Clean up the file name extensions and transliterate.
+      $original_filepath = $filepath;
+      $new_filepath = filefield_sources_clean_filename($filepath, $instance->getSetting('file_extensions'));
+      rename($filepath, $new_filepath);
+      $filepath = $new_filepath;
+
+      // Run all the normal validations, minus file size restrictions.
+      $validators = $element['#upload_validators'];
+      if (isset($validators['file_validate_size'])) {
+        unset($validators['file_validate_size']);
+      }
+
+      // Save the file to the new location.
+      if ($file = filefield_sources_save_file($filepath, $validators, $directory)) {
+        if (!in_array($file->id(), $input['fids'])) {
+          $input['fids'][] = $file->id();
+        }
+
+        // Delete the original file if "moving" the file instead of copying.
+        if ($element['#filefield_sources_settings']['source_attach']['attach_mode'] !== FILEFIELD_SOURCE_ATTACH_MODE_COPY) {
+          @unlink($filepath);
+        }
+      }
+
+      // Restore the original file name if the file still exists.
+      if (file_exists($filepath) && $filepath != $original_filepath) {
+        rename($filepath, $original_filepath);
+      }
+
+      $input['filefield_attach']['filename'] = '';
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function process(array &$element, FormStateInterface $form_state, array &$complete_form) {
+    $settings = $element['#filefield_sources_settings']['source_attach'];
+    $field_name = $element['#field_name'];
+    $instance = entity_load('field_config', $element['#entity_type'] . '.' . $element['#bundle'] . '.' . $field_name);
+
+    $element['filefield_attach'] = array(
+      '#weight' => 100.5,
+      '#theme' => 'filefield_sources_element',
+      '#source_id' => 'attach',
+      // Required for proper theming.
+      '#filefield_source' => TRUE,
+    );
+
+    $path = static::getDirectory($settings);
+    $options = static::getAttachOptions($path, $instance->getSetting('file_extensions'));
+
+    // If we have built this element before, append the list of options that we
+    // had previously. This allows files to be deleted after copying them and
+    // still be considered a valid option during the validation and submit.
+    $triggering_element = $form_state->getTriggeringElement();
+    $property = array(
+      'filefield_sources',
+      $field_name,
+      'attach_options',
+    );
+    if (!isset($triggering_element) && $form_state->has($property)) {
+      $attach_options = $form_state->get($property);
+      $options = $options + $attach_options;
+    }
+    // On initial form build and rebuilds after processing input, save the
+    // original list of options so they can be restored in the line above.
+    else {
+      $form_state->set(array('filefield_sources', $field_name, 'attach_options'), $options);
+    }
+
+    $description = t('This method may be used to attach files that exceed the file size limit. Files may be attached from the %directory directory on the server, usually uploaded through FTP.', array('%directory' => realpath($path)));
+
+    // Error messages.
+    if ($options === FALSE || empty($settings['path'])) {
+      $attach_message = t('A file attach directory could not be located.');
+      $attach_description = t('Please check your settings for the %field field.', array('%field' => $instance->getLabel()));
+    }
+    elseif (!count($options)) {
+      $attach_message = t('There currently are no files to attach.');
+      $attach_description = $description;
+    }
+
+    if (isset($attach_message)) {
+      $element['filefield_attach']['attach_message'] = array(
+        '#markup' => $attach_message,
+      );
+      $element['filefield_attach']['#description'] = $attach_description;
+    }
+    else {
+      $validators = $element['#upload_validators'];
+      if (isset($validators['file_validate_size'])) {
+        unset($validators['file_validate_size']);
+      }
+      $description .= '<br />' . filefield_sources_element_validation_help($validators);
+      $element['filefield_attach']['filename'] = array(
+        '#type' => 'select',
+        '#options' => $options,
+      );
+      $element['filefield_attach']['#description'] = $description;
+    }
+    $class = '\Drupal\file\Element\ManagedFile';
+    $ajax_settings = [
+      'callback' => [$class, 'uploadAjaxCallback'],
+      'options' => [
+        'query' => [
+          'element_parents' => implode('/', $element['#array_parents']),
+        ],
+      ],
+      'wrapper' => $element['upload_button']['#ajax']['wrapper'],
+      'effect' => 'fade',
+    ];
+    $element['filefield_attach']['attach'] = [
+      '#name' => implode('_', $element['#parents']) . '_attach',
+      '#type' => 'submit',
+      '#value' => t('Attach'),
+      '#validate' => [],
+      '#submit' => ['filefield_sources_field_submit'],
+      '#limit_validation_errors' => [$element['#parents']],
+      '#ajax' => $ajax_settings,
+    ];
+
+    return $element;
+  }
+
+  /**
+   * Theme the output of the attach element.
+   */
+  public static function element($variables) {
+    $element = $variables['element'];
+    if (isset($element['attach_message'])) {
+      $output = $element['attach_message']['#markup'];
+    }
+    elseif (isset($element['filename'])) {
+      // Get rendered options.
+      $options = form_select_options($element['filename']);
+      $option_output = '';
+      foreach ($options as $key => $value) {
+        $option_output .= '<option value=' . $value["value"] . '>' . $value["label"] . '</option>';
+      }
+      // Get rendered select.
+      $size = !empty($element['filename']['#size']) ? ' size="' . $element['filename']['#size'] . '"' : '';
+      $element['filename']['#attributes']['class'][] = 'form-select';
+      $multiple = !empty($element['#multiple']);
+      $output = '<select name="' . $element['filename']['#name'] . '' . ($multiple ? '[]' : '') . '"' . ($multiple ? ' multiple="multiple" ' : '') . new Attribute($element['filename']['#attributes']) . ' id="' . $element['filename']['#id'] . '" ' . $size . '>' . $option_output . '</select>';
+    }
+
+    $output .= drupal_render($element['attach']);
+    $element['#children'] = $output;
+    $element['#theme_wrappers'] = array('form_element');
+    return '<div class="filefield-source filefield-source-attach clear-block">' . drupal_render($element) . '</div>';
+  }
+
+  /**
+   * Get directory from settings.
+   *
+   * @param array $settings
+   *   Attach source's settings.
+   * @param object $account
+   *   User to replace token.
+   *
+   * @return string
+   *   Path that contains files to attach.
+   */
+  protected static function getDirectory(array $settings, $account = NULL) {
+    $account = isset($account) ? $account : \Drupal::currentUser();
+    $path = $settings['path'];
+    $absolute = !empty($settings['absolute']);
+
+    // Replace user level tokens.
+    // Node level tokens require a lot of complexity like temporary storage
+    // locations when values don't exist. See the filefield_paths module.
+    if (\Drupal::moduleHandler()->moduleExists('token')) {
+      $token = \Drupal::token();
+      $path = $token->replace($path, array('user' => $account));
+    }
+
+    return $absolute ? $path : file_default_scheme() . '://' . $path;
+  }
+
+  /**
+   * Get attach options.
+   *
+   * @param string $path
+   *   Path to scan files.
+   * @param string $extensions
+   *   Path to scan files.
+   *
+   * @return array
+   *   List of options.
+   */
+  protected static function getAttachOptions($path, $extensions = FALSE) {
+    if (!file_prepare_directory($path, FILE_CREATE_DIRECTORY)) {
+      drupal_set_message(t('Specified file attach path %path must exist or be writable.', array('%path' => $path)), 'error');
+      return FALSE;
+    }
+
+    $options = array();
+    $pattern = !empty($extensions) ? '/\.(' . strtr($extensions, ' ', '|') . ')$/' : '/.*/';
+    $files = file_scan_directory($path, $pattern);
+
+    if (count($files)) {
+      $options = array('' => t('-- Select file --'));
+      foreach ($files as $file) {
+        $options[$file->uri] = str_replace($path . '/', '', $file->uri);
+      }
+      natcasesort($options);
+    }
+
+    return $options;
+  }
+
+  /**
+   * Implements hook_filefield_source_settings().
+   */
+  public static function settings(WidgetInterface $plugin) {
+    $settings = $plugin->getThirdPartySetting('filefield_sources', 'filefield_sources', array(
+      'source_attach' => array(
+        'path' => FILEFIELD_SOURCE_ATTACH_DEFAULT_PATH,
+        'absolute' => FILEFIELD_SOURCE_ATTACH_RELATIVE,
+        'attach_mode' => FILEFIELD_SOURCE_ATTACH_MODE_MOVE,
+      ),
+    ));
+
+    $return['source_attach'] = array(
+      '#title' => t('File attach settings'),
+      '#type' => 'details',
+      '#description' => t('File attach allows for selecting a file from a directory on the server, commonly used in combination with FTP.') . ' <strong>' . t('This file source will ignore file size checking when used.') . '</strong>',
+      '#element_validate' => array(array(get_called_class(), 'filePathValidate')),
+      '#weight' => 3,
+    );
+    $return['source_attach']['path'] = array(
+      '#type' => 'textfield',
+      '#title' => t('File attach path'),
+      '#default_value' => $settings['source_attach']['path'],
+      '#size' => 60,
+      '#maxlength' => 128,
+      '#description' => t('The directory within the <em>File attach location</em> that will contain attachable files.'),
+    );
+    if (\Drupal::moduleHandler()->moduleExists('token')) {
+      $return['source_attach']['tokens'] = array(
+        '#theme' => 'token_tree',
+        '#token_types' => array('user'),
+      );
+    }
+    $return['source_attach']['absolute'] = array(
+      '#type' => 'radios',
+      '#title' => t('File attach location'),
+      '#options' => array(
+        FILEFIELD_SOURCE_ATTACH_RELATIVE => t('Within the files directory'),
+        FILEFIELD_SOURCE_ATTACH_ABSOLUTE => t('Absolute server path'),
+      ),
+      '#default_value' => $settings['source_attach']['absolute'],
+      '#description' => t('The <em>File attach path</em> may be with the files directory (%file_directory) or from the root of your server. If an absolute path is used and it does not start with a "/" your path will be relative to your site directory: %realpath.', array('%file_directory' => drupal_realpath(file_default_scheme() . '://'), '%realpath' => realpath('./'))),
+    );
+    $return['source_attach']['attach_mode'] = array(
+      '#type' => 'radios',
+      '#title' => t('Attach method'),
+      '#options' => array(
+        FILEFIELD_SOURCE_ATTACH_MODE_MOVE => t('Move the file directly to the final location'),
+        FILEFIELD_SOURCE_ATTACH_MODE_COPY => t('Leave a copy of the file in the attach directory'),
+      ),
+      '#default_value' => isset($settings['source_attach']['attach_mode']) ? $settings['source_attach']['attach_mode'] : 'move',
+    );
+
+    return $return;
+  }
+
+  /**
+   * Validate file path.
+   *
+   * @param array $element
+   *   Form element.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   Form state.
+   * @param array $complete_form
+   *   Complete form.
+   */
+  public static function filePathValidate(array &$element, FormStateInterface $form_state, array &$complete_form) {
+    $parents = $element['#parents'];
+    array_pop($parents);
+    $input_exists = FALSE;
+
+    // Get input of the whole parent element.
+    $input = NestedArray::getValue($form_state->getValues(), $parents, $input_exists);
+    if ($input_exists) {
+      // Only validate if this source is enabled.
+      if (!$input['sources']['attach']) {
+        return;
+      }
+
+      // Strip slashes from the end of the file path.
+      $filepath = rtrim($element['path']['#value'], '\\/');
+      $form_state->setValueForElement($element['path'], $filepath);
+      $filepath = static::getDirectory($input['source_attach']);
+
+      // Check that the directory exists and is writable.
+      if (!file_prepare_directory($filepath, FILE_CREATE_DIRECTORY)) {
+        $form_state->setError($element['path'], t('Specified file attach path %path must exist or be writable.', array('%path' => $filepath)));
+      }
+    }
+  }
+
+}
diff --git a/web/modules/contrib/filefield_sources/src/Plugin/FilefieldSource/Clipboard.php b/web/modules/contrib/filefield_sources/src/Plugin/FilefieldSource/Clipboard.php
new file mode 100644 (file)
index 0000000..b0ed271
--- /dev/null
@@ -0,0 +1,152 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\filefield_sources\Plugin\FilefieldSource\Clipboard.
+ */
+
+namespace Drupal\filefield_sources\Plugin\FilefieldSource;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\filefield_sources\FilefieldSourceInterface;
+use Drupal\Core\Site\Settings;
+
+/**
+ * A FileField source plugin to allow transfer of files through the clipboard.
+ *
+ * @FilefieldSource(
+ *   id = "clipboard",
+ *   name = @Translation("Paste from clipboard (<a href=""http://drupal.org/node/1775902"">limited browser support</a>)"),
+ *   label = @Translation("Clipboard"),
+ *   description = @Translation("Allow users to paste a file directly from the clipboard."),
+ *   weight = 1
+ * )
+ */
+class Clipboard implements FilefieldSourceInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function value(array &$element, &$input, FormStateInterface $form_state) {
+    if (isset($input['filefield_clipboard']['contents']) && strlen($input['filefield_clipboard']['contents']) > 0) {
+      // Check that the destination is writable.
+      $temporary_directory = 'temporary://';
+      if (!file_prepare_directory($temporary_directory, FILE_MODIFY_PERMISSIONS)) {
+        \Drupal::logger('filefield_sources')->log(E_NOTICE, 'The directory %directory is not writable, because it does not have the correct permissions set.', array('%directory' => drupal_realpath($temporary_directory)));
+        drupal_set_message(t('The file could not be transferred because the temporary directory is not writable.'), 'error');
+        return;
+      }
+      // Check that the destination is writable.
+      $directory = $element['#upload_location'];
+      $mode = Settings::get('file_chmod_directory', FILE_CHMOD_DIRECTORY);
+
+      // This first chmod check is for other systems such as S3, which don't
+      // work with file_prepare_directory().
+      if (!drupal_chmod($directory, $mode) && !file_prepare_directory($directory, FILE_CREATE_DIRECTORY)) {
+        $url = $input['filefield_clipboard']['filename'];
+        \Drupal::logger('filefield_sources')->log(E_NOTICE, 'File %file could not be copied, because the destination directory %destination is not configured correctly.', array('%file' => $url, '%destination' => drupal_realpath($directory)));
+        drupal_set_message(t('The specified file %file could not be copied, because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions. More information is available in the system log.', array('%file' => $url)), 'error');
+        return;
+      }
+
+      // Split the file information in mimetype and base64 encoded binary.
+      $base64_data = $input['filefield_clipboard']['contents'];
+      $comma_position = strpos($base64_data, ',');
+      $semicolon_position = strpos($base64_data, ';');
+      $file_contents = base64_decode(substr($base64_data, $comma_position + 1));
+      $mimetype = substr($base64_data, 5, $semicolon_position - 5);
+
+      $extension = \Drupal::service('file.mime_type.guesser.extension')->convertMimeTypeToExtension($mimetype);
+
+      $filename = trim($input['filefield_clipboard']['filename']);
+      $filename = preg_replace('/\.[a-z0-9]{3,4}$/', '', $filename);
+      $filename = (empty($filename) ? 'paste_' . REQUEST_TIME : $filename) . '.' . $extension;
+      $filepath = file_create_filename($filename, $temporary_directory);
+
+      $copy_success = FALSE;
+      if ($fp = @fopen($filepath, 'w')) {
+        fwrite($fp, $file_contents);
+        fclose($fp);
+        $copy_success = TRUE;
+      }
+
+      if ($copy_success && $file = filefield_sources_save_file($filepath, $element['#upload_validators'], $element['#upload_location'])) {
+        if (!in_array($file->id(), $input['fids'])) {
+          $input['fids'][] = $file->id();
+        }
+      }
+
+      // Remove the temporary file generated from paste.
+      @unlink($filepath);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function process(array &$element, FormStateInterface $form_state, array &$complete_form) {
+    $element['filefield_clipboard'] = array(
+      '#weight' => 100.5,
+      '#theme' => 'filefield_sources_element',
+      '#source_id' => 'clipboard',
+      // Required for proper theming.
+      '#filefield_source' => TRUE,
+      '#filefield_sources_hint_text' => t('Enter filename then paste.'),
+      '#description' => filefield_sources_element_validation_help($element['#upload_validators']),
+    );
+
+    $element['filefield_clipboard']['capture'] = array(
+      '#type' => 'item',
+      '#markup' => '<div class="filefield-source-clipboard-capture" contenteditable="true"><span class="hint">example_filename.png</span></div> <span class="hint">' . t('ctrl + v') . '</span>',
+      '#description' => t('Enter a file name and paste an image from the clipboard. This feature only works in <a href="http://drupal.org/node/1775902">limited browsers</a>.'),
+    );
+
+    $element['filefield_clipboard']['filename'] = array(
+      '#type' => 'hidden',
+      '#attributes' => array('class' => array('filefield-source-clipboard-filename')),
+    );
+    $element['filefield_clipboard']['contents'] = array(
+      '#type' => 'hidden',
+      '#attributes' => array('class' => array('filefield-source-clipboard-contents')),
+    );
+
+    $class = '\Drupal\file\Element\ManagedFile';
+    $ajax_settings = [
+      'callback' => [$class, 'uploadAjaxCallback'],
+      'options' => [
+        'query' => [
+          'element_parents' => implode('/', $element['#array_parents']),
+        ],
+      ],
+      'wrapper' => $element['upload_button']['#ajax']['wrapper'],
+      'effect' => 'fade',
+      'progress' => [
+        'type' => 'throbber',
+        'message' => t('Transfering file...'),
+      ],
+    ];
+
+    $element['filefield_clipboard']['upload'] = [
+      '#name' => implode('_', $element['#parents']) . '_clipboard_upload_button',
+      '#type' => 'submit',
+      '#value' => t('Upload'),
+      '#attributes' => ['class' => ['js-hide']],
+      '#validate' => [],
+      '#submit' => ['filefield_sources_field_submit'],
+      '#limit_validation_errors' => [$element['#parents']],
+      '#ajax' => $ajax_settings,
+    ];
+
+    return $element;
+  }
+
+  /**
+   * Theme the output of the clipboard element.
+   */
+  public static function element($variables) {
+    $element = $variables['element'];
+
+    return '<div class="filefield-source filefield-source-clipboard clear-block">' . drupal_render_children($element) . '</div>';
+  }
+
+}
diff --git a/web/modules/contrib/filefield_sources/src/Plugin/FilefieldSource/Imce.php b/web/modules/contrib/filefield_sources/src/Plugin/FilefieldSource/Imce.php
new file mode 100644 (file)
index 0000000..5830a86
--- /dev/null
@@ -0,0 +1,216 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\filefield_sources\Plugin\FilefieldSource\Imce.
+ */
+
+namespace Drupal\filefield_sources\Plugin\FilefieldSource;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\filefield_sources\FilefieldSourceInterface;
+use Symfony\Component\Routing\Route;
+use Drupal\Core\Field\WidgetInterface;
+
+/**
+ * A FileField source plugin to allow referencing of files from IMCE.
+ *
+ * @FilefieldSource(
+ *   id = "imce",
+ *   name = @Translation("IMCE file browser"),
+ *   label = @Translation("File browser"),
+ *   description = @Translation("Select a file to use from a file browser."),
+ *   weight = -1
+ * )
+ */
+class Imce implements FilefieldSourceInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function value(array &$element, &$input, FormStateInterface $form_state) {
+    if (isset($input['filefield_imce']['imce_paths']) && $input['filefield_imce']['imce_paths'] != '') {
+      $instance = entity_load('field_config', $element['#entity_type'] . '.' . $element['#bundle'] . '.' . $element['#field_name']);
+      $field_settings = $instance->getSettings();
+      $scheme = $field_settings['uri_scheme'];
+      $imce_paths = explode(':', $input['filefield_imce']['imce_paths']);
+      $uris = [];
+
+      foreach ($imce_paths as $imce_path) {
+        //$wrapper = \Drupal::service('stream_wrapper_manager')->getViaScheme($scheme);
+        //$file_directory_prefix = $scheme == 'private' ? 'system/files' : $wrapper->getDirectoryPath();
+        //$uri = preg_replace('/^' . preg_quote(base_path() . $file_directory_prefix . '/', '/') . '/', $scheme . '://', $imce_path);
+        $uri = rawurldecode($scheme . '://' . $imce_path);
+        $uris[] = $uri;
+      }
+
+      // Resolve the file path to an FID.
+      $fids = db_select('file_managed', 'f')
+        ->condition('uri', $uris, 'IN')
+        ->fields('f', array('fid'))
+        ->execute()
+        ->fetchCol();
+      if ($fids) {
+        $files = file_load_multiple($fids);
+        foreach ($files as $file) {
+          if (filefield_sources_element_validate($element, $file, $form_state)) {
+            if (!in_array($file->id(), $input['fids'])) {
+              $input['fids'][] = $file->id();
+            }
+          }
+        }
+      }
+      else {
+        $form_state->setError($element, t('The selected file could not be used because the file does not exist in the database.'));
+      }
+      // No matter what happens, clear the value from the file path field.
+      $input['filefield_imce']['imce_paths'] = '';
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function process(array &$element, FormStateInterface $form_state, array &$complete_form) {
+    $instance = entity_load('field_config', $element['#entity_type'] . '.' . $element['#bundle'] . '.' . $element['#field_name']);
+
+    $element['filefield_imce'] = array(
+      '#weight' => 100.5,
+      '#theme' => 'filefield_sources_element',
+      '#source_id' => 'imce',
+      // Required for proper theming.
+      '#filefield_source' => TRUE,
+      '#description' => filefield_sources_element_validation_help($element['#upload_validators']),
+    );
+
+    $imce_url = \Drupal::url('filefield_sources.imce', array(
+      'entity_type' => $element['#entity_type'],
+      'bundle_name' => $element['#bundle'],
+      'field_name' => $element['#field_name'],
+    ),
+    array(
+      'query' => array(
+        'sendto' => 'imceFileField.sendto',
+        'fieldId' => $element['#attributes']['data-drupal-selector'] . '-filefield-imce',
+      ),
+    ));
+    $element['filefield_imce']['browse'] = array(
+      '#type' => 'markup',
+      '#markup' => '<span>' . t('No file selected') . '</span> (<a class="filefield-sources-imce-browse" href="' . $imce_url . '">' . t('browse') . '</a>)',
+    );
+
+    $element['#attached']['library'][] = 'imce/drupal.imce.filefield';
+    // Set the pre-renderer to conditionally disable the elements.
+    $element['#pre_render'][] = array(get_called_class(), 'preRenderWidget');
+
+    // Path input
+    $element['filefield_imce']['imce_paths'] = array(
+      '#type' => 'hidden',
+      // Reset value to prevent consistent errors
+      '#value' => '',
+    );
+
+    $class = '\Drupal\file\Element\ManagedFile';
+    $ajax_settings = [
+      'callback' => [$class, 'uploadAjaxCallback'],
+      'options' => [
+        'query' => [
+          'element_parents' => implode('/', $element['#array_parents']),
+        ],
+      ],
+      'wrapper' => $element['upload_button']['#ajax']['wrapper'],
+      'effect' => 'fade',
+    ];
+
+    $element['filefield_imce']['imce_button'] = array(
+      '#name' => implode('_', $element['#parents']) . '_imce_select',
+      '#type' => 'submit',
+      '#value' => t('Select'),
+      '#attributes' => ['class' => ['js-hide']],
+      '#validate' => [],
+      '#submit' => ['filefield_sources_field_submit'],
+      '#limit_validation_errors' => [$element['#parents']],
+      '#ajax' => $ajax_settings,
+    );
+
+    return $element;
+  }
+
+  /**
+   * Theme the output of the imce element.
+   */
+  public static function element($variables) {
+    $element = $variables['element'];
+
+    $output = drupal_render_children($element);
+    return '<div class="filefield-source filefield-source-imce clear-block">' . $output . '</div>';
+  }
+
+  /**
+   * Define routes for Imce source.
+   *
+   * @return array
+   *   Array of routes.
+   */
+  public static function routes() {
+    $routes = array();
+
+    $routes['filefield_sources.imce'] = new Route(
+      '/file/imce/{entity_type}/{bundle_name}/{field_name}',
+      array(
+        '_controller' => '\Drupal\filefield_sources\Controller\ImceController::page',
+        '_title' => 'File Manager',
+      ),
+      array(
+        '_access_filefield_sources_field' => 'TRUE',
+      )
+    );
+
+    return $routes;
+  }
+
+  /**
+   * Implements hook_filefield_source_settings().
+   */
+  public static function settings(WidgetInterface $plugin) {
+    $settings = $plugin->getThirdPartySetting('filefield_sources', 'filefield_sources', array(
+      'source_imce' => array(
+        'imce_mode' => 0,
+      ),
+    ));
+
+    $return['source_imce'] = array(
+      '#title' => t('IMCE file browser settings'),
+      '#type' => 'details',
+      '#access' => \Drupal::moduleHandler()->moduleExists('imce'),
+    );
+
+    // $imce_admin_url = \Drupal::url('imce.admin');
+    $imce_admin_url = 'admin/config/media/imce';
+    $return['source_imce']['imce_mode'] = array(
+      '#type' => 'radios',
+      '#title' => t('File browser mode'),
+      '#options' => array(
+        0 => t('Restricted: Users can only browse the field directory. No file operations are allowed.'),
+        1 => t('Full: Browsable directories are defined by <a href=":imce-admin-url">IMCE configuration profiles</a>. File operations are allowed.', array(':imce-admin-url' => $imce_admin_url)),
+      ),
+      '#default_value' => isset($settings['source_imce']['imce_mode']) ? $settings['source_imce']['imce_mode'] : 0,
+    );
+
+    return $return;
+
+  }
+
+  /**
+   * Pre-renders widget form.
+   */
+  public static function preRenderWidget($element) {
+    // Hide elements if there is already an uploaded file.
+    if (!empty($element['#value']['fids'])) {
+      $element['filefield_imce']['imce_paths']['#access'] = FALSE;
+      $element['filefield_imce']['imce_button']['#access'] = FALSE;
+    }
+    return $element;
+  }
+
+}
diff --git a/web/modules/contrib/filefield_sources/src/Plugin/FilefieldSource/Reference.php b/web/modules/contrib/filefield_sources/src/Plugin/FilefieldSource/Reference.php
new file mode 100644 (file)
index 0000000..6d47f12
--- /dev/null
@@ -0,0 +1,239 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\filefield_sources\Plugin\FilefieldSource\Reference.
+ */
+
+namespace Drupal\filefield_sources\Plugin\FilefieldSource;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\filefield_sources\FilefieldSourceInterface;
+use Symfony\Component\Routing\Route;
+use Drupal\Core\Field\WidgetInterface;
+use Symfony\Component\HttpFoundation\JsonResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Drupal\Component\Utility\Unicode;
+use Drupal\Component\Utility\Html;
+
+/**
+ * A FileField source plugin to allow referencing of existing files.
+ *
+ * @FilefieldSource(
+ *   id = "reference",
+ *   name = @Translation("Autocomplete reference textfield"),
+ *   label = @Translation("Reference existing"),
+ *   description = @Translation("Reuse an existing file by entering its file name."),
+ *   weight = 1
+ * )
+ */
+class Reference implements FilefieldSourceInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function value(array &$element, &$input, FormStateInterface $form_state) {
+    if (isset($input['filefield_reference']['autocomplete']) && strlen($input['filefield_reference']['autocomplete']) > 0 && $input['filefield_reference']['autocomplete'] != FILEFIELD_SOURCE_REFERENCE_HINT_TEXT) {
+      $matches = array();
+      if (preg_match('/\[fid:(\d+)\]/', $input['filefield_reference']['autocomplete'], $matches)) {
+        $fid = $matches[1];
+        if ($file = file_load($fid)) {
+
+          // Remove file size restrictions, since the file already exists on
+          // disk.
+          if (isset($element['#upload_validators']['file_validate_size'])) {
+            unset($element['#upload_validators']['file_validate_size']);
+          }
+
+          // Check that the user has access to this file through
+          // hook_download().
+          if (!$file->access('download')) {
+            $form_state->setError($element, t('You do not have permission to use the selected file.'));
+          }
+          elseif (filefield_sources_element_validate($element, (object) $file, $form_state)) {
+            if (!in_array($file->id(), $input['fids'])) {
+              $input['fids'][] = $file->id();
+            }
+          }
+        }
+        else {
+          $form_state->setError($element, t('The referenced file could not be used because the file does not exist in the database.'));
+        }
+      }
+      // No matter what happens, clear the value from the autocomplete.
+      $input['filefield_reference']['autocomplete'] = '';
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function process(array &$element, FormStateInterface $form_state, array &$complete_form) {
+
+    $element['filefield_reference'] = array(
+      '#weight' => 100.5,
+      '#theme' => 'filefield_sources_element',
+      '#source_id' => 'reference',
+      // Required for proper theming.
+      '#filefield_source' => TRUE,
+      '#filefield_sources_hint_text' => FILEFIELD_SOURCE_REFERENCE_HINT_TEXT,
+    );
+
+    $autocomplete_route_parameters = array(
+      'entity_type' => $element['#entity_type'],
+      'bundle_name' => $element['#bundle'],
+      'field_name' => $element['#field_name'],
+    );
+
+    $element['filefield_reference']['autocomplete'] = array(
+      '#type' => 'textfield',
+      '#autocomplete_route_name' => 'filefield_sources.autocomplete',
+      '#autocomplete_route_parameters' => $autocomplete_route_parameters,
+      '#description' => filefield_sources_element_validation_help($element['#upload_validators']),
+    );
+
+    $class = '\Drupal\file\Element\ManagedFile';
+    $ajax_settings = [
+      'callback' => [$class, 'uploadAjaxCallback'],
+      'options' => [
+        'query' => [
+          'element_parents' => implode('/', $element['#array_parents']),
+        ],
+      ],
+      'wrapper' => $element['upload_button']['#ajax']['wrapper'],
+      'effect' => 'fade',
+    ];
+
+    $element['filefield_reference']['select'] = [
+      '#name' => implode('_', $element['#parents']) . '_autocomplete_select',
+      '#type' => 'submit',
+      '#value' => t('Select'),
+      '#validate' => [],
+      '#submit' => ['filefield_sources_field_submit'],
+      '#limit_validation_errors' => [$element['#parents']],
+      '#ajax' => $ajax_settings,
+    ];
+
+    return $element;
+  }
+
+  /**
+   * Theme the output of the reference element.
+   */
+  public static function element($variables) {
+    $element = $variables['element'];
+
+    $element['autocomplete']['#field_suffix'] = drupal_render($element['select']);
+    return '<div class="filefield-source filefield-source-reference clear-block">' . drupal_render($element['autocomplete']) . '</div>';
+  }
+
+  /**
+   * Menu callback; autocomplete.js callback to return a list of files.
+   */
+  public static function autocomplete(Request $request, $entity_type, $bundle_name, $field_name) {
+    $matches = array();
+    $string = Unicode::strtolower($request->query->get('q'));
+
+    if (isset($string)) {
+      $widget = entity_get_form_display($entity_type, $bundle_name, 'default')->getComponent($field_name);
+      if ($widget) {
+        // // If we are looking at a single field, cache its settings, in case we want to search all fields.
+        $setting_autocomplete = $widget['third_party_settings']['filefield_sources']['filefield_sources']['source_reference']['autocomplete'];
+        $setting_search_all_fields = $widget['third_party_settings']['filefield_sources']['filefield_sources']['source_reference']['search_all_fields'];
+      }
+
+      $field_definition = entity_load('field_config', $entity_type . '.' . $bundle_name . '.' . $field_name);
+      if (!isset($field_definition) || $setting_search_all_fields) {
+        $field_definitions = \Drupal::entityManager()->getStorage('field_config')->loadByProperties(array('type' => array('file', 'image')));
+      }
+      else {
+        $field_definitions = array($field_definition);
+      }
+
+      foreach ($field_definitions as $field_definition) {
+        $handler = \Drupal::getContainer()->get('plugin.manager.entity_reference_selection')->getSelectionHandler($field_definition);
+
+        // If we are searching all fields, use the autocomplete settings from the source field.
+        $match_operator = empty($setting_autocomplete) ? 'STARTS_WITH' : 'CONTAINS';
+        // Get an array of matching entities.
+        $entity_labels = $handler->getReferenceableEntities($string, $match_operator, 10);
+
+        // Loop through the entities and convert them into autocomplete output.
+        foreach ($entity_labels as $values) {
+          foreach ($values as $entity_id => $label) {
+            $key = "$label [fid:$entity_id]";
+            // Strip things like starting/trailing white spaces, line breaks and
+            // tags.
+            $key = preg_replace('/\s\s+/', ' ', str_replace("\n", '', trim(Html::decodeEntities(strip_tags($key)))));
+            // Names containing commas or quotes must be wrapped in quotes.
+            $matches[] = array('value' => $key, 'label' => $label);
+          }
+        }
+      }
+    }
+
+    return new JsonResponse($matches);
+  }
+
+  /**
+   * Define routes for Reference source.
+   *
+   * @return array
+   *   Array of routes.
+   */
+  public static function routes() {
+    $routes = array();
+
+    $routes['filefield_sources.autocomplete'] = new Route(
+      '/file/reference/{entity_type}/{bundle_name}/{field_name}',
+      array(
+        '_controller' => get_called_class() . '::autocomplete',
+      ),
+      array(
+        '_access_filefield_sources_field' => 'TRUE',
+      )
+    );
+
+    return $routes;
+  }
+
+  /**
+   * Implements hook_filefield_source_settings().
+   */
+  public static function settings(WidgetInterface $plugin) {
+    $settings = $plugin->getThirdPartySetting('filefield_sources', 'filefield_sources', array(
+      'source_reference' => array(
+        'autocomplete' => FILEFIELD_SOURCE_REFERENCE_MATCH_STARTS_WITH,
+        'search_all_fields' => FILEFIELD_SOURCE_REFERENCE_SEARCH_ALL_NO,
+      ),
+    ));
+
+    $return['source_reference'] = array(
+      '#title' => t('Autocomplete reference options'),
+      '#type' => 'details',
+    );
+
+    $return['source_reference']['autocomplete'] = array(
+      '#title' => t('Match file name'),
+      '#options' => array(
+        FILEFIELD_SOURCE_REFERENCE_MATCH_STARTS_WITH => t('Starts with'),
+        FILEFIELD_SOURCE_REFERENCE_MATCH_CONTAINS => t('Contains'),
+      ),
+      '#type' => 'radios',
+      '#default_value' => isset($settings['source_reference']['autocomplete']) ? $settings['source_reference']['autocomplete'] : FILEFIELD_SOURCE_REFERENCE_MATCH_STARTS_WITH,
+    );
+
+    $return['source_reference']['search_all_fields'] = array(
+      '#title' => t('Search all file fields'),
+      '#options' => array(
+        FILEFIELD_SOURCE_REFERENCE_SEARCH_ALL_NO => t('No (only fields with the same field base will be searched)'),
+        FILEFIELD_SOURCE_REFERENCE_SEARCH_ALL_YES => t('Yes (all file fields will be searched, regardless of type)'),
+      ),
+      '#type' => 'radios',
+      '#default_value' => isset($settings['source_reference']['search_all_fields']) ? $settings['source_reference']['search_all_fields'] : FILEFIELD_SOURCE_REFERENCE_SEARCH_ALL_NO,
+     );
+
+    return $return;
+  }
+
+}
diff --git a/web/modules/contrib/filefield_sources/src/Plugin/FilefieldSource/Remote.php b/web/modules/contrib/filefield_sources/src/Plugin/FilefieldSource/Remote.php
new file mode 100644 (file)
index 0000000..7bce7b9
--- /dev/null
@@ -0,0 +1,400 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\filefield_sources\Plugin\FilefieldSource\Remote.
+ */
+
+namespace Drupal\filefield_sources\Plugin\FilefieldSource;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\filefield_sources\FilefieldSourceInterface;
+use Symfony\Component\Routing\Route;
+use Drupal\Core\Field\WidgetInterface;
+use Symfony\Component\HttpFoundation\JsonResponse;
+use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Site\Settings;
+use Drupal\Component\Utility\Unicode;
+
+/**
+ * A FileField source plugin to allow downloading a file from a remote server.
+ *
+ * @FilefieldSource(
+ *   id = "remote",
+ *   name = @Translation("Remote URL textfield"),
+ *   label = @Translation("Remote URL"),
+ *   description = @Translation("Download a file from a remote server.")
+ * )
+ */
+class Remote implements FilefieldSourceInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function value(array &$element, &$input, FormStateInterface $form_state) {
+    if (isset($input['filefield_remote']['url']) && strlen($input['filefield_remote']['url']) > 0 && UrlHelper::isValid($input['filefield_remote']['url']) && $input['filefield_remote']['url'] != FILEFIELD_SOURCE_REMOTE_HINT_TEXT) {
+      $field = entity_load('field_config', $element['#entity_type'] . '.' . $element['#bundle'] . '.' . $element['#field_name']);
+      $url = $input['filefield_remote']['url'];
+
+      // Check that the destination is writable.
+      $temporary_directory = 'temporary://';
+      if (!file_prepare_directory($temporary_directory, FILE_MODIFY_PERMISSIONS)) {
+        \Drupal::logger('filefield_sources')->log(E_NOTICE, 'The directory %directory is not writable, because it does not have the correct permissions set.', array('%directory' => drupal_realpath($temporary_directory)));
+        drupal_set_message(t('The file could not be transferred because the temporary directory is not writable.'), 'error');
+        return;
+      }
+
+      // Check that the destination is writable.
+      $directory = $element['#upload_location'];
+      $mode = Settings::get('file_chmod_directory', FILE_CHMOD_DIRECTORY);
+
+      // This first chmod check is for other systems such as S3, which don't
+      // work with file_prepare_directory().
+      if (!drupal_chmod($directory, $mode) && !file_prepare_directory($directory, FILE_CREATE_DIRECTORY)) {
+        \Drupal::logger('filefield_sources')->log(E_NOTICE, 'File %file could not be copied, because the destination directory %destination is not configured correctly.', array('%file' => $url, '%destination' => drupal_realpath($directory)));
+        drupal_set_message(t('The specified file %file could not be copied, because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions. More information is available in the system log.', array('%file' => $url)), 'error');
+        return;
+      }
+
+      // Check the headers to make sure it exists and is within the allowed
+      // size.
+      $ch = curl_init();
+      curl_setopt($ch, CURLOPT_URL, $url);
+      curl_setopt($ch, CURLOPT_HEADER, TRUE);
+      curl_setopt($ch, CURLOPT_NOBODY, TRUE);
+      curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
+      curl_setopt($ch, CURLOPT_HEADERFUNCTION, array(get_called_class(), 'parseHeader'));
+      // Causes a warning if PHP safe mode is on.
+      @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
+      curl_exec($ch);
+      $info = curl_getinfo($ch);
+      if ($info['http_code'] != 200) {
+        curl_setopt($ch, CURLOPT_HTTPGET, TRUE);
+        $file_contents = curl_exec($ch);
+        $info = curl_getinfo($ch);
+      }
+      curl_close($ch);
+
+      if ($info['http_code'] != 200) {
+        switch ($info['http_code']) {
+          case 403:
+            $form_state->setError($element, t('The remote file could not be transferred because access to the file was denied.'));
+            break;
+
+          case 404:
+            $form_state->setError($element, t('The remote file could not be transferred because it was not found.'));
+            break;
+
+          default:
+            $form_state->setError($element, t('The remote file could not be transferred due to an HTTP error (@code).', array('@code' => $info['http_code'])));
+        }
+        return;
+      }
+
+      // Update the $url variable to reflect any redirects.
+      $url = $info['url'];
+      $url_info = parse_url($url);
+
+      // Determine the proper filename by reading the filename given in the
+      // Content-Disposition header. If the server fails to send this header,
+      // fall back on the basename of the URL.
+      //
+      // We prefer to use the Content-Disposition header, because we can then
+      // use URLs like http://example.com/get_file/23 which would otherwise be
+      // rejected because the URL basename lacks an extension.
+      $filename = static::filename();
+      if (empty($filename)) {
+        $filename = rawurldecode(basename($url_info['path']));
+      }
+
+      $pathinfo = pathinfo($filename);
+
+      // Create the file extension from the MIME header if all else has failed.
+      if (empty($pathinfo['extension']) && $extension = static::mimeExtension()) {
+        $filename = $filename . '.' . $extension;
+        $pathinfo = pathinfo($filename);
+      }
+
+      $filename = filefield_sources_clean_filename($filename, $field->getSetting('file_extensions'));
+      $filepath = file_create_filename($filename, $temporary_directory);
+
+      if (empty($pathinfo['extension'])) {
+        $form_state->setError($element, t('The remote URL must be a file and have an extension.'));
+        return;
+      }
+
+      // Perform basic extension check on the file before trying to transfer.
+      $extensions = $field->getSetting('file_extensions');
+      $regex = '/\.(' . preg_replace('/[ +]/', '|', preg_quote($extensions)) . ')$/i';
+      if (!empty($extensions) && !preg_match($regex, $filename)) {
+        $form_state->setError($element, t('Only files with the following extensions are allowed: %files-allowed.', array('%files-allowed' => $extensions)));
+        return;
+      }
+
+      // Check file size based off of header information.
+      if (!empty($element['#upload_validators']['file_validate_size'][0])) {
+        $max_size = $element['#upload_validators']['file_validate_size'][0];
+        $file_size = $info['download_content_length'];
+        if ($file_size > $max_size) {
+          $form_state->setError($element, t('The remote file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => format_size($file_size), '%maxsize' => format_size($max_size))));
+          return;
+        }
+      }
+
+      // Set progress bar information.
+      $options = array(
+        'key' => $element['#entity_type'] . '_' . $element['#bundle'] . '_' . $element['#field_name'] . '_' . $element['#delta'],
+        'filepath' => $filepath,
+      );
+      static::setTransferOptions($options);
+
+      $transfer_success = FALSE;
+      // If we've already downloaded the entire file because the
+      // header-retrieval failed, just ave the contents we have.
+      if (isset($file_contents)) {
+        if ($fp = @fopen($filepath, 'w')) {
+          fwrite($fp, $file_contents);
+          fclose($fp);
+          $transfer_success = TRUE;
+        }
+      }
+      // If we don't have the file contents, download the actual file.
+      else {
+        $ch = curl_init();
+        curl_setopt($ch, CURLOPT_URL, $url);
+        curl_setopt($ch, CURLOPT_HEADER, FALSE);
+        curl_setopt($ch, CURLOPT_WRITEFUNCTION, array(get_called_class(), 'curlWrite'));
+        // Causes a warning if PHP safe mode is on.
+        @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
+        $transfer_success = curl_exec($ch);
+        curl_close($ch);
+      }
+      if ($transfer_success && $file = filefield_sources_save_file($filepath, $element['#upload_validators'], $element['#upload_location'])) {
+        if (!in_array($file->id(), $input['fids'])) {
+          $input['fids'][] = $file->id();
+        }
+      }
+
+      // Delete the temporary file.
+      @unlink($filepath);
+    }
+  }
+
+  /**
+   * Set a transfer key that can be retreived by the progress function.
+   */
+  protected static function setTransferOptions($options = NULL) {
+    static $current = FALSE;
+    if (isset($options)) {
+      $current = $options;
+    }
+    return $current;
+  }
+
+  /**
+   * Get a transfer key that can be retrieved by the progress function.
+   */
+  protected static function getTransferOptions() {
+    return static::setTransferOptions();
+  }
+
+  /**
+   * Save the file to disk. Also updates progress bar.
+   */
+  protected static function curlWrite(&$ch, $data) {
+    $progress_update = 0;
+    $options = static::getTransferOptions();
+
+    // Get the current progress and update the progress value.
+    // Only update every 64KB to reduce Drupal::cache()->set() calls.
+    // cURL usually writes in 16KB chunks.
+    if (curl_getinfo($ch, CURLINFO_SIZE_DOWNLOAD) / 65536 > $progress_update) {
+      $progress_update++;
+      $progress = array(
+        'current' => curl_getinfo($ch, CURLINFO_SIZE_DOWNLOAD),
+        'total' => curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD),
+      );
+      // Set a cache so that we can retrieve this value from the progress bar.
+      $cid = 'filefield_transfer:' . session_id() . ':' . $options['key'];
+      if ($progress['current'] != $progress['total']) {
+        \Drupal::cache()->set($cid, $progress, time() + 300);
+      }
+      else {
+        \Drupal::cache()->delete($cid);
+      }
+    }
+
+    $data_length = 0;
+    if ($fp = @fopen($options['filepath'], 'a')) {
+      fwrite($fp, $data);
+      fclose($fp);
+      $data_length = strlen($data);
+    }
+
+    return $data_length;
+  }
+
+  /**
+   * Parse cURL header and record the filename specified in Content-Disposition.
+   */
+  protected static function parseHeader(&$ch, $header) {
+    if (preg_match('/Content-Disposition:.*?filename="(.+?)"/', $header, $matches)) {
+      // Content-Disposition: attachment; filename="FILE NAME HERE"
+      static::filename($matches[1]);
+    }
+    elseif (preg_match('/Content-Disposition:.*?filename=([^; ]+)/', $header, $matches)) {
+      // Content-Disposition: attachment; filename=file.ext
+      $uri = trim($matches[1]);
+      static::filename($uri);
+    }
+    elseif (preg_match('/Content-Type:[ ]*([a-z0-9_\-]+\/[a-z0-9_\-]+)/i', $header, $matches)) {
+      $mime_type = $matches[1];
+      static::mimeExtension($mime_type);
+    }
+
+    // This is required by cURL.
+    return strlen($header);
+  }
+
+  /**
+   * Get/set the remote file extension in a static variable.
+   */
+  protected static function mimeExtension($curl_mime_type = NULL) {
+    static $extension = NULL;
+    $mimetype = Unicode::strtolower($curl_mime_type);
+    $result = \Drupal::service('file.mime_type.guesser.extension')->convertMimeTypeToMostCommonExtension($mimetype);
+    if ($result) {
+      $extension = $result;
+    }
+    return $extension;
+  }
+
+  /**
+   * Get/set the remote file name in a static variable.
+   */
+  protected static function filename($curl_filename = NULL) {
+    static $filename = NULL;
+    if (isset($curl_filename)) {
+      $filename = $curl_filename;
+    }
+    return $filename;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function process(array &$element, FormStateInterface $form_state, array &$complete_form) {
+
+    $element['filefield_remote'] = array(
+      '#weight' => 100.5,
+      '#theme' => 'filefield_sources_element',
+      '#source_id' => 'remote',
+       // Required for proper theming.
+      '#filefield_source' => TRUE,
+      '#filefield_sources_hint_text' => FILEFIELD_SOURCE_REMOTE_HINT_TEXT,
+    );
+
+    $element['filefield_remote']['url'] = array(
+      '#type' => 'textfield',
+      '#description' => filefield_sources_element_validation_help($element['#upload_validators']),
+      '#maxlength' => NULL,
+    );
+
+    $class = '\Drupal\file\Element\ManagedFile';
+    $ajax_settings = [
+      'callback' => [$class, 'uploadAjaxCallback'],
+      'options' => [
+        'query' => [
+          'element_parents' => implode('/', $element['#array_parents']),
+        ],
+      ],
+      'wrapper' => $element['upload_button']['#ajax']['wrapper'],
+      'effect' => 'fade',
+      'progress' => [
+        'type' => 'bar',
+        'path' => 'file/remote/progress/' . $element['#entity_type'] . '/' . $element['#bundle'] . '/' . $element['#field_name'] . '/' . $element['#delta'],
+        'message' => t('Starting transfer...'),
+      ],
+    ];
+
+    $element['filefield_remote']['transfer'] = [
+      '#name' => implode('_', $element['#parents']) . '_transfer',
+      '#type' => 'submit',
+      '#value' => t('Transfer'),
+      '#validate' => array(),
+      '#submit' => ['filefield_sources_field_submit'],
+      '#limit_validation_errors' => [$element['#parents']],
+      '#ajax' => $ajax_settings,
+    ];
+
+    return $element;
+  }
+
+  /**
+   * Theme the output of the remote element.
+   */
+  public static function element($variables) {
+    $element = $variables['element'];
+
+    $element['url']['#field_suffix'] = drupal_render($element['transfer']);
+    return '<div class="filefield-source filefield-source-remote clear-block">' . drupal_render($element['url']) . '</div>';
+  }
+
+  /**
+   * Menu callback; progress.js callback to return upload progress.
+   */
+  public static function progress($entity_type, $bundle_name, $field_name, $delta) {
+    $key = $entity_type . '_' . $bundle_name . '_' . $field_name . '_' . $delta;
+    $progress = array(
+      'message' => t('Starting transfer...'),
+      'percentage' => -1,
+    );
+
+    if ($cache = \Drupal::cache()->get('filefield_transfer:' . session_id() . ':' . $key)) {
+      $current = $cache->data['current'];
+      $total = $cache->data['total'];
+      $progress['message'] = t('Transferring... (@current of @total)', array('@current' => format_size($current), '@total' => format_size($total)));
+      $progress['percentage'] = round(100 * $current / $total);
+    }
+
+    return new JsonResponse($progress);
+  }
+
+  /**
+   * Define routes for Remote source.
+   *
+   * @return array
+   *   Array of routes.
+   */
+  public static function routes() {
+    $routes = array();
+
+    $routes['filefield_sources.remote'] = new Route(
+      '/file/remote/progress/{entity_type}/{bundle_name}/{field_name}/{delta}',
+      array(
+        '_controller' => get_called_class() . '::progress',
+      ),
+      array(
+        '_access' => 'TRUE',
+      )
+    );
+
+    return $routes;
+  }
+
+  /**
+   * Implements hook_filefield_source_settings().
+   */
+  public static function settings(WidgetInterface $plugin) {
+    $return = array();
+
+    // Add settings to the FileField widget form.
+    if (!filefield_sources_curl_enabled()) {
+      drupal_set_message(t('<strong>Filefield sources:</strong> remote plugin will be disabled without php-curl extension.'), 'warning');
+    }
+
+    return $return;
+
+  }
+
+}
diff --git a/web/modules/contrib/filefield_sources/src/ProxyClass/File/MimeType/ExtensionMimeTypeGuesser.php b/web/modules/contrib/filefield_sources/src/ProxyClass/File/MimeType/ExtensionMimeTypeGuesser.php
new file mode 100644 (file)
index 0000000..178bcf6
--- /dev/null
@@ -0,0 +1,108 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\filefield_sources\ProxyClass\File\MimeType\ExtensionMimeTypeGuesser.
+ */
+
+/**
+ * This file was generated via php core/scripts/generate-proxy-class.php 'Drupal\filefield_sources\File\MimeType\ExtensionMimeTypeGuesser' "modules/filefield_sources/src".
+ */
+
+namespace Drupal\filefield_sources\ProxyClass\File\MimeType {
+
+    /**
+     * Provides a proxy class for \Drupal\filefield_sources\File\MimeType\ExtensionMimeTypeGuesser.
+     *
+     * @see \Drupal\Component\ProxyBuilder
+     */
+    class ExtensionMimeTypeGuesser implements \Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface
+    {
+
+        use \Drupal\Core\DependencyInjection\DependencySerializationTrait;
+
+        /**
+         * The id of the original proxied service.
+         *
+         * @var string
+         */
+        protected $drupalProxyOriginalServiceId;
+
+        /**
+         * The real proxied service, after it was lazy loaded.
+         *
+         * @var \Drupal\filefield_sources\File\MimeType\ExtensionMimeTypeGuesser
+         */
+        protected $service;
+
+        /**
+         * The service container.
+         *
+         * @var \Symfony\Component\DependencyInjection\ContainerInterface
+         */
+        protected $container;
+
+        /**
+         * Constructs a ProxyClass Drupal proxy object.
+         *
+         * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
+         *   The container.
+         * @param string $drupal_proxy_original_service_id
+         *   The service ID of the original service.
+         */
+        public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container, $drupal_proxy_original_service_id)
+        {
+            $this->container = $container;
+            $this->drupalProxyOriginalServiceId = $drupal_proxy_original_service_id;
+        }
+
+        /**
+         * Lazy loads the real service from the container.
+         *
+         * @return object
+         *   Returns the constructed real service.
+         */
+        protected function lazyLoadItself()
+        {
+            if (!isset($this->service)) {
+                $this->service = $this->container->get($this->drupalProxyOriginalServiceId);
+            }
+
+            return $this->service;
+        }
+
+        /**
+         * {@inheritdoc}
+         */
+        public function convertMimeTypeToExtension($mimetype)
+        {
+            return $this->lazyLoadItself()->convertMimeTypeToExtension($mimetype);
+        }
+
+        /**
+         * {@inheritdoc}
+         */
+        public function convertMimeTypeToMostCommonExtension($mimetype)
+        {
+            return $this->lazyLoadItself()->convertMimeTypeToMostCommonExtension($mimetype);
+        }
+
+        /**
+         * {@inheritdoc}
+         */
+        public function guess($path)
+        {
+            return $this->lazyLoadItself()->guess($path);
+        }
+
+        /**
+         * {@inheritdoc}
+         */
+        public function setMapping(array $mapping = NULL)
+        {
+            return $this->lazyLoadItself()->setMapping($mapping);
+        }
+
+    }
+
+}
diff --git a/web/modules/contrib/filefield_sources/src/Routing/FilefieldSourcesRoutes.php b/web/modules/contrib/filefield_sources/src/Routing/FilefieldSourcesRoutes.php
new file mode 100644 (file)
index 0000000..0530566
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\filefield_sources\Routing\FilefieldSourcesRoutes.
+ */
+
+namespace Drupal\filefield_sources\Routing;
+
+/**
+ * Defines a route subscriber to register a url for serving filefield sources.
+ */
+class FilefieldSourcesRoutes {
+
+  /**
+   * Returns an array of route objects.
+   *
+   * @return \Symfony\Component\Routing\Route[]
+   *   An array of route objects.
+   */
+  public function routes() {
+    $routes = array();
+
+    foreach (\Drupal::service('filefield_sources')->getDefinitions() as $definition) {
+      // Get routes defined by each plugin.
+      $callback = array($definition['class'], 'routes');
+      if (is_callable($callback)) {
+        $routes = array_merge($routes, call_user_func($callback));
+      }
+    }
+
+    return $routes;
+  }
+
+}
diff --git a/web/modules/contrib/filefield_sources/src/Tests/AttachSourceTest.php b/web/modules/contrib/filefield_sources/src/Tests/AttachSourceTest.php
new file mode 100644 (file)
index 0000000..71ff7b1
--- /dev/null
@@ -0,0 +1,168 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\filefield_sources\Tests\AttachSourceTest.
+ */
+
+namespace Drupal\filefield_sources\Tests;
+
+use Drupal\Component\Render\PlainTextOutput;
+
+/**
+ * Tests the attach source.
+ *
+ * @group filefield_sources
+ */
+class AttachSourceTest extends FileFieldSourcesTestBase {
+
+  /**
+   * Check to see if a option is present.
+   *
+   * @param string $uri
+   *   The option to check.
+   *
+   * @return bool
+   *   TRUE if the option is present, FALSE otherwise.
+   */
+  public function isOptionPresent($uri) {
+    $options = $this->xpath('//select[@name=:name]/option[@value=:option]', array(
+      ':name' => $this->fieldName . '[0][filefield_attach][filename]',
+      ':option' => $uri,
+    ));
+    return isset($options[0]);
+  }
+
+  /**
+   * Check to see if can attach file.
+   *
+   * @param object $file
+   *   File to attach.
+   */
+  public function assertCanAttachFile($file) {
+    // Ensure option is present.
+    $this->assertTrue($this->isOptionPresent($file->uri), 'File option is present.');
+
+    // Ensure empty message is not present.
+    $this->assertNoText('There currently are no files to attach.', "Empty message is not present.");
+
+    // Attach button is always present.
+    $this->assertFieldByXpath('//input[@type="submit"]', t('Attach'), 'Attach button is present.');
+  }
+
+  /**
+   * Check to see if can attach file.
+   *
+   * @param object $file
+   *   File to attach.
+   */
+  public function assertCanNotAttachFile($file) {
+    // Ensure option is not present.
+    $this->assertFalse($this->isOptionPresent($file->uri), 'File option is not present.');
+
+    // Ensure empty message is present.
+    $this->assertText('There currently are no files to attach.', "Empty message is present.");
+
+    // Attach button is always present.
+    $this->assertFieldByXpath('//input[@type="submit"]', t('Attach'), 'Attach button is present.');
+  }
+
+  /**
+   * Tests move file from relative path.
+   *
+   * Default settings: Move file from 'public://file_attach' to 'public://'.
+   */
+  public function testMoveFileFromRelativePath() {
+    $uri_scheme = $this->getFieldSetting('uri_scheme');
+    $path = $uri_scheme . '://' . FILEFIELD_SOURCE_ATTACH_DEFAULT_PATH . '/';
+
+    // Create test file.
+    $file = $this->createTemporaryFile($path);
+    $dest_uri = $this->getDestinationUri($file, $uri_scheme);
+
+    $this->enableSources(array(
+      'attach' => TRUE,
+    ));
+
+    $this->assertCanAttachFile($file);
+
+    // Upload a file.
+    $this->uploadFileByAttachSource($file->uri, $file->filename, 0);
+
+    // We can only attach one file on single value field.
+    $this->assertNoFieldByXPath('//input[@type="submit"]', t('Attach'), 'After uploading a file, "Attach" button is no longer displayed.');
+
+    // Ensure file is moved.
+    $this->assertFalse(is_file($file->uri), 'Source file has been removed.');
+    $this->assertTrue(is_file($dest_uri), 'Destination file has been created.');
+
+    $this->removeFile($file->filename, 0);
+
+    $this->assertCanNotAttachFile($file);
+  }
+
+  /**
+   * Calculate custom absolute path.
+   */
+  public function getCustomAttachPath() {
+    $path = drupal_realpath($this->getFieldSetting('uri_scheme') . '://');
+    $path = str_replace(realpath('./'), '', $path);
+    $path = ltrim($path, '/');
+    $path = $path . '/custom_file_attach/';
+    return $path;
+  }
+
+  /**
+   * Tests copy file from absolute path.
+   *
+   * Copy file from 'sites/default/files/custom_file_attach' to 'public://'.
+   */
+  public function testCopyFileFromAbsolutePath() {
+    $uri_scheme = $this->getFieldSetting('uri_scheme');
+    $path = $this->getCustomAttachPath();
+
+    // Create test file.
+    $file = $this->createTemporaryFile($path);
+    $dest_uri = $this->getDestinationUri($file, $uri_scheme);
+
+    // Change settings.
+    $this->updateFilefieldSourcesSettings('source_attach', 'path', $path);
+    $this->updateFilefieldSourcesSettings('source_attach', 'absolute', FILEFIELD_SOURCE_ATTACH_ABSOLUTE);
+    $this->updateFilefieldSourcesSettings('source_attach', 'attach_mode', FILEFIELD_SOURCE_ATTACH_MODE_COPY);
+
+    $this->enableSources(array(
+      'attach' => TRUE,
+    ));
+
+    $this->assertCanAttachFile($file);
+
+    // Upload a file.
+    $this->uploadFileByAttachSource($file->uri, $file->filename, 0);
+
+    // We can only attach one file on single value field.
+    $this->assertNoFieldByXPath('//input[@type="submit"]', t('Attach'), 'After uploading a file, "Attach" button is no longer displayed.');
+
+    // Ensure file is copied.
+    $this->assertTrue(is_file($file->uri), 'Source file still exists.');
+    $this->assertTrue(is_file($dest_uri), 'Destination file has been created.');
+
+    $this->removeFile($file->filename, 0);
+
+    $this->assertCanAttachFile($file);
+  }
+
+  /**
+   * Get destination uri of a .
+   *
+   * @param object $file
+   *   File.
+   * @param string $uri_scheme
+   *   Uri scheme.
+   */
+  public function getDestinationUri($file, $uri_scheme) {
+    $destination = trim($this->getFieldSetting('file_directory'), '/');
+    $destination = PlainTextOutput::renderFromHtml(\Drupal::token()->replace($destination));
+    return $uri_scheme . '://' . $destination . '/' . $file->filename;
+  }
+
+}
diff --git a/web/modules/contrib/filefield_sources/src/Tests/ClipboardSourceTest.php b/web/modules/contrib/filefield_sources/src/Tests/ClipboardSourceTest.php
new file mode 100644 (file)
index 0000000..215883a
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\filefield_sources\Tests\ClipboardSourceTest.
+ */
+
+namespace Drupal\filefield_sources\Tests;
+
+/**
+ * Tests the clipboard source.
+ *
+ * @group filefield_sources
+ */
+class ClipboardSourceTest extends FileFieldSourcesTestBase {
+
+  /**
+   * Tests clipboard source enabled.
+   */
+  public function testClipboardSourceEnabled() {
+    $this->enableSources(array(
+      'clipboard' => TRUE,
+    ));
+    $file = $this->createTemporaryFileEntity();
+
+    $this->uploadFileByClipboardSource($file->getFileUri(), $file->getFilename(), 0);
+
+    // We can only upload one file on single value field.
+    $this->assertNoFieldByXPath('//input[@type="submit"]', t('Upload'), t('After uploading a file, "Upload" button is no longer displayed.'));
+
+    $this->removeFile($file->getFilename(), 0);
+
+    // Can upload file again.
+    $this->assertFieldByXpath('//input[@type="submit"]', t('Upload'), 'After clicking the "Remove" button, the "Upload" button is displayed.');
+  }
+
+}
diff --git a/web/modules/contrib/filefield_sources/src/Tests/EmptyValuesTest.php b/web/modules/contrib/filefield_sources/src/Tests/EmptyValuesTest.php
new file mode 100644 (file)
index 0000000..065ff57
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\filefield_sources\Tests\EmptyValuesTest.
+ */
+
+namespace Drupal\filefield_sources\Tests;
+
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+
+/**
+ * Tests empty values.
+ *
+ * @group filefield_sources
+ */
+class EmptyValuesTest extends FileFieldSourcesTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('imce');
+
+  /**
+   * Sets up for empty values test case.
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->setUpImce();
+  }
+
+  /**
+   * Tests all sources enabled.
+   */
+  public function testAllSourcesEnabled() {
+    // Change allowed number of values.
+    $this->drupalPostForm('admin/structure/types/manage/' . $this->typeName . '/fields/node.' . $this->typeName . '.' . $this->fieldName . '/storage', array('cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED), t('Save field settings'));
+
+    $this->enableSources(array(
+      'upload' => TRUE,
+      'remote' => TRUE,
+      'clipboard' => TRUE,
+      'reference' => TRUE,
+      'attach' => TRUE,
+      'imce' => TRUE,
+    ));
+
+    // Upload a file by 'Remote' source.
+    $this->uploadFileByRemoteSource();
+
+    // Upload a file by 'Reference' source.
+    $this->uploadFileByReferenceSource();
+
+    // Upload a file by 'Clipboard' source.
+    $this->uploadFileByClipboardSource();
+
+    // Upload a file by 'Attach' source.
+    $this->uploadFileByAttachSource();
+
+    // Upload a file by 'Upload' source.
+    $this->uploadFileByUploadSource('', '', 0, TRUE);
+
+    // Upload a file by 'Imce' source.
+    $this->uploadFileByImceSource();
+
+    $this->assertUniqueSubmitButtons();
+  }
+
+  /**
+   * Check that there is only one submit button of a source.
+   */
+  protected function assertUniqueSubmitButtons() {
+    $buttons = array(
+      $this->fieldName . '_0_attach' => t('Attach'),
+      $this->fieldName . '_0_clipboard_upload_button' => t('Upload'),
+      $this->fieldName . '_0_autocomplete_select' => t('Select'),
+      $this->fieldName . '_0_transfer' => t('Transfer'),
+      $this->fieldName . '_0_upload_button' => t('Upload'),
+      $this->fieldName . '_0_imce_select' => t('Select'),
+    );
+    foreach ($buttons as $button_name => $button_label) {
+      // Ensure that there is only one button with name.
+      $buttons = $this->xpath('//input[@name="' . $button_name . '" and @value="' . $button_label . '"]');
+      $this->assertEqual(count($buttons), 1, format_string('There is only one button with name %name and label %label', array('%name' => $button_name, '%label' => $button_label)));
+    }
+  }
+
+}
diff --git a/web/modules/contrib/filefield_sources/src/Tests/FileFieldSourcesTestBase.php b/web/modules/contrib/filefield_sources/src/Tests/FileFieldSourcesTestBase.php
new file mode 100644 (file)
index 0000000..14a16da
--- /dev/null
@@ -0,0 +1,450 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\filefield_sources\Tests\FileFieldSourcesTestBase.
+ */
+
+namespace Drupal\filefield_sources\Tests;
+
+use Drupal\file\Tests\FileFieldTestBase;
+use Drupal\simpletest\WebTestBase;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\user\Entity\Role;
+
+/**
+ * Base class for File Field Sources test cases.
+ */
+abstract class FileFieldSourcesTestBase extends FileFieldTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('filefield_sources');
+
+  protected $adminUser;
+
+  protected $typeName;
+  protected $fieldName;
+  protected $node;
+
+  /**
+   * Sets up for file field sources test cases.
+   */
+  protected function setUp() {
+    WebTestBase::setUp();
+
+    // Create admin user, then login.
+    $this->adminUser = $this->drupalCreateUser(array(
+      'access content',
+      'access administration pages',
+      'administer site configuration',
+      'administer users',
+      'administer permissions',
+      'administer content types',
+      'administer node fields',
+      'administer node display',
+      'administer node form display',
+      'administer nodes',
+      'bypass node access',
+    ));
+    $this->drupalLogin($this->adminUser);
+
+    // Create content type.
+    $this->typeName = 'article';
+    $this->drupalCreateContentType(array('type' => $this->typeName, 'name' => 'Article'));
+
+    // Add node.
+    $this->node = $this->drupalCreateNode();
+
+    // Add file field.
+    $this->fieldName = strtolower($this->randomMachineName());
+    $this->createFileField($this->fieldName, 'node', $this->typeName);
+  }
+
+  /**
+   * Sets up for imce test cases.
+   */
+  protected function setUpImce() {
+    foreach ($this->adminUser->getRoles(TRUE) as $rid) {
+      // Grant permission.
+      $role = Role::load($rid);
+      $this->grantPermissions($role, ['administer imce']);
+      // Assign member profile to user's role.
+      $edit["roles_profiles[$rid][public]"] = 'member';
+      $this->drupalPostForm('admin/config/media/imce', $edit, t('Save configuration'));
+    }
+  }
+
+  /**
+   * Enable file field sources.
+   *
+   * @param array $sources
+   *   List of sources to enable or disable. e.g
+   *   array(
+   *     'upload' => FALSE,
+   *     'remote' => TRUE,
+   *   ).
+   */
+  public function enableSources($sources = array()) {
+    $sources += array('upload' => TRUE);
+    $map = array(
+      'upload' => 'Upload',
+      'remote' => 'Remote URL',
+      'clipboard' => 'Clipboard',
+      'reference' => 'Reference existing',
+      'attach' => 'File attach',
+      'imce' => 'File browser',
+    );
+    $sources = array_intersect_key($sources, $map);
+    ksort($sources);
+
+    // Upload source enabled by default.
+    $manage_display = 'admin/structure/types/manage/' . $this->typeName . '/form-display';
+    $this->drupalGet($manage_display);
+    $this->assertText("File field sources: upload", 'The expected summary is displayed.');
+
+    // Click on the widget settings button to open the widget settings form.
+    $this->drupalPostAjaxForm(NULL, array(), $this->fieldName . "_settings_edit");
+
+    // Enable sources.
+    $prefix = 'fields[' . $this->fieldName . '][settings_edit_form][third_party_settings][filefield_sources][filefield_sources][sources]';
+    $edit = array();
+    foreach ($sources as $source => $enabled) {
+      $edit[$prefix . '[' . $source . ']'] = $enabled ? TRUE : FALSE;
+    }
+    $this->drupalPostAjaxForm(NULL, $edit, array($this->fieldName . '_plugin_settings_update' => t('Update')));
+    $this->assertText("File field sources: " . implode(', ', array_keys($sources)), 'The expected summary is displayed.');
+
+    // Save the form to save the third party settings.
+    $this->drupalPostForm(NULL, array(), t('Save'));
+
+    $add_node = 'node/add/' . $this->typeName;
+    $this->drupalGet($add_node);
+    if (count($sources) > 1) {
+      // We can swith between sources.
+      foreach ($sources as $source => $enabled) {
+        $label = $map[$source];
+        $this->assertLink($label);
+      }
+    }
+    else {
+      foreach ($map as $source => $label) {
+        $this->assertNoLink($label);
+      }
+    }
+  }
+
+  /**
+   * Create permanent file entity.
+   *
+   * @return object
+   *   Permanent file entity.
+   */
+  public function createPermanentFileEntity() {
+    $file = $this->createTemporaryFileEntity();
+    // Only permanent file can be referred.
+    $file->status = FILE_STATUS_PERMANENT;
+    // Author has permission to access file.
+    $file->uid = $this->adminUser->id();
+    $file->save();
+
+    // Permanent file must be used by an entity.
+    \Drupal::service('file.usage')->add($file, 'file', 'node', $this->node->id());
+
+    return $file;
+  }
+
+  /**
+   * Create temporary file entity.
+   *
+   * @return object
+   *   Temporary file entity.
+   */
+  public function createTemporaryFileEntity() {
+    $file = $this->createTemporaryFile();
+
+    // Add a filesize property to files as would be read by file_load().
+    $file->filesize = filesize($file->uri);
+
+    return entity_create('file', (array) $file);
+  }
+
+  /**
+   * Create temporary file.
+   *
+   * @return object
+   *   Permanent file object.
+   */
+  public function createTemporaryFile($path = '') {
+    $filename = $this->randomMachineName() . '.txt';
+    if (empty($path)) {
+      $path = file_default_scheme() . '://';
+    }
+    $uri = $path . $filename;
+    $contents = $this->randomString();
+
+    // Change mode so that we can create files.
+    file_prepare_directory($path, FILE_CREATE_DIRECTORY);
+    drupal_chmod($path, FILE_CHMOD_DIRECTORY);
+
+    file_put_contents($uri, $contents);
+    $this->assertTrue(is_file($uri), 'The temporary file has been created.');
+
+    // Change mode so that we can delete created file.
+    drupal_chmod($uri, FILE_CHMOD_FILE);
+
+    // Return object similar to file_scan_directory().
+    $file = new \stdClass();
+    $file->uri = $uri;
+    $file->filename = $filename;
+    $file->name = pathinfo($filename, PATHINFO_FILENAME);
+    return $file;
+  }
+
+  /**
+   * Update file field sources settings.
+   *
+   * @param string $source_key
+   *   Wrapper, defined by each source.
+   * @param string $key
+   *   Key, defined by each source.
+   * @param mixed $value
+   *   Value to set.
+   */
+  public function updateFilefieldSourcesSettings($source_key, $key, $value) {
+    $manage_display = 'admin/structure/types/manage/' . $this->typeName . '/form-display';
+    $this->drupalGet($manage_display);
+
+    // Click on the widget settings button to open the widget settings form.
+    $this->drupalPostAjaxForm(NULL, array(), $this->fieldName . "_settings_edit");
+
+    // Update settings.
+    $name = 'fields[' . $this->fieldName . '][settings_edit_form][third_party_settings][filefield_sources][filefield_sources]' . "[$source_key][$key]";
+    $edit = array($name => $value);
+    $this->drupalPostAjaxForm(NULL, $edit, array($this->fieldName . '_plugin_settings_update' => t('Update')));
+
+    // Save the form to save the third party settings.
+    $this->drupalPostForm(NULL, array(), t('Save'));
+  }
+
+  /**
+   * Upload file by 'Attach' source.
+   *
+   * @param string $uri
+   *   File uri.
+   * @param string $filename
+   *   File name.
+   * @param int $delta
+   *   Delta in multiple values field.
+   */
+  public function uploadFileByAttachSource($uri = '', $filename = '', $delta = 0) {
+    if ($uri) {
+      $edit = array(
+        $this->fieldName . '[' . $delta . '][filefield_attach][filename]' => $uri,
+      );
+    }
+    else {
+      $edit = array();
+    }
+    $this->drupalPostAjaxForm(NULL, $edit, array($this->fieldName . '_' . $delta . '_attach' => t('Attach')));
+
+    if ($filename) {
+      $this->assertFileUploaded($filename, $delta);
+    }
+    else {
+      $this->assertFileNotUploaded($delta);
+    }
+  }
+
+  /**
+   * Upload file by 'Reference' source.
+   *
+   * @param int $fid
+   *   File id.
+   * @param string $filename
+   *   File name.
+   * @param int $delta
+   *   Delta in multiple values field.
+   */
+  public function uploadFileByReferenceSource($fid = 0, $filename = '', $delta = 0) {
+    $name = $this->fieldName . '[' . $delta . '][filefield_reference][autocomplete]';
+    $value = $fid ? $filename . ' [fid:' . $fid . ']' : '';
+    $edit = array($name => $value);
+    $this->drupalPostAjaxForm(NULL, $edit, array($this->fieldName . '_' . $delta . '_autocomplete_select' => t('Select')));
+
+    if ($filename) {
+      $this->assertFileUploaded($filename, $delta);
+    }
+    else {
+      $this->assertFileNotUploaded($delta);
+    }
+  }
+
+  /**
+   * Upload file by 'Clipboard' source.
+   *
+   * @param string $uri
+   *   File uri.
+   * @param string $filename
+   *   File name.
+   * @param int $delta
+   *   Delta in multiple values field.
+   */
+  public function uploadFileByClipboardSource($uri = '', $filename = '', $delta = 0) {
+    $prefix = $this->fieldName . '[' . $delta . '][filefield_clipboard]';
+    $file_content = $uri ? 'data:text/plain;base64,' . base64_encode(file_get_contents($uri)) : '';
+    $edit = array(
+      $prefix . '[filename]' => $filename,
+      $prefix . '[contents]' => $file_content,
+    );
+    $this->drupalPostAjaxForm(NULL, $edit, array($this->fieldName . '_' . $delta . '_clipboard_upload_button' => t('Upload')));
+
+    if ($filename) {
+      $this->assertFileUploaded($filename, $delta);
+    }
+    else {
+      $this->assertFileNotUploaded($delta);
+    }
+  }
+
+  /**
+   * Upload file by 'Imce' source.
+   *
+   * @param string $uri
+   *   File uri.
+   * @param string $filename
+   *   File name.
+   * @param int $delta
+   *   Delta in multiple values field.
+   */
+  public function uploadFileByImceSource($uri = '', $filename = '', $delta = 0) {
+    $scheme = parse_url($uri, PHP_URL_SCHEME);
+    $imce_path = str_replace("$scheme://", '', $uri);
+    $edit = array(
+      $this->fieldName . '[' . $delta . '][filefield_imce][imce_paths]' => $imce_path,
+    );
+    $this->drupalPostAjaxForm(NULL, $edit, array($this->fieldName . '_' . $delta . '_imce_select' => t('Select')));
+
+
+    if ($filename) {
+      $this->assertFileUploaded($filename, $delta);
+    }
+    else {
+      $this->assertFileNotUploaded($delta);
+    }
+  }
+
+  /**
+   * Upload file by 'Remote' source.
+   *
+   * @param string $url
+   *   File url.
+   * @param string $filename
+   *   File name.
+   * @param int $delta
+   *   Delta in multiple values field.
+   */
+  public function uploadFileByRemoteSource($url = '', $filename = '', $delta = 0) {
+    $name = $this->fieldName . '[' . $delta . '][filefield_remote][url]';
+    $edit = array($name => $url);
+    $this->drupalPostAjaxForm(NULL, $edit, array($this->fieldName . '_' . $delta . '_transfer' => t('Transfer')));
+
+    if ($filename) {
+      $this->assertFileUploaded($filename, $delta);
+    }
+    else {
+      $this->assertFileNotUploaded($delta);
+    }
+  }
+
+  /**
+   * Upload file by 'Upload' source.
+   *
+   * @param string $uri
+   *   File uri.
+   * @param string $filename
+   *   File name.
+   * @param int $delta
+   *   Delta in multiple values field.
+   */
+  public function uploadFileByUploadSource($uri = '', $filename = '', $delta = 0, $multiple = FALSE) {
+    $name = 'files[' . $this->fieldName . '_' . $delta . ']';
+    if ($multiple) {
+      $name .= '[]';
+    }
+    $edit = array(
+      $name => $uri ? drupal_realpath($uri) : '',
+    );
+    $this->drupalPostAjaxForm(NULL, $edit, array($this->fieldName . '_' . $delta . '_upload_button' => t('Upload')));
+
+    if ($filename) {
+      $this->assertFileUploaded($filename, $delta);
+    }
+    else {
+      $this->assertFileNotUploaded($delta);
+    }
+  }
+
+  /**
+   * Check to see if file is uploaded.
+   *
+   * @param string $filename
+   *   File name.
+   * @param int $delta
+   *   Delta in multiple values field.
+   */
+  public function assertFileUploaded($filename, $delta = 0) {
+    $this->assertLink($filename);
+    $this->assertFieldByXPath('//input[@name="' . $this->fieldName . '_' . $delta . '_remove_button"]', t('Remove'), 'After uploading a file, "Remove" button is displayed.');
+  }
+
+  /**
+   * Check to see if file is not uploaded.
+   *
+   * @param int $delta
+   *   Delta in multiple values field.
+   */
+  public function assertFileNotUploaded($delta = 0) {
+    $this->assertNoFieldByXPath('//input[@name="' . $this->fieldName . '_' . $delta . '_remove_button"]', t('Remove'), '"Remove" button is not displayed.');
+  }
+
+  /**
+   * Remove uploaded file.
+   *
+   * @param string $filename
+   *   File name.
+   * @param int $delta
+   *   Delta in multiple values field.
+   */
+  public function removeFile($filename, $delta = 0) {
+    $this->drupalPostAjaxForm(NULL, array(), array($this->fieldName . '_' . $delta . '_remove_button' => t('Remove')));
+
+    // Ensure file is removed.
+    $this->assertFileRemoved($filename);
+  }
+
+  /**
+   * Check to see if file is removed.
+   *
+   * @param string $filename
+   *   File name.
+   */
+  public function assertFileRemoved($filename) {
+    $this->assertNoLink($filename);
+  }
+
+  /**
+   * Get field setting.
+   *
+   * @param string $setting_name
+   *   Setting name.
+   */
+  public function getFieldSetting($setting_name) {
+    $field_definition = FieldConfig::load("node.{$this->typeName}.{$this->fieldName}");
+    return $field_definition->getSetting($setting_name);
+  }
+
+}
diff --git a/web/modules/contrib/filefield_sources/src/Tests/ImceSourceTest.php b/web/modules/contrib/filefield_sources/src/Tests/ImceSourceTest.php
new file mode 100644 (file)
index 0000000..5b0ea2c
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\filefield_sources\Tests\ClipboardSourceTest.
+ */
+
+namespace Drupal\filefield_sources\Tests;
+
+/**
+ * Tests the imce source.
+ *
+ * @group filefield_sources
+ */
+class ImceSourceTest extends FileFieldSourcesTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('imce');
+
+  /**
+   * Sets up for imce source test case.
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->setUpImce();
+  }
+
+  /**
+   * Tests imce source enabled.
+   */
+  public function testImceSourceEnabled() {
+    $this->enableSources(array(
+      'imce' => TRUE,
+    ));
+    $file = $this->createPermanentFileEntity();
+
+    $this->uploadFileByImceSource($file->getFileUri(), $file->getFilename(), 0);
+
+    // We can only upload one file on single value field.
+    $this->assertNoFieldByXPath('//input[@type="submit"]', t('Select'), t('After uploading a file, "Select" button is no longer displayed.'));
+
+    $this->removeFile($file->getFilename(), 0);
+
+    // Can upload file again.
+    $this->assertFieldByXpath('//input[@type="submit"]', t('Select'), 'After clicking the "Remove" button, the "Select" button is displayed.');
+  }
+
+}
diff --git a/web/modules/contrib/filefield_sources/src/Tests/MultipleValuesTest.php b/web/modules/contrib/filefield_sources/src/Tests/MultipleValuesTest.php
new file mode 100644 (file)
index 0000000..97052ee
--- /dev/null
@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\filefield_sources\Tests\MultipleValuesTest.
+ */
+
+namespace Drupal\filefield_sources\Tests;
+
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+
+/**
+ * Tests multiple sources on multiple values field.
+ *
+ * @group filefield_sources
+ */
+class MultipleValuesTest extends FileFieldSourcesTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('imce');
+
+  /**
+   * Sets up for multiple values test case.
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->setUpImce();
+
+    // Create test files.
+    $this->permanent_file_entity_1 = $this->createPermanentFileEntity();
+    $this->permanent_file_entity_2 = $this->createPermanentFileEntity();
+    $this->temporary_file_entity_1 = $this->createTemporaryFileEntity();
+    $this->temporary_file_entity_2 = $this->createTemporaryFileEntity();
+
+    $path = file_default_scheme() . '://' . FILEFIELD_SOURCE_ATTACH_DEFAULT_PATH . '/';
+    $this->temporary_file = $this->createTemporaryFile($path);
+
+    // Change allowed number of values.
+    $this->drupalPostForm('admin/structure/types/manage/' . $this->typeName . '/fields/node.' . $this->typeName . '.' . $this->fieldName . '/storage', array('cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED), t('Save field settings'));
+
+    $this->enableSources(array(
+      'upload' => TRUE,
+      'remote' => TRUE,
+      'clipboard' => TRUE,
+      'reference' => TRUE,
+      'attach' => TRUE,
+      'imce' => TRUE,
+    ));
+  }
+
+  /**
+   * Tests uploading then removing files.
+   */
+  public function testUploadThenRemoveFiles() {
+    $this->uploadFiles();
+
+    // Remove all uploaded files.
+    $this->removeFile($this->temporary_file_entity_2->getFilename(), 4);
+    $this->removeFile('INSTALL.txt', 0);
+    $this->removeFile($this->temporary_file_entity_1->getFilename(), 1);
+    $this->removeFile($this->temporary_file->filename, 1);
+    $this->removeFile($this->permanent_file_entity_1->getFilename(), 0);
+    $this->removeFile($this->permanent_file_entity_2->getFilename(), 0);
+
+    // Ensure all files have been removed.
+    $this->assertNoFieldByXPath('//input[@type="submit"]', t('Remove'), 'All files have been removed.');
+  }
+
+  /**
+   * Tests uploading files and saving node.
+   */
+  public function testUploadFilesThenSaveNode() {
+    $this->uploadFiles();
+
+    $this->drupalPostForm(NULL, array('title[0][value]' => $this->randomMachineName()), t('Save and publish'));
+
+    // Ensure all files are saved to node.
+    $this->assertLink('INSTALL.txt');
+    $this->assertLink($this->permanent_file_entity_1->getFilename());
+    $this->assertLink($this->permanent_file_entity_2->getFilename());
+    $this->assertLink($this->temporary_file_entity_1->getFilename());
+    $this->assertLink($this->temporary_file_entity_2->getFilename());
+    $this->assertLink($this->temporary_file->filename);
+  }
+
+  /**
+   * Upload files.
+   *
+   * @return int
+   *   Number of files uploaded.
+   */
+  protected function uploadFiles() {
+    $uploaded_files = 0;
+
+    // Ensure no files has been uploaded.
+    $this->assertNoFieldByXPath('//input[@type="submit"]', t('Remove'), 'There are no file have been uploaded.');
+
+    // Upload a file by 'Remote' source.
+    $this->uploadFileByRemoteSource($GLOBALS['base_url'] . '/core/INSTALL.txt', 'INSTALL.txt', $uploaded_files);
+    $uploaded_files++;
+
+    // Upload a file by 'Reference' source.
+    $this->uploadFileByReferenceSource($this->permanent_file_entity_1->id(), $this->permanent_file_entity_1->getFilename(), $uploaded_files);
+    $uploaded_files++;
+
+    // Upload a file by 'Clipboard' source.
+    $this->uploadFileByClipboardSource($this->temporary_file_entity_1->getFileUri(), $this->temporary_file_entity_1->getFileName(), $uploaded_files);
+    $uploaded_files++;
+
+    // Upload a file by 'Attach' source.
+    $this->uploadFileByAttachSource($this->temporary_file->uri, $this->temporary_file->filename, $uploaded_files);
+    $uploaded_files++;
+
+    // Upload a file by 'Upload' source.
+    $this->uploadFileByUploadSource($this->temporary_file_entity_2->getFileUri(), $this->temporary_file_entity_2->getFilename(), $uploaded_files, TRUE);
+    $uploaded_files++;
+
+    // Upload a file by 'Imce' source.
+    $this->uploadFileByImceSource($this->permanent_file_entity_2->getFileUri(), $this->permanent_file_entity_2->getFileName(), $uploaded_files);
+    $uploaded_files++;
+
+    // Ensure files have been uploaded.
+    $remove_buttons = $this->xpath('//input[@type="submit" and @value="' . t('Remove') . '"]');
+    $this->assertEqual(count($remove_buttons), $uploaded_files, "There are $uploaded_files files have been uploaded.");
+
+    return $uploaded_files;
+  }
+
+}
diff --git a/web/modules/contrib/filefield_sources/src/Tests/ReferenceSourceTest.php b/web/modules/contrib/filefield_sources/src/Tests/ReferenceSourceTest.php
new file mode 100644 (file)
index 0000000..9b39278
--- /dev/null
@@ -0,0 +1,109 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\filefield_sources\Tests\ReferenceSourceTest.
+ */
+
+namespace Drupal\filefield_sources\Tests;
+
+use Drupal\Component\Utility\Unicode;
+
+/**
+ * Tests the reference source.
+ *
+ * @group filefield_sources
+ */
+class ReferenceSourceTest extends FileFieldSourcesTestBase {
+
+  /**
+   * Tests reference source enabled.
+   */
+  public function testReferenceSourceEnabled() {
+
+    // Create test file.
+    $file = $this->createPermanentFileEntity();
+
+    $this->enableSources(array(
+      'reference' => TRUE,
+    ));
+
+    // Upload a file by 'Reference' source.
+    $this->uploadFileByReferenceSource($file->id(), $file->getFilename(), 0);
+
+    // We can only refer one file on single value field.
+    $this->assertNoFieldByXPath('//input[@type="submit"]', t('Select'), t('After uploading a file, "Select" button is no longer displayed.'));
+
+    // Remove uploaded file.
+    $this->removeFile($file->getFileName(), 0);
+
+    // Can select file again.
+    $this->assertFieldByXpath('//input[@type="submit"]', t('Select'), 'After clicking the "Remove" button, the "Select" button is displayed.');
+  }
+
+  /**
+   * Test autocompletion.
+   */
+  public function testAutocompletion() {
+    // Create test file.
+    $file = $this->createPermanentFileEntity();
+    $filename = $file->getFileName();
+    $first_character = substr($filename, 0, 1);
+    $second_character = substr($filename, 1, 1);
+
+    // Switch to 'Starts with' match type.
+    $this->updateFilefieldSourcesSettings('source_reference', 'autocomplete', '0');
+
+    // STARTS_WITH: empty results.
+    $query = $this->findCharacterNotInString($first_character);
+    $autocomplete_result = $this->drupalGetJSON('file/reference/node/' . $this->typeName . '/' . $this->fieldName, array('query' => array('q' => $query)));
+    $this->assertEqual($autocomplete_result, array(), "No files that have name starts with '$query'");
+
+    // STARTS_WITH: not empty results.
+    $query = $first_character;
+    $autocomplete_result = $this->drupalGetJSON('file/reference/node/' . $this->typeName . '/' . $this->fieldName, array('query' => array('q' => $query)));
+    $this->assertEqual($autocomplete_result[0]['label'], $filename, 'Autocompletion return correct label.');
+    $this->assertEqual($autocomplete_result[0]['value'], $filename . ' [fid:' . $file->id() . ']', 'Autocompletion return correct value.');
+
+    // Switch to 'Contains' match type.
+    $this->updateFilefieldSourcesSettings('source_reference', 'autocomplete', '1');
+
+    // CONTAINS: empty results.
+    $query = $this->findCharacterNotInString($filename);
+    $autocomplete_result = $this->drupalGetJSON('file/reference/node/' . $this->typeName . '/' . $this->fieldName, array('query' => array('q' => $query)));
+    $this->assertEqual($autocomplete_result, array(), "No files that have name contains '$query'");
+
+    // CONTAINS: not empty results.
+    $query = $second_character;
+    $autocomplete_result = $this->drupalGetJSON('file/reference/node/' . $this->typeName . '/' . $this->fieldName, array('query' => array('q' => $query)));
+    $this->assertEqual($autocomplete_result[0]['label'], $filename, 'Autocompletion return correct label.');
+    $this->assertEqual($autocomplete_result[0]['value'], $filename . ' [fid:' . $file->id() . ']', 'Autocompletion return correct value.');
+  }
+
+  /**
+   * Find the first character that is not in string.
+   *
+   * Only find for lower case character.
+   *
+   * @param string $string
+   *   String to check.
+   *
+   * @return string
+   *   First character that is not in the string.
+   */
+  protected function findCharacterNotInString($string) {
+    // Only check for lower case string.
+    $string = Unicode::strtolower($string);
+
+    // Lower case characters and numbers generated by
+    // \Drupal\simpletest\TestBase::randomMachineName().
+    $values = array_merge(range(97, 122), range(48, 57));
+    foreach ($values as $value) {
+      $character = chr($value);
+      if (strpos($string, $character) === FALSE) {
+        return $character;
+      }
+    }
+  }
+
+}
diff --git a/web/modules/contrib/filefield_sources/src/Tests/RemoteSourceTest.php b/web/modules/contrib/filefield_sources/src/Tests/RemoteSourceTest.php
new file mode 100644 (file)
index 0000000..2cc5500
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\filefield_sources\Tests\RemoteSourceTest.
+ */
+
+namespace Drupal\filefield_sources\Tests;
+
+/**
+ * Tests the remote source.
+ *
+ * @group filefield_sources
+ */
+class RemoteSourceTest extends FileFieldSourcesTestBase {
+
+  /**
+   * Tests remote source enabled.
+   */
+  public function testRemoteSourceEnabled() {
+    $this->enableSources(array(
+      'remote' => TRUE,
+    ));
+
+    // Upload a file by 'Remote' source.
+    $this->uploadFileByRemoteSource($GLOBALS['base_url'] . '/README.txt', 'README.txt', 0);
+
+    // We can only transfer one file on single value field.
+    $this->assertNoFieldByXPath('//input[@type="submit"]', t('Transfer'), t('After uploading a file, "Transfer" button is no longer displayed.'));
+
+    // Remove uploaded file.
+    $this->removeFile('README.txt', 0);
+
+    // Can transfer file again.
+    $this->assertFieldByXpath('//input[@type="submit"]', t('Transfer'), 'After clicking the "Remove" button, the "Transfer" button is displayed.');
+  }
+
+}
diff --git a/web/modules/contrib/filefield_sources/src/Tests/UploadSourceTest.php b/web/modules/contrib/filefield_sources/src/Tests/UploadSourceTest.php
new file mode 100644 (file)
index 0000000..01afc53
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\filefield_sources\Tests\UploadSourceTest.
+ */
+
+namespace Drupal\filefield_sources\Tests;
+
+/**
+ * Tests the upload source.
+ *
+ * @group filefield_sources
+ */
+class UploadSourceTest extends FileFieldSourcesTestBase {
+
+  /**
+   * Tests upload source enabled.
+   */
+  public function testUploadSourceEnabled() {
+    $this->enableSources(array(
+      'upload' => TRUE,
+    ));
+
+    $this->assertUploadSourceWorkProperly();
+  }
+
+  /**
+   * Tests all sources enabled.
+   */
+  public function testAllSourcesEnabled() {
+    $this->enableSources(array(
+      'upload' => TRUE,
+      'remote' => TRUE,
+      'clipboard' => TRUE,
+      'reference' => TRUE,
+      'attach' => TRUE,
+    ));
+
+    $this->assertUploadSourceWorkProperly();
+  }
+
+  /**
+   * Tests upload source still working properly.
+   */
+  protected function assertUploadSourceWorkProperly() {
+    $file = $this->createTemporaryFileEntity();
+
+    // Upload a file by 'Upload' source.
+    $this->uploadFileByUploadSource($file->getFileUri(), $file->getFilename(), 0, FALSE);
+
+    // We can only upload one file on single value field.
+    $this->assertNoFieldByXPath('//input[@type="submit"]', t('Upload'), t('After uploading a file, "Upload" button is no longer displayed.'));
+
+    // Remove uploaded file.
+    $this->removeFile($file->getFilename(), 0);
+
+    // Can upload file again.
+    $this->assertFieldByXpath('//input[@type="submit"]', t('Upload'), 'After clicking the "Remove" button, the "Upload" button is displayed.');
+  }
+
+}
diff --git a/web/modules/contrib/libraries b/web/modules/contrib/libraries
deleted file mode 160000 (submodule)
index 061ead0..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 061ead081c92a6209b09eaf23b4e3103f360946e
diff --git a/web/modules/contrib/libraries/CHANGELOG.txt b/web/modules/contrib/libraries/CHANGELOG.txt
new file mode 100644 (file)
index 0000000..6d1daa8
--- /dev/null
@@ -0,0 +1,163 @@
+
+Libraries 8.x-3.x, xxxx-xx-xx
+-----------------------------
+#2833756 by 20th: Check that public://library-definitions directory does not exist
+#2825940 by tstoeckler, rjacobs: Remove LibrariesServiceProvider
+#2816115 by rjacobs: Remove ExtensionHandler service
+#2770983 by rjacobs: Update services.yml to reference new registry URL of http://cgit.drupalcode.org/libraries_registry/plain/registry/8
+#2770983 by rjacobs: Update shipped config to reference new registry URL of http://cgit.drupalcode.org/libraries_registry/plain/registry/8
+#2815189 by rjacobs, tstoeckler: Library dependency checking can fail for install profile
+#2770983 by tstoeckler, rjacobs: Fetch definitions remotely and store them locally.
+#2770983 by tstoeckler: Make Libraries API work on Windows.
+#2770983 by tstoeckler: Allow libraries to be symlinked to their right place.
+#2756265 by rajeshwari10: Replace deprecated usage of SafeMarkup::checkPlain().
+#2742333 by jrockowitz: Fix missing @group in LinePatternDetectorTest
+by tstoeckler: Document plugin alter hooks
+by tstoeckler: Fix coding standards
+by tstoeckler: Split out library dependencies to a separate interface
+by tstoeckler: Implement version detection for libraries
+by tstoeckler: Add deprecation notices to all legacy functions and hooks
+#2090623 by tstoeckler: Introduce the notion of library types
+#2090623 by tstoeckler: Provide support for remote asset libraries
+#2090623 by tstoeckler: Introduce a stream wrapper for asset libraries
+#2090623 by tstoeckler: Introduce the concept of locators
+#2606420 by googletorp, tstoeckler: Fix profile library detection
+#2090623 by tstoeckler: Add an external library registry
+#2572401 by rjacobs, yas: Fix missing @group annotation in PhpFileLibraryTest
+#2090623 by tstoeckler: Add a test for PHP file loading
+#2090623 by tstoeckler: Provide a modern, flexible library API
+#2525898 by rjacobs, jonhattan: Fix obsoleted cache bin declaration.
+#2471501 by LKS90: Replace all occurrences of String with the SafeMarkup equivalent.
+by tstoeckler: Fix drush libraries-list and drush cache-clear libraries.
+#2427801 by Anushka-mp, tstoeckler: Replace module_invoke() call.
+by tstoeckler: Fix tests.
+#2390301 by rjacobs: Fix DrupalUnitTestBase no longer exists so tests can't load.
+#2332157 by tstoeckler: Add a composer.json.
+#2287529 by drupalshrek, tstoeckler: Update installation link in README.txt.
+by tstoeckler: Fix tests.
+#2309203 by JayeshSolanki: Replace removed functions with module handler service.
+#2290767 by yukare: Replace removed cache() function.
+#2183087 by tstoeckler, rjacobs: Update for removed core functions.
+by tstoeckler: Fix tests.
+by tstoeckler: Provide required 'type' key in test library info file.
+#2090351 by tstoeckler: Remove obsolete hook_flush_caches().
+#2090425 by tstoeckler: Adapt for renamed ControllerInterface.
+#2090323 by tstoeckler: Remove obsolete libraries_parse_dependency().
+#2090379 by tstoeckler: Change 'pattern' to 'path' in routing YAML file.
+#2058371 by gordon: Re-port to Drupal 8 (.info.yml, controllers, cache service, ...).
+#1779714 by tstoeckler, klonos: Wrong filepath in README.txt and fix JS testing.
+#1167496 by tstoeckler: Remove leftover libraries.test file.
+#1167496 by tstoeckler, benshell: Port to Drupal 8.
+
+
+Libraries 7.x-3.x, xxxx-xx-xx
+-----------------------------
+#1938638 by tstoeckler: Remove unneeded check.
+
+
+Libraries 7.x-2.x, xxxx-xx-xx
+-----------------------------
+#2352251 by netw3rker: Fix incorrect hook name in libraries.api.php.
+#2352237 by netw3rker, tstoeckler: Allow clearing the libraries cache from Drush.
+#2193969 by tstoeckler: Avoid warnings for stale library caches.
+#2287529 by drupalshrek, tstoeckler: Update installation link in README.txt.
+
+Libraries 7.x-2.2, 2014-02-09
+-----------------------------
+#2046919 by tstoeckler: Clarify 'version' docs.
+#1946110 by munroe_richard: Allow uppercase letters as library machine names.
+#1953260 by tstoeckler: Improve documentation of libraries_get_version().
+#1855918 by tstoeckler: Make integration file loading backwards-compatible.
+#1876124 by tstoeckler: Fix integration files for themes.
+#1876124 by tstoeckler: Add tests for theme-provided library information.
+#1876124 by tstoeckler: Prepare for adding a test theme.
+#1876124 by tstoeckler | whastings, fubhy: Fix hook_libraries_info() for themes.
+#2015721 by tstoeckler, CaptainHook: Protect against files overriding local variables.
+#2046919 by tstoeckler: Improve documentation around 'version callback'.
+#1844272 by tstoeckler, jweowu: Fix typos in libraries.api.php.
+#1938638 by tstoeckler: Prevent weird PHP notice on update.
+#1329388 by RobLoach, tstoeckler: Clear static caches in libraries_flush_caches().
+#1855918 by rbayliss: Load integration files after library files.
+#1938638 by Pol: Fix typo in libraries.api.php.
+
+Libraries 7.x-2.1, 2013-03-09
+-----------------------------
+#1937446 by Pol, tstoeckler: Add a 'pre-dependencies-load' callback group.
+#1775668 by tstoeckler: Fix bogus assertion message in assertLibraryFiles().
+#1773640 by tstoeckler: Use drupal_get_path() to find the profile directory.
+#1565426 by tstoeckler: Invoke hook_libraries_info() in enabled themes.
+
+Libraries 7.x-2.0, 2012-07-29
+-----------------------------
+#1606018 by chemical: Tests fail if the module is downloaded from Drupal.org.
+#1386250 by tstoeckler: Clarify module and library installation in README.txt.
+#1578618 by iamEAP: Fixed Fatal cache flush failure on major version upgrades.
+#1449346 by tstoeckler, sun: Clean-up libraries.test
+
+Libraries 7.x-2.0-alpha2, 2011-12-15
+------------------------------------
+#1299076 by tstoeckler: Improve testing of JS, CSS, and PHP files.
+#1347214 by rfay: Improve update function 7200.
+#1323530 by tstoeckler: Document libraries_get_version() pattern matching.
+#1325524 by sun, Rob Loach, tstoeckler: Statically cache libraries_detect().
+#1321372 by Rob Loach: Provide a 'post-load' callback group.
+#1205854 by tstoeckler, sun: Test library caching.
+
+Libraries 7.x-2.0-alpha1, 2011-10-01
+------------------------------------
+#1268342 by tstoeckler: Clean up drush libraries-list command.
+#962214 by tstoeckler, sun: Add support for library dependencies.
+#1224838 by sun, mjpa: Fix library path not being prepended to JS/CSS files.
+#1023258 by tstoeckler: Make 'files' consistently keyed by filename.
+#958162 by sun, tstoeckler: Add pre-detect callback group.
+#958162 by sun, tstoeckler: Make tests debuggable and provide libraries_info_defaults().
+#961476 by tstoeckler: Changed libraries_get_path() to return FALSE by default.
+#958162 by tstoeckler, sun, good_man: Allow to apply callbacks to libraries.
+#1125904 by tstoeckler, boombatower: Fix drush libraries-list.
+#1050076 by tstoeckler: Re-utilize libraries_detect() and remove libraries_detect_library().
+#466090 by tstoeckler: Add update function.
+#466090 by tstoeckler: Allow cache to be flushed.
+#466090 by tstoeckler, sun: Cache library information.
+#1064008 by tstoeckler, bfroehle: Fix outdated API examples in libraries.api.php.
+#1028744 by tstoeckler: Code clean-up.
+#1023322 by tstoeckler, sun: Fixed libraries shouldn't be loaded multiple times.
+#1024080 by hswong3i, tstoeckler: Fixed installation profile retrieval.
+#995988 by good_man: Wrong default install profile.
+#975498 by Gábor Hojtsy: Update JS/CSS-loading to new drupal_add_js/css() API.
+#958162 by tsteoeckler, sun: Consistent variable naming.
+#924130 by aaronbauman: Fixed libraries_get_path() should use drupal_static().
+#958162 by tstoeckler, sun: Code clean-up, tests revamp, more robust loading.
+#919632 by tstoeckler, sun: Allow library information to be stored in info files.
+by sun: Fixed testbot breaks upon directory name/info file name mismatch.
+#864376 by tstoeckler, sun: Code-cleanup, allow hard-coded 'version'.
+#939174 by sun, tstoeckler: Rename example.info to libraries_example.info.
+by sun: Fixed testbot breaks upon .info file without .module file.
+#542940 by tstoeckler, sun: Add libraries-list command.
+#919632 by tstoeckler: Add example library info file for testing purposes.
+#719896 by tstoeckler, sun: Documentation clean-up and tests improvement.
+#542940 by sun: Added initial Drush integration file.
+#719896 by tstoeckler, sun: Improved library detection and library loading.
+#855050 by Gábor Hojtsy: Avoid call-time pass by reference in libraries_detect().
+#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.
+#496732 by tstoeckler, robphillips: Allow placing libraries in root directory.
+
+Libraries 6.x-1.0-alpha1, 2009-12-30
+------------------------------------
+#480440 by markus_petrux: Fixed base_path() not applied to default library path.
+#320562 by sun: Added basic functions.
diff --git a/web/modules/contrib/libraries/README.txt b/web/modules/contrib/libraries/README.txt
new file mode 100644 (file)
index 0000000..9ff0ece
--- /dev/null
@@ -0,0 +1,40 @@
+
+# Libraries API
+
+## General information
+
+Libraries API provides external library handling for Drupal modules.
+
+Relevant links:
+- [Project page](https://www.drupal.org/project/libraries)
+- [Issue tracker](https://www.drupal.org/project/issues/libraries)
+- [Repository viewer](http://cgit.drupalcode.org/libraries)
+
+### Installation
+
+Install like any module, see the
+[Drupal.org handbook](https://www.drupal.org/documentation/install/modules-themes/modules-8)
+for further information. Note that installing external libraries is separate from
+installing this module and should happen in the top-level `libraries` directory.
+See the online [module documentation](https://www.drupal.org/node/1440066) for more
+information.
+
+
+### Maintainers
+
+Current maintainers:
+- Daniel F. Kudwien ([sun](https://www.drupal.org/u/sun))
+- Tobias Stöckler ([tstoeckler](http://www.drupal.org/u/tstoeckler))
+- Ryan Jacobs ([rjacobs](http://www.drupal.org/u/rjacobs))
+- Pol Dellaiera ([pol](http://www.drupal.org/u/pol))
+
+### Sponsorship
+
+This project has been sponsored by:
+- **UNLEASHED MIND**
+  Specialized in consulting and planning of Drupal powered sites, UNLEASHED
+  MIND offers installation, development, theming, customization, and hosting
+  to get you started. Visit
+  [http://www.unleashedmind.com](http://www.unleashedmind.com) for more
+  information.
+
diff --git a/web/modules/contrib/libraries/composer.json b/web/modules/contrib/libraries/composer.json
new file mode 100644 (file)
index 0000000..4af11e1
--- /dev/null
@@ -0,0 +1,14 @@
+{
+  "name": "drupal/libraries",
+  "description": "Allows version-dependent and shared usage of external libraries in Drupal.",
+  "type": "drupal-module",
+  "homepage": "http://drupal.org/project/libraries",
+  "authors": [
+  ],
+  "support": {
+    "issues": "http://drupal.org/project/issues/libraries",
+    "irc": "irc://irc.freenode.org/drupal-contribute",
+    "source": "http://cgit.drupalcode.org/libraries"
+  },
+  "license": "GPL-2.0+"
+}
diff --git a/web/modules/contrib/libraries/config/install/libraries.settings.yml b/web/modules/contrib/libraries/config/install/libraries.settings.yml
new file mode 100644 (file)
index 0000000..0981884
--- /dev/null
@@ -0,0 +1,10 @@
+definition:
+  local:
+    # @todo Implement a stream wrapper that finds library definitions in e.g.
+    # sites/all/libraries.
+    path: 'public://library-definitions'
+  remote:
+    enable: TRUE
+    urls:
+      - 'http://cgit.drupalcode.org/libraries_registry/plain/registry/8'
+global_locators: []
\ No newline at end of file
diff --git a/web/modules/contrib/libraries/config/schema/libraries.schema.yml b/web/modules/contrib/libraries/config/schema/libraries.schema.yml
new file mode 100644 (file)
index 0000000..72c4c52
--- /dev/null
@@ -0,0 +1,53 @@
+# Configuration schema for the Libraries API module.
+
+# Base configuration schema
+libraries.settings:
+  type: config_object
+  label: 'Libraries API settings'
+  mapping:
+    definition:
+      type: mapping
+      label: 'Library definition settings'
+      mapping:
+        local:
+          type: mapping
+          label: 'Local'
+          mapping:
+            path:
+              type: path
+              label: 'Local path'
+        remote:
+          type: mapping
+          title: 'Remote'
+          mapping:
+            enable:
+              type: boolean
+              label: 'Enable remote fetching of library definitions'
+            urls:
+              type: sequence
+              label: 'A list of remote library registry URLs'
+              sequence:
+                type: uri
+                label: 'The URL of a remote library registry'
+    global_locators:
+      type: sequence
+      title: 'Global library locators'
+      sequence:
+        type: mapping
+        title: 'Global locator plugins'
+        mapping:
+          id:
+            type: string
+            title: 'The locator plugin id'
+          configuration:
+            type: libraries.locator.[%parent.id]
+            title: 'The plugin configuration'
+
+# Dynamic locator plugin schema
+libraries.locator.uri:
+  type: mapping
+  label: 'URI locator configuration'
+  mapping:
+    uri:
+      type: uri
+      label: 'The locator URI'
\ No newline at end of file
diff --git a/web/modules/contrib/libraries/libraries.api.php b/web/modules/contrib/libraries/libraries.api.php
new file mode 100644 (file)
index 0000000..859e602
--- /dev/null
@@ -0,0 +1,609 @@
+<?php
+
+/**
+ * @file
+ * Documents API functions for Libraries module.
+ */
+
+/**
+ * @defgroup libraries External libraries
+ * @{
+ * External libraries are not shipped as part of contributed modules for
+ * licensing and maintenance reasons. The Libraries API module aims to solve the
+ * problem of integrating with and loading external libraries as part of the
+ * Drupal request-response process in a generic way.
+ *
+ * @section sec_definitions Library definitions
+ * In order to be useful to other modules Libraries API needs a list of known
+ * libraries and metadata about each of the libraries. Because multiple modules
+ * themes may integrate with the same external library a key objective of
+ * Libraries API is to keep this information separate from any one module or
+ * theme.
+ *
+ * Definitions are accessed via a discovery that is responsible for checking
+ * whether a given definition exists and fetching it, if it is. See
+ * LibraryRegistryInterface and StreamDefinitionDiscovery for more information.
+ *
+ * @subsection sub_definitions_machine_name
+ * A central part of a library's metadata is the library's machine name or ID.
+ * For maximum interoperability it must consist of only lowercase ASCII letters,
+ * numbers, and underscores. As the machine name is the single identifier of a
+ * library and is independent of any given module or theme name it must be
+ * unique among all libraries known to Libraries API.
+ *
+ * @subsection sub_definitions_history Historical background
+ * In Drupal 7 library information could already be provided by
+ * module-independent info files, but this was not widely used, because there
+ * was no way to distribute these info files properly. The information was
+ * predominantly provided by a hook that modules could implement, which caused
+ * race conditions between modules providing information for the same library.
+ * Thus, in Drupal 8 there is no longer a hook making it necessary to properly
+ * solve the problem of centrally maintaining and distributing library info
+ * files. This has yet to be done. See https://www.drupal.org/node/773508 for
+ * more information.
+ *
+ * @section sec_types Library types
+ * Libraries are classed objects that implement LibraryInterface. This generic
+ * interface only dictates that a library is aware of its ID. Any further
+ * functionality depends on the type of library, each type of library comes with
+ * a dedicated interface. See LibraryInterface for more information.
+ *
+ * @subsection sub_types_version Version detection
+ * A central aspect of Libraries API is version detection. Modules or themes may
+ * only work with a specific version of an external library, so Libraries API
+ * needs a way to detect the version of a library by inspecting the library
+ * files.
+ *
+ * As the mechanism for doing this is generally not specific to any one
+ * library, it is handled by version detector plugins. A 'line_pattern' plugin
+ * that scans a file line by line whether for whether a pattern containing the
+ * version is matched. It can be used if the version is always specified in a
+ * particular place in a particular file, for example a changelog. See
+ * VersionDetectorInterface and LinePatternDetector for more information.
+ *
+ * @subsection sub_types_dependency Dependency handling
+ * Many libraries depend on other libraries to function. Thus, most library
+ * classes should implement DependentLibraryInterface to allow libraries to
+ * declare their dependencies as part of their metadata. In case of API changes
+ * in the dependencies libraries need to be able to declare dependencies on
+ * specific versions or version ranges of other libraries. This has yet to be
+ * implemented.
+ *
+ * Furthermore, Libraries API must also maintain a list of libraries that are
+ * required by the installed installation profile, modules, and themes
+ * (extensions). With this information installation of extensions with library
+ * dependencies can be prevented until the libraries are properly installed.
+ * This is currently not implemented. In the future this will be used to
+ * automatically retrieve library definitions of required libraries, and
+ * possibly to automatically download the libraries themselves.
+ *
+ * To declare library dependencies extensions can place a 'library_dependencies'
+ * key in their info file with a list of library machine names as the value.
+ * For example:
+ * @code
+ *   name: My module
+ *   type: module
+ *   core: 8.x
+ *   library_dependencies:
+ *     - flexslider
+ *     - jquery_mobile
+ * @endcode
+ *
+ * @subsection sub_types_asset Asset libraries
+ * With Drupal 8 relying on Composer for autoloading and dependency resolution
+ * of PHP libraries, asset libraries are the primary use-case for Libraries API.
+ * Because asset libraries cannot be loaded ad-hoc, but must be attached to a
+ * renderable element, Libraries API registers external asset libraries that are
+ * required by the installed extensions with the core asset library system. See
+ * AssetLibraryInterface for more information.
+ *
+ * @subsection sub_types_php_file
+ * For feature parity with the Drupal 7 version of this module, a PHP file
+ * library type is provided, that can load a list of PHP files on demand.
+ * Generally, it is encouraged to use Composer instead of this library type and
+ * avoid Libraries API altogether for PHP libraries. See PhpFileLibraryInterface
+ * for more information.
+ *
+ * This library type might be removed in a future version of Libraries API.
+ *
+ * @see \Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryInterface
+ * @see \Drupal\libraries\ExternalLibrary\Definition\StreamDefinitionDiscovery
+ * @see \Drupal\libraries\ExternalLibrary\LibraryInterface
+ * @see \Drupal\libraries\ExternalLibrary\Version\VersionDetectorInterface
+ * @see \Drupal\libraries\Plugin\libraries\VersionDetector\LinePatternDetector
+ * @see \Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface
+ * @see \Drupal\libraries\ExternalLibrary\Dependency\DependentLibraryInterface
+ * @see \Drupal\libraries\ExternalLibrary\Asset\AssetLibraryInterface
+ * @see \Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLibraryInterface
+ *
+ * @}
+ */
+
+/**
+ * Alter library type information.
+ *
+ * @param array $library_types
+ *   An array of library types keyed by ID. Each library type is an array with
+ *   the following keys:
+ *   - id: The ID of the library type.
+ *   - class: The class to use for this library type.
+ *   - provider: The provider of this library type.
+ */
+function hook_libraries_library_type_info_alter(array &$library_types) {
+  // Use a different class for the asset library type. Note that this class is
+  // distinct from the class actually for asset libraries themselves.
+  $library_types['asset']['class'] = 'Drupal\mymodule\ExternalLibrary\BetterAssetLibraryType';
+}
+
+/**
+ * Alter library locator information.
+ *
+ * @param array $locators
+ *   An array of library locators keyed by ID. Each locator is an array with the
+ *   following keys:
+ *   - id: The ID of the library locator.
+ *   - class: The class to use for this library locator.
+ *   - provider: The provider of this library locator.
+ */
+function hook_libraries_locator_info_alter(array &$locators) {
+  // Use a different class for the stream locator.
+  $locators['stream']['class'] = 'Drupal\mymodule\ExternalLibrary\BetterStreamLocator';
+}
+
+/**
+ * Alter library version detector information.
+ *
+ * @param array $version_detectors
+ *   An array of library version detectors keyed by ID. Each detector is an
+ *   array with the following keys:
+ *   - id: The ID of the library version detector.
+ *   - class: The class to use for this library version detector.
+ *   - provider: The provider of this library version detector.
+ */
+function hook_libraries_version_detector_info_alter(array &$version_detectors) {
+  // Use a different class for the line pattern locator.
+  $version_detectors['line_pattern']['class'] = 'Drupal\mymodule\ExternalLibrary\BetterLinePatternDetector';
+}
+
+/**
+ * Return information about external libraries.
+ *
+ * @return
+ *   An associative array whose keys are internal names of libraries and whose
+ *   values are describing each library. Each key is the directory name below
+ *   the 'libraries' directory, in which the library may be found. Each value is
+ *   an associative array containing:
+ *   - name: The official, human-readable name of the library.
+ *   - vendor url: The URL of the homepage of the library.
+ *   - download url: The URL of a web page on which the library can be obtained.
+ *   - path: (optional) A relative path from the directory of the library to the
+ *     actual library. Only required if the extracted download package contains
+ *     the actual library files in a sub-directory.
+ *   - library path: (optional) The absolute path to the library directory. This
+ *     should not be declared normally, as it is automatically detected, to
+ *     allow for multiple possible library locations. A valid use-case is an
+ *     external library, in which case the full URL to the library should be
+ *     specified here.
+ *   - version: (optional) The version of the library. This should not be
+ *     declared normally, as it is automatically detected (see 'version
+ *     callback' below) to allow for version changes of libraries without code
+ *     changes of implementing modules and to support different versions of a
+ *     library simultaneously (though only one version can be installed per
+ *     site). A valid use-case is an external library whose version cannot be
+ *     determined programmatically.
+ *   - version callback: (optional) The name of a function that detects and
+ *     returns the full version string of the library. The first argument is
+ *     always $library, an array containing all library information as described
+ *     here. There are two ways to declare the version callback's additional
+ *     arguments, either as a single $options parameter or as multiple
+ *     parameters, which correspond to the two ways to specify the argument
+ *     values (see 'version arguments'). Defaults to libraries_get_version().
+ *   - version arguments: 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 specifying
+ *     keys. If declared as an associative array, the arguments get passed to
+ *     the version callback as a single $options parameter whose keys are the
+ *     argument names (i.e. $options is identical to the specified array). If
+ *     declared as an indexed array, the array values get passed to the version
+ *     callback as separate arguments in the order they were declared. The
+ *     default version callback libraries_get_version() expects a single,
+ *     associative array with named keys:
+ *     - file: The filename to parse for the version, relative to the library
+ *       path. For example: 'docs/changelog.txt'.
+ *     - pattern: A string containing a regular expression (PCRE) to match the
+ *       library version. For example: '@version\s+([0-9a-zA-Z\.-]+)@'. Note
+ *       that the returned version is not the match of the entire pattern (i.e.
+ *       '@version 1.2.3' in the above example) but the match of the first
+ *       sub-pattern (i.e. '1.2.3' in the above example).
+ *     - lines: (optional) The maximum number of lines to search the pattern in.
+ *       Defaults to 20.
+ *     - cols: (optional) The maximum number of characters per line to take into
+ *       account. Defaults to 200. In case of minified or compressed files, this
+ *       prevents reading the entire file into memory.
+ *   - files: An associative array of library files to load. Supported keys are:
+ *     - js: A list of JavaScript files to load, using the same syntax as Drupal
+ *       core's hook_library().
+ *     - css: A list of CSS files to load, using the same syntax as Drupal
+ *       core's hook_library().
+ *     - php: A list of PHP files to load.
+ *   - dependencies: An array of libraries this library depends on. Similar to
+ *     declaring module dependencies, the dependency declaration may contain
+ *     information on the supported version. Examples of supported declarations:
+ *     @code
+ *     $libraries['dependencies'] = array(
+ *       // Load the 'example' library, regardless of the version available:
+ *       'example',
+ *       // Only load the 'example' library, if version 1.2 is available:
+ *       'example (1.2)',
+ *       // Only load a version later than 1.3-beta2 of the 'example' library:
+ *       'example (>1.3-beta2)'
+ *       // Only load a version equal to or later than 1.3-beta3:
+ *       'example (>=1.3-beta3)',
+ *       // Only load a version earlier than 1.5:
+ *       'example (<1.5)',
+ *       // Only load a version equal to or earlier than 1.4:
+ *       'example (<=1.4)',
+ *       // Combinations of the above are allowed as well:
+ *       'example (>=1.3-beta2, <1.5)',
+ *     );
+ *     @endcode
+ *   - variants: (optional) An associative array of available library variants.
+ *     For example, the top-level 'files' property may refer to a default
+ *     variant that is compressed. If the library also ships with a minified and
+ *     uncompressed/source variant, those can be defined here. Each key should
+ *     describe the variant type, e.g. 'minified' or 'source'. Each value is an
+ *     associative array of top-level properties that are entirely overridden by
+ *     the variant, most often just 'files'. Additionally, each variant can
+ *     contain following properties:
+ *     - variant callback: (optional) The name of a function that detects the
+ *       variant and returns TRUE or FALSE, depending on whether the variant is
+ *       available or not. The first argument is always $library, an array
+ *       containing all library information as described here. The second
+ *       argument is always a string containing the variant name. There are two
+ *       ways to declare the variant callback's additional arguments, either as a
+ *       single $options parameter or as multiple parameters, which correspond
+ *       to the two ways to specify the argument values (see 'variant
+ *       arguments'). If omitted, the variant is expected to always be
+ *       available.
+ *     - variant arguments: A list of arguments to pass to the variant callback.
+ *       Variant arguments can be declared either as an associative array whose
+ *       keys are the argument names or as an indexed array without specifying
+ *       keys. If declared as an associative array, the arguments get passed to
+ *       the variant callback as a single $options parameter whose keys are the
+ *       argument names (i.e. $options is identical to the specified array). If
+ *       declared as an indexed array, the array values get passed to the
+ *       variant callback as separate arguments in the order they were declared.
+ *     Variants can be version-specific (see 'versions').
+ *   - versions: (optional) An associative array of supported library versions.
+ *     Naturally, libraries evolve over time and so do their APIs. In case a
+ *     library changes between versions, different 'files' may need to be
+ *     loaded, different 'variants' may become available, or Drupal modules need
+ *     to load different integration files adapted to the new version. Each key
+ *     is a version *string* (PHP does not support floats as keys). Each value
+ *     is an associative array of top-level properties that are entirely
+ *     overridden by the version.
+ *   - integration files: (optional) An associative array whose keys are module
+ *     names and whose values are sets of files to load for the module, using
+ *     the same notion as the top-level 'files' property. Each specified file
+ *     should contain the path to the file relative to the module it belongs to.
+ *   - callbacks: An associative array whose keys are callback groups and whose
+ *     values are arrays of callbacks to apply to the library in that group.
+ *     Each callback receives the following arguments:
+ *     - $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.
+ *     - $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.
+ *     - $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.
+ *     Valid callback groups are:
+ *     - info: Callbacks registered in this group are applied after the library
+ *       information has been retrieved via hook_libraries_info() or info files.
+ *     - pre-detect: Callbacks registered in this group are applied after the
+ *       library path has been determined and before the version callback is
+ *       invoked. At this point the following additional information is available:
+ *       - $library['library path']: The path on the file system to the library.
+ *     - post-detect: Callbacks registered in this group are applied after the
+ *       library has been successfully detected. At this point the library
+ *       contains the version-specific information, if specified, and following
+ *       additional information is available:
+ *       - $library['installed']: A boolean indicating whether the library is
+ *         installed or not.
+ *       - $library['version']: If it could be detected, a string containing the
+ *         version of the library.
+ *       - $library['variants'][$variant]['installed']: For each specified
+ *         variant, a boolean indicating whether the variant is installed or
+ *         not.
+ *       Note that in this group the 'versions' property is no longer available.
+ *     - pre-load: Callbacks registered in this group are applied directly
+ *       before this 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.
+ *     - post-load: Callbacks registered in this group are applied directly
+ *       after this library is loaded. At this point, the library contains a
+ *       'loaded' key, which contains the number of files that were loaded.
+ *   Additional top-level properties can be registered as needed.
+ *
+ * @see hook_library()
+ *
+ * @deprecated Will be removed before a stable Drupal 8 release.
+ */
+function hook_libraries_info() {
+  // The following is a full explanation of all properties. See below for more
+  // concrete example implementations.
+
+  // This array key lets Libraries API search for 'sites/all/libraries/example'
+  // directory, which should contain the entire, original extracted library.
+  $libraries['example'] = array(
+    // Only used in administrative UI of Libraries API.
+    'name' => 'Example library',
+    'vendor url' => 'http://example.com',
+    'download url' => 'http://example.com/download',
+    // Optional: If, after extraction, the actual library files are contained in
+    // 'sites/all/libraries/example/lib', specify the relative path here.
+    'path' => 'lib',
+    // Optional: Define a custom version detection callback, if required.
+    'version callback' => 'mymodule_get_version',
+    // Specify arguments for the version callback. By default,
+    // libraries_get_version() takes a named argument array:
+    'version arguments' => array(
+      'file' => 'docs/CHANGELOG.txt',
+      'pattern' => '@version\s+([0-9a-zA-Z\.-]+)@',
+      'lines' => 5,
+      'cols' => 20,
+    ),
+    // Default list of files of the library to load. Important: Only specify
+    // third-party files belonging to the library here, not integration files of
+    // your module.
+    'files' => array(
+      // 'js' and 'css' follow the syntax of hook_library(), but file paths are
+      // relative to the library path.
+      'js' => array(
+        'exlib.js',
+        'gadgets/foo.js',
+      ),
+      'css' => array(
+        'lib_style.css',
+        'skin/example.css',
+      ),
+      // For PHP libraries, specify include files here, still relative to the
+      // library path.
+      'php' => array(
+        'exlib.php',
+        'exlib.inc',
+      ),
+    ),
+    // Optional: Specify alternative variants of the library, if available.
+    'variants' => array(
+      // All properties defined for 'minified' override top-level properties.
+      'minified' => array(
+        'files' => array(
+          'js' => array(
+            'exlib.min.js',
+            'gadgets/foo.min.js',
+          ),
+          'css' => array(
+            'lib_style.css',
+            'skin/example.css',
+          ),
+        ),
+        'variant callback' => 'mymodule_check_variant',
+        'variant arguments' => array(
+          'variant' => 'minified',
+        ),
+      ),
+    ),
+    // Optional, but usually required: Override top-level properties for later
+    // versions of the library. The properties of the minimum version that is
+    // matched override the top-level properties. Note:
+    // - When registering 'versions', it usually does not make sense to register
+    //   'files', 'variants', and 'integration files' on the top-level, as most
+    //   of those likely need to be different per version and there are no
+    //   defaults.
+    // - The array keys have to be strings, as PHP does not support floats for
+    //   array keys.
+    'versions' => array(
+      '2' => array(
+        'files' => array(
+          'js' => array('exlib.js'),
+          'css' => array('exlib_style.css'),
+        ),
+      ),
+      '3.0' => array(
+        'files' => array(
+          'js' => array('exlib.js'),
+          'css' => array('lib_style.css'),
+        ),
+      ),
+      '3.2' => array(
+        'files' => array(
+          'js' => array(
+            'exlib.js',
+            'gadgets/foo.js',
+          ),
+          'css' => array(
+            'lib_style.css',
+            'skin/example.css',
+          ),
+        ),
+      ),
+    ),
+    // Optional: Register files to auto-load for your module. All files must be
+    // keyed by module, and follow the syntax of the 'files' property.
+    'integration files' => array(
+      'mymodule' => array(
+        'js' => array('ex_lib.inc'),
+      ),
+    ),
+    // Optionally register callbacks to apply to the library during different
+    // stages of its lifetime ('callback groups').
+    'callbacks' => array(
+      // Used to alter the info associated with the library.
+      'info' => array(
+        'mymodule_example_libraries_info_callback',
+      ),
+      // Called before detecting the given library.
+      'pre-detect' => array(
+        'mymodule_example_libraries_predetect_callback',
+      ),
+      // Called after detecting the library.
+      'post-detect' => array(
+        'mymodule_example_libraries_postdetect_callback',
+      ),
+      // Called before the library is loaded.
+      'pre-load' => array(
+        'mymodule_example_libraries_preload_callback',
+      ),
+      // Called after the library is loaded.
+      'post-load' => array(
+        'mymodule_example_libraries_postload_callback',
+      ),
+    ),
+  );
+
+  // A very simple library. No changing APIs (hence, no versions), no variants.
+  // Expected to be extracted into 'sites/all/libraries/simple'.
+  $libraries['simple'] = array(
+    'name' => 'Simple library',
+    'vendor url' => 'http://example.com/simple',
+    'download url' => 'http://example.com/simple',
+    'version arguments' => array(
+      'file' => 'readme.txt',
+      // Best practice: Document the actual version strings for later reference.
+      // 1.x: Version 1.0
+      'pattern' => '/Version (\d+)/',
+      'lines' => 5,
+    ),
+    'files' => array(
+      'js' => array('simple.js'),
+    ),
+  );
+
+  // A library that (naturally) evolves over time with API changes.
+  $libraries['tinymce'] = array(
+    'name' => 'TinyMCE',
+    'vendor url' => 'http://tinymce.moxiecode.com',
+    'download url' => 'http://tinymce.moxiecode.com/download.php',
+    'path' => 'jscripts/tiny_mce',
+    // The regular expression catches two parts (the major and the minor
+    // version), which libraries_get_version() doesn't allow.
+    'version callback' => 'tinymce_get_version',
+    'version arguments' => array(
+      // It can be easier to parse the first characters of a minified file
+      // instead of doing a multi-line pattern matching in a source file. See
+      // 'lines' and 'cols' below.
+      'file' => 'jscripts/tiny_mce/tiny_mce.js',
+      // Best practice: Document the actual version strings for later reference.
+      // 2.x: this.majorVersion="2";this.minorVersion="1.3"
+      // 3.x: majorVersion:'3',minorVersion:'2.0.1'
+      'pattern' => '@majorVersion[=:]["\'](\d).+?minorVersion[=:]["\']([\d\.]+)@',
+      'lines' => 1,
+      'cols' => 100,
+    ),
+    'versions' => array(
+      '2.1' => array(
+        'files' => array(
+          'js' => array('tiny_mce.js'),
+        ),
+        'variants' => array(
+          'source' => array(
+            'files' => array(
+              'js' => array('tiny_mce_src.js'),
+            ),
+          ),
+        ),
+        'integration files' => array(
+          'wysiwyg' => array(
+            'js' => array('editors/js/tinymce-2.js'),
+            'css' => array('editors/js/tinymce-2.css'),
+          ),
+        ),
+      ),
+      // Definition used if 3.1 or above is detected.
+      '3.1' => array(
+        // Does not support JS aggregation.
+        'files' => array(
+          'js' => array(
+            'tiny_mce.js' => array('preprocess' => FALSE),
+          ),
+        ),
+        'variants' => array(
+          // New variant leveraging jQuery. Not stable yet; therefore not the
+          // default variant.
+          'jquery' => array(
+            'files' => array(
+              'js' => array(
+                'tiny_mce_jquery.js' => array('preprocess' => FALSE),
+              ),
+            ),
+          ),
+          'source' => array(
+            'files' => array(
+              'js' => array(
+                'tiny_mce_src.js' => array('preprocess' => FALSE),
+              ),
+            ),
+          ),
+        ),
+        'integration files' => array(
+          'wysiwyg' => array(
+            'js' => array('editors/js/tinymce-3.js'),
+            'css' => array('editors/js/tinymce-3.css'),
+          ),
+        ),
+      ),
+    ),
+  );
+  return $libraries;
+}
+
+/**
+ * Alter the library information before detection and caching takes place.
+ *
+ * The library definitions are passed by reference. A common use-case is adding
+ * a module's integration files to the library array, so that the files are
+ * loaded whenever the library is. As noted above, it is important to declare
+ * integration files inside of an array, whose key is the module name.
+ *
+ * @see hook_libraries_info()
+ *
+ * @deprecated Will be removed before a stable Drupal 8 release.
+ */
+function hook_libraries_info_alter(&$libraries) {
+  $files = array(
+    'php' => array('example_module.php_spellchecker.inc'),
+  );
+  $libraries['php_spellchecker']['integration files']['example_module'] = $files;
+}
+
+/**
+ * Specify paths to look for library info files.
+ *
+ * Libraries API looks in the following directories for library info files by
+ * default:
+ * - libraries
+ * - profiles/$profile/libraries
+ * - sites/all/libraries
+ * - sites/$site/libraries
+ * This hook allows you to specify additional locations to look for library info
+ * files. This should only be used for modules that declare many libraries.
+ * Modules that only implement a few libraries should implement
+ * hook_libraries_info().
+ *
+ * @return
+ *   An array of paths.
+ *
+ * @deprecated Will be removed before a stable Drupal 8 release.
+ */
+function hook_libraries_info_file_paths() {
+  // Taken from the Libraries test module, which needs to specify the path to
+  // the test library.
+  return array(drupal_get_path('module', 'libraries_test') . '/example');
+}
diff --git a/web/modules/contrib/libraries/libraries.drush.inc b/web/modules/contrib/libraries/libraries.drush.inc
new file mode 100644 (file)
index 0000000..22b7d62
--- /dev/null
@@ -0,0 +1,169 @@
+<?php
+
+/**
+ * @file
+ * Drush integration for Libraries API.
+ */
+
+use Drupal\Component\Utility\Unicode;
+
+/**
+ * Implements hook_drush_command().
+ */
+function libraries_drush_command() {
+  $items['libraries-list'] = array(
+    'callback' => 'libraries_drush_list',
+    'description' => dt('Lists registered library information.'),
+    'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
+  );
+  /**$items['libraries-download'] = array(
+    'callback' => 'libraries_drush_download',
+    'description' => dt('Downloads a registered library into the libraries directory for the active site.'),
+    'arguments' => array(
+      'name' => dt('The internal name of the registered library.'),
+    ),
+  );*/
+  return $items;
+}
+
+/**
+ * Implements hook_drush_help().
+ */
+function libraries_drush_help($section) {
+  switch ($section) {
+    case 'drush:libraries-list':
+      return dt('Lists registered library information.');
+
+    case 'drush:libraries-download':
+      return dt('Downloads a registered library into the libraries directory for the active site.
+
+See libraries-list for a list of registered libraries.');
+  }
+}
+
+/**
+ * Implements hook_drush_cache_clear().
+ *
+ * @see drush_cache_clear_types()
+ */
+function libraries_drush_cache_clear(&$types) {
+  $types['libraries'] = 'libraries_drush_invalidate_cache';
+}
+
+/**
+ * Clears the library cache.
+ */
+function libraries_drush_invalidate_cache() {
+  \Drupal::cache('libraries')->deleteAll();
+}
+
+/**
+ * Lists registered library information.
+ */
+function libraries_drush_list() {
+  $libraries = array();
+  foreach (libraries_info() as $name => $info) {
+    $libraries[$name] = libraries_detect($name);
+  }
+  ksort($libraries);
+
+  if (empty($libraries)) {
+    drush_print('There are no registered libraries.');
+  }
+
+  else {
+    $rows = array();
+    // drush_print_table() automatically treats the first row as the header, if
+    // $header is TRUE.
+    $rows[] = array(dt('Name'), dt('Status'), dt('Version'), dt('Variants'), dt('Dependencies'));
+    foreach ($libraries as $name => $library) {
+      $status = ($library['installed'] ? dt('OK') : Unicode::ucfirst($library['error']));
+      $version = (($library['installed'] && !empty($library['version'])) ? $library['version'] : '-');
+
+      // Only list installed variants.
+      $variants = array();
+      foreach ($library['variants'] as $variant_name => $variant) {
+        if ($variant['installed']) {
+          $variants[] = $variant_name;
+        }
+      }
+      $variants = (empty($variants) ? '-' : implode(', ', $variants));
+
+      $dependencies = (!empty($library['dependencies']) ? implode(', ', $library['dependencies']) : '-');
+
+      $rows[] = array($name, $status, $version, $variants, $dependencies);
+    }
+    // Make the possible values for the 'Status' column and the 'Version' header
+    // wrap nicely.
+    $widths = array(0, 12, 7, 0, 0);
+    drush_print_table($rows, TRUE, $widths);
+  }
+}
+
+/**
+ * Downloads a library.
+ *
+ * @param $name
+ *   The internal name of the library to download.
+ */
+function libraries_drush_download($name) {
+  return;
+
+  // @todo Looks wonky?
+  if (!drush_shell_exec('type unzip')) {
+    return drush_set_error(dt('Missing dependency: unzip. Install it before using this command.'));
+  }
+
+  // @todo Simply use current drush site.
+  $args = func_get_args();
+  if ($args[0]) {
+    $path = $args[0];
+  }
+  else {
+    $path = 'sites/all/libraries';
+  }
+
+  // Create the path if it does not exist.
+  if (!is_dir($path)) {
+    drush_op('mkdir', $path);
+    drush_log(dt('Directory @path was created', array('@path' => $path)), 'notice');
+  }
+
+  // Set the directory to the download location.
+  $olddir = getcwd();
+  chdir($path);
+
+  $filename = basename(COLORBOX_DOWNLOAD_URI);
+  $dirname = basename(COLORBOX_DOWNLOAD_URI, '.zip');
+
+  // Remove any existing Colorbox plugin directory
+  if (is_dir($dirname)) {
+    drush_log(dt('A existing Colorbox plugin was overwritten at @path', array('@path' => $path)), 'notice');
+  }
+  // Remove any existing Colorbox plugin zip archive
+  if (is_file($filename)) {
+    drush_op('unlink', $filename);
+  }
+
+  // Download the zip archive
+  if (!drush_shell_exec('wget '. COLORBOX_DOWNLOAD_URI)) {
+    drush_shell_exec('curl -O '. COLORBOX_DOWNLOAD_URI);
+  }
+
+  if (is_file($filename)) {
+    // Decompress the zip archive
+    drush_shell_exec('unzip -qq -o '. $filename);
+    // Remove the zip archive
+    drush_op('unlink', $filename);
+  }
+
+  // Set working directory back to the previous working directory.
+  chdir($olddir);
+
+  if (is_dir($path .'/'. $dirname)) {
+    drush_log(dt('Colorbox plugin has been downloaded to @path', array('@path' => $path)), 'success');
+  }
+  else {
+    drush_log(dt('Drush was unable to download the Colorbox plugin to @path', array('@path' => $path)), 'error');
+  }
+}
diff --git a/web/modules/contrib/libraries/libraries.info.yml b/web/modules/contrib/libraries/libraries.info.yml
new file mode 100644 (file)
index 0000000..1891a6f
--- /dev/null
@@ -0,0 +1,4 @@
+name: Libraries
+type: module
+description: Allows version-dependent and shared usage of external libraries.
+core: 8.x
diff --git a/web/modules/contrib/libraries/libraries.install b/web/modules/contrib/libraries/libraries.install
new file mode 100644 (file)
index 0000000..a2edea4
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file
+ * Containsinstall, uninstall and update functions for Libraries API.
+ */
+
+use Drupal\libraries\ExternalLibrary\Definition\FileDefinitionDiscovery;
+
+/**
+ * Implements hook_install().
+ */
+function libraries_install() {
+  if (!is_dir('public://library-definitions')) {
+    /** @var \Drupal\Core\File\FileSystemInterface $file_system */
+    $file_system = \Drupal::service('file_system');
+    $file_system->mkdir('public://library-definitions');
+  }
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function libraries_uninstall() {
+  if (is_dir('public://library-definitions')) {
+    file_unmanaged_delete_recursive('public://library-definitions');
+  }
+}
diff --git a/web/modules/contrib/libraries/libraries.module b/web/modules/contrib/libraries/libraries.module
new file mode 100644 (file)
index 0000000..3006c34
--- /dev/null
@@ -0,0 +1,868 @@
+<?php
+
+/**
+ * @file
+ * External library handling for Drupal modules.
+ */
+
+use Drupal\Core\DrupalKernel;
+use Drupal\Core\Extension\ModuleHandler;
+use Drupal\libraries\ExternalLibrary\Asset\AttachableAssetLibraryRegistrationInterface;
+use Drupal\libraries\ExternalLibrary\Utility\LibraryAccessorInterface;
+use Drupal\libraries\ExternalLibrary\Utility\LibraryIdAccessorInterface;
+use Symfony\Component\Yaml\Parser;
+
+/**
+ * Implements hook_library_info_build().
+ *
+ * Register external asset libraries with Drupal core's library APIs.
+ */
+function libraries_library_info_build() {
+  /** @var \Drupal\libraries\ExternalLibrary\LibraryManagerInterface $library_manager */
+  $library_manager = \Drupal::service('libraries.manager');
+  $attachable_libraries = [];
+  $libraries_with_errors = [];
+  foreach ($library_manager->getRequiredLibraryIds() as $external_library_id) {
+    try {
+      $external_library = $library_manager->getLibrary($external_library_id);
+      $library_type = $external_library->getType();
+      if ($library_type instanceof AttachableAssetLibraryRegistrationInterface) {
+        $attachable_libraries += $library_type->getAttachableAssetLibraries($external_library, $library_manager);
+      }
+    }
+    catch (\Exception $e) {
+      // Library-specific exceptions should not be allowed to kill the rest of
+      // the build process, but should be logged.
+      if ($e instanceof LibraryIdAccessorInterface || $e instanceof LibraryAccessorInterface) {
+        $libraries_with_errors[] = $external_library_id;
+        watchdog_exception('libraries', $e);
+      }
+      else {
+        // Re-throw exceptions that are not library-specific.
+        throw $e;
+      }
+    }
+  }
+  // If we had library specific errors also log an informative message to
+  // tell admins that detection will not be run again without a cache clear.
+  if ($libraries_with_errors) {
+    \Drupal::logger('libraries')->error('The following external libraries could not successfully be registered with Drupal core: @libs. See earlier log entries for more details. Once these issues are addressed please be sure to clear your Drupal library cache to ensure external library detection is run again.', ['@libs' => implode(',', $libraries_with_errors)]);
+  }
+  return $attachable_libraries;
+}
+
+/**
+ * Gets the path of a library.
+ *
+ * @param $name
+ *   The machine name of a library to return the path for.
+ * @param $base_path
+ *   Whether to prefix the resulting path with base_path().
+ *
+ * @return
+ *   The path to the specified library or FALSE if the library wasn't found.
+ *
+ * @ingroup libraries
+ *
+ * @deprecated Will be removed before a stable Drupal 8 release. Please use the
+ * new library load and managment concepts described at:
+ * https://www.drupal.org/node/2170763
+ */
+function libraries_get_path($name, $base_path = FALSE) {
+  $libraries = &drupal_static(__FUNCTION__);
+
+  if (!isset($libraries)) {
+    $libraries = libraries_get_libraries();
+  }
+
+  $path = ($base_path ? base_path() : '');
+  if (!isset($libraries[$name])) {
+    return FALSE;
+  }
+  else {
+    $path .= $libraries[$name];
+  }
+
+  return $path;
+}
+
+/**
+ * Returns an array of library directories.
+ *
+ * Returns an array of library directories from the all-sites directory
+ * (i.e. sites/all/libraries/), the profiles directory, and site-specific
+ * directory (i.e. sites/somesite/libraries/). The returned array will be keyed
+ * by the library name. Site-specific libraries are prioritized over libraries
+ * in the default directories. That is, if a library with the same name appears
+ * in both the site-wide directory and site-specific directory, only the
+ * site-specific version will be listed.
+ *
+ * @return
+ *   A list of library directories.
+ *
+ * @ingroup libraries
+ *
+ * @deprecated Will be removed before a stable Drupal 8 release. Please use the
+ * new library load and managment concepts described at:
+ * https://www.drupal.org/node/2170763
+ */
+function libraries_get_libraries() {
+  $searchdir = array();
+  $config = DrupalKernel::findSitePath(\Drupal::request());
+
+  // @todo core/libraries
+
+  // Similar to 'modules' and 'themes' directories inside an installation
+  // profile, installation profiles may want to place libraries into a
+  // 'libraries' directory.
+  if ($profile = drupal_get_profile()) {
+    $profile_path = drupal_get_path('profile', $profile);
+    $searchdir[] = "$profile_path/libraries";
+  };
+
+  // Search sites/all/libraries for backwards-compatibility.
+  $searchdir[] = 'sites/all/libraries';
+
+  // Always search the root 'libraries' directory.
+  $searchdir[] = 'libraries';
+
+  // Also search sites/<domain>/*.
+  $searchdir[] = "$config/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;
+}
+
+/**
+ * Looks for library info files.
+ *
+ * This function scans the following directories for info files:
+ * - libraries
+ * - profiles/$profilename/libraries
+ * - sites/all/libraries
+ * - sites/$sitename/libraries
+ * - any directories specified via hook_libraries_info_file_paths()
+ *
+ * @return
+ *   An array of info files, keyed by library name. The values are the paths of
+ *   the files.
+ *
+ * @deprecated Will be removed before a stable Drupal 8 release. Please use the
+ * new library load and managment concepts described at:
+ * https://www.drupal.org/node/2170763
+ */
+function libraries_scan_info_files() {
+  $profile = drupal_get_path('profile', drupal_get_profile());
+  $config = DrupalKernel::findSitePath(\Drupal::request());
+
+  // Build a list of directories.
+  $directories = \Drupal::moduleHandler()->invokeAll('libraries_info_file_paths', $args = array());
+  $directories[] = "$profile/libraries";
+  $directories[] = 'sites/all/libraries';
+  $directories[] = 'libraries';
+  $directories[] = "$config/libraries";
+
+  // Scan for info files.
+  $files = array();
+  foreach ($directories as $dir) {
+    if (file_exists($dir)) {
+      $files = array_merge($files, file_scan_directory($dir, '@^[a-z0-9._-]+\.libraries\.info\.yml$@', array(
+        'key' => 'name',
+        'recurse' => FALSE,
+      )));
+    }
+  }
+
+  foreach ($files as $filename => $file) {
+    $files[basename($filename, '.libraries.info')] = $file;
+    unset($files[$filename]);
+  }
+
+  return $files;
+}
+
+/**
+ * Invokes library callbacks.
+ *
+ * @param $group
+ *   A string containing the group of callbacks that is to be applied. Should be
+ *   either 'info', 'pre-detect', 'post-detect', or 'load'.
+ * @param $library
+ *   An array of library information, passed by reference.
+ *
+ * @deprecated Will be removed before a stable Drupal 8 release. Please use the
+ * new library load and managment concepts described at:
+ * https://www.drupal.org/node/2170763
+ */
+function libraries_invoke($group, &$library) {
+  foreach ($library['callbacks'][$group] as $callback) {
+    libraries_traverse_library($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 $library
+ *   An array of library information, passed by reference.
+ * @param $callback
+ *   A string containing the callback to apply to all parts of a library.
+ *
+ * @deprecated Will be removed before a stable Drupal 8 release. Please use the
+ * new library load and managment concepts described at:
+ * https://www.drupal.org/node/2170763
+ */
+function libraries_traverse_library(&$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);
+    }
+  }
+}
+
+/**
+ * Library info callback to make all 'files' properties consistent.
+ *
+ * This turns libraries' file information declared as e.g.
+ * @code
+ * $library['files']['js'] = array('example_1.js', 'example_2.js');
+ * @endcode
+ * into
+ * @code
+ * $library['files']['js'] = array(
+ *   'example_1.js' => array(),
+ *   'example_2.js' => array(),
+ * );
+ * @endcode
+ * It does the same for the 'integration files' property.
+ *
+ * @param $library
+ *   An associative array of library information or a part of it, passed by
+ *   reference.
+ * @param $version
+ *   If the library information belongs to a specific version, the version
+ *   string. NULL otherwise.
+ * @param $variant
+ *   If the library information belongs to a specific variant, the variant name.
+ *   NULL otherwise.
+ *
+ * @see libraries_info()
+ * @see libraries_invoke()
+ *
+ * @deprecated Will be removed before a stable Drupal 8 release. Please use the
+ * new library load and managment concepts described at:
+ * https://www.drupal.org/node/2170763
+ */
+function libraries_prepare_files(&$library, $version = NULL, $variant = NULL) {
+  // Both the 'files' property and the 'integration files' property contain file
+  // declarations, and we want to make both consistent.
+  $file_types = array();
+  if (isset($library['files'])) {
+    $file_types[] = &$library['files'];
+  }
+  if (isset($library['integration files'])) {
+    // Integration files are additionally keyed by module.
+    foreach ($library['integration files'] as &$integration_files) {
+      $file_types[] = &$integration_files;
+    }
+  }
+  foreach ($file_types as &$files) {
+    // Go through all supported types of files.
+    foreach (array('js', 'css', 'php') as $type) {
+      if (isset($files[$type])) {
+        foreach ($files[$type] as $key => $value) {
+          // Unset numeric keys and turn the respective values into keys.
+          if (is_numeric($key)) {
+            $files[$type][$value] = array();
+            unset($files[$type][$key]);
+          }
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Library post-detect callback to process and detect dependencies.
+ *
+ * It checks whether each of the dependencies of a library are installed and
+ * available in a compatible version.
+ *
+ * @param $library
+ *   An associative array of library information or a part of it, passed by
+ *   reference.
+ * @param $version
+ *   If the library information belongs to a specific version, the version
+ *   string. NULL otherwise.
+ * @param $variant
+ *   If the library information belongs to a specific variant, the variant name.
+ *   NULL otherwise.
+ *
+ * @see libraries_info()
+ * @see libraries_invoke()
+ *
+ * @deprecated Will be removed before a stable Drupal 8 release. Please use the
+ * new library load and managment concepts described at:
+ * https://www.drupal.org/node/2170763
+ */
+function libraries_detect_dependencies(&$library, $version = NULL, $variant = NULL) {
+  if (isset($library['dependencies'])) {
+    foreach ($library['dependencies'] as &$dependency_string) {
+      $dependency_info = ModuleHandler::parseDependency($dependency_string);
+      $dependency = libraries_detect($dependency_info['name']);
+      if (!$dependency['installed']) {
+        $library['installed'] = FALSE;
+        $library['error'] = 'missing dependency';
+        $library['error message'] = t('The %dependency library, which the %library library depends on, is not installed.', array(
+          '%dependency' => $dependency['name'],
+          '%library' => $library['name'],
+        ));
+      }
+      elseif (drupal_check_incompatibility($dependency_info, $dependency['version'])) {
+        $library['installed'] = FALSE;
+        $library['error'] = 'incompatible dependency';
+        $library['error message'] = t('The version %dependency_version of the %dependency library is not compatible with the %library library.', array(
+          '%dependency_version' => $dependency['version'],
+          '%dependency' => $dependency['name'],
+          '%library' => $library['name'],
+        ));
+      }
+
+      // Remove the version string from the dependency, so libraries_load() can
+      // load the libraries directly.
+      $dependency_string = $dependency_info['name'];
+    }
+  }
+}
+
+/**
+ * Returns information about registered libraries.
+ *
+ * The returned information is unprocessed; i.e., as registered by modules.
+ *
+ * @param $name
+ *   (optional) The machine name of a library to return registered information
+ *   for. If omitted, information about all registered libraries is returned.
+ *
+ * @return array|false
+ *   An associative array containing registered information for all libraries,
+ *   the registered information for the library specified by $name, or FALSE if
+ *   the library $name is not registered.
+ *
+ * @see hook_libraries_info()
+ *
+ * @todo Re-introduce support for include file plugin system - either by copying
+ *   Wysiwyg's code, or directly switching to CTools.
+ *
+ * @deprecated Will be removed before a stable Drupal 8 release. Please use the
+ * new library load and managment concepts described at:
+ * https://www.drupal.org/node/2170763
+ */
+function &libraries_info($name = NULL) {
+  // This static cache is re-used by libraries_detect() to save memory.
+  $libraries = &drupal_static(__FUNCTION__);
+
+  if (!isset($libraries)) {
+    $libraries = array();
+    // Gather information from hook_libraries_info().
+    $module_handler = \Drupal::moduleHandler();
+    foreach ($module_handler->getImplementations('libraries_info') as $module) {
+      foreach ($module_handler->invoke($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()
+    global $theme, $base_theme_info;
+    if (isset($theme)) {
+      $theme_keys = array();
+      foreach ($base_theme_info as $base) {
+        $theme_keys[] = $base->name;
+      }
+      $theme_keys[] = $theme;
+      foreach ($theme_keys as $theme_key) {
+        $function = $theme_key . '_' . 'libraries_info';
+        if (function_exists($function)) {
+          foreach ($function() as $machine_name => $properties) {
+            $properties['theme'] = $theme_key;
+            $libraries[$machine_name] = $properties;
+          }
+        }
+      }
+    }
+
+    // Gather information from .info files.
+    // .info files override module definitions.
+    // In order to stop Drupal's extension and the Drupal.org packaging
+    // system from finding library info files we use the 'libraries.info.yml'
+    // file extension. Therefore, having a 'type' key, like info files of
+    // modules, themes, and profiles have, is superfluous.
+    // \Drupal\Core\Extension\InfoParser, however, enforces the existence of a
+    // 'type' key in info files. We therefore use Symfony's YAML parser
+    // directly.
+    // @todo Consider creating a dedicating InfoParser for library info files
+    //   similar to \Drupal\Core\Extension\InfoParser
+    $parser = new Parser();
+    foreach (libraries_scan_info_files() as $machine_name => $file) {
+      $properties = $parser->parse(file_get_contents($file->uri));
+      $properties['info file'] = $file->uri;
+      $libraries[$machine_name] = $properties;
+    }
+
+    // Provide defaults.
+    foreach ($libraries as $machine_name => &$properties) {
+      libraries_info_defaults($properties, $machine_name);
+    }
+
+    // Allow modules to alter the registered libraries.
+    $module_handler->alter('libraries_info', $libraries);
+
+    // Invoke callbacks in the 'info' group.
+    foreach ($libraries as &$properties) {
+      libraries_invoke('info', $properties);
+    }
+  }
+
+  if (isset($name)) {
+    if (!empty($libraries[$name])) {
+      return $libraries[$name];
+    }
+    else {
+      $false = FALSE;
+      return $false;
+    }
+  }
+  return $libraries;
+}
+
+/**
+ * Applies default properties to a library definition.
+ *
+ * @library
+ *   An array of library information, passed by reference.
+ * @name
+ *   The machine name of the passed-in library.
+ *
+ * @deprecated Will be removed before a stable Drupal 8 release. Please use the
+ * new library load and managment concepts described at:
+ * https://www.drupal.org/node/2170763
+ */
+function libraries_info_defaults(&$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-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;
+}
+
+/**
+ * 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()
+ *
+ * @deprecated Will be removed before a stable Drupal 8 release. Please use the
+ * new library load and managment concepts described at:
+ * https://www.drupal.org/node/2170763
+ */
+function libraries_detect($name) {
+  // Re-use the statically cached value of libraries_info() to save memory.
+  $library = &libraries_info($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'] = libraries_get_path($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.
+  libraries_invoke('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']));
+    }
+    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.
+  libraries_invoke('post-detect', $library);
+
+  return $library;
+}
+
+/**
+ * Loads a library.
+ *
+ * @param $name
+ *   The name of the library to load.
+ * @param $variant
+ *   The name of the variant to load. Note that only one variant of a library
+ *   can be loaded within a single request. The variant that has been passed
+ *   first is used; different variant names in subsequent calls are ignored.
+ *
+ * @return
+ *   An associative array of the library information as returned from
+ *   libraries_info(). The top-level properties contain the effective definition
+ *   of the library (variant) that has been loaded. Additionally:
+ *   - installed: Whether the library is installed, as determined by
+ *     libraries_detect_library().
+ *   - loaded: Either the amount of library files that have been loaded, or
+ *     FALSE if the library could not be loaded.
+ *   See hook_libraries_info() for more information.
+ *
+ * @deprecated Will be removed before a stable Drupal 8 release. Please use the
+ * new library load and managment concepts described at:
+ * https://www.drupal.org/node/2170763
+ */
+function libraries_load($name, $variant = NULL) {
+  $loaded = &drupal_static(__FUNCTION__, array());
+
+  if (!isset($loaded[$name])) {
+    $library = \Drupal::cache('libraries')->get($name);
+    if ($library) {
+      $library = $library->data;
+    }
+    else {
+      $library = libraries_detect($name);
+      \Drupal::cache('libraries')->set($name, $library);
+    }
+    // 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']);
+
+    // 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) {
+          libraries_load($dependency);
+        }
+      }
+
+      // Invoke callbacks in the 'pre-load' group.
+      libraries_invoke('pre-load', $library);
+
+      // Load all the files associated with the library.
+      $library['loaded'] = libraries_load_files($library);
+
+      // Invoke callbacks in the 'post-load' group.
+      libraries_invoke('post-load', $library);
+    }
+    $loaded[$name] = $library;
+  }
+
+  return $loaded[$name];
+}
+
+/**
+ * Loads a library's files.
+ *
+ * @param $library
+ *   An array of library information as returned by libraries_info().
+ *
+ * @return
+ *   The number of loaded files.
+ *
+ * @deprecated Will be removed before a stable Drupal 8 release. Please use the
+ * new library load and managment concepts described at:
+ * https://www.drupal.org/node/2170763
+ */
+function libraries_load_files($library) {
+
+  // Construct the full path to the library for later use.
+  $path = $library['library path'];
+  $path = ($library['path'] !== '' ? $path . '/' . $library['path'] : $path);
+
+  // Count the number of loaded files for the return value.
+  $count = 0;
+
+  // Load both the JavaScript and the CSS files.
+  // The parameters for drupal_add_js() and drupal_add_css() require special
+  // handling.
+  // @see drupal_process_attached()
+  foreach (array('js', 'css') as $type) {
+    // Given the removal of core functions like _drupal_add_js and
+    // _drupal_add_css the logic below cannot safely be run anymore.
+    // @see https://www.drupal.org/node/2702563
+    break;
+    if (!empty($library['files'][$type])) {
+      foreach ($library['files'][$type] as $data => $options) {
+        // If the value is not an array, it's a filename and passed as first
+        // (and only) argument.
+        if (!is_array($options)) {
+          $data = $options;
+          $options = array();
+        }
+        // In some cases, the first parameter ($data) is an array. Arrays can't
+        // be passed as keys in PHP, so we have to get $data from the value
+        // array.
+        if (is_numeric($data)) {
+          $data = $options['data'];
+          unset($options['data']);
+        }
+        // Prepend the library path to the file name.
+        $data = "$path/$data";
+        // Apply the default group if the group isn't explicitly given.
+        if (!isset($options['group'])) {
+          $options['group'] = ($type == 'js') ? JS_DEFAULT : CSS_AGGREGATE_DEFAULT;
+        }
+        if ($type === 'js') {
+          $options['version'] = -1;
+        }
+        // @todo Avoid the usage of _drupal_add_js() and _drupal_add_css()
+        call_user_func('_drupal_add_' . $type, $data, $options);
+        $count++;
+      }
+    }
+  }
+
+  // Load PHP files.
+  if (!empty($library['files']['php'])) {
+    foreach ($library['files']['php'] as $file => $array) {
+      $file_path = DRUPAL_ROOT . '/' . $path . '/' . $file;
+      if (file_exists($file_path)) {
+        require_once $file_path;
+        $count++;
+      }
+    }
+  }
+
+  // Load integration files.
+  if (!empty($library['integration files'])) {
+    foreach ($library['integration files'] as $module => $files) {
+      libraries_load_files(array(
+        'files' => $files,
+        'path' => '',
+        'library path' => drupal_get_path('module', $module),
+      ));
+    }
+  }
+
+  return $count;
+}
+
+/**
+ * Gets the version information from an arbitrary library.
+ *
+ * @param $library
+ *   An associative array containing all information about the library.
+ * @param $options
+ *   An associative array containing with the following keys:
+ *   - file: The filename to parse for the version, relative to the library
+ *     path. For example: 'docs/changelog.txt'.
+ *   - pattern: A string containing a regular expression (PCRE) to match the
+ *     library version. For example: '@version\s+([0-9a-zA-Z\.-]+)@'. Note that
+ *     the returned version is not the match of the entire pattern (i.e.
+ *     '@version 1.2.3' in the above example) but the match of the first
+ *     sub-pattern (i.e. '1.2.3' in the above example).
+ *   - lines: (optional) The maximum number of lines to search the pattern in.
+ *     Defaults to 20.
+ *   - cols: (optional) The maximum number of characters per line to take into
+ *     account. Defaults to 200. In case of minified or compressed files, this
+ *     prevents reading the entire file into memory.
+ *
+ * @return
+ *   A string containing the version of the library.
+ *
+ * @see libraries_get_path()
+ *
+ * @deprecated Will be removed before a stable Drupal 8 release. Please use the
+ * new library load and managment concepts described at:
+ * https://www.drupal.org/node/2170763
+ */
+function libraries_get_version($library, $options) {
+  // Provide defaults.
+  $options += array(
+    'file' => '',
+    'pattern' => '',
+    'lines' => 20,
+    'cols' => 200,
+  );
+
+  $file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $options['file'];
+  if (empty($options['file']) || !file_exists($file)) {
+    return;
+  }
+  $file = fopen($file, 'r');
+  while ($options['lines'] && $line = fgets($file, $options['cols'])) {
+    if (preg_match($options['pattern'], $line, $version)) {
+      fclose($file);
+      return $version[1];
+    }
+    $options['lines']--;
+  }
+  fclose($file);
+}
diff --git a/web/modules/contrib/libraries/libraries.services.yml b/web/modules/contrib/libraries/libraries.services.yml
new file mode 100644 (file)
index 0000000..b2880ad
--- /dev/null
@@ -0,0 +1,73 @@
+services:
+  libraries.manager:
+    class: Drupal\libraries\ExternalLibrary\LibraryManager
+    arguments:
+      - '@libraries.definition.discovery'
+      - '@plugin.manager.libraries.library_type'
+
+  # By default Libraries API downloads library definitions from a number of
+  # remote library registries, the canonical one being
+  # https://www.drupal.org/project/libraries_registry, and stores them locally
+  # in the public://library-definitions directory. The URLs of the remote
+  # library registries and the local base path can be configured. The remote
+  # fetching can also be disabled altogether.
+  libraries.definition.discovery:
+    class: Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryInterface
+    factory: 'libraries.definition.discovery.factory:get'
+  libraries.definition.discovery.factory:
+    class: Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryFactory
+    arguments:
+      - '@config.factory'
+      - '@serialization.json'
+      - '@http_client'
+      - '@serialization.json'
+  # If you instead want to check your library definitions into version control
+  # and use YAML for them instead of JSON, you can place the following service
+  # definition in your site's services.yml file:
+  # libraries.definition.discovery:
+  #   class: Drupal\libraries\ExternalLibrary\Definition\FileDefinitionDiscovery
+  #   arguments:
+  #     - '@serialization.yaml'
+  #     # Replace this with the location of the library definitions in your setup.
+  #     - '../library-definitions'
+
+  plugin.manager.libraries.library_type:
+    class: Drupal\libraries\ExternalLibrary\Type\LibraryTypeFactory
+    parent: default_plugin_manager
+  plugin.manager.libraries.locator:
+    class: Drupal\libraries\ExternalLibrary\Local\LocatorManager
+    parent: default_plugin_manager
+  plugin.manager.libraries.version_detector:
+    class: Drupal\libraries\ExternalLibrary\Version\VersionDetectorManager
+    parent: default_plugin_manager
+
+  libraries.config_subscriber:
+    class: Drupal\libraries\Config\LibrariesConfigSubscriber
+    arguments: ['@service_container']
+    tags:
+      - { name: event_subscriber }
+
+  libraries.php_file_loader:
+    class: Drupal\libraries\ExternalLibrary\PhpFile\PhpRequireLoader
+
+  stream_wrapper.library_definitions:
+    class: Drupal\libraries\StreamWrapper\LibraryDefinitionsStream
+    arguments: ['@config.factory']
+    tags:
+      - { name: stream_wrapper, scheme: 'library-definitions' }
+  stream_wrapper.asset_libraries:
+    class: Drupal\libraries\StreamWrapper\AssetLibrariesStream
+    tags:
+      - { name: stream_wrapper, scheme: 'asset' }
+  stream_wrapper.php_file_libraries:
+    class: Drupal\libraries\StreamWrapper\PhpFileLibrariesStream
+    tags:
+      - { name: stream_wrapper, scheme: 'php-file' }
+
+
+  cache.libraries:
+    class: Drupal\Core\Cache\CacheBackendInterface
+    tags:
+      - { name: cache.bin }
+    factory: cache_factory:get
+    arguments: [library]
diff --git a/web/modules/contrib/libraries/src/Annotation/LibraryType.php b/web/modules/contrib/libraries/src/Annotation/LibraryType.php
new file mode 100644 (file)
index 0000000..3cf5491
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+
+namespace Drupal\libraries\Annotation;
+
+use Drupal\Component\Annotation\PluginID;
+
+/**
+ * Provides an annotation class for locator plugins.
+ *
+ * @Annotation
+ */
+class LibraryType extends PluginID {
+
+}
diff --git a/web/modules/contrib/libraries/src/Annotation/Locator.php b/web/modules/contrib/libraries/src/Annotation/Locator.php
new file mode 100644 (file)
index 0000000..ab4311d
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+
+namespace Drupal\libraries\Annotation;
+
+use Drupal\Component\Annotation\PluginID;
+
+/**
+ * Provides an annotation class for locator plugins.
+ *
+ * @Annotation
+ */
+class Locator extends PluginID {
+
+}
diff --git a/web/modules/contrib/libraries/src/Annotation/VersionDetector.php b/web/modules/contrib/libraries/src/Annotation/VersionDetector.php
new file mode 100644 (file)
index 0000000..a664f99
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+
+namespace Drupal\libraries\Annotation;
+
+use Drupal\Component\Annotation\PluginID;
+
+/**
+ * Provides an annotation class for version detector plugins.
+ *
+ * @Annotation
+ */
+class VersionDetector extends PluginID {
+
+}
diff --git a/web/modules/contrib/libraries/src/Config/LibrariesConfigSubscriber.php b/web/modules/contrib/libraries/src/Config/LibrariesConfigSubscriber.php
new file mode 100644 (file)
index 0000000..6578f42
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+
+namespace Drupal\libraries\Config;
+
+use Drupal\Core\Config\ConfigCrudEvent;
+use Drupal\Core\Config\ConfigEvents;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Reacts to configuration changes of the 'libraries.settings' configuration.
+ */
+class LibrariesConfigSubscriber implements EventSubscriberInterface {
+
+  /**
+   * The service container.
+   *
+   * @var \Symfony\Component\DependencyInjection\ContainerInterface
+   */
+  protected $container;
+
+  /**
+   * Constructs a Libraries API configuration subscriber.
+   *
+   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
+   *   The service container.
+   */
+  public function __construct(ContainerInterface $container) {
+    $this->container = $container;
+  }
+
+  /**
+   * Unsets the definition discovery service when its configuration changes.
+   *
+   * @param \Drupal\Core\Config\ConfigCrudEvent $event
+   *   The configuration event.
+   */
+  public function onConfigSave(ConfigCrudEvent $event) {
+    if (($event->getConfig()->getName() === 'libraries.settings') && $event->isChanged('definition')) {
+      $this->container->set('libraries.definition.discovery', NULL);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    return [ConfigEvents::SAVE => 'onConfigSave'];
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Asset/AssetLibrary.php b/web/modules/contrib/libraries/src/ExternalLibrary/Asset/AssetLibrary.php
new file mode 100644 (file)
index 0000000..50a3186
--- /dev/null
@@ -0,0 +1,128 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Asset;
+
+use Drupal\Component\Plugin\Factory\FactoryInterface;
+use Drupal\libraries\ExternalLibrary\Exception\LibraryNotInstalledException;
+use Drupal\libraries\ExternalLibrary\LibraryBase;
+use Drupal\libraries\ExternalLibrary\LibraryManagerInterface;
+use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface;
+use Drupal\libraries\ExternalLibrary\Local\LocalLibraryTrait;
+use Drupal\libraries\ExternalLibrary\Remote\RemoteLibraryInterface;
+use Drupal\libraries\ExternalLibrary\Remote\RemoteLibraryTrait;
+use Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface;
+
+/**
+ * Provides a class for a single attachable asset library.
+ */
+class AssetLibrary extends LibraryBase implements
+  AssetLibraryInterface,
+  LocalLibraryInterface,
+  RemoteLibraryInterface
+{
+
+  use
+    LocalLibraryTrait,
+    RemoteLibraryTrait,
+    LocalRemoteAssetTrait
+  ;
+
+  /**
+   * An array containing the CSS assets of the library.
+   *
+   * @var array
+   */
+  protected $cssAssets = [];
+
+  /**
+   * An array containing the JavaScript assets of the library.
+   *
+   * @var array
+   */
+  protected $jsAssets = [];
+
+  /**
+   * An array of attachable asset library IDs that this library depends on.
+   *
+   * @todo Explain the difference to regular dependencies.
+   */
+  protected $attachableDependencies = [];
+
+  /**
+   * Construct an external library.
+   *
+   * @param string $id
+   *   The library ID.
+   * @param array $definition
+   *   The library definition array.
+   * @param \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface $library_type
+   *   The library type of the library.
+   */
+  public function __construct($id, array $definition, LibraryTypeInterface $library_type) {
+    parent::__construct($id, $definition, $library_type);
+    $this->remoteUrl = $definition['remote_url'];
+    $this->cssAssets = $definition['css'];
+    $this->jsAssets = $definition['js'];
+    $this->attachableDependencies = $definition['attachable_dependencies'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static function processDefinition(array &$definition) {
+    parent::processDefinition($definition);
+    $definition += [
+      'remote_url' => '',
+      'css' => [],
+      'js' => [],
+      'attachable_dependencies' => [],
+    ];
+  }
+
+  /**
+   * Returns a core library array structure for this library.
+   *
+   * @param \Drupal\libraries\ExternalLibrary\LibraryManagerInterface $library_manager
+   *   The library manager that can be used to fetch dependencies.
+   *
+   * @return array
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Asset\getAttachableAssetLibraries::getAttachableAssetLibraries()
+   *
+   * @throws \Drupal\libraries\ExternalLibrary\Exception\InvalidLibraryDependencyException
+   * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException
+   * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryTypeNotFoundException
+   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   *
+   * @todo Document the return value.
+   */
+  public function getAttachableAssetLibrary(LibraryManagerInterface $library_manager) {
+    if (!$this->canBeAttached()) {
+      throw new LibraryNotInstalledException($this);
+    }
+    return [
+      'version' => $this->getVersion(),
+      'css' => $this->processCssAssets($this->cssAssets),
+      'js' => $this->processJsAssets($this->jsAssets),
+      'dependencies' => $this->attachableDependencies,
+    ];
+  }
+
+  /**
+   * Gets the locator of this library using the locator factory.
+   *
+   * @param \Drupal\Component\Plugin\Factory\FactoryInterface $locator_factory
+   *
+   * @return \Drupal\libraries\ExternalLibrary\Local\LocatorInterface
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface::getLocator()
+   */
+  public function getLocator(FactoryInterface $locator_factory) {
+    // @todo Consider consolidating the stream wrappers used here. For now we
+    // allow asset libs to live almost anywhere.
+    return $locator_factory->createInstance('chain')
+      ->addLocator($locator_factory->createInstance('uri', ['uri' => 'asset://']))
+      ->addLocator($locator_factory->createInstance('uri', ['uri' => 'php-file://']));
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Asset/AssetLibraryInterface.php b/web/modules/contrib/libraries/src/ExternalLibrary/Asset/AssetLibraryInterface.php
new file mode 100644 (file)
index 0000000..eac6290
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Asset;
+
+use Drupal\libraries\ExternalLibrary\Dependency\DependentLibraryInterface;
+use Drupal\libraries\ExternalLibrary\LibraryInterface;
+use Drupal\libraries\ExternalLibrary\LibraryManagerInterface;
+use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface;
+
+/**
+ * Provides an interface for external asset libraries with a single library.
+ *
+ * Asset is the generic term for CSS and JavaScript files.
+ *
+ * In order to load assets of external libraries as part of a page request the
+ * assets must be registered with Drupal core's library system. Therefore,
+ * Libraries API makes all libraries that are required by the installed
+ * installation profile, modules, and themes available as core asset libraries
+ * with the identifier 'libraries/[machine_name]' where '[machine_name]' is
+ * the Libraries API machine name of the external library.
+ *
+ * Thus, assuming that the external library 'flexslider' has been declared as a
+ * dependency, for example, it can be attached to a render array in the $build
+ * variable with the following code:
+ * @code
+ *   $build['#attached']['library'] = ['libraries/flexslider'];
+ * @endcode
+ *
+ * In some cases an external library may contain multiple components, that
+ * should be loadable independently from each other. In this case, implement
+ * MultipleAssetLibraryInterface instead.
+ *
+ * @see libraries_library_info_build()
+ * @see \Drupal\libraries\ExternalLibrary\Asset\AssetLibraryTrait
+ * @see \Drupal\libraries\ExternalLibrary\Asset\MultipleAssetLibraryInterface
+ *
+ * @todo Support loading of source or minified assets.
+ * @todo Document how library dependencies work.
+ *
+ * @ingroup libraries
+ */
+interface AssetLibraryInterface extends
+  LibraryInterface,
+  VersionedLibraryInterface,
+  DependentLibraryInterface
+{
+
+  /**
+   * Returns a core asset library array structure for this library.
+   *
+   * @param \Drupal\libraries\ExternalLibrary\LibraryManagerInterface $library_manager
+   *   The library manager that can be used to fetch dependencies.
+   *
+   * @return array
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Asset\SingleAssetLibraryTrait
+   *
+   * @throws \Drupal\libraries\ExternalLibrary\Exception\InvalidLibraryDependencyException
+   *
+   * @todo Document the return value.
+   * @todo Reconsider passing the library manager.
+   */
+  public function getAttachableAssetLibrary(LibraryManagerInterface $library_manager);
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Asset/AttachableAssetLibraryRegistrationInterface.php b/web/modules/contrib/libraries/src/ExternalLibrary/Asset/AttachableAssetLibraryRegistrationInterface.php
new file mode 100644 (file)
index 0000000..8a8a6e5
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Asset;
+
+use Drupal\libraries\ExternalLibrary\LibraryInterface;
+use Drupal\libraries\ExternalLibrary\LibraryManagerInterface;
+
+/**
+ * An interface for library types that want to react to library instantiation.
+ */
+interface AttachableAssetLibraryRegistrationInterface {
+
+  /**
+   * Reacts to the instantiation of a library.
+   *
+   * @param \Drupal\libraries\ExternalLibrary\LibraryInterface $external_library
+   *   The library that is being instantiated.
+   * @param \Drupal\libraries\ExternalLibrary\LibraryManagerInterface $library_manager
+   */
+  public function getAttachableAssetLibraries(LibraryInterface $external_library, LibraryManagerInterface $library_manager);
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Asset/LocalRemoteAssetTrait.php b/web/modules/contrib/libraries/src/ExternalLibrary/Asset/LocalRemoteAssetTrait.php
new file mode 100644 (file)
index 0000000..e673cab
--- /dev/null
@@ -0,0 +1,108 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Asset;
+
+/**
+ * A trait for asset libraries that serve local and remote files.
+ *
+ * If the library files are available locally, they are served locally.
+ * Otherwise, the remote files are served, assuming a remote URL is specified.
+ *
+ * This trait should only be used in classes implementing LocalLibraryInterface
+ * and RemoteLibraryInterface.
+ *
+ * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface
+ * @see \Drupal\libraries\ExternalLibrary\Remote\RemoteLibraryInterface
+ */
+trait LocalRemoteAssetTrait {
+
+  /**
+   * Checks whether this library can be attached.
+   *
+   * @return bool
+   *   TRUE if the library can be attached; FALSE otherwise.
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Asset\SingleAssetLibraryTrait::canBeAttached()
+   */
+  protected function canBeAttached() {
+    /** @var \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface|\Drupal\libraries\ExternalLibrary\Remote\RemoteLibraryInterface $this */
+    return ($this->isInstalled() || $this->hasRemoteUrl());
+  }
+
+  /**
+   * Gets the prefix to prepend to file paths.
+   *
+   * For local libraries this is the library path, for remote libraries this is
+   * the remote URL.
+   *
+   * @return string
+   *   The path prefix.
+   */
+  protected function getPathPrefix() {
+    /** @var \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface|\Drupal\libraries\ExternalLibrary\Remote\RemoteLibraryInterface $this */
+    if ($this->isInstalled()) {
+      // LocalLibraryInterface::getLocalPath() returns the path relative to the
+      // app root. In order for the core core asset system to register the path
+      // as relative to the app root, a leading slash is required.
+      /** @see \Drupal\Core\Asset\LibraryDiscoveryParser::buildByExtension() */
+      return '/' . $this->getLocalPath();
+    }
+    elseif ($this->hasRemoteUrl()) {
+      return $this->getRemoteUrl();
+    }
+    else {
+      // @todo Throw an exception.
+    }
+  }
+
+  /**
+   * Gets the CSS assets attached to this library.
+   *
+   * @param array $assets
+   *
+   * @return array
+   *   An array of CSS assets of the library following the core library CSS
+   *   structure. The keys of the array must be among the SMACSS categories
+   *   'base', 'layout, 'component', 'state', and 'theme'. The value of each
+   *   category is in turn an array where the keys are the file paths of the CSS
+   *   files and values are CSS options.
+   *
+   * @see https://smacss.com/
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Asset\SingleAssetLibraryTrait::getCssAssets()
+   */
+  protected function processCssAssets(array $assets) {
+    // @todo Consider somehow caching the processed information.
+    $processed_assets = [];
+    foreach ($assets as $category => $category_assets) {
+      // @todo Somehow consolidate this with getJsAssets().
+      foreach ($category_assets as $filename => $options) {
+        $processed_assets[$category][$this->getPathPrefix() . '/' . $filename] = $options;
+      }
+    }
+    return $processed_assets;
+  }
+
+  /**
+   * Gets the JavaScript assets attached to this library.
+   *
+   * @param array $assets
+   *
+   * @return array
+   *   An array of JavaScript assets of the library. The keys of the array are
+   *   the file paths of the JavaScript files and the values are JavaScript
+   *   options.
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Asset\SingleAssetLibraryTrait::getJsAssets()
+   */
+  protected function processJsAssets(array $assets) {
+    // @todo Consider somehow caching the processed information.
+    $processed_assets = [];
+    // @todo Somehow consolidate this with getCssAssets().
+    foreach ($assets as $filename => $options) {
+      $processed_assets[$this->getPathPrefix() . '/' . $filename] = $options;
+    }
+    return $processed_assets;
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Asset/MultipleAssetLibrary.php b/web/modules/contrib/libraries/src/ExternalLibrary/Asset/MultipleAssetLibrary.php
new file mode 100644 (file)
index 0000000..f2c3d3c
--- /dev/null
@@ -0,0 +1,123 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Asset;
+
+use Drupal\Component\Plugin\Factory\FactoryInterface;
+use Drupal\libraries\ExternalLibrary\Dependency\DependentLibraryInterface;
+use Drupal\libraries\ExternalLibrary\Exception\LibraryNotInstalledException;
+use Drupal\libraries\ExternalLibrary\LibraryBase;
+use Drupal\libraries\ExternalLibrary\LibraryManagerInterface;
+use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface;
+use Drupal\libraries\ExternalLibrary\Local\LocalLibraryTrait;
+use Drupal\libraries\ExternalLibrary\Remote\RemoteLibraryInterface;
+use Drupal\libraries\ExternalLibrary\Remote\RemoteLibraryTrait;
+use Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface;
+use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface;
+
+/**
+ * Provides a class for a library with multiple attachable asset libraries.
+ */
+class MultipleAssetLibrary extends LibraryBase implements
+  MultipleAssetLibraryInterface,
+  VersionedLibraryInterface,
+  DependentLibraryInterface,
+  LocalLibraryInterface,
+  RemoteLibraryInterface
+{
+
+  use
+    LocalLibraryTrait,
+    RemoteLibraryTrait,
+    LocalRemoteAssetTrait
+  ;
+
+  /**
+   * An array of attachable asset libraries.
+   */
+  protected $libraries = [];
+
+  /**
+   * Construct an external library.
+   *
+   * @param string $id
+   *   The library ID.
+   * @param array $definition
+   *   The library definition array.
+   * @param \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface $library_type
+   *   The library type of the library.
+   */
+  public function __construct($id, array $definition, LibraryTypeInterface $library_type) {
+    parent::__construct($id, $definition, $library_type);
+    $this->remoteUrl = $definition['remote_url'];
+    $this->libraries = $definition['libraries'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static function processDefinition(array &$definition) {
+    parent::processDefinition($definition);
+    $definition += [
+      'remote_url' => '',
+      'libraries' => [],
+    ];
+    foreach ($definition['libraries'] as &$library) {
+      $library += [
+        'css' => [],
+        'js' => [],
+        'dependencies' => [],
+      ];
+    }
+  }
+
+  /**
+   * Returns a core library array structure for this library.
+   *
+   * @param \Drupal\libraries\ExternalLibrary\LibraryManagerInterface $library_manager
+   *   The library manager that can be used to fetch dependencies.
+   *
+   * @return array
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Asset\getAttachableAssetLibraries::getAttachableAssetLibraries()
+   *
+   * @throws \Drupal\libraries\ExternalLibrary\Exception\InvalidLibraryDependencyException
+   * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException
+   * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryTypeNotFoundException
+   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   *
+   * @todo Document the return value.
+   */
+  public function getAttachableAssetLibraries(LibraryManagerInterface $library_manager) {
+    if (!$this->canBeAttached()) {
+      throw new LibraryNotInstalledException($this);
+    }
+    $attachable_libraries = [];
+    foreach ($this->libraries as $attachable_library_id => $attachable_library) {
+      $attachable_libraries[$attachable_library_id] = [
+        'version' => $this->getVersion(),
+        'css' => $this->processCssAssets($attachable_library['css']),
+        'js' => $this->processJsAssets($attachable_library['js']),
+        'dependencies' => $attachable_library['dependencies'],
+      ];
+    }
+    return $attachable_libraries;
+  }
+
+  /**
+   * Gets the locator of this library using the locator factory.
+   *
+   * @param \Drupal\Component\Plugin\Factory\FactoryInterface $locator_factory
+   *
+   * @return \Drupal\libraries\ExternalLibrary\Local\LocatorInterface
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface::getLocator()
+   */
+  public function getLocator(FactoryInterface $locator_factory) {
+    // @todo Consider consolidating the stream wrappers used here. For now we
+    // allow asset libs to live almost anywhere.
+    return $locator_factory->createInstance('chain')
+      ->addLocator($locator_factory->createInstance('uri', ['uri' => 'asset://']))
+      ->addLocator($locator_factory->createInstance('uri', ['uri' => 'php-file://']));
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Asset/MultipleAssetLibraryInterface.php b/web/modules/contrib/libraries/src/ExternalLibrary/Asset/MultipleAssetLibraryInterface.php
new file mode 100644 (file)
index 0000000..5dc43ff
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Asset;
+
+use Drupal\libraries\ExternalLibrary\LibraryInterface;
+use Drupal\libraries\ExternalLibrary\LibraryManagerInterface;
+
+/**
+ * Provides an interface for external asset libraries with multiple libraries.
+ *
+ * See SingleAssetLibraryInterface for more information on external asset
+ * libraries in general.
+ *
+ * In case an external asset library contains multiple components that should
+ * be loadable independently from each other, Libraries API registers each
+ * library component as a separate library in the core asset library system. The
+ * resulting core library identifier is
+ * 'libraries/[machine_name].[component_name]' where '[machine_name]' is the
+ * Libraries API machine name of the external library and '[component_name]' is
+ * the component name specified by the library definition.
+ *
+ * Thus, assuming that the external library 'bootstrap' has been declared as a
+ * dependency, for example, and it has 'button' and 'form' components, they can
+ * be attached to a render array in the $build variable with the following code:
+ * @code
+ *   $build['#attached']['library'] = [
+ *     'libraries/bootstrap.button',
+ *     'libraries/bootstrap.form',
+ *   ];
+ * @endcode
+ *
+ * @see \Drupal\libraries\ExternalLibrary\Asset\AssetLibraryInterface
+ *
+ * @todo Support loading of source or minified assets.
+ * @todo Document how library dependencies work.
+ */
+interface MultipleAssetLibraryInterface extends LibraryInterface {
+
+  /**
+   * Separates the library machine name from its component name.
+   *
+   * The period is chosen in alignment with core asset libraries, which are
+   * named, for example, 'core/jquery.once'.
+   */
+  const SEPARATOR = '.';
+
+  /**
+   * Returns a core asset library array structure for this library.
+   *
+   * @param \Drupal\libraries\ExternalLibrary\LibraryManagerInterface $library_manager
+   *   The library manager that can be used to fetch dependencies.
+   *
+   * @return array
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Asset\SingleAssetLibraryTrait
+   *
+   * @throws \Drupal\libraries\ExternalLibrary\Exception\InvalidLibraryDependencyException
+   *
+   * @todo Document the return value.
+   * @todo Reconsider passing the library manager.
+   */
+  public function getAttachableAssetLibraries(LibraryManagerInterface $library_manager);
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Definition/ChainDefinitionDiscovery.php b/web/modules/contrib/libraries/src/ExternalLibrary/Definition/ChainDefinitionDiscovery.php
new file mode 100644 (file)
index 0000000..f588018
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Definition;
+
+use Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException;
+
+/**
+ * Provides a definition discovery that checks a list of other discoveries.
+ *
+ * The discoveries are checked sequentially. If the definition was not present
+ * in some discoveries but is found in a later discovery the definition will be
+ * written to the earlier discoveries if they implement
+ * WritableDefinitionDiscoveryInterface.
+ *
+ * @see \Drupal\libraries\ExternalLibrary\Definition\WritableDefinitionDiscoveryInterface
+ */
+class ChainDefinitionDiscovery implements DefinitionDiscoveryInterface {
+
+  /**
+   * The list of definition discoveries that will be checked.
+   *
+   * @var \Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryInterface[]
+   */
+  protected $discoveries = [];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasDefinition($id) {
+    foreach ($this->discoveries as $discovery) {
+      if ($discovery->hasDefinition($id)) {
+        return TRUE;
+      }
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDefinition($id) {
+    /** @var \Drupal\libraries\ExternalLibrary\Definition\WritableDefinitionDiscoveryInterface[] $discoveries_to_write */
+    $discoveries_to_write = [];
+    foreach ($this->discoveries as $discovery) {
+      if ($discovery->hasDefinition($id)) {
+        $definition = $discovery->getDefinition($id);
+        break;
+      }
+      elseif ($discovery instanceof WritableDefinitionDiscoveryInterface) {
+        $discoveries_to_write[] = $discovery;
+      }
+    }
+
+    if (!isset($definition)) {
+      throw new LibraryDefinitionNotFoundException($id);
+    }
+
+    foreach ($discoveries_to_write as $discovery_to_write) {
+      $discovery_to_write->writeDefinition($id, $definition);
+    }
+
+    return $definition;
+  }
+
+  /**
+   * Adds a definition discovery to the list to check.
+   *
+   * @param \Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryInterface $discovery
+   *   The definition discovery to add.
+   *
+   * @return $this
+   */
+  public function addDiscovery(DefinitionDiscoveryInterface $discovery) {
+    $this->discoveries[] = $discovery;
+    return $this;
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Definition/DefinitionDiscoveryFactory.php b/web/modules/contrib/libraries/src/ExternalLibrary/Definition/DefinitionDiscoveryFactory.php
new file mode 100644 (file)
index 0000000..970385e
--- /dev/null
@@ -0,0 +1,104 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Definition;
+
+use Drupal\Component\Serialization\SerializationInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use GuzzleHttp\ClientInterface;
+
+/**
+ * Instantiates a library definition discovery based on configuration.
+ */
+class DefinitionDiscoveryFactory {
+
+  /**
+   * The configuration factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * The serializer for local definition files.
+   *
+   * @var \Drupal\Component\Serialization\SerializationInterface
+   */
+  protected $localSerializer;
+
+  /**
+   * The HTTP client used to fetch remote definitions.
+   *
+   * @var \GuzzleHttp\ClientInterface
+   */
+  protected $httpClient;
+
+  /**
+   * The serializer for remote definitions.
+   *
+   * @var \Drupal\Component\Serialization\SerializationInterface
+   */
+  protected $remoteSerializer;
+
+  /**
+   * Constructs a definition discovery factory.
+   *
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The configuration factory.
+   * @param \Drupal\Component\Serialization\SerializationInterface $local_serializer
+   *   The serializer for local definition files.
+   * @param \GuzzleHttp\ClientInterface $http_client
+   *   The HTTP client used to fetch remote definitions.
+   * @param \Drupal\Component\Serialization\SerializationInterface $remote_serializer
+   *   The serializer for remote definitions.
+   */
+  public function __construct(
+    ConfigFactoryInterface $config_factory,
+    SerializationInterface $local_serializer,
+    ClientInterface $http_client,
+    SerializationInterface $remote_serializer
+  ) {
+    $this->configFactory = $config_factory;
+    $this->localSerializer = $local_serializer;
+    $this->httpClient = $http_client;
+    $this->remoteSerializer = $remote_serializer;
+  }
+
+  /**
+   * Gets a library definition discovery.
+   *
+   * @return \Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryInterface
+   *   The library definition discovery.
+   */
+  public function get() {
+    $config = $this->configFactory->get('libraries.settings');
+
+    if ($config->get('definition.remote.enable')) {
+      $discovery = new ChainDefinitionDiscovery();
+
+      $local_discovery = new WritableFileDefinitionDiscovery(
+        $this->localSerializer,
+        $config->get('definition.local.path')
+      );
+      $discovery->addDiscovery($local_discovery);
+
+      foreach ($config->get('definition.remote.urls') as $remote_url) {
+        $remote_discovery = new GuzzleDefinitionDiscovery(
+          $this->httpClient,
+          $this->remoteSerializer,
+          $remote_url
+        );
+
+        $discovery->addDiscovery($remote_discovery);
+      }
+    }
+    else {
+      $discovery = new FileDefinitionDiscovery(
+        $this->localSerializer,
+        $config->get('definition.local.path')
+      );
+    }
+
+    return $discovery;
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Definition/DefinitionDiscoveryInterface.php b/web/modules/contrib/libraries/src/ExternalLibrary/Definition/DefinitionDiscoveryInterface.php
new file mode 100644 (file)
index 0000000..61fc589
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Definition;
+
+/**
+ * Provides an interface for library definition discoveries.
+ *
+ * This is similar to the plugin system's DiscoveryInterface, except that this
+ * does not require knowing all definitions upfront, so there is no
+ * getDefinitions() method.
+ *
+ * @see \Drupal\Component\Plugin\Discovery\DiscoveryInterface
+ *
+ * @ingroup libraries
+ */
+interface DefinitionDiscoveryInterface {
+
+  /**
+   * Checks whether a library definition exists.
+   *
+   * @param string $id
+   *   The library ID.
+   *
+   * @return bool
+   *   TRUE if a library definition with the given ID exists; FALSE otherwise.
+   */
+  public function hasDefinition($id);
+
+  /**
+   * Gets a library definition by its ID.
+   *
+   * @param string $id
+   *   The library ID.
+   *
+   * @return array
+   *   The library definition.
+   *
+   * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException
+   *
+   * @todo Consider returning a classed object instead of an array or at least
+   *   document and validate the array structure.
+   */
+  public function getDefinition($id);
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Definition/FileDefinitionDiscovery.php b/web/modules/contrib/libraries/src/ExternalLibrary/Definition/FileDefinitionDiscovery.php
new file mode 100644 (file)
index 0000000..6ad62c4
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Definition;
+
+use Drupal\Component\Serialization\SerializationInterface;
+use Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException;
+
+/**
+ * Provides a libraries definition discovery using PHP's native file functions.
+ *
+ * It supports either a URI with a stream wrapper, an absolute file path or a
+ * file path relative to the Drupal root as a base URI.
+ *
+ * By default YAML files are used.
+ *
+ * @see \Drupal\libraries\StreamWrapper\LibraryDefinitionsStream
+ *
+ * @ingroup libraries
+ */
+class FileDefinitionDiscovery extends FileDefinitionDiscoveryBase implements DefinitionDiscoveryInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasDefinition($id) {
+    return file_exists($this->getFileUri($id));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getSerializedDefinition($id) {
+    return file_get_contents($this->getFileUri($id));
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Definition/FileDefinitionDiscoveryBase.php b/web/modules/contrib/libraries/src/ExternalLibrary/Definition/FileDefinitionDiscoveryBase.php
new file mode 100644 (file)
index 0000000..0151f27
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Definition;
+
+use Drupal\Component\Serialization\SerializationInterface;
+use Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException;
+
+/**
+ * Provides a base implementation for file-based definition discoveries.
+ *
+ * This discovery assumes that library files contain the serialized library
+ * definition and are accessible under a common base URI. The expected library
+ * file URI will be constructed from this by appending '/$id.$extension' to
+ * this, where $id is the library ID and $extension is the serializer extension.
+ */
+abstract class FileDefinitionDiscoveryBase implements DefinitionDiscoveryInterface {
+
+  /**
+   * The serializer for the library definition files.
+   *
+   * @var \Drupal\Component\Serialization\SerializationInterface
+   */
+  protected $serializer;
+
+  /**
+   * The base URI for the library files.
+   *
+   * @var string
+   */
+  protected $baseUri;
+
+  /**
+   * Constructs a stream-based library definition discovery.
+   *
+   * @param \Drupal\Component\Serialization\SerializationInterface $serializer
+   *   The serializer for the library definition files.
+   * @param string $base_uri
+   *   The base URI for the library files.
+   */
+  public function __construct(SerializationInterface $serializer, $base_uri) {
+    $this->serializer = $serializer;
+    $this->baseUri = $base_uri;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDefinition($id) {
+    if (!$this->hasDefinition($id)) {
+      throw new LibraryDefinitionNotFoundException($id);
+    }
+    return $this->serializer->decode($this->getSerializedDefinition($id));
+  }
+
+  /**
+   * Gets the contents of the library file.
+   *
+   * @param $id
+   *   The library ID to retrieve the serialized definition for.
+   *
+   * @return string
+   *   The serialized library definition.
+   *
+   * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException
+   */
+  abstract protected function getSerializedDefinition($id);
+
+  /**
+   * Returns the file URI of the library definition file for a given library ID.
+   *
+   * @param $id
+   *   The ID of the external library.
+   *
+   * @return string
+   *   The file URI of the file the library definition resides in.
+   */
+  protected function getFileUri($id) {
+    $filename = $id . '.' . $this->serializer->getFileExtension();
+    return "$this->baseUri/$filename";
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Definition/GuzzleDefinitionDiscovery.php b/web/modules/contrib/libraries/src/ExternalLibrary/Definition/GuzzleDefinitionDiscovery.php
new file mode 100644 (file)
index 0000000..59745b7
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Definition;
+
+use Drupal\Component\Serialization\SerializationInterface;
+use Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException;
+use GuzzleHttp\ClientInterface;
+use GuzzleHttp\Exception\GuzzleException;
+
+/**
+ * Provides a definition discovery that fetches remote definitions using Guzzle.
+ *
+ * By default JSON files are assumed to be in JSON format.
+ *
+ * @todo Cache responses statically by ID to avoid multiple HTTP requests when
+ *   calling hasDefinition() and getDefinition() sequentially.
+ */
+class GuzzleDefinitionDiscovery extends FileDefinitionDiscoveryBase implements DefinitionDiscoveryInterface {
+
+  /**
+   * The HTTP client.
+   *
+   * @var \GuzzleHttp\ClientInterface
+   */
+  protected $httpClient;
+
+  /**
+   * Constructs a Guzzle-based definition discvoery.
+   *
+   * @param \GuzzleHttp\ClientInterface $http_client
+   *   The HTTP client.
+   * @param \Drupal\Component\Serialization\SerializationInterface $serializer
+   *   The serializer for the library definition files.
+   * @param string $base_url
+   *   The base URL for the library files.
+   */
+  public function __construct(ClientInterface $http_client, SerializationInterface $serializer, $base_url) {
+    parent::__construct($serializer, $base_url);
+    $this->httpClient = $http_client;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasDefinition($id) {
+    try {
+      $response = $this->httpClient->request('GET', $this->getFileUri($id));
+      return $response->getStatusCode() === 200;
+    }
+    catch (GuzzleException $exception) {
+      return FALSE;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getSerializedDefinition($id) {
+    try {
+      $response = $this->httpClient->request('GET', $this->getFileUri($id));
+      return (string) $response->getBody();
+    }
+    catch (GuzzleException $exception) {
+      throw new LibraryDefinitionNotFoundException($id, '', 0, $exception);
+    }
+    catch (\RuntimeException $exception) {
+      throw new LibraryDefinitionNotFoundException($id, '', 0, $exception);
+    }
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Definition/WritableDefinitionDiscoveryInterface.php b/web/modules/contrib/libraries/src/ExternalLibrary/Definition/WritableDefinitionDiscoveryInterface.php
new file mode 100644 (file)
index 0000000..f644a0d
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Definition;
+
+/**
+ * Provides an interface for library definition discoveries that are writable.
+ *
+ * @see \Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryInterface
+ * @see \Drupal\libraries\ExternalLibrary\Definition\ChainDefinitionDiscovery
+ */
+interface WritableDefinitionDiscoveryInterface extends DefinitionDiscoveryInterface {
+
+  /**
+   * Writes a library definition persistently.
+   *
+   * @param string $id
+   *   The library ID.
+   * @param array $definition
+   *   The library definition to write.
+   *
+   * @return $this
+   */
+  public function writeDefinition($id, $definition);
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Definition/WritableFileDefinitionDiscovery.php b/web/modules/contrib/libraries/src/ExternalLibrary/Definition/WritableFileDefinitionDiscovery.php
new file mode 100644 (file)
index 0000000..b8204fb
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Definition;
+
+/**
+ * Provides a definition discovery based on a writable directory or stream.
+ *
+ * @see \Drupal\libraries\ExternalLibrary\Definition\FileDefinitionDiscovery
+ */
+class WritableFileDefinitionDiscovery extends FileDefinitionDiscovery implements WritableDefinitionDiscoveryInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function writeDefinition($id, $definition) {
+    file_put_contents($this->getFileUri($id), $this->serializer->encode($definition));
+    return $this;
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Dependency/DependentLibraryInterface.php b/web/modules/contrib/libraries/src/ExternalLibrary/Dependency/DependentLibraryInterface.php
new file mode 100644 (file)
index 0000000..fd83810
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Dependency;
+
+use Drupal\libraries\ExternalLibrary\LibraryInterface;
+
+/**
+ * Provides an interface for libraries that depend on other libraries.
+ *
+ * @todo Implement versioned dependencies.
+ */
+interface DependentLibraryInterface extends LibraryInterface {
+
+  /**
+   * Returns the libraries dependencies, if any.
+   *
+   * @return string[]
+   *   An array of library IDs of libraries that the library depends on.
+   */
+  public function getDependencies();
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Dependency/DependentLibraryTrait.php b/web/modules/contrib/libraries/src/ExternalLibrary/Dependency/DependentLibraryTrait.php
new file mode 100644 (file)
index 0000000..e7623ae
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Dependency;
+
+/**
+ * Provides a trait for libraries that depend on other libraries.
+ */
+trait DependentLibraryTrait {
+
+  /**
+   * An array of library IDs of libraries that the library depends on.
+   *
+   * @return string[]
+   */
+  protected $dependencies;
+
+  /**
+   * Returns the libraries dependencies, if any.
+   *
+   * @return string[]
+   *   An array of library IDs of libraries that the library depends on.
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Dependency\DependentLibraryInterface::getDependencies()
+   */
+  public function getDependencies() {
+    return $this->dependencies;
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Exception/InvalidLibraryDependencyException.php b/web/modules/contrib/libraries/src/ExternalLibrary/Exception/InvalidLibraryDependencyException.php
new file mode 100644 (file)
index 0000000..7ab3eeb
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Exception;
+
+use Drupal\libraries\ExternalLibrary\Utility\DependencyAccessorTrait;
+use Drupal\libraries\ExternalLibrary\LibraryInterface;
+use Drupal\libraries\ExternalLibrary\Utility\LibraryAccessorTrait;
+use Drupal\libraries\ExternalLibrary\Utility\LibraryAccessorInterface;
+
+/**
+ * Provides an exception for an invalid library exception.
+ */
+class InvalidLibraryDependencyException extends \UnexpectedValueException implements LibraryAccessorInterface {
+
+  use LibraryAccessorTrait;
+  use DependencyAccessorTrait;
+
+  /**
+   * Constructs a library exception.
+   *
+   * @param \Drupal\libraries\ExternalLibrary\LibraryInterface $library
+   *   The library with the invalid dependency.
+   * @param \Drupal\libraries\ExternalLibrary\LibraryInterface $dependency
+   *   The dependency.
+   * @param string $message
+   *   (optional) The exception message.
+   * @param int $code
+   *   (optional) The error code.
+   * @param \Exception $previous
+   *   (optional) The previous exception.
+   */
+  public function __construct(
+    LibraryInterface $library,
+    LibraryInterface $dependency,
+    $message = '',
+    $code = 0,
+    \Exception $previous = NULL
+  ) {
+    $this->library = $library;
+    $this->dependency = $dependency;
+    $message = $message ?: "The library '{$this->library->getId()}' cannot depend on the library '{$this->dependency->getId()}'.";
+    parent::__construct($message, $code, $previous);
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Exception/LibraryDefinitionNotFoundException.php b/web/modules/contrib/libraries/src/ExternalLibrary/Exception/LibraryDefinitionNotFoundException.php
new file mode 100644 (file)
index 0000000..c274302
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Exception;
+
+use Drupal\libraries\ExternalLibrary\Utility\LibraryIdAccessorTrait;
+use Drupal\libraries\ExternalLibrary\Utility\LibraryIdAccessorInterface;
+
+/**
+ * Provides an exception for a library definition that cannot be found.
+ */
+class LibraryDefinitionNotFoundException extends \RuntimeException implements LibraryIdAccessorInterface {
+
+  use LibraryIdAccessorTrait;
+
+  /**
+   * Constructs a library exception.
+   *
+   * @param string $library_id
+   *   The library ID.
+   * @param string $message
+   *   (optional) The exception message.
+   * @param int $code
+   *   (optional) The error code.
+   * @param \Exception $previous
+   *   (optional) The previous exception.
+   */
+  public function __construct(
+    $library_id,
+    $message = '',
+    $code = 0,
+    \Exception $previous = NULL
+  ) {
+    $this->libraryId = (string) $library_id;
+    $message = $message ?: "The library definition for the library '{$this->libraryId}' could not be found.";
+    parent::__construct($message, $code, $previous);
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Exception/LibraryNotInstalledException.php b/web/modules/contrib/libraries/src/ExternalLibrary/Exception/LibraryNotInstalledException.php
new file mode 100644 (file)
index 0000000..ed48a62
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Exception;
+
+use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface;
+use Drupal\libraries\ExternalLibrary\Utility\LibraryAccessorTrait;
+use Drupal\libraries\ExternalLibrary\Utility\LibraryAccessorInterface;
+
+/**
+ * Provides an exception for a library that is not installed.
+ */
+class LibraryNotInstalledException extends \RuntimeException implements LibraryAccessorInterface {
+
+  use LibraryAccessorTrait;
+
+  /**
+   * Constructs a library exception.
+   *
+   * @param \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface $library
+   *   The library that is not installed.
+   * @param string $message
+   *   (optional) The exception message.
+   * @param int $code
+   *   (optional) The error code.
+   * @param \Exception $previous
+   *   (optional) The previous exception.
+   */
+  public function __construct(
+    LocalLibraryInterface $library,
+    $message = '',
+    $code = 0,
+    \Exception $previous = NULL
+  ) {
+    $this->library = $library;
+    $message = $message ?: "The library '{$this->library->getId()}' is not installed.";
+    parent::__construct($message, $code, $previous);
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Exception/LibraryTypeNotFoundException.php b/web/modules/contrib/libraries/src/ExternalLibrary/Exception/LibraryTypeNotFoundException.php
new file mode 100644 (file)
index 0000000..b5655bb
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Exception;
+
+use Drupal\libraries\ExternalLibrary\Utility\LibraryIdAccessorTrait;
+use Drupal\libraries\ExternalLibrary\Utility\LibraryIdAccessorInterface;
+
+/**
+ * Provides an exception for a library definition without a type declaration.
+ */
+class LibraryTypeNotFoundException extends \RuntimeException implements LibraryAccessorInterface {
+
+  use LibraryIdAccessorTrait;
+
+  /**
+   * Constructs a library exception.
+   *
+   * @param string $library_id
+   *   The library ID.
+   * @param string $message
+   *   (optional) The exception message.
+   * @param int $code
+   *   (optional) The error code.
+   * @param \Exception $previous
+   *   (optional) The previous exception.
+   */
+  public function __construct(
+    $library_id,
+    $message = '',
+    $code = 0,
+    \Exception $previous = NULL
+  ) {
+    $this->libraryId = (string) $library_id;
+    $message = $message ?: "The library type for the library '{$this->libraryId}' could not be found.";
+    parent::__construct($message, $code, $previous);
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Exception/UnknownLibraryVersionException.php b/web/modules/contrib/libraries/src/ExternalLibrary/Exception/UnknownLibraryVersionException.php
new file mode 100644 (file)
index 0000000..4afe059
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Exception;
+
+use Drupal\libraries\ExternalLibrary\Utility\LibraryAccessorTrait;
+use Drupal\libraries\ExternalLibrary\Utility\LibraryAccessorInterface;
+use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface;
+
+/**
+ * Provides an exception for libraries whose version has not been detected.
+ */
+class UnknownLibraryVersionException extends \RuntimeException implements LibraryAccessorInterface {
+
+  use LibraryAccessorTrait;
+
+  /**
+   * Constructs a library exception.
+   *
+   * @param \Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface $library
+   *   The library.
+   * @param string $message
+   *   (optional) The exception message.
+   * @param int $code
+   *   (optional) The error code.
+   * @param \Exception $previous
+   *   (optional) The previous exception.
+   */
+  public function __construct(
+    VersionedLibraryInterface $library,
+    $message = '',
+    $code = 0,
+    \Exception $previous = NULL
+  ) {
+    $this->library = $library;
+    $message = $message ?: "The version of library '{$this->library->getId()}' could not be detected.";
+    parent::__construct($message, $code, $previous);
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/LibraryBase.php b/web/modules/contrib/libraries/src/ExternalLibrary/LibraryBase.php
new file mode 100644 (file)
index 0000000..8571552
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary;
+
+use Drupal\libraries\ExternalLibrary\Dependency\DependentLibraryInterface;
+use Drupal\libraries\ExternalLibrary\Dependency\DependentLibraryTrait;
+use Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface;
+use Drupal\libraries\ExternalLibrary\Utility\IdAccessorTrait;
+use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface;
+use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryTrait;
+
+/**
+ * Provides a base external library implementation.
+ */
+abstract class LibraryBase implements
+  LibraryInterface,
+  DependentLibraryInterface,
+  VersionedLibraryInterface
+{
+
+  use
+    IdAccessorTrait,
+    DependentLibraryTrait,
+    VersionedLibraryTrait
+  ;
+
+  /**
+   * The library type of this library.
+   *
+   * @var \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface
+   */
+  protected $type;
+
+  /**
+   * Constructs a library.
+   *
+   * @param string $id
+   *   The library ID.
+   * @param array $definition
+   *   The library definition array.
+   * @param \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface $type
+   *   The library type of this library.
+   */
+  public function __construct($id, array $definition, LibraryTypeInterface $type) {
+    $this->id = (string) $id;
+    $this->type = $type;
+    $this->dependencies = $definition['dependencies'];
+    $this->versionDetector = $definition['version_detector'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create($id, array $definition, LibraryTypeInterface $type) {
+    static::processDefinition($definition);
+    return new static($id, $definition, $type);
+  }
+
+  /**
+   * Gets library definition defaults.
+   *
+   * @param array $definition
+   *   A library definition array.
+   */
+  protected static function processDefinition(array &$definition) {
+    $definition += [
+      'dependencies' => [],
+      // @todo This fallback is not very elegant.
+      'version_detector' => [
+        'id' => 'static',
+        'configuration' => ['version' => ''],
+      ],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getType() {
+    return $this->type;
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/LibraryInterface.php b/web/modules/contrib/libraries/src/ExternalLibrary/LibraryInterface.php
new file mode 100644 (file)
index 0000000..ebf43a9
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary;
+
+use Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface;
+
+/**
+ * Provides an interface for different types of external libraries.
+ *
+ * @ingroup libraries
+ */
+interface LibraryInterface {
+
+  /**
+   * Creates an instance of the library from its definition.
+   *
+   * @param string $id
+   *   The library ID.
+   * @param array $definition
+   *   The library definition array.
+   * @param \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface $type
+   *   The library type of this library.
+   *
+   * @return static
+   */
+  public static function create($id, array $definition, LibraryTypeInterface $type);
+
+  /**
+   * Returns the ID of the library.
+   *
+   * @return string
+   *   The library ID. This must be unique among all known libraries.
+   */
+  public function getId();
+
+  /**
+   * Returns the library type of the library.
+   *
+   * @return \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface
+   *   The library of the library.
+   */
+  public function getType();
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/LibraryManager.php b/web/modules/contrib/libraries/src/ExternalLibrary/LibraryManager.php
new file mode 100644 (file)
index 0000000..17a6223
--- /dev/null
@@ -0,0 +1,120 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary;
+
+use Drupal\Component\Plugin\Factory\FactoryInterface;
+use Drupal\libraries\ExternalLibrary\Exception\LibraryTypeNotFoundException;
+use Drupal\libraries\ExternalLibrary\Type\LibraryCreationListenerInterface;
+use Drupal\libraries\ExternalLibrary\Type\LibraryLoadingListenerInterface;
+use Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryInterface;
+
+/**
+ * Provides a manager for external libraries.
+ *
+ * @todo Dispatch events at various points in the library lifecycle.
+ * @todo Automatically load PHP file libraries that are required by modules or
+ *   themes.
+ */
+class LibraryManager implements LibraryManagerInterface {
+
+  /**
+   * The library definition discovery.
+   *
+   * @var \Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryInterface
+   */
+  protected $definitionDiscovery;
+
+  /**
+   * The library type factory.
+   *
+   * @var \Drupal\Component\Plugin\Factory\FactoryInterface
+   */
+  protected $libraryTypeFactory;
+
+  /**
+   * Constructs an external library manager.
+   *
+   * @param \Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryInterface $definition_disovery
+   *   The library registry.
+   * @param \Drupal\Component\Plugin\Factory\FactoryInterface $library_type_factory
+   *   The library type factory.
+   */
+  public function __construct(
+    DefinitionDiscoveryInterface $definition_disovery,
+    FactoryInterface $library_type_factory
+  ) {
+    $this->definitionDiscovery = $definition_disovery;
+    $this->libraryTypeFactory = $library_type_factory;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLibrary($id) {
+    $definition = $this->definitionDiscovery->getDefinition($id);
+    return $this->getLibraryFromDefinition($id, $definition);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRequiredLibraryIds() {
+    $library_ids = [];
+    foreach (['module', 'theme'] as $type) {
+      foreach (system_get_info($type) as $info) {
+        if (isset($info['library_dependencies'])) {
+          $library_ids = array_merge($library_ids, $info['library_dependencies']);
+        }
+      }
+    }
+    return array_unique($library_ids);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function load($id) {
+    $definition = $this->definitionDiscovery->getDefinition($id);
+    $library_type = $this->getLibraryType($id, $definition);
+    // @todo Throw an exception instead of silently failing.
+    if ($library_type instanceof LibraryLoadingListenerInterface) {
+      $library_type->onLibraryLoad($this->getLibraryFromDefinition($id, $definition));
+    }
+  }
+
+  /**
+   * @param $id
+   * @param $definition
+   * @return mixed
+   */
+  protected function getLibraryFromDefinition($id, $definition) {
+    $library_type = $this->getLibraryType($id, $definition);
+
+    // @todo Make this alter-able.
+    $class = $library_type->getLibraryClass();
+
+    // @todo Make sure that the library class implements the correct interface.
+    $library = $class::create($id, $definition, $library_type);
+
+    if ($library_type instanceof LibraryCreationListenerInterface) {
+      $library_type->onLibraryCreate($library);
+      return $library;
+    }
+    return $library;
+  }
+
+  /**
+   * @param string $id
+   * @param array $definition
+   *
+   * @return \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface
+   */
+  protected function getLibraryType($id, $definition) {
+    // @todo Validate that the type is a string.
+    if (!isset($definition['type'])) {
+      throw new LibraryTypeNotFoundException($id);
+    }
+    return $this->libraryTypeFactory->createInstance($definition['type']);
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/LibraryManagerInterface.php b/web/modules/contrib/libraries/src/ExternalLibrary/LibraryManagerInterface.php
new file mode 100644 (file)
index 0000000..0c4383f
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary;
+
+/**
+ * Provides an interface for external library managers.
+ */
+interface LibraryManagerInterface {
+
+  /**
+   * Gets a library by its ID.
+   *
+   * @param string $id
+   *   The library ID.
+   *
+   * @return \Drupal\libraries\ExternalLibrary\LibraryInterface
+   *   The library object.
+   *
+   * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException
+   * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryTypeNotFoundException
+   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   */
+  public function getLibrary($id);
+
+  /**
+   * Gets the list of libraries that are required by enabled extensions.
+   *
+   * Modules, themes, and installation profiles can declare library dependencies
+   * by specifying a 'library_dependencies' key in their info files.
+   *
+   * @return string[]
+   *   An array of library IDs.
+   */
+  public function getRequiredLibraryIds();
+
+  /**
+   * Loads library files for a library.
+   *
+   * Note that not all library types support explicit loading. Asset libraries,
+   * in particular, are declared to Drupal core's library system and are then
+   * loaded using that.
+   *
+   * @param string $id
+   *   The ID of the library.
+   *
+   * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException
+   * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryNotInstalledException
+   */
+  public function load($id);
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Local/LocalLibraryInterface.php b/web/modules/contrib/libraries/src/ExternalLibrary/Local/LocalLibraryInterface.php
new file mode 100644 (file)
index 0000000..f8c1fbe
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Local;
+
+use Drupal\Component\Plugin\Factory\FactoryInterface;
+use Drupal\libraries\ExternalLibrary\LibraryInterface;
+
+/**
+ * Provides an interface for local libraries.
+ *
+ * Local libraries are libraries that can be found on the filesystem. If the
+ * library files can be found in the filesystem a library is considered
+ * installed and its library path can be retrieved.
+ *
+ * Because determining whether or not the library is available locally is not
+ * the responsibility of the library itself, but of a designated locator, this
+ * interface declares setter methods, as well.
+ *
+ * @see \Drupal\libraries\ExternalLibrary\Local\LocatorInterface
+ */
+interface LocalLibraryInterface extends LibraryInterface {
+
+  /**
+   * Checks whether the library is installed.
+   *
+   * @return bool
+   *   TRUE if the library is installed; FALSE otherwise;
+   */
+  public function isInstalled();
+
+  /**
+   * Marks the library as uninstalled.
+   *
+   * A corresponding method to mark the library as installed is not provided as
+   * an installed library should have a library path, so that
+   * LocalLibraryInterface::setLibraryPath() can be used instead.
+   *
+   * @return $this
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface::isInstalled()
+   * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface::setLocalPath()
+   */
+  public function setUninstalled();
+
+  /**
+   * Gets the local path to the library.
+   *
+   * @return string
+   *   The absolute path to the library on the filesystem.
+   *
+   * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryNotInstalledException
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface::setLocalPath()
+   */
+  public function getLocalPath();
+
+  /**
+   * Sets the local path of the library.
+   *
+   * @param string $path
+   *   The path to the library.
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface::getLocalPath()
+   */
+  public function setLocalPath($path);
+
+  /**
+   * Gets the locator of this library using the locator factory.
+   *
+   * Because determining the installation status and library path of a library
+   * is not specific to any library or even any library type, this logic is
+   * offloaded to separate locator objects.
+   *
+   * @param \Drupal\Component\Plugin\Factory\FactoryInterface $locator_factory
+   *
+   * @return \Drupal\libraries\ExternalLibrary\Local\LocatorInterface
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Local\LocatorInterface
+   */
+  public function getLocator(FactoryInterface $locator_factory);
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Local/LocalLibraryTrait.php b/web/modules/contrib/libraries/src/ExternalLibrary/Local/LocalLibraryTrait.php
new file mode 100644 (file)
index 0000000..3f08e4b
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Local;
+use Drupal\libraries\ExternalLibrary\Exception\LibraryNotInstalledException;
+
+/**
+ * Provides a trait for local libraries utilizing a stream wrapper.
+ *
+ * It assumes that the library files can be accessed using a specified stream
+ * wrapper and that the first component of the file URIs are the library IDs.
+ * Thus, file URIs are of the form:
+ * stream-wrapper-scheme://library-id/path/to/file/within/the/library/filename
+ *
+ * This trait should only be used by classes implementing LocalLibraryInterface.
+ *
+ * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface
+ */
+trait LocalLibraryTrait {
+
+  /**
+   * Whether or not the library is installed.
+   *
+   * A library being installed means its files can be found on the filesystem.
+   *
+   * @var bool
+   */
+  protected $installed = FALSE;
+
+  /**
+   * The local path to the library relative to the app root.
+   *
+   * @var string
+   */
+  protected $localPath;
+
+  /**
+   * Checks whether the library is installed.
+   *
+   * @return bool
+   *   TRUE if the library is installed; FALSE otherwise;
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface::isInstalled()
+   */
+  public function isInstalled() {
+    return $this->installed;
+  }
+
+  /**
+   * Marks the library as uninstalled.
+   *
+   * A corresponding method to mark the library as installed is not provided as
+   * an installed library should have a library path, so that
+   * LocalLibraryInterface::setLibraryPath() can be used instead.
+   *
+   * @return $this
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface::setUninstalled()
+   */
+  public function setUninstalled() {
+    $this->installed = FALSE;
+    return $this;
+  }
+
+  /**
+   * Gets the path to the library.
+   *
+   * @return string
+   *   The path to the library relative to the app root.
+   *
+   * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryNotInstalledException
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface::getLocalPath()
+   */
+  public function getLocalPath() {
+    if (!$this->isInstalled()) {
+      /** @var \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface $this */
+      throw new LibraryNotInstalledException($this);
+    }
+
+    return $this->localPath;
+  }
+
+  /**
+   * Sets the library path of the library.
+   *
+   * @param string $path
+   *   The path to the library.
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface::getLocalPath()
+   */
+  public function setLocalPath($path) {
+    $this->installed = TRUE;
+    $this->localPath = (string) $path;
+
+    assert('$this->localPath !== ""');
+    assert('$this->localPath[0] !== "/"');
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Local/LocatorInterface.php b/web/modules/contrib/libraries/src/ExternalLibrary/Local/LocatorInterface.php
new file mode 100644 (file)
index 0000000..a1be894
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Local;
+
+/**
+ * Provides an interface for library locators.
+ *
+ * Because determining the installation status and library path of a library
+ * is not specific to any library or even any library type, this logic can be
+ * implemented generically in form of a locator.
+ */
+interface LocatorInterface {
+
+  /**
+   * Locates a library.
+   *
+   * @param \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface $library
+   *   The library to locate.
+   */
+  public function locate(LocalLibraryInterface $library);
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Local/LocatorManager.php b/web/modules/contrib/libraries/src/ExternalLibrary/Local/LocatorManager.php
new file mode 100644 (file)
index 0000000..2f377ff
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Local;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\libraries\Annotation\Locator;
+
+/**
+ * Provides a plugin manager for library locator plugins.
+ *
+ * @see \Drupal\libraries\ExternalLibrary\Local\LocatorInterface
+ */
+class LocatorManager extends DefaultPluginManager {
+
+  /**
+   * Constructs a locator manager.
+   *
+   * @param \Traversable $namespaces
+   *   An object that implements \Traversable which contains the root paths
+   *   keyed by the corresponding namespace to look for plugin implementations.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   Cache backend instance to use.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler to invoke the alter hook with.
+   */
+  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
+    parent::__construct('Plugin/libraries/Locator', $namespaces, $module_handler, LocatorInterface::class, Locator::class);
+    $this->alterInfo('libraries_locator_info');
+    $this->setCacheBackend($cache_backend, 'libraries_locator_info');
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/PhpFile/PhpFileLibrary.php b/web/modules/contrib/libraries/src/ExternalLibrary/PhpFile/PhpFileLibrary.php
new file mode 100644 (file)
index 0000000..bf3c683
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\PhpFile;
+
+use Drupal\Component\Plugin\Factory\FactoryInterface;
+use Drupal\libraries\ExternalLibrary\Exception\LibraryNotInstalledException;
+use Drupal\libraries\ExternalLibrary\LibraryBase;
+use Drupal\libraries\ExternalLibrary\Local\LocalLibraryTrait;
+use Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface;
+
+/**
+ * Provides a base PHP file library implementation.
+ */
+class PhpFileLibrary extends LibraryBase implements PhpFileLibraryInterface {
+
+  use LocalLibraryTrait;
+
+  /**
+   * An array of PHP files for this library.
+   *
+   * @var array
+   */
+  protected $files = [];
+
+  /**
+   * Constructs a PHP file library.
+   *
+   * @param string $id
+   *   The library ID.
+   * @param array $definition
+   *   The library definition array.
+   * @param \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface $type
+   *   The library type of this library.
+   */
+  public function __construct($id, array $definition, LibraryTypeInterface $type) {
+    parent::__construct($id, $definition, $type);
+    $this->files = $definition['files'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static function processDefinition(array &$definition) {
+    parent::processDefinition($definition);
+    $definition += [
+      'files' => [],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPhpFiles() {
+    if (!$this->isInstalled()) {
+      throw new LibraryNotInstalledException($this);
+    }
+
+    $processed_files = [];
+    foreach ($this->files as $file) {
+      $processed_files[] = $this->getLocalPath() . '/' . $file;
+    }
+    return $processed_files;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLocator(FactoryInterface $locator_factory) {
+    // @todo Consider refining the stream wrapper used here.
+    return $locator_factory->createInstance('uri', ['uri' => 'php-file://']);
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/PhpFile/PhpFileLibraryInterface.php b/web/modules/contrib/libraries/src/ExternalLibrary/PhpFile/PhpFileLibraryInterface.php
new file mode 100644 (file)
index 0000000..01ca453
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\PhpFile;
+
+use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface;
+
+/**
+ * Provides an interface for libraries with PHP files.
+ *
+ * @see \Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLoaderInterface
+ */
+interface PhpFileLibraryInterface extends LocalLibraryInterface {
+
+  /**
+   * Returns the PHP files of this library.
+   *
+   * @return string[]
+   *   An array of absolute file paths of PHP files.
+   */
+  public function getPhpFiles();
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/PhpFile/PhpFileLoaderInterface.php b/web/modules/contrib/libraries/src/ExternalLibrary/PhpFile/PhpFileLoaderInterface.php
new file mode 100644 (file)
index 0000000..52c1c62
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\PhpFile;
+
+/**
+ * Provides an interface for PHP file loaders.
+ *
+ * @see \Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLibraryInterface
+ */
+interface PhpFileLoaderInterface {
+
+  /**
+   * Loads a PHP file.
+   *
+   * @param string $file
+   *   The absolute file path to the PHP file.
+   */
+  public function load($file);
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/PhpFile/PhpRequireLoader.php b/web/modules/contrib/libraries/src/ExternalLibrary/PhpFile/PhpRequireLoader.php
new file mode 100644 (file)
index 0000000..ef6b695
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\PhpFile;
+
+/**
+ * Provides a PHP file loader using PHP's require_once.
+ *
+ * @todo Provide a separate PhpIncludeOnceLoader.
+ */
+class PhpRequireLoader implements PhpFileLoaderInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function load($file) {
+    // @todo Because libraries cannot be loaded twice it should be possible to
+    //   use 'require' instead of 'require_once'.
+    /** @noinspection PhpIncludeInspection */
+    require_once $file;
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Remote/RemoteLibraryInterface.php b/web/modules/contrib/libraries/src/ExternalLibrary/Remote/RemoteLibraryInterface.php
new file mode 100644 (file)
index 0000000..1fe7016
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Remote;
+
+use Drupal\libraries\ExternalLibrary\LibraryInterface;
+
+/**
+ * Provides an interface for remote libraries.
+ *
+ * Assuming they declare a remote URL, remote libraries are always loaded. It is
+ * not checked whether or not the Drupal site has network access or the remote
+ * resource is available.
+ */
+interface RemoteLibraryInterface extends LibraryInterface {
+
+  /**
+   * Checks whether the library has a remote URL.
+   *
+   * This check allows using the same library class for multiple libraries only
+   * some of which are available remotely.
+   *
+   * @return bool
+   *   TRUE if the library has a remote URL; FALSE otherwise.
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Asset\AssetLibraryInterface
+   */
+  public function hasRemoteUrl();
+
+  /**
+   * Returns the remote URL of the library.
+   *
+   * @return string
+   *   The remote URL of the library.
+   *
+   * @todo Consider throwing an exception if hasRemoteUrl() return FALSE.
+   */
+  public function getRemoteUrl();
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Remote/RemoteLibraryTrait.php b/web/modules/contrib/libraries/src/ExternalLibrary/Remote/RemoteLibraryTrait.php
new file mode 100644 (file)
index 0000000..d55f3c2
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Remote;
+
+/**
+ * Provides a trait for remote libraries.
+ */
+trait RemoteLibraryTrait {
+
+  /**
+   * The remote library URL.
+   *
+   * @var string
+   */
+  protected $remoteUrl;
+
+  /**
+   * Checks whether the library has a remote URL.
+   *
+   * This check allows using the same library class for multiple libraries only
+   * some of which are available remotely.
+   *
+   * @return bool
+   *   TRUE if the library has a remote URL; FALSE otherwise.
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Remote\RemoteLibraryInterface::hasRemoteUrl()
+   */
+  public function hasRemoteUrl() {
+    return !empty($this->remoteUrl);
+  }
+
+  /**
+   * Returns the remote URL of the library.
+   *
+   * @return string
+   *   The remote URL of the library.
+   *
+   * \Drupal\libraries\ExternalLibrary\Remote\RemoteLibraryInterface::getRemoteUrl()
+   */
+  public function getRemoteUrl() {
+    return $this->remoteUrl;
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Type/LibraryCreationListenerInterface.php b/web/modules/contrib/libraries/src/ExternalLibrary/Type/LibraryCreationListenerInterface.php
new file mode 100644 (file)
index 0000000..e86115f
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Type;
+
+use Drupal\libraries\ExternalLibrary\LibraryInterface;
+
+/**
+ * An interface for library types that want to react to library instantiation.
+ */
+interface LibraryCreationListenerInterface {
+
+  /**
+   * Reacts to the instantiation of a library.
+   *
+   * @param \Drupal\libraries\ExternalLibrary\LibraryInterface $library
+   *   The library that is being instantiated.
+   */
+  public function onLibraryCreate(LibraryInterface $library);
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Type/LibraryLoadingListenerInterface.php b/web/modules/contrib/libraries/src/ExternalLibrary/Type/LibraryLoadingListenerInterface.php
new file mode 100644 (file)
index 0000000..962e9db
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Type;
+
+use Drupal\libraries\ExternalLibrary\LibraryInterface;
+
+/**
+ * An interface for library types that want to react to library instantiation.
+ */
+interface LibraryLoadingListenerInterface {
+
+  /**
+   * Reacts to the instantiation of a library.
+   *
+   * @param \Drupal\libraries\ExternalLibrary\LibraryInterface $library
+   *   The library that is being instantiated.
+   */
+  public function onLibraryLoad(LibraryInterface $library);
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Type/LibraryTypeBase.php b/web/modules/contrib/libraries/src/ExternalLibrary/Type/LibraryTypeBase.php
new file mode 100644 (file)
index 0000000..6cd9f59
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Type;
+
+use Drupal\Component\Plugin\Factory\FactoryInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\libraries\ExternalLibrary\LibraryInterface;
+use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface;
+use Drupal\libraries\ExternalLibrary\Utility\IdAccessorTrait;
+use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a base class for library types.
+ */
+abstract class LibraryTypeBase implements
+  LibraryTypeInterface,
+  LibraryCreationListenerInterface,
+  ContainerFactoryPluginInterface
+{
+
+  use IdAccessorTrait;
+
+  /**
+   * The locator factory.
+   *
+   * @var \Drupal\Component\Plugin\Factory\FactoryInterface
+   */
+  protected $locatorFactory;
+
+  /**
+   * The version detector factory.
+   *
+   * @var \Drupal\Component\Plugin\Factory\FactoryInterface
+   */
+  protected $detectorFactory;
+
+  /**
+   * Constructs the asset library type.
+   *
+   * @param string $plugin_id
+   *   The plugin ID taken from the class annotation.
+   * @param \Drupal\Component\Plugin\Factory\FactoryInterface $locator_factory
+   *   The locator factory.
+   * @param \Drupal\Component\Plugin\Factory\FactoryInterface $detector_factory
+   *   The version detector factory.
+   */
+  public function __construct($plugin_id, FactoryInterface $locator_factory, FactoryInterface $detector_factory) {
+    $this->id = $plugin_id;
+    $this->locatorFactory = $locator_factory;
+    $this->detectorFactory = $detector_factory;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $plugin_id,
+      $container->get('plugin.manager.libraries.locator'),
+      $container->get('plugin.manager.libraries.version_detector')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onLibraryCreate(LibraryInterface $library) {
+    if ($library instanceof LocalLibraryInterface) {
+      $library->getLocator($this->locatorFactory)->locate($library);
+      // Fallback on global locators.
+      // @todo Consider if global locators should be checked as a fallback or as
+      // the primary locator source.
+      if (!$library->isInstalled()) {
+        $this->locatorFactory->createInstance('global')->locate($library);
+      }
+      // Also fetch version information.
+      if ($library instanceof VersionedLibraryInterface) {
+        // @todo Consider if this should be wrapped in some conditional logic
+        // or exception handling so that version detection errors do not
+        // prevent a library from being loaded.
+        $library->getVersionDetector($this->detectorFactory)->detectVersion($library);
+      }
+    }
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Type/LibraryTypeFactory.php b/web/modules/contrib/libraries/src/ExternalLibrary/Type/LibraryTypeFactory.php
new file mode 100644 (file)
index 0000000..6689f51
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Type;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\libraries\Annotation\LibraryType;
+
+/**
+ * Provides a plugin manager for library type plugins.
+ */
+class LibraryTypeFactory extends DefaultPluginManager {
+
+  /**
+   * Constructs a locator manager.
+   *
+   * @param \Traversable $namespaces
+   *   An object that implements \Traversable which contains the root paths
+   *   keyed by the corresponding namespace to look for plugin implementations.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   Cache backend instance to use.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler to invoke the alter hook with.
+   */
+  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
+    parent::__construct('Plugin/libraries/Type', $namespaces, $module_handler, LibraryTypeInterface::class, LibraryType::class);
+    $this->alterInfo('libraries_library_type_info');
+    $this->setCacheBackend($cache_backend, 'libraries_library_type_info');
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Type/LibraryTypeInterface.php b/web/modules/contrib/libraries/src/ExternalLibrary/Type/LibraryTypeInterface.php
new file mode 100644 (file)
index 0000000..d6537fb
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Type;
+
+/**
+ * Provides an interface for library types.
+ */
+interface LibraryTypeInterface {
+
+  /**
+   * Returns the ID of the library type.
+   *
+   * @return string
+   *   The library type ID.
+   */
+  public function getId();
+
+  /**
+   * Returns the class used for libraries of this type.
+   *
+   * @return string|\Drupal\libraries\ExternalLibrary\LibraryInterface
+   *   The library class for this library type.
+   *
+   * @todo Consider adding a getLibraryInterface() method, as well.
+   */
+  public function getLibraryClass();
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Utility/DependencyAccessorTrait.php b/web/modules/contrib/libraries/src/ExternalLibrary/Utility/DependencyAccessorTrait.php
new file mode 100644 (file)
index 0000000..3ec56fe
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Utility;
+
+/**
+ * Provides a trait for classes giving access to a library dependency.
+ */
+trait DependencyAccessorTrait {
+
+  /**
+   * The dependency.
+   *
+   * @var \Drupal\libraries\ExternalLibrary\LibraryInterface
+   */
+  protected $dependency;
+
+  /**
+   * Returns the dependency.
+   *
+   * @return \Drupal\libraries\ExternalLibrary\LibraryInterface
+   *   The library.
+   */
+  public function getLibrary() {
+    return $this->dependency;
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Utility/IdAccessorTrait.php b/web/modules/contrib/libraries/src/ExternalLibrary/Utility/IdAccessorTrait.php
new file mode 100644 (file)
index 0000000..34476f2
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Utility;
+
+/**
+ * Provides a trait for classes that have a string identifier.
+ */
+trait IdAccessorTrait {
+
+  /**
+   * The ID.
+   *
+   * @var string
+   */
+  protected $id;
+
+  /**
+   * Returns the ID.
+   *
+   * @return string
+   *   The ID.
+   *
+   * @see \Drupal\libraries\ExternalLibrary\LibraryInterface::getId()
+   * @see \Drupal\libraries\ExternalLibrary\LibraryType\LibraryTypeInterface::getId()
+   */
+  public function getId() {
+    return $this->id;
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Utility/LibraryAccessorInterface.php b/web/modules/contrib/libraries/src/ExternalLibrary/Utility/LibraryAccessorInterface.php
new file mode 100644 (file)
index 0000000..72b0093
--- /dev/null
@@ -0,0 +1,18 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Utility;
+
+/**
+ * Provides an interface for classes giving access to a library.
+ */
+interface LibraryAccessorInterface {
+
+  /**
+   * Returns the library.
+   *
+   * @return \Drupal\libraries\ExternalLibrary\LibraryInterface
+   *   The library.
+   */
+  public function getLibrary();
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Utility/LibraryAccessorTrait.php b/web/modules/contrib/libraries/src/ExternalLibrary/Utility/LibraryAccessorTrait.php
new file mode 100644 (file)
index 0000000..f8ad920
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Utility;
+
+/**
+ * Provides a trait for classes giving access to a library.
+ */
+trait LibraryAccessorTrait {
+
+  /**
+   * The library.
+   *
+   * @var \Drupal\libraries\ExternalLibrary\LibraryInterface
+   */
+  protected $library;
+
+  /**
+   * Returns the library.
+   *
+   * @return \Drupal\libraries\ExternalLibrary\LibraryInterface
+   *   The library.
+   */
+  public function getLibrary() {
+    return $this->library;
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Utility/LibraryIdAccessorInterface.php b/web/modules/contrib/libraries/src/ExternalLibrary/Utility/LibraryIdAccessorInterface.php
new file mode 100644 (file)
index 0000000..98928e9
--- /dev/null
@@ -0,0 +1,18 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Utility;
+
+/**
+ * Provides an interface for classes giving access to a library ID.
+ */
+interface LibraryAccessorIdInterface {
+
+  /**
+   * Returns the ID of the library.
+   *
+   * @return string
+   *   The library ID.
+   */
+  public function getLibraryId();
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Utility/LibraryIdAccessorTrait.php b/web/modules/contrib/libraries/src/ExternalLibrary/Utility/LibraryIdAccessorTrait.php
new file mode 100644 (file)
index 0000000..8ec901f
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Utility;
+
+/**
+ * Provides a trait for classes giving access to a library ID.
+ */
+trait LibraryIdAccessorTrait {
+
+  /**
+   * The ID of the library.
+   *
+   * @var string
+   */
+  protected $libraryId;
+
+  /**
+   * Returns the ID of the library.
+   *
+   * @return string
+   *   The library ID.
+   */
+  public function getLibraryId() {
+    return $this->libraryId;
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Version/VersionDetectorInterface.php b/web/modules/contrib/libraries/src/ExternalLibrary/Version/VersionDetectorInterface.php
new file mode 100644 (file)
index 0000000..8b6d2a5
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Version;
+
+/**
+ * Provides an interface for version detectors.
+ *
+ * @ingroup libraries
+ */
+interface VersionDetectorInterface {
+
+  /**
+   * Detects the version of a library.
+   *
+   * @param \Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface $library
+   *   The library whose version to detect.
+   *
+   * @throws \Drupal\libraries\ExternalLibrary\Exception\UnknownLibraryVersionException
+   *
+   * @todo Provide a mechanism for version detectors to provide a reason for
+   *   failing.
+   */
+  public function detectVersion(VersionedLibraryInterface $library);
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Version/VersionDetectorManager.php b/web/modules/contrib/libraries/src/ExternalLibrary/Version/VersionDetectorManager.php
new file mode 100644 (file)
index 0000000..6272c5a
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Version;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\libraries\Annotation\VersionDetector;
+
+/**
+ * Provides a plugin manager for library version detector plugins.
+ *
+ * @see \Drupal\libraries\ExternalLibrary\Version\VersionDetectorInterface
+ */
+class VersionDetectorManager extends DefaultPluginManager {
+
+  /**
+   * Constructs a version detector manager.
+   *
+   * @param \Traversable $namespaces
+   *   An object that implements \Traversable which contains the root paths
+   *   keyed by the corresponding namespace to look for plugin implementations.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   Cache backend instance to use.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler to invoke the alter hook with.
+   */
+  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
+    parent::__construct('Plugin/libraries/VersionDetector', $namespaces, $module_handler, VersionDetectorInterface::class, VersionDetector::class);
+    $this->alterInfo('libraries_version_detector_info');
+    $this->setCacheBackend($cache_backend, 'libraries_version_detector_info');
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Version/VersionedLibraryInterface.php b/web/modules/contrib/libraries/src/ExternalLibrary/Version/VersionedLibraryInterface.php
new file mode 100644 (file)
index 0000000..abeb758
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Version;
+
+use Drupal\Component\Plugin\Factory\FactoryInterface;
+use Drupal\libraries\ExternalLibrary\LibraryInterface;
+
+/**
+ * Provides an interface for versioned libraries.
+ *
+ * Version detection and negotiation is a key aspect of Libraries API's
+ * functionality so most libraries should implement this interface. In theory,
+ * however, it might be possible for the same library to be available in
+ * multiple versions and, for example, different versions being loaded on
+ * different pages. In this case, a simple getVersion() method, does not make
+ * sense. To support such advanced version detection behavior in the future or
+ * in a separate module, version detection is split into a separate interface.
+ *
+ * @ingroup libraries
+ *
+ * @todo Support versioned metadata, i.e. different library file names or
+ *   locations for different library versions.
+ */
+interface VersionedLibraryInterface extends LibraryInterface {
+
+  /**
+   * Gets the version of the library.
+   *
+   * @return string
+   *   The version string, for example 1.0, 2.1.4, or 3.0.0-alpha5.
+   *
+   * @throws \Drupal\libraries\ExternalLibrary\Exception\UnknownLibraryVersionException
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface::setVersion()
+   */
+  public function getVersion();
+
+  /**
+   * Sets the version of the library.
+   *
+   * @param string $version
+   *   The version of the library.
+   *
+   * @reutrn $this
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface::getVersion()
+   */
+  public function setVersion($version);
+
+  /**
+   * Gets the version detector of this library using the detector factory.
+   *
+   * Because determining the installation version of a library is not specific
+   * to any library or even any library type, this logic is offloaded to
+   * separate detector objects.
+   *
+   * @param \Drupal\Component\Plugin\Factory\FactoryInterface $detector_factory
+   *
+   * @return \Drupal\libraries\ExternalLibrary\Version\VersionDetectorInterface
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Version\VersionDetectorInterface
+   */
+  public function getVersionDetector(FactoryInterface $detector_factory);
+
+}
diff --git a/web/modules/contrib/libraries/src/ExternalLibrary/Version/VersionedLibraryTrait.php b/web/modules/contrib/libraries/src/ExternalLibrary/Version/VersionedLibraryTrait.php
new file mode 100644 (file)
index 0000000..2404a10
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+
+namespace Drupal\libraries\ExternalLibrary\Version;
+
+use Drupal\Component\Plugin\Factory\FactoryInterface;
+use Drupal\libraries\ExternalLibrary\Exception\UnknownLibraryVersionException;
+
+/**
+ * Provides a trait for versioned libraries.
+ *
+ * @see \Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface
+ */
+trait VersionedLibraryTrait {
+
+  /**
+   * The library version.
+   *
+   * @var string
+   */
+  protected $version;
+
+  /**
+   * Information about the version detector to use fo rthis library.
+   *
+   * Contains the following keys:
+   * id: The plugin ID of the version detector.
+   * configuration: The plugin configuration of the version detector.
+   *
+   * @var array
+   */
+  protected $versionDetector = [
+    'id' => NULL,
+    'configuration' => [],
+  ];
+
+  /**
+   * Gets the version of the library.
+   *
+   * @return string
+   *   The version string, for example 1.0, 2.1.4, or 3.0.0-alpha5.
+   *
+   * @throws \Drupal\libraries\ExternalLibrary\Exception\UnknownLibraryVersionException
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface::getVersion()
+   */
+  public function getVersion() {
+    if (!isset($this->version)) {
+      throw new UnknownLibraryVersionException($this);
+    }
+    return $this->version;
+  }
+
+  /**
+   * Sets the version of the library.
+   *
+   * @param string $version
+   *   The version of the library.
+   *
+   * @return $this
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface::setVersion()
+   */
+  public function setVersion($version) {
+    $this->version = (string) $version;
+    return $this;
+  }
+
+  /**
+   * Gets the version detector of this library using the detector factory.
+   *
+   * Because determining the installation version of a library is not specific
+   * to any library or even any library type, this logic is offloaded to
+   * separate detector objects.
+   *
+   * @param \Drupal\Component\Plugin\Factory\FactoryInterface $detector_factory
+   *
+   * @return \Drupal\libraries\ExternalLibrary\Version\VersionDetectorInterface
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Version\VersionDetectorInterface
+   */
+  public function getVersionDetector(FactoryInterface $detector_factory) {
+    return $detector_factory->createInstance($this->versionDetector['id'], $this->versionDetector['configuration']);
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/Plugin/MissingPluginConfigurationException.php b/web/modules/contrib/libraries/src/Plugin/MissingPluginConfigurationException.php
new file mode 100644 (file)
index 0000000..515f133
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+
+namespace Drupal\libraries\Plugin;
+
+use Drupal\Component\Plugin\Exception\PluginException;
+
+/**
+ * Provides an exception class for missing plugin configuration.
+ *
+ * The plugin system allows passing arbitrary data to plugins in form of the
+ * $configuration array. Some plugins, however, may depend on certain keys to
+ * be present in $configuration. This exception class can be used if such keys
+ * are missing.
+ *
+ * @todo Provide accessors for the passed-in information.
+ */
+class MissingPluginConfigurationException extends PluginException {
+
+  /**
+   * Constructs an exception for a missing plugin configuration value.
+   *
+   * @param string $plugin_id
+   *   The plugin ID.
+   * @param $plugin_definition
+   *   The plugin definition
+   * @param array $configuration
+   *   The plugin configuration.
+   * @param $missing_key
+   *   The missing key in the configuration.
+   * @param string $message
+   *   (optional) The exception message.
+   * @param int $code
+   *   (optional) The error code.
+   * @param \Exception $previous
+   *   (optional) The previous exception.
+   */
+  public function __construct(
+    $plugin_id,
+    $plugin_definition,
+    array $configuration,
+    $missing_key,
+    $message = '',
+    $code = 0,
+    \Exception $previous = NULL
+  ) {
+    $message = $message ?: "The '{$missing_key}' key is missing in the configuration of the '{$plugin_id}' plugin.";
+    parent::__construct($message, $code, $previous);
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/Plugin/libraries/Locator/ChainLocator.php b/web/modules/contrib/libraries/src/Plugin/libraries/Locator/ChainLocator.php
new file mode 100644 (file)
index 0000000..4e0acff
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+
+namespace Drupal\libraries\Plugin\libraries\Locator;
+
+use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface;
+use Drupal\libraries\ExternalLibrary\Local\LocatorInterface;
+
+/**
+ * Provides a locator utilizing a chain of other individual locators.
+ *
+ * @Locator("chain")
+ *
+ * @see \Drupal\libraries\ExternalLibrary\Local\LocatorInterface
+ */
+class ChainLocator implements LocatorInterface {
+
+  /**
+   * The locators to check.
+   *
+   * @var \Drupal\libraries\ExternalLibrary\Local\LocatorInterface[]
+   */
+  protected $locators = [];
+
+  /**
+   * Add a locator to the chain.
+   *
+   * @param \Drupal\libraries\ExternalLibrary\Local\LocatorInterface $locator
+   *   A locator to add to the chain.
+   */
+  public function addLocator(LocatorInterface $locator) {
+    $this->locators[] = $locator;
+    return $this;
+  }
+
+  /**
+   * Locates a library.
+   *
+   * @param \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface $library
+   *   The library to locate.
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Local\LocatorInterface::locate()
+   */
+  public function locate(LocalLibraryInterface $library) {
+    foreach ($this->locators as $locator) {
+      $locator->locate($library);
+      if ($library->isInstalled()) {
+        return;
+      }
+    }
+    $library->setUninstalled();
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/Plugin/libraries/Locator/GlobalLocator.php b/web/modules/contrib/libraries/src/Plugin/libraries/Locator/GlobalLocator.php
new file mode 100644 (file)
index 0000000..3501338
--- /dev/null
@@ -0,0 +1,76 @@
+<?php
+
+namespace Drupal\libraries\Plugin\libraries\Locator;
+
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Component\Plugin\Factory\FactoryInterface;
+use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface;
+use Drupal\libraries\ExternalLibrary\Local\LocatorInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a locator based on global configuration.
+ *
+ * @Locator("global")
+ *
+ * @see \Drupal\libraries\ExternalLibrary\Local\LocatorInterface
+ */
+class GlobalLocator implements LocatorInterface, ContainerFactoryPluginInterface {
+
+  /**
+   * The Drupal config factory service.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * The locator factory.
+   *
+   * @var \Drupal\Component\Plugin\Factory\FactoryInterface
+   */
+  protected $locatorFactory;
+
+  /**
+   * Constructs a global locator.
+   *
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The Drupal config factory service.
+   * @param \Drupal\Component\Plugin\Factory\FactoryInterface $locator_factory
+   *   The locator factory.
+   */
+  public function __construct(ConfigFactoryInterface $config_factory, FactoryInterface $locator_factory) {
+    $this->configFactory = $config_factory;
+    $this->locatorFactory = $locator_factory;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $container->get('config.factory'),
+      $container->get('plugin.manager.libraries.locator')
+    );
+  }
+
+  /**
+   * Locates a library.
+   *
+   * @param \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface $library
+   *   The library to locate.
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Local\LocatorInterface::locate()
+   */
+  public function locate(LocalLibraryInterface $library) {
+    foreach ($this->configFactory->get('libraries.settings')->get('global_locators') as $locator) {
+      $this->locatorFactory->createInstance($locator['id'], $locator['configuration'])->locate($library);
+      if ($library->isInstalled()) {
+        return;
+      }
+    }
+    $library->setUninstalled();
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/Plugin/libraries/Locator/UriLocator.php b/web/modules/contrib/libraries/src/Plugin/libraries/Locator/UriLocator.php
new file mode 100644 (file)
index 0000000..348be0c
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+
+namespace Drupal\libraries\Plugin\libraries\Locator;
+
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
+use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface;
+use Drupal\libraries\ExternalLibrary\Local\LocatorInterface;
+use Drupal\libraries\Plugin\MissingPluginConfigurationException;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a locator utilizing a URI.
+ *
+ * It makes the following assumptions:
+ * - The library files can be accessed using a specified stream.
+ * - The stream wrapper is local (i.e. it is a subclass of
+ *   \Drupal\Core\StreamWrapper\LocalStream).
+ * - The first component of the file URIs are the library IDs (i.e. file URIs
+ *   are of the form: scheme://library-id/path/to/file/filename).
+ *
+ * @Locator("uri")
+ *
+ * @see \Drupal\libraries\ExternalLibrary\Local\LocatorInterface
+ */
+class UriLocator implements LocatorInterface, ContainerFactoryPluginInterface {
+
+  /**
+   * The stream wrapper manager.
+   *
+   * @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface
+   */
+  protected $streamWrapperManager;
+
+  /**
+   * The URI to check.
+   *
+   * @var string
+   */
+  protected $uri;
+
+  /**
+   * Constructs a URI locator.
+   *
+   * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager
+   *   The stream wrapper manager.
+   * @param string $uri
+   *   The URI to check.
+   */
+  public function __construct(StreamWrapperManagerInterface $stream_wrapper_manager, $uri) {
+    $this->streamWrapperManager = $stream_wrapper_manager;
+    $this->uri = (string) $uri;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    if (!isset($configuration['uri'])) {
+      throw new MissingPluginConfigurationException($plugin_id, $plugin_definition, $configuration, 'uri');
+    }
+    return new static($container->get('stream_wrapper_manager'), $configuration['uri']);
+  }
+
+  /**
+   * Locates a library.
+   *
+   * @param \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface $library
+   *   The library to locate.
+   *
+   * @see \Drupal\libraries\ExternalLibrary\Local\LocatorInterface::locate()
+   */
+  public function locate(LocalLibraryInterface $library) {
+    /** @var \Drupal\Core\StreamWrapper\LocalStream $stream_wrapper */
+    $stream_wrapper = $this->streamWrapperManager->getViaUri($this->uri);
+    assert('$stream_wrapper instanceof \Drupal\Core\StreamWrapper\LocalStream');
+    // Calling LocalStream::getDirectoryPath() explicitly avoids the realpath()
+    // usage in LocalStream::getLocalPath(), which breaks if Libraries API is
+    // symbolically linked into the Drupal installation.
+    list($scheme, $target) = explode('://', $this->uri, 2);
+    $base_path = str_replace('//', '/', $stream_wrapper->getDirectoryPath() . '/' . $target . '/' . $library->getId());
+    if (is_dir($base_path) && is_readable($base_path)) {
+      $library->setLocalPath($base_path);
+      return;
+    }
+    $library->setUninstalled();
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/Plugin/libraries/Type/AssetLibraryType.php b/web/modules/contrib/libraries/src/Plugin/libraries/Type/AssetLibraryType.php
new file mode 100644 (file)
index 0000000..ce0b276
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+
+namespace Drupal\libraries\Plugin\libraries\Type;
+
+use Drupal\libraries\ExternalLibrary\Asset\AssetLibrary;
+use Drupal\libraries\ExternalLibrary\Asset\AttachableAssetLibraryRegistrationInterface;
+use Drupal\libraries\ExternalLibrary\LibraryInterface;
+use Drupal\libraries\ExternalLibrary\LibraryManagerInterface;
+use Drupal\libraries\ExternalLibrary\Type\LibraryTypeBase;
+
+/**
+ * @LibraryType("asset")
+ */
+class AssetLibraryType extends LibraryTypeBase implements AttachableAssetLibraryRegistrationInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLibraryClass() {
+    return AssetLibrary::class;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAttachableAssetLibraries(LibraryInterface $library, LibraryManagerInterface $library_manager) {
+    assert('$library instanceof \Drupal\libraries\ExternalLibrary\Asset\AssetLibraryInterface');
+    /** @var \Drupal\libraries\ExternalLibrary\Asset\AssetLibraryInterface $library */
+    return [$library->getId() => $library->getAttachableAssetLibrary($library_manager)];
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/Plugin/libraries/Type/MultipleAssetLibraryType.php b/web/modules/contrib/libraries/src/Plugin/libraries/Type/MultipleAssetLibraryType.php
new file mode 100644 (file)
index 0000000..31ec6a6
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+
+namespace Drupal\libraries\Plugin\libraries\Type;
+
+use Drupal\libraries\ExternalLibrary\Asset\AttachableAssetLibraryRegistrationInterface;
+use Drupal\libraries\ExternalLibrary\Asset\MultipleAssetLibrary;
+use Drupal\libraries\ExternalLibrary\Asset\MultipleAssetLibraryInterface;
+use Drupal\libraries\ExternalLibrary\LibraryInterface;
+use Drupal\libraries\ExternalLibrary\LibraryManagerInterface;
+use Drupal\libraries\ExternalLibrary\Type\LibraryTypeBase;
+
+/**
+ * @LibraryType("asset_multiple")
+ */
+class MultipleAssetLibraryType extends LibraryTypeBase implements AttachableAssetLibraryRegistrationInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLibraryClass() {
+    return MultipleAssetLibrary::class;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAttachableAssetLibraries(LibraryInterface $external_library, LibraryManagerInterface $library_manager) {
+    assert('$external_library instanceof \Drupal\libraries\ExternalLibrary\Asset\MultipleAssetLibraryInterface');
+    /** @var \Drupal\libraries\ExternalLibrary\Asset\MultipleAssetLibraryInterface $external_library */
+    $attachable_libraries = [];
+    foreach ($external_library->getAttachableAssetLibraries($library_manager) as $component_name => $attachable_library) {
+      $attachable_library_id = $this->getAttachableLibraryId($external_library, $component_name);
+      $attachable_libraries[$attachable_library_id] = $attachable_library;
+    }
+    return $attachable_libraries;
+  }
+
+  /**
+   * @param \Drupal\libraries\ExternalLibrary\LibraryInterface $external_library
+   * @param string $component_name
+   *
+   * @return string
+   */
+  protected function getAttachableLibraryId(LibraryInterface $external_library, $component_name) {
+    return $external_library->getId() . MultipleAssetLibraryInterface::SEPARATOR . $component_name;
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/Plugin/libraries/Type/PhpFileLibraryType.php b/web/modules/contrib/libraries/src/Plugin/libraries/Type/PhpFileLibraryType.php
new file mode 100644 (file)
index 0000000..aa93b00
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+
+namespace Drupal\libraries\Plugin\libraries\Type;
+
+use Drupal\Component\Plugin\Factory\FactoryInterface;
+use Drupal\libraries\ExternalLibrary\LibraryInterface;
+use Drupal\libraries\ExternalLibrary\Type\LibraryLoadingListenerInterface;
+use Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLibrary;
+use Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLoaderInterface;
+use Drupal\libraries\ExternalLibrary\Type\LibraryTypeBase;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * @LibraryType("php_file")
+ */
+class PhpFileLibraryType extends LibraryTypeBase implements LibraryLoadingListenerInterface {
+
+  /**
+   * The PHP file loader.
+   *
+   * @var \Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLoaderInterface
+   */
+  protected $phpFileLoader;
+
+  /**
+   * Constructs the PHP file library type.
+   *
+   * @param string $plugin_id
+   *   The plugin ID taken from the class annotation.
+   * @param \Drupal\Component\Plugin\Factory\FactoryInterface $locator_factory
+   *   The locator factory.
+   * @param \Drupal\Component\Plugin\Factory\FactoryInterface $detector_factory
+   *   The version detector factory.
+   * @param \Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLoaderInterface $php_file_loader
+   *   The PHP file loader.
+   */
+  public function __construct($plugin_id, FactoryInterface $locator_factory, FactoryInterface $detector_factory, PhpFileLoaderInterface $php_file_loader) {
+    parent::__construct($plugin_id, $locator_factory, $detector_factory);
+    $this->phpFileLoader = $php_file_loader;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $plugin_id,
+      $container->get('plugin.manager.libraries.locator'),
+      $container->get('plugin.manager.libraries.version_detector'),
+      $container->get('libraries.php_file_loader')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLibraryClass() {
+    return PhpFileLibrary::class;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onLibraryLoad(LibraryInterface $library) {
+    /** @var \Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLibraryInterface $library */
+    // @todo Prevent loading a library multiple times.
+    foreach ($library->getPhpFiles() as $file) {
+      $this->phpFileLoader->load($file);
+    }
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/Plugin/libraries/VersionDetector/LinePatternDetector.php b/web/modules/contrib/libraries/src/Plugin/libraries/VersionDetector/LinePatternDetector.php
new file mode 100644 (file)
index 0000000..25a799c
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+
+namespace Drupal\libraries\Plugin\libraries\VersionDetector;
+
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Plugin\PluginBase;
+use Drupal\libraries\ExternalLibrary\Exception\UnknownLibraryVersionException;
+use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface;
+use Drupal\libraries\ExternalLibrary\Version\VersionDetectorInterface;
+use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Detects the version by matching lines in a file against a specified pattern.
+ *
+ * This version detector can be used if the library version is denoted in a
+ * particular format in a changelog or readme file, for example.
+ *
+ * @VersionDetector("line_pattern")
+ *
+ * @ingroup libraries
+ */
+class LinePatternDetector extends PluginBase implements VersionDetectorInterface, ContainerFactoryPluginInterface {
+
+  /**
+   * The app root.
+   *
+   * @var string
+   */
+  protected $appRoot;
+
+  /**
+   * Constructs a line pattern version detector.
+   *
+   * @param array $configuration
+   * @param string $plugin_id
+   * @param array $plugin_definition
+   * @param string $app_root
+   */
+  public function __construct(array $configuration, $plugin_id, array $plugin_definition, $app_root) {
+    $configuration += [
+      'file' => '',
+      'pattern' => '',
+      'lines' => 20,
+      'columns' => 200,
+    ];
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->appRoot = $app_root;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('app.root')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function detectVersion(VersionedLibraryInterface $library) {
+    if (!($library instanceof LocalLibraryInterface)) {
+      throw new UnknownLibraryVersionException($library);
+    }
+
+    $filepath = $this->appRoot . '/' . $library->getLocalPath() . '/' . $this->configuration['file'];
+    if (!file_exists($filepath)) {
+      throw new UnknownLibraryVersionException($library);
+    }
+
+    $file = fopen($filepath, 'r');
+    $lines = $this->configuration['lines'];
+    while ($lines && $line = fgets($file, $this->configuration['columns'])) {
+      if (preg_match($this->configuration['pattern'], $line, $version)) {
+        fclose($file);
+        $library->setVersion($version[1]);
+        return;
+      }
+      $lines--;
+    }
+    fclose($file);
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/Plugin/libraries/VersionDetector/StaticDetector.php b/web/modules/contrib/libraries/src/Plugin/libraries/VersionDetector/StaticDetector.php
new file mode 100644 (file)
index 0000000..ed697de
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+
+namespace Drupal\libraries\Plugin\libraries\VersionDetector;
+
+use Drupal\Core\Plugin\PluginBase;
+use Drupal\libraries\ExternalLibrary\Exception\UnknownLibraryVersionException;
+use Drupal\libraries\ExternalLibrary\Version\VersionDetectorInterface;
+use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface;
+
+/**
+ * Detects the version by returning a static string.
+ *
+ * As this does not perform any actual detection and, thus, circumvents any
+ * negotiation of versions by Libraries API it should only be used for testing
+ * or when the version of a library cannot be determined from the source code
+ * itself.
+ *
+ * @VersionDetector("static")
+ */
+class StaticDetector extends PluginBase implements VersionDetectorInterface {
+
+  /**
+   * Constructs a static version detector.
+   *
+   * @param array $configuration
+   * @param string $plugin_id
+   * @param array $plugin_definition
+   */
+  public function __construct(array $configuration, $plugin_id, array $plugin_definition) {
+    $configuration += [
+      'version' => NULL,
+    ];
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function detectVersion(VersionedLibraryInterface $library) {
+    if (!isset($this->configuration['version'])) {
+      throw new UnknownLibraryVersionException($library);
+    }
+    $library->setVersion($this->configuration['version']);
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/StreamWrapper/AssetLibrariesStream.php b/web/modules/contrib/libraries/src/StreamWrapper/AssetLibrariesStream.php
new file mode 100644 (file)
index 0000000..54893a7
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\libraries\StreamWrapper;
+
+use Drupal\Core\StreamWrapper\LocalStream;
+
+/**
+ * Provides a stream wrapper for asset libraries.
+ *
+ * Can be used with the 'asset://' scheme, for example
+ * 'asset://jquery/jquery.js'.
+ */
+class AssetLibrariesStream extends LocalStream {
+
+  use LocalHiddenStreamTrait;
+  use PrivateStreamTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName() {
+    return t('Assets');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return t('Provides access to asset library files.');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDirectoryPath() {
+    // @todo Provide support for site-specific directories, etc.
+    return 'sites/all/assets/vendor';
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/StreamWrapper/LibraryDefinitionsStream.php b/web/modules/contrib/libraries/src/StreamWrapper/LibraryDefinitionsStream.php
new file mode 100644 (file)
index 0000000..7cf0764
--- /dev/null
@@ -0,0 +1,86 @@
+<?php
+
+namespace Drupal\libraries\StreamWrapper;
+
+use Drupal\Core\StreamWrapper\LocalStream;
+
+/**
+ * Provides a stream wrapper for library definitions.
+ *
+ * Can be used with the 'library-definitions' scheme, for example
+ * 'library-definitions://example.json' for a library ID of 'example'.
+ *
+ * By default this stream wrapper reads from a single directory that is
+ * configurable and points to the 'library-definitions' directory within the
+ * public files directory by default. This makes library definitions writable
+ * by the webserver by default, which is in anticipation of a user interface
+ * that fetches definitions from a remote repository and stores them locally.
+ * For improved security the library definitions can be managed manually (or put
+ * under version control) and placed in a directory that is not writable by the
+ * webserver.
+ *
+ * The idea of using a stream wrapper for this as well as the default location
+ * is taken from the 'translations' stream wrapper provided by the Interface
+ * Translation module.
+ *
+ * @see \Drupal\locale\StreamWrapper\TranslationsStream
+ *
+ * @todo Use a setting instead of configuration for the directory.
+ */
+class LibraryDefinitionsStream extends LocalStream {
+
+  use LocalHiddenStreamTrait;
+  use PrivateStreamTrait;
+
+  /**
+   * The config factory
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * Constructs an external library registry.
+   *
+   * @todo Dependency injection.
+   */
+  public function __construct() {
+    $this->configFactory = \Drupal::configFactory();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName() {
+    return t('Library definitions');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return t('Provides access to library definition files.');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDirectoryPath() {
+    return $this->getConfig('local.path');
+  }
+
+  /**
+   * Fetches a configuration value from the library definitions configuration.
+   * @param $key
+   *   The configuration key to fetch.
+   *
+   * @return array|mixed|null
+   *   The configuration value.
+   */
+  protected function getConfig($key) {
+    return $this->configFactory
+      ->get('libraries.settings')
+      ->get("definitions.$key");
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/StreamWrapper/LocalHiddenStreamTrait.php b/web/modules/contrib/libraries/src/StreamWrapper/LocalHiddenStreamTrait.php
new file mode 100644 (file)
index 0000000..f71c0de
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+
+namespace Drupal\libraries\StreamWrapper;
+
+use Drupal\Core\StreamWrapper\StreamWrapperInterface;
+
+/**
+ * Provides a trait for local hidden streams.
+ */
+trait LocalHiddenStreamTrait {
+
+  /**
+   * Returns the type of stream wrapper.
+   *
+   * @return int
+   *
+   * @see \Drupal\Core\StreamWrapper\StreamWrapperInterface::getType()
+   */
+  public static function getType() {
+    return StreamWrapperInterface::LOCAL_HIDDEN;
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/StreamWrapper/PhpFileLibrariesStream.php b/web/modules/contrib/libraries/src/StreamWrapper/PhpFileLibrariesStream.php
new file mode 100644 (file)
index 0000000..a493385
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\libraries\StreamWrapper;
+
+use Drupal\Core\StreamWrapper\LocalStream;
+
+/**
+ * Provides a stream wrapper for PHP file libraries.
+ *
+ * Can be used with the 'php-file://' scheme, for example
+ * 'php-file-library://guzzle/src/functions_include.php'.
+ */
+class PhpFileLibrariesStream extends LocalStream {
+
+  use LocalHiddenStreamTrait;
+  use PrivateStreamTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName() {
+    return t('PHP library files');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return t('Provides access to PHP library files.');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDirectoryPath() {
+    // @todo Provide support for site-specific directories, etc.
+    return 'sites/all/libraries';
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/StreamWrapper/PrivateStreamTrait.php b/web/modules/contrib/libraries/src/StreamWrapper/PrivateStreamTrait.php
new file mode 100644 (file)
index 0000000..4690551
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+
+namespace Drupal\libraries\StreamWrapper;
+
+/**
+ * Provides a trait for local streams that are not publicly accessible.
+ *
+ * @see \Drupal\locale\StreamWrapper\TranslationsStream
+ */
+trait PrivateStreamTrait {
+
+  /**
+   * Returns a web accessible URL for the resource.
+   *
+   * This function should return a URL that can be embedded in a web page
+   * and accessed from a browser. For example, the external URL of
+   * "youtube://xIpLd0WQKCY" might be
+   * "http://www.youtube.com/watch?v=xIpLd0WQKCY".
+   *
+   * @return string
+   *   Returns a string containing a web accessible URL for the resource.
+   *
+   * @see \Drupal\Core\StreamWrapper\StreamWrapperInterface::getExternalUrl()
+   */
+  function getExternalUrl() {
+    throw new \LogicException("{$this->getName()} should not be public.");
+  }
+
+  /**
+   * Returns the name of the stream wrapper for use in the UI.
+   *
+   * @return string
+   *   The stream wrapper name.
+   *
+   * @see \Drupal\Core\StreamWrapper\StreamWrapperInterface::getName()
+   */
+  abstract public function getName();
+
+}
diff --git a/web/modules/contrib/libraries/src/Tests/LibrariesUnitTest.php b/web/modules/contrib/libraries/src/Tests/LibrariesUnitTest.php
new file mode 100644 (file)
index 0000000..f616a92
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+
+namespace Drupal\libraries\Tests;
+
+use Drupal\simpletest\KernelTestBase;
+
+/**
+ * Tests basic Libraries API functions.
+ *
+ * @group libraries
+ */
+class LibrariesUnitTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = array('libraries');
+
+  /**
+   * Tests libraries_get_path().
+   */
+  function testLibrariesGetPath() {
+    // Note that, even though libraries_get_path() doesn't find the 'example'
+    // library, we are able to make it 'installed' by specifying the 'library
+    // path' up-front. This is only used for testing purposed and is strongly
+    // discouraged as it defeats the purpose of Libraries API in the first
+    // place.
+    $this->assertEqual(libraries_get_path('example'), FALSE, 'libraries_get_path() returns FALSE for a missing library.');
+  }
+
+  /**
+   * Tests libraries_prepare_files().
+   */
+  function testLibrariesPrepareFiles() {
+    $expected = array(
+      'files' => array(
+        'js' => array('example.js' => array()),
+        'css' => array('example.css' => array()),
+        'php' => array('example.php' => array()),
+      ),
+    );
+    $library = array(
+      'files' => array(
+        'js' => array('example.js'),
+        'css' => array('example.css'),
+        'php' => array('example.php'),
+      ),
+    );
+    libraries_prepare_files($library, NULL, NULL);
+    $this->assertEqual($expected, $library, 'libraries_prepare_files() works correctly.');
+  }
+
+}
diff --git a/web/modules/contrib/libraries/src/Tests/LibrariesWebTest.php b/web/modules/contrib/libraries/src/Tests/LibrariesWebTest.php
new file mode 100644 (file)
index 0000000..2c9254c
--- /dev/null
@@ -0,0 +1,525 @@
+<?php
+
+namespace Drupal\libraries\Tests;
+
+use Drupal\Component\Utility\Html;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests basic detection and loading of libraries.
+ *
+ * @group libraries
+ */
+class LibrariesWebTest extends WebTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $profile = 'testing';
+
+  /**
+   * Modules to install.
+   *
+   * @var array
+   */
+  public static $modules = array('libraries', 'libraries_test');
+
+  /**
+   * The URL generator used in this test.
+   *
+   * @var \Drupal\Core\Utility\UnroutedUrlAssemblerInterface
+   */
+  protected $urlAssembler;
+
+  /**
+   * The state service used in this test.
+   *
+   * @var \Drupal\Core\State\StateInterface
+   */
+  protected $state;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->urlAssembler = $this->container->get('unrouted_url_assembler');
+    $this->state = $this->container->get('state');
+  }
+
+  /**
+   * Tests libraries_detect_dependencies().
+   */
+  function testLibrariesDetectDependencies() {
+    $library = array(
+      'name' => 'Example',
+      'dependencies' => array('example_missing'),
+    );
+    libraries_detect_dependencies($library);
+    $this->assertEqual($library['error'], 'missing dependency', 'libraries_detect_dependencies() detects missing dependency');
+    $error_message = t('The %dependency library, which the %library library depends on, is not installed.', array(
+      '%dependency' => 'Example missing',
+      '%library' => $library['name'],
+    ));
+    $this->verbose("Expected:<br>$error_message");
+    $this->verbose('Actual:<br>' . $library['error message']);
+    $this->assertEqual($library['error message'], $error_message, 'Correct error message for a missing dependency');
+    // Test versioned dependencies.
+    $version = '1.1';
+    $compatible = array(
+      '1.1',
+      '<=1.1',
+      '>=1.1',
+      '<1.2',
+      '<2.0',
+      '>1.0',
+      '>1.0-rc1',
+      '>1.0-beta2',
+      '>1.0-alpha3',
+      '>0.1',
+      '<1.2, >1.0',
+      '>0.1, <=1.1',
+    );
+    $incompatible = array(
+      '1.2',
+      '2.0',
+      '<1.1',
+      '>1.1',
+      '<=1.0',
+      '<=1.0-rc1',
+      '<=1.0-beta2',
+      '<=1.0-alpha3',
+      '>=1.2',
+      '<1.1, >0.9',
+      '>=0.1, <1.1',
+    );
+    $library = array(
+      'name' => 'Example',
+    );
+    foreach ($compatible as $version_string) {
+      $library['dependencies'][0] = "example_dependency ($version_string)";
+      // libraries_detect_dependencies() is a post-detect callback, so
+      // 'installed' is already set, when it is called. It sets the value to
+      // FALSE for missing or incompatible dependencies.
+      $library['installed'] = TRUE;
+      libraries_detect_dependencies($library);
+      $this->assertTrue($library['installed'], "libraries_detect_dependencies() detects compatible version string: '$version_string' is compatible with '$version'");
+    }
+    foreach ($incompatible as $version_string) {
+      $library['dependencies'][0] = "example_dependency ($version_string)";
+      $library['installed'] = TRUE;
+      unset($library['error'], $library['error message']);
+      libraries_detect_dependencies($library);
+      $this->assertEqual($library['error'], 'incompatible dependency', "libraries_detect_dependencies() detects incompatible version strings: '$version_string' is incompatible with '$version'");
+    }
+    // Instead of repeating this assertion for each version string, we just
+    // re-use the $library variable from the foreach loop.
+    $error_message = t('The version %dependency_version of the %dependency library is not compatible with the %library library.', array(
+      '%dependency_version' => $version,
+      '%dependency' => 'Example dependency',
+      '%library' => $library['name'],
+    ));
+    $this->verbose("Expected:<br>$error_message");
+    $this->verbose('Actual:<br>' . $library['error message']);
+    $this->assertEqual($library['error message'], $error_message, 'Correct error message for an incompatible dependency');
+  }
+
+  /**
+   * Tests libraries_scan_info_files().
+   */
+  function testLibrariesScanInfoFiles() {
+    $expected = array('example_info_file' => (object) array(
+      'uri' => drupal_get_path('module', 'libraries') . '/tests/example/example_info_file.libraries.info.yml',
+      'filename' => 'example_info_file.libraries.info.yml',
+      'name' => 'example_info_file.libraries.info',
+    ));
+    $actual = libraries_scan_info_files();
+    $this->verbose('Expected:<pre>' . var_export($expected, TRUE) . '</pre>');
+    $this->verbose('Actual:<pre>' . var_export($actual, TRUE) . '</pre>');
+    $this->assertEqual($actual, $expected, 'libraries_scan_info_files() correctly finds the example info file.');
+    $this->verbose('<pre>' . var_export(libraries_scan_info_files(), TRUE) . '</pre>');
+  }
+
+  /**
+   * Tests libraries_info().
+   */
+  function testLibrariesInfo() {
+    // Test that library information is found correctly.
+    $expected = array(
+      'name' => 'Example files',
+      'library path' => drupal_get_path('module', 'libraries') . '/tests/example',
+      'version' => '1',
+      'files' => array(
+        'js' => array('example_1.js' => array()),
+        'css' => array('example_1.css' => array()),
+        'php' => array('example_1.php' => array()),
+      ),
+      'module' => 'libraries_test',
+    );
+    libraries_info_defaults($expected, 'example_files');
+    $library = libraries_info('example_files');
+    $this->verbose('Expected:<pre>' . var_export($expected, TRUE) . '</pre>');
+    $this->verbose('Actual:<pre>' . var_export($library, TRUE) . '</pre>');
+    $this->assertEqual($library, $expected, 'Library information is correctly gathered.');
+
+    // Test a library specified with an .info file gets detected.
+    $expected = array(
+      'name' => 'Example info file',
+      'info file' => drupal_get_path('module', 'libraries') . '/tests/example/example_info_file.libraries.info.yml',
+    );
+    libraries_info_defaults($expected, 'example_info_file');
+    $library = libraries_info('example_info_file');
+    // If this module was downloaded from Drupal.org, the Drupal.org packaging
+    // system has corrupted the test info file.
+    // @see http://drupal.org/node/1606606
+    unset($library['core'], $library['datestamp'], $library['project'], $library['version']);
+    $this->verbose('Expected:<pre>' . var_export($expected, TRUE) . '</pre>');
+    $this->verbose('Actual:<pre>' . var_export($library, TRUE) . '</pre>');
+    $this->assertEqual($library, $expected, 'Library specified with an .info file found');
+  }
+
+  /**
+   * Tests libraries_detect().
+   */
+  function testLibrariesDetect() {
+    // Test missing library.
+    $library = libraries_detect('example_missing');
+    $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
+    $this->assertEqual($library['error'], 'not found', 'Missing library not found.');
+    $error_message = t('The %library library could not be found.', array(
+      '%library' => $library['name'],
+    ));
+    $this->assertEqual($library['error message'], $error_message, 'Correct error message for a missing library.');
+
+    // Test unknown library version.
+    $library = libraries_detect('example_undetected_version');
+    $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
+    $this->assertEqual($library['error'], 'not detected', 'Undetected version detected as such.');
+    $error_message = t('The version of the %library library could not be detected.', array(
+      '%library' => $library['name'],
+    ));
+    $this->assertEqual($library['error message'], $error_message, 'Correct error message for a library with an undetected version.');
+
+    // Test unsupported library version.
+    $library = libraries_detect('example_unsupported_version');
+    $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
+    $this->assertEqual($library['error'], 'not supported', 'Unsupported version detected as such.');
+    $error_message = t('The installed version %version of the %library library is not supported.', array(
+      '%version' => $library['version'],
+      '%library' => $library['name'],
+    ));
+    $this->assertEqual($library['error message'], $error_message, 'Correct error message for a library with an unsupported version.');
+
+    // Test supported library version.
+    $library = libraries_detect('example_supported_version');
+    $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
+    $this->assertEqual($library['installed'], TRUE, 'Supported library version found.');
+
+    // Test libraries_get_version().
+    $library = libraries_detect('example_default_version_callback');
+    $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
+    $this->assertEqual($library['version'], '1', 'Expected version returned by default version callback.');
+
+    // Test a multiple-parameter version callback.
+    $library = libraries_detect('example_multiple_parameter_version_callback');
+    $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
+    $this->assertEqual($library['version'], '1', 'Expected version returned by multiple parameter version callback.');
+
+    // Test a top-level files property.
+    $library = libraries_detect('example_files');
+    $files = array(
+      'js' => array('example_1.js' => array()),
+      'css' => array('example_1.css' => array()),
+      'php' => array('example_1.php' => array()),
+    );
+    $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
+    $this->assertEqual($library['files'], $files, 'Top-level files property works.');
+
+    // Test version-specific library files.
+    $library = libraries_detect('example_versions');
+    $files = array(
+      'js' => array('example_2.js' => array()),
+      'css' => array('example_2.css' => array()),
+      'php' => array('example_2.php' => array()),
+    );
+    $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
+    $this->assertEqual($library['files'], $files, 'Version-specific library files found.');
+
+    // Test missing variant.
+    $library = libraries_detect('example_variant_missing');
+    $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
+    $this->assertEqual($library['variants']['example_variant']['error'], 'not found', 'Missing variant not found');
+    $error_message = t('The %variant variant of the %library library could not be found.', array(
+      '%variant' => 'example_variant',
+      '%library' => 'Example variant missing',
+    ));
+    $this->assertEqual($library['variants']['example_variant']['error message'], $error_message, 'Correct error message for a missing variant.');
+
+    // Test existing variant.
+    $library = libraries_detect('example_variant');
+    $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
+    $this->assertEqual($library['variants']['example_variant']['installed'], TRUE, 'Existing variant found.');
+  }
+
+  /**
+   * Tests libraries_load().
+   *
+   * @todo Remove or rewrite to accomodate integration with core Libraries.
+   */
+  function _testLibrariesLoad() {
+    // Test dependencies.
+    $library = libraries_load('example_dependency_missing');
+    $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
+    $this->assertFalse($library['loaded'], 'Library with missing dependency cannot be loaded');
+    $library = libraries_load('example_dependency_incompatible');
+    $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
+    $this->assertFalse($library['loaded'], 'Library with incompatible dependency cannot be loaded');
+    $library = libraries_load('example_dependency_compatible');
+    $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>');
+    $this->assertEqual($library['loaded'], 1, 'Library with compatible dependency is loaded');
+    $loaded = &drupal_static('libraries_load');
+    $this->verbose('<pre>' . var_export($loaded, TRUE) . '</pre>');
+    $this->assertEqual($loaded['example_dependency']['loaded'], 1, 'Dependency library is also loaded');
+  }
+
+  /**
+   * Tests the applying of callbacks.
+   */
+  function testCallbacks() {
+    $expected = array(
+      'name' => 'Example callback',
+      'library path' => drupal_get_path('module', 'libraries') . '/tests/example',
+      'version' => '1',
+      'versions' => array(
+        '1' => array(
+          'variants' => array(
+            'example_variant' => array(
+              'info callback' => 'not applied',
+              'pre-detect callback' => 'not applied',
+              'post-detect callback' => 'not applied',
+              'pre-load callback' => 'not applied',
+              'post-load callback' => 'not applied',
+            ),
+          ),
+          'info callback' => 'not applied',
+          'pre-detect callback' => 'not applied',
+          'post-detect callback' => 'not applied',
+          'pre-load callback' => 'not applied',
+          'post-load callback' => 'not applied',
+        ),
+      ),
+      'variants' => array(
+        'example_variant' => array(
+          'info callback' => 'not applied',
+          'pre-detect callback' => 'not applied',
+          'post-detect callback' => 'not applied',
+          'pre-load callback' => 'not applied',
+          'post-load callback' => 'not applied',
+        ),
+      ),
+      'callbacks' => array(
+        'info' => array('_libraries_test_info_callback'),
+        'pre-detect' => array('_libraries_test_pre_detect_callback'),
+        'post-detect' => array('_libraries_test_post_detect_callback'),
+        'pre-load' => array('_libraries_test_pre_load_callback'),
+        'post-load' => array('_libraries_test_post_load_callback'),
+      ),
+      'info callback' => 'not applied',
+      'pre-detect callback' => 'not applied',
+      'post-detect callback' => 'not applied',
+      'pre-load callback' => 'not applied',
+      'post-load callback' => 'not applied',
+      'module' => 'libraries_test',
+    );
+    libraries_info_defaults($expected, 'example_callback');
+
+    // Test a callback in the 'info' group.
+    $expected['info callback'] = 'applied (top-level)';
+    $expected['versions']['1']['info callback'] = 'applied (version 1)';
+    $expected['versions']['1']['variants']['example_variant']['info callback'] = 'applied (version 1, variant example_variant)';
+    $expected['variants']['example_variant']['info callback'] = 'applied (variant example_variant)';
+    $library = libraries_info('example_callback');
+    $this->verbose('Expected:<pre>' . var_export($expected, TRUE) . '</pre>');
+    $this->verbose('Actual:<pre>' . var_export($library, TRUE) . '</pre>');
+    $this->assertEqual($library, $expected, 'Prepare callback was applied correctly.');
+
+    // Test a callback in the 'pre-detect' and 'post-detect' phases.
+    // Successfully detected libraries should only contain version information
+    // for the detected version and thus, be marked as installed.
+    unset($expected['versions']);
+    $expected['installed'] = TRUE;
+    // Additionally, version-specific properties of the detected version are
+    // supposed to override the corresponding top-level properties.
+    $expected['info callback'] = 'applied (version 1)';
+    $expected['variants']['example_variant']['installed'] = TRUE;
+    $expected['variants']['example_variant']['info callback'] = 'applied (version 1, variant example_variant)';
+    // Version-overloading takes place after the 'pre-detect' callbacks have
+    // been applied.
+    $expected['pre-detect callback'] = 'applied (version 1)';
+    $expected['post-detect callback'] = 'applied (top-level)';
+    $expected['variants']['example_variant']['pre-detect callback'] = 'applied (version 1, variant example_variant)';
+    $expected['variants']['example_variant']['post-detect callback'] = 'applied (variant example_variant)';
+    $library = libraries_detect('example_callback');
+    $this->verbose('Expected:<pre>' . var_export($expected, TRUE) . '</pre>');
+    $this->verbose('Actual:<pre>' . var_export($library, TRUE) . '</pre>');
+    $this->assertEqual($library, $expected, 'Detect callback was applied correctly.');
+
+    // Test a callback in the 'pre-load' and 'post-load' phases.
+    // Successfully loaded libraries should only contain information about the
+    // already loaded variant.
+    unset($expected['variants']);
+    $expected['loaded'] = 0;
+    $expected['pre-load callback'] = 'applied (top-level)';
+    $expected['post-load callback'] = 'applied (top-level)';
+    $library = libraries_load('example_callback');
+    $this->verbose('Expected:<pre>' . var_export($expected, TRUE) . '</pre>');
+    $this->verbose('Actual:<pre>' . var_export($library, TRUE) . '</pre>');
+    $this->assertEqual($library, $expected, 'Pre-load and post-load callbacks were applied correctly.');
+    // This is not recommended usually and is only used for testing purposes.
+    drupal_static_reset('libraries_load');
+    // Successfully loaded library variants are supposed to contain the specific
+    // variant information only.
+    $expected['info callback'] = 'applied (version 1, variant example_variant)';
+    $expected['pre-detect callback'] = 'applied (version 1, variant example_variant)';
+    $expected['post-detect callback'] = 'applied (variant example_variant)';
+    $library = libraries_load('example_callback', 'example_variant');
+    $this->verbose('Expected:<pre>' . var_export($expected, TRUE) . '</pre>');
+    $this->verbose('Actual:<pre>' . var_export($library, TRUE) . '</pre>');
+    $this->assertEqual($library, $expected, 'Pre-detect and post-detect callbacks were applied correctly to a variant.');
+  }
+
+  /**
+   * Tests that library files are properly added to the page output.
+   *
+   * We check for JavaScript and CSS files directly in the DOM and add a list of
+   * included PHP files manually to the page output.
+   *
+   * @todo Remove or rewrite to accomodate integration with core Libraries.
+   * @see _libraries_test_load()
+   */
+  function _testLibrariesOutput() {
+    // Test loading of a simple library with a top-level files property.
+    $this->drupalGet('libraries_test/files');
+    $this->assertLibraryFiles('example_1', 'File loading');
+
+    // Test loading of integration files.
+    $this->drupalGet('libraries_test/integration_files');
+    $this->assertRaw('libraries_test.js', 'Integration file loading: libraries_test.js found');
+    $this->assertRaw('libraries_test.css', 'Integration file loading: libraries_test.css found');
+    $this->assertRaw('libraries_test.inc', 'Integration file loading: libraries_test.inc found');
+
+    // Test version overloading.
+    $this->drupalGet('libraries_test/versions');
+    $this->assertLibraryFiles('example_2', 'Version overloading');
+
+    // Test variant loading.
+    $this->drupalGet('libraries_test/variant');
+    $this->assertLibraryFiles('example_3', 'Variant loading');
+
+    // Test version overloading and variant loading.
+    $this->drupalGet('libraries_test/versions_and_variants');
+    $this->assertLibraryFiles('example_4', 'Concurrent version and variant overloading');
+
+    // Test caching.
+    \Drupal::state()->set('libraries_test.cache', TRUE);
+    \Drupal::cache('libraries')->delete('example_callback');
+    // When the library information is not cached, all callback groups should be
+    // invoked.
+    $this->drupalGet('libraries_test/cache');
+    $this->assertRaw('The <em>info</em> callback group was invoked.', 'Info callback invoked for uncached libraries.');
+    $this->assertRaw('The <em>pre-detect</em> callback group was invoked.', 'Pre-detect callback invoked for uncached libraries.');
+    $this->assertRaw('The <em>post-detect</em> callback group was invoked.', 'Post-detect callback invoked for uncached libraries.');
+    $this->assertRaw('The <em>pre-load</em> callback group was invoked.', 'Pre-load callback invoked for uncached libraries.');
+    $this->assertRaw('The <em>post-load</em> callback group was invoked.', 'Post-load callback invoked for uncached libraries.');
+    // When the library information is cached only the 'pre-load' and
+    // 'post-load' callback groups should be invoked.
+    $this->drupalGet('libraries_test/cache');
+    $this->assertNoRaw('The <em>info</em> callback group was not invoked.', 'Info callback not invoked for cached libraries.');
+    $this->assertNoRaw('The <em>pre-detect</em> callback group was not invoked.', 'Pre-detect callback not invoked for cached libraries.');
+    $this->assertNoRaw('The <em>post-detect</em> callback group was not invoked.', 'Post-detect callback not invoked for cached libraries.');
+    $this->assertRaw('The <em>pre-load</em> callback group was invoked.', 'Pre-load callback invoked for cached libraries.');
+    $this->assertRaw('The <em>post-load</em> callback group was invoked.', 'Post-load callback invoked for cached libraries.');
+    \Drupal::state()->set('libraries_test.cache', FALSE);
+  }
+
+  /**
+   * Helper function to assert that a library was correctly loaded.
+   *
+   * Asserts that all the correct files were loaded and all the incorrect ones
+   * were not.
+   *
+   * @param $name
+   *   The name of the files that should be loaded. The current testing system
+   *   knows of 'example_1', 'example_2', 'example_3' and 'example_4'. Each name
+   *   has an associated JavaScript, CSS and PHP file that will be asserted. All
+   *   other files will be asserted to not be loaded. See
+   *   tests/example/README.txt for more information on how the loading of the
+   *   files is tested.
+   * @param $label
+   *   (optional) A label to prepend to the assertion messages, to make them
+   *   less ambiguous.
+   * @param $extensions
+   *   (optional) The expected file extensions of $name. Defaults to
+   *   array('js', 'css', 'php').
+   */
+  function assertLibraryFiles($name, $label = '', $extensions = array('js', 'css', 'php')) {
+    $label = ($label !== '' ? "$label: " : '');
+
+    // Test that the wrong files are not loaded...
+    $names = array(
+      'example_1' => FALSE,
+      'example_2' => FALSE,
+      'example_3' => FALSE,
+      'example_4' => FALSE,
+    );
+    // ...and the correct ones are.
+    $names[$name] = TRUE;
+
+    // Test for the specific HTML that the different file types appear as in the
+    // DOM.
+    $html = array(
+      'js' => array('<script src="', '"></script>'),
+      'css' => array('<link rel="stylesheet" href="', '" media="all" />'),
+      // PHP files do not get added to the DOM directly.
+      // @see _libraries_test_load()
+      'php' => array('<li>', '</li>'),
+    );
+
+    $html_expected = array();
+    $html_not_expected = array();
+
+    foreach ($names as $name => $expected) {
+      foreach ($extensions as $extension) {
+        $filepath = drupal_get_path('module', 'libraries') . "/tests/example/$name.$extension";
+        // JavaScript and CSS files appear as full URLs and with an appended
+        // query string.
+        if (in_array($extension, array('js', 'css'))) {
+          $filepath = $this->urlAssembler->assemble("base://$filepath", [
+            'query' => [
+              $this->state->get('system.css_js_query_string') ?: '0' => NULL,
+            ],
+            'absolute' => TRUE,
+          ]);
+          // If index.php is part of the generated URLs, we need to strip it.
+          //$filepath = str_replace('index.php/', '', $filepath);
+        }
+        list($prefix, $suffix) = $html[$extension];
+        $raw = $prefix . $filepath . $suffix;
+        if ($expected) {
+          $html_expected[] = Html::escape($raw);
+          $this->assertRaw($raw, "$label$name.$extension found.");
+        }
+        else {
+          $html_not_expected[] = Html::escape($raw);
+          $this->assertNoRaw($raw, "$label$name.$extension not found.");
+        }
+      }
+    }
+
+    $html_expected = '<ul><li><pre>' . implode('</pre></li><li><pre>', $html_expected) . '</pre></li></ul>';
+    $html_not_expected = '<ul><li><pre>' . implode('</pre></li><li><pre>', $html_not_expected) . '</pre></li></ul>';
+    $this->verbose("Strings of HTML that are expected to be present:{$html_expected}Strings of HTML that are expected to not be present:{$html_not_expected}");
+  }
+
+}
diff --git a/web/modules/contrib/libraries/tests/assets/vendor/test_asset_library/example.css b/web/modules/contrib/libraries/tests/assets/vendor/test_asset_library/example.css
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/web/modules/contrib/libraries/tests/assets/vendor/test_asset_library/example.js b/web/modules/contrib/libraries/tests/assets/vendor/test_asset_library/example.js
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/web/modules/contrib/libraries/tests/assets/vendor/test_asset_multiple_library/example.first.css b/web/modules/contrib/libraries/tests/assets/vendor/test_asset_multiple_library/example.first.css
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/web/modules/contrib/libraries/tests/assets/vendor/test_asset_multiple_library/example.first.js b/web/modules/contrib/libraries/tests/assets/vendor/test_asset_multiple_library/example.first.js
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/web/modules/contrib/libraries/tests/assets/vendor/test_asset_multiple_library/example.second.css b/web/modules/contrib/libraries/tests/assets/vendor/test_asset_multiple_library/example.second.css
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/web/modules/contrib/libraries/tests/assets/vendor/test_asset_multiple_library/example.second.js b/web/modules/contrib/libraries/tests/assets/vendor/test_asset_multiple_library/example.second.js
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/web/modules/contrib/libraries/tests/example/README.txt b/web/modules/contrib/libraries/tests/example/README.txt
new file mode 100644 (file)
index 0000000..e3582c2
--- /dev/null
@@ -0,0 +1,42 @@
+
+Example library
+
+Version 1
+
+This file is an example file to test version detection.
+
+The various other files in this directory are to test the loading of JavaScript,
+CSS and PHP files.
+- JavaScript: The filenames of the JavaScript files are asserted to be in the
+  raw HTML via SimpleTest. Since the filename could appear, for instance, in an
+  error message, this is not very robust. Explicit testing of JavaScript,
+  though, is not yet possible with SimpleTest. To allow for easier debugging, we
+  place the following text on the page:
+  "If this text shows up, no JavaScript test file was loaded."
+  This text is replaced via JavaScript by a text of the form:
+  "If this text shows up, [[file] was loaded successfully."
+  [file] is either 'example_1.js', 'example_2.js', 'example_3.js',
+  'example_4.js' or 'libraries_test.js'. If you have SimpleTest's verbose mode
+  enabled and see the above text in one of the debug pages, the noted JavaScript
+  file was loaded successfully.
+- CSS: The filenames of the CSS files are asserted to be in the raw HTML via
+  SimpleTest. Since the filename could appear, for instance, in an error
+  message, this is not very robust. Explicit testing of CSS, though, is not yet
+  possible with SimpleTest. Hence, the CSS files, if loaded, make the following
+  text a certain color:
+  "If one of the CSS test files has been loaded, this text will be colored:
+  - example_1: red
+  - example_2: green
+  - example_3: orange
+  - example_4: blue
+  - libraries_test: purple"
+  If you have SimpleTest's verbose mode enabled, and see the above text in a
+  certain color (i.e. not in black), a CSS file was loaded successfully. Which
+  file depends on the color as referenced in the text above.
+- PHP: The loading of PHP files is tested by defining a dummy function in the
+  PHP files and then checking whether this function was defined using
+  function_exists(). This can be checked programatically with SimpleTest.
+The loading of integration files is tested with the same method. The integration
+files are libraries_test.js, libraries_test.css, libraries_test.inc and are
+located in the tests directory alongside libraries_test.module (i.e. they are
+not in the same directory as this file).
diff --git a/web/modules/contrib/libraries/tests/example/example_1.css b/web/modules/contrib/libraries/tests/example/example_1.css
new file mode 100644 (file)
index 0000000..a732bda
--- /dev/null
@@ -0,0 +1,11 @@
+
+/**
+ * @file
+ * Test CSS file for Libraries loading.
+ *
+ * Color the 'libraries-test-css' div red. See README.txt for more information.
+ */
+
+.libraries-test-css {
+  color: red;
+}
diff --git a/web/modules/contrib/libraries/tests/example/example_1.js b/web/modules/contrib/libraries/tests/example/example_1.js
new file mode 100644 (file)
index 0000000..8a1b9a2
--- /dev/null
@@ -0,0 +1,18 @@
+
+/**
+ * @file
+ * Test JavaScript file for Libraries loading.
+ *
+ * Replace the text in the 'libraries-test-javascript' div. See README.txt for
+ * more information.
+ */
+
+(function ($) {
+
+Drupal.behaviors.librariesTest = {
+  attach: function(context, settings) {
+    $('.libraries-test-javascript').text('If this text shows up, example_1.js was loaded successfully.')
+  }
+};
+
+})(jQuery);
diff --git a/web/modules/contrib/libraries/tests/example/example_1.php b/web/modules/contrib/libraries/tests/example/example_1.php
new file mode 100644 (file)
index 0000000..92e6711
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+
+/**
+ * @file
+ * Test PHP file for Libraries loading.
+ */
+
+/**
+ * Dummy function to see if this file was loaded.
+ */
+function _libraries_test_example_1() {
+}
diff --git a/web/modules/contrib/libraries/tests/example/example_2.css b/web/modules/contrib/libraries/tests/example/example_2.css
new file mode 100644 (file)
index 0000000..c8f9289
--- /dev/null
@@ -0,0 +1,11 @@
+
+/**
+ * @file
+ * Test CSS file for Libraries loading.
+ *
+ * Color the 'libraries-test-css' div green. See README.txt for more information.
+ */
+
+.libraries-test-css {
+  color: green;
+}
diff --git a/web/modules/contrib/libraries/tests/example/example_2.js b/web/modules/contrib/libraries/tests/example/example_2.js
new file mode 100644 (file)
index 0000000..1b744bb
--- /dev/null
@@ -0,0 +1,18 @@
+
+/**
+ * @file
+ * Test JavaScript file for Libraries loading.
+ *
+ * Replace the text in the 'libraries-test-javascript' div. See README.txt for
+ * more information.
+ */
+
+(function ($) {
+
+Drupal.behaviors.librariesTest = {
+  attach: function(context, settings) {
+    $('.libraries-test-javascript').text('If this text shows up, example_2.js was loaded successfully.')
+  }
+};
+
+})(jQuery);
diff --git a/web/modules/contrib/libraries/tests/example/example_2.php b/web/modules/contrib/libraries/tests/example/example_2.php
new file mode 100644 (file)
index 0000000..ddded40
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+
+/**
+ * @file
+ * Test PHP file for Libraries loading.
+ */
+
+/**
+ * Dummy function to see if this file was loaded.
+ */
+function _libraries_test_example_2() {
+}
diff --git a/web/modules/contrib/libraries/tests/example/example_3.css b/web/modules/contrib/libraries/tests/example/example_3.css
new file mode 100644 (file)
index 0000000..ffef054
--- /dev/null
@@ -0,0 +1,11 @@
+
+/**
+ * @file
+ * Test CSS file for Libraries loading.
+ *
+ * Color the 'libraries-test-css' div orange. See README.txt for more information.
+ */
+
+.libraries-test-css {
+  color: orange;
+}
diff --git a/web/modules/contrib/libraries/tests/example/example_3.js b/web/modules/contrib/libraries/tests/example/example_3.js
new file mode 100644 (file)
index 0000000..d6a3fa4
--- /dev/null
@@ -0,0 +1,18 @@
+
+/**
+ * @file
+ * Test JavaScript file for Libraries loading.
+ *
+ * Replace the text in the 'libraries-test-javascript' div. See README.txt for
+ * more information.
+ */
+
+(function ($) {
+
+Drupal.behaviors.librariesTest = {
+  attach: function(context, settings) {
+    $('.libraries-test-javascript').text('If this text shows up, example_3.js was loaded successfully.')
+  }
+};
+
+})(jQuery);
diff --git a/web/modules/contrib/libraries/tests/example/example_3.php b/web/modules/contrib/libraries/tests/example/example_3.php
new file mode 100644 (file)
index 0000000..cf74cf0
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+
+/**
+ * @file
+ * Test PHP file for Libraries loading.
+ */
+
+/**
+ * Dummy function to see if this file was loaded.
+ */
+function _libraries_test_example_3() {
+}
diff --git a/web/modules/contrib/libraries/tests/example/example_4.css b/web/modules/contrib/libraries/tests/example/example_4.css
new file mode 100644 (file)
index 0000000..5030a61
--- /dev/null
@@ -0,0 +1,11 @@
+
+/**
+ * @file
+ * Test CSS file for Libraries loading.
+ *
+ * Color the 'libraries-test-css' div blue. See README.txt for more information.
+ */
+
+.libraries-test-css {
+  color: blue;
+}
diff --git a/web/modules/contrib/libraries/tests/example/example_4.js b/web/modules/contrib/libraries/tests/example/example_4.js
new file mode 100644 (file)
index 0000000..ce5dc26
--- /dev/null
@@ -0,0 +1,18 @@
+
+/**
+ * @file
+ * Test JavaScript file for Libraries loading.
+ *
+ * Replace the text in the 'libraries-test-javascript' div. See README.txt for
+ * more information.
+ */
+
+(function ($) {
+
+Drupal.behaviors.librariesTest = {
+  attach: function(context, settings) {
+    $('.libraries-test-javascript').text('If this text shows up, example_4.js was loaded successfully.')
+  }
+};
+
+})(jQuery);
diff --git a/web/modules/contrib/libraries/tests/example/example_4.php b/web/modules/contrib/libraries/tests/example/example_4.php
new file mode 100644 (file)
index 0000000..b46507f
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+
+/**
+ * @file
+ * Test PHP file for Libraries loading.
+ */
+
+/**
+ * Dummy function to see if this file was loaded.
+ */
+function _libraries_test_example_4() {
+}
diff --git a/web/modules/contrib/libraries/tests/example/example_info_file.libraries.info.yml b/web/modules/contrib/libraries/tests/example/example_info_file.libraries.info.yml
new file mode 100644 (file)
index 0000000..b52e340
--- /dev/null
@@ -0,0 +1,2 @@
+# This is an example info file of a library used for testing purposes.
+name: Example info file
diff --git a/web/modules/contrib/libraries/tests/libraries/test_php_file_library/test_php_file_library.php b/web/modules/contrib/libraries/tests/libraries/test_php_file_library/test_php_file_library.php
new file mode 100644 (file)
index 0000000..8ef69fb
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * @file
+ * Test PHP file.
+ *
+ * This file is part of the 'test_php_file_library' test library.
+ *
+ * @see \Drupal\Tests\libraries\Kernel\ExternalLibrary\PhpFile\PhpFileLibraryTest
+ */
+
+/**
+ * A test function to be able to test whether this file was loaded or not.
+ */
+function _libraries_test_php_function() {
+}
diff --git a/web/modules/contrib/libraries/tests/library_definitions/test_asset_library.json b/web/modules/contrib/libraries/tests/library_definitions/test_asset_library.json
new file mode 100644 (file)
index 0000000..aca82a3
--- /dev/null
@@ -0,0 +1,18 @@
+{
+  "type": "asset",
+  "version_detector": {
+    "id": "static",
+    "configuration": {
+      "version": "1.0.0"
+    }
+  },
+  "remote_url": "http://example.com",
+  "css": {
+    "base": {
+      "example.css": {}
+    }
+  },
+  "js": {
+    "example.js": {}
+  }
+}
diff --git a/web/modules/contrib/libraries/tests/library_definitions/test_asset_multiple_library.json b/web/modules/contrib/libraries/tests/library_definitions/test_asset_multiple_library.json
new file mode 100644 (file)
index 0000000..8bcff3c
--- /dev/null
@@ -0,0 +1,32 @@
+{
+  "type": "asset_multiple",
+  "version_detector": {
+    "id": "static",
+    "configuration": {
+      "version": "1.0.0"
+    }
+  },
+  "remote_url": "http://example.com",
+  "libraries": {
+    "first": {
+      "css": {
+        "base": {
+          "example.first.css": {}
+        }
+      },
+      "js": {
+        "example.first.js": {}
+      }
+    },
+    "second": {
+      "css": {
+        "base": {
+          "example.second.css": {}
+        }
+      },
+      "js": {
+        "example.second.js": {}
+      }
+    }
+  }
+}
diff --git a/web/modules/contrib/libraries/tests/library_definitions/test_php_file_library.json b/web/modules/contrib/libraries/tests/library_definitions/test_php_file_library.json
new file mode 100644 (file)
index 0000000..61cb28c
--- /dev/null
@@ -0,0 +1,6 @@
+{
+  "type": "php_file",
+  "files": [
+    "test_php_file_library.php"
+  ]
+}
diff --git a/web/modules/contrib/libraries/tests/modules/libraries_test/libraries_test.css b/web/modules/contrib/libraries/tests/modules/libraries_test/libraries_test.css
new file mode 100644 (file)
index 0000000..3505281
--- /dev/null
@@ -0,0 +1,12 @@
+
+/**
+ * @file
+ * Test CSS file for Libraries loading.
+ *
+ * Color the 'libraries-test-css' div purple. See README.txt for more
+ * information.
+ */
+
+.libraries-test-css {
+  color: purple;
+}
diff --git a/web/modules/contrib/libraries/tests/modules/libraries_test/libraries_test.inc b/web/modules/contrib/libraries/tests/modules/libraries_test/libraries_test.inc
new file mode 100644 (file)
index 0000000..68e2d2e
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+
+/**
+ * @file
+ * Test PHP file for Libraries loading.
+ */
+
+/**
+ * Dummy function to see if this file was loaded.
+ */
+function _libraries_test_integration_file() {
+}
diff --git a/web/modules/contrib/libraries/tests/modules/libraries_test/libraries_test.info.yml b/web/modules/contrib/libraries/tests/modules/libraries_test/libraries_test.info.yml
new file mode 100644 (file)
index 0000000..fb26baa
--- /dev/null
@@ -0,0 +1,10 @@
+name: Libraries test module
+type: module
+description: Tests library detection and loading.
+core: 8.x
+dependencies:
+  - libraries
+hidden: TRUE
+library_dependencies:
+  - test_asset_library
+  - test_asset_multiple_library
diff --git a/web/modules/contrib/libraries/tests/modules/libraries_test/libraries_test.js b/web/modules/contrib/libraries/tests/modules/libraries_test/libraries_test.js
new file mode 100644 (file)
index 0000000..25ac3ec
--- /dev/null
@@ -0,0 +1,18 @@
+
+/**
+ * @file
+ * Test JavaScript file for Libraries loading.
+ *
+ * Replace the text in the 'libraries-test-javascript' div. See README.txt for
+ * more information.
+ */
+
+(function ($) {
+
+Drupal.behaviors.librariesTest = {
+  attach: function(context, settings) {
+    $('.libraries-test-javascript').text('If this text shows up, libraries_test.js was loaded successfully.')
+  }
+};
+
+})(jQuery);
diff --git a/web/modules/contrib/libraries/tests/modules/libraries_test/libraries_test.module b/web/modules/contrib/libraries/tests/modules/libraries_test/libraries_test.module
new file mode 100644 (file)
index 0000000..1a30ebc
--- /dev/null
@@ -0,0 +1,497 @@
+<?php
+
+/**
+ * @file
+ * Tests the library detection and loading.
+ */
+
+use Drupal\Component\Utility\SafeMarkup;
+
+/**
+ * Implements hook_libraries_info().
+ */
+function libraries_test_libraries_info() {
+  // Test library detection.
+  $libraries['example_missing'] = array(
+    'name' => 'Example missing',
+    'library path' => drupal_get_path('module', 'libraries') . '/tests/missing',
+  );
+  $libraries['example_undetected_version'] = array(
+    'name' => 'Example undetected version',
+    'library path' => drupal_get_path('module', 'libraries') . '/tests',
+    'version callback' => '_libraries_test_return_version',
+    'version arguments' => array(FALSE),
+  );
+  $libraries['example_unsupported_version'] = array(
+    'name' => 'Example unsupported version',
+    'library path' => drupal_get_path('module', 'libraries') . '/tests',
+    'version callback' => '_libraries_test_return_version',
+    'version arguments' => array('1'),
+    'versions' => array(
+      '2' => array(),
+    ),
+  );
+
+  $libraries['example_supported_version'] = array(
+    'name' => 'Example supported version',
+    'library path' => drupal_get_path('module', 'libraries') . '/tests',
+    'version callback' => '_libraries_test_return_version',
+    'version arguments' => array('1'),
+    'versions' => array(
+      '1' => array(),
+    ),
+  );
+
+  // Test the default version callback.
+  $libraries['example_default_version_callback'] = array(
+    'name' => 'Example default version callback',
+    'library path' => drupal_get_path('module', 'libraries') . '/tests/example',
+    'version arguments' => array(
+      'file' => 'README.txt',
+      // Version 1
+      'pattern' => '/Version (\d+)/',
+      'lines' => 5,
+    ),
+  );
+
+  // Test a multiple-parameter version callback.
+  $libraries['example_multiple_parameter_version_callback'] = array(
+    'name' => 'Example multiple parameter version callback',
+    'library path' => drupal_get_path('module', 'libraries') . '/tests/example',
+    // Version 1
+    'version callback' => '_libraries_test_get_version',
+    'version arguments' => array('README.txt', '/Version (\d+)/', 5),
+  );
+
+  // Test a top-level files property.
+  $libraries['example_files'] = array(
+    'name' => 'Example files',
+    'library path' => drupal_get_path('module', 'libraries') . '/tests/example',
+    'version' => '1',
+    'files' => array(
+      'js' => array('example_1.js'),
+      'css' => array('example_1.css'),
+      'php' => array('example_1.php'),
+    ),
+  );
+
+  // Test loading of integration files.
+  // Normally added by the corresponding module via hook_libraries_info_alter(),
+  // these files should be automatically loaded when the library is loaded.
+  $libraries['example_integration_files'] = array(
+    'name' => 'Example integration files',
+    'library path' => drupal_get_path('module', 'libraries') . '/tests/example',
+    'version' => '1',
+    'integration files' => array(
+      'libraries_test' => array(
+        'js' => array('libraries_test.js'),
+        'css' => array('libraries_test.css'),
+        'php' => array('libraries_test.inc'),
+      ),
+    ),
+  );
+
+  // Test version overloading.
+  $libraries['example_versions'] = array(
+    'name' => 'Example versions',
+    'library path' => drupal_get_path('module', 'libraries') . '/tests/example',
+    'version' => '2',
+    'versions' => array(
+      '1' => array(
+        'files' => array(
+          'js' => array('example_1.js'),
+          'css' => array('example_1.css'),
+          'php' => array('example_1.php'),
+        ),
+      ),
+      '2' => array(
+        'files' => array(
+          'js' => array('example_2.js'),
+          'css' => array('example_2.css'),
+          'php' => array('example_2.php'),
+        ),
+      ),
+    ),
+  );
+
+  // Test variant detection.
+  $libraries['example_variant_missing'] = array(
+    'name' => 'Example variant missing',
+    'library path' => drupal_get_path('module', 'libraries') . '/tests/example',
+    'version' => '1',
+    'variants' => array(
+      'example_variant' => array(
+        'files' => array(
+          'js' => array('example_3.js'),
+          'css' => array('example_3.css'),
+          'php' => array('example_3.php'),
+        ),
+        'variant callback' => '_libraries_test_return_installed',
+        'variant arguments' => array(FALSE),
+      ),
+    ),
+  );
+
+  $libraries['example_variant'] = array(
+    'name' => 'Example variant',
+    'library path' => drupal_get_path('module', 'libraries') . '/tests/example',
+    'version' => '1',
+    'variants' => array(
+      'example_variant' => array(
+        'files' => array(
+          'js' => array('example_3.js'),
+          'css' => array('example_3.css'),
+          'php' => array('example_3.php'),
+        ),
+        'variant callback' => '_libraries_test_return_installed',
+        'variant arguments' => array(TRUE),
+      ),
+    ),
+  );
+
+  // Test correct behaviour with multiple versions and multiple variants.
+  $libraries['example_versions_and_variants'] = array(
+    'name' => 'Example versions and variants',
+    'library path' => drupal_get_path('module', 'libraries') . '/tests/example',
+    'version' => '2',
+    'versions' => array(
+      '1' => array(
+        'variants' => array(
+          'example_variant_1' => array(
+            'files' => array(
+              'js' => array('example_1.js'),
+              'css' => array('example_1.css'),
+              'php' => array('example_1.php'),
+            ),
+            'variant callback' => '_libraries_test_return_installed',
+            'variant arguments' => array(TRUE),
+          ),
+          'example_variant_2' => array(
+            'files' => array(
+              'js' => array('example_2.js'),
+              'css' => array('example_2.css'),
+              'php' => array('example_2.php'),
+            ),
+            'variant callback' => '_libraries_test_return_installed',
+            'variant arguments' => array(TRUE),
+          ),
+        ),
+      ),
+      '2' => array(
+        'variants' => array(
+          'example_variant_1' => array(
+            'files' => array(
+              'js' => array('example_3.js'),
+              'css' => array('example_3.css'),
+              'php' => array('example_3.php'),
+            ),
+            'variant callback' => '_libraries_test_return_installed',
+            'variant arguments' => array(TRUE),
+          ),
+          'example_variant_2' => array(
+            'files' => array(
+              'js' => array('example_4.js'),
+              'css' => array('example_4.css'),
+              'php' => array('example_4.php'),
+            ),
+            'variant callback' => '_libraries_test_return_installed',
+            'variant arguments' => array(TRUE),
+          ),
+        ),
+      ),
+    ),
+  );
+
+  // Test dependency loading.
+  // We add one file to each library to be able to verify if it was loaded with
+  // libraries_load().
+  // This library acts as a dependency for the libraries below.
+  $libraries['example_dependency'] = array(
+    'name' => 'Example dependency',
+    'library path' => drupal_get_path('module', 'libraries') . '/tests/example',
+    'version' => '1.1',
+    'files' => array('js' => array('example_1.js')),
+  );
+  $libraries['example_dependency_missing'] = array(
+    'name' => 'Example dependency missing',
+    'library path' => drupal_get_path('module', 'libraries') . '/tests/example',
+    'version' => '1',
+    'dependencies' => array('example_missing'),
+    'files' => array('js' => array('example_1.js')),
+  );
+  $libraries['example_dependency_incompatible'] = array(
+    'name' => 'Example dependency incompatible',
+    'library path' => drupal_get_path('module', 'libraries') . '/tests/example',
+    'version' => '1',
+    'dependencies' => array('example_dependency (>1.1)'),
+    'files' => array('js' => array('example_1.js')),
+  );
+  $libraries['example_dependency_compatible'] = array(
+    'name' => 'Example dependency compatible',
+    'library path' => drupal_get_path('module', 'libraries') . '/tests/example',
+    'version' => '1',
+    'dependencies' => array('example_dependency (>=1.1)'),
+    'files' => array('js' => array('example_1.js')),
+  );
+
+  // Test the applying of callbacks.
+  $libraries['example_callback'] = array(
+    'name' => 'Example callback',
+    'library path' => drupal_get_path('module', 'libraries') . '/tests/example',
+    'version' => '1',
+    'versions' => array(
+      '1' => array(
+        'variants' => array(
+          'example_variant' => array(
+            // These keys are for testing purposes only.
+            'info callback' => 'not applied',
+            'pre-detect callback' => 'not applied',
+            'post-detect callback' => 'not applied',
+            'pre-load callback' => 'not applied',
+            'post-load callback' => 'not applied',
+          ),
+        ),
+        // These keys are for testing purposes only.
+        'info callback' => 'not applied',
+        'pre-detect callback' => 'not applied',
+        'post-detect callback' => 'not applied',
+        'pre-load callback' => 'not applied',
+        'post-load callback' => 'not applied',
+      ),
+    ),
+    'variants' => array(
+      'example_variant' => array(
+        // These keys are for testing purposes only.
+        'info callback' => 'not applied',
+        'pre-detect callback' => 'not applied',
+        'post-detect callback' => 'not applied',
+        'pre-load callback' => 'not applied',
+        'post-load callback' => 'not applied',
+      ),
+    ),
+    'callbacks' => array(
+      'info' => array('_libraries_test_info_callback'),
+      'pre-detect' => array('_libraries_test_pre_detect_callback'),
+      'post-detect' => array('_libraries_test_post_detect_callback'),
+      'pre-load' => array('_libraries_test_pre_load_callback'),
+      'post-load' => array('_libraries_test_post_load_callback'),
+    ),
+    // These keys are for testing purposes only.
+    'info callback' => 'not applied',
+    'pre-detect callback' => 'not applied',
+    'post-detect callback' => 'not applied',
+    'pre-load callback' => 'not applied',
+    'post-load callback' => 'not applied',
+  );
+
+  return $libraries;
+}
+
+/**
+ * Implements hook_libraries_info_file_paths()
+ */
+function libraries_test_libraries_info_file_paths() {
+  return array(drupal_get_path('module', 'libraries') . '/tests/example');
+}
+
+/**
+ * Gets the version of an example library.
+ *
+ * Returns exactly the version string entered as the $version parameter. This
+ * function cannot be collapsed with _libraries_test_return_installed(), because
+ * of the different arguments that are passed automatically.
+ */
+function _libraries_test_return_version($library, $version) {
+  return $version;
+}
+
+/**
+ * Gets the version information from an arbitrary library.
+ *
+ * Test function for a version callback with multiple arguments. This is an
+ * exact copy of libraries_get_version(), which uses a single $option argument,
+ * except for the fact that it uses multiple arguments. Since we support both
+ * type of version callbacks, detecting the version of a test library with this
+ * function ensures that the arguments are passed correctly. This function might
+ * be a useful reference for a custom version callback that uses multiple
+ * parameters.
+ *
+ * @param $library
+ *   An associative array containing all information about the library.
+ * @param $file
+ *   The filename to parse for the version, relative to the library path. For
+ *   example: 'docs/changelog.txt'.
+ * @param pattern
+ *   A string containing a regular expression (PCRE) to match the library
+ *   version. For example: '/@version (\d+)\.(\d+)/'.
+ * @param lines
+ *   (optional) The maximum number of lines to search the pattern in. Defaults
+ *   to 20.
+ * @param cols
+ *   (optional) The maximum number of characters per line to take into account.
+ *   Defaults to 200. In case of minified or compressed files, this prevents
+ *   reading the entire file into memory.
+ *
+ * @return
+ *   A string containing the version of the library.
+ *
+ * @see libraries_get_version()
+ */
+function _libraries_test_get_version($library, $file, $pattern, $lines = 20, $cols = 200) {
+
+  $file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $file;
+  if (!file_exists($file)) {
+    return;
+  }
+  $file = fopen($file, 'r');
+  while ($lines && $line = fgets($file, $cols)) {
+    if (preg_match($pattern, $line, $version)) {
+      fclose($file);
+      return $version[1];
+    }
+    $lines--;
+  }
+  fclose($file);
+}
+
+/**
+ * Detects the variant of an example library.
+ *
+ * Returns exactly the value of $installed, either TRUE or FALSE. This function
+ * cannot be collapsed with _libraries_test_return_version(), because of the
+ * different arguments that are passed automatically.
+ */
+function _libraries_test_return_installed($library, $name, $installed) {
+  return $installed;
+}
+
+/**
+ * Sets the 'info callback' key.
+ *
+ * This function is used as a test callback for the 'info' callback group.
+ *
+ * @see _libraries_test_callback()
+ */
+function _libraries_test_info_callback(&$library, $version, $variant) {
+  _libraries_test_callback($library, $version, $variant, 'info');
+}
+
+/**
+ * Sets the 'pre-detect callback' key.
+ *
+ * This function is used as a test callback for the 'pre-detect' callback group.
+ *
+ * @see _libraries_test_callback()
+ */
+function _libraries_test_pre_detect_callback(&$library, $version, $variant) {
+  _libraries_test_callback($library, $version, $variant, 'pre-detect');
+}
+
+/**
+ * Sets the 'post-detect callback' key.
+ *
+ * This function is used as a test callback for the 'post-detect callback group.
+ *
+ * @see _libraries_test_callback()
+ */
+function _libraries_test_post_detect_callback(&$library, $version, $variant) {
+  _libraries_test_callback($library, $version, $variant, 'post-detect');
+}
+
+/**
+ * Sets the 'pre-load callback' key.
+ *
+ * This function is used as a test callback for the 'pre-load' callback group.
+ *
+ * @see _libraries_test_callback()
+ */
+function _libraries_test_pre_load_callback(&$library, $version, $variant) {
+  _libraries_test_callback($library, $version, $variant, 'pre-load');
+}
+
+/**
+ * Sets the 'post-load callback' key.
+ *
+ * This function is used as a test callback for the 'post-load' callback group.
+ *
+ * @see _libraries_test_callback()
+ */
+function _libraries_test_post_load_callback(&$library, $version, $variant) {
+  _libraries_test_callback($library, $version, $variant, 'post-load');
+}
+
+/**
+ * Sets the '[group] callback' key, where [group] is prepare, detect, or load.
+ *
+ * This function is used as a test callback for the all callback groups.
+ *
+ * It sets the '[group] callback' (see above) key to 'applied ([part])' where
+ * [part] is either 'top-level', 'version x.y' (where x.y is the passed-in
+ * version string), 'variant example' (where example is the passed-in variant
+ * name), or 'version x.y, variant example' (see above), depending on the part
+ * of the library the passed-in library information belongs to.
+ *
+ * @param $library
+ *   An array of library information, which may be version- or variant-specific.
+ *   Passed by reference.
+ * @param $version
+ *   The version the library information passed in $library belongs to, or NULL
+ *   if the passed library information is not version-specific.
+ * @param $variant
+ *   The variant the library information passed in $library belongs to, or NULL
+ *   if the passed library information is not variant-specific.
+ */
+function _libraries_test_callback(&$library, $version, $variant, $group) {
+  $string = 'applied';
+  if (isset($version) && isset($variant)) {
+    $string .= " (version $version, variant $variant)";
+  }
+  elseif (isset($version)) {
+    $string .= " (version $version)";
+  }
+  elseif (isset($variant)) {
+    $string .= " (variant $variant)";
+  }
+  else {
+    $string .= ' (top-level)';
+  }
+  $library["$group callback"] = $string;
+
+  // The following is used to test caching of library information.
+  // Only set the message for the top-level library to prevent confusing,
+  // duplicate messages.
+  if (!isset($version) && !isset($variant) && \Drupal::state()->get('libraries_test.cache', FALSE)) {
+    drupal_set_message(SafeMarkup::set("The <em>$group</em> callback group was invoked."));
+  }
+}
+
+/**
+ * Implements hook_menu().
+ */
+function libraries_test_menu() {
+  $items['libraries_test/files'] = array(
+    'title' => 'Test files',
+    'route_name' => 'libraries_test_files',
+  );
+  $items['libraries_test/integration_files'] = array(
+    'title' => 'Test integration files',
+    'route_name' => 'libraries_test_integration_files',
+  );
+  $items['libraries_test/versions'] = array(
+    'title' => 'Test version loading',
+    'route_name' => 'libraries_test_versions',
+  );
+  $items['libraries_test/variant'] = array(
+    'title' => 'Test variant loading',
+    'route_name' => 'libraries_test_variant',
+  );
+  $items['libraries_test/versions_and_variants'] = array(
+    'title' => 'Test concurrent version and variant loading',
+    'route_name' => 'libraries_test_versions_and_variants',
+  );
+  $items['libraries_test/cache'] = array(
+    'title' => 'Test caching of library information',
+    'route_name' => 'libraries_test_cache',
+  );
+  return $items;
+}
diff --git a/web/modules/contrib/libraries/tests/modules/libraries_test/libraries_test.routing.yml b/web/modules/contrib/libraries/tests/modules/libraries_test/libraries_test.routing.yml
new file mode 100644 (file)
index 0000000..a11f0be
--- /dev/null
@@ -0,0 +1,36 @@
+libraries_test_files:
+  path: '/libraries_test/files'
+  defaults:
+    _controller: Drupal\libraries_test\Controller\ExampleController::files
+  requirements:
+    _access: 'TRUE'
+libraries_test_integration_files:
+  path: '/libraries_test/integration_files'
+  defaults:
+    _controller: Drupal\libraries_test\Controller\ExampleController::integration
+  requirements:
+    _access: 'TRUE'
+libraries_test_versions:
+  path: '/libraries_test/versions'
+  defaults:
+    _controller: Drupal\libraries_test\Controller\ExampleController::versions
+  requirements:
+    _access: 'TRUE'
+libraries_test_variant:
+  path: '/libraries_test/variant'
+  defaults:
+    _controller: Drupal\libraries_test\Controller\ExampleController::variant
+  requirements:
+    _access: 'TRUE'
+libraries_test_versions_and_variants:
+  path: '/libraries_test/versions_and_variants'
+  defaults:
+    _controller: Drupal\libraries_test\Controller\ExampleController::versionsAndVariants
+  requirements:
+    _access: 'TRUE'
+libraries_test_cache:
+  path: '/libraries_test/cache'
+  defaults:
+    _controller: Drupal\libraries_test\Controller\ExampleController::cache
+  requirements:
+    _access: 'TRUE'
diff --git a/web/modules/contrib/libraries/tests/modules/libraries_test/src/Controller/ExampleController.php b/web/modules/contrib/libraries/tests/modules/libraries_test/src/Controller/ExampleController.php
new file mode 100644 (file)
index 0000000..217ebf2
--- /dev/null
@@ -0,0 +1,94 @@
+<?php
+
+namespace Drupal\libraries_test\Controller;
+
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+class ExampleController implements ContainerInjectionInterface {
+
+  /**
+   * Injects BookManager Service.
+   */
+  public static function create(ContainerInterface $container) {
+    return new static();
+  }
+
+  /**
+   * Loads a specified library (variant) for testing.
+   *
+   * JavaScript and CSS files can be checked directly by SimpleTest, so we only
+   * need to manually check for PHP files. We provide information about the loaded
+   * JavaScript and CSS files for easier debugging. See example/README.txt for
+   * more information.
+   */
+  private function buildPage($library, $variant = NULL) {
+    libraries_load($library, $variant);
+    // JavaScript and CSS files can be checked directly by SimpleTest, so we only
+    // need to manually check for PHP files.
+    $output = '';
+
+    // For easer debugging of JS loading, a text is shown that the JavaScript will
+    // replace.
+    $output .= '<h2>JavaScript</h2>';
+    $output .= '<div class="libraries-test-javascript">';
+    $output .= 'If this text shows up, no JavaScript test file was loaded.';
+    $output .= '</div>';
+
+    // For easier debugging of CSS loading, the loaded CSS files will color the
+    // following text.
+    $output .= '<h2>CSS</h2>';
+    $output .= '<div class="libraries-test-css">';
+    $output .= 'If one of the CSS test files has been loaded, this text will be colored:';
+    $output .= '<ul>';
+    // Do not reference the actual CSS files (i.e. including '.css'), because that
+    // breaks testing.
+    $output .= '<li>example_1: red</li>';
+    $output .= '<li>example_2: green</li>';
+    $output .= '<li>example_3: orange</li>';
+    $output .= '<li>example_4: blue</li>';
+    $output .= '<li>libraries_test: purple</li>';
+    $output .= '</ul>';
+    $output .= '</div>';
+
+    $output .= '<h2>PHP</h2>';
+    $output .= '<div class="libraries-test-php">';
+    $output .= 'The following is a list of all loaded test PHP files:';
+    $output .= '<ul>';
+    $files = get_included_files();
+    foreach ($files as $file) {
+      if ((strpos($file, 'libraries/test') || strpos($file, 'libraries_test')) && !strpos($file, 'libraries_test.module') && !strpos($file, 'lib/Drupal/libraries_test')) {
+        $output .= '<li>' . str_replace(DRUPAL_ROOT . '/', '', $file) . '</li>';
+      }
+    }
+    $output .= '</ul>';
+    $output .= '</div>';
+
+    return ['#markup' => $output];
+  }
+
+  public function files() {
+    return $this->buildPage('example_files');
+  }
+
+  public function integration() {
+    return $this->buildPage('example_integration_files');
+  }
+
+  public function versions() {
+    return $this->buildPage('example_versions');
+  }
+
+  public function variant() {
+    return $this->buildPage('example_variant', 'example_variant');
+  }
+
+  public function versionsAndVariants() {
+    return $this->buildPage('example_versions_and_variants', 'example_variant_2');
+  }
+
+  public function cache() {
+    return $this->buildPage('example_callback');
+  }
+
+}
diff --git a/web/modules/contrib/libraries/tests/src/Functional/ExternalLibrary/Definition/DefinitionDiscoveryFactoryTest.php b/web/modules/contrib/libraries/tests/src/Functional/ExternalLibrary/Definition/DefinitionDiscoveryFactoryTest.php
new file mode 100644 (file)
index 0000000..14bf74e
--- /dev/null
@@ -0,0 +1,118 @@
+<?php
+
+namespace Drupal\Tests\libraries\Functional\ExternalLibrary\Definition;
+
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests that remote library definitions are found and downloaded.
+ *
+ * This is a browser test because Guzzle is not usable from a kernel test.
+ *
+ * @group libraries
+ *
+ * @todo Make this a kernel test when https://www.drupal.org/node/2571475 is in.
+ */
+class DefinitionDiscoveryFactoryTest extends BrowserTestBase {
+
+  /**
+   * The 'libraries.settings' configuration object.
+   *
+   * @var \Drupal\Core\Config\Config
+   */
+  protected $config;
+
+  /**
+   * The path to the test library definitions.
+   *
+   * @var string
+   */
+  protected $definitionPath;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['libraries'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    /** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */
+    $config_factory = $this->container->get('config.factory');
+    $this->config = $config_factory->getEditable('libraries.settings');
+
+    // Set up the remote library definition URL to point to the local website.
+    /** @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler */
+    $module_handler = $this->container->get('module_handler');
+    $module_path = $module_handler->getModule('libraries')->getPath();
+    $this->definitionPath = "$module_path/tests/library_definitions";
+  }
+
+  /**
+   * Tests that the discovery works according to the configuration.
+   */
+  public function testDiscovery() {
+    $library_id = 'test_asset_library';
+    $expected_definition = [
+      'type' => 'asset',
+      'version_detector' => [
+        'id' => 'static',
+        'configuration' => [
+          'version' => '1.0.0'
+        ],
+      ],
+      'remote_url' => 'http://example.com',
+      'css' => [
+        'base' => [
+          'example.css' => [],
+        ],
+      ],
+      'js' => [
+        'example.js' => [],
+      ],
+    ];
+    $discovery_service_id = 'libraries.definition.discovery';
+
+    // Test the local discovery with an incorrect path.
+    $this->config
+      ->set('definition.local.path', 'path/that/does/not/exist')
+      ->set('definition.remote.enable', FALSE)
+      ->save();
+    $discovery = $this->container->get($discovery_service_id);
+    $this->assertFalse($discovery->hasDefinition($library_id));
+
+    // Test the local discovery with a proper path.
+    $this->config
+      ->set('definition.local.path', $this->definitionPath)
+      ->save();
+    $discovery = $this->container->get($discovery_service_id);
+    $this->assertTrue($discovery->hasDefinition($library_id));
+
+    // Test a remote discovery with an incorrect path.
+    $definitions_directory = 'public://library-definitions';
+    $this->config
+      ->set('definition.local.path', $definitions_directory)
+      ->set('definition.remote.enable', TRUE)
+      ->set('definition.remote.urls', ["$this->baseUrl/path/that/does/not/exist"])
+      ->save();
+    $discovery = $this->container->get($discovery_service_id);
+    $this->assertFalse($discovery->hasDefinition($library_id));
+
+    // Test a remote discovery with a proper path.
+    $this->config
+      ->set('definition.remote.urls', ["$this->baseUrl/$this->definitionPath"])
+      ->save();
+    /** @var \Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryInterface $discovery */
+    $discovery = $this->container->get($discovery_service_id);
+    $definition_file = "$definitions_directory/$library_id.json";
+    $this->assertFalse(file_exists($definition_file));
+    $this->assertTrue($discovery->hasDefinition($library_id));
+    $this->assertFalse(file_exists($definition_file));
+    $this->assertEquals($discovery->getDefinition($library_id), $expected_definition);
+    $this->assertTrue(file_exists($definition_file));
+  }
+
+}
diff --git a/web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTest.php b/web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTest.php
new file mode 100644 (file)
index 0000000..cb2551e
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+
+namespace Drupal\Tests\libraries\Kernel\ExternalLibrary\Asset;
+
+use Drupal\Tests\libraries\Kernel\ExternalLibrary\TestLibraryFilesStream;
+
+/**
+ * Tests that external asset libraries are registered as core asset libraries.
+ *
+ * @group libraries
+ */
+class AssetLibraryTest extends AssetLibraryTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getLibraryTypeId() {
+    return 'asset';
+  }
+
+  /**
+   * Tests that attachable asset library info is correctly gathered.
+   */
+  public function testAttachableAssetInfo() {
+    /** @var \Drupal\libraries\ExternalLibrary\Asset\AttachableAssetLibraryRegistrationInterface $library_type */
+    $library_type = $this->getLibraryType();
+    $library = $this->getLibrary();
+    $expected = [
+      'test_asset_library' => [
+        'version' => '1.0.0',
+        'css' => ['base' => ['http://example.com/example.css' => []]],
+        'js' => ['http://example.com/example.js' => []],
+        'dependencies' => [],
+      ],
+    ];
+    $this->assertEquals($expected, $library_type->getAttachableAssetLibraries($library, $this->libraryManager));
+  }
+
+  /**
+   * Tests that a remote asset library is registered as a core asset library.
+   *
+   * @see \Drupal\libraries\Extension\Extension
+   * @see \Drupal\libraries\Extension\ExtensionHandler
+   * @see \Drupal\libraries\ExternalLibrary\Asset\AssetLibrary
+   * @see \Drupal\libraries\ExternalLibrary\Asset\AssetLibraryTrait
+   * @see \Drupal\libraries\ExternalLibrary\ExternalLibraryManager
+   * @see \Drupal\libraries\ExternalLibrary\ExternalLibraryTrait
+   * @see \Drupal\libraries\ExternalLibrary\Registry\ExternalLibraryRegistry
+   */
+  public function testAssetLibraryRemote() {
+    $library = $this->coreLibraryDiscovery->getLibraryByName('libraries', 'test_asset_library');
+    $expected = [
+      'version' => '1.0.0',
+      'css' => [[
+        'weight' => -200,
+        'group' => 0,
+        'type' => 'external',
+        'data' => 'http://example.com/example.css',
+        'version' => '1.0.0',
+      ]],
+      'js' => [[
+        'group' => -100,
+        'type' => 'external',
+        'data' => 'http://example.com/example.js',
+        'version' => '1.0.0',
+      ]],
+      'dependencies' => [],
+      'license' => [
+        'name' => 'GNU-GPL-2.0-or-later',
+        'url' => 'https://www.drupal.org/licensing/faq',
+        'gpl-compatible' => TRUE,
+      ]
+    ];
+    $this->assertEquals($expected, $library);
+  }
+
+  /**
+   * Tests that a local asset library is registered as a core asset library.
+   */
+  public function testAssetLibraryLocal() {
+    $this->container->set('stream_wrapper.asset_libraries', new TestLibraryFilesStream(
+      $this->container->get('module_handler'),
+      $this->container->get('string_translation'),
+      'assets/vendor'
+    ));
+    $this->coreLibraryDiscovery->clearCachedDefinitions();
+    $library = $this->coreLibraryDiscovery->getLibraryByName('libraries', 'test_asset_library');
+    $expected = [
+      'version' => '1.0.0',
+      'css' => [[
+        'weight' => -200,
+        'group' => 0,
+        'type' => 'file',
+        'data' => $this->modulePath . '/tests/assets/vendor/test_asset_library/example.css',
+        'version' => '1.0.0',
+      ]],
+      'js' => [[
+        'group' => -100,
+        'type' => 'file',
+        'data' => $this->modulePath . '/tests/assets/vendor/test_asset_library/example.js',
+        'version' => '1.0.0',
+        'minified' => FALSE,
+      ]],
+      'dependencies' => [],
+      'license' => [
+        'name' => 'GNU-GPL-2.0-or-later',
+        'url' => 'https://www.drupal.org/licensing/faq',
+        'gpl-compatible' => TRUE,
+      ]
+    ];
+    $this->assertEquals($expected, $library);
+  }
+
+}
diff --git a/web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTestBase.php b/web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTestBase.php
new file mode 100644 (file)
index 0000000..4f835b7
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+
+namespace Drupal\Tests\libraries\Kernel\ExternalLibrary\Asset;
+
+use Drupal\Tests\libraries\Kernel\LibraryTypeKernelTestBase;
+
+/**
+ * Provides a base test class for asset library type tests.
+ */
+abstract class AssetLibraryTestBase extends LibraryTypeKernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   *
+   * LibraryManager requires system_get_info() which is in system.module.
+   *
+   * @see \Drupal\libraries\ExternalLibrary\LibraryManager::getRequiredLibraryIds()
+   */
+  public static $modules = ['system'];
+
+  /**
+   * The Drupal core library discovery.
+   *
+   * @var \Drupal\Core\Asset\LibraryDiscoveryInterface
+   */
+  protected $coreLibraryDiscovery;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->coreLibraryDiscovery = $this->container->get('library.discovery');
+  }
+
+}
diff --git a/web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/Asset/MultipleAssetLibraryTest.php b/web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/Asset/MultipleAssetLibraryTest.php
new file mode 100644 (file)
index 0000000..4a2af5c
--- /dev/null
@@ -0,0 +1,172 @@
+<?php
+
+namespace Drupal\Tests\libraries\Kernel\ExternalLibrary\Asset;
+
+use Drupal\Tests\libraries\Kernel\ExternalLibrary\TestLibraryFilesStream;
+
+/**
+ * Tests that external asset libraries can register multiple core libraries.
+ *
+ * @group libraries
+ */
+class MultipleAssetLibraryTest extends AssetLibraryTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getLibraryTypeId() {
+    return 'asset_multiple';
+  }
+
+  /**
+   * Tests that attachable asset library info is correctly gathered.
+   */
+  public function testAttachableAssetInfo() {
+    /** @var \Drupal\libraries\ExternalLibrary\Asset\AttachableAssetLibraryRegistrationInterface $library_type */
+    $library_type = $this->getLibraryType();
+    $library = $this->getLibrary();
+    $expected = [
+      'test_asset_multiple_library.first' => [
+        'version' => '1.0.0',
+        'css' => ['base' => ['http://example.com/example.first.css' => []]],
+        'js' => ['http://example.com/example.first.js' => []],
+        'dependencies' => [],
+      ],
+      'test_asset_multiple_library.second' => [
+        'version' => '1.0.0',
+        'css' => ['base' => ['http://example.com/example.second.css' => []]],
+        'js' => ['http://example.com/example.second.js' => []],
+        'dependencies' => [],
+      ],
+    ];
+    $this->assertEquals($expected, $library_type->getAttachableAssetLibraries($library, $this->libraryManager));
+  }
+
+  /**
+   * Tests that a remote asset library is registered as a core asset library.
+   *
+   * @see \Drupal\libraries\Extension\Extension
+   * @see \Drupal\libraries\Extension\ExtensionHandler
+   * @see \Drupal\libraries\ExternalLibrary\Asset\AssetLibrary
+   * @see \Drupal\libraries\ExternalLibrary\Asset\AssetLibraryTrait
+   * @see \Drupal\libraries\ExternalLibrary\ExternalLibraryManager
+   * @see \Drupal\libraries\ExternalLibrary\ExternalLibraryTrait
+   * @see \Drupal\libraries\ExternalLibrary\Registry\ExternalLibraryRegistry
+   */
+  public function testAssetLibraryRemote() {
+    $library = $this->coreLibraryDiscovery->getLibraryByName('libraries', 'test_asset_multiple_library.first');
+    $expected = [
+      'version' => '1.0.0',
+      'css' => [[
+        'weight' => -200,
+        'group' => 0,
+        'type' => 'external',
+        'data' => 'http://example.com/example.first.css',
+        'version' => '1.0.0',
+      ]],
+      'js' => [[
+        'group' => -100,
+        'type' => 'external',
+        'data' => 'http://example.com/example.first.js',
+        'version' => '1.0.0',
+      ]],
+      'dependencies' => [],
+      'license' => [
+        'name' => 'GNU-GPL-2.0-or-later',
+        'url' => 'https://www.drupal.org/licensing/faq',
+        'gpl-compatible' => TRUE,
+      ]
+    ];
+    $this->assertEquals($expected, $library);
+
+    $library = $this->coreLibraryDiscovery->getLibraryByName('libraries', 'test_asset_multiple_library.second');
+    $expected = [
+      'version' => '1.0.0',
+      'css' => [[
+        'weight' => -200,
+        'group' => 0,
+        'type' => 'external',
+        'data' => 'http://example.com/example.second.css',
+        'version' => '1.0.0',
+      ]],
+      'js' => [[
+        'group' => -100,
+        'type' => 'external',
+        'data' => 'http://example.com/example.second.js',
+        'version' => '1.0.0',
+      ]],
+      'dependencies' => [],
+      'license' => [
+        'name' => 'GNU-GPL-2.0-or-later',
+        'url' => 'https://www.drupal.org/licensing/faq',
+        'gpl-compatible' => TRUE,
+      ]
+    ];
+    $this->assertEquals($expected, $library);
+  }
+
+  /**
+   * Tests that a local asset library is registered as a core asset library.
+   */
+  public function testAssetLibraryLocal() {
+    $this->container->set('stream_wrapper.asset_libraries', new TestLibraryFilesStream(
+      $this->container->get('module_handler'),
+      $this->container->get('string_translation'),
+      'assets/vendor'
+    ));
+    $this->coreLibraryDiscovery->clearCachedDefinitions();
+
+    $library = $this->coreLibraryDiscovery->getLibraryByName('libraries', 'test_asset_multiple_library.first');
+    $expected = [
+      'version' => '1.0.0',
+      'css' => [[
+        'weight' => -200,
+        'group' => 0,
+        'type' => 'file',
+        'data' => $this->modulePath . '/tests/assets/vendor/test_asset_multiple_library/example.first.css',
+        'version' => '1.0.0',
+      ]],
+      'js' => [[
+        'group' => -100,
+        'type' => 'file',
+        'data' => $this->modulePath . '/tests/assets/vendor/test_asset_multiple_library/example.first.js',
+        'version' => '1.0.0',
+        'minified' => FALSE,
+      ]],
+      'dependencies' => [],
+      'license' => [
+        'name' => 'GNU-GPL-2.0-or-later',
+        'url' => 'https://www.drupal.org/licensing/faq',
+        'gpl-compatible' => TRUE,
+      ]
+    ];
+    $this->assertEquals($expected, $library);
+
+    $library = $this->coreLibraryDiscovery->getLibraryByName('libraries', 'test_asset_multiple_library.second');
+    $expected = [
+      'version' => '1.0.0',
+      'css' => [[
+        'weight' => -200,
+        'group' => 0,
+        'type' => 'file',
+        'data' => $this->modulePath . '/tests/assets/vendor/test_asset_multiple_library/example.second.css',
+        'version' => '1.0.0',
+      ]],
+      'js' => [[
+        'group' => -100,
+        'type' => 'file',
+        'data' => $this->modulePath . '/tests/assets/vendor/test_asset_multiple_library/example.second.js',
+        'version' => '1.0.0',
+        'minified' => FALSE,
+      ]],
+      'dependencies' => [],
+      'license' => [
+        'name' => 'GNU-GPL-2.0-or-later',
+        'url' => 'https://www.drupal.org/licensing/faq',
+        'gpl-compatible' => TRUE,
+      ]
+    ];
+    $this->assertEquals($expected, $library);
+  }
+
+}
diff --git a/web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/GlobalLocatorTest.php b/web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/GlobalLocatorTest.php
new file mode 100644 (file)
index 0000000..84eb86e
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+
+namespace Drupal\Tests\libraries\Kernel\ExternalLibrary;
+
+use Drupal\Tests\libraries\Kernel\ExternalLibrary\TestLibraryFilesStream;
+use Drupal\Tests\libraries\Kernel\LibraryTypeKernelTestBase;
+
+/**
+ * Tests that a global locator can be properly used to load a libraries.
+ *
+ * @group libraries
+ */
+class GlobalLocatorTest extends LibraryTypeKernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    // Assign our test stream (which points to the test php lib) to the asset
+    // scheme. This gives us a scheme to work with in the test that is not
+    // used to locate a php lib by default.
+    $this->container->set('stream_wrapper.asset_libraries', new TestLibraryFilesStream(
+      $this->container->get('module_handler'),
+      $this->container->get('string_translation'),
+      'libraries'
+    ));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getLibraryTypeId() {
+    return 'php_file';
+  }
+
+  /**
+   * Tests that the library is located via the global loactor.
+   */
+  public function testGlobalLocator() {
+    // By default the library will not be locatable (control assertion) until we
+    // add the asset stream to the global loctors conf list.
+    $library = $this->getLibrary();
+    $this->assertFalse($library->isInstalled());
+    $config_factory = $this->container->get('config.factory');
+    $config_factory->getEditable('libraries.settings')
+      ->set('global_locators', [['id' => 'uri', 'configuration' => ['uri' => 'asset://']]])
+      ->save();
+    $library = $this->getLibrary();
+    $this->assertTrue($library->isInstalled());
+  }
+
+}
diff --git a/web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/PhpFile/PhpFileLibraryTest.php b/web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/PhpFile/PhpFileLibraryTest.php
new file mode 100644 (file)
index 0000000..10c1d2c
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+
+namespace Drupal\Tests\libraries\Kernel\ExternalLibrary\PhpFile;
+
+use Drupal\Tests\libraries\Kernel\ExternalLibrary\TestLibraryFilesStream;
+use Drupal\Tests\libraries\Kernel\LibraryTypeKernelTestBase;
+
+/**
+ * Tests that the external library manager properly loads PHP file libraries.
+ *
+ * @group libraries
+ */
+class PhpFileLibraryTest extends LibraryTypeKernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->container->set('stream_wrapper.php_file_libraries', new TestLibraryFilesStream(
+      $this->container->get('module_handler'),
+      $this->container->get('string_translation'),
+      'libraries'
+    ));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getLibraryTypeId() {
+    return 'php_file';
+  }
+
+  /**
+   * Tests that the list of PHP files is correctly gathered.
+   */
+  public function testPhpFileInfo() {
+    /** @var \Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLibrary $library */
+    $library = $this->getLibrary();
+    $this->assertTrue($library->isInstalled());
+    $library_path = $this->modulePath . '/tests/libraries/test_php_file_library';
+    $this->assertEquals($library_path, $library->getLocalPath());
+    $this->assertEquals(["$library_path/test_php_file_library.php"], $library->getPhpFiles());
+  }
+
+  /**
+   * Tests that the external library manager properly loads PHP files.
+   *
+   * @see \Drupal\libraries\ExternalLibrary\ExternalLibraryManager
+   * @see \Drupal\libraries\ExternalLibrary\ExternalLibraryTrait
+   * @see \Drupal\libraries\ExternalLibrary\PhpFile\PhpRequireLoader
+   */
+  public function testFileLoading() {
+    $function_name = '_libraries_test_php_function';
+    if (function_exists($function_name)) {
+      $this->markTestSkipped('Cannot test file inclusion if the file to be included has already been included prior.');
+      return;
+    }
+
+    $this->assertFalse(function_exists($function_name));
+    $this->libraryManager->load('test_php_file_library');
+    $this->assertTrue(function_exists($function_name));
+  }
+
+}
diff --git a/web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/TestLibraryFilesStream.php b/web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/TestLibraryFilesStream.php
new file mode 100644 (file)
index 0000000..ec20647
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+
+namespace Drupal\Tests\libraries\Kernel\ExternalLibrary;
+
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\StreamWrapper\LocalStream;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\libraries\StreamWrapper\LocalHiddenStreamTrait;
+use Drupal\libraries\StreamWrapper\PrivateStreamTrait;
+
+/**
+ * Provides a stream wrapper for accessing test library files.
+ */
+class TestLibraryFilesStream extends LocalStream {
+
+  use LocalHiddenStreamTrait;
+  use PrivateStreamTrait;
+  use StringTranslationTrait;
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The test directory.
+   *
+   * @var string
+   */
+  protected $directory;
+
+  /**
+   * Constructs a stream wrapper for test library files.
+   *
+   * Dependency injection is generally not possible to implement for stream
+   * wrappers, because stream wrappers are initialized before the container is
+   * booted, but this stream wrapper is only registered explicitly from tests
+   * so it is possible here.
+   *
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
+   *   The string translation handler.
+   * @param string $directory
+   *   The directory within the Libraries API's tests directory that is to be
+   *   searched for test library files.
+   */
+  public function __construct(ModuleHandlerInterface $module_handler, TranslationInterface $string_translation, $directory) {
+    $this->moduleHandler = $module_handler;
+    $this->directory = (string) $directory;
+
+    $this->setStringTranslation($string_translation);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName() {
+    $this->t('Test library files');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    $this->t('Provides access to test library files.');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDirectoryPath() {
+    $module_path = $this->moduleHandler->getModule('libraries')->getPath();
+    return $module_path . '/tests/' . $this->directory;
+  }
+
+}
diff --git a/web/modules/contrib/libraries/tests/src/Kernel/LibraryTypeKernelTestBase.php b/web/modules/contrib/libraries/tests/src/Kernel/LibraryTypeKernelTestBase.php
new file mode 100644 (file)
index 0000000..8185794
--- /dev/null
@@ -0,0 +1,171 @@
+<?php
+
+namespace Drupal\Tests\libraries\Kernel;
+
+use Drupal\Component\Plugin\Exception\PluginException;
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException;
+use Drupal\libraries\ExternalLibrary\Exception\LibraryTypeNotFoundException;
+use Drupal\libraries\ExternalLibrary\LibraryInterface;
+use Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface;
+
+/**
+ * Provides an improved version of the core kernel test base class.
+ */
+abstract class LibraryTypeKernelTestBase extends KernelTestBase {
+
+  /**
+   * The external library manager.
+   *
+   * @var \Drupal\libraries\ExternalLibrary\LibraryManagerInterface
+   */
+  protected $libraryManager;
+
+  /**
+   * The library type factory.
+   *
+   * @var \Drupal\Component\Plugin\Factory\FactoryInterface
+   */
+  protected $libraryTypeFactory;
+
+  /**
+   * The absolute path to the Libraries API module.
+   *
+   * @var string
+   */
+  protected $modulePath;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['libraries', 'libraries_test'];
+
+  /**
+   * Gets the ID of the library type that is being tested.
+   *
+   * @return string
+   */
+  abstract protected function getLibraryTypeId();
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    /** @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler */
+    $module_handler = $this->container->get('module_handler');
+    $this->modulePath = $module_handler->getModule('libraries')->getPath();
+
+    $this->installConfig('libraries');
+    // Disable remote definition fetching and set the local definitions path to
+    // the module directory.
+    /** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */
+    $config_factory = $this->container->get('config.factory');
+    $config_factory->getEditable('libraries.settings')
+      ->set('definition.local.path', "{$this->modulePath}/tests/library_definitions")
+      ->set('definition.remote.enable', FALSE)
+      ->save();
+
+    // LibrariesConfigSubscriber::onConfigSave() invalidates the container so
+    // that it is rebuilt on the next request. We need the container rebuilt
+    // immediately, however.
+    /** @var \Drupal\Core\DrupalKernelInterface $kernel */
+    $kernel = $this->container->get('kernel');
+    $this->container = $kernel->rebuildContainer();
+
+    $this->libraryManager = $this->container->get('libraries.manager');
+    $this->libraryTypeFactory = $this->container->get('plugin.manager.libraries.library_type');
+  }
+
+  /**
+   * Tests that the library type can be instantiated.
+   */
+  public function testLibraryType() {
+    $type_id = $this->getLibraryTypeId();
+    try {
+      $this->libraryTypeFactory->createInstance($type_id);
+      $this->assertTrue(TRUE, "Library type '$type_id' can be instantiated.");
+    }
+    catch (PluginException $exception) {
+      $this->fail("Library type '$type_id' cannot be instantiated.");
+    }
+  }
+
+  /**
+   * Tests that the test library can be instantiated.
+   */
+  public function testLibrary() {
+    $type_id = $this->getLibraryTypeId();
+    $id = $this->getLibraryId();
+    try {
+      $library = $this->libraryManager->getLibrary($id);
+      $this->assertTrue(TRUE, "Test $type_id library can be instantiated.");
+      $this->assertInstanceOf($this->getLibraryType()->getLibraryClass(), $library);
+      $this->assertEquals($this->getLibraryId(), $library->getId());
+
+    }
+    catch (LibraryDefinitionNotFoundException $exception) {
+      $this->fail("Missing library definition for test $type_id library.");
+    }
+    catch (LibraryTypeNotFoundException $exception) {
+      $this->fail("Missing library type declaration for test $type_id library.");
+    }
+  }
+
+  /**
+   * Returns the library type that is being tested.
+   *
+   * @return \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface
+   *   The test library type.
+   */
+  protected function getLibraryType() {
+    try {
+      $library_type = $this->libraryTypeFactory->createInstance($this->getLibraryTypeId());
+    }
+    catch (PluginException $exception) {
+      $library_type = $this->prophesize(LibraryTypeInterface::class)->reveal();
+    }
+    finally {
+      return $library_type;
+    }
+  }
+
+  /**
+   * Retuns the library ID of the library used in the test.
+   *
+   * Defaults to 'test_[library_type]_library', where [library_type] is the
+   * ID of the library type being tested.
+   *
+   * @return string
+   */
+  protected function getLibraryId() {
+    $type_id = $this->getLibraryTypeId();
+    return "test_{$type_id}_library";
+  }
+
+  /**
+   * Returns the test library for this library type.
+   *
+   * @return \Drupal\libraries\ExternalLibrary\LibraryInterface
+   *   The test library.
+   */
+  protected function getLibrary() {
+    try {
+      $library = $this->libraryManager->getLibrary($this->getLibraryId());
+    }
+    catch (LibraryDefinitionNotFoundException $exception) {
+      $library = $this->prophesize(LibraryInterface::class)->reveal();
+    }
+    catch (LibraryTypeNotFoundException $exception) {
+      $library = $this->prophesize(LibraryInterface::class)->reveal();
+    }
+    catch (PluginException $exception) {
+      $library = $this->prophesize(LibraryInterface::class)->reveal();
+    }
+    finally {
+      return $library;
+    }
+  }
+
+}
diff --git a/web/modules/contrib/libraries/tests/src/Unit/Plugin/libraries/VersionDetector/LinePatternDetectorTest.php b/web/modules/contrib/libraries/tests/src/Unit/Plugin/libraries/VersionDetector/LinePatternDetectorTest.php
new file mode 100644 (file)
index 0000000..5e4e34b
--- /dev/null
@@ -0,0 +1,197 @@
+<?php
+
+namespace Drupal\Tests\libraries\Unit\Plugin\libraries\VersionDetector;
+
+use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface;
+use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface;
+use Drupal\libraries\Plugin\libraries\VersionDetector\LinePatternDetector;
+use Drupal\Tests\UnitTestCase;
+use org\bovigo\vfs\vfsStream;
+
+/**
+ * Tests the line pattern version detector.
+ *
+ * @group libraries
+ *
+ * @coversDefaultClass \Drupal\libraries\Plugin\libraries\VersionDetector\LinePatternDetector
+ */
+class LinePatternDetectorTest extends UnitTestCase {
+
+  protected $libraryId = 'test_library';
+
+  /**
+   * Tests that version detection fails for a non-local library.
+   *
+   * @expectedException \Drupal\libraries\ExternalLibrary\Exception\UnknownLibraryVersionException
+   *
+   * @covers ::detectVersion
+   */
+  public function testDetectVersionNonLocal() {
+    $library = $this->prophesize(VersionedLibraryInterface::class);
+    $detector = $this->setupDetector();
+    $detector->detectVersion($library->reveal());
+  }
+
+  /**
+   * Tests that version detection fails for a missing file.
+   *
+   * @expectedException \Drupal\libraries\ExternalLibrary\Exception\UnknownLibraryVersionException
+   *
+   * @covers ::detectVersion
+   */
+  public function testDetectVersionMissingFile() {
+    $library = $this->setupLibrary();
+
+    $detector = $this->setupDetector(['file' => 'CHANGELOG.txt']);
+    $detector->detectVersion($library->reveal());
+  }
+
+  /**
+   * Tests that version detection fails without a version in the file.
+   *
+   * @dataProvider providerTestDetectVersionNoVersion
+   *
+   * @covers ::detectVersion
+   */
+  public function testDetectVersionNoVersion($configuration, $file_contents) {
+    $library = $this->setupLibrary();
+
+    $detector = $this->setupDetector($configuration);
+    $this->setupFile($configuration['file'], $file_contents);
+
+    $library->setVersion()->shouldNotBeCalled();
+    $detector->detectVersion($library->reveal());
+  }
+
+  /**
+   * @return array
+   */
+  public function providerTestDetectVersionNoVersion() {
+    $test_cases = [];
+
+    $configuration = [
+      'file' => 'CHANGELOG.txt',
+      'pattern' => '/@version (\d+\.\d+\.\d+)/'
+    ];
+
+    $test_cases['empty_file'] = [$configuration, ''];
+
+    $test_cases['no_version'] = [$configuration, <<<EOF
+This is a file with
+multiple lines that does
+not contain a version.
+EOF
+    ];
+
+    $configuration['lines'] = 3;
+    $test_cases['long_file'] = [$configuration, <<<EOF
+This is a file that
+contains the version after
+the maximum number of lines
+to test has been surpassed.
+
+@version 1.2.3
+EOF
+    ];
+
+    $configuration['columns'] = 10;
+    // @todo Document why this is necessary.
+    $configuration['lines'] = 2;
+    $test_cases['long_column'] = [$configuration, <<<EOF
+This is a file that contains the version after
+the maximum number of columns to test has been surpassed. @version 1.2.3
+EOF
+    ];
+
+    return $test_cases;
+  }
+
+  /**
+   * Tests that version detection succeeds with a version in the file.
+   *
+   * @dataProvider providerTestDetectVersion
+   *
+   * @covers ::detectVersion
+   */
+  public function testDetectVersion($configuration, $file_contents, $version) {
+    $library = $this->setupLibrary();
+
+    $detector = $this->setupDetector($configuration);
+    $this->setupFile($configuration['file'], $file_contents);
+
+    $library->setVersion($version)->shouldBeCalled();
+    $detector->detectVersion($library->reveal());
+  }
+
+  /**
+   * @return array
+   */
+  public function providerTestDetectVersion() {
+    $test_cases = [];
+
+    $configuration = [
+      'file' => 'CHANGELOG.txt',
+      'pattern' => '/@version (\d+\.\d+\.\d+)/'
+    ];
+    $version = '1.2.3';
+
+    $test_cases['version'] = [$configuration, <<<EOF
+This a file with a version
+
+@version $version
+EOF
+    , $version];
+
+    return $test_cases;
+  }
+
+  /**
+   * Sets up the library prophecy and returns it.
+   *
+   * @return \Prophecy\Prophecy\ObjectProphecy
+   */
+  protected function setupLibrary() {
+    $library = $this->prophesize(VersionedLibraryInterface::class);
+    $library->willImplement(LocalLibraryInterface::class);
+    $library->getId()->willReturn($this->libraryId);
+    $library->getLocalPath()->willReturn('libraries/' . $this->libraryId);
+    return $library;
+  }
+
+  /**
+   * Sets up the version detector for testing and returns it.
+   *
+   * @param array $configuration
+   *   The plugin configuration to set the version detector up with.
+   *
+   * @return \Drupal\libraries\Plugin\libraries\VersionDetector\LinePatternDetector
+   *   The line pattern version detector to test.
+   */
+  protected function setupDetector(array $configuration = []) {
+    $app_root = 'root';
+    vfsStream::setup($app_root);
+
+    $plugin_id = 'line_pattern';
+    $plugin_definition = [
+      'id' => $plugin_id,
+      'class' => LinePatternDetector::class,
+      'provider' => 'libraries',
+    ];
+    return new LinePatternDetector($configuration, $plugin_id, $plugin_definition, 'vfs://' . $app_root);
+  }
+
+  /**
+   * @param $file
+   * @param $file_contents
+   */
+  protected function setupFile($file, $file_contents) {
+    vfsStream::create([
+      'libraries' => [
+        $this->libraryId => [
+          $file => $file_contents,
+        ],
+      ],
+    ]);
+  }
+
+}
diff --git a/web/modules/contrib/linkchecker b/web/modules/contrib/linkchecker
deleted file mode 160000 (submodule)
index 5137713..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 51377136199d15d6e0ca36339f68934c4eb19204
diff --git a/web/modules/contrib/linkchecker/CHANGELOG.txt b/web/modules/contrib/linkchecker/CHANGELOG.txt
new file mode 100644 (file)
index 0000000..57ee5ff
--- /dev/null
@@ -0,0 +1,3 @@
+linkchecker 8.x-1.x, nightly
+----------------------------
+
diff --git a/web/modules/contrib/linkchecker/README.txt b/web/modules/contrib/linkchecker/README.txt
new file mode 100644 (file)
index 0000000..96c733b
--- /dev/null
@@ -0,0 +1,51 @@
+
+Link Checker
+------------
+
+Installation:
+
+1. Install linkchecker via Modules page.
+2. Go to Modules and enable the "Link checker" module.
+3. Go to Configuration -> Content authoring -> Link checker and enable the node types to scan.
+4. Under "Link extraction" check all HTML tags that should be scanned.
+5. Adjust the other settings if the defaults don't suit your needs.
+6. Save configuration
+7. Wait for cron to check all your links... this may take some time! :-)
+
+If links are broken they appear under Reports -> Broken links.
+
+If not, make sure cron is configured and running properly on your Drupal
+installation. The Link checker module also logs somewhat useful info about it's
+activity under Reports -> Recent log messages.
+
+
+Required:
+
+1. For internal URL extraction you need to make sure that Cron always get called
+   with your real public site URL (for e.g. http://example.com/cron.php). Make
+   sure it's never executed with http://localhost/cron.php or any other
+   hostnames or ports, not available from public. Otherwise all links may be
+   reported as broken and cannot verified as they should be.
+
+   To make sure it always works - it's required to configure the $base_url in
+   the sites settings.php with your public sites URL. Better safe than sorry!
+
+
+Known issues:
+
+There are a lot of known issues in drupal_http_request(). These have been solved
+in HTTPRL. As a workaround it's recommended to use HTTPRL in linkchecker.
+
+Issues list:
+* #997648: drupal_http_request() always calls fread() one more time than necessary
+* #164365-12: drupal_http_request() does handle (invalid) non-absolute redirects
+* #205969-11: drupal_http_request() assumes presence of Reason-Phrase in response Status-Line
+* #371495: Error message from drupal_http_request() not UTF8 encoded
+* #193073-11: drupal_http_request - socket not initialized
+* #106506-8: drupal_http_request() does not handle 'chunked' responses - Make it support HTTP 1.1
+* #1096890-15: drupal_http_request should return error if reaches max allowed redirects
+* #875342-21: drupal_http_request() should pick up X-Drupal-Assertion-* HTTP headers
+* #965078-31: HTTP request checking is unreliable and should be removed in favor of watchdog() calls
+* #336367: HTTP client should protect commas when folding (compatibility with legacy HTTP/1.0)
+* #45338: log fsockopen errors to watchdog
diff --git a/web/modules/contrib/linkchecker/composer.json b/web/modules/contrib/linkchecker/composer.json
new file mode 100644 (file)
index 0000000..32b0629
--- /dev/null
@@ -0,0 +1,22 @@
+{
+  "name": "drupal/linkchecker",
+  "description": "Periodically checks for broken links in node types, blocks and fields and reports the results.",
+  "type": "drupal-module",
+  "homepage": "https://www.drupal.org/project/linkchecker",
+  "authors": [
+    {
+      "name": "hass",
+      "homepage": "https://www.drupal.org/u/hass"
+    },
+    {
+      "name": "See other contributors",
+      "homepage":"https://www.drupal.org/node/243795/committers"
+    }
+  ],
+  "support": {
+    "issues": "https://www.drupal.org/project/issues/linkchecker",
+    "source": "http://git.drupal.org/project/linkchecker.git"
+  },
+  "license": "GPL-2.0+",
+  "require": {}
+}
diff --git a/web/modules/contrib/linkchecker/config/install/linkchecker.settings.yml b/web/modules/contrib/linkchecker/config/install/linkchecker.settings.yml
new file mode 100644 (file)
index 0000000..7fc94d6
--- /dev/null
@@ -0,0 +1,34 @@
+scan_blocks: false
+check_links_types: 1
+extract:
+  from_a: true
+  from_audio: false
+  from_embed: false
+  from_iframe: false
+  from_img: false
+  from_object: false
+  from_video: false
+  filter_blacklist:
+    filter_align: filter_align
+    filter_autop: filter_autop
+    filter_caption: filter_caption
+    insert_block: insert_block
+    insert_view: insert_view
+    smiley: smiley
+    smileys: smileys
+    weblinks_embed: weblinks_embed
+    weblinks_filter: weblinks_filter
+check:
+  connections_max: 8
+  connections_max_per_domain: 2
+  disable_link_check_for_urls: "example.com\nexample.net\nexample.org"
+  library: core
+  interval: 2419200
+  useragent: 'Drupal (+http://drupal.org/)'
+error:
+  action_status_code_301: 0
+  action_status_code_404: 0
+  ignore_response_codes: "200\n206\n302\n304\n401\n403"
+  impersonate_account: ''
+logging:
+  level: 6
diff --git a/web/modules/contrib/linkchecker/config/schema/linkchecker.schema.yml b/web/modules/contrib/linkchecker/config/schema/linkchecker.schema.yml
new file mode 100644 (file)
index 0000000..a2de036
--- /dev/null
@@ -0,0 +1,99 @@
+# Schema for the configuration files of the linkchecker module.
+
+linkchecker.settings:
+  type: config_object
+  label: 'Linkchecker settings'
+  mapping:
+    scan_blocks:
+      type: boolean
+      label: 'Scan blocks for links'
+    check_links_types:
+      type: integer
+      label: 'What type of links should be checked?'
+    extract:
+      type: mapping
+      label: 'Link extraction'
+      mapping:
+        from_a:
+          type: boolean
+          label: 'Extract links in <a> and <area> tags'
+        from_audio:
+          type: boolean
+          label: 'Extract links in <audio>'
+        from_embed:
+          type: boolean
+          label: 'Extract links in <embed>'
+        from_iframe:
+          type: boolean
+          label: 'Extract links in <iframe>'
+        from_img:
+          type: boolean
+          label: 'Extract links in <img>'
+        from_object:
+          type: boolean
+          label: 'Extract links in <object> and <param> tags'
+        from_video:
+          type: boolean
+          label: 'Extract links in <video>'
+        filter_blacklist:
+          type: sequence
+          label: 'Filters disabled for link extraction'
+          sequence:
+            type: string
+            label: 'Filter name'
+    check:
+      type: mapping
+      label: 'Check settings'
+      mapping:
+        connections_max:
+          type: integer
+          label: 'Maximum number of simultaneous connections'
+        connections_max_per_domain:
+          type: integer
+          label: 'Maximum number of simultaneous connections per domain'
+        disable_link_check_for_urls:
+          type: string
+          label: 'Do not check the link status of links containing these URLs'
+        library:
+          type: string
+          label: 'Check library'
+        interval:
+          type: integer
+          label: 'Check interval for links'
+        useragent:
+          type: string
+          label: 'User-Agent'
+    error:
+      type: mapping
+      label: 'Error handling'
+      mapping:
+        action_status_code_301:
+          type: integer
+          label: 'Update permanently moved links'
+        action_status_code_404:
+          type: integer
+          label: 'Unpublish content on file not found error'
+        ignore_response_codes:
+          type: string
+          label: 'Do not treat these response codes as errors'
+        impersonate_account:
+          type: string
+          label: 'Impersonate user account'
+    logging:
+      type: mapping
+      label: 'Logging'
+      mapping:
+        level:
+          type: integer
+          label: 'Logging level'
+
+node.type.*.third_party.linkchecker:
+  type: mapping
+  label: 'Per-content type linkchecker settings'
+  mapping:
+    scan_node:
+      type: boolean
+      label: 'Scan content'
+    scan_comment:
+      type: boolean
+      label: 'Scan comments'
diff --git a/web/modules/contrib/linkchecker/js/linkchecker.content_types.js b/web/modules/contrib/linkchecker/js/linkchecker.content_types.js
new file mode 100644 (file)
index 0000000..abc961c
--- /dev/null
@@ -0,0 +1,36 @@
+/**
+ * @file
+ * Javascript for the node content type editing form.
+ */
+
+(function ($, Drupal) {
+
+  'use strict';
+
+  /**
+   * Behaviors for setting summaries on content type form.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches summary behaviors on content type edit forms.
+   */  
+  Drupal.behaviors.linkcheckerContentTypes = {
+    attach: function (context) {
+      var $context = $(context);
+      // Provide the vertical tab summaries.
+      $context.find('#edit-linkchecker').drupalSetSummary(function (context) {
+        var vals = [];
+        var $editContext = $(context);
+        $editContext.find('input:checked').next('label').each(function () {
+          vals.push(Drupal.checkPlain($(this).text()));
+        });
+        if (!vals.length) {
+          return Drupal.t('Disabled');
+        }
+        return vals.join(', ');
+      });
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/web/modules/contrib/linkchecker/linkchecker.batch.inc b/web/modules/contrib/linkchecker/linkchecker.batch.inc
new file mode 100644 (file)
index 0000000..b8af795
--- /dev/null
@@ -0,0 +1,365 @@
+<?php
+
+/**
+ * @file
+ * Batch API callbacks for the linkchecker module.
+ */
+
+/**
+ * Batch: Scan nodes for links.
+ */
+function _linkchecker_batch_import_nodes($node_types = array()) {
+  // Get all active {node}.nid's.
+  $result = db_query('SELECT n.nid FROM {node} n WHERE n.status = :status AND n.type IN (:types) ORDER BY n.nid', array(':status' => 1, ':types' => $node_types));
+
+  $operations = array();
+  foreach ($result as $row) {
+    $operations[] = array('_linkchecker_batch_node_import_op', array($row->nid));
+  }
+  $batch = array(
+    'file' => drupal_get_path('module', 'linkchecker') . '/linkchecker.batch.inc',
+    'finished' => '_linkchecker_batch_node_import_finished',
+    'operations' => $operations,
+    'title' => t('Scanning for links'),
+  );
+
+  return $batch;
+}
+
+/**
+ * Batch operation: Scan one by one node for links.
+ */
+function _linkchecker_batch_node_import_op($nid, &$context) {
+  // Load the node and scan for links.
+  $node = node_load($nid, NULL, TRUE);
+  _linkchecker_add_node_links($node);
+
+  // Store results for post-processing in the finished callback.
+  $context['results'][] = $node->nid;
+  $context['message'] = t('Content: @title', array('@title' => $node->title));
+}
+
+/**
+ * Output node batch result messages.
+ *
+ * @param bool $success
+ *   If scan completed successfully or not.
+ * @param int $results
+ *   Number of nodes scanned.
+ * @param array $operations
+ *   Array of functions called.
+ */
+function _linkchecker_batch_node_import_finished($success, $results, $operations) {
+  if ($success) {
+    $message = format_plural(count($results), 'One node has been scanned.', '@count nodes have been scanned.');
+  }
+  else {
+    $message = t('Scanning for links in content has failed with an error.');
+  }
+  drupal_set_message($message);
+}
+
+/**
+ * Batch: Scan comments for links.
+ */
+function _linkchecker_batch_import_comments($node_types = array()) {
+  // Get all active {comment}.cid's.
+  $result = db_query('SELECT c.cid FROM {comment} c INNER JOIN {node} n ON c.nid = n.nid WHERE c.status = :cstatus AND n.status = :nstatus AND n.type IN (:types) ORDER BY c.cid', array(':cstatus' => COMMENT_PUBLISHED, ':nstatus' => 1, ':types' => $node_types));
+
+  $operations = array();
+  foreach ($result as $row) {
+    $operations[] = array('_linkchecker_batch_comments_import_op', array($row->cid));
+  }
+  $batch = array(
+    'file' => drupal_get_path('module', 'linkchecker') . '/linkchecker.batch.inc',
+    'finished' => '_linkchecker_batch_comments_import_finished',
+    'operations' => $operations,
+    'title' => t('Scanning for links'),
+  );
+
+  return $batch;
+}
+
+/**
+ * Batch operation: Scan one by one comment for links.
+ */
+function _linkchecker_batch_comments_import_op($cid, &$context) {
+  // Load the comment and scan for links.
+  $comment = comment_load($cid);
+  _linkchecker_add_comment_links($comment);
+
+  // Store results for post-processing in the finished callback.
+  $context['results'][] = $comment->cid;
+  $context['message'] = t('Comment: @title', array('@title' => $comment->subject));
+}
+
+/**
+ * Output comment batch result messages.
+ *
+ * @param bool $success
+ *   If scan completed successfully or not.
+ * @param int $results
+ *   Number of comments scanned.
+ * @param array $operations
+ *   Array of functions called.
+ */
+function _linkchecker_batch_comments_import_finished($success, $results, $operations) {
+  if ($success) {
+    $message = format_plural(count($results), 'One comment has been scanned.', '@count comments have been scanned.');
+  }
+  else {
+    $message = t('Scanning for links in comments have failed with an error.');
+  }
+  drupal_set_message($message);
+}
+
+/**
+ * Batch: Scan blocks for links.
+ */
+function _linkchecker_batch_import_block_custom() {
+  // Get all {block_custom}.bid's as block module suxxx and has no usable hooks.
+  $result = db_query('SELECT bid FROM {block_custom} ORDER BY bid');
+
+  $operations = array();
+  foreach ($result as $row) {
+    $operations[] = array('_linkchecker_batch_import_block_custom_op', array($row->bid));
+  }
+  $batch = array(
+    'file' => drupal_get_path('module', 'linkchecker') . '/linkchecker.batch.inc',
+    'finished' => '_linkchecker_batch_block_custom_import_finished',
+    'operations' => $operations,
+    'title' => t('Scanning for links'),
+  );
+
+  return $batch;
+}
+
+/**
+ * Batch operation: Scan one by one block for links.
+ */
+function _linkchecker_batch_import_block_custom_op($bid, &$context) {
+  // Load the custom block and scan for links.
+  $block_custom = linkchecker_block_custom_block_get($bid);
+  _linkchecker_add_block_custom_links($block_custom, $block_custom->delta);
+
+  // Store some result for post-processing in the finished callback.
+  $context['results'][] = $block_custom->delta;
+  $context['message'] = t('Block: @title', array('@title' => $block_custom->info));
+}
+
+/**
+ * Output block batch result messages.
+ *
+ * @param bool $success
+ *   If scan completed successfully or not.
+ * @param int $results
+ *   Number of blocks scanned.
+ * @param array $operations
+ *   Array of functions called.
+ */
+function _linkchecker_batch_block_custom_import_finished($success, $results, $operations) {
+  if ($success) {
+    $message = format_plural(count($results), 'One block has been scanned.', '@count blocks have been scanned.');
+  }
+  else {
+    $message = t('Scanning for links in blocks have failed with an error.');
+  }
+  drupal_set_message($message);
+}
+
+/**
+ * Recurring scans of a single node via batch API.
+ *
+ * @param int $nid
+ *   The unique node id to scan for links.
+ * @param int $missing_links_count
+ *   The number of links not yet added to linkchecker_links table. By this
+ *   number the re-scan rounds are calulated.
+ *
+ * @return array
+ *   The batch task definition.
+ */
+function _linkchecker_batch_import_single_node($nid, $missing_links_count) {
+  $operations = array();
+  for ($i = 0; $i <= $missing_links_count; $i = $i + LINKCHECKER_SCAN_MAX_LINKS_PER_RUN) {
+    $operations[] = array('_linkchecker_batch_single_node_import_op', array($nid));
+  }
+  $batch = array(
+    'file' => drupal_get_path('module', 'linkchecker') . '/linkchecker.batch.inc',
+    'finished' => '_linkchecker_batch_single_node_import_finished',
+    'operations' => $operations,
+    'title' => t('Scanning for links'),
+    'progress_message' => t('Remaining @remaining of @total scans.'),
+  );
+
+  return $batch;
+}
+
+/**
+ * Run single node link extraction.
+ *
+ * @param int $nid
+ *   Node ID.
+ * @param array $context
+ *   Batch context array.
+ */
+function _linkchecker_batch_single_node_import_op($nid, &$context) {
+  // Load the node and scan for links.
+  $node = node_load($nid, NULL, TRUE);
+  _linkchecker_add_node_links($node, TRUE);
+
+  // Store results for post-processing in the finished callback.
+  $context['results'][] = $node->nid;
+  $context['message'] = t('Content: @title', array('@title' => $node->title));
+}
+
+/**
+ * Output single node batch result messages.
+ *
+ * @param bool $success
+ *   If scan completed successfully or not.
+ * @param int $results
+ *   How often the node has been scanned.
+ * @param array $operations
+ *   Array of functions called.
+ */
+function _linkchecker_batch_single_node_import_finished($success, $results, $operations) {
+  if ($success) {
+    $message = format_plural(count($results), 'Node @nid has been re-scanned once to collect all links.', 'Node @nid has been re-scanned @count times to collect all links.', array('@nid' => $results[0]));
+  }
+  else {
+    $message = t('Recurring scanning for links in node @nid has failed with an error.', array('@nid' => $results[0]));
+  }
+  drupal_set_message($message);
+}
+
+/**
+ * Recurring scans of a single comment via batch API.
+ *
+ * @param int $cid
+ *   The unique comment id to scan for links.
+ * @param int $missing_links_count
+ *   The number of links not yet added to linkchecker_links table. By this
+ *   number the re-scan rounds are calulated.
+ *
+ * @return array
+ *   The batch task definition.
+ */
+function _linkchecker_batch_import_single_comment($cid, $missing_links_count) {
+  $operations = array();
+  for ($i = 0; $i <= $missing_links_count; $i = $i + LINKCHECKER_SCAN_MAX_LINKS_PER_RUN) {
+    $operations[] = array('_linkchecker_batch_single_comment_import_op', array($cid));
+  }
+  $batch = array(
+    'file' => drupal_get_path('module', 'linkchecker') . '/linkchecker.batch.inc',
+    'finished' => '_linkchecker_batch_single_comment_import_finished',
+    'operations' => $operations,
+    'title' => t('Scanning for links'),
+    'progress_message' => t('Remaining @remaining of @total scans.'),
+  );
+
+  return $batch;
+}
+
+/**
+ * Run single comment link extraction.
+ *
+ * @param int $cid
+ *   Comment ID.
+ * @param array $context
+ *   Batch context array.
+ */
+function _linkchecker_batch_single_comment_import_op($cid, &$context) {
+  $comment = comment_load($cid);
+  _linkchecker_add_comment_links($comment, TRUE);
+
+  // Store results for post-processing in the finished callback.
+  $context['results'][] = $comment->cid;
+  $context['message'] = t('Comment: @title', array('@title' => $comment->subject));
+}
+
+/**
+ * Output single comment batch result messages.
+ *
+ * @param bool $success
+ *   If scan completed successfully or not.
+ * @param int $results
+ *   How often the comment has been scanned.
+ * @param array $operations
+ *   Array of functions called.
+ */
+function _linkchecker_batch_single_comment_import_finished($success, $results, $operations) {
+  if ($success) {
+    $message = format_plural(count($results), 'Comment @cid has been re-scanned once to collect all links.', 'Comment @cid has been re-scanned @count times to collect all links.', array('@cid' => $results[0]));
+  }
+  else {
+    $message = t('Recurring scanning for links in comment @cid has failed with an error.', array('@cid' => $results[0]));
+  }
+  drupal_set_message($message);
+}
+
+/**
+ * Recurring scans of a single block via batch API.
+ *
+ * @param int $bid
+ *   The unique block id to scan for links.
+ * @param int $missing_links_count
+ *   The number of links not yet added to linkchecker_links table. By this
+ *   number the re-scan rounds are calulated.
+ *
+ * @return array
+ *   The batch task definition.
+ */
+function _linkchecker_batch_import_single_block_custom($bid, $missing_links_count) {
+  $operations = array();
+  for ($i = 0; $i <= $missing_links_count; $i = $i + LINKCHECKER_SCAN_MAX_LINKS_PER_RUN) {
+    $operations[] = array('_linkchecker_batch_single_block_custom_import_op', array($bid));
+  }
+  $batch = array(
+    'file' => drupal_get_path('module', 'linkchecker') . '/linkchecker.batch.inc',
+    'finished' => '_linkchecker_batch_single_block_custom_import_finished',
+    'operations' => $operations,
+    'title' => t('Scanning for links'),
+    'progress_message' => t('Remaining @remaining of @total scans.'),
+  );
+
+  return $batch;
+}
+
+/**
+ * Run single block link extraction.
+ *
+ * @param int $bid
+ *   Node ID.
+ * @param array $context
+ *   Batch context array.
+ */
+function _linkchecker_batch_single_block_custom_import_op($bid, &$context) {
+  // Load the custom block and scan for links.
+  $block_custom = linkchecker_block_custom_block_get($bid);
+  _linkchecker_add_block_custom_links($block_custom, $block_custom->delta, TRUE);
+
+  // Store some result for post-processing in the finished callback.
+  $context['results'][] = $block_custom->delta;
+  $context['message'] = t('Block: @title', array('@title' => $block_custom->info));
+}
+
+/**
+ * Output single block batch result messages.
+ *
+ * @param bool $success
+ *   If scan completed successfully or not.
+ * @param int $results
+ *   How often the block has been scanned.
+ * @param array $operations
+ *   Array of functions called.
+ */
+function _linkchecker_batch_single_block_custom_import_finished($success, $results, $operations) {
+  if ($success) {
+    $message = format_plural(count($results), 'Block @bid has been re-scanned once to collect all links.', 'Block @bid has been re-scanned @count times to collect all links.', array('@bid' => $results[0]));
+  }
+  else {
+    $message = t('Recurring scanning for links in block @bid has failed with an error.', array('@bid' => $results[0]));
+  }
+  drupal_set_message($message);
+}
diff --git a/web/modules/contrib/linkchecker/linkchecker.drush.inc b/web/modules/contrib/linkchecker/linkchecker.drush.inc
new file mode 100644 (file)
index 0000000..5941e72
--- /dev/null
@@ -0,0 +1,100 @@
+<?php
+
+/**
+ * @file
+ * Drush interface to linkchecker functionalities.
+ */
+
+/**
+ * Implements hook_drush_command().
+ */
+function linkchecker_drush_command() {
+  $commands = [];
+
+  $commands['linkchecker-analyze'] = [
+    'description' => 'Reanalyzes content for links. Recommended after module has been upgraded.',
+  ];
+  $commands['linkchecker-clear'] = [
+    'description' => 'Clears all link data and analyze content for links. WARNING: Custom link check settings are deleted.',
+  ];
+  $commands['linkchecker-check'] = [
+    'description' => 'Check link status.',
+    // 'options' => array(
+    //   'links' => 'Number of links to check in one round. Default: 1000',
+    // ),
+  ];
+
+  return $commands;
+}
+
+/**
+ * Callback for command linkchecker-analyze.
+ */
+function drush_linkchecker_analyze() {
+  // @fixme
+  global $base_url;
+  if ($base_url == 'http://default') {
+    drush_die('You MUST configure the site $base_url or provide --uri parameter.');
+  }
+
+  // @fixme
+  module_load_include('admin.inc', 'linkchecker');
+
+  // Fake $form_state to leverage _submit function.
+  $form_state = [
+    'values' => ['op' => t('Analyze content for links')],
+    'buttons' => [],
+  ];
+
+  $node_types = linkchecker_scan_node_types();
+  if (!empty($node_types) || \Drupal::config('linkchecker.settings')->get('scan_blocks')) {
+    linkchecker_analyze_links_submit(NULL, $form_state);
+    drush_backend_batch_process();
+  }
+  else {
+    drush_log('No content configured for link analysis.', 'status');
+  }
+}
+
+/**
+ * Callback for command linkchecker-analyze.
+ */
+function drush_linkchecker_clear() {
+  // @fixme
+  global $base_url;
+  if ($base_url == 'http://default') {
+    drush_die('You MUST configure the site $base_url or provide --uri parameter.');
+  }
+
+  // @fixme
+  module_load_include('admin.inc', 'linkchecker');
+
+  // Fake $form_state to leverage _submit function.
+  $form_state = [
+    'values' => ['op' => t('Clear link data and analyze content for links')],
+    'buttons' => [],
+  ];
+
+  $node_types = linkchecker_scan_node_types();
+  if (!empty($node_types) || \Drupal::config('linkchecker.settings')->get('scan_blocks')) {
+    linkchecker_clear_analyze_links_submit(NULL, $form_state);
+    drush_backend_batch_process();
+  }
+  else {
+    drush_log('No content configured for link analysis.', 'status');
+  }
+}
+
+/**
+ * Callback for command linkchecker-check.
+ */
+function drush_linkchecker_check() {
+  drush_log('Starting link checking...', 'info');
+  $run = _linkchecker_check_links();
+  if (!$run) {
+    drush_log('Attempted to re-run link checks while they are already running.', 'warning');
+  }
+  else {
+    drush_log('Finished checking links.', 'completed');
+  }
+}
diff --git a/web/modules/contrib/linkchecker/linkchecker.info.yml b/web/modules/contrib/linkchecker/linkchecker.info.yml
new file mode 100644 (file)
index 0000000..5df6a29
--- /dev/null
@@ -0,0 +1,5 @@
+name: 'Link checker'
+type: module
+description: 'Periodically checks for broken links in node types, blocks and fields and reports the results.'
+core: 8.x
+configure: linkchecker.admin_settings_form
diff --git a/web/modules/contrib/linkchecker/linkchecker.install b/web/modules/contrib/linkchecker/linkchecker.install
new file mode 100644 (file)
index 0000000..8a9c3ce
--- /dev/null
@@ -0,0 +1,199 @@
+<?php
+
+/**
+ * @file
+ * Installation file for Link Checker module.
+ */
+
+use Drupal\user\Entity\User;
+
+/**
+ * Implements hook_install().
+ */
+function linkchecker_install() {
+  $linkchecker_default_impersonate_account = User::load(1);
+  \Drupal::configFactory()->getEditable('linkchecker.settings')->set('error.impersonate_account', $linkchecker_default_impersonate_account->getAccountName())->save();
+}
+
+/**
+ * Implements hook_schema().
+ */
+function linkchecker_schema() {
+
+  $schema['linkchecker_block_custom'] = array(
+    'description' => 'Stores all link references for custom blocks.',
+    'fields' => array(
+      'bid'  => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'description' => 'Primary Key: Unique {block_custom}.bid.',
+      ),
+      'lid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'description' => 'Primary Key: Unique {linkchecker_link}.lid.',
+      ),
+    ),
+    'primary key' => array('bid', 'lid'),
+    'foreign keys' => array(
+      'bid' => array(
+        'table' => 'block_custom',
+        'columns' => array('bid' => 'bid'),
+      ),
+      'lid' => array(
+        'table' => 'linkchecker_link',
+        'columns' => array('lid' => 'lid'),
+      ),
+    ),
+    'indexes' => array('lid' => array('lid')),
+  );
+
+  $schema['linkchecker_comment'] = array(
+    'description' => 'Stores all link references for comments.',
+    'fields' => array(
+      'cid'  => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'description' => 'Primary Key: Unique {comment}.cid.',
+      ),
+      'lid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'description' => 'Primary Key: Unique {linkchecker_link}.lid.',
+      ),
+    ),
+    'primary key' => array('cid', 'lid'),
+    'foreign keys' => array(
+      'cid' => array(
+        'table' => 'comment',
+        'columns' => array('cid' => 'cid'),
+      ),
+      'lid' => array(
+        'table' => 'linkchecker_link',
+        'columns' => array('lid' => 'lid'),
+      ),
+    ),
+    'indexes' => array('lid' => array('lid')),
+  );
+
+  $schema['linkchecker_node'] = array(
+    'description' => 'Stores all link references for nodes.',
+    'fields' => array(
+      'nid'  => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'description' => 'Primary Key: Unique {node}.nid.',
+      ),
+      'lid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'description' => 'Primary Key: Unique {linkchecker_link}.lid.',
+      ),
+    ),
+    'primary key' => array('nid', 'lid'),
+    'foreign keys' => array(
+      'nid' => array(
+        'table' => 'node',
+        'columns' => array('nid' => 'nid'),
+      ),
+      'lid' => array(
+        'table' => 'linkchecker_link',
+        'columns' => array('lid' => 'lid'),
+      ),
+    ),
+    'indexes' => array('lid' => array('lid')),
+  );
+
+  $schema['linkchecker_link'] = array(
+    'description' => 'Stores all links.',
+    'fields' => array(
+      'lid'  => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'description' => 'Primary Key: Unique link ID.',
+      ),
+      'urlhash' => array(
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'description' => 'The indexable hash of the {linkchecker_link}.url.',
+      ),
+      'url' => array(
+        'type' => 'text',
+        'not null' => TRUE,
+        'description' => 'The full qualified link.',
+      ),
+      'method' => array(
+        'type' => 'varchar',
+        'length' => 4,
+        'default' => 'HEAD',
+        'not null' => TRUE,
+        'description' => 'The method for checking links (HEAD, GET, POST).',
+      ),
+      'code' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => -1,
+        'description' => 'HTTP status code from link checking.',
+      ),
+      'error' => array(
+        'type' => 'text',
+        'not null' => FALSE,
+        'description' => 'The error message received from the remote server while doing link checking.',
+      ),
+      'fail_count' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => 'Fail count of unsuccessful link checks. No flapping detection. (Successful = 0, Unsuccessful = fail_count+1).',
+      ),
+      'last_checked' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => 'Timestamp of the last link check.',
+      ),
+      'status' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 1,
+        'description' => 'Boolean indicating if a link should be checked or not.',
+      ),
+    ),
+    'primary key' => array('lid'),
+    'unique keys' => array('urlhash' => array('urlhash')),
+    'indexes' => array(
+      'method' => array('method'),
+      'code' => array('code'),
+      'fail_count' => array('fail_count'),
+      'last_checked' => array('last_checked'),
+      'status' => array('status'),
+    ),
+  );
+
+  return $schema;
+}
+
+/**
+ * Implements hook_modules_uninstalled().
+ *
+ * If the core modules are disabled the integration need to be disabled.
+ */
+function linkchecker_modules_uninstalled($modules) {
+  // Disable link checks for custom blocks.
+  if (in_array('block', $modules)) {
+    \Drupal::config('linkchecker.settings')->set('scan_blocks', 0);
+    drupal_set_message(t('Link checks for blocks have been disabled.'));
+  }
+
+  // Disable link checks for comments.
+  if (in_array('comment', $modules)) {
+    foreach (node_type_get_names() as $type => $name) {
+      // @fixme: This is incorrect. The variable is inside node.type.*.third_party.linkchecker
+      //\Drupal::config('node.type.' . $type .  '.third_party.linkchecker')->clear('scan_comment');
+      //\Drupal::service('config.manager');
+      //$type->unsetThirdPartySetting('linkchecker', 'scan_comment', $form_state->getValue('scan_comment'));
+    }
+    drupal_set_message(t('Link checks for comments have been disabled.'));
+  }
+}
diff --git a/web/modules/contrib/linkchecker/linkchecker.libraries.yml b/web/modules/contrib/linkchecker/linkchecker.libraries.yml
new file mode 100644 (file)
index 0000000..0632689
--- /dev/null
@@ -0,0 +1,7 @@
+linkchecker.content_types:
+  version: VERSION
+  js:
+    js/linkchecker.content_types.js: {}
+  dependencies:
+    - core/jquery
+    - core/drupal
diff --git a/web/modules/contrib/linkchecker/linkchecker.links.menu.yml b/web/modules/contrib/linkchecker/linkchecker.links.menu.yml
new file mode 100644 (file)
index 0000000..b373f40
--- /dev/null
@@ -0,0 +1,19 @@
+linkchecker.admin_settings_form:
+  title: 'Link checker'
+  parent: system.admin_config_content
+  description: 'Configure the content types that should be checked for broken links and how the hypertext links will be checked and reported and repaired.'
+  route_name: linkchecker.admin_settings_form
+linkchecker.admin_report_page:
+  title: 'Broken links'
+  parent: system.admin_reports
+  description: 'Shows a list of broken links in content.'
+  route_name: linkchecker.admin_report_page
+linkchecker.user_report_page:
+  title: 'Broken links'
+  parent: user.page
+  description: 'Shows a list of broken links in content.'
+  route_name: linkchecker.user_report_page
+linkchecker.edit_link_settings_form:
+  title: 'Edit link settings'
+  description: 'Edit check settings of one link.'
+  route_name: linkchecker.edit_link_settings_form
diff --git a/web/modules/contrib/linkchecker/linkchecker.links.task.yml b/web/modules/contrib/linkchecker/linkchecker.links.task.yml
new file mode 100644 (file)
index 0000000..1d361b8
--- /dev/null
@@ -0,0 +1,13 @@
+linkchecker.admin_settings_form_tab:
+  route_name: linkchecker.admin_settings_form
+  title: Settings
+  base_route: linkchecker.admin_settings_form
+linkchecker.admin_report_page_tab:
+  route_name: linkchecker.admin_report_page
+  title: Broken links
+  base_route: linkchecker.admin_report_page
+linkchecker.user_report_page_tab:
+  route_name: linkchecker.user_report_page
+  title: Broken links
+  base_route: linkchecker.user_report_page
+  
\ No newline at end of file
diff --git a/web/modules/contrib/linkchecker/linkchecker.module b/web/modules/contrib/linkchecker/linkchecker.module
new file mode 100644 (file)
index 0000000..fdc7d23
--- /dev/null
@@ -0,0 +1,2614 @@
+<?php
+
+/**
+ * @file
+ * This module periodically check links in given node types, blocks etc.
+ *
+ * Developed by Alexander Hass, http://www.yaml-for-drupal.com/.
+ */
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Session\UserSession;
+use Drupal\node\NodeTypeInterface;
+use GuzzleHttp\Exception\RequestException;
+
+
+/**
+ * Defines the maximum limit of links collected in one chunk if content is
+ * scanned for links. A value that is too high may overload the database server.
+ */
+define('LINKCHECKER_SCAN_MAX_LINKS_PER_RUN', '100');
+
+/**
+ * A list of domain names reserved for use in documentation and not available
+ * for registration. See RFC 2606, Section 3 for more information.
+ */
+define('LINKCHECKER_RESERVED_DOCUMENTATION_DOMAINS', "example.com\nexample.net\nexample.org");
+
+/**
+ * A list of blacklisted filters the modules do not need to run for the link
+ * extraction process. This filters only eat processing time or holds references
+ * to other nodes.
+ *
+ * - Align images, http://drupal.org/project/drupal
+ *     name: filter_align
+ * - Line break converter, http://drupal.org/project/drupal
+ *     name: filter_autop
+ * - Caption images, http://drupal.org/project/drupal
+ *     name: filter_caption
+ * - Insert block, http://drupal.org/project/insert_block
+ *     name: insert_block
+ *     tags: [block:name of module=delta of block]
+ * - Insert view filter, http://drupal.org/project/insert_view
+ *     name: insert_view
+ *     tags: [view:my_view]
+ * - Smiley filter, http://drupal.org/project/smiley
+ *     name: smiley
+ *     tags: Depends on icon set, for e.g: ":) :-) :smile:"
+ * - Web Links Embed, http://drupal.org/project/weblinks
+ *     name: weblinks_embed
+ *     tags: [links-embed: id], [links-embed: name]
+ * - Web Links Filter, http://drupal.org/project/weblinks
+ *     name: weblinks_filter
+ *     tags: [link: title]
+ *
+ * @todo
+ * - Smileys Filter, http://drupal.org/project/smileys
+ *     name: smileys
+ *     tags: Depends on icon set, for e.g: ":) :-) :smile:"
+ * - Insert node, http://drupal.org/project/InsertNode
+ *     name: insert_node/0
+ *     tags: [node:<name of node> <parameters>]
+ * - Weblink filter, http://drupal.org/project/links
+ *     name: links_weblink/0
+ *     tags: [weblink:node_id|text], [weblink:node_id/link_id], [weblink:http://weblink.example.com/]
+ */
+define('LINKCHECKER_DEFAULT_FILTER_BLACKLIST', 'filter_align|filter_autop|filter_caption|insert_block|insert_view|smiley|smileys|weblinks_embed|weblinks_filter');
+
+/**
+ * Implements hook_help().
+ */
+function linkchecker_help($route_name, RouteMatchInterface $route_match) {
+  switch ($route_name) {
+    case 'help.page.linkchecker':
+      return '<p>' . t('This module provides an aid to finding broken links on your site. It periodically checks contents of all public nodes, tries to find any html links and check for their validity. It reports broken links through the admin interface. For more information about status codes see <a href="@rfc">Status Code Definitions</a>.', ['@rfc' => 'http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html']) . '</p>';
+  }
+}
+
+/**
+ * Conditionally logs a system message.
+ *
+ * @param $type
+ *   The category to which this message belongs. Can be any string, but the
+ *   general practice is to use the name of the module calling watchdog().
+ * @param $message
+ *   The message to store in the log. Keep $message translatable
+ *   by not concatenating dynamic values into it! Variables in the
+ *   message should be added by using placeholder strings alongside
+ *   the variables argument to declare the value of the placeholders.
+ *   See t() for documentation on how $message and $variables interact.
+ * @param $variables
+ *   Array of variables to replace in the message on display or
+ *   NULL if message is already translated or not possible to
+ *   translate.
+ * @param $severity
+ *   The severity of the message; one of the following values as defined in
+ *   @link http://www.faqs.org/rfcs/rfc3164.html RFC 3164: @endlink
+ *   - WATCHDOG_EMERGENCY: Emergency, system is unusable.
+ *   - WATCHDOG_ALERT: Alert, action must be taken immediately.
+ *   - WATCHDOG_CRITICAL: Critical conditions.
+ *   - WATCHDOG_ERROR: Error conditions.
+ *   - WATCHDOG_WARNING: Warning conditions.
+ *   - WATCHDOG_NOTICE: (default) Normal but significant conditions.
+ *   - WATCHDOG_INFO: Informational messages.
+ *   - WATCHDOG_DEBUG: Debug-level messages.
+ * @param $link
+ *   A link to associate with the message.
+ *
+ * @see watchdog_severity_levels()
+ * @see watchdog()
+ */
+function linkchecker_watchdog_log($type, $message, $variables = array(), $severity = WATCHDOG_NOTICE, $link = NULL) {
+  if ($severity <= variable_get('linkchecker_log_level', WATCHDOG_INFO)) {
+    watchdog($type, $message, $variables, $severity, $link);
+  }
+}
+
+/**
+ * Access callback for user/%user/linkchecker.
+ *
+ * @param object $account
+ *   The user account.
+ *
+ * @return int|bool
+ */
+function _linkchecker_user_access_account_broken_links_report($account) {
+  global $user;
+
+  // Users with 'access own broken links report' permission can only view their
+  // own report. Users with the 'access broken links report' permission can
+  // view the report for any authenticated user.
+  return $account->uid && (($user->uid == $account->uid && user_access('access own broken links report')) || user_access('access broken links report'));
+}
+
+/**
+ * Access callback for linkchecker/%linkchecker_link/edit.
+ *
+ * @param object $link
+ *   An object representing the link to check.
+ *
+ * @return bool
+ *   TRUE if the current user has the requested permission.
+ */
+function _linkchecker_user_access_edit_link_settings($link) {
+  return user_access('edit link settings') && _linkchecker_link_access($link);
+}
+
+/**
+ * Determines if the current user has access to view a link.
+ *
+ * Link URLs can contain private information (for example, usernames and
+ * passwords). So this module should only display links to a user if the link
+ * already appears in at least one place on the site where the user would
+ * otherwise have access to see it.
+ *
+ * @param object $link
+ *   An object representing the link to check.
+ *
+ * @return array
+ */
+function _linkchecker_link_access($link) {
+  $link = (object) $link;
+  return _linkchecker_link_node_ids($link) || _linkchecker_link_comment_ids($link) || _linkchecker_link_block_ids($link);
+}
+
+/**
+ * Returns IDs of nodes that contain a link which the current user may be allowed to view.
+ *
+ * Important note: For performance reasons, this function is not always
+ * guaranteed to return the exact list of node IDs that the current user is
+ * allowed to view. It will, however, always return an empty array if the user
+ * does not have access to view *any* such nodes, thereby meeting the security
+ * goals of _linkchecker_link_access() and other places that call it.
+ *
+ * In the case where a user has access to some of the nodes that contain the
+ * link, this function may return some node IDs that the user does not have
+ * access to. Therefore, use caution with its results.
+ *
+ * @param object $link
+ *   An object representing the link to check.
+ * @param object $node_author_account
+ *   (optional) If a user account object is provided, the returned nodes will
+ *   additionally be restricted to only those owned by this account. Otherwise,
+ *   nodes owned by any user account may be returned.
+ *
+ * @return array
+ *   An array of node IDs that contain the provided link and that the current
+ *   user may be allowed to view.
+ */
+function _linkchecker_link_node_ids($link, $node_author_account = NULL) {
+  static $fields_with_node_links = array();
+
+  // Exit if all node types are disabled or if the user cannot access content,
+  // there is no need to check further.
+  $linkchecker_scan_nodetypes = linkchecker_scan_node_types();
+  if (empty($linkchecker_scan_nodetypes) || !user_access('access content')) {
+    return array();
+  }
+
+  // Get a list of nodes containing the link, using addTag('node_access') to
+  // allow node access modules to exclude nodes that the current user does not
+  // have access to view.
+  if (!empty($node_author_account)) {
+    $query = db_select('node', 'n');
+    $query->addTag('node_access');
+    $query->innerJoin('linkchecker_node', 'ln', 'ln.nid = n.nid');
+    $query->innerJoin('node_revision', 'r', 'r.vid = n.vid');
+    $query->condition('ln.lid', $link->lid);
+    $query->condition(db_or()
+      ->condition('n.uid', $node_author_account->uid)
+      ->condition('r.uid', $node_author_account->uid)
+    );
+    $query->fields('n', array('nid'));
+  }
+  else {
+    $query = db_select('node', 'n');
+    $query->addTag('node_access');
+    $query->innerJoin('linkchecker_node', 'ln', 'ln.nid = n.nid');
+    $query->condition('ln.lid', $link->lid);
+    $query->fields('n', array('nid'));
+  }
+  $nodes = $query->execute();
+
+  // Check if the current user has access to view the link in each node.
+  // However, for performance reasons, as soon as we find one node where that
+  // is the case, stop checking and return the remainder of the list.
+  $nids = array();
+  $access_allowed = FALSE;
+  foreach ($nodes as $node) {
+    if ($access_allowed) {
+      $nids[] = $node->nid;
+      continue;
+    }
+    $node = node_load($node->nid);
+
+    // We must check whether the link is currently part of the node; if not, we
+    // do not want to return it (and it is not safe to, since we cannot know if
+    // it contained access restrictions for the current user at the point which
+    // it was originally extracted by the Link checker module).
+    if (!isset($fields_with_node_links[$node->nid])) {
+      $fields_with_node_links[$node->nid] = _linkchecker_extract_node_links($node, TRUE);
+    }
+    if (empty($fields_with_node_links[$node->nid][$link->url])) {
+      continue;
+    }
+    // If the link appears in fields and a field access module is being used,
+    // we must check that the current user has access to view at least one field
+    // that contains the link; if they don't, we should not return the node.
+    $fields = $fields_with_node_links[$node->nid][$link->url];
+    if (module_implements('field_access')) {
+      $fields_with_access = array();
+
+      $bundle_instances = field_info_instances('node', $node->type);
+      foreach ($bundle_instances as $field_name => $field_instance) {
+        $field = field_info_field($field_name);
+
+        // Field types supported by linkchecker.
+        $fields_supported = array(
+          'text_with_summary',
+          'text_long',
+          'text',
+          'link_field',
+        );
+
+        // Only check link and text fields, since those are the only types we
+        // extract links from.
+        if (in_array($field['type'], $fields_supported) && field_access('view', $field, 'node', $node)) {
+          $fields_with_access[] = $field['field_name'];
+        }
+      }
+      if (!array_intersect($fields, $fields_with_access)) {
+        continue;
+      }
+    }
+    $nids[] = $node->nid;
+    $access_allowed = TRUE;
+  }
+
+  return $nids;
+}
+
+/**
+ * Returns IDs of comments that contain a link which the current user is allowed to view.
+ *
+ * @param object $link
+ *   An object representing the link to check.
+ * @param object $comment_author_account
+ *   (optional) If a user account object is provided, the returned comments
+ *   will additionally be restricted to only those owned by this account.
+ *   Otherwise, comments owned by any user account may be returned.
+ *
+ * @return array
+ *   An array of comment IDs that contain the provided link and that the
+ *   current user is allowed to view.
+ */
+function _linkchecker_link_comment_ids($link, $comment_author_account = NULL) {
+  // Exit if comments are disabled or if the user cannot access comments, there
+  // is no need to check further.
+  $comment_types = linkchecker_scan_comment_types();
+  if (empty($comment_types) || !user_access('access comments')) {
+    return array();
+  }
+
+  // Get a list of comments containing the link, using addTag('node_access') to
+  // allow comment access modules to exclude comments that the current user
+  // does not have access to view.
+  if (!empty($comment_author_account)) {
+    $query = db_select('comment', 'c');
+    $query->addMetaData('base_table', 'comment');
+    $query->addTag('node_access');
+    $query->innerJoin('linkchecker_comment', 'lc', 'lc.cid = c.cid');
+    $query->condition('lc.lid', $link->lid);
+    $query->condition('c.uid', $comment_author_account->uid);
+    $query->fields('c', array('cid'));
+  }
+  else {
+    $query = db_select('comment', 'c');
+    $query->addMetaData('base_table', 'comment');
+    $query->addTag('node_access');
+    $query->innerJoin('linkchecker_comment', 'lc', 'lc.cid = c.cid');
+    $query->condition('lc.lid', $link->lid);
+    $query->fields('c', array('cid'));
+  }
+  $cids = $query->execute()->fetchCol();
+
+  // Return the array of comment IDs.
+  return $cids;
+}
+
+/**
+ * Returns IDs of blocks that contain a link which the current user is allowed to view.
+ *
+ * @param object $link
+ *   An object representing the link to check.
+ *
+ * @return array
+ *   An array of custom block IDs that contain the provided link and that the
+ *   current user is allowed to view.
+ */
+function _linkchecker_link_block_ids($link) {
+  // Exit if blocks are disabled.
+  if (!variable_get('linkchecker_scan_blocks', 0)) {
+    return array();
+  }
+
+  // Get the initial list of block IDs.
+  $bids = db_query('SELECT bid FROM {linkchecker_block_custom} WHERE lid = :lid', array(':lid' => $link->lid))->fetchCol();
+
+  // If the user can administer blocks, they're able to see all block content.
+  if (user_access('administer blocks')) {
+    return $bids;
+  }
+
+  // Otherwise, only return blocks that this user (or anonymous users) have
+  // access to.
+  global $user;
+  $rids = array_keys($user->roles);
+  $rids[] = DRUPAL_ANONYMOUS_RID;
+
+  $query = db_select('block', 'b');
+  $query->leftJoin('block_role', 'r', 'b.module = r.module AND b.delta = r.delta');
+  $query->condition('b.module', 'block');
+  $query->condition(db_or()
+    ->condition('r.rid', $rids, 'IN')
+    ->isNull('r.rid')
+  );
+  $query->fields('b', array('delta'));
+  $query->distinct();
+  $allowed_bids = $query->execute()->fetchCol();
+
+  return array_intersect($bids, $allowed_bids);
+}
+
+/**
+ * Implements hook_cron().
+ */
+function linkchecker_cron() {
+  // Remove outdated links no longer in use once per day.
+  if (REQUEST_TIME - \Drupal::state()->get('linkchecker.cleanup_links_last') >= 86400) {
+    _linkchecker_cleanup_links();
+    \Drupal::state()->set('linkchecker.cleanup_links_last', REQUEST_TIME);
+  }
+
+  // Run link checker in a new process, independent of cron.
+  if (\Drupal::moduleHandler()->moduleExists('httprl') && \Drupal::config('linkchecker.settings')->get('check.library') == 'httprl') {
+    // Setup callback options array; call _linkchecker_check_links() in the
+    // background.
+    $callback_options = array(array('function' => '_linkchecker_check_links'));
+    // Queue up the request.
+    httprl_queue_background_callback($callback_options);
+    // Execute request.
+    httprl_send_request();
+
+    // Exit here so we don't call _linkchecker_check_links() in this process.
+    return;
+  }
+  // Run the link checks the normal way.
+  _linkchecker_check_links();
+}
+
+/**
+ * Run link checks.
+ */
+function _linkchecker_check_links() {
+  $config = \Drupal::config('linkchecker.settings');
+
+  // Get max_execution_time from configuration, override 0 with 240 seconds.
+  $max_execution_time = ini_get('max_execution_time') == 0 ? 240 : ini_get('max_execution_time');
+  // Make sure we have enough time to validate all of the links.
+  drupal_set_time_limit($max_execution_time);
+
+  // Make sure this is the only process trying to run this function.
+  $lock = \Drupal::lock();
+  if ($lock->acquire(__FUNCTION__, $max_execution_time)) {
+    linkchecker_watchdog_log('linkchecker', 'Attempted to re-run link checks while they are already running.', array(), WATCHDOG_WARNING);
+    return FALSE;
+  }
+
+  $has_httprl = (\Drupal::moduleHandler()->moduleExists('httprl') && $config->get('check.library') == 'httprl');
+
+  // Do not confuse admins with a setting of maximum checkable links per cron
+  // run and guess that 2 links can be checked per second with 1 thread, what is
+  // nevertheless uncommon. The max_execution_time can be used to calculate
+  // a useful value that is higher, but not totally out of scope and limits the
+  // query result set to a reasonable size.
+  $linkchecker_check_connections_max = $config->get('check.connections_max');
+  $check_links_max_per_cron_run = ($has_httprl) ? ($linkchecker_check_connections_max * $max_execution_time) : $max_execution_time;
+
+  $linkchecker_check_links_interval = $config->get('check.interval');
+  $linkchecker_check_useragent = $config->get('check.useragent');
+
+  // Connection limit can be overridden via settings.php. Two connections is the
+  // limit defined in RFC http://www.ietf.org/rfc/rfc2616.txt. Modern browsers
+  // are typically using 6-8 connections and no more. Never use more and keep
+  // in mind that you can overload other people servers.
+  $linkchecker_check_domain_connections = $config->get('check.connections_max_per_domain');
+
+  // Get URLs for checking.
+  $links = db_query_range('SELECT * FROM {linkchecker_link} WHERE last_checked < :last_checked AND status = :status ORDER BY last_checked, lid ASC', 0, $check_links_max_per_cron_run, [':last_checked' => REQUEST_TIME - $linkchecker_check_links_interval, ':status' => 1]);
+  $links_remaining = $links->rowCount();
+
+  foreach ($links as $link) {
+    $headers = [];
+    $headers['User-Agent'] = $linkchecker_check_useragent;
+
+    $uri = @parse_url($link->url);
+
+    // URL contains a fragment.
+    if (in_array($link->method, ['HEAD', 'GET']) && !empty($uri['fragment'])) {
+      // We need the full content and not only the HEAD.
+      $link->method = 'GET';
+      // Request text content only (like Firefox/Chrome).
+      $headers['Accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8';
+    }
+    elseif ($link->method == 'GET') {
+      // Range: Only request the first 1024 bytes from remote server. This is
+      // required to prevent timeouts on URLs that are large downloads.
+      $headers['Range'] = 'bytes=0-1024';
+    }
+
+    // Add in the headers.
+    $options = [
+      'headers' => $headers,
+      'method' => $link->method,
+      'max_redirects' => 0,
+    ];
+
+    if ($has_httprl) {
+      // Define the callback and add the $link object to it.
+      // Notes:
+      // - 'global_timeout' does not require a timer_read('page'), as this job
+      //   runs in a new process, independent of cron.
+      $options += [
+        'global_connections' => $linkchecker_check_connections_max,
+        'global_timeout' => $max_execution_time - 30,
+        'domain_connections' => $linkchecker_check_domain_connections,
+        'callback' => [
+          [
+            'function' => '_linkchecker_status_handling',
+          ],
+          $link, // This need to be passed or it's not send back to _linkchecker_status_handling()
+        ],
+      ];
+      // Queue up the requests.
+      httprl_request($link->url, $options);
+      $links_remaining--;
+
+      // After all links are queued, run the url checks.
+      if ($links_remaining == 0) {
+        httprl_send_request();
+      }
+    }
+    else {
+      // Drupal core.
+      try {
+        // @fixme: Object is totally different in D8.
+        $response = \Drupal::httpClient()->request($link->method, $link->url, $options);
+        //$response = drupal_http_request($link->url, $options);
+
+        // @fixme
+        // Add 'redirect_code' property to core response object for consistency
+        // with HTTPRL object.
+        //if ($response->code == 301 && !isset($response->redirect_code)) {
+        //  $response->redirect_code = $response->code;
+        //}
+        // Add 'uri' property to core response object for 'fragment' check and
+        // consistency with HTTPRL object.
+        //$response->uri = $uri;
+
+        _linkchecker_status_handling($response, $link);
+
+        if ((timer_read('page') / 1000) > ($max_execution_time / 2)) {
+          // Stop once we have used over half of the maximum execution time.
+          break;
+        }
+      }
+      catch (RequestException $exception) {
+        watchdog_exception('linkchecker', $exception);
+      }
+    }
+  }
+
+  // Release the lock.
+  $lock->release(__FUNCTION__);
+  linkchecker_watchdog_log('linkchecker', 'Link checks completed.', array(), WATCHDOG_INFO);
+  linkchecker_watchdog_log('linkchecker', 'Memory usage: @memory_get_usage, Peak memory usage: @memory_get_peak_usage.', array('@memory_get_peak_usage' => format_size(memory_get_peak_usage()), '@memory_get_usage' => format_size(memory_get_usage())), WATCHDOG_DEBUG);
+  return TRUE;
+}
+
+/**
+ * Status code handling.
+ *
+ * @param object $response
+ *   An object containing the HTTP request headers, response code, headers,
+ *   data and redirect status.
+ * @param string $link
+ *   An object containing the url, lid and fail_count.
+ */
+function _linkchecker_status_handling(&$response, $link) {
+  $config = \Drupal::config('linkchecker.settings');
+  $ignore_response_codes = preg_split('/(\r\n?|\n)/', variable_get('linkchecker_ignore_response_codes', "200\n206\n302\n304\n401\n403"));
+
+  // - Prevent E_ALL warnings in DB updates for non-existing $response->error.
+  // - @todo drupal_http_request() may not provide an UTF8 encoded error message
+  //   what results in a database UPDATE failure. For more information, see
+  //   http://drupal.org/node/371495.
+  //   Workaround: ISO-8859-1 as source encoding may be wrong, but WFM.
+  if (!isset($response->error)) {
+    $response->error = '';
+  }
+  if (!isset($response->status_message)) {
+    $response->status_message = '';
+  }
+  $response->error = trim(drupal_convert_to_utf8($response->error, 'ISO-8859-1'));
+  $response->status_message = trim(drupal_convert_to_utf8($response->status_message, 'ISO-8859-1'));
+
+  // Destination anchors in HTML documents may be specified either by:
+  // - the A element (naming it with the name attribute)
+  // - or by any other element (naming with the id attribute)
+  // - and must not contain a key/value pair as these type of hash fragments are
+  //   typically used by AJAX applications to prevent additionally HTTP requests
+  //   e.g. http://www.example.com/ajax.html#key1=value1&key2=value2
+  // - and must not contain '/' or ',' as this are not normal anchors.
+  // - and '#top' is a reserved fragment that must not exist in a page.
+  // See http://www.w3.org/TR/html401/struct/links.html
+  if ($response->code == 200
+    && !empty($response->data)
+    && !empty($response->headers['content-type'])
+    && !empty($response->uri['fragment'])
+    && preg_match('/=|\/|,/', $response->uri['fragment']) == FALSE
+    && !in_array($response->uri['fragment'], array('#top'))
+    && in_array($response->headers['content-type'], array('text/html', 'application/xhtml+xml', 'application/xml'))
+    && !preg_match('/(\s[^>]*(name|id)(\s+)?=(\s+)?["\'])(' . preg_quote($response->uri['fragment'], '/') . ')(["\'][^>]*>)/i', $response->data)
+    ) {
+    // Override status code 200 with status code 404 so it can be handled with
+    // default status code 404 logic and custom error text.
+    $response->code = 404;
+    $response->status_message = $response->error = 'URL fragment identifier not found in content';
+  }
+
+  switch ($response->code) {
+    case -4: // HTTPRL: httprl_send_request timed out.
+      // Skip these and try them again next cron run.
+      break;
+
+    case -2: // HTTPRL: maximum allowed redirects exhausted.
+    case 301:
+      // Remote site send status code 301 and link needs an update.
+      db_update('linkchecker_link')
+        ->condition('lid', $link->lid)
+        ->fields(array(
+          'code' => $response->redirect_code,
+          'error' => $response->status_message,
+          'fail_count' => 0,
+          'last_checked' => time(),
+        ))
+        ->expression('fail_count', 'fail_count + 1')
+        ->execute();
+
+      // A HTTP status code of 301 tells us an existing link have changed to
+      // a new link. The remote site owner was so kind to provide us the new
+      // link and if we trust this change we are able to replace the old link
+      // with the new one without any hand work.
+      $auto_repair_301 = variable_get('linkchecker_action_status_code_301', 0);
+      if ($auto_repair_301 && $auto_repair_301 <= ($link->fail_count + 1) && valid_url($response->redirect_url, TRUE)) {
+        // Switch anonymous user to an admin.
+        $accountSwitcher = Drupal::service('account_switcher');
+        $accountSwitcher->switchTo(new UserSession(array('uid' => user_load_by_name($config->get('error.impersonate_account')))));
+
+        // NODES: Autorepair all nodes having this outdated link.
+        $result = db_query('SELECT nid FROM {linkchecker_node} WHERE lid = :lid', array(':lid' => $link->lid));
+        foreach ($result as $row) {
+          // Explicitly don't use node_load_multiple() or the module may run
+          // into issues like http://drupal.org/node/1210606. With this logic
+          // nodes can be updated until an out of memory occurs and further
+          // updates will be made on the remaining nodes only.
+          $node = node_load($row->nid);
+
+          // Has the node object loaded successfully?
+          if (is_object($node)) {
+            $node_original = clone $node;
+            $node = _linkchecker_replace_fields('node', $node->type, $node, $link->url, $response->redirect_url);
+
+            if ($node_original != $node) {
+              // Always use the default revision setting. For more information,
+              // see node_object_prepare().
+              $node_options = variable_get('node_options_' . $node->type, array('status', 'promote'));
+              $node->revision = in_array('revision', $node_options);
+
+              // Generate a log message for the node_revisions table, visible on
+              // the node's revisions tab.
+              $node->log = t('Changed permanently moved link in %node from %src to %dst.', array('%node' => url('node/' . $node->nid), '%src' => $link->url, '%dst' => $response->redirect_url));
+
+              // Save changed node and update the node link list.
+              node_save($node);
+              linkchecker_watchdog_log('linkchecker', 'Changed permanently moved link in %node from %src to %dst.', array('%node' => url('node/' . $node->nid), '%src' => $link->url, '%dst' => $response->redirect_url), WATCHDOG_INFO);
+            }
+            else {
+              linkchecker_watchdog_log('linkchecker', 'Link update in node failed. Permanently moved link %src not found in node %node. Manual fix required.', array('%node' => url('node/' . $row->nid), '%src' => $link->url), WATCHDOG_WARNING);
+            }
+          }
+          else {
+            linkchecker_watchdog_log('linkchecker', 'Loading node %node for update failed. Manual fix required.', array('%node' => $row->nid), WATCHDOG_ERROR);
+          }
+        }
+
+        // COMMENTS: Autorepair all comments having this outdated link.
+        $result = db_query('SELECT cid FROM {linkchecker_comment} WHERE lid = :lid', array(':lid' => $link->lid));
+        foreach ($result as $row) {
+          // Explicitly don't use comment_load_multiple() or the module may run
+          // into issues like http://drupal.org/node/1210606. With this logic
+          // comment can be updated until an out of memory occurs and further
+          // updates will be made on the remaining comments only.
+          $comment = comment_load($row->cid);
+
+          // Has the comment object loaded successfully?
+          if (is_object($comment)) {
+            $comment_original = clone $comment;
+
+            // Replace links in subject.
+            _linkchecker_link_replace($comment->subject, $link->url, $response->redirect_url);
+
+            // Replace links in fields.
+            $comment = _linkchecker_replace_fields('comment', $comment->node_type, $comment, $link->url, $response->redirect_url);
+
+            // Save changed comment and update the comment link list.
+            if ($comment_original != $comment) {
+              comment_save($comment);
+              linkchecker_watchdog_log('linkchecker', 'Changed permanently moved link in comment %comment from %src to %dst.', array('%comment' => $comment->cid, '%src' => $link->url, '%dst' => $response->redirect_url), WATCHDOG_INFO);
+            }
+            else {
+              linkchecker_watchdog_log('linkchecker', 'Link update in comment failed. Permanently moved link %src not found in comment %comment. Manual fix required.', array('%comment' => $comment->cid, '%src' => $link->url), WATCHDOG_WARNING);
+            }
+          }
+          else {
+            linkchecker_watchdog_log('linkchecker', 'Loading comment %comment for update failed. Manual fix required.', array('%comment' => $comment->cid), WATCHDOG_ERROR);
+          }
+        }
+
+        // CUSTOM BLOCKS: Autorepair all custom blocks having this outdated
+        // link.
+        $result = db_query('SELECT bid FROM {linkchecker_block_custom} WHERE lid = :lid', array(':lid' => $link->lid));
+        foreach ($result as $row) {
+          $block_custom = linkchecker_block_custom_block_get($row->bid);
+
+          // Has the custom block object loaded successfully?
+          if (is_object($block_custom)) {
+            $block_custom_original = clone $block_custom;
+
+            // Now replace the outdated link with the permanently moved one in
+            // all custom block fields.
+            _linkchecker_link_replace($block_custom->info, $link->url, $response->redirect_url);
+            _linkchecker_link_replace($block_custom->body['value'], $link->url, $response->redirect_url);
+
+            if ($block_custom_original != $block_custom) {
+              // Save changed block and update the block link list.
+              block_custom_block_save((array) $block_custom, $block_custom->delta);
+              // There is no hook that fires on block_custom_block_save(),
+              // therefore do link extraction programmatically.
+              _linkchecker_add_block_custom_links($block_custom, $block_custom->delta);
+              linkchecker_watchdog_log('linkchecker', 'Changed permanently moved link in custom block %bid from %src to %dst.', array('%bid' => $block_custom->delta, '%src' => $link->url, '%dst' => $response->redirect_url), WATCHDOG_INFO);
+            }
+            else {
+              linkchecker_watchdog_log('linkchecker', 'Link update in block failed. Permanently moved link %src not found in block %bid. Manual fix required.', array('%bid' => $block_custom->delta, '%src' => $link->url), WATCHDOG_WARNING);
+            }
+          }
+          else {
+            linkchecker_watchdog_log('linkchecker', 'Loading block %bid for update failed. Manual fix required.', array('%bid' => $block_custom->delta), WATCHDOG_ERROR);
+          }
+        }
+
+        // Revert user back to anonymous.
+        $accountSwitcher->switchBack();
+      }
+      else {
+        linkchecker_watchdog_log('linkchecker', 'Link %link has changed and needs to be updated.', array('%link' => $link->url), WATCHDOG_NOTICE, l(t('Broken links'), 'admin/reports/linkchecker'));
+      }
+      break;
+
+    case 404:
+      db_update('linkchecker_link')
+        ->condition('lid', $link->lid)
+        ->fields(array(
+          'code' => $response->code,
+          'error' => $response->error,
+          'fail_count' => 0,
+          'last_checked' => time(),
+        ))
+        ->expression('fail_count', 'fail_count + 1')
+        ->execute();
+      linkchecker_watchdog_log('linkchecker', 'Broken link %link has been found.', array('%link' => $link->url), WATCHDOG_NOTICE, l(t('Broken links'), 'admin/reports/linkchecker'));
+
+      // If unpublishing limit is reached, unpublish all nodes having this link.
+      $linkchecker_action_status_code_404 = variable_get('linkchecker_action_status_code_404', 0);
+      if ($linkchecker_action_status_code_404 && $linkchecker_action_status_code_404 <= ($link->fail_count + 1)) {
+        // Switch anonymous user to an admin.
+        $accountSwitcher = Drupal::service('account_switcher');
+        $accountSwitcher->switchTo(new UserSession(array('uid' => user_load_by_name($config->get('error.impersonate_account')))));
+        _linkchecker_unpublish_nodes($link->lid);
+        $accountSwitcher->switchBack();
+      }
+      break;
+
+    case 405:
+      // - 405: Special error handling if method is not allowed. Switch link
+      //   checking to GET method and try again.
+      db_update('linkchecker_link')
+        ->condition('lid', $link->lid)
+        ->fields(array(
+          'method' => 'GET',
+          'code' => $response->code,
+          'error' => $response->error,
+          'fail_count' => 0,
+          'last_checked' => time(),
+        ))
+        ->expression('fail_count', 'fail_count + 1')
+        ->execute();
+
+      linkchecker_watchdog_log('linkchecker', 'Method HEAD is not allowed for link %link. Method has been changed to GET.', array('%link' => $link->url), WATCHDOG_INFO, l(t('Broken links'), 'admin/reports/linkchecker'));
+      break;
+
+    case 500:
+      // - 500: Like WGET, try with GET on "500 Internal server error".
+      // - If GET also fails with status code 500, than the link is broken.
+      if ($link->method == 'GET' && $response->code == 500) {
+        db_update('linkchecker_link')
+        ->condition('lid', $link->lid)
+        ->fields(array(
+          'code' => $response->code,
+          'error' => $response->error,
+          'fail_count' => 0,
+          'last_checked' => time(),
+        ))
+        ->expression('fail_count', 'fail_count + 1')
+        ->execute();
+
+        linkchecker_watchdog_log('linkchecker', 'Broken link %link has been found.', array('%link' => $link->url), WATCHDOG_NOTICE, l(t('Broken links'), 'admin/reports/linkchecker'));
+      }
+      else {
+        db_update('linkchecker_link')
+        ->condition('lid', $link->lid)
+        ->fields(array(
+          'method' => 'GET',
+          'code' => $response->code,
+          'error' => $response->error,
+          'fail_count' => 0,
+          'last_checked' => time(),
+        ))
+        ->expression('fail_count', 'fail_count + 1')
+        ->execute();
+
+        linkchecker_watchdog_log('linkchecker', 'Internal server error for link %link. Method has been changed to GET.', array('%link' => $link->url), WATCHDOG_INFO, l(t('Broken links'), 'admin/reports/linkchecker'));
+      }
+      break;
+
+    default:
+      // Don't treat ignored response codes as errors.
+      if (in_array($response->code, $ignore_response_codes)) {
+        db_update('linkchecker_link')
+          ->condition('lid', $link->lid)
+          ->fields(array(
+            'code' => $response->code,
+            'error' => $response->error,
+            'fail_count' => 0,
+            'last_checked' => time(),
+          ))
+          ->execute();
+        // linkchecker_watchdog_log('linkchecker', 'Unhandled link error %link has been found.', array('%link' => $link->url), WATCHDOG_ERROR, l(t('Broken links'), 'admin/reports/linkchecker'));
+      }
+      else {
+        db_update('linkchecker_link')
+          ->condition('lid', $link->lid)
+          ->fields(array(
+            'code' => $response->code,
+            'error' => $response->error,
+            'fail_count' => 0,
+            'last_checked' => time(),
+          ))
+          ->expression('fail_count', 'fail_count + 1')
+          ->execute();
+        // linkchecker_watchdog_log('linkchecker', 'Unhandled link error %link has been found.', array('%link' => $link->url), WATCHDOG_ERROR, l(t('Broken links'), 'admin/reports/linkchecker'));
+      }
+  }
+
+  // Free Memory.
+  $response = new stdClass();
+}
+
+/**
+ * @fixme: remove after migration
+ * Implements hook_node_type_delete().
+ */
+function linkchecker_node_type_delete($info) {
+  variable_del('linkchecker_scan_node_' . $info->type);
+  variable_del('linkchecker_scan_comment_' . $info->type);
+}
+
+/**
+ * Implements hook_node_prepare().
+ */
+function linkchecker_node_prepare($node) {
+  // Node edit tab is viewed.
+  if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == 'edit' && isset($node->nid)) {
+    // Show a message on node edit page if a link check failed once or more.
+    $ignore_response_codes = preg_split('/(\r\n?|\n)/', variable_get('linkchecker_ignore_response_codes', "200\n206\n302\n304\n401\n403"));
+    $links = db_query('SELECT ll.* FROM {linkchecker_node} ln INNER JOIN {linkchecker_link} ll ON ln.lid = ll.lid WHERE ln.nid = :nid AND ll.fail_count > :fail_count AND ll.status = :status AND ll.code NOT IN (:codes)', array(':nid' => $node->nid, ':fail_count' => 0, ':status' => 1, ':codes' => $ignore_response_codes));
+    foreach ($links as $link) {
+      if (_linkchecker_link_access($link)) {
+        drupal_set_message(format_plural($link->fail_count, 'Link check of <a href="@url">@url</a> failed once (status code: @code).', 'Link check of <a href="@url">@url</a> failed @count times (status code: @code).', array('@url' => $link->url, '@code' => $link->code)), 'warning', FALSE);
+      }
+    }
+  }
+}
+
+/**
+ * Implements hook_node_delete().
+ */
+function linkchecker_node_delete($node) {
+  _linkchecker_delete_node_links($node->nid);
+}
+
+/**
+ * Implements hook_node_insert().
+ */
+function linkchecker_node_insert($node) {
+  // Every moderation module saving a forward revision needs to exit here.
+  // Please see _linkchecker_isdefaultrevision() for more details.
+  // @todo: Refactor this workaround under D8.
+  if (!_linkchecker_isdefaultrevision($node)) {
+    return;
+  }
+
+  // The node is going to be published.
+  if (variable_get('linkchecker_scan_node_' . $node->type, FALSE) && $node->status == NODE_PUBLISHED) {
+    _linkchecker_add_node_links($node);
+  }
+}
+
+/**
+ * Implements hook_node_update().
+ */
+function linkchecker_node_update($node) {
+  // Every moderation module saving a forward revision needs to exit here.
+  // Please see _linkchecker_isdefaultrevision() for more details.
+  // @todo: Refactor this workaround under D8.
+  if (!_linkchecker_isdefaultrevision($node)) {
+    return;
+  }
+
+  // The node is going to be published.
+  if (variable_get('linkchecker_scan_node_' . $node->type, FALSE) && $node->status == NODE_PUBLISHED) {
+    _linkchecker_add_node_links($node);
+  }
+  else {
+    // The node is going to be unpublished.
+    linkchecker_node_delete($node);
+  }
+}
+
+/**
+ * Implements hook_comment_delete().
+ */
+function linkchecker_comment_delete($comment) {
+  _linkchecker_delete_comment_links($comment->cid);
+}
+
+/**
+ * Implements hook_comment_insert().
+ */
+function linkchecker_comment_insert($comment) {
+  // The comment is going to be published.
+  $node_type = db_query('SELECT type FROM {node} WHERE nid = :nid', array(':nid' => $comment->nid))->fetchField();
+  if (variable_get('linkchecker_scan_comment_' . $node_type, FALSE) && $comment->status == COMMENT_PUBLISHED) {
+    _linkchecker_add_comment_links($comment);
+  }
+}
+
+/**
+ * Implements hook_comment_update().
+ */
+function linkchecker_comment_update($comment) {
+  // The node is going to be published.
+  $node_type = db_query('SELECT type FROM {node} WHERE nid = :nid', array(':nid' => $comment->nid))->fetchField();
+  if (variable_get('linkchecker_scan_comment_' . $node_type, FALSE) && $comment->status == COMMENT_PUBLISHED) {
+    _linkchecker_add_comment_links($comment);
+  }
+  else {
+    // The node is going to be unpublished.
+    linkchecker_comment_delete($comment);
+  }
+}
+
+/**
+ * Implements hook_form_alter().
+ */
+function linkchecker_form_alter(&$form, FormStateInterface $form_state, $form_id) {
+  switch ($form_id) {
+    // Catch the custom block add/configure form and add custom submit handler.
+    case 'block_add_block_form':
+      // Add custom submit handler to custom block add form.
+      $form['#submit'][] = 'linkchecker_block_custom_add_form_submit';
+      break;
+
+    case 'block_admin_configure':
+      // When displaying the form, show the broken links warning.
+      if (empty($form_state['input']) && is_numeric(arg(5))) {
+        // Show a message on custom block edit page if a link check failed once
+        // or more often.
+        $ignore_response_codes = preg_split('/(\r\n?|\n)/', \Drupal::config('linkchecker.settings')->get('error.ignore_response_codes'));
+        $links = db_query('SELECT ll.* FROM {linkchecker_block_custom} lb INNER JOIN {linkchecker_link} ll ON lb.lid = ll.lid WHERE lb.bid = :bid AND ll.fail_count > :fail_count AND ll.status = :status AND ll.code NOT IN (:codes)', array(':bid' => arg(5), ':fail_count' => 0, ':status' => 1, ':codes' => $ignore_response_codes));
+        foreach ($links as $link) {
+          if (_linkchecker_link_access($link)) {
+            drupal_set_message(format_plural($link->fail_count, 'Link check of <a href=":url">:url</a> failed once (status code: @code).', 'Link check of <a href=":url">:url</a> failed @count times (status code: @code).', array(':url' => $link->url, '@code' => $link->code)), 'warning', FALSE);
+          }
+        }
+      }
+
+      // Add custom submit handler to custom block configuration form.
+      $form['#submit'][] = 'linkchecker_block_custom_configure_form_submit';
+      break;
+
+    case 'block_custom_block_delete':
+      // Add custom submit handler to custom block delete form.
+      $form['#submit'][] = 'linkchecker_block_custom_delete_form_submit';
+      break;
+  }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter() for \Drupal\node\NodeTypeForm.
+ *
+ * Adds linkchecker options to the node type form.
+ *
+ * @see NodeTypeForm::form()
+ * @see linkchecker_form_node_type_form_submit()
+ */
+function linkchecker_form_node_type_form_alter(&$form, FormStateInterface $form_state) {
+  /** @var \Drupal\node\NodeTypeInterface $type */
+  $type = $form_state->getFormObject()->getEntity();
+  $form['linkchecker'] = [
+    '#type' => 'details',
+    '#title' => t('Link checker'),
+    '#attached' => [
+      'library' => ['linkchecker/linkchecker.content_types'],
+    ],
+    '#group' => 'additional_settings',
+  ];
+  $form['linkchecker']['linkchecker_scan_node'] = [
+    '#type' => 'checkbox',
+    '#title' => t('Scan content'),
+    '#description' => t('Enables link checking for this content type.'),
+    '#default_value' => $type->getThirdPartySetting('linkchecker', 'scan_node', FALSE),
+  ];
+  if (\Drupal::moduleHandler()->moduleExists('comment')) {
+    $form['linkchecker']['linkchecker_scan_comment'] = [
+      '#type' => 'checkbox',
+      '#title' => t('Scan comments'),
+      '#description' => t('Enables link checking for comments.'),
+      '#default_value' => $type->getThirdPartySetting('linkchecker', 'scan_comment', FALSE),
+    ];
+  }
+
+  $form['#submit'][] = 'linkchecker_form_node_type_form_submit';
+  $form['#entity_builders'][] = 'linkchecker_form_node_type_form_builder';
+}
+
+/**
+ * Submit handler for forms with linkchecker options.
+ *
+ * @see linkchecker_form_node_type_form_alter()
+ */
+function linkchecker_form_node_type_form_submit(&$form, FormStateInterface $form_state) {
+  $node_type = $form_state->getValue('type');
+  if (!$form['linkchecker']['linkchecker_scan_node']['#default_value'] && $form_state->getValue('linkchecker_scan_node')) {
+    // We need to scan this node-type now.
+    module_load_include('inc', 'linkchecker', 'linkchecker.batch');
+    batch_set(_linkchecker_batch_import_nodes(array($node_type)));
+  }
+
+  // Default to TRUE if comment module isn't enabled, we don't care.
+  $original_linkchecker_comment_state = TRUE;
+  if (\Drupal::moduleHandler()->moduleExists('comment')) {
+    $original_linkchecker_comment_state = $form['linkchecker']['linkchecker_scan_comment']['#default_value'];
+  }
+  // Use !empty here for when comment module isn't enabled and there is no
+  // field.
+  if (!$original_linkchecker_comment_state && !empty($form_state->getValue('linkchecker_scan_comment'))) {
+    // We need to scan comments for this node-type now.
+    module_load_include('inc', 'linkchecker', 'linkchecker.batch');
+    batch_set(_linkchecker_batch_import_comments(array($node_type)));
+  }
+}
+
+/**
+ * Entity builder for the node type form with linkchecker options.
+ *
+ * @see linkchecker_form_node_type_form_alter()
+ */
+function linkchecker_form_node_type_form_builder($entity_type, NodeTypeInterface $type, &$form, FormStateInterface $form_state) {
+  $type->setThirdPartySetting('linkchecker', 'scan_node', $form_state->getValue('linkchecker_scan_node'));
+  $type->setThirdPartySetting('linkchecker', 'scan_comment', $form_state->getValue('linkchecker_scan_comment'));
+}
+
+/**
+ * Implements hook_form_BASE_FORM_ID_alter().
+ */
+function linkchecker_form_comment_form_alter(&$form, &$form_state, $form_id) {
+  // When displaying the form as 'view' or 'preview', show the broken links
+  // warning.
+  if ((empty($form_state['input']) || (isset($form_state['input']['op']) && $form_state['input']['op'] == t('Preview'))) && arg(0) == 'comment' && is_numeric(arg(1)) && arg(2) == 'edit') {
+    // Show a message on comment edit page if a link check failed once or
+    // more often.
+    $ignore_response_codes = preg_split('/(\r\n?|\n)/', variable_get('linkchecker_ignore_response_codes', "200\n206\n302\n304\n401\n403"));
+    $links = db_query('SELECT ll.* FROM {linkchecker_comment} lc INNER JOIN {linkchecker_link} ll ON lc.lid = ll.lid WHERE lc.cid = :cid AND ll.fail_count > :fail_count AND ll.status = :status AND ll.code NOT IN (:codes)', array(':cid' => arg(1), ':fail_count' => 0, ':status' => 1, ':codes' => $ignore_response_codes));
+    foreach ($links as $link) {
+      if (_linkchecker_link_access($link)) {
+        drupal_set_message(format_plural($link->fail_count, 'Link check of <a href="@url">@url</a> failed once (status code: @code).', 'Link check of <a href="@url">@url</a> failed @count times (status code: @code).', array('@url' => $link->url, '@code' => $link->code)), 'warning', FALSE);
+      }
+    }
+  }
+}
+
+/**
+ * Custom submit handler for block add page.
+ */
+function linkchecker_block_custom_add_form_submit($form, &$form_state) {
+  if (variable_get('linkchecker_scan_blocks', 0)) {
+    $bid = db_query('SELECT MAX(bid) FROM {block_custom}')->fetchField();
+    _linkchecker_add_block_custom_links($form_state['values'], $bid);
+  }
+}
+
+/**
+ * Custom submit handler for block configure page.
+ */
+function linkchecker_block_custom_configure_form_submit($form, &$form_state) {
+  if (variable_get('linkchecker_scan_blocks', 0)) {
+    _linkchecker_add_block_custom_links($form_state['values'], $form_state['values']['delta']);
+  }
+}
+
+/**
+ * Custom submit handler for block delete page.
+ */
+function linkchecker_block_custom_delete_form_submit($form, &$form_state) {
+  _linkchecker_delete_block_custom_links($form_state['values']['bid']);
+}
+
+/**
+ * Returns information from database about a user-created (custom) block.
+ *
+ * @param int $bid
+ *   ID of the block to get information for.
+ *
+ * @return object
+ *   Associative object of information stored in the database for this block.
+ *   Object keys:
+ *   - module: 'block' as the source of the custom blocks data.
+ *   - delta: Block ID.
+ *   - info: Block description.
+ *   - body['value']: Block contents.
+ *   - body['format']: Filter ID of the filter format for the body.
+ */
+function linkchecker_block_custom_block_get($bid) {
+  $block_custom = block_custom_block_get($bid);
+
+  if ($block_custom) {
+    $block = new stdClass();
+    $block->module = 'block';
+    $block->delta = $block_custom['bid'];
+    $block->info = $block_custom['info'];
+    $block->body = array();
+    $block->body['value'] = $block_custom['body'];
+    $block->body['format'] = $block_custom['format'];
+  }
+  else {
+    $block = FALSE;
+  }
+
+  return $block;
+}
+
+/**
+ * Extracts links from a node.
+ *
+ * @param object $node
+ *   The fully populated node object.
+ * @param bool $return_field_names
+ *   If set to TRUE, the returned array will contain the link URLs as keys, and
+ *   each element will be an array containing all field names in which the URL
+ *   is found. Otherwise, a simple array of URLs will be returned.
+ *
+ * @return array
+ *   An array whose keys are fully qualified and unique URLs found in the node
+ *   (as returned by _linkchecker_extract_links()), or a more complex
+ *   structured array (see above) if $return_field_names is TRUE.
+ */
+function _linkchecker_extract_node_links($node, $return_field_names = FALSE) {
+
+  $filter = new stdClass();
+  $filter->settings['filter_url_length'] = 72;
+
+  // Create array of node fields to scan.
+  $text_items = array();
+  $text_items_by_field = array();
+
+  // Add fields typically not used for urls to the bottom. This way a link may
+  // found earlier while looping over $text_items_by_field below.
+  $text_items_by_field = array_merge($text_items_by_field, _linkchecker_parse_fields('node', $node->type, $node, TRUE));
+  $text_items_by_field['title'][] = _filter_url($node->title, $filter);
+  $text_items = _linkchecker_array_values_recursive($text_items_by_field);
+
+  // Get the absolute node path for extraction of relative links.
+  $languages = language_list();
+  // Note: An "undefined language" (value: 'und') isn't listed in the available
+  // languages variable $languages.
+  $url_options = (empty($node->language) || empty($languages[$node->language])) ? array('absolute' => TRUE) : array('language' => $languages[$node->language], 'absolute' => TRUE);
+  $path = url('node/' . $node->nid, $url_options);
+
+  // Extract all links in a node.
+  $links = _linkchecker_extract_links(implode(' ', $text_items), $path);
+
+  // Return either the array of links, or an array of field names containing
+  // each link, depending on what was requested.
+  if (!$return_field_names) {
+    return $links;
+  }
+  else {
+    $field_names = array();
+    foreach ($text_items_by_field as $field_name => $items) {
+      foreach ($items as $item) {
+        foreach ($links as $uri => $link) {
+          // We only need to do a quick check here to see if the URL appears
+          // anywhere in the text; if so, that means users with access to this
+          // field will be able to see the URL (and any private data such as
+          // passwords contained in it). This is sufficient for the purposes of
+          // _linkchecker_link_node_ids(), where this information is used.
+          foreach ($link as $original_link) {
+            if (strpos($item, $original_link) !== FALSE) {
+              $field_names[$uri][$field_name] = $field_name;
+            }
+            // URLs in $links have been auto-decoded by DOMDocument->loadHTML
+            // and does not provide the RAW url with html special chars.
+            // NOTE: htmlspecialchars() is 30% slower than str_replace().
+            elseif (strpos($item, str_replace('&', '&amp;', $original_link)) !== FALSE) {
+              $field_names[$uri][$field_name] = $field_name;
+            }
+          }
+        }
+      }
+    }
+
+    return $field_names;
+  }
+}
+
+/**
+ * Add node links to database.
+ *
+ * @param object $node
+ *   The fully populated node object.
+ * @param bool $skip_missing_links_detection
+ *   To prevent endless batch loops the value need to be TRUE. With FALSE
+ *   the need for content re-scans is detected by the number of missing links.
+ */
+function _linkchecker_add_node_links($node, $skip_missing_links_detection = FALSE) {
+  $links = array_keys(_linkchecker_extract_node_links($node));
+
+  // Node have links.
+  if (!empty($links)) {
+    // Remove all links from the links array already in the database and only
+    // add missing links to database.
+    $missing_links = _linkchecker_node_links_missing($node->nid, $links);
+
+    // Only add links to database that do not exists.
+    $i = 0;
+    foreach ($missing_links as $url) {
+      $urlhash = drupal_hash_base64($url);
+      $link = db_query('SELECT lid FROM {linkchecker_link} WHERE urlhash = :urlhash', array(':urlhash' => $urlhash))->fetchObject();
+      if (!$link) {
+        $link = new stdClass();
+        $link->urlhash = $urlhash;
+        $link->url = $url;
+        $link->status = _linkchecker_link_check_status_filter($url);
+        drupal_write_record('linkchecker_link', $link);
+      }
+      db_insert('linkchecker_node')
+        ->fields(array(
+          'nid' => $node->nid,
+          'lid' => $link->lid,
+        ))
+        ->execute();
+
+      // Break processing if max links limit per run has been reached.
+      $i++;
+      if ($i >= LINKCHECKER_SCAN_MAX_LINKS_PER_RUN) {
+        break;
+      }
+    }
+
+    // The first chunk of links not yet found in the {linkchecker_link} table
+    // have now been imported by the above code. If the number of missing links
+    // still exceeds the scan limit defined in LINKCHECKER_SCAN_MAX_LINKS_PER_RUN
+    // the content need to be re-scanned until all links have been collected and
+    // saved in {linkchecker_link} table.
+    //
+    // Above code has already scanned a number of LINKCHECKER_SCAN_MAX_LINKS_PER_RUN
+    // links and need to be substracted from the number of missing links to
+    // calculate the correct number of re-scan rounds.
+    //
+    // To prevent endless loops the $skip_missing_links_detection need to be TRUE.
+    // This value will be set by the calling batch process that already knows
+    // that it is running a batch job and the number of required re-scan rounds.
+    $missing_links_count = count($missing_links) - LINKCHECKER_SCAN_MAX_LINKS_PER_RUN;
+    if (!$skip_missing_links_detection && $missing_links_count > 0) {
+      module_load_include('inc', 'linkchecker', 'linkchecker.batch');
+      batch_set(_linkchecker_batch_import_single_node($node->nid, $missing_links_count));
+
+      // If batches were set in the submit handlers, we process them now,
+      // possibly ending execution. We make sure we do not react to the batch
+      // that is already being processed (if a batch operation performs a
+      // drupal_execute).
+      if ($batch = &batch_get() && !isset($batch['current_set'])) {
+        batch_process('node/' . $node->nid);
+      }
+    }
+  }
+
+  // Remove dead link references for cleanup reasons as very last step.
+  _linkchecker_cleanup_node_references($node->nid, $links);
+}
+
+/**
+ * Add comment links to database.
+ *
+ * @param object $comment
+ *   The fully populated comment object.
+ * @param bool $skip_missing_links_detection
+ *   To prevent endless batch loops the value need to be TRUE. With FALSE
+ *   the need for content re-scans is detected by the number of missing links.
+ */
+function _linkchecker_add_comment_links($comment, $skip_missing_links_detection = FALSE) {
+
+  $filter = new stdClass();
+  $filter->settings['filter_url_length'] = 72;
+
+  // Create array of comment fields to scan.
+  $text_items = array();
+  $text_items[] = _filter_url($comment->subject, $filter);
+  $text_items = array_merge($text_items, _linkchecker_parse_fields('comment', $comment->node_type, $comment));
+
+  // Get the absolute node path for extraction of relative links.
+  $languages = language_list();
+  $node = node_load($comment->nid);
+  $url_options = (empty($node->language) || empty($languages[$node->language])) ? array('absolute' => TRUE) : array('language' => $languages[$node->language], 'absolute' => TRUE);
+  $path = url('node/' . $comment->nid, $url_options);
+
+  // Extract all links in a comment.
+  $links = array_keys(_linkchecker_extract_links(implode(' ', $text_items), $path));
+
+  // Comment have links.
+  if (!empty($links)) {
+    // Remove all links from the links array already in the database and only
+    // add missing links to database.
+    $missing_links = _linkchecker_comment_links_missing($comment->cid, $links);
+
+    // Only add unique links to database that do not exist.
+    $i = 0;
+    foreach ($missing_links as $url) {
+      $urlhash = drupal_hash_base64($url);
+      $link = db_query('SELECT lid FROM {linkchecker_link} WHERE urlhash = :urlhash', array(':urlhash' => $urlhash))->fetchObject();
+      if (!$link) {
+        $link = new stdClass();
+        $link->urlhash = $urlhash;
+        $link->url = $url;
+        $link->status = _linkchecker_link_check_status_filter($url);
+        drupal_write_record('linkchecker_link', $link);
+      }
+      db_insert('linkchecker_comment')
+        ->fields(array(
+          'cid' => $comment->cid,
+          'lid' => $link->lid,
+        ))
+        ->execute();
+
+      // Break processing if max links limit per run has been reached.
+      $i++;
+      if ($i >= LINKCHECKER_SCAN_MAX_LINKS_PER_RUN) {
+        break;
+      }
+    }
+
+    // The first chunk of links not yet found in the {linkchecker_link} table
+    // have now been imported by the above code. If the number of missing links
+    // still exceeds the scan limit defined in LINKCHECKER_SCAN_MAX_LINKS_PER_RUN
+    // the content need to be re-scanned until all links have been collected and
+    // saved in {linkchecker_link} table.
+    //
+    // Above code has already scanned a number of LINKCHECKER_SCAN_MAX_LINKS_PER_RUN
+    // links and need to be substracted from the number of missing links to
+    // calculate the correct number of re-scan rounds.
+    //
+    // To prevent endless loops the $skip_missing_links_detection need to be TRUE.
+    // This value will be set by the calling batch process that already knows
+    // that it is running a batch job and the number of required re-scan rounds.
+    $missing_links_count = count($missing_links) - LINKCHECKER_SCAN_MAX_LINKS_PER_RUN;
+    if (!$skip_missing_links_detection && $missing_links_count > 0) {
+      module_load_include('inc', 'linkchecker', 'linkchecker.batch');
+      batch_set(_linkchecker_batch_import_single_comment($comment->cid, $missing_links_count));
+
+      // If batches were set in the submit handlers, we process them now,
+      // possibly ending execution. We make sure we do not react to the batch
+      // that is already being processed (if a batch operation performs a
+      // drupal_execute).
+      if ($batch = &batch_get() && !isset($batch['current_set'])) {
+        batch_process('node/' . $comment->nid);
+      }
+    }
+  }
+
+  // Remove dead link references for cleanup reasons as very last step.
+  _linkchecker_cleanup_comment_references($comment->cid, $links);
+}
+
+/**
+ * Add custom block links to database.
+ *
+ * @param array|object $block_custom
+ *   The fully populated custom block object.
+ * @param int $bid
+ *   Block id from table {block}.bid.
+ * @param bool $skip_missing_links_detection
+ *   To prevent endless batch loops the value need to be TRUE. With FALSE
+ *   the need for content re-scans is detected by the number of missing links.
+ */
+function _linkchecker_add_block_custom_links($block_custom, $bid, $skip_missing_links_detection = FALSE) {
+  // Convert custom block array to object.
+  // @todo: Are we able to remove this global conversion?
+  $block_custom = (object) $block_custom;
+
+  // Custom blocks really suxxx as it's very inconsistent core logic (values are
+  // integers or strings) and there are no usable hooks. Try to workaround this
+  // bad logic as good as possible to prevent warnings/errors.
+  // NOTE: Only custom blocks from block.module are supported. Skip all others.
+  if ($block_custom->module != 'block' || !is_numeric($block_custom->delta) || !is_numeric($bid) || $block_custom->delta != $bid) {
+    return;
+  }
+
+  $filter = new stdClass();
+  $filter->settings['filter_url_length'] = 72;
+
+  // Create array of custom block fields to scan. All fields cannot exists.
+  $text_items = array();
+  if (!empty($block_custom->info)) {
+    $text_items[] = _filter_url($block_custom->info, $filter);
+  }
+  // $block_custom from editing/scanning a block. See block_custom_block_save().
+  if (!empty($block_custom->body) && is_array($block_custom->body) && array_key_exists('value', $block_custom->body) && array_key_exists('format', $block_custom->body)) {
+    $text_items[] = _linkchecker_check_markup($block_custom->body['value'], $block_custom->body['format']);
+  }
+
+  // Extract all links in a custom block.
+  $links = array_keys(_linkchecker_extract_links(implode(' ', $text_items)));
+
+  // Custom block has links.
+  if (!empty($links)) {
+    // Remove all links from the links array already in the database and only
+    // add missing links to database.
+    $missing_links = _linkchecker_block_custom_links_missing($bid, $links);
+
+    // Only add unique links to database that do not exist.
+    $i = 0;
+    foreach ($missing_links as $url) {
+      $urlhash = drupal_hash_base64($url);
+      $link = db_query('SELECT lid FROM {linkchecker_link} WHERE urlhash = :urlhash', array(':urlhash' => $urlhash))->fetchObject();
+      if (!$link) {
+        $link = new stdClass();
+        $link->urlhash = $urlhash;
+        $link->url = $url;
+        $link->status = _linkchecker_link_check_status_filter($url);
+        drupal_write_record('linkchecker_link', $link);
+      }
+      db_insert('linkchecker_block_custom')
+        ->fields(array(
+          'bid' => $bid,
+          'lid' => $link->lid,
+        ))
+        ->execute();
+
+      // Break processing if max links limit per run has been reached.
+      $i++;
+      if ($i >= LINKCHECKER_SCAN_MAX_LINKS_PER_RUN) {
+        break;
+      }
+    }
+
+    // The first chunk of links not yet found in the {linkchecker_link} table
+    // have now been imported by the above code. If the number of missing links
+    // still exceeds the scan limit defined in LINKCHECKER_SCAN_MAX_LINKS_PER_RUN
+    // the content need to be re-scanned until all links have been collected and
+    // saved in {linkchecker_link} table.
+    //
+    // Above code has already scanned a number of LINKCHECKER_SCAN_MAX_LINKS_PER_RUN
+    // links and need to be substracted from the number of missing links to
+    // calculate the correct number of re-scan rounds.
+    //
+    // To prevent endless loops the $skip_missing_links_detection need to be TRUE.
+    // This value will be set by the calling batch process that already knows
+    // that it is running a batch job and the number of required re-scan rounds.
+    $missing_links_count = count($missing_links) - LINKCHECKER_SCAN_MAX_LINKS_PER_RUN;
+    if (!$skip_missing_links_detection && $missing_links_count > 0) {
+      module_load_include('inc', 'linkchecker', 'linkchecker.batch');
+      batch_set(_linkchecker_batch_import_single_block_custom($bid, $missing_links_count));
+
+      // If batches were set in the submit handlers, we process them now,
+      // possibly ending execution. We make sure we do not react to the batch
+      // that is already being processed (if a batch operation performs a
+      // drupal_execute).
+      if ($batch = &batch_get() && !isset($batch['current_set'])) {
+        batch_process('admin/structure/block');
+      }
+    }
+  }
+
+  // Remove dead link references for cleanup reasons as very last step.
+  _linkchecker_cleanup_block_custom_references($bid, $links);
+}
+
+/**
+ * Remove all node references to links in the linkchecker_node table.
+ *
+ * @param int $nid
+ *   The node ID.
+ */
+function _linkchecker_delete_node_links($nid) {
+  db_delete('linkchecker_node')
+    ->condition('nid', $nid)
+    ->execute();
+}
+
+/**
+ * Remove all comment references to links in the linkchecker_comment table.
+ *
+ * @param int $cid
+ *   The comment ID.
+ */
+function _linkchecker_delete_comment_links($cid) {
+  db_delete('linkchecker_comment')
+    ->condition('cid', $cid)
+    ->execute();
+}
+
+/**
+ * Remove all block references to links in the linkchecker_block_custom table.
+ *
+ * @param int $bid
+ *   The block ID.
+ *
+ */
+function _linkchecker_delete_block_custom_links($bid) {
+  db_delete('linkchecker_block_custom')
+    ->condition('bid', $bid)
+    ->execute();
+}
+
+/**
+ * Cleanup no longer used node references to links in the linkchecker_node table.
+ *
+ * @param int $nid
+ *   The node ID.
+ * @param array $links
+ */
+function _linkchecker_cleanup_node_references($nid = 0, $links = array()) {
+  if (empty($links)) {
+    // Node do not have links. Delete all references if exists.
+    db_delete('linkchecker_node')
+      ->condition('nid', $nid)
+      ->execute();
+  }
+  else {
+    // The node still have more than one link, but other links may have been
+    // removed and links no longer in the content need to be deleted from the
+    // linkchecker_node reference table.
+    $subquery = db_select('linkchecker_link')
+      ->fields('linkchecker_link', array('lid'))
+      ->condition('urlhash', array_map('drupal_hash_base64', $links), 'IN');
+
+    db_delete('linkchecker_node')
+      ->condition('nid', $nid)
+      ->condition('lid', $subquery, 'NOT IN')
+      ->execute();
+  }
+}
+
+/**
+ * Cleanup no longer used comment references to links in the linkchecker_comment table.
+ *
+ * @param int $cid
+ *   The comment ID.
+ * @param array $links
+ */
+function _linkchecker_cleanup_comment_references($cid = 0, $links = array()) {
+  if (empty($links)) {
+    // Comment do not have links. Delete all references if exists.
+    db_delete('linkchecker_comment')
+      ->condition('cid', $cid)
+      ->execute();
+  }
+  else {
+    // The comment still have more than one link, but other links may have been
+    // removed and links no longer in the content need to be deleted from the
+    // linkchecker_comment reference table.
+    $subquery = db_select('linkchecker_link', 'll')
+      ->fields('ll', array('lid'))
+      ->condition('ll.urlhash', array_map('drupal_hash_base64', $links), 'IN');
+
+    db_delete('linkchecker_comment')
+      ->condition('cid', $cid)
+      ->condition('lid', $subquery, 'NOT IN')
+      ->execute();
+  }
+}
+
+/**
+ * Cleanup no longer used custom block references to links in the linkchecker_block_custom table.
+ *
+ * @param int $bid
+ *   The block ID.
+ * @param array $links
+ */
+function _linkchecker_cleanup_block_custom_references($bid = 0, $links = array()) {
+  if (empty($links)) {
+    // Block do not have links. Delete all references if exists.
+    db_delete('linkchecker_block_custom')
+      ->condition('bid', $bid)
+      ->execute();
+  }
+  else {
+    // The block still have more than one link, but other links may have been
+    // removed and links no longer in the content need to be deleted from the
+    // linkchecker_block_custom reference table.
+    $subquery = db_select('linkchecker_link')
+      ->fields('linkchecker_link', array('lid'))
+      ->condition('urlhash', array_map('drupal_hash_base64', $links), 'IN');
+
+    db_delete('linkchecker_block_custom')
+      ->condition('bid', $bid)
+      ->condition('lid', $subquery, 'NOT IN')
+      ->execute();
+  }
+}
+
+/**
+ * Returns an array of node references missing in the linkchecker_node table.
+ *
+ * @param int $nid
+ *   The node ID.
+ * @param array $links
+ *   An array of links.
+ *
+ * @return array
+ *   An array of node references missing in the linkchecker_node table.
+ */
+function _linkchecker_node_links_missing($nid, $links) {
+  $result = db_query('SELECT ll.url FROM {linkchecker_link} ll INNER JOIN {linkchecker_node} ln ON ln.lid = ll.lid WHERE ln.nid = :nid AND ll.urlhash IN (:urlhashes)', array(':nid' => $nid, ':urlhashes' => array_map('drupal_hash_base64', $links)));
+  $links_in_database = array();
+  foreach ($result as $row) {
+    $links_in_database[] = $row->url;
+  }
+  return array_diff($links, $links_in_database);
+}
+
+/**
+ * Returns an array of comment references missing in the linkchecker_comment table.
+ *
+ * @param int $cid
+ *   The comment ID.
+ * @param array $links
+ *   An array of links.
+ *
+ * @return array
+ *   An array of comment references missing in the linkchecker_comment table.
+ */
+function _linkchecker_comment_links_missing($cid, $links) {
+  $result = db_query('SELECT ll.url FROM {linkchecker_link} ll INNER JOIN {linkchecker_comment} lc ON lc.lid = ll.lid WHERE lc.cid = :cid AND ll.urlhash IN (:urlhashes)', array(':cid' => $cid, ':urlhashes' => array_map('drupal_hash_base64', $links)));
+  $links_in_database = array();
+  foreach ($result as $row) {
+    $links_in_database[] = $row->url;
+  }
+  return array_diff($links, $links_in_database);
+}
+
+/**
+ * Returns an array of custom block references missing in the linkchecker_block_custom table.
+ *
+ * @param int $bid
+ *   The block ID.
+ * @param array $links
+ *   An array of links.
+ *
+ * @return array
+ *   An array of custom block references missing in the linkchecker_block_custom
+ *   table.
+ */
+function _linkchecker_block_custom_links_missing($bid, $links) {
+  $result = db_query('SELECT ll.url FROM {linkchecker_link} ll INNER JOIN {linkchecker_block_custom} lb ON lb.lid = ll.lid WHERE lb.bid = :bid AND ll.urlhash IN (:urlhashes)', array(':bid' => $bid, ':urlhashes' => array_map('drupal_hash_base64', $links)));
+  $links_in_database = array();
+  foreach ($result as $row) {
+    $links_in_database[] = $row->url;
+  }
+  return array_diff($links, $links_in_database);
+}
+
+/**
+ * Parse the urls from entity.
+ *
+ * This function parse all fields from the entity and returns an array of
+ * filtered field items.
+ *
+ * @param string $entity_type
+ *   The type of entity; e.g., 'node', 'comment'.
+ * @param string $bundle_name
+ *   The name of the bundle aka node type, e.g., 'article', 'page'.
+ * @param object $entity
+ *   The entity to parse, a $node or a $comment object.
+ * @param bool $return_field_names
+ *   If set to TRUE, the returned array will contain the content as keys, and
+ *   each element will be an array containing all field names in which the
+ *   content is found. Otherwise, a simple array with content will be returned.
+ *
+ * @return array
+ *   Array of field items with filters applied.
+ */
+function _linkchecker_parse_fields($entity_type, $bundle_name, $entity, $return_field_names = FALSE) {
+  $text_items = array();
+  $text_items_by_field = array();
+
+  // Create settings for _filter_url() function.
+  $filter = new stdClass();
+  $filter->settings['filter_url_length'] = 72;
+
+  // Collect the fields from this entity_type and bundle.
+  foreach (field_info_instances($entity_type, $bundle_name) as $field_name => $instance) {
+    $field = field_info_field($field_name);
+    // #1923328: field_name array may be missing in $entity.
+    $entity_field = isset($entity->{$field['field_name']}) ? $entity->{$field['field_name']} : array();
+
+    switch ($field['type']) {
+      // Core fields.
+      case 'text_with_summary':
+        foreach ($entity_field as $language) {
+          foreach ($language as $item) {
+            $item += array(
+              'format' => NULL,
+              'summary' => '',
+              'value' => '',
+            );
+            $text_items[] = $text_items_by_field[$field['field_name']][] = _linkchecker_check_markup($item['value'], $item['format'], linkchecker_entity_language($entity_type, $entity), TRUE);
+            $text_items[] = $text_items_by_field[$field['field_name']][] = _linkchecker_check_markup($item['summary'], $item['format'], linkchecker_entity_language($entity_type, $entity), TRUE);
+          }
+        }
+        break;
+
+      // Core fields.
+      case 'text_long':
+      case 'text':
+        foreach ($entity_field as $language) {
+          foreach ($language as $item) {
+            $item += array(
+              'format' => NULL,
+              'value' => '',
+            );
+            $text_items[] = $text_items_by_field[$field['field_name']][] = _linkchecker_check_markup($item['value'], $item['format'], linkchecker_entity_language($entity_type, $entity), TRUE);
+          }
+        }
+        break;
+
+      // Link module field, http://drupal.org/project/link.
+      case 'link_field':
+        foreach ($entity_field as $language) {
+          foreach ($language as $item) {
+            $item += array(
+              'title' => '',
+            );
+            $options = drupal_parse_url(link_cleanup_url($item['url']));
+            $text_items[] = $text_items_by_field[$field['field_name']][] = l($item['title'], $options['path'], $options);
+            $text_items[] = $text_items_by_field[$field['field_name']][] = _linkchecker_check_markup($item['title'], NULL, linkchecker_entity_language($entity_type, $entity), TRUE);
+          }
+        }
+        break;
+    }
+  }
+
+  return ($return_field_names) ? $text_items_by_field : $text_items;
+}
+
+/**
+ * Replace the old url by a new url on 301 status codes.
+ *
+ * @param string $entity_type
+ *   The type of entity; e.g., 'node', 'comment'.
+ * @param string $bundle_name
+ *   The name of the bundle aka node type, e.g., 'article', 'page'.
+ * @param object $entity
+ *   The entity to parse, a $node or a $comment object.
+ * @param string $old_url
+ *   The previous url.
+ * @param string $new_url
+ *   The new url to replace the old.
+ *
+ * @return object
+ */
+function _linkchecker_replace_fields($entity_type, $bundle_name, $entity, $old_url, $new_url) {
+  // Collect the fields from this entity_type and bundle.
+  foreach (field_info_instances($entity_type, $bundle_name) as $field_name => $instance) {
+    $field = field_info_field($field_name);
+    $entity_field =& $entity->{$field['field_name']};
+
+    switch ($field['type']) {
+      // Core fields.
+      case 'text_with_summary':
+        foreach ($entity_field as $language_name => $language_value) {
+          foreach ($language_value as $item_name => $item_value) {
+            _linkchecker_link_replace($entity_field[$language_name][$item_name]['value'], $old_url, $new_url);
+            _linkchecker_link_replace($entity_field[$language_name][$item_name]['summary'], $old_url, $new_url);
+          }
+        }
+        break;
+
+      // Core fields.
+      case 'text_long':
+      case 'text':
+        foreach ($entity_field as $language_name => $language_value) {
+          foreach ($language_value as $item_name => $item_value) {
+            _linkchecker_link_replace($entity_field[$language_name][$item_name]['value'], $old_url, $new_url);
+          }
+        }
+        break;
+
+      // Link module field, http://drupal.org/project/link.
+      case 'link_field':
+        foreach ($entity_field as $language_name => $language_value) {
+          foreach ($language_value as $item_name => $item_value) {
+            _linkchecker_link_replace($entity_field[$language_name][$item_name]['url'], $old_url, $new_url);
+            _linkchecker_link_replace($entity_field[$language_name][$item_name]['title'], $old_url, $new_url);
+          }
+        }
+        break;
+    }
+  }
+
+  return $entity;
+}
+
+/**
+ * Run perodically via cron and delete all links without a references.
+ *
+ * For speed reasons and check results we keep the links for some time
+ * as they may be reused by other new content.
+ */
+function _linkchecker_cleanup_links() {
+  // Remove disabled node types no longer in use.
+  $node_types = linkchecker_scan_node_types();
+  if (!empty($node_types)) {
+    $subquery1 = db_select('node', 'n')
+      ->fields('n', array('nid'))
+      ->condition('n.type', $node_types, 'NOT IN');
+
+    db_delete('linkchecker_node')
+      ->condition('nid', $subquery1, 'IN')
+      ->execute();
+
+    // @todo Remove comments link references from table.
+    // db_query('DELETE FROM {linkchecker_comment} WHERE cid IN (SELECT nid FROM {node} n WHERE n.type NOT IN (' . db_placeholders($node_types, 'varchar') . '))', $node_types);
+  }
+  else {
+    // No active node_type. Remove all items from table.
+    db_truncate('linkchecker_node')->execute();
+    // @todo Remove comments link references from table.
+  }
+
+  // Remove comment link references if comment scanning is disabled.
+  // @todo Remove comments of unpublished nodes.
+  $comment_types = linkchecker_scan_comment_types();
+  if (empty($comment_types)) {
+    db_truncate('linkchecker_comment')->execute();
+  }
+
+  // Remove block link references if block scanning is disabled.
+  if (variable_get('linkchecker_scan_blocks', 0) == 0) {
+    db_truncate('linkchecker_block_custom')->execute();
+  }
+
+  // Remove dead links without references.
+  $linkchecker_node = db_select('linkchecker_node', 'ln')
+    ->distinct()
+    ->fields('ln', array('lid'));
+  $linkchecker_comment = db_select('linkchecker_comment', 'lc')
+    ->distinct()
+    ->fields('lc', array('lid'));
+  $linkchecker_block_custom = db_select('linkchecker_block_custom', 'lb')
+    ->distinct()
+    ->fields('lb', array('lid'));
+
+  // UNION all linkchecker type tables.
+  $subquery2 = db_select($linkchecker_block_custom->union($linkchecker_comment)->union($linkchecker_node), 'q1')
+    ->distinct()
+    ->fields('q1', array('lid'));
+
+  db_delete('linkchecker_link')
+    ->condition('lid', $subquery2, 'NOT IN')
+    ->execute();
+
+}
+
+/**
+ * Extract links from content.
+ *
+ * @param string $text
+ *   The text to be scanned for links.
+ * @param string $content_path
+ *   Path to the content that is currently scanned for links. This value is
+ *   required to build full qualified links from relative links. Relative links
+ *   are not extracted from content, if path is not provided.
+ *
+ * @return array
+ *   Array whose keys are fully qualified and unique URLs found in the
+ *   content, and whose values are arrays of actual text (raw URLs or paths)
+ *   corresponding to each fully qualified URL.
+ */
+function _linkchecker_extract_links($text = '', $content_path = NULL) {
+  global $base_root, $is_https;
+
+  $html_dom = filter_dom_load($text);
+  $urls = array();
+
+  // Finds all hyperlinks in the content.
+  if (variable_get('linkchecker_extract_from_a', 1) == 1) {
+    $links = $html_dom->getElementsByTagName('a');
+    foreach ($links as $link) {
+      $urls[] = $link->getAttribute('href');
+    }
+
+    $links = $html_dom->getElementsByTagName('area');
+    foreach ($links as $link) {
+      $urls[] = $link->getAttribute('href');
+    }
+  }
+
+  // Finds all audio links in the content.
+  if (variable_get('linkchecker_extract_from_audio', 0) == 1) {
+    $audios = $html_dom->getElementsByTagName('audio');
+    foreach ($audios as $audio) {
+      $urls[] = $audio->getAttribute('src');
+
+      // Finds source tags with links in the audio tag.
+      $sources = $audio->getElementsByTagName('source');
+      foreach ($sources as $source) {
+        $urls[] = $source->getAttribute('src');
+      }
+      // Finds track tags with links in the audio tag.
+      $tracks = $audio->getElementsByTagName('track');
+      foreach ($tracks as $track) {
+        $urls[] = $track->getAttribute('src');
+      }
+    }
+  }
+
+  // Finds embed tags with links in the content.
+  if (variable_get('linkchecker_extract_from_embed', 0) == 1) {
+    $embeds = $html_dom->getElementsByTagName('embed');
+    foreach ($embeds as $embed) {
+      $urls[] = $embed->getAttribute('src');
+      $urls[] = $embed->getAttribute('pluginurl');
+      $urls[] = $embed->getAttribute('pluginspage');
+    }
+  }
+
+  // Finds iframe tags with links in the content.
+  if (variable_get('linkchecker_extract_from_iframe', 0) == 1) {
+    $iframes = $html_dom->getElementsByTagName('iframe');
+    foreach ($iframes as $iframe) {
+      $urls[] = $iframe->getAttribute('src');
+    }
+  }
+
+  // Finds img tags with links in the content.
+  if (variable_get('linkchecker_extract_from_img', 0) == 1) {
+    $imgs = $html_dom->getElementsByTagName('img');
+    foreach ($imgs as $img) {
+      $urls[] = $img->getAttribute('src');
+      $urls[] = $img->getAttribute('longdesc');
+    }
+  }
+
+  // Finds object/param tags with links in the content.
+  if (variable_get('linkchecker_extract_from_object', 0) == 1) {
+    $objects = $html_dom->getElementsByTagName('object');
+    foreach ($objects as $object) {
+      $urls[] = $object->getAttribute('data');
+      $urls[] = $object->getAttribute('codebase');
+
+      // Finds param tags with links in the object tag.
+      $params = $object->getElementsByTagName('param');
+      foreach ($params as $param) {
+        // @todo
+        // - Try to extract links in unkown "flashvars" values
+        //   (e.g., file=http://, data=http://).
+        $names = array('archive', 'filename', 'href', 'movie', 'src', 'url');
+        if ($param->hasAttribute('name') && in_array($param->getAttribute('name'), $names)) {
+          $urls[] = $param->getAttribute('value');
+        }
+
+        $srcs = array('movie');
+        if ($param->hasAttribute('src') && in_array($param->getAttribute('src'), $srcs)) {
+          $urls[] = $param->getAttribute('value');
+        }
+      }
+    }
+  }
+
+  // Finds video tags with links in the content.
+  if (variable_get('linkchecker_extract_from_video', 0) == 1) {
+    $videos = $html_dom->getElementsByTagName('video');
+    foreach ($videos as $video) {
+      $urls[] = $video->getAttribute('poster');
+      $urls[] = $video->getAttribute('src');
+
+      // Finds source tags with links in the video tag.
+      $sources = $video->getElementsByTagName('source');
+      foreach ($sources as $source) {
+        $urls[] = $source->getAttribute('src');
+      }
+      // Finds track tags with links in the audio tag.
+      $tracks = $video->getElementsByTagName('track');
+      foreach ($tracks as $track) {
+        $urls[] = $track->getAttribute('src');
+      }
+    }
+  }
+
+  // Remove empty values.
+  $urls = array_filter($urls);
+  // Remove duplicate urls.
+  $urls = array_unique($urls);
+
+  // What type of links should be checked?
+  $linkchecker_check_links_types = variable_get('linkchecker_check_links_types', 1);
+
+  $links = array();
+  foreach ($urls as $url) {
+    // Decode HTML links into plain text links.
+    // DOMDocument->loadHTML does not provide the RAW url from code. All html
+    // entities are already decoded.
+    // @todo: Try to find a way to get the raw value.
+    $url_decoded = $url;
+
+    // Prefix protocol relative urls with a protocol to allow link checking.
+    if (preg_match('!^//!', $url_decoded)) {
+      $http_protocol = $is_https ? 'https' : 'http';
+      $url_decoded = $http_protocol . ':' . $url_decoded;
+    }
+
+    // FIXME: #1149596 HACK - Encode spaces in URLs, so validation equals TRUE and link gets added.
+    $url_encoded = str_replace(' ', '%20', $url_decoded);
+
+    // Full qualified URLs.
+    if ($linkchecker_check_links_types != 2 && valid_url($url_encoded, TRUE)) {
+      // Add to Array and change HTML links into plain text links.
+      $links[$url_decoded][] = $url;
+    }
+    // Skip mailto:, javascript:, etc.
+    elseif (preg_match('/^\w[\w.+]*:/', $url_decoded)) {
+      continue;
+    }
+    // Local URLs. $linkchecker_check_links_types = 0 or 2
+    elseif ($linkchecker_check_links_types != 1 && valid_url($url_encoded, FALSE)) {
+      // Get full qualified url with base path of content.
+      $absolute_content_path = _linkchecker_absolute_content_path($content_path);
+
+      // Absolute local URLs need to start with [/].
+      if (preg_match('!^/!', $url_decoded)) {
+        // Add to Array and change HTML encoded links into plain text links.
+        $links[$base_root . $url_decoded][] = $url;
+      }
+      // Anchors and URL parameters like "#foo" and "?foo=bar".
+      elseif (!empty($content_path) && preg_match('!^[?#]!', $url_decoded)) {
+        // Add to Array and change HTML encoded links into plain text links.
+        $links[$content_path . $url_decoded][] = $url;
+      }
+      // Relative URLs like "./foo/bar" and "../foo/bar".
+      elseif (!empty($absolute_content_path) && preg_match('!^\.{1,2}/!', $url_decoded)) {
+        // Build the URI without hostname before the URI is normalized and
+        // dot-segments will be removed. The hostname is added back after the
+        // normalization has completed to prevent hostname removal by the regex.
+        // This logic intentionally does not implement all the rules definied in
+        // RFC 3986, section 5.2.4 to show broken links and over-dot-segmented
+        // URIs; e.g., http://example.com/../../foo/bar.
+        // For more information, see http://drupal.org/node/832388.
+        $path = substr_replace($absolute_content_path . $url_decoded, '', 0, strlen($base_root));
+
+        // Remove './' segments where possible.
+        $path = str_replace('/./', '/', $path);
+
+        // Remove '../' segments where possible. Loop until all segments are
+        // removed. Taken over from _drupal_build_css_path() in common.inc.
+        $last = '';
+        while ($path != $last) {
+          $last = $path;
+          $path = preg_replace('`(^|/)(?!\.\./)([^/]+)/\.\./`', '$1', $path);
+        }
+
+        // Glue the hostname and path to full-qualified URI.
+        $links[$base_root . $path][] = $url;
+      }
+      // Relative URLs like "test.png".
+      elseif (!empty($absolute_content_path) && preg_match('!^[^/]!', $url_decoded)) {
+        $links[$absolute_content_path . $url_decoded][] = $url;
+      }
+      else {
+        // @todo Are there more special cases the module need to handle?
+      }
+    }
+  }
+
+  return $links;
+}
+
+/**
+ * Replaces old link with new link in text.
+ *
+ * @param string $text
+ *   The text a link is inside. Passed in as a reference.
+ * @param string $old_link_fqdn
+ *   The old link to search for in strings.
+ * @param string $new_link_fqdn
+ *   The old link should be overwritten with this new link.
+ */
+function _linkchecker_link_replace(&$text, $old_link_fqdn = '', $new_link_fqdn = '') {
+  // Don't do any string replacement if one of the values is empty.
+  if (!empty($text) && !empty($old_link_fqdn) && !empty($new_link_fqdn)) {
+    // Remove protocols and hostname from local URLs.
+    $base_roots = array(
+      drupal_strtolower('http://' . $_SERVER['HTTP_HOST']),
+      drupal_strtolower('https://' . $_SERVER['HTTP_HOST']),
+    );
+    $old_link = str_replace($base_roots, '', $old_link_fqdn);
+    $new_link = str_replace($base_roots, '', $new_link_fqdn);
+
+    // Build variables with all URLs and run check_url() only once.
+    $old_html_link_fqdn = check_url($old_link_fqdn);
+    $new_html_link_fqdn = check_url($new_link_fqdn);
+    $old_html_link = check_url($old_link);
+    $new_html_link = check_url($new_link);
+
+    // Replace links in link fields and text and Links weblink fields.
+    if (in_array($text, array($old_html_link_fqdn, $old_html_link, $old_link_fqdn, $old_link))) {
+      // Keep old and new links in the same encoding and format and short or
+      // fully qualified.
+      $text = str_replace($old_html_link_fqdn, $new_html_link_fqdn, $text);
+      $text = str_replace($old_html_link, $new_html_link, $text);
+      $text = str_replace($old_link_fqdn, $new_link_fqdn, $text);
+      $text = str_replace($old_link, $new_link, $text);
+    }
+    else {
+      // Create an array of links with HTML decoded and encoded URLs.
+      $old_links = array(
+        $old_html_link_fqdn,
+        $old_html_link,
+        $old_link,
+      );
+
+      // Remove duplicate URLs from array if URLs do not have URL parameters.
+      // If more than one URL parameter exists - one URL in the array will have
+      // an unencoded ampersand "&" and a second URL will have an HTML encoded
+      // ampersand "&amp;".
+      $old_links = array_unique($old_links);
+
+      // Load HTML code into DOM.
+      $html_dom = filter_dom_load($text);
+
+      // Finds all hyperlinks in the content.
+      if (variable_get('linkchecker_extract_from_a', 1) == 1) {
+        $links = $html_dom->getElementsByTagName('a');
+        foreach ($links as $link) {
+          if (in_array($link->getAttribute('href'), $old_links)) {
+            $link->setAttribute('href', $new_html_link);
+          }
+          // Replace link text, if same like the URL. If a link text contains
+          // other child tags like <img> it will be skipped.
+          if (in_array($link->nodeValue, $old_links)) {
+            $link->nodeValue = $new_html_link;
+          }
+        }
+
+        $links = $html_dom->getElementsByTagName('area');
+        foreach ($links as $link) {
+          if (in_array($link->getAttribute('href'), $old_links)) {
+            $link->setAttribute('href', $new_html_link);
+          }
+        }
+      }
+
+      // Finds all audio links in the content.
+      if (variable_get('linkchecker_extract_from_audio', 0) == 1) {
+        $audios = $html_dom->getElementsByTagName('audio');
+        foreach ($audios as $audio) {
+          if (in_array($audio->getAttribute('src'), $old_links)) {
+            $audio->setAttribute('src', $new_html_link);
+          }
+
+          // Finds source tags with links in the audio tag.
+          $sources = $audio->getElementsByTagName('source');
+          foreach ($sources as $source) {
+            if (in_array($source->getAttribute('src'), $old_links)) {
+              $source->setAttribute('src', $new_html_link);
+            }
+          }
+          // Finds track tags with links in the audio tag.
+          $tracks = $audio->getElementsByTagName('track');
+          foreach ($tracks as $track) {
+            if (in_array($track->getAttribute('src'), $old_links)) {
+              $track->setAttribute('src', $new_html_link);
+            }
+          }
+        }
+      }
+
+      // Finds embed tags with links in the content.
+      if (variable_get('linkchecker_extract_from_embed', 0) == 1) {
+        $embeds = $html_dom->getElementsByTagName('embed');
+        foreach ($embeds as $embed) {
+          if (in_array($embed->getAttribute('src'), $old_links)) {
+            $embed->setAttribute('src', $new_html_link);
+          }
+          if (in_array($embed->getAttribute('pluginurl'), $old_links)) {
+            $embed->setAttribute('pluginurl', $new_html_link);
+          }
+          if (in_array($embed->getAttribute('pluginspage'), $old_links)) {
+            $embed->setAttribute('pluginspage', $new_html_link);
+          }
+        }
+      }
+
+      // Finds iframe tags with links in the content.
+      if (variable_get('linkchecker_extract_from_iframe', 0) == 1) {
+        $iframes = $html_dom->getElementsByTagName('iframe');
+        foreach ($iframes as $iframe) {
+          if (in_array($iframe->getAttribute('src'), $old_links)) {
+            $iframe->setAttribute('src', $new_html_link);
+          }
+        }
+      }
+
+      // Finds img tags with links in the content.
+      if (variable_get('linkchecker_extract_from_img', 0) == 1) {
+        $imgs = $html_dom->getElementsByTagName('img');
+        foreach ($imgs as $img) {
+          if (in_array($img->getAttribute('src'), $old_links)) {
+            $img->setAttribute('src', $new_html_link);
+          }
+          if (in_array($img->getAttribute('longdesc'), $old_links)) {
+            $img->setAttribute('longdesc', $new_html_link);
+          }
+        }
+      }
+
+      // Finds object/param tags with links in the content.
+      if (variable_get('linkchecker_extract_from_object', 0) == 1) {
+        $objects = $html_dom->getElementsByTagName('object');
+        foreach ($objects as $object) {
+          if (in_array($object->getAttribute('data'), $old_links)) {
+            $object->setAttribute('data', $new_html_link);
+          }
+          if (in_array($object->getAttribute('codebase'), $old_links)) {
+            $object->setAttribute('codebase', $new_html_link);
+          }
+
+          // Finds param tags with links in the object tag.
+          $params = $object->getElementsByTagName('param');
+          foreach ($params as $param) {
+            // @todo
+            // - Try to replace links in unkown "flashvars" values
+            //   (e.g., file=http://, data=http://).
+            $names = array('archive', 'filename', 'href', 'movie', 'src', 'url');
+            if ($param->hasAttribute('name') && in_array($param->getAttribute('name'), $names)) {
+              if (in_array($param->getAttribute('value'), $old_links)) {
+                $param->setAttribute('value', $new_html_link);
+              }
+            }
+
+            $srcs = array('movie');
+            if ($param->hasAttribute('src') && in_array($param->getAttribute('src'), $srcs)) {
+              if (in_array($param->getAttribute('value'), $old_links)) {
+                $param->setAttribute('value', $new_html_link);
+              }
+            }
+          }
+        }
+      }
+
+      // Finds video tags with links in the content.
+      if (variable_get('linkchecker_extract_from_video', 0) == 1) {
+        $videos = $html_dom->getElementsByTagName('video');
+        foreach ($videos as $video) {
+          if (in_array($video->getAttribute('poster'), $old_links)) {
+            $video->setAttribute('poster', $new_html_link);
+          }
+          if (in_array($video->getAttribute('src'), $old_links)) {
+            $video->setAttribute('src', $new_html_link);
+          }
+
+          // Finds source tags with links in the video tag.
+          $sources = $video->getElementsByTagName('source');
+          foreach ($sources as $source) {
+            if (in_array($source->getAttribute('src'), $old_links)) {
+              $source->setAttribute('src', $new_html_link);
+            }
+          }
+          // Finds track tags with links in the audio tag.
+          $tracks = $video->getElementsByTagName('track');
+          foreach ($tracks as $track) {
+            if (in_array($track->getAttribute('src'), $old_links)) {
+              $track->setAttribute('src', $new_html_link);
+            }
+          }
+        }
+      }
+
+      // Set the updated $text for the calling function.
+      $text = filter_dom_serialize($html_dom);
+    }
+  }
+}
+
+/**
+ * Customized clone of core check_markup() with additional filter blacklist.
+ *
+ * See http://api.drupal.org/api/function/check_markup/7 for API documentation.
+ */
+function _linkchecker_check_markup($text, $format_id = NULL, $langcode = '', $cache = FALSE) {
+  if (!isset($text)) {
+    return '';
+  }
+
+  if (!isset($format_id)) {
+    $format_id = filter_fallback_format();
+  }
+  // If the requested text format does not exist, the text cannot be filtered.
+  if (!$format = filter_format_load($format_id)) {
+    linkchecker_watchdog_log('filter', 'Missing text format: %format.', array('%format' => $format_id), WATCHDOG_ALERT);
+    return '';
+  }
+
+  // Check for a cached version of this piece of text.
+  $cache = $cache && !empty($format->cache);
+  $cache_id = '';
+  if ($cache) {
+    $cache_id = 'linkchecker:' . $format->format . ':' . $langcode . ':' . hash('sha256', $text);
+    if ($cached = cache_get($cache_id, 'cache_filter')) {
+      return $cached->data;
+    }
+  }
+
+  // Convert all Windows and Mac newlines to a single newline, so filters only
+  // need to deal with one possibility.
+  $text = str_replace(array("\r\n", "\r"), "\n", $text);
+
+  // Get a complete list of filters, ordered properly.
+  $filters = filter_list_format($format->format);
+  $filter_info = filter_get_filters();
+
+  // Do not run placeholder or special tag filters used as references to nodes
+  // like 'weblink' or 'weblinks' node types. If the original link node is
+  // updated, all links are automatically up-to-date and there is no need to
+  // notify about the broken link on all nodes having a link reference in
+  // content. This would only confuse the authors as they may also not be able
+  // to fix the source node of the reference.
+  $filters_blacklist = array_keys(array_filter(variable_get('linkchecker_filter_blacklist', explode('|', LINKCHECKER_DEFAULT_FILTER_BLACKLIST))));
+
+  // Give filters the chance to escape HTML-like data such as code or formulas.
+  foreach ($filters as $name => $filter) {
+    if (!in_array($name, $filters_blacklist)) {
+      if ($filter->status && isset($filter_info[$name]['prepare callback']) && function_exists($filter_info[$name]['prepare callback'])) {
+        $function = $filter_info[$name]['prepare callback'];
+        $text = $function($text, $filter, $format, $langcode, $cache, $cache_id);
+      }
+    }
+  }
+
+  // Perform filtering.
+  foreach ($filters as $name => $filter) {
+    if (!in_array($name, $filters_blacklist)) {
+      if ($filter->status && isset($filter_info[$name]['process callback']) && function_exists($filter_info[$name]['process callback'])) {
+        $function = $filter_info[$name]['process callback'];
+        $text = $function($text, $filter, $format, $langcode, $cache, $cache_id);
+      }
+    }
+  }
+
+  // Store in cache with a minimum expiration time of 1 day.
+  if ($cache) {
+    cache_set($cache_id, $text, 'cache_filter', REQUEST_TIME + (60 * 60 * 24));
+  }
+
+  return $text;
+}
+
+/**
+ * Get the path of an URL.
+ *
+ * @param string $url
+ *   The http/https URL to parse.
+ *
+ * @return string
+ *   Full qualified URL with absolute path of the URL.
+ */
+function _linkchecker_absolute_content_path($url) {
+
+  // Parse the URL and make sure we can handle the schema.
+  $uri = @parse_url($url);
+
+  if ($uri == FALSE) {
+    return NULL;
+  }
+
+  if (!isset($uri['scheme'])) {
+    return NULL;
+  }
+
+  // Break if the schema is not supported.
+  if (!in_array($uri['scheme'], array('http', 'https'))) {
+    return NULL;
+  }
+
+  $scheme = isset($uri['scheme']) ? $uri['scheme'] . '://' : '';
+  $user = isset($uri['user']) ? $uri['user'] . ($uri['pass'] ? ':' . $uri['pass'] : '') . '@' : '';
+  $port = isset($uri['port']) ? $uri['port'] : 80;
+  $host = $uri['host'] . ($port != 80 ? ':' . $port : '');
+  $path = isset($uri['path']) ? $uri['path'] : '/';
+
+  // Glue the URL variables.
+  $absolute_url = $scheme . $user . $host . $path;
+
+  // Find the last slash and remove all after the last slash to get the path.
+  $last_slash = strrpos($absolute_url, '/');
+  $absolute_content_url = drupal_substr($absolute_url, 0, $last_slash + 1);
+
+  return $absolute_content_url;
+}
+
+/**
+ * Verifies against blacklists, if the link status should be checked or not.
+ */
+function _linkchecker_link_check_status_filter($url) {
+  $status = TRUE;
+
+  // Is url in domain blacklist?
+  $urls = variable_get('linkchecker_disable_link_check_for_urls', LINKCHECKER_RESERVED_DOCUMENTATION_DOMAINS);
+  if (!empty($urls) && preg_match('/' . implode('|', array_map(create_function('$links', 'return preg_quote($links, \'/\');'), preg_split('/(\r\n?|\n)/', $urls))) . '/', $url)) {
+    $status = FALSE;
+  }
+
+  // Protocol whitelist check (without curl, only http/https is supported).
+  if (!preg_match('/^(https?):\/\//i', $url)) {
+    $status = FALSE;
+  }
+
+  return $status;
+}
+
+/**
+ * Defines the list of allowed response codes for form input validation.
+ *
+ * @param int $code
+ *   An numeric response code.
+ *
+ * @return bool
+ *   TRUE if the status code is valid, otherwise FALSE.
+ */
+function _linkchecker_isvalid_response_code($code) {
+
+  $responses = array(
+    100 => 'Continue',
+    101 => 'Switching Protocols',
+    200 => 'OK',
+    201 => 'Created',
+    202 => 'Accepted',
+    203 => 'Non-Authoritative Information',
+    204 => 'No Content',
+    205 => 'Reset Content',
+    206 => 'Partial Content',
+    300 => 'Multiple Choices',
+    301 => 'Moved Permanently',
+    302 => 'Found',
+    303 => 'See Other',
+    304 => 'Not Modified',
+    305 => 'Use Proxy',
+    307 => 'Temporary Redirect',
+    400 => 'Bad Request',
+    401 => 'Unauthorized',
+    402 => 'Payment Required',
+    403 => 'Forbidden',
+    404 => 'Not Found',
+    405 => 'Method Not Allowed',
+    406 => 'Not Acceptable',
+    407 => 'Proxy Authentication Required',
+    408 => 'Request Time-out',
+    409 => 'Conflict',
+    410 => 'Gone',
+    411 => 'Length Required',
+    412 => 'Precondition Failed',
+    413 => 'Request Entity Too Large',
+    414 => 'Request-URI Too Large',
+    415 => 'Unsupported Media Type',
+    416 => 'Requested range not satisfiable',
+    417 => 'Expectation Failed',
+    500 => 'Internal Server Error',
+    501 => 'Not Implemented',
+    502 => 'Bad Gateway',
+    503 => 'Service Unavailable',
+    504 => 'Gateway Time-out',
+    505 => 'HTTP Version not supported',
+  );
+
+  return array_key_exists($code, $responses);
+}
+
+/**
+ * Return all content type enable with link checking.
+ *
+ * @return array
+ *   An array of node type names, keyed by the type.
+ */
+function linkchecker_scan_node_types() {
+  $types = array();
+  foreach (node_type_get_names() as $type => $name) {
+    if (variable_get('linkchecker_scan_node_' . $type, FALSE)) {
+      $types[$type] = $type;
+    }
+  }
+  return $types;
+}
+
+/**
+ * Return all content type enable with comment link checking.
+ *
+ * @return array
+ *   An array of node type names, keyed by the type.
+ */
+function linkchecker_scan_comment_types() {
+  $types = array();
+  foreach (node_type_get_names() as $type => $name) {
+    if (variable_get('linkchecker_scan_comment_' . $type, FALSE)) {
+      $types[$type] = $type;
+    }
+  }
+  return $types;
+}
+
+/**
+ * Unpublishes all nodes having the specified link id.
+ *
+ * @param int $lid
+ *   A link ID that have reached a defined failcount.
+ */
+function _linkchecker_unpublish_nodes($lid) {
+  $result = db_query('SELECT nid FROM {linkchecker_node} WHERE lid = :lid', array(':lid' => $lid));
+  foreach ($result as $row) {
+    // Explicitly don't use node_load_multiple() or the module may run
+    // into issues like http://drupal.org/node/1210606. With this logic
+    // nodes can be updated until an out of memory occurs and further
+    // updates will be made on the remaining nodes only.
+    $node = node_load($row->nid);
+    $node->status = NODE_NOT_PUBLISHED;
+    node_save($node);
+    linkchecker_watchdog_log('linkchecker', 'Set @type %title to unpublished.', array('@type' => $node->type, '%title' => $node->title));
+  }
+}
+
+/**
+ * Load link as object.
+ *
+ * @param int $lid
+ *  The link id.
+ *
+ * @return object
+ */
+function linkchecker_link_load($lid) {
+  return db_query('SELECT * FROM {linkchecker_link} WHERE lid = :lid', array(':lid' => $lid))->fetchObject();
+}
+
+/**
+ * Checks if this entity is the default revision (published).
+ *
+ * @param object $entity
+ *   The entity object, e.g., $node.
+ *
+ * @return bool
+ *   TRUE if the entity is the default revision, FALSE otherwise.
+ */
+function _linkchecker_isdefaultrevision($entity) {
+  // D7 "Forward revisioning" is complex and causes a node_save() with the
+  // future node in node table. This fires hook_node_update() twice and cause
+  // abnormal behaviour in linkchecker.
+  //
+  // The steps taken by Workbench Moderation is to save the forward revision
+  // first and overwrite this with the live version in a shutdown function in
+  // a second step. This will confuse linkchecker. D7 has no generic property
+  // in the node object, if the node that is updated is the 'published' version
+  // or only a draft of a future version.
+  //
+  // This behaviour will change in D8 where $node->isDefaultRevision has been
+  // introduced. See below links for more details.
+  // - http://drupal.org/node/1879482
+  // - http://drupal.org/node/218755
+  // - http://drupal.org/node/1522154
+  //
+  // Every moderation module saving a forward revision needs to return FALSE.
+  // @todo: Refactor this workaround under D8.
+
+  // Workbench Moderation module.
+  if (module_exists('workbench_moderation') && workbench_moderation_node_type_moderated($entity->type) === TRUE && empty($entity->workbench_moderation['updating_live_revision'])) {
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+/**
+ * Returns the language code of the given entity.
+ *
+ * Backward compatibility layer to ensure that installations running an older
+ * version of core where entity_language() is not avilable do not break.
+ *
+ * @param string $entity_type
+ *   An entity type.
+ * @param object $entity
+ *   An entity object.
+ *
+ * @return string
+ *   The entity language code.
+ */
+function linkchecker_entity_language($entity_type, $entity) {
+  $langcode = NULL;
+  if (function_exists('entity_language')) {
+    $langcode = entity_language($entity_type, $entity);
+  }
+  elseif (!empty($entity->language)) {
+    $langcode = $entity->language;
+  }
+  return $langcode;
+}
+
+/**
+ * Return all the values of one-dimensional and multidimensional arrays.
+ *
+ * @return array
+ *   Returns all the values from the input array and indexes the array numerically.
+ */
+function _linkchecker_array_values_recursive(array $array) {
+  $array_values = array();
+
+  foreach ($array as $value) {
+    if (is_array($value)) {
+      $array_values = array_merge($array_values, _linkchecker_array_values_recursive($value));
+    }
+    else {
+      $array_values[] = $value;
+    }
+  }
+
+  return $array_values;
+}
diff --git a/web/modules/contrib/linkchecker/linkchecker.pages.inc b/web/modules/contrib/linkchecker/linkchecker.pages.inc
new file mode 100644 (file)
index 0000000..b04349b
--- /dev/null
@@ -0,0 +1,233 @@
+<?php
+
+/**
+ * @file
+ * User page callbacks for the linkchecker module.
+ */
+
+/**
+ * Menu callback for general reporting.
+ *
+ * @return string
+ *   Themed report page.
+ */
+function linkchecker_admin_report_page() {
+
+  $ignore_response_codes = preg_split('/(\r\n?|\n)/', variable_get('linkchecker_ignore_response_codes', "200\n206\n302\n304\n401\n403"));
+
+  // Search for broken links in nodes and comments and blocks of all users.
+  // @todo Try to make UNION'ed subselect resultset smaller.
+  $subquery4 = db_select('linkchecker_node', 'ln')
+    ->distinct()
+    ->fields('ln', array('lid'));
+
+  $subquery3 = db_select('linkchecker_comment', 'lc')
+    ->distinct()
+    ->fields('lc', array('lid'));
+
+  $subquery2 = db_select('linkchecker_block_custom', 'lb')
+    ->distinct()
+    ->fields('lb', array('lid'));
+
+  // UNION all linkchecker type tables.
+  $subquery1 = db_select($subquery2->union($subquery3)->union($subquery4), 'q1')->fields('q1', array('lid'));
+
+  // Build pager query.
+  $query = db_select('linkchecker_link', 'll')->extend('PagerDefault')->extend('TableSort');
+  $query->innerJoin($subquery1, 'q2', 'q2.lid = ll.lid');
+  $query->fields('ll');
+  $query->condition('ll.last_checked', 0, '<>');
+  $query->condition('ll.status', 1);
+  $query->condition('ll.code', $ignore_response_codes, 'NOT IN');
+
+  return _linkchecker_report_page($query);
+}
+
+/**
+ * Menu callback for author specific reporting.
+ *
+ * @param object $account
+ *   The user account.
+ *
+ * @return string
+ *   Themed report page.
+ */
+function linkchecker_user_report_page($account) {
+  drupal_set_title($account->name);
+
+  $ignore_response_codes = preg_split('/(\r\n?|\n)/', variable_get('linkchecker_ignore_response_codes', "200\n206\n302\n304\n401\n403"));
+
+  // Build query for broken links in nodes of the current user.
+  $subquery2 = db_select('node', 'n');
+  $subquery2->innerJoin('node_revision', 'r', 'r.vid = n.vid');
+  $subquery2->innerJoin('linkchecker_node', 'ln', 'ln.nid = n.nid');
+  $subquery2->innerJoin('linkchecker_link', 'll', 'll.lid = ln.lid');
+  $subquery2->condition('ll.last_checked', 0, '<>');
+  $subquery2->condition('ll.status', 1);
+  $subquery2->condition('ll.code', $ignore_response_codes, 'NOT IN');
+  $subquery2->condition(db_or()
+    ->condition('n.uid', $account->uid)
+    ->condition('r.uid', $account->uid)
+  );
+  $subquery2->distinct();
+  $subquery2->fields('ll', array('lid'));
+
+  $comment_types = linkchecker_scan_comment_types();
+  if (!empty($comment_types)) {
+    // Build query for broken links in nodes and comments of the current user.
+    $subquery3 = db_select('comment', 'c');
+    $subquery3->innerJoin('linkchecker_comment', 'lc', 'lc.cid = c.cid');
+    $subquery3->innerJoin('linkchecker_link', 'll', 'll.lid = lc.lid');
+    $subquery3->condition('ll.last_checked', 0, '<>');
+    $subquery3->condition('ll.status', 1);
+    $subquery3->condition('ll.code', $ignore_response_codes, 'NOT IN');
+    $subquery3->condition('c.uid', $account->uid);
+    $subquery3->distinct();
+    $subquery3->fields('ll', array('lid'));
+
+    // UNION the linkchecker_node and linkchecker_comment tables.
+    $subquery1 = db_select($subquery2->union($subquery3), 'q1')->fields('q1', array('lid'));
+  }
+  else {
+    // Build query for broken links in nodes of the current user.
+    $subquery1 = db_select($subquery2, 'q1')->fields('q1', array('lid'));
+  }
+
+  // Build pager query.
+  $query = db_select('linkchecker_link', 'll')->extend('PagerDefault')->extend('TableSort');
+  $query->innerJoin($subquery1, 'q2', 'q2.lid = ll.lid');
+  $query->fields('ll');
+  $query->condition('ll.last_checked', 0, '<>');
+  $query->condition('ll.status', 1);
+  $query->condition('ll.code', $ignore_response_codes, 'NOT IN');
+
+  return _linkchecker_report_page($query, $account);
+}
+
+/**
+ * Builds the HTML report page table with pager.
+ *
+ * @param SelectQueryInterface $query
+ *   The pager query for the report page. Can be per user report or global.
+ * @param object|null $account
+ *   The user account object.
+ *
+ * @return string
+ *   Themed report page.
+ */
+function _linkchecker_report_page($query, $account = NULL) {
+
+  $links_unchecked = db_query('SELECT COUNT(1) FROM {linkchecker_link} WHERE last_checked = :last_checked AND status = :status', array(':last_checked' => 0, ':status' => 1))->fetchField();
+  if ($links_unchecked > 0) {
+    $links_all = db_query('SELECT COUNT(1) FROM {linkchecker_link} WHERE status = :status', array(':status' => 1))->fetchField();
+    drupal_set_message(format_plural($links_unchecked,
+      'There is 1 unchecked link of about @links_all links in the database. Please be patient until all links have been checked via cron.',
+      'There are @count unchecked links of about @links_all links in the database. Please be patient until all links have been checked via cron.',
+      array('@links_all' => $links_all)), 'warning');
+  }
+
+  $header = array(
+    array('data' => t('URL'), 'field' => 'url', 'sort' => 'desc'),
+    array('data' => t('Response'), 'field' => 'code', 'sort' => 'desc'),
+    array('data' => t('Error'), 'field' => 'error'),
+    array('data' => t('Operations')),
+  );
+
+  $result = $query
+    ->limit(50)
+    ->orderByHeader($header)
+    ->execute();
+
+  // Evaluate permission once for performance reasons.
+  $access_edit_link_settings = user_access('edit link settings');
+  $access_administer_blocks = user_access('administer blocks');
+  $access_administer_redirects = user_access('administer redirects');
+
+  $rows = array();
+  foreach ($result as $link) {
+    // Get the node, block and comment IDs that refer to this broken link and
+    // that the current user has access to.
+    $nids = _linkchecker_link_node_ids($link, $account);
+    $cids = _linkchecker_link_comment_ids($link, $account);
+    $bids = _linkchecker_link_block_ids($link);
+
+    // If the user does not have access to see this link anywhere, do not
+    // display it, for reasons explained in _linkchecker_link_access(). We
+    // still need to fill the table row, though, so as not to throw off the
+    // number of items in the pager.
+    if (empty($nids) && empty($cids) && empty($bids)) {
+      $rows[] = array(array('data' => t('Permission restrictions deny you access to this broken link.'), 'colspan' => count($header)));
+      continue;
+    }
+
+    $links = array();
+
+    // Show links to link settings.
+    if ($access_edit_link_settings) {
+      $links[] = l(t('Edit link settings'), 'linkchecker/' . $link->lid . '/edit', array('query' => drupal_get_destination()));
+    }
+
+    // Show link to nodes having this broken link.
+    foreach ($nids as $nid) {
+      $links[] = l(t('Edit node @node', array('@node' => $nid)), 'node/' . $nid . '/edit', array('query' => drupal_get_destination()));
+    }
+
+    // Show link to comments having this broken link.
+    $comment_types = linkchecker_scan_comment_types();
+    if (module_exists('comment') && !empty($comment_types)) {
+      foreach ($cids as $cid) {
+        $links[] = l(t('Edit comment @comment', array('@comment' => $cid)), 'comment/' . $cid . '/edit', array('query' => drupal_get_destination()));
+      }
+    }
+
+    // Show link to blocks having this broken link.
+    if ($access_administer_blocks) {
+      foreach ($bids as $bid) {
+        $links[] = l(t('Edit block @block', array('@block' => $bid)), 'admin/structure/block/manage/block/' . $bid . '/configure', array('query' => drupal_get_destination()));
+      }
+    }
+
+    // Show link to redirect this broken internal link.
+    if (module_exists('redirect') && $access_administer_redirects && _linkchecker_is_internal_url($link)) {
+      $links[] = l(t('Create redirection'), 'admin/config/search/redirect/add', array('query' => array('source' => $link->internal, drupal_get_destination())));
+    }
+
+    // Create table data for output.
+    $rows[] = array(
+      'data' => array(
+        l(_filter_url_trim($link->url, 40), $link->url),
+        $link->code,
+        check_plain($link->error),
+        theme('item_list', array('items' => $links)),
+      ),
+    );
+  }
+
+  $build['linkchecker_table'] = array(
+    '#theme' => 'table',
+    '#header' => $header,
+    '#rows' => $rows,
+    '#empty' => t('No broken links have been found.'),
+  );
+  $build['linkchecker_pager'] = array('#theme' => 'pager');
+
+  return $build;
+}
+
+/**
+ * Check if the link is an internal URL or not.
+ *
+ * @param object $link
+ *   Link object.
+ *
+ * @return bool
+ *   TRUE if link is internal, otherwise FALSE.
+ */
+function _linkchecker_is_internal_url(&$link) {
+  global $base_url;
+
+  if (strpos($link->url, $base_url) === 0) {
+    $link->internal = trim(substr($link->url, strlen($base_url)), " \t\r\n\0\\/");
+    return TRUE;
+  }
+}
diff --git a/web/modules/contrib/linkchecker/linkchecker.permissions.yml b/web/modules/contrib/linkchecker/linkchecker.permissions.yml
new file mode 100644 (file)
index 0000000..2b89e28
--- /dev/null
@@ -0,0 +1,13 @@
+access broken links report:
+  title: 'Access broken links report'
+  description: 'Allows users to access the global broken links report.'
+access own broken links report:
+  title: 'Access own broken links report'
+  description: 'Allows users to access their user specific broken links report.'
+administer linkchecker:
+  title: 'Administer linkchecker'
+  description: 'Allows users to administer linkchecker settings.'
+  restrict access: true
+edit link settings:
+  title: 'Edit link settings'
+  description: 'Allows users to edit broken link settings.'
diff --git a/web/modules/contrib/linkchecker/linkchecker.redirect.inc b/web/modules/contrib/linkchecker/linkchecker.redirect.inc
new file mode 100644 (file)
index 0000000..0b1ea20
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ * Redirect interface to linkchecker functionalities.
+ */
+
+/**
+ * Implements hook_redirect_insert().
+ */
+function linkchecker_redirect_insert($redirect) {
+  linkchecker_redirect_update($redirect);
+}
+
+/**
+ * Implements hook_redirect_update().
+ */
+function linkchecker_redirect_update($redirect) {
+  // It's unknown if this is a redirect for HTTP/HTTPS or the encoded urls.
+  $url_http = url($redirect->source, array('absolute' => TRUE, $redirect->source_options));
+  $url_https = url($redirect->source, array('absolute' => TRUE, 'https' => TRUE, $redirect->source_options));
+
+  $urls = array(
+    $url_http,
+    $url_https,
+    rawurldecode($url_http),
+    rawurldecode($url_https),
+  );
+
+  _linkchecker_redirect_reset($urls);
+}
+
+/**
+ * Reset last_checked status.
+ *
+ * @param array $urls
+ *   An array of urls that should be checked on next cron run.
+ */
+function _linkchecker_redirect_reset($urls = array()) {
+  $urls = array_unique($urls);
+
+  $num_updated = db_update('linkchecker_link')
+    ->condition('urlhash', array_map('drupal_hash_base64', $urls))
+    ->condition('fail_count', 0, '>')
+    ->condition('status', 1)
+    ->fields(array('last_checked' => 0))
+    ->execute();
+
+  if ($num_updated) {
+    drupal_set_message(t('The link %url will be checked again on the next cron run.', array('%url' => $urls[0])));
+  }
+}
diff --git a/web/modules/contrib/linkchecker/linkchecker.routing.yml b/web/modules/contrib/linkchecker/linkchecker.routing.yml
new file mode 100644 (file)
index 0000000..a2a43e8
--- /dev/null
@@ -0,0 +1,37 @@
+linkchecker.admin_settings_form:
+  path: '/admin/config/content/linkchecker'
+  defaults:
+    _form: '\Drupal\linkchecker\Form\LinkCheckerAdminSettingsForm'
+    _title: 'Link checker'
+  requirements:
+    _permission: 'administer linkchecker'
+
+linkchecker.admin_report_page:
+  path: '/admin/reports/linkchecker'
+  defaults:
+    _controller: '\Drupal\linkchecker\Controller\LinkCheckerAdminReportPage::content'
+    _title: 'Broken links'
+  requirements:
+    _permission: 'access broken links report'
+
+linkchecker.user_report_page:
+  path: '/user/{user}/linkchecker'
+  defaults:
+    _title: 'Broken links'
+    _controller: '\Drupal\linkchecker\Controller\LinkCheckerUserReportPage::content'
+  options:
+    _admin_route: TRUE
+  requirements:
+    _custom_access: '\Drupal\linkchecker\Controller\LinkCheckerUserReportPage::access'
+    user: \d+
+
+linkchecker.edit_link_settings_form:
+  path: '/linkchecker/{linkchecker_link}/edit'
+  defaults:
+    _title: 'Edit link settings'
+    _form: '\Drupal\linkchecker\Form\LinkCheckerEditLinkSettingsForm'
+  options:
+    _admin_route: TRUE
+  requirements:
+    _custom_access: '\Drupal\linkchecker\Form\LinkCheckerEditLinkSettingsForm::access'
+    linkchecker_link: \d+
diff --git a/web/modules/contrib/linkchecker/migration_templates/d6_linkchecker_settings.yml b/web/modules/contrib/linkchecker/migration_templates/d6_linkchecker_settings.yml
new file mode 100644 (file)
index 0000000..2a13f03
--- /dev/null
@@ -0,0 +1,51 @@
+id: d6_linkchecker_settings
+label: Linkchecker 6 configuration
+migration_tags:
+  - Drupal 6
+source:
+  plugin: variable
+  variables:
+    - linkchecker_action_status_code_301
+    - linkchecker_action_status_code_404
+    - linkchecker_check_connections_max
+    - linkchecker_check_library
+    - linkchecker_check_links_interval
+    - linkchecker_fqdn_only
+    - linkchecker_check_useragent
+    - linkchecker_disable_link_check_for_urls
+    - linkchecker_extract_from_a
+    - linkchecker_extract_from_audio
+    - linkchecker_extract_from_embed
+    - linkchecker_extract_from_iframe
+    - linkchecker_extract_from_img
+    - linkchecker_extract_from_object
+    - linkchecker_extract_from_video
+    - linkchecker_filter_blacklist
+    - linkchecker_ignore_response_codes
+    - linkchecker_impersonate_user
+    - linkchecker_scan_blocks
+    - linkchecker_log_level
+process:
+  'scan_blocks': linkchecker_scan_blocks
+  'check_links_types': linkchecker_fqdn_only
+  'extract/from_a': linkchecker_extract_from_a
+  'extract/from_audio': linkchecker_extract_from_audio
+  'extract/from_embed': linkchecker_extract_from_embed
+  'extract/from_iframe': linkchecker_extract_from_iframe
+  'extract/from_img': linkchecker_extract_from_img
+  'extract/from_object': linkchecker_extract_from_object
+  'extract/from_video': linkchecker_extract_from_video
+  'extract/filter_blacklist': linkchecker_filter_blacklist
+  'check/connections_max': linkchecker_check_connections_max
+  'check/disable_link_check_for_urls': linkchecker_disable_link_check_for_urls
+  'check/library': linkchecker_check_library
+  'check/interval': linkchecker_check_links_interval
+  'check/useragent': linkchecker_check_useragent
+  'error/action_status_code_301': linkchecker_action_status_code_301
+  'error/action_status_code_404': linkchecker_action_status_code_404
+  'error/ignore_response_codes': linkchecker_ignore_response_codes
+  'error/impersonate_account': linkchecker_impersonate_user
+  'logging/level': linkchecker_log_level
+destination:
+  plugin: config
+  config_name: linkchecker.settings
diff --git a/web/modules/contrib/linkchecker/migration_templates/d7_linkchecker_settings.yml b/web/modules/contrib/linkchecker/migration_templates/d7_linkchecker_settings.yml
new file mode 100644 (file)
index 0000000..3d49573
--- /dev/null
@@ -0,0 +1,51 @@
+id: d7_linkchecker_settings
+label: Linkchecker 7 configuration
+migration_tags:
+  - Drupal 7
+source:
+  plugin: variable
+  variables:
+    - linkchecker_action_status_code_301
+    - linkchecker_action_status_code_404
+    - linkchecker_check_connections_max
+    - linkchecker_check_library
+    - linkchecker_check_links_interval
+    - linkchecker_check_links_types
+    - linkchecker_check_useragent
+    - linkchecker_disable_link_check_for_urls
+    - linkchecker_extract_from_a
+    - linkchecker_extract_from_audio
+    - linkchecker_extract_from_embed
+    - linkchecker_extract_from_iframe
+    - linkchecker_extract_from_img
+    - linkchecker_extract_from_object
+    - linkchecker_extract_from_video
+    - linkchecker_filter_blacklist
+    - linkchecker_ignore_response_codes
+    - linkchecker_impersonate_user
+    - linkchecker_scan_blocks
+    - linkchecker_log_level
+process:
+  'scan_blocks': linkchecker_scan_blocks
+  'check_links_types': linkchecker_check_links_types
+  'extract/from_a': linkchecker_extract_from_a
+  'extract/from_audio': linkchecker_extract_from_audio
+  'extract/from_embed': linkchecker_extract_from_embed
+  'extract/from_iframe': linkchecker_extract_from_iframe
+  'extract/from_img': linkchecker_extract_from_img
+  'extract/from_object': linkchecker_extract_from_object
+  'extract/from_video': linkchecker_extract_from_video
+  'extract/filter_blacklist': linkchecker_filter_blacklist
+  'check/connections_max': linkchecker_check_connections_max
+  'check/disable_link_check_for_urls': linkchecker_disable_link_check_for_urls
+  'check/library': linkchecker_check_library
+  'check/interval': linkchecker_check_links_interval
+  'check/useragent': linkchecker_check_useragent
+  'error/action_status_code_301': linkchecker_action_status_code_301
+  'error/action_status_code_404': linkchecker_action_status_code_404
+  'error/ignore_response_codes': linkchecker_ignore_response_codes
+  'error/impersonate_account': linkchecker_impersonate_user
+  'logging/level': linkchecker_log_level
+destination:
+  plugin: config
+  config_name: linkchecker.settings
diff --git a/web/modules/contrib/linkchecker/src/Controller/LinkCheckerAdminReportPage.php b/web/modules/contrib/linkchecker/src/Controller/LinkCheckerAdminReportPage.php
new file mode 100644 (file)
index 0000000..27cf549
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+
+namespace Drupal\linkchecker\Controller;
+
+use Drupal\Core\Session\AccountInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Builds admin broken link report page.
+ */
+class LinkCheckerAdminReportPage {
+
+  public function content() {
+    return '@TODO';
+  }
+
+}
diff --git a/web/modules/contrib/linkchecker/src/Controller/LinkCheckerUserReportPage.php b/web/modules/contrib/linkchecker/src/Controller/LinkCheckerUserReportPage.php
new file mode 100644 (file)
index 0000000..bc1479a
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+
+namespace Drupal\linkchecker\Controller;
+
+use Drupal\Core\Session\AccountInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Builds user broken link report page.
+ */
+class LinkCheckerUserReportPage {
+
+  public function content() {
+    return '@TODO';
+  }
+
+  /**
+   * Checks access for a specific request.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   Run access checks for this account.
+   */
+  public function access(AccountInterface $account) {
+    $user = \Drupal::currentUser();
+
+    // Users with 'access own broken links report' permission can only view their
+    // own report. Users with the 'access broken links report' permission can
+    // view the report for any authenticated user.
+    return AccessResult::allowedIf($account->id() && (($user->id()) == $account->id()) && $account->hasPermission('access own broken links report') || $account->hasPermission('access broken links report'));
+  }
+
+}
diff --git a/web/modules/contrib/linkchecker/src/Form/LinkCheckerAdminSettingsForm.php b/web/modules/contrib/linkchecker/src/Form/LinkCheckerAdminSettingsForm.php
new file mode 100644 (file)
index 0000000..f49ae59
--- /dev/null
@@ -0,0 +1,407 @@
+<?php
+
+namespace Drupal\linkchecker\Form;
+
+use Drupal\Core\Form\ConfigFormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Logger\RfcLogLevel;
+use Drupal\Core\Url;
+use Drupal\filter\FilterPluginCollection;
+use Drupal\user\Entity\User;
+
+/**
+ * Configure Linkchecker settings for this site.
+ */
+class LinkCheckerAdminSettingsForm extends ConfigFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'linkchecker_admin_settings';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditableConfigNames() {
+    return ['linkchecker.settings'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $config = $this->config('linkchecker.settings');
+
+    $form['general'] = [
+      '#type' => 'details',
+      '#title' => $this->t('General settings'),
+      '#description' => $this->t('Configure the <a href=":url">content types</a> that should be scanned for broken links.', [':url' => Url::fromRoute('entity.node_type.collection')->toString()]),
+      '#open' => TRUE,
+    ];
+
+    $block_custom_dependencies = '<div class="admin-requirements">';
+    $block_custom_dependencies .= $this->t('Requires: @module-list', ['@module-list' => (\Drupal::moduleHandler()->moduleExists('block') ? $this->t('@module (<span class="admin-enabled">enabled</span>)', ['@module' => 'Block']) : $this->t('@module (<span class="admin-disabled">disabled</span>)', ['@module' => 'Block']))]);
+    $block_custom_dependencies .= '</div>';
+
+    $form['general']['linkchecker_scan_blocks'] = [
+      '#default_value' => $config->get('scan_blocks'),
+      '#type' => 'checkbox',
+      '#title' => $this->t('Scan blocks for links'),
+      '#description' => $this->t('Enable this checkbox if links in blocks should be checked.') . $block_custom_dependencies,
+      '#disabled' => !\Drupal::moduleHandler()->moduleExists('block'),
+    ];
+    $form['general']['linkchecker_check_links_types'] = [
+      '#type' => 'select',
+      '#title' => $this->t('What type of links should be checked?'),
+      '#description' => $this->t('A full qualified link (http://example.com/foo/bar) to a page is considered external, whereas an absolute (/foo/bar) or relative link (node/123) without a domain is considered internal.'),
+      '#default_value' => $config->get('check_links_types'),
+      '#options' => [
+        '0' => $this->t('Internal and external'),
+        '1' => $this->t('External only (http://example.com/foo/bar)'),
+        '2' => $this->t('Internal only (node/123)'),
+      ],
+    ];
+
+    $form['tag'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Link extraction'),
+      '#open' => TRUE,
+    ];
+    $form['tag']['linkchecker_extract_from_a'] = [
+      '#default_value' => $config->get('extract.from_a'),
+      '#type' => 'checkbox',
+      '#title' => $this->t('Extract links in <code>&lt;a&gt;</code> and <code>&lt;area&gt;</code> tags'),
+      '#description' => $this->t('Enable this checkbox if normal hyperlinks should be extracted. The anchor element defines a hyperlink, the named target destination for a hyperlink, or both. The area element defines a hot-spot region on an image, and associates it with a hypertext link.'),
+    ];
+    $form['tag']['linkchecker_extract_from_audio'] = [
+      '#default_value' => $config->get('extract.from_audio'),
+      '#type' => 'checkbox',
+      '#title' => $this->t('Extract links in <code>&lt;audio&gt;</code> tags including their <code>&lt;source&gt;</code> and <code>&lt;track&gt;</code> tags'),
+      '#description' => $this->t('Enable this checkbox if links in audio tags should be extracted. The audio element is used to embed audio content.'),
+    ];
+    $form['tag']['linkchecker_extract_from_embed'] = [
+      '#default_value' => $config->get('extract.from_embed'),
+      '#type' => 'checkbox',
+      '#title' => $this->t('Extract links in <code>&lt;embed&gt;</code> tags'),
+      '#description' => $this->t('Enable this checkbox if links in embed tags should be extracted. This is an obsolete and non-standard element that was used for embedding plugins in past and should no longer used in modern websites.'),
+    ];
+    $form['tag']['linkchecker_extract_from_iframe'] = [
+      '#default_value' => $config->get('extract.from_iframe'),
+      '#type' => 'checkbox',
+      '#title' => $this->t('Extract links in <code>&lt;iframe&gt;</code> tags'),
+      '#description' => $this->t('Enable this checkbox if links in iframe tags should be extracted. The iframe element is used to embed another HTML page into a page.'),
+    ];
+    $form['tag']['linkchecker_extract_from_img'] = [
+      '#default_value' => $config->get('extract.from_img'),
+      '#type' => 'checkbox',
+      '#title' => $this->t('Extract links in <code>&lt;img&gt;</code> tags'),
+      '#description' => $this->t('Enable this checkbox if links in image tags should be extracted. The img element is used to add images to the content.'),
+    ];
+    $form['tag']['linkchecker_extract_from_object'] = [
+      '#default_value' => $config->get('extract.from_object'),
+      '#type' => 'checkbox',
+      '#title' => $this->t('Extract links in <code>&lt;object&gt;</code> and <code>&lt;param&gt;</code> tags'),
+      '#description' => $this->t('Enable this checkbox if multimedia and other links in object and their param tags should be extracted. The object tag is used for flash, java, quicktime and other applets.'),
+    ];
+    $form['tag']['linkchecker_extract_from_video'] = [
+      '#default_value' => $config->get('extract.from_video'),
+      '#type' => 'checkbox',
+      '#title' => $this->t('Extract links in <code>&lt;video&gt;</code> tags including their <code>&lt;source&gt;</code> and <code>&lt;track&gt;</code> tags'),
+      '#description' => $this->t('Enable this checkbox if links in video tags should be extracted. The video element is used to embed video content.'),
+    ];
+
+    // Get all filters available on the system.
+    $manager = \Drupal::service('plugin.manager.filter');
+    $bag = new FilterPluginCollection($manager, []);
+    $filter_info = $bag->getAll();
+    $filter_options = [];
+    $filter_descriptions = [];
+    foreach ($filter_info as $name => $filter) {
+      if (in_array($name, explode('|', LINKCHECKER_DEFAULT_FILTER_BLACKLIST))) {
+        $filter_options[$name] = $this->t('@title <span class="marker">(Recommended)</span>', array('@title' => $filter->getLabel()));
+      }
+      else {
+        $filter_options[$name] = $filter->getLabel();
+      }
+      $filter_descriptions[$name] = [
+        '#description' => $filter->getDescription(),
+      ];
+    }
+    $form['tag']['linkchecker_filter_blacklist'] = [
+      '#type' => 'checkboxes',
+      '#title' => $this->t('Text formats disabled for link extraction'),
+      '#default_value' => $config->get('extract.filter_blacklist'),
+      '#options' => $filter_options,
+      '#description' => $this->t('If a filter has been enabled for an input format it runs first and afterwards the link extraction. This helps the link checker module to find all links normally created by custom filters (e.g. Markdown filter, Bbcode). All filters used as inline references (e.g. Weblink filter <code>[link: id]</code>) to other content and filters only wasting processing time (e.g. Line break converter) should be disabled. This setting does not have any effect on how content is shown on a page. This feature optimizes the internal link extraction process for link checker and prevents false alarms about broken links in content not having the real data of a link.'),
+    ];
+    $form['tag']['linkchecker_filter_blacklist'] = array_merge($form['tag']['linkchecker_filter_blacklist'], $filter_descriptions);
+
+    $count_lids_enabled = db_query("SELECT count(lid) FROM {linkchecker_link} WHERE status = :status", array(':status' => 1))->fetchField();
+    $count_lids_disabled = db_query("SELECT count(lid) FROM {linkchecker_link} WHERE status = :status", array(':status' => 0))->fetchField();
+    $form['check'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Check settings'),
+      '#description' => $this->t('For simultaneous link checks it is recommended to install the <a href=":httprl">HTTP Parallel Request & Threading Library</a>. This may be <strong>necessary</strong> on larger sites with very many links (30.000+), but will also improve overall link check duration on smaller sites. Currently the site has @count links (@count_enabled enabled / @count_disabled disabled).', [':httprl' => 'http://drupal.org/project/httprl', '@count' => $count_lids_enabled+$count_lids_disabled, '@count_enabled' => $count_lids_enabled, '@count_disabled' => $count_lids_disabled]),
+      '#open' => TRUE,
+    ];
+    $form['check']['linkchecker_check_library'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Check library'),
+      '#description' => $this->t('Defines the library that is used for checking links.'),
+      '#default_value' => $config->get('check.library'),
+      '#options' => [
+        'core' => $this->t('Drupal core'),
+        'httprl' => $this->t('HTTP Parallel Request & Threading Library'),
+      ],
+    ];
+    $form['check']['linkchecker_check_connections_max'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Number of simultaneous connections'),
+      '#description' => $this->t('Defines the maximum number of simultaneous connections that can be opened by the server. <em>HTTP Parallel Request & Threading Library</em> make sure that a single domain is not overloaded beyond RFC limits. For small hosting plans with very limited CPU and RAM it may be required to reduce the default limit.'),
+      '#default_value' => $config->get('check.connections_max'),
+      '#options' => array_combine([2, 4, 8, 16, 24, 32, 48, 64, 96, 128], [2, 4, 8, 16, 24, 32, 48, 64, 96, 128]),
+      '#states' => [
+        // Hide the setting when Drupal core check library is selected.
+        'invisible' => [
+          ':input[name="check_library"]' => ['value' => 'core'],
+        ],
+      ],
+    ];
+    $form['check']['linkchecker_check_useragent'] = [
+      '#type' => 'select',
+      '#title' => $this->t('User-Agent'),
+      '#description' => $this->t('Defines the user agent that will be used for checking links on remote sites. If someone blocks the standard Drupal user agent you can try with a more common browser.'),
+      '#default_value' => $config->get('check.useragent'),
+      '#options' => [
+        'Drupal (+http://drupal.org/)' => 'Drupal (+http://drupal.org/)',
+        'Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; Touch; rv:11.0) like Gecko' => 'Windows 8.1 (x64), Internet Explorer 11.0',
+        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586' => 'Windows 10 (x64), Edge',
+        'Mozilla/5.0 (Windows NT 6.3; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0' => 'Windows 8.1 (x64), Mozilla Firefox 47.0',
+        'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0' => 'Windows 10 (x64), Mozilla Firefox 47.0',
+      ],
+    ];
+    $intervals = [86400, 172800, 259200, 604800, 1209600, 2419200, 4838400];
+    $period = array_map([\Drupal::service('date.formatter'), 'formatInterval'], array_combine($intervals, $intervals));
+    $form['check']['linkchecker_check_interval'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Check interval for links'),
+      '#description' => $this->t('This interval setting defines how often cron will re-check the status of links.'),
+      '#default_value' => $config->get('check.interval'),
+      '#options' => $period,
+    ];
+    $form['check']['linkchecker_disable_link_check_for_urls'] = [
+      '#default_value' => $config->get('check.disable_link_check_for_urls'),
+      '#type' => 'textarea',
+      '#title' => $this->t('Do not check the link status of links containing these URLs'),
+      '#description' => $this->t('By default this list contains the domain names reserved for use in documentation and not available for registration. See <a href=":rfc-2606">RFC 2606</a>, Section 3 for more information. URLs on this list are still extracted, but the link setting <em>Check link status</em> becomes automatically disabled to prevent false alarms. If you change this list you need to clear all link data and re-analyze your content. Otherwise this setting will only affect new links added after the configuration change.', array(':rfc-2606' => 'http://www.rfc-editor.org/rfc/rfc2606.txt')),
+    ];
+    //@fixme: constants no longer exists.
+    $form['check']['linkchecker_logging_level'] = [
+      '#default_value' => $config->get('logging.level'),
+      '#type' => 'select',
+      '#title' => $this->t('Log level'),
+      '#description' => $this->t('Controls the severity of logging.'),
+      '#options' => [
+        RfcLogLevel::DEBUG => $this->t('Debug messages'),
+        RfcLogLevel::INFO => $this->t('All messages (default)'),
+        RfcLogLevel::NOTICE => $this->t('Notices and errors'),
+        RfcLogLevel::WARNING => $this->t('Warnings and errors'),
+        RfcLogLevel::ERROR => $this->t('Errors only'),
+      ],
+    ];
+
+    $form['error'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Error handling'),
+      '#description' => $this->t('Defines error handling and custom actions to be executed if specific HTTP requests are failing.'),
+      '#open' => TRUE,
+    ];
+    $linkchecker_default_impersonate_account = User::load(1);
+    $form['error']['linkchecker_impersonate_account'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Impersonate user account'),
+      '#description' => $this->t('If below error handling actions are executed they can be impersonated with a custom user account. By default this is user %name, but you are able to assign a custom user to allow easier identification of these automatic revision updates. Make sure you select a user with <em>full</em> permissions on your site or the user may not able to access and save all content.', array('%name' => $linkchecker_default_impersonate_account->getAccountName())),
+      '#size' => 30,
+      '#maxlength' => 60,
+      '#autocomplete_path' => 'user/autocomplete',
+      '#default_value' => $config->get('error.impersonate_account'),
+    ];
+    $form['error']['linkchecker_action_status_code_301'] = [
+      '#title' => $this->t('Update permanently moved links'),
+      '#description' => $this->t('If enabled, outdated links in content providing a status <em>Moved Permanently</em> (status code 301) are automatically updated to the most recent URL. If used, it is recommended to use a value of <em>three</em> to make sure this is not only a temporarily change. This feature trust sites to provide a valid permanent redirect. A new content revision is automatically created on link updates if <em>create new revision</em> is enabled in the <a href=":content_types">content types</a> publishing options. It is recommended to create new revisions for all link checker enabled content types. Link updates are nevertheless always logged in <a href=":dblog">recent log entries</a>.', array(':dblog' => Url::fromRoute('dblog.overview')->toString(), ':content_types' => Url::fromRoute('entity.node_type.collection')->toString())),
+      '#type' => 'select',
+      '#default_value' => $config->get('error.action_status_code_301'),
+      '#options' => [
+        0 => $this->t('Disabled'),
+        1 => $this->t('After one failed check'),
+        2 => $this->t('After two failed checks'),
+        3 => $this->t('After three failed checks'),
+        5 => $this->t('After five failed checks'),
+        10 => $this->t('After ten failed checks'),
+      ],
+    ];
+    $form['error']['linkchecker_action_status_code_404'] = [
+      '#title' => $this->t('Unpublish content on file not found error'),
+      '#description' => $this->t('If enabled, content with one or more broken links (status code 404) will be unpublished and moved to moderation queue for review after the number of specified checks failed. If used, it is recommended to use a value of <em>three</em> to make sure this is not only a temporarily error.'),
+      '#type' => 'select',
+      '#default_value' => $config->get('error.action_status_code_404'),
+      '#options' => [
+        0 => $this->t('Disabled'),
+        1 => $this->t('After one file not found error'),
+        2 => $this->t('After two file not found errors'),
+        3 => $this->t('After three file not found errors'),
+        5 => $this->t('After five file not found errors'),
+        10 => $this->t('After ten file not found errors'),
+      ],
+    ];
+    $form['error']['linkchecker_ignore_response_codes'] = [
+      '#default_value' => $config->get('error.ignore_response_codes'),
+      '#type' => 'textarea',
+      '#title' => $this->t("Don't treat these response codes as errors"),
+      '#description' => $this->t('One HTTP status code per line, e.g. 403.'),
+    ];
+
+    // Buttons are only required for testing and debugging reasons.
+    $description = '<p>' . $this->t('These actions will either clear all link checker tables in the database and/or analyze all selected content types, blocks and fields (see settings above) for new/updated/removed links. Normally there is no need to press one of these buttons. Use this only for immediate cleanup tasks and to force a full re-build of the links to be checked in the linkchecker tables. Keep in mind that all custom link settings will be lost if you clear link data!') . '</p>';
+    $description .= '<p>' . $this->t('<strong>Note</strong>: These functions ONLY collect the links, they do not evaluate the HTTP response codes, this will be done during normal cron runs.') . '</p>';
+
+    $form['clear'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Maintenance'),
+      '#description' => $description,
+      '#open' => FALSE,
+    ];
+    $form['clear']['linkchecker_analyze'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Reanalyze content for links'),
+      '#submit' => ['::submitForm', '::submitAnalyzeLinks'],
+    ];
+    $form['clear']['linkchecker_clear_analyze'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Clear link data and analyze content for links'),
+      '#submit' => ['::submitForm', '::submitClearAnalyzeLinks'],
+    ];
+
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    parent::validateForm($form, $form_state);
+
+    $form_state->setValue('linkchecker_disable_link_check_for_urls', trim($form_state->getValue('linkchecker_disable_link_check_for_urls')));
+    $form_state->setValue('linkchecker_ignore_response_codes', trim($form_state->getValue('linkchecker_ignore_response_codes')));
+    $ignore_response_codes = preg_split('/(\r\n?|\n)/', $form_state->getValue('linkchecker_ignore_response_codes'));
+    foreach ($ignore_response_codes as $ignore_response_code) {
+      if (!_linkchecker_isvalid_response_code($ignore_response_code)) {
+        $form_state->setErrorByName('linkchecker_ignore_response_codes', $this->t('Invalid response code %code found.', ['%code' => $ignore_response_code]));
+      }
+    }
+
+    // @fixme: remove constant?
+    // Prevent the removal of RFC documentation domains. This are the official and
+    // reserved documentation domains and not "example" hostnames!
+    $linkchecker_disable_link_check_for_urls = array_filter(preg_split('/(\r\n?|\n)/', $form_state->getValue('linkchecker_disable_link_check_for_urls')));
+    $form_state->setValue('linkchecker_disable_link_check_for_urls', implode("\n", array_unique(array_merge(explode("\n", LINKCHECKER_RESERVED_DOCUMENTATION_DOMAINS), $linkchecker_disable_link_check_for_urls))));
+
+    // Validate impersonation user name.
+    $linkchecker_impersonate_account = user_load_by_name($form_state->getValue('linkchecker_impersonate_account'));
+    if (empty($linkchecker_impersonate_account->id())) {
+      $form_state->setErrorByName('linkchecker_impersonate_account', $this->t('User account %name cannot found.', ['%name' => $form_state->getValue('linkchecker_impersonate_account')]));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $config = $this->config('linkchecker.settings');
+    $config
+      ->set('scan_blocks', $form_state->getValue('linkchecker_scan_blocks'))
+      ->set('check_links_types', $form_state->getValue('linkchecker_check_links_types'))
+      ->set('extract.from_a', $form_state->getValue('linkchecker_extract_from_a'))
+      ->set('extract.from_audio', $form_state->getValue('linkchecker_extract_from_audio'))
+      ->set('extract.from_embed', $form_state->getValue('linkchecker_extract_from_embed'))
+      ->set('extract.from_iframe', $form_state->getValue('linkchecker_extract_from_iframe'))
+      ->set('extract.from_img', $form_state->getValue('linkchecker_extract_from_img'))
+      ->set('extract.from_object', $form_state->getValue('linkchecker_extract_from_object'))
+      ->set('extract.from_video', $form_state->getValue('linkchecker_extract_from_video'))
+      ->set('extract.filter_blacklist', $form_state->getValue('linkchecker_filter_blacklist'))
+      ->set('check.connections_max', $form_state->getValue('linkchecker_check_connections_max'))
+      ->set('check.disable_link_check_for_urls', $form_state->getValue('linkchecker_disable_link_check_for_urls'))
+      ->set('check.library', $form_state->getValue('linkchecker_check_library'))
+      ->set('check.interval', $form_state->getValue('linkchecker_check_interval'))
+      ->set('check.useragent', $form_state->getValue('linkchecker_check_useragent'))
+      ->set('error.action_status_code_301', $form_state->getValue('linkchecker_action_status_code_301'))
+      ->set('error.action_status_code_404', $form_state->getValue('linkchecker_action_status_code_404'))
+      ->set('error.ignore_response_codes', $form_state->getValue('linkchecker_ignore_response_codes'))
+      ->set('error.impersonate_account', $form_state->getValue('linkchecker_impersonate_account'))
+      ->set('logging.level', $form_state->getValue('linkchecker_logging_level'))
+      ->save();
+
+    // If block scanning has been selected.
+    if ($form_state->getValue('linkchecker_scan_blocks') > $form['general']['linkchecker_scan_blocks']['#default_value']) {
+      module_load_include('inc', 'linkchecker', 'linkchecker.batch');
+      batch_set(_linkchecker_batch_import_block_custom());
+    }
+
+    parent::submitForm($form, $form_state);
+  }
+
+  /**
+   * Analyze fields in all node types, comments, custom blocks.
+   */
+  function submitAnalyzeLinks(array &$form, FormStateInterface $form_state) {
+    // Start batch and analyze all nodes.
+    $node_types = linkchecker_scan_node_types();
+    if (!empty($node_types)) {
+      module_load_include('inc', 'linkchecker', 'linkchecker.batch');
+      batch_set(_linkchecker_batch_import_nodes($node_types));
+    }
+
+    $comment_types = linkchecker_scan_comment_types();
+    if (!empty($comment_types)) {
+      module_load_include('inc', 'linkchecker', 'linkchecker.batch');
+      batch_set(_linkchecker_batch_import_comments($comment_types));
+    }
+
+    if ($this->config('linkchecker.settings')->get('scan_blocks')) {
+      module_load_include('inc', 'linkchecker', 'linkchecker.batch');
+      batch_set(_linkchecker_batch_import_block_custom());
+    }
+  }
+
+  /**
+   * Clear link data and analyze fields in all content types, comments, custom
+   * blocks.
+   */
+  function submitClearAnalyzeLinks(array &$form, FormStateInterface $form_state) {
+    db_truncate('linkchecker_block_custom')->execute();
+    db_truncate('linkchecker_comment')->execute();
+    db_truncate('linkchecker_node')->execute();
+    db_truncate('linkchecker_link')->execute();
+
+    // Start batch and analyze all nodes.
+    $node_types = linkchecker_scan_node_types();
+    if (!empty($node_types)) {
+      module_load_include('inc', 'linkchecker', 'linkchecker.batch');
+      batch_set(_linkchecker_batch_import_nodes($node_types));
+    }
+
+    $comment_types = linkchecker_scan_comment_types();
+    if (!empty($comment_types)) {
+      module_load_include('inc', 'linkchecker', 'linkchecker.batch');
+      batch_set(_linkchecker_batch_import_comments($comment_types));
+    }
+
+    if ($this->config('linkchecker.settings')->get('scan_blocks')) {
+      module_load_include('inc', 'linkchecker', 'linkchecker.batch');
+      batch_set(_linkchecker_batch_import_block_custom());
+    }
+  }
+
+}
diff --git a/web/modules/contrib/linkchecker/src/Form/LinkCheckerEditLinkSettingsForm.php b/web/modules/contrib/linkchecker/src/Form/LinkCheckerEditLinkSettingsForm.php
new file mode 100644 (file)
index 0000000..d020b06
--- /dev/null
@@ -0,0 +1,117 @@
+<?php
+
+namespace Drupal\linkchecker\Form;
+
+use Drupal\Core\Datetime\DateFormatter;
+use Drupal\Core\Session\AccountInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Builds edit link settings form.
+ */
+class LinkCheckerEditLinkSettingsForm {
+
+  /**
+   * Edit link settings form.
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $link) {
+    $config = $this->config('linkchecker.settings');
+
+    $form['settings'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Settings'),
+      '#description' => $this->t('The link <a href=":url">:url</a> was last checked on @last_checked and failed @fail_count times.', [':url' => $link->url, '@fail_count' => $link->fail_count, '@last_checked' => DateFormatter::format($link->last_checked)]),
+      '#open' => TRUE,
+    ];
+
+    $form['settings']['lid'] = ['#type' => 'value', '#value' => $link->lid];
+    $form['settings']['url'] = ['#type' => 'value', '#value' => $link->url];
+
+    $form['settings']['method'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Select request method'),
+      '#default_value' => $link->method,
+      '#options' => [
+        'HEAD' => $this->t('HEAD'),
+        'GET' => $this->t('GET'),
+      ],
+      '#description' => $this->t('Select the request method used for link checks of this link. If you encounter issues like status code 500 errors with the HEAD request method you should try the GET request method before ignoring a link.'),
+    ];
+
+    $form['settings']['status'] = [
+      '#default_value' => $link->status,
+      '#type' => 'checkbox',
+      '#title' => $this->t('Check link status'),
+      '#description' => $this->t('Uncheck if you wish to ignore this link. Use this setting only as a last resort if there is no other way to solve a failed link check.'),
+    ];
+
+    $form['maintenance'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Maintenance'),
+      '#open' => TRUE,
+    ];
+
+    $form['maintenance']['recheck'] = [
+      '#default_value' => 0,
+      '#type' => 'checkbox',
+      '#title' => $this->t('Re-check link status on next cron run'),
+      '#description' => $this->t('Enable this checkbox if you want to re-check the link during the next cron job rather than wait for the next scheduled check on @date.', ['@date' => DateFormatter::format($link->last_checked + $config->get('check.interval'))]),
+    ];
+
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * Edit link settings form submit handler.
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    // Force link re-check asap.
+    if ($form_state->getValue('recheck')) {
+      db_update('linkchecker_link')
+        ->condition('lid', $form_state->getValue('lid'))
+        ->fields(array('last_checked' => 0))
+        ->execute();
+      drupal_set_message(t('The link %url will be checked again on the next cron run.', ['%url' => $form_state->getValue('url')]));
+    }
+
+    if ($form_state->getValue('method') != $form['settings']['method']['#default_value']) {
+      // Update settings and reset statistics for a quick re-check.
+      db_update('linkchecker_link')
+        ->condition('lid', $form_state->getValue('lid'))
+        ->fields(array(
+          'method' => $form_state->getValue('method'),
+          'fail_count' => 0,
+          'last_checked' => 0,
+          'status' => $form_state->getValue('status'),
+        ))
+        ->execute();
+      drupal_set_message(t('The link settings for %url have been saved and the fail counter has been reset.', array('%url' => $form_state->getValue('url'))));
+    }
+    else {
+      // Update setting only.
+      db_update('linkchecker_link')
+        ->condition('lid', $form_state->getValue('lid'))
+        ->fields(array(
+          'method' => $form_state->getValue('method'),
+          'status' => $form_state->getValue('status'),
+        ))
+        ->execute();
+      drupal_set_message(t('The link settings for %url have been saved.', array('%url' => $form_state->getValue('url'))));
+    }
+  }
+
+  /**
+   * Checks access for a specific request.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   Run access checks for this account.
+   */
+  // @FIXME
+  public function access($link) {
+    // Check permissions and combine that with any custom access checking needed. Pass forward
+    // parameters from the route and/or request as needed.
+    return AccessResult::allowedIf($account->hasPermission('edit link settings') && _linkchecker_link_access($link));
+        //$this->someOtherCustomCondition());
+  }
+
+}
diff --git a/web/modules/contrib/linkchecker/src/Tests/LinkCheckerInterfaceTest.php b/web/modules/contrib/linkchecker/src/Tests/LinkCheckerInterfaceTest.php
new file mode 100644 (file)
index 0000000..d303b94
--- /dev/null
@@ -0,0 +1,214 @@
+<?php
+
+namespace Drupal\linkchecker\Tests;
+
+use Drupal\Core\Session\AccountInterface;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Test case for interface tests.
+ *
+ * @group Link checker
+ */
+class LinkCheckerInterfaceTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = [
+    'block',
+    'linkchecker',
+    'path',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $full_html_format = filter_format_load('full_html');
+    $permissions = [
+      // Block permissions.
+      'administer blocks',
+      // Comment permissions.
+      'administer comments',
+      'access comments',
+      'post comments',
+      'skip comment approval',
+      'edit own comments',
+      // Node permissions.
+      'create page content',
+      'edit own page content',
+      // Path aliase permissions.
+      'administer url aliases',
+      'create url aliases',
+      // Content filter permissions.
+      filter_permission_name($full_html_format),
+    ];
+
+    // User to set up google_analytics.
+    $this->admin_user = $this->drupalCreateUser($permissions);
+    $this->drupalLogin($this->admin_user);
+  }
+
+  /**
+   * Test the interface functionality.
+   */
+  public function testLinkCheckerCreateNodeWithBrokenLinks() {
+    // Enable all node type page for link extraction.
+    variable_set('linkchecker_scan_node_page', TRUE);
+
+    // Core enables the URL filter for "Full HTML" by default.
+    // -> Blacklist / Disable URL filter for testing.
+    variable_set('linkchecker_filter_blacklist', array('filter_url' => 'filter_url'));
+
+    // Extract from all link checker supported HTML tags.
+    variable_set('linkchecker_extract_from_a', 1);
+    variable_set('linkchecker_extract_from_audio', 1);
+    variable_set('linkchecker_extract_from_embed', 1);
+    variable_set('linkchecker_extract_from_iframe', 1);
+    variable_set('linkchecker_extract_from_img', 1);
+    variable_set('linkchecker_extract_from_object', 1);
+    variable_set('linkchecker_extract_from_video', 1);
+
+    $url1 = 'http://example.com/node/broken/link';
+    $body = 'Lorem ipsum dolor sit amet <a href="' . $url1 . '">broken link</a> sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat';
+
+    // Save folder names in variables for reuse.
+    $folder1 = $this->randomName(10);
+    $folder2 = $this->randomName(5);
+
+    // Fill node array.
+    $langcode = LANGUAGE_NONE;
+    $edit = array();
+    $edit['title'] = $this->randomName(32);
+    $edit["body[$langcode][0][value]"] = $body;
+    $edit['path[alias]'] = $folder1 . '/' . $folder2;
+    $edit["body[$langcode][0][format]"] = 'full_html';
+
+    // Extract only full qualified URLs.
+    variable_set('linkchecker_check_links_types', 1);
+
+    // Verify path input field appears on add "Basic page" form.
+    $this->drupalGet('node/add/page');
+    // Verify path input is present.
+    $this->assertFieldByName('path[alias]', '', 'Path input field present on add Basic page form.');
+
+    // Save node.
+    $this->drupalPost('node/add/page', $edit, t('Save'));
+    $this->assertText(t('@type @title has been created.', array('@type' => 'Basic page', '@title' => $edit['title'])), 'Node was created.');
+
+    $node = $this->drupalGetNodeByTitle($edit['title']);
+    $this->assertTrue($node, 'Node found in database.');
+
+    // Verify if the content link is extracted properly.
+    $link = $this->getLinkCheckerLink($url1);
+    if ($link) {
+      $this->assertIdentical($link->url, $url1, format_string('URL %url found.', array('%url' => $url1)));
+    }
+    else {
+      $this->fail(format_string('URL %url not found.', array('%url' => $url1)));
+    }
+
+    // Set link as failed once.
+    $fail_count = 1;
+    $status = '301';
+    $this->setLinkAsBroken($url1, $status, $fail_count);
+    $this->drupalGet('node/' . $node->nid . '/edit');
+    $this->assertRaw(format_plural($fail_count, 'Link check of <a href="@url">@url</a> failed once (status code: @code).', 'Link check of <a href="@url">@url</a> failed @count times (status code: @code).', array('@url' => $url1, '@code' => $status)), 'Link check failed once found.');
+
+    // Set link as failed multiple times.
+    $fail_count = 4;
+    $status = '404';
+    $this->setLinkAsBroken($url1, $status, $fail_count);
+    $this->drupalGet('node/' . $node->nid . '/edit');
+    $this->assertRaw(format_plural($fail_count, 'Link check of <a href="@url">@url</a> failed once (status code: @code).', 'Link check of <a href="@url">@url</a> failed @count times (status code: @code).', array('@url' => $url1, '@code' => $status)), 'Link check failed multiple times found.');
+  }
+
+  public function testLinkCheckerCreateBlockWithBrokenLinks() {
+    // Enable all blocks for link extraction.
+    variable_set('linkchecker_scan_blocks', 1);
+
+    // Confirm that the add block link appears on block overview pages.
+    $this->drupalGet('admin/structure/block');
+    $this->assertRaw(l(t('Add block'), 'admin/structure/block/add'), 'Add block link is present on block overview page for default theme.');
+
+    $url1 = 'http://example.com/block/broken/link';
+    $body = 'Lorem ipsum dolor sit amet <a href="' . $url1 . '">broken link</a> sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat';
+
+    // Add a new custom block by filling out the input form on the admin/structure/block/add page.
+    $custom_block = array();
+    $custom_block['info'] = $this->randomName(8);
+    $custom_block['title'] = $this->randomName(8);
+    $custom_block['body[value]'] = $body;
+    $custom_block['body[format]'] = 'full_html';
+    $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block'));
+
+    // Confirm that the custom block has been created, and then query the created bid.
+    $this->assertText(t('The block has been created.'), 'Custom block successfully created.');
+    $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField();
+
+    // Check to see if the custom block was created by checking that it's in the database.
+    $this->assertNotNull($bid, 'Custom block found in database');
+
+    // Verify if the content link is extracted properly.
+    $link = $this->getLinkCheckerLink($url1);
+    if ($link) {
+      $this->assertIdentical($link->url, $url1, format_string('URL %url found.', array('%url' => $url1)));
+    }
+    else {
+      $this->fail(format_string('URL %url not found.', array('%url' => $url1)));
+    }
+
+    // Set link as failed once.
+    $fail_count = 1;
+    $status = '301';
+    $this->setLinkAsBroken($url1, $status, $fail_count);
+    $this->drupalGet('admin/structure/block/manage/block/' . $bid . '/configure');
+    $this->assertRaw(format_plural($fail_count, 'Link check of <a href="@url">@url</a> failed once (status code: @code).', 'Link check of <a href="@url">@url</a> failed @count times (status code: @code).', array('@url' => $url1, '@code' => $status)), 'Link check failed once found.');
+
+    // Set link as failed multiple times.
+    $fail_count = 4;
+    $status = '404';
+    $this->setLinkAsBroken($url1, $status, $fail_count);
+    $this->drupalGet('admin/structure/block/manage/block/' . $bid . '/configure');
+    $this->assertRaw(format_plural($fail_count, 'Link check of <a href="@url">@url</a> failed once (status code: @code).', 'Link check of <a href="@url">@url</a> failed @count times (status code: @code).', array('@url' => $url1, '@code' => $status)), 'Link check failed multiple times found.');
+  }
+
+  /**
+   * Set an URL as broken.
+   *
+   * @param string $url
+   *   URL of the link to find.
+   * @param string $status
+   *   A fake HTTP code for testing.
+   */
+  function setLinkAsBroken($url = NULL, $status = '404', $fail_count = 0) {
+    db_update('linkchecker_link')
+    ->condition('urlhash', drupal_hash_base64($url))
+    ->fields(array(
+      'code' => $status,
+      'error' => 'Not available (test running)',
+      'fail_count' => $fail_count,
+      'last_checked' => time(),
+      'status' => 1,
+    ))
+    ->execute();
+  }
+
+  /**
+   * Get linkchecker link by url.
+   *
+   * @param string $url
+   *   URL of the link to find.
+   *
+   * @return object
+   *   The link object.
+   */
+  function getLinkCheckerLink($url) {
+    return db_query('SELECT * FROM {linkchecker_link} WHERE urlhash = :urlhash', array(':urlhash' => drupal_hash_base64($url)))->fetchObject();
+  }
+}
diff --git a/web/modules/contrib/linkchecker/src/Tests/LinkCheckerLinkExtractionTest.php b/web/modules/contrib/linkchecker/src/Tests/LinkCheckerLinkExtractionTest.php
new file mode 100644 (file)
index 0000000..de937e0
--- /dev/null
@@ -0,0 +1,395 @@
+<?php
+
+namespace Drupal\linkchecker\Tests;
+
+use Drupal\Core\Session\AccountInterface;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Test Link checker module link extraction functionality.
+ *
+ * @group Link checker
+ */
+class LinkCheckerLinkExtractionTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = [
+    'linkchecker',
+    'path',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $full_html_format = filter_format_load('full_html');
+    $permissions = [
+      'create page content',
+      'edit own page content',
+      'administer url aliases',
+      'create url aliases',
+      filter_permission_name($full_html_format),
+    ];
+
+    // User to set up google_analytics.
+    $this->admin_user = $this->drupalCreateUser($permissions);
+    $this->drupalLogin($this->admin_user);
+  }
+
+  public function testLinkCheckerCreateNodeWithLinks() {
+
+    // Enable all node type page for link extraction.
+    variable_set('linkchecker_scan_node_page', TRUE);
+    variable_set('linkchecker_scan_blocks', 1);
+
+    // Core enables the URL filter for "Full HTML" by default.
+    // -> Blacklist / Disable URL filter for testing.
+    variable_set('linkchecker_filter_blacklist', array('filter_url' => 'filter_url'));
+
+    // Extract from all link checker supported HTML tags.
+    variable_set('linkchecker_extract_from_a', 1);
+    variable_set('linkchecker_extract_from_audio', 1);
+    variable_set('linkchecker_extract_from_embed', 1);
+    variable_set('linkchecker_extract_from_iframe', 1);
+    variable_set('linkchecker_extract_from_img', 1);
+    variable_set('linkchecker_extract_from_object', 1);
+    variable_set('linkchecker_extract_from_video', 1);
+
+    $body = <<<EOT
+<!-- UNSUPPORTED for link checking: -->
+
+<a href="mailto:test@example.com">Send email</a>
+<a href="javascript:foo()">Execute JavaScript</a>
+
+<!-- SUPPORTED for link checking: -->
+
+<!-- URL in HTML comment: http://example.com/test-if-url-filter-is-disabled -->
+
+<!-- Relative URLs -->
+<img src="test.png" alt="Test image 1" />
+<img src="../foo1/test.png" alt="Test image 2" />
+
+<a href="../foo1/bar1">../foo1/bar1</a>
+<a href="./foo2/bar2">./foo2/bar2</a>
+<a href="../foo3/../foo4/foo5">../foo3/../foo4/foo5</a>
+<a href="./foo4/../foo5/foo6">./foo4/../foo5/foo6</a>
+<a href="./foo4/./foo5/foo6">./foo4/./foo5/foo6</a>
+<a href="./test/foo bar/is_valid-hack.test">./test/foo bar/is_valid-hack.test</a>
+
+<!-- URL with uncommon chars that could potentially fail to extract. See http://drupal.org/node/465462. -->
+<a href="http://www.lagrandeepicerie.fr/#e-boutique/Les_produits_du_moment,2/coffret_vins_doux_naturels,149">URL with uncommon chars</a>
+<a href="http://example.com/foo bar/is_valid-hack.test">URL with space</a>
+<a href="http://example.com/ajax.html#key1=value1&key2=value2">URL with ajax query params</a>
+<a href="http://example.com/test.html#test">URL with standard anchor</a>
+
+<!-- object tag: Embed SWF files -->
+<object width="150" height="116"
+  type="application/x-shockwave-flash"
+  data="http://wetterservice.msn.de/phclip.swf?zip=60329&ort=Frankfurt">
+    <param name="movie" value="http://wetterservice.msn.de/phclip.swf?zip=60329&ort=Frankfurt" />
+    <img src="flash.png" width="150" height="116" alt="" /> <br />
+      No weather report visible? At <a href="http://www.msn.de/">MSN</a>
+      you are able to find the weather report missing here and the
+      Flash plugin can be found at <a href="http://www.adobe.com/">Adobe</a>.
+</object>
+
+<!-- object tag: Embed Quicktime Movies on HTML pages -->
+<object width="420" height="282"
+  classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B"
+  codebase="http://www.apple.com/qtactivex/qtplugin.cab">
+  <param name="src" value="http://example.net/video/foo1.mov" />
+  <param name="href" value="http://example.net/video/foo2.mov" />
+  <param name="controller" value="true" />
+  <param name="autoplay" value="false" />
+  <param name="scale" value="aspect" />
+  <!--[if gte IE 7]> <!-->
+  <object type="video/quicktime" data="http://example.net/video/foo3.mov" width="420" height="282">
+    <param name="controller" value="true" />
+    <param name="autoplay" value="false" />
+  </object>
+  <!--<![endif]-->
+</object>
+
+<!-- object tag: Play MP4 videos on HTML pages -->
+<object data="http://example.org/video/foo1.mp4" type="video/mp4" width="420" height="288">
+  <param name="src" value="http://example.org/video/foo2.mp4" />
+  <param name="autoplay" value="false" />
+  <param name="autoStart" value="0" />
+  <a href="http://example.org/video/foo3.mp4">/video/foo3.mp4</a>
+</object>
+
+<!-- object tag: Play MP4 videos with Quicktime -->
+<object width="420" height="282" codebase="http://www.apple.com/qtactivex/qtplugin.cab">
+  <param name="src" value="http://example.org/video/foo4.mp4" />
+  <param name="href" value="http://example.org/video/foo5.mp4" />
+  <param name="controller" value="true" />
+  <param name="autoplay" value="false" />
+  <param name="scale" value="aspect" />
+  <!--[if gte IE 7]> <!-->
+  <object type="video/quicktime" data="http://example.org/video/foo6.mp4" width="420" height="282">
+    <param name="controller" value="true" />
+    <param name="autoplay" value="false" />
+  </object>
+  <!--<![endif]-->
+</object>
+
+<!-- object tag: Play flash videos on HTML pages -->
+<object type="application/x-shockwave-flash" data="http://example.org/video/player1.swf" width="420" height="270">
+    <param name="movie" value="http://example.org/video/player2.swf" />
+    <param src="movie" value="http://example.org/video/player3.swf" />
+    <param name="flashvars" value="file=http://example.org/video/foo1.flv&width=420&height=270" />
+</object>
+
+<!-- Embed ActiveX control as objekt -->
+<object width="267" height="175" classid="CLSID:05589FA1-C356-11CE-BF01-00AA0055595A">
+  <param name="filename" value="ritmo.mid">
+</object>
+
+<!-- Add inline frames -->
+<iframe src="http://example.com/iframe/" name="ExampleIFrame" width="300" height="200">
+  <p>Your browser does not support inline frames.</p>
+</iframe>
+
+<!-- https://developer.mozilla.org/en/Using_audio_and_video_in_Firefox -->
+
+<!-- http://www.theora.org/cortado/ -->
+<video src="my_ogg_video.ogg" controls width="320" height="240">
+  <object type="application/x-java-applet" width="320" height="240">
+    <param name="archive" value="http://www.theora.org/cortado.jar">
+    <param name="code" value="com.fluendo.player.Cortado.class">
+    <param name="url" value="my_ogg_video.ogg">
+    <p>You need to install Java to play this file.</p>
+  </object>
+</video>
+
+<video src="video.ogv" controls>
+  <object data="flvplayer1.swf" type="application/x-shockwave-flash">
+    <param name="movie" value="flvplayer2.swf" />
+  </object>
+</video>
+
+<video controls>
+  <source src="http://v2v.cc/~j/theora_testsuite/pixel_aspect_ratio.ogg" type="video/ogg">
+  <source src="http://v2v.cc/~j/theora_testsuite/pixel_aspect_ratio.mov">
+  Your browser does not support the <code>video</code> element.
+</video>
+
+<video controls>
+  <source src="foo.ogg" type="video/ogg; codecs=&quot;dirac, speex&quot;">
+  Your browser does not support the <code>video</code> element.
+</video>
+
+<video src="http://v2v.cc/~j/theora_testsuite/320x240.ogg" controls>
+  Your browser does not support the <code>video</code> element.
+</video>
+EOT;
+
+    // Save folder names in variables for reuse.
+    $folder1 = $this->randomName(10);
+    $folder2 = $this->randomName(5);
+
+    // Fill node array.
+    $langcode = LANGUAGE_NONE;
+    $edit = array();
+    $edit['title'] = $this->randomName(32);
+    $edit["body[$langcode][0][value]"] = $body;
+    $edit['path[alias]'] = $folder1 . '/' . $folder2;
+    $edit["body[$langcode][0][format]"] = 'full_html';
+
+    // Extract only full qualified URLs.
+    variable_set('linkchecker_check_links_types', 1);
+
+    // Verify path input field appears on add "Basic page" form.
+    $this->drupalGet('node/add/page');
+    // Verify path input is present.
+    $this->assertFieldByName('path[alias]', '', 'Path input field present on add Basic page form.');
+
+    // Save node.
+    $this->drupalPost('node/add/page', $edit, t('Save'));
+    $this->assertText(t('@type @title has been created.', array('@type' => 'Basic page', '@title' => $edit['title'])), 'Node was created.');
+
+    // Verify if the content links are extracted properly.
+    $urls_fqdn = array(
+      'http://www.lagrandeepicerie.fr/#e-boutique/Les_produits_du_moment,2/coffret_vins_doux_naturels,149',
+      'http://wetterservice.msn.de/phclip.swf?zip=60329&ort=Frankfurt',
+      'http://www.msn.de/',
+      'http://www.adobe.com/',
+      'http://www.apple.com/qtactivex/qtplugin.cab',
+      'http://example.net/video/foo1.mov',
+      'http://example.net/video/foo2.mov',
+      'http://example.net/video/foo3.mov',
+      'http://example.org/video/foo1.mp4',
+      'http://example.org/video/foo2.mp4',
+      'http://example.org/video/foo3.mp4',
+      'http://example.org/video/foo4.mp4',
+      'http://example.org/video/foo5.mp4',
+      'http://example.org/video/foo6.mp4',
+      'http://example.org/video/player1.swf',
+      'http://example.org/video/player2.swf',
+      'http://example.org/video/player3.swf',
+      'http://example.com/iframe/',
+      'http://www.theora.org/cortado.jar',
+      'http://v2v.cc/~j/theora_testsuite/pixel_aspect_ratio.ogg',
+      'http://v2v.cc/~j/theora_testsuite/pixel_aspect_ratio.mov',
+      'http://v2v.cc/~j/theora_testsuite/320x240.ogg',
+      'http://example.com/foo bar/is_valid-hack.test',
+      'http://example.com/ajax.html#key1=value1&key2=value2',
+      'http://example.com/test.html#test',
+    );
+
+    foreach ($urls_fqdn as $org_url => $check_url) {
+      $link = $this->getLinkCheckerLink($check_url);
+      if ($link) {
+        $this->assertIdentical($link->url, $check_url, format_string('Absolute URL %org_url matches expected result %check_url.', array('%org_url' => $org_url, '%check_url' => $check_url)));
+      }
+      else {
+        $this->fail(format_string('URL %check_url not found.', array('%check_url' => $check_url)));
+      }
+    }
+
+    // Check if the number of links is correct.
+    // - Verifies if all HTML tag regexes matched.
+    // - Verifies that the linkchecker filter blacklist works well.
+    $urls_in_database = $this->getLinkCheckerLinksCount();
+    $urls_expected_count = count($urls_fqdn);
+    $this->assertEqual($urls_in_database, $urls_expected_count, format_string('Found @urls_in_database URLs in database matches expected result of @urls_expected_count.', array('@urls_in_database' => $urls_in_database, '@urls_expected_count' => $urls_expected_count)));
+
+    // Extract all URLs including relative path.
+    variable_set('clean_url', 1);
+    variable_set('linkchecker_check_links_types', 0);
+
+    $node = $this->drupalGetNodeByTitle($edit['title']);
+    $this->assertTrue($node, 'Node found in database.');
+    $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+    $this->assertRaw(t('@type %title has been updated.', array('@type' => 'Basic page', '%title' => $edit['title'])));
+
+    // @todo Path alias seems not saved!???
+    // $this->assertIdentical($node->path, $edit['path'], format_string('URL alias "@node-path" matches path "@edit-path".', array('@node-path' => $node->path, '@edit-path' => $edit['path'])));
+
+    // Verify if the content links are extracted properly.
+    global $base_root, $base_path;
+    $urls_relative = array(
+      '../foo1/test.png' => $base_root . $base_path . 'foo1/test.png',
+      'test.png' => $base_root . $base_path . $folder1 . '/test.png',
+      '../foo1/bar1' => $base_root . $base_path . 'foo1/bar1',
+      './foo2/bar2' => $base_root . $base_path . $folder1 . '/foo2/bar2',
+      '../foo3/../foo4/foo5' => $base_root . $base_path . 'foo4/foo5',
+      './foo4/../foo5/foo6' => $base_root . $base_path . $folder1 . '/foo5/foo6',
+      './foo4/./foo5/foo6' => $base_root . $base_path . $folder1 . '/foo4/foo5/foo6',
+      './test/foo bar/is_valid-hack.test' => $base_root . $base_path . $folder1 . '/test/foo bar/is_valid-hack.test',
+      'flash.png' => $base_root . $base_path . $folder1 . '/flash.png',
+      'ritmo.mid' => $base_root . $base_path . $folder1 . '/ritmo.mid',
+      'my_ogg_video.ogg' => $base_root . $base_path . $folder1 . '/my_ogg_video.ogg',
+      'video.ogv' => $base_root . $base_path . $folder1 . '/video.ogv',
+      'flvplayer1.swf' => $base_root . $base_path . $folder1 . '/flvplayer1.swf',
+      'flvplayer2.swf' => $base_root . $base_path . $folder1 . '/flvplayer2.swf',
+      'foo.ogg' => $base_root . $base_path . $folder1 . '/foo.ogg',
+    );
+    $this->verbose(theme('item_list', array('items' => $urls_relative, 'title' => 'Verify if following relative URLs exists:')));
+
+    $links_debug = array();
+    $result = db_query('SELECT url FROM {linkchecker_link}');
+    foreach ($result as $row) {
+      $links_debug[] = $row->url;
+    }
+    $this->verbose(theme('item_list', array('items' => $links_debug, 'title' => 'Following URLs exists:')));
+
+    foreach ($urls_relative as $org_url => $check_url) {
+      $link = $this->getLinkCheckerLink($check_url);
+      if ($link) {
+        $this->assertIdentical($link->url, $check_url, format_string('Relative URL %org_url matches expected result %check_url.', array('%org_url' => $org_url, '%check_url' => $check_url)));
+      }
+      else {
+        $this->fail(format_string('URL %check_url not found.', array('%check_url' => $check_url)));
+      }
+    }
+
+    // Check if the number of links is correct.
+    $urls_in_database = $this->getLinkCheckerLinksCount();
+    $urls_expected_count = count($urls_fqdn + $urls_relative);
+    $this->assertEqual($urls_in_database, $urls_expected_count, format_string('Found @urls_in_database URLs in database matches expected result of @urls_expected_count.', array('@urls_in_database' => $urls_in_database, '@urls_expected_count' => $urls_expected_count)));
+
+    // Verify if link check has been enabled for normal URLs.
+    $urls = array(
+      'http://www.lagrandeepicerie.fr/#e-boutique/Les_produits_du_moment,2/coffret_vins_doux_naturels,149',
+      'http://wetterservice.msn.de/phclip.swf?zip=60329&ort=Frankfurt',
+      'http://www.msn.de/',
+      'http://www.adobe.com/',
+      'http://www.apple.com/qtactivex/qtplugin.cab',
+      'http://www.theora.org/cortado.jar',
+      'http://v2v.cc/~j/theora_testsuite/pixel_aspect_ratio.ogg',
+      'http://v2v.cc/~j/theora_testsuite/pixel_aspect_ratio.mov',
+      'http://v2v.cc/~j/theora_testsuite/320x240.ogg',
+      $base_root . $base_path . 'foo1/test.png',
+      $base_root . $base_path . $folder1 . '/test.png',
+      $base_root . $base_path . 'foo1/bar1',
+      $base_root . $base_path . $folder1 . '/foo2/bar2',
+      $base_root . $base_path . 'foo4/foo5',
+      $base_root . $base_path . $folder1 . '/foo5/foo6',
+      $base_root . $base_path . $folder1 . '/foo4/foo5/foo6',
+      $base_root . $base_path . $folder1 . '/test/foo bar/is_valid-hack.test',
+      $base_root . $base_path . $folder1 . '/flash.png',
+      $base_root . $base_path . $folder1 . '/ritmo.mid',
+      $base_root . $base_path . $folder1 . '/my_ogg_video.ogg',
+      $base_root . $base_path . $folder1 . '/video.ogv',
+      $base_root . $base_path . $folder1 . '/flvplayer1.swf',
+      $base_root . $base_path . $folder1 . '/flvplayer2.swf',
+      $base_root . $base_path . $folder1 . '/foo.ogg',
+    );
+
+    foreach ($urls as $url) {
+      $this->assertTrue($this->getLinkcheckerLink($url)->status, format_string('Link check for %url is enabled.', array('%url' => $url)));
+    }
+
+    // Verify if link check has been disabled for example.com/net/org URLs.
+    $documentation_urls = array(
+      'http://example.net/video/foo1.mov',
+      'http://example.net/video/foo2.mov',
+      'http://example.net/video/foo3.mov',
+      'http://example.org/video/foo1.mp4',
+      'http://example.org/video/foo2.mp4',
+      'http://example.org/video/foo3.mp4',
+      'http://example.org/video/foo4.mp4',
+      'http://example.org/video/foo5.mp4',
+      'http://example.org/video/foo6.mp4',
+      'http://example.org/video/player1.swf',
+      'http://example.org/video/player2.swf',
+      'http://example.org/video/player3.swf',
+      'http://example.com/iframe/',
+      'http://example.com/foo bar/is_valid-hack.test',
+      'http://example.com/ajax.html#key1=value1&key2=value2',
+      'http://example.com/test.html#test',
+    );
+
+    foreach ($documentation_urls as $documentation_url) {
+      $this->assertFalse($this->getLinkcheckerLink($documentation_url)->status, format_string('Link check for %url is disabled.', array('%url' => $documentation_url)));
+    }
+
+  }
+
+  /**
+   * Get linkchecker link by url.
+   *
+   * @param string $url
+   *   URL of the link to find.
+   *
+   * @return object
+   *   The link object.
+   */
+  function getLinkCheckerLink($url) {
+    return db_query('SELECT * FROM {linkchecker_link} WHERE urlhash = :urlhash', array(':urlhash' => drupal_hash_base64($url)))->fetchObject();
+  }
+
+  /**
+   * Get the current number of links in linkchecker_links table.
+   */
+  function getLinkCheckerLinksCount() {
+    return db_query('SELECT COUNT(1) FROM {linkchecker_link}')->fetchField();
+  }
+}
diff --git a/web/modules/contrib/livereload b/web/modules/contrib/livereload
deleted file mode 160000 (submodule)
index 223feb7..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 223feb798d2af436818c3d8fd0b47718569ebd4b
diff --git a/web/modules/contrib/livereload/README.txt b/web/modules/contrib/livereload/README.txt
new file mode 100644 (file)
index 0000000..b941b00
--- /dev/null
@@ -0,0 +1,45 @@
+LiveReload
+http://drupal.org/project/livereload
+====================================
+
+
+DESCRIPTION
+-----------
+LiveReload is a Desktop app + Safari/Chrome/Firefox extension that:
+1. Applies CSS and JavaScript file changes without reloading a page.
+2. Automatically reloads a page when any other file changes
+  (html, image, server-side script, etc).
+
+This module adds a LiveReload javascript to your page, aiming the functuallity
+of the Browserplugin. This means that no plugin is needed to use this.
+Additionally it gives you the possibilty to load styles with tags instead of
+@import, this might be an issue in firefox and orher oler browsers.
+
+
+REQUIREMENTS
+------------
+Drupal 7.x
+livereload - http://livereload.com/
+
+
+INSTALLING
+----------
+1. To install the module copy the 'livereload' folder to your sites/all/modules
+   directory.
+2. Read more about installing modules at http://drupal.org/node/70151
+
+
+CONFIGURING AND USING
+---------------------
+1. Go to admin/people/permissions to give the "Use LiveReload" permission to
+   selcted roles.
+1. Go to admin/config/development/performance to disable livereload.js manually
+   and to force the css to be rendered with <style> instead of @import.
+
+
+REPORTING ISSUE. REQUESTING SUPPORT. REQUESTING NEW FEATURE
+-----------------------------------------------------------
+1. Go to the module issue queue at http://drupal.org/project/issues/livereload?text=&status=All
+2. Click on CREATE A NEW ISSUE link.
+3. Fill the form.
+4. To get a status report on your request go to http://drupal.org/project/issues/user
diff --git a/web/modules/contrib/livereload/config/install/livereload.settings.yml b/web/modules/contrib/livereload/config/install/livereload.settings.yml
new file mode 100644 (file)
index 0000000..e312f86
--- /dev/null
@@ -0,0 +1,2 @@
+livereload_js: 1
+livereload_css: 0
diff --git a/web/modules/contrib/livereload/config/schema/livereload.schema.yml b/web/modules/contrib/livereload/config/schema/livereload.schema.yml
new file mode 100644 (file)
index 0000000..fabf4d0
--- /dev/null
@@ -0,0 +1,8 @@
+livereload.settings:
+  type: mapping
+  label: Settings
+  mapping:
+    livereload_js:
+      type: integer
+    livereload_css:
+      type: integer
\ No newline at end of file
diff --git a/web/modules/contrib/livereload/js/pseudo.js b/web/modules/contrib/livereload/js/pseudo.js
new file mode 100644 (file)
index 0000000..810d0c5
--- /dev/null
@@ -0,0 +1,7 @@
+/**
+ * @file
+ * Pseudo library file for the libraries module.
+ *
+ * Since it is not possible yet to define a dynamic library path, we need to provide a pseudo script for the validator,
+ * which we will then change by our dynamic path via hook_library_info_alter().
+ */
\ No newline at end of file
diff --git a/web/modules/contrib/livereload/livereload.info.yml b/web/modules/contrib/livereload/livereload.info.yml
new file mode 100644 (file)
index 0000000..44b5900
--- /dev/null
@@ -0,0 +1,5 @@
+name: LiveReload
+description: 'Enables and enhances use of LiveReload during development.'
+configure: system.performance_settings
+core: 8.x
+type: module
diff --git a/web/modules/contrib/livereload/livereload.libraries.yml b/web/modules/contrib/livereload/livereload.libraries.yml
new file mode 100644 (file)
index 0000000..4b4be72
--- /dev/null
@@ -0,0 +1,3 @@
+drupal.livereload:
+  js:
+    js/pseudo.js: {}
diff --git a/web/modules/contrib/livereload/livereload.module b/web/modules/contrib/livereload/livereload.module
new file mode 100644 (file)
index 0000000..df6b658
--- /dev/null
@@ -0,0 +1,92 @@
+<?php
+
+use Drupal\Core\Asset\AttachedAssetsInterface;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Implements hook_page_attachments_alter().
+ *
+ * Add the livereload.js script to the site.
+ */
+function livereload_page_attachments_alter(array &$page) {
+  if (\Drupal::currentUser()->hasPermission('use livereload') && \Drupal::config('livereload.settings')->get('livereload_js')) {
+    // Checking whether the file exists before including it. Because it can be
+    // external, we have to use @fopen instead of file_exists().
+    if (@fopen(livereload_get_path(), "r")) {
+      $page['#attached']['library'][] = 'livereload/drupal.livereload';
+    }
+    else {
+      drupal_set_message(t('Livereload.js could not be included.'), 'warning');
+    }
+  }
+}
+
+/**
+ * Build and prepare the path, we suggest the livereload script to be.
+ *
+ * @return string
+ *   The path, we suggest livereload script to be.
+ */
+function livereload_get_path() {
+  $http_host = explode(':', $_SERVER['HTTP_HOST']);
+  return 'http://' . reset($http_host) . ':35729/livereload.js?snipver=1';
+}
+
+/**
+ * Implements hook_css_alter().
+ *
+ * Force CSS to be added with link tags, rather than @import.
+ */
+function livereload_css_alter(&$css, AttachedAssetsInterface $assets) {
+  if (\Drupal::currentUser()->hasPermission('use livereload') && \Drupal::config('livereload.settings')->get('livereload_css')) {
+    foreach ($css as $key => $value) {
+      $css[$key]['preprocess'] = FALSE;
+    }
+  }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ *
+ * Add the possibility to en- and disable the livereload.js.
+ */
+function livereload_form_system_performance_settings_alter(&$form, FormStateInterface $form_state, $form_id) {
+  $form['livereload'] = array(
+    '#type' => 'fieldset',
+    '#title' => 'LiveReload',
+  );
+  $form['livereload']['livereload_js'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Add livereload.js. <em>Note: this will only work if css aggregation is disabled.</em>'),
+    '#default_value' => \Drupal::config('livereload.settings')->get('livereload_js'),
+    '#disabled' => FALSE,
+  );
+  $form['livereload']['livereload_css'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Force CSS to be added with link tags, rather than @import. <em>Note: if this option is enabled, the "%aggregate_css" functionallity of Drupal will not work.</em>', array('%aggregate_css' => t('Aggregate and compress CSS files.'))),
+    '#default_value' => \Drupal::config('livereload.settings')->get('livereload_css'),
+    '#disabled' => FALSE,
+  );
+
+  // Add submit handler to save contact configuration.
+  $form['#submit'][] = 'livereload_form_system_performance_settings_submit';
+}
+
+/**
+ * @see livereload_form_system_performance_settings_alter()
+ */
+function livereload_form_system_performance_settings_submit($form, FormStateInterface &$form_state) {
+  \Drupal::configFactory()->getEditable('livereload.settings')
+    ->set('livereload_js', $form_state->getValue('livereload_js'))
+    ->set('livereload_css', $form_state->getValue('livereload_css'))
+    ->save();
+}
+
+/**
+ * Implements hook_library_info_alter().
+ */
+function livereload_library_info_alter(&$libraries, $extension) {
+  if ($extension === 'livereload' && isset($libraries['drupal.livereload'])) {
+    $libraries['drupal.livereload']['js'][livereload_get_path()] = [];
+  }
+}
diff --git a/web/modules/contrib/livereload/livereload.permissions.yml b/web/modules/contrib/livereload/livereload.permissions.yml
new file mode 100644 (file)
index 0000000..3e08056
--- /dev/null
@@ -0,0 +1,3 @@
+'use livereload':
+  title: 'Use LiveReload'
+  description: 'Adds the livereload.js if it is enabled.'
diff --git a/web/modules/contrib/media b/web/modules/contrib/media
deleted file mode 160000 (submodule)
index b9d72f2..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit b9d72f21ea20c583ec4b83081720eba65670a8da
diff --git a/web/modules/contrib/media/.travis.yml b/web/modules/contrib/media/.travis.yml
new file mode 100644 (file)
index 0000000..a42844a
--- /dev/null
@@ -0,0 +1,124 @@
+# @file
+# .travis.yml - Drupal for Travis CI Integration
+#
+# Template provided by https://github.com/LionsAd/drupal_ti.
+#
+# Based for simpletest upon:
+#   https://github.com/sonnym/travis-ci-drupal-module-example
+
+language: php
+
+sudo: false
+
+php:
+  - 5.5
+  - 5.6
+  - 7
+  - hhvm
+
+matrix:
+  fast_finish: true
+  allow_failures:
+    - php: hhvm
+
+env:
+  global:
+    # add composer's global bin directory to the path
+    # see: https://github.com/drush-ops/drush#install---composer
+    - PATH="$PATH:$HOME/.composer/vendor/bin"
+
+    # Configuration variables.
+    - DRUPAL_TI_MODULE_NAME="media"
+    - DRUPAL_TI_SIMPLETEST_GROUP="media"
+
+    # Define runners and environment vars to include before and after the
+    # main runners / environment vars.
+    #- DRUPAL_TI_SCRIPT_DIR_BEFORE="./drupal_ti/before"
+    #- DRUPAL_TI_SCRIPT_DIR_AFTER="./drupal_ti/after"
+
+    # The environment to use, supported are: drupal-7, drupal-8
+    - DRUPAL_TI_ENVIRONMENT="drupal-8"
+    - DRUPAL_TI_CORE_BRANCH="8.2.x"
+    
+    # The installation profile to use:
+    #- DRUPAL_TI_INSTALL_PROFILE="testing"
+
+    # Drupal specific variables.
+    - DRUPAL_TI_DB="drupal_travis_db"
+    - DRUPAL_TI_DB_URL="mysql://root:@127.0.0.1/drupal_travis_db"
+    # Note: Do not add a trailing slash here.
+    - DRUPAL_TI_WEBSERVER_URL="http://127.0.0.1"
+    - DRUPAL_TI_WEBSERVER_PORT="8080"
+
+    # Simpletest specific commandline arguments, the DRUPAL_TI_SIMPLETEST_GROUP is appended at the end.
+    - DRUPAL_TI_SIMPLETEST_ARGS="--verbose --color --concurrency 4 --url $DRUPAL_TI_WEBSERVER_URL:$DRUPAL_TI_WEBSERVER_PORT"
+
+    # === Behat specific variables.
+    # This is relative to $TRAVIS_BUILD_DIR
+    - DRUPAL_TI_BEHAT_DIR="./tests/behat"
+    # These arguments are passed to the bin/behat command.
+    - DRUPAL_TI_BEHAT_ARGS=""
+    # Specify the filename of the behat.yml with the $DRUPAL_TI_DRUPAL_DIR variables.
+    - DRUPAL_TI_BEHAT_YML="behat.yml.dist"
+    # This is used to setup Xvfb.
+    - DRUPAL_TI_BEHAT_SCREENSIZE_COLOR="1280x1024x16"
+    # The version of selenium that should be used.
+    - DRUPAL_TI_BEHAT_SELENIUM_VERSION="2.48.2"
+    # Set DRUPAL_TI_BEHAT_DRIVER to "selenium" to use "firefox" or "chrome" here.
+    - DRUPAL_TI_BEHAT_DRIVER="phantomjs"
+    - DRUPAL_TI_BEHAT_BROWSER="firefox"
+
+    # PHPUnit specific commandline arguments.
+    - DRUPAL_TI_PHPUNIT_ARGS=""
+    # Specifying the phpunit-core src/ directory is useful when e.g. a vendor/
+    # directory is present in the module directory, which phpunit would then
+    # try to find tests in. This option is relative to $TRAVIS_BUILD_DIR.
+    #- DRUPAL_TI_PHPUNIT_CORE_SRC_DIRECTORY="./tests/src"
+
+    # Code coverage via coveralls.io
+    - DRUPAL_TI_COVERAGE="satooshi/php-coveralls:0.6.*"
+    # This needs to match your .coveralls.yml file.
+    - DRUPAL_TI_COVERAGE_FILE="build/logs/clover.xml"
+
+    # Debug options
+    #- DRUPAL_TI_DEBUG="-x -v"
+    # Set to "all" to output all files, set to e.g. "xvfb selenium" or "selenium",
+    # etc. to only output those channels.
+    #- DRUPAL_TI_DEBUG_FILE_OUTPUT="selenium xvfb webserver"
+
+  matrix:
+    # [[[ SELECT ANY OR MORE OPTIONS ]]]
+    #- DRUPAL_TI_RUNNERS="phpunit"
+    - DRUPAL_TI_RUNNERS="simpletest"
+    #- DRUPAL_TI_RUNNERS="behat"
+    #- DRUPAL_TI_RUNNERS="phpunit simpletest behat"
+    # Use phpunit-core to test modules with phpunit with Drupal 8 core.
+    #- DRUPAL_TI_RUNNERS="phpunit-core"
+
+mysql:
+  database: drupal_travis_db
+  username: root
+  encoding: utf8
+
+before_install:
+  - composer self-update
+  #- cd ./tests
+  - composer global require "lionsad/drupal_ti:dev-master"
+  - drupal-ti before_install
+
+install:
+  - drupal-ti install
+
+before_script:
+  - drupal-ti --include drupal_ti/before/before_script.sh
+  - drupal-ti before_script
+
+script:
+  - drupal-ti script
+
+after_script:
+  - drupal-ti after_script
+
+notifications:
+  email: false
+
diff --git a/web/modules/contrib/media/README.md b/web/modules/contrib/media/README.md
new file mode 100644 (file)
index 0000000..e260805
--- /dev/null
@@ -0,0 +1,44 @@
+# Media Module
+
+[![Build Status](https://travis-ci.org/drupal-media/media.svg?branch=8.x-1.x)](https://travis-ci.org/drupal-media/media) [![Scrutinizer](https://scrutinizer-ci.com/g/drupal-media/media/badges/quality-score.png?b=8.x-1.x)](https://scrutinizer-ci.com/g/drupal-media/media/?branch=8.x-1.x)
+
+## Introduction
+
+The Media module provides an extensible framework for managing files and multimedia assets, regardless of whether they are hosted on your own site or a 3rd party site.
+
+Media's aim is to solve Drupal's long standing media handling problem.
+
+## Requirements
+
+* [Media Entity](https://www.drupal.org/project/media_entity)
+* [Media Entity Image](https://www.drupal.org/project/media_entity_image)
+* [Video Embed Field](https://www.drupal.org/project/video_embed_field)
+* [Media Entity Slideshow](https://www.drupal.org/project/media_entity_slideshow)
+* [Media Entity Instagram](https://www.drupal.org/project/media_entity_instagram)
+* [Media Entity Twitter](https://www.drupal.org/project/media_entity_twitter)
+* [Media Entity Document](https://www.drupal.org/project/media_entity_document)
+* [Slick Media](https://www.drupal.org/project/slick_media)
+* [Entity Browser](https://www.drupal.org/project/entity_browser)
+* [Dropzonejs](https://www.drupal.org/project/dropzonejs)
+* [Image Widget Crop](https://www.drupal.org/project/image_widget_crop)
+
+## Installation
+
+After downloading the module in the modules folder, visit the Extend (/admin/modules) page to enable the module. You might have to install the modules required before enabling this module.
+
+Documentation for the module is available in the [Drupal 8 Media Guide](https://drupal-media.gitbooks.io/drupal8-guide/content/modules/media/intro.html).
+
+## Configuration
+
+1. Enable the module
+2. visit /media/add to add media content to website.
+3. Visit /admin/structure/media to edit the default media bundles.
+
+## Technical details
+
+@ToDo
+
+## Maintainers
+
+@ToDo
+
diff --git a/web/modules/contrib/media/composer.json b/web/modules/contrib/media/composer.json
new file mode 100644 (file)
index 0000000..fdd1c6c
--- /dev/null
@@ -0,0 +1,13 @@
+{
+  "name": "drupal/media",
+  "description": "Media module for Drupal",
+  "type": "drupal-module",
+  "homepage": "https://github.com/drupal-media/media/",
+  "support": {
+    "irc": "irc://irc.freenode.org/drupal-contribute"
+  },
+  "license": "GPL-2.0+",
+  "minimum-stability": "dev",
+  "require": { }
+}
+
diff --git a/web/modules/contrib/media/config/install/core.entity_form_display.media.document.default.yml b/web/modules/contrib/media/config/install/core.entity_form_display.media.document.default.yml
new file mode 100644 (file)
index 0000000..128855c
--- /dev/null
@@ -0,0 +1,40 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.document.field_document
+    - field.field.media.document.field_document_size
+    - field.field.media.document.field_media_in_library
+    - field.field.media.document.field_mime_type
+    - media_entity.bundle.document
+  module:
+    - file
+id: media.document.default
+targetEntityType: media
+bundle: document
+mode: default
+content:
+  field_document:
+    weight: 1
+    settings:
+      progress_indicator: throbber
+    third_party_settings: {  }
+    type: file_generic
+  field_media_in_library:
+    weight: 3
+    settings:
+      display_label: true
+    third_party_settings: {  }
+    type: boolean_checkbox
+  name:
+    type: string_textfield
+    weight: 0
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+hidden:
+  created: true
+  field_document_size: true
+  field_mime_type: true
+  uid: true
diff --git a/web/modules/contrib/media/config/install/core.entity_form_display.media.gallery.default.yml b/web/modules/contrib/media/config/install/core.entity_form_display.media.gallery.default.yml
new file mode 100644 (file)
index 0000000..38593ea
--- /dev/null
@@ -0,0 +1,42 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.gallery.field_media_in_library
+    - field.field.media.gallery.field_slide
+    - media_entity.bundle.gallery
+  module:
+    - entity_browser
+id: media.gallery.default
+targetEntityType: media
+bundle: gallery
+mode: default
+content:
+  field_media_in_library:
+    weight: 3
+    settings:
+      display_label: true
+    third_party_settings: {  }
+    type: boolean_checkbox
+  field_slide:
+    weight: 1
+    settings:
+      entity_browser: gallery_media_library
+      field_widget_display: rendered_entity
+      field_widget_edit: true
+      field_widget_remove: true
+      field_widget_display_settings:
+        view_mode: media_library
+      open: false
+    third_party_settings: {  }
+    type: entity_browser_entity_reference
+  name:
+    type: string_textfield
+    weight: 0
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+hidden:
+  created: true
+  uid: true
diff --git a/web/modules/contrib/media/config/install/core.entity_form_display.media.image.default.yml b/web/modules/contrib/media/config/install/core.entity_form_display.media.image.default.yml
new file mode 100644 (file)
index 0000000..17d30f8
--- /dev/null
@@ -0,0 +1,43 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.image.field_image
+    - field.field.media.image.field_media_in_library
+    - image.style.thumbnail
+    - media_entity.bundle.image
+  module:
+    - image_widget_crop
+id: media.image.default
+targetEntityType: media
+bundle: image
+mode: default
+content:
+  field_image:
+    weight: 1
+    settings:
+      show_default_crop: true
+      preview_image_style: thumbnail
+      crop_preview_image_style: media_crop_preview
+      crop_list:
+        - media_crop
+      progress_indicator: throbber
+      show_crop_area: false
+    third_party_settings: {  }
+    type: image_widget_crop
+  field_media_in_library:
+    weight: 3
+    settings:
+      display_label: true
+    third_party_settings: {  }
+    type: boolean_checkbox
+  name:
+    type: string_textfield
+    weight: 0
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+hidden:
+  created: true
+  uid: true
diff --git a/web/modules/contrib/media/config/install/core.entity_form_display.media.instagram.default.yml b/web/modules/contrib/media/config/install/core.entity_form_display.media.instagram.default.yml
new file mode 100644 (file)
index 0000000..0bc4688
--- /dev/null
@@ -0,0 +1,50 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.instagram.field_instagram_shortcode
+    - field.field.media.instagram.field_instagram_url
+    - field.field.media.instagram.field_media_in_library
+    - media_entity.bundle.instagram
+  module:
+    - link
+id: media.instagram.default
+targetEntityType: media
+bundle: instagram
+mode: default
+content:
+  created:
+    type: datetime_timestamp
+    weight: 10
+    settings: {  }
+    third_party_settings: {  }
+  field_instagram_url:
+    weight: 11
+    settings:
+      placeholder_url: ''
+      placeholder_title: ''
+    third_party_settings: {  }
+    type: link_default
+  field_media_in_library:
+    weight: 12
+    settings:
+      display_label: true
+    third_party_settings: {  }
+    type: boolean_checkbox
+  name:
+    type: string_textfield
+    weight: -5
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+  uid:
+    type: entity_reference_autocomplete
+    weight: 5
+    settings:
+      match_operator: CONTAINS
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+hidden:
+  field_instagram_shortcode: true
diff --git a/web/modules/contrib/media/config/install/core.entity_form_display.media.tweet.default.yml b/web/modules/contrib/media/config/install/core.entity_form_display.media.tweet.default.yml
new file mode 100644 (file)
index 0000000..6e61336
--- /dev/null
@@ -0,0 +1,52 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.tweet.field_media_in_library
+    - field.field.media.tweet.field_tweet_author
+    - field.field.media.tweet.field_tweet_id
+    - field.field.media.tweet.field_tweet_url
+    - media_entity.bundle.tweet
+  module:
+    - link
+id: media.tweet.default
+targetEntityType: media
+bundle: tweet
+mode: default
+content:
+  created:
+    type: datetime_timestamp
+    weight: 10
+    settings: {  }
+    third_party_settings: {  }
+  field_media_in_library:
+    weight: 12
+    settings:
+      display_label: true
+    third_party_settings: {  }
+    type: boolean_checkbox
+  field_tweet_url:
+    weight: 11
+    settings:
+      placeholder_url: ''
+      placeholder_title: ''
+    third_party_settings: {  }
+    type: link_default
+  name:
+    type: string_textfield
+    weight: -5
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+  uid:
+    type: entity_reference_autocomplete
+    weight: 5
+    settings:
+      match_operator: CONTAINS
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+hidden:
+  field_tweet_author: true
+  field_tweet_id: true
diff --git a/web/modules/contrib/media/config/install/core.entity_form_display.media.video.default.yml b/web/modules/contrib/media/config/install/core.entity_form_display.media.video.default.yml
new file mode 100644 (file)
index 0000000..e2aee5f
--- /dev/null
@@ -0,0 +1,39 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.video.field_id
+    - field.field.media.video.field_media_in_library
+    - field.field.media.video.field_source
+    - field.field.media.video.field_video
+    - media_entity.bundle.video
+  module:
+    - video_embed_field
+id: media.video.default
+targetEntityType: media
+bundle: video
+mode: default
+content:
+  field_media_in_library:
+    weight: 3
+    settings:
+      display_label: true
+    third_party_settings: {  }
+    type: boolean_checkbox
+  field_video:
+    type: video_embed_field_textfield
+    weight: 1
+    settings: {  }
+    third_party_settings: {  }
+  name:
+    type: string_textfield
+    weight: 0
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+hidden:
+  created: true
+  field_id: true
+  field_source: true
+  uid: true
diff --git a/web/modules/contrib/media/config/install/core.entity_view_display.media.document.default.yml b/web/modules/contrib/media/config/install/core.entity_view_display.media.document.default.yml
new file mode 100644 (file)
index 0000000..275ac47
--- /dev/null
@@ -0,0 +1,57 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.document.field_document
+    - field.field.media.document.field_document_size
+    - field.field.media.document.field_media_in_library
+    - field.field.media.document.field_mime_type
+    - media_entity.bundle.document
+  module:
+    - file
+    - user
+id: media.document.default
+targetEntityType: media
+bundle: document
+mode: default
+content:
+  created:
+    label: hidden
+    type: timestamp
+    weight: 1
+    settings:
+      date_format: medium
+      custom_date_format: ''
+      timezone: ''
+    third_party_settings: {  }
+  field_document:
+    type: file_default
+    weight: 3
+    label: above
+    settings: {  }
+    third_party_settings: {  }
+  field_document_size:
+    weight: 4
+    label: above
+    settings:
+      thousand_separator: ''
+      prefix_suffix: true
+    third_party_settings: {  }
+    type: number_integer
+  name:
+    label: hidden
+    type: string
+    weight: 0
+    settings:
+      link_to_entity: false
+    third_party_settings: {  }
+  uid:
+    label: hidden
+    type: author
+    weight: 2
+    settings: {  }
+    third_party_settings: {  }
+hidden:
+  field_media_in_library: true
+  field_mime_type: true
+  thumbnail: true
diff --git a/web/modules/contrib/media/config/install/core.entity_view_display.media.document.media_library.yml b/web/modules/contrib/media/config/install/core.entity_view_display.media.document.media_library.yml
new file mode 100644 (file)
index 0000000..6826ce9
--- /dev/null
@@ -0,0 +1,67 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - core.entity_view_mode.media.media_library
+    - field.field.media.document.field_document
+    - field.field.media.document.field_document_size
+    - field.field.media.document.field_mime_type
+    - image.style.thumbnail
+    - media_entity.bundle.document
+  module:
+    - file
+    - image
+    - user
+_core:
+  default_config_hash: x_n0Hh8fjeFKYeDq-rWrWsjFS7WwXqE5Cs2pP4waH4k
+id: media.document.media_library
+targetEntityType: media
+bundle: document
+mode: media_library
+content:
+  created:
+    label: hidden
+    type: timestamp
+    weight: 5
+    settings:
+      date_format: medium
+      custom_date_format: ''
+      timezone: ''
+    third_party_settings: {  }
+  field_document:
+    weight: 2
+    label: hidden
+    settings: {  }
+    third_party_settings: {  }
+    type: file_default
+  field_document_size:
+    weight: 3
+    label: hidden
+    settings:
+      thousand_separator: ''
+      prefix_suffix: true
+    third_party_settings: {  }
+    type: number_integer
+  name:
+    label: hidden
+    type: string
+    weight: 0
+    settings:
+      link_to_entity: true
+    third_party_settings: {  }
+  thumbnail:
+    type: image
+    weight: 1
+    label: hidden
+    settings:
+      image_style: thumbnail
+      image_link: ''
+    third_party_settings: {  }
+  uid:
+    label: hidden
+    type: author
+    weight: 4
+    settings: {  }
+    third_party_settings: {  }
+hidden:
+  field_mime_type: true
diff --git a/web/modules/contrib/media/config/install/core.entity_view_display.media.gallery.default.yml b/web/modules/contrib/media/config/install/core.entity_view_display.media.gallery.default.yml
new file mode 100644 (file)
index 0000000..3a66a5e
--- /dev/null
@@ -0,0 +1,105 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.gallery.field_media_in_library
+    - field.field.media.gallery.field_slide
+    - media_entity.bundle.gallery
+  module:
+    - slick_media
+id: media.gallery.default
+targetEntityType: media
+bundle: gallery
+mode: default
+content:
+  field_slide:
+    weight: 0
+    label: hidden
+    settings:
+      vanilla: true
+      optionset: default
+      optionset_thumbnail: ''
+      skin: ''
+      skin_thumbnail: ''
+      image_style: gallery_item
+      thumbnail_style: ''
+      thumbnail_effect: ''
+      media_switch: ''
+      ratio: ''
+      view_mode: gallery
+      image: ''
+      title: ''
+      link: ''
+      layout: ''
+      thumbnail_caption: ''
+      class: ''
+      cache: -1
+      sizes: ''
+      current_view_mode: default
+      background: false
+      caption:
+        field_image: '0'
+        field_id: '0'
+        field_source: '0'
+        field_video: '0'
+      skin_arrows: ''
+      skin_dots: ''
+      responsive_image_style: ''
+      iframe_lazy: false
+      thumbnail: ''
+      overlay: ''
+      id: ''
+      breakpoints:
+        xs:
+          breakpoint: ''
+          image_style: ''
+          width: ''
+        sm:
+          breakpoint: ''
+          image_style: ''
+          width: ''
+        md:
+          breakpoint: ''
+          image_style: ''
+          width: ''
+        lg:
+          breakpoint: ''
+          image_style: ''
+          width: ''
+        xl:
+          breakpoint: ''
+          image_style: ''
+          width: ''
+      override: false
+      overridables:
+        arrows: '0'
+        autoplay: '0'
+        dots: '0'
+        draggable: '0'
+        infinite: '0'
+        mouseWheel: '0'
+        randomize: '0'
+        variableWidth: '0'
+      color_field: ''
+      grid: 0
+      grid_header: ''
+      grid_medium: 0
+      grid_small: 0
+      preserve_keys: false
+      visible_items: 0
+      display: main
+      item_id: ''
+      box_caption: {  }
+      box_caption_custom: ''
+      box_style: ''
+      icon: false
+      dimension: ''
+      thumbnail_position: ''
+    third_party_settings: {  }
+    type: slick_media
+hidden:
+  created: true
+  field_media_in_library: true
+  name: true
+  thumbnail: true
+  uid: true
diff --git a/web/modules/contrib/media/config/install/core.entity_view_display.media.gallery.media_library.yml b/web/modules/contrib/media/config/install/core.entity_view_display.media.gallery.media_library.yml
new file mode 100644 (file)
index 0000000..86e2b18
--- /dev/null
@@ -0,0 +1,50 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - core.entity_view_mode.media.media_library
+    - field.field.media.gallery.field_slide
+    - image.style.media_library_item
+    - media_entity.bundle.gallery
+  module:
+    - image
+    - user
+_core:
+  default_config_hash: 5R-2OBWiHE28_qMjRDLXxZ9YLEVrlS2uYY6y7ETJS9E
+id: media.gallery.media_library
+targetEntityType: media
+bundle: gallery
+mode: media_library
+content:
+  created:
+    type: timestamp
+    weight: 3
+    label: hidden
+    settings:
+      date_format: medium
+      custom_date_format: ''
+      timezone: ''
+    third_party_settings: {  }
+  name:
+    type: string
+    weight: 0
+    label: hidden
+    settings:
+      link_to_entity: true
+    third_party_settings: {  }
+  thumbnail:
+    type: image
+    weight: 1
+    label: hidden
+    settings:
+      image_style: media_library_item
+      image_link: ''
+    third_party_settings: {  }
+  uid:
+    type: author
+    weight: 2
+    label: hidden
+    settings: {  }
+    third_party_settings: {  }
+hidden:
+  field_slide: true
diff --git a/web/modules/contrib/media/config/install/core.entity_view_display.media.image.default.yml b/web/modules/contrib/media/config/install/core.entity_view_display.media.image.default.yml
new file mode 100644 (file)
index 0000000..913fa96
--- /dev/null
@@ -0,0 +1,49 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.image.field_image
+    - field.field.media.image.field_media_in_library
+    - image.style.media_crop
+    - media_entity.bundle.image
+  module:
+    - image
+    - user
+id: media.image.default
+targetEntityType: media
+bundle: image
+mode: default
+content:
+  created:
+    label: hidden
+    type: timestamp
+    weight: 1
+    settings:
+      date_format: medium
+      custom_date_format: ''
+      timezone: ''
+    third_party_settings: {  }
+  field_image:
+    weight: 3
+    label: hidden
+    settings:
+      image_style: media_crop
+      image_link: ''
+    third_party_settings: {  }
+    type: image
+  name:
+    label: hidden
+    type: string
+    weight: 0
+    settings:
+      link_to_entity: false
+    third_party_settings: {  }
+  uid:
+    label: hidden
+    type: author
+    weight: 2
+    settings: {  }
+    third_party_settings: {  }
+hidden:
+  field_media_in_library: true
+  thumbnail: true
diff --git a/web/modules/contrib/media/config/install/core.entity_view_display.media.image.media_library.yml b/web/modules/contrib/media/config/install/core.entity_view_display.media.image.media_library.yml
new file mode 100644 (file)
index 0000000..eaee114
--- /dev/null
@@ -0,0 +1,50 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - core.entity_view_mode.media.media_library
+    - field.field.media.image.field_image
+    - image.style.media_library_item
+    - media_entity.bundle.image
+  module:
+    - image
+    - user
+_core:
+  default_config_hash: R3AspO8uJJjAq_RyxT35nzRCriMjTj5qmyAlkCNlEtg
+id: media.image.media_library
+targetEntityType: media
+bundle: image
+mode: media_library
+content:
+  created:
+    type: timestamp
+    weight: 3
+    label: hidden
+    settings:
+      date_format: medium
+      custom_date_format: ''
+      timezone: ''
+    third_party_settings: {  }
+  name:
+    label: hidden
+    type: string
+    weight: 0
+    settings:
+      link_to_entity: true
+    third_party_settings: {  }
+  thumbnail:
+    type: image
+    weight: 1
+    label: hidden
+    settings:
+      image_style: media_library_item
+      image_link: ''
+    third_party_settings: {  }
+  uid:
+    type: author
+    weight: 2
+    label: hidden
+    settings: {  }
+    third_party_settings: {  }
+hidden:
+  field_image: true
diff --git a/web/modules/contrib/media/config/install/core.entity_view_display.media.instagram.default.yml b/web/modules/contrib/media/config/install/core.entity_view_display.media.instagram.default.yml
new file mode 100644 (file)
index 0000000..2650094
--- /dev/null
@@ -0,0 +1,50 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.instagram.field_instagram_shortcode
+    - field.field.media.instagram.field_instagram_url
+    - field.field.media.instagram.field_media_in_library
+    - media_entity.bundle.instagram
+  module:
+    - media_entity_instagram
+    - user
+id: media.instagram.default
+targetEntityType: media
+bundle: instagram
+mode: default
+content:
+  created:
+    label: hidden
+    type: timestamp
+    weight: 1
+    settings:
+      date_format: medium
+      custom_date_format: ''
+      timezone: ''
+    third_party_settings: {  }
+  field_instagram_url:
+    weight: 3
+    label: hidden
+    settings:
+      width: 480
+      height: 640
+    third_party_settings: {  }
+    type: instagram_embed
+  name:
+    label: hidden
+    type: string
+    weight: 0
+    settings:
+      link_to_entity: false
+    third_party_settings: {  }
+  uid:
+    label: hidden
+    type: author
+    weight: 2
+    settings: {  }
+    third_party_settings: {  }
+hidden:
+  field_instagram_shortcode: true
+  field_media_in_library: true
+  thumbnail: true
diff --git a/web/modules/contrib/media/config/install/core.entity_view_display.media.instagram.media_library.yml b/web/modules/contrib/media/config/install/core.entity_view_display.media.instagram.media_library.yml
new file mode 100644 (file)
index 0000000..b5e2a69
--- /dev/null
@@ -0,0 +1,49 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - core.entity_view_mode.media.media_library
+    - field.field.media.instagram.field_instagram_shortcode
+    - field.field.media.instagram.field_instagram_url
+    - media_entity.bundle.instagram
+  module:
+    - media_entity_instagram
+    - user
+id: media.instagram.media_library
+targetEntityType: media
+bundle: instagram
+mode: media_library
+content:
+  created:
+    label: hidden
+    type: timestamp
+    weight: 3
+    settings:
+      date_format: medium
+      custom_date_format: ''
+      timezone: ''
+    third_party_settings: {  }
+  field_instagram_url:
+    weight: 1
+    label: hidden
+    settings:
+      width: 260
+      height: 340
+    third_party_settings: {  }
+    type: instagram_embed
+  name:
+    label: hidden
+    type: string
+    weight: 0
+    settings:
+      link_to_entity: true
+    third_party_settings: {  }
+  uid:
+    label: hidden
+    type: author
+    weight: 2
+    settings: {  }
+    third_party_settings: {  }
+hidden:
+  field_instagram_shortcode: true
+  thumbnail: true
diff --git a/web/modules/contrib/media/config/install/core.entity_view_display.media.tweet.default.yml b/web/modules/contrib/media/config/install/core.entity_view_display.media.tweet.default.yml
new file mode 100644 (file)
index 0000000..b89849d
--- /dev/null
@@ -0,0 +1,50 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.tweet.field_media_in_library
+    - field.field.media.tweet.field_tweet_author
+    - field.field.media.tweet.field_tweet_id
+    - field.field.media.tweet.field_tweet_url
+    - media_entity.bundle.tweet
+  module:
+    - media_entity_twitter
+    - user
+id: media.tweet.default
+targetEntityType: media
+bundle: tweet
+mode: default
+content:
+  created:
+    label: hidden
+    type: timestamp
+    weight: 1
+    settings:
+      date_format: medium
+      custom_date_format: ''
+      timezone: ''
+    third_party_settings: {  }
+  field_tweet_url:
+    weight: 3
+    label: hidden
+    settings: {  }
+    third_party_settings: {  }
+    type: twitter_embed
+  name:
+    label: hidden
+    type: string
+    weight: 0
+    settings:
+      link_to_entity: false
+    third_party_settings: {  }
+  uid:
+    label: hidden
+    type: author
+    weight: 2
+    settings: {  }
+    third_party_settings: {  }
+hidden:
+  field_media_in_library: true
+  field_tweet_author: true
+  field_tweet_id: true
+  thumbnail: true
diff --git a/web/modules/contrib/media/config/install/core.entity_view_display.media.tweet.media_library.yml b/web/modules/contrib/media/config/install/core.entity_view_display.media.tweet.media_library.yml
new file mode 100644 (file)
index 0000000..cfa4511
--- /dev/null
@@ -0,0 +1,51 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - core.entity_view_mode.media.media_library
+    - field.field.media.tweet.field_tweet_author
+    - field.field.media.tweet.field_tweet_id
+    - field.field.media.tweet.field_tweet_url
+    - media_entity.bundle.tweet
+  module:
+    - media_entity_twitter
+    - user
+_core:
+  default_config_hash: CGkENzPx_szsVtDhN6ptOL28tAGnZDTvboNR7L38_sI
+id: media.tweet.media_library
+targetEntityType: media
+bundle: tweet
+mode: media_library
+content:
+  created:
+    label: hidden
+    type: timestamp
+    weight: 3
+    settings:
+      date_format: medium
+      custom_date_format: ''
+      timezone: ''
+    third_party_settings: {  }
+  field_tweet_url:
+    weight: 1
+    label: hidden
+    settings: {  }
+    third_party_settings: {  }
+    type: twitter_embed
+  name:
+    label: hidden
+    type: string
+    weight: 0
+    settings:
+      link_to_entity: true
+    third_party_settings: {  }
+  uid:
+    label: hidden
+    type: author
+    weight: 2
+    settings: {  }
+    third_party_settings: {  }
+hidden:
+  field_tweet_author: true
+  field_tweet_id: true
+  thumbnail: true
diff --git a/web/modules/contrib/media/config/install/core.entity_view_display.media.video.default.yml b/web/modules/contrib/media/config/install/core.entity_view_display.media.video.default.yml
new file mode 100644 (file)
index 0000000..0684116
--- /dev/null
@@ -0,0 +1,54 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.video.field_id
+    - field.field.media.video.field_media_in_library
+    - field.field.media.video.field_source
+    - field.field.media.video.field_video
+    - media_entity.bundle.video
+  module:
+    - user
+    - video_embed_field
+id: media.video.default
+targetEntityType: media
+bundle: video
+mode: default
+content:
+  created:
+    label: hidden
+    type: timestamp
+    weight: 1
+    settings:
+      date_format: medium
+      custom_date_format: ''
+      timezone: ''
+    third_party_settings: {  }
+  field_video:
+    type: video_embed_field_video
+    weight: 3
+    label: hidden
+    settings:
+      responsive: true
+      width: 854
+      height: 480
+      autoplay: true
+    third_party_settings: {  }
+  name:
+    label: hidden
+    type: string
+    weight: 0
+    settings:
+      link_to_entity: false
+    third_party_settings: {  }
+  uid:
+    label: hidden
+    type: author
+    weight: 2
+    settings: {  }
+    third_party_settings: {  }
+hidden:
+  field_id: true
+  field_media_in_library: true
+  field_source: true
+  thumbnail: true
diff --git a/web/modules/contrib/media/config/install/core.entity_view_display.media.video.media_library.yml b/web/modules/contrib/media/config/install/core.entity_view_display.media.video.media_library.yml
new file mode 100644 (file)
index 0000000..9cb722a
--- /dev/null
@@ -0,0 +1,54 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - core.entity_view_mode.media.media_library
+    - field.field.media.video.field_id
+    - field.field.media.video.field_source
+    - field.field.media.video.field_video
+    - image.style.media_library_item
+    - media_entity.bundle.video
+  module:
+    - image
+    - user
+_core:
+  default_config_hash: pZES-uG_mHURoSF49BhzbQPzEz32mWcPz4yxP-Nzrp0
+id: media.video.media_library
+targetEntityType: media
+bundle: video
+mode: media_library
+content:
+  created:
+    type: timestamp
+    weight: 3
+    label: hidden
+    settings:
+      date_format: medium
+      custom_date_format: ''
+      timezone: ''
+    third_party_settings: {  }
+  name:
+    label: hidden
+    type: string
+    weight: 0
+    settings:
+      link_to_entity: true
+    third_party_settings: {  }
+  thumbnail:
+    type: image
+    weight: 1
+    label: hidden
+    settings:
+      image_style: media_library_item
+      image_link: ''
+    third_party_settings: {  }
+  uid:
+    type: author
+    weight: 2
+    label: hidden
+    settings: {  }
+    third_party_settings: {  }
+hidden:
+  field_id: true
+  field_source: true
+  field_video: true
diff --git a/web/modules/contrib/media/config/install/core.entity_view_mode.media.gallery.yml b/web/modules/contrib/media/config/install/core.entity_view_mode.media.gallery.yml
new file mode 100644 (file)
index 0000000..834c850
--- /dev/null
@@ -0,0 +1,12 @@
+langcode: en
+status: true
+dependencies:
+  enforced:
+    module:
+      - media
+  module:
+    - media_entity
+id: media.gallery
+label: Gallery
+targetEntityType: media
+cache: true
diff --git a/web/modules/contrib/media/config/install/core.entity_view_mode.media.media_library.yml b/web/modules/contrib/media/config/install/core.entity_view_mode.media.media_library.yml
new file mode 100644 (file)
index 0000000..0760cd9
--- /dev/null
@@ -0,0 +1,9 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media_entity
+id: media.media_library
+label: 'Media Library'
+targetEntityType: media
+cache: true
diff --git a/web/modules/contrib/media/config/install/crop.type.media_crop.yml b/web/modules/contrib/media/config/install/crop.type.media_crop.yml
new file mode 100644 (file)
index 0000000..b00ebc0
--- /dev/null
@@ -0,0 +1,11 @@
+langcode: en
+status: true
+dependencies: {  }
+label: 'Media Crop'
+id: media_crop
+description: 'Crop used for image media bundle.'
+aspect_ratio: ''
+soft_limit_width: null
+soft_limit_height: null
+hard_limit_width: null
+hard_limit_height: null
diff --git a/web/modules/contrib/media/config/install/embed.button.media.yml b/web/modules/contrib/media/config/install/embed.button.media.yml
new file mode 100644 (file)
index 0000000..f60bf94
--- /dev/null
@@ -0,0 +1,19 @@
+langcode: en
+status: true
+dependencies:
+  content:
+    - 'file:file:39004672-000c-4b13-af59-838822a4bc5a'
+  module:
+    - entity_embed
+    - media_entity
+label: Media
+id: media
+type_id: entity
+type_settings:
+  entity_type: media
+  bundles: {  }
+  display_plugins: {  }
+  entity_browser: media_embed
+  entity_browser_settings:
+    display_review: false
+icon_uuid: 39004672-000c-4b13-af59-838822a4bc5a
diff --git a/web/modules/contrib/media/config/install/entity_browser.browser.gallery_media_library.yml b/web/modules/contrib/media/config/install/entity_browser.browser.gallery_media_library.yml
new file mode 100644 (file)
index 0000000..4116e24
--- /dev/null
@@ -0,0 +1,59 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.image.field_image
+    - media_entity.bundle.image
+    - views.view.media_library
+  module:
+    - dropzonejs_eb_widget
+    - entity_browser_entity_form
+    - views
+name: gallery_media_library
+label: 'Gallery media library'
+display: modal
+display_configuration:
+  width: '950'
+  height: '600'
+  link_text: 'Select media'
+selection_display: no_display
+selection_display_configuration: {  }
+widget_selector: tabs
+widget_selector_configuration: {  }
+widgets:
+  c1df873e-9e41-41c0-b22f-f4b56d51d9c0:
+    settings:
+      media_entity_bundle: image
+      upload_location: 'public://[date:custom:Y]-[date:custom:m]'
+      dropzone_description: 'Drag and drop files here to upload'
+      max_filesize: 2M
+      extensions: 'jpg jpeg gif png'
+    uuid: c1df873e-9e41-41c0-b22f-f4b56d51d9c0
+    weight: -8
+    label: 'Upload images'
+    id: dropzonejs_media_entity
+  3da26b3a-8d09-4270-a0a8-a89691b0b73a:
+    settings:
+      view: media_library
+      view_display: gallery_media_select_modal
+    uuid: 3da26b3a-8d09-4270-a0a8-a89691b0b73a
+    weight: -10
+    label: 'All media'
+    id: view
+  297329aa-dcf8-4ec3-aac2-dd4f46c89d83:
+    settings:
+      view: media_library
+      view_display: gallery_user_media_select_modal
+    uuid: 297329aa-dcf8-4ec3-aac2-dd4f46c89d83
+    weight: -9
+    label: 'My media'
+    id: view
+  6cf9f4b5-d1b8-4325-96ef-a48da34e4ea7:
+    settings:
+      entity_type: media
+      bundle: video
+    uuid: 6cf9f4b5-d1b8-4325-96ef-a48da34e4ea7
+    weight: -7
+    label: 'Add video'
+    id: entity_form
+submit_text: 'Select media files'
diff --git a/web/modules/contrib/media/config/install/entity_browser.browser.media_embed.yml b/web/modules/contrib/media/config/install/entity_browser.browser.media_embed.yml
new file mode 100644 (file)
index 0000000..ab5c57f
--- /dev/null
@@ -0,0 +1,74 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - media_entity.bundle.image
+    - views.view.media_library
+  module:
+    - dropzonejs_eb_widget
+    - entity_browser_entity_form
+    - media_entity
+    - views
+name: media_embed
+label: 'Media Embed'
+display: iframe
+display_configuration:
+  width: 100%
+  height: '768'
+  link_text: 'Select media'
+  auto_open: true
+selection_display: no_display
+selection_display_configuration: {  }
+widget_selector: tabs
+widget_selector_configuration: {  }
+widgets:
+  1672850b-3c24-400f-a0ed-a160efa55310:
+    settings:
+      view: media_library
+      view_display: media_select_modal
+      submit_text: 'Select media'
+    uuid: 1672850b-3c24-400f-a0ed-a160efa55310
+    weight: 1
+    label: 'All media'
+    id: view
+  6eea7af9-0310-4b02-a243-3eadc488a077:
+    settings:
+      view: media_library
+      view_display: user_media_select_modal
+      submit_text: 'Select media'
+    uuid: 6eea7af9-0310-4b02-a243-3eadc488a077
+    weight: 2
+    label: 'My media'
+    id: view
+  f3cd984b-a622-432b-b87c-e211a6b901e2:
+    settings:
+      media_entity_bundle: image
+      upload_location: 'public://[date:custom:Y]-[date:custom:m]'
+      dropzone_description: 'Drag and drop files here to upload'
+      max_filesize: 2M
+      extensions: 'png jpg jpeg gif'
+      submit_text: 'Select entities'
+    uuid: f3cd984b-a622-432b-b87c-e211a6b901e2
+    weight: 3
+    label: 'Add images'
+    id: dropzonejs_media_entity
+  927280a8-090f-4de8-b66a-932c81ed13c3:
+    settings:
+      entity_type: media
+      bundle: video
+      form_mode: default
+      submit_text: 'Save video'
+    uuid: 927280a8-090f-4de8-b66a-932c81ed13c3
+    weight: 4
+    label: 'Add video'
+    id: entity_form
+  c2f178e7-de55-4369-9c67-fee6ca2c11f0:
+    settings:
+      entity_type: media
+      bundle: document
+      form_mode: default
+      submit_text: 'Save document'
+    uuid: c2f178e7-de55-4369-9c67-fee6ca2c11f0
+    weight: 5
+    label: 'Add document'
+    id: entity_form
diff --git a/web/modules/contrib/media/config/install/entity_browser.browser.media_library.yml b/web/modules/contrib/media/config/install/entity_browser.browser.media_library.yml
new file mode 100644 (file)
index 0000000..c6f7820
--- /dev/null
@@ -0,0 +1,62 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.image.field_image
+    - media_entity.bundle.image
+    - views.view.media_library
+  module:
+    - dropzonejs_eb_widget
+    - entity_browser_entity_form
+    - views
+_core:
+  default_config_hash: 4iRNH-RBNv_P6dA3wXHiA9BjuBZYMOXScslFy1qoPQI
+name: media_library
+label: 'Media Library'
+display: modal
+display_configuration:
+  width: '950'
+  height: '600'
+  link_text: 'Select media'
+  auto_open: true
+selection_display: no_display
+selection_display_configuration: {  }
+widget_selector: tabs
+widget_selector_configuration: {  }
+widgets:
+  f7742394-192d-4026-9947-055f7e167fb8:
+    settings:
+      view: media_library
+      view_display: media_select_modal
+    uuid: f7742394-192d-4026-9947-055f7e167fb8
+    weight: 1
+    label: 'All Media'
+    id: view
+  1d256d06-dcd8-4fb8-86a9-52c8024c2c73:
+    settings:
+      media_entity_bundle: 'image'
+      upload_location: 'public://[date:custom:Y]-[date:custom:m]'
+      dropzone_description: 'Drag and drop files here to upload'
+      max_filesize: 2M
+      extensions: 'png jpg jpeg gif'
+    uuid: 1d256d06-dcd8-4fb8-86a9-52c8024c2c73
+    weight: 0
+    label: 'Upload files'
+    id: dropzonejs_media_entity
+  6c56db32-05ff-4665-bf1a-c146b129c616:
+    settings:
+      view: media_library
+      view_display: user_media_select_modal
+    uuid: 6c56db32-05ff-4665-bf1a-c146b129c616
+    weight: 3
+    label: 'My Media'
+    id: view
+  ccb53382-c6c1-4e74-8d3a-9f7e850d6ebc:
+    settings:
+      entity_type: media
+      bundle: video
+    uuid: ccb53382-c6c1-4e74-8d3a-9f7e850d6ebc
+    weight: 4
+    label: 'Add video'
+    id: entity_form
+submit_text: 'Select media files'
diff --git a/web/modules/contrib/media/config/install/field.field.media.document.field_document.yml b/web/modules/contrib/media/config/install/field.field.media.document.field_document.yml
new file mode 100644 (file)
index 0000000..96f26d5
--- /dev/null
@@ -0,0 +1,26 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_document
+    - media_entity.bundle.document
+  module:
+    - file
+id: media.document.field_document
+field_name: field_document
+entity_type: media
+bundle: document
+label: Document
+description: 'This field stores the document file.'
+required: false
+translatable: false
+default_value: {  }
+default_value_callback: ''
+settings:
+  file_directory: '[date:custom:Y]-[date:custom:m]'
+  file_extensions: 'txt pdf doc docx odf ods odt otp ots ott odp ppt pptx rtf xls xlsx csv'
+  max_filesize: ''
+  description_field: false
+  handler: 'default:file'
+  handler_settings: {  }
+field_type: file
diff --git a/web/modules/contrib/media/config/install/field.field.media.document.field_document_size.yml b/web/modules/contrib/media/config/install/field.field.media.document.field_document_size.yml
new file mode 100644 (file)
index 0000000..b7c3d56
--- /dev/null
@@ -0,0 +1,22 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_document_size
+    - media_entity.bundle.document
+id: media.document.field_document_size
+field_name: field_document_size
+entity_type: media
+bundle: document
+label: Size
+description: 'This field stores the size of the document file.'
+required: false
+translatable: false
+default_value: {  }
+default_value_callback: ''
+settings:
+  min: null
+  max: null
+  prefix: ''
+  suffix: byte|bytes
+field_type: integer
diff --git a/web/modules/contrib/media/config/install/field.field.media.document.field_media_in_library.yml b/web/modules/contrib/media/config/install/field.field.media.document.field_media_in_library.yml
new file mode 100644 (file)
index 0000000..5d1c8d8
--- /dev/null
@@ -0,0 +1,22 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_media_in_library
+    - media_entity.bundle.document
+id: media.document.field_media_in_library
+field_name: field_media_in_library
+entity_type: media
+bundle: document
+label: 'Save to my media library'
+description: ''
+required: false
+translatable: true
+default_value:
+  -
+    value: 1
+default_value_callback: ''
+settings:
+  on_label: 'Saved to my media library'
+  off_label: 'Not in my media library'
+field_type: boolean
diff --git a/web/modules/contrib/media/config/install/field.field.media.document.field_mime_type.yml b/web/modules/contrib/media/config/install/field.field.media.document.field_mime_type.yml
new file mode 100644 (file)
index 0000000..3562dc1
--- /dev/null
@@ -0,0 +1,18 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_mime_type
+    - media_entity.bundle.document
+id: media.document.field_mime_type
+field_name: field_mime_type
+entity_type: media
+bundle: document
+label: 'MIME Type'
+description: 'This field stores the mime type of the document.'
+required: false
+translatable: false
+default_value: {  }
+default_value_callback: ''
+settings: {  }
+field_type: string
diff --git a/web/modules/contrib/media/config/install/field.field.media.gallery.field_media_in_library.yml b/web/modules/contrib/media/config/install/field.field.media.gallery.field_media_in_library.yml
new file mode 100644 (file)
index 0000000..2f33884
--- /dev/null
@@ -0,0 +1,22 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_media_in_library
+    - media_entity.bundle.gallery
+id: media.gallery.field_media_in_library
+field_name: field_media_in_library
+entity_type: media
+bundle: gallery
+label: 'Save to my media library'
+description: ''
+required: false
+translatable: true
+default_value:
+  -
+    value: 1
+default_value_callback: ''
+settings:
+  on_label: 'Saved to my media library'
+  off_label: 'Not in my media library'
+field_type: boolean
diff --git a/web/modules/contrib/media/config/install/field.field.media.gallery.field_slide.yml b/web/modules/contrib/media/config/install/field.field.media.gallery.field_slide.yml
new file mode 100644 (file)
index 0000000..2279550
--- /dev/null
@@ -0,0 +1,37 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_slide
+    - media_entity.bundle.document
+    - media_entity.bundle.gallery
+    - media_entity.bundle.image
+    - media_entity.bundle.instagram
+    - media_entity.bundle.tweet
+    - media_entity.bundle.video
+_core:
+  default_config_hash: '-Vokz3E77rQR00KAD_b4OzhvWKUK_0RWM68Bs8jeAhM'
+id: media.gallery.field_slide
+field_name: field_slide
+entity_type: media
+bundle: gallery
+label: Slides
+description: 'Media type items for slideshow'
+required: false
+translatable: false
+default_value: {  }
+default_value_callback: ''
+settings:
+  handler: 'default:media'
+  handler_settings:
+    target_bundles:
+      document: document
+      image: image
+      instagram: instagram
+      tweet: tweet
+      video: video
+    sort:
+      field: _none
+    auto_create: false
+    auto_create_bundle: image
+field_type: entity_reference
diff --git a/web/modules/contrib/media/config/install/field.field.media.image.field_image.yml b/web/modules/contrib/media/config/install/field.field.media.image.field_image.yml
new file mode 100644 (file)
index 0000000..e1ce9a0
--- /dev/null
@@ -0,0 +1,37 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_image
+    - media_entity.bundle.image
+  module:
+    - image
+id: media.image.field_image
+field_name: field_image
+entity_type: media
+bundle: image
+label: Image
+description: ''
+required: true
+translatable: false
+default_value: {  }
+default_value_callback: ''
+settings:
+  file_directory: '[date:custom:Y]-[date:custom:m]'
+  file_extensions: 'png gif jpg jpeg'
+  max_filesize: ''
+  max_resolution: ''
+  min_resolution: ''
+  alt_field: true
+  alt_field_required: true
+  title_field: false
+  title_field_required: false
+  default_image:
+    uuid: ''
+    alt: ''
+    title: ''
+    width: null
+    height: null
+  handler: 'default:file'
+  handler_settings: {  }
+field_type: image
diff --git a/web/modules/contrib/media/config/install/field.field.media.image.field_media_in_library.yml b/web/modules/contrib/media/config/install/field.field.media.image.field_media_in_library.yml
new file mode 100644 (file)
index 0000000..a989000
--- /dev/null
@@ -0,0 +1,22 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_media_in_library
+    - media_entity.bundle.image
+id: media.image.field_media_in_library
+field_name: field_media_in_library
+entity_type: media
+bundle: image
+label: 'Save to my media library'
+description: ''
+required: false
+translatable: true
+default_value:
+  -
+    value: 1
+default_value_callback: ''
+settings:
+  on_label: 'Saved to my media library'
+  off_label: 'Not in my media library'
+field_type: boolean
diff --git a/web/modules/contrib/media/config/install/field.field.media.instagram.field_instagram_shortcode.yml b/web/modules/contrib/media/config/install/field.field.media.instagram.field_instagram_shortcode.yml
new file mode 100644 (file)
index 0000000..d7d12b3
--- /dev/null
@@ -0,0 +1,18 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_instagram_shortcode
+    - media_entity.bundle.instagram
+id: media.instagram.field_instagram_shortcode
+field_name: field_instagram_shortcode
+entity_type: media
+bundle: instagram
+label: 'Instagram Shortcode'
+description: 'Instagram post''s unique identifier. Ex: BHak8cShinX is the shortcode for www.instagram.com/p/BHak8cShinX/'
+required: false
+translatable: false
+default_value: {  }
+default_value_callback: ''
+settings: {  }
+field_type: string
diff --git a/web/modules/contrib/media/config/install/field.field.media.instagram.field_instagram_url.yml b/web/modules/contrib/media/config/install/field.field.media.instagram.field_instagram_url.yml
new file mode 100644 (file)
index 0000000..1f5336a
--- /dev/null
@@ -0,0 +1,22 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_instagram_url
+    - media_entity.bundle.instagram
+  module:
+    - link
+id: media.instagram.field_instagram_url
+field_name: field_instagram_url
+entity_type: media
+bundle: instagram
+label: 'Instagram URL'
+description: 'URL of the Instagram post. Example url: https://www.instagram.com/p/BHak8cShinX/'
+required: true
+translatable: false
+default_value: {  }
+default_value_callback: ''
+settings:
+  link_type: 16
+  title: 0
+field_type: link
diff --git a/web/modules/contrib/media/config/install/field.field.media.instagram.field_media_in_library.yml b/web/modules/contrib/media/config/install/field.field.media.instagram.field_media_in_library.yml
new file mode 100644 (file)
index 0000000..7aa938b
--- /dev/null
@@ -0,0 +1,22 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_media_in_library
+    - media_entity.bundle.instagram
+id: media.instagram.field_media_in_library
+field_name: field_media_in_library
+entity_type: media
+bundle: instagram
+label: 'Save to my media library'
+description: ''
+required: false
+translatable: false
+default_value:
+  -
+    value: 1
+default_value_callback: ''
+settings:
+  on_label: 'Saved to my media library'
+  off_label: 'Not in my media library'
+field_type: boolean
diff --git a/web/modules/contrib/media/config/install/field.field.media.tweet.field_media_in_library.yml b/web/modules/contrib/media/config/install/field.field.media.tweet.field_media_in_library.yml
new file mode 100644 (file)
index 0000000..bf3ad1c
--- /dev/null
@@ -0,0 +1,22 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_media_in_library
+    - media_entity.bundle.tweet
+id: media.tweet.field_media_in_library
+field_name: field_media_in_library
+entity_type: media
+bundle: tweet
+label: 'Save to my media library'
+description: ''
+required: false
+translatable: true
+default_value:
+  -
+    value: 1
+default_value_callback: ''
+settings:
+  on_label: 'Saved to my media library'
+  off_label: 'Not in my media library'
+field_type: boolean
diff --git a/web/modules/contrib/media/config/install/field.field.media.tweet.field_tweet_author.yml b/web/modules/contrib/media/config/install/field.field.media.tweet.field_tweet_author.yml
new file mode 100644 (file)
index 0000000..638d756
--- /dev/null
@@ -0,0 +1,18 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_tweet_author
+    - media_entity.bundle.tweet
+id: media.tweet.field_tweet_author
+field_name: field_tweet_author
+entity_type: media
+bundle: tweet
+label: 'Tweet author'
+description: 'This field stores the author of the tweet.'
+required: false
+translatable: false
+default_value: {  }
+default_value_callback: ''
+settings: {  }
+field_type: string
diff --git a/web/modules/contrib/media/config/install/field.field.media.tweet.field_tweet_id.yml b/web/modules/contrib/media/config/install/field.field.media.tweet.field_tweet_id.yml
new file mode 100644 (file)
index 0000000..3b5f955
--- /dev/null
@@ -0,0 +1,18 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_tweet_id
+    - media_entity.bundle.tweet
+id: media.tweet.field_tweet_id
+field_name: field_tweet_id
+entity_type: media
+bundle: tweet
+label: 'Tweet ID'
+description: 'This field stores the id of the tweet. Example: 20 is the id in twitter.com/jack/status/20'
+required: false
+translatable: false
+default_value: {  }
+default_value_callback: ''
+settings: {  }
+field_type: string
diff --git a/web/modules/contrib/media/config/install/field.field.media.tweet.field_tweet_url.yml b/web/modules/contrib/media/config/install/field.field.media.tweet.field_tweet_url.yml
new file mode 100644 (file)
index 0000000..a1dffab
--- /dev/null
@@ -0,0 +1,22 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_tweet_url
+    - media_entity.bundle.tweet
+  module:
+    - link
+id: media.tweet.field_tweet_url
+field_name: field_tweet_url
+entity_type: media
+bundle: tweet
+label: 'Tweet URL'
+description: 'This field stores the URL of the tweet. Example: https://twitter.com/jack/status/20'
+required: true
+translatable: false
+default_value: {  }
+default_value_callback: ''
+settings:
+  link_type: 16
+  title: 0
+field_type: link
diff --git a/web/modules/contrib/media/config/install/field.field.media.video.field_id.yml b/web/modules/contrib/media/config/install/field.field.media.video.field_id.yml
new file mode 100644 (file)
index 0000000..817428d
--- /dev/null
@@ -0,0 +1,18 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_id
+    - media_entity.bundle.video
+id: media.video.field_id
+field_name: field_id
+entity_type: media
+bundle: video
+label: 'Video ID'
+description: ''
+required: false
+translatable: false
+default_value: {  }
+default_value_callback: ''
+settings: {  }
+field_type: string
diff --git a/web/modules/contrib/media/config/install/field.field.media.video.field_media_in_library.yml b/web/modules/contrib/media/config/install/field.field.media.video.field_media_in_library.yml
new file mode 100644 (file)
index 0000000..d79b0b2
--- /dev/null
@@ -0,0 +1,22 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_media_in_library
+    - media_entity.bundle.video
+id: media.video.field_media_in_library
+field_name: field_media_in_library
+entity_type: media
+bundle: video
+label: 'Save to my media library'
+description: ''
+required: false
+translatable: true
+default_value:
+  -
+    value: 1
+default_value_callback: ''
+settings:
+  on_label: 'Saved to my media library'
+  off_label: 'Not in my media library'
+field_type: boolean
diff --git a/web/modules/contrib/media/config/install/field.field.media.video.field_source.yml b/web/modules/contrib/media/config/install/field.field.media.video.field_source.yml
new file mode 100644 (file)
index 0000000..8dcce43
--- /dev/null
@@ -0,0 +1,18 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_source
+    - media_entity.bundle.video
+id: media.video.field_source
+field_name: field_source
+entity_type: media
+bundle: video
+label: 'Video source name'
+description: ''
+required: false
+translatable: false
+default_value: {  }
+default_value_callback: ''
+settings: {  }
+field_type: string
diff --git a/web/modules/contrib/media/config/install/field.field.media.video.field_video.yml b/web/modules/contrib/media/config/install/field.field.media.video.field_video.yml
new file mode 100644 (file)
index 0000000..21df4fe
--- /dev/null
@@ -0,0 +1,24 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_video
+    - media_entity.bundle.video
+  module:
+    - video_embed_field
+id: media.video.field_video
+field_name: field_video
+entity_type: media
+bundle: video
+label: 'Video URL'
+description: 'Enter a YouTube/Vimeo video URL. <a href="https://www.drupal.org/project/video_embed_field">Click here</a> to learn about adding more providers.'
+required: true
+translatable: true
+default_value: {  }
+default_value_callback: ''
+settings:
+  allowed_providers:
+    vimeo: '0'
+    youtube_playlist: '0'
+    youtube: '0'
+field_type: video_embed_field
diff --git a/web/modules/contrib/media/config/install/field.storage.media.field_document.yml b/web/modules/contrib/media/config/install/field.storage.media.field_document.yml
new file mode 100644 (file)
index 0000000..96b3d87
--- /dev/null
@@ -0,0 +1,22 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - file
+    - media_entity
+id: media.field_document
+field_name: field_document
+entity_type: media
+type: file
+settings:
+  target_type: file
+  display_field: false
+  display_default: false
+  uri_scheme: public
+module: file
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/web/modules/contrib/media/config/install/field.storage.media.field_document_size.yml b/web/modules/contrib/media/config/install/field.storage.media.field_document_size.yml
new file mode 100644 (file)
index 0000000..53fd371
--- /dev/null
@@ -0,0 +1,19 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media_entity
+id: media.field_document_size
+field_name: field_document_size
+entity_type: media
+type: integer
+settings:
+  unsigned: false
+  size: normal
+module: core
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/web/modules/contrib/media/config/install/field.storage.media.field_id.yml b/web/modules/contrib/media/config/install/field.storage.media.field_id.yml
new file mode 100644 (file)
index 0000000..2e46226
--- /dev/null
@@ -0,0 +1,20 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media_entity
+id: media.field_id
+field_name: field_id
+entity_type: media
+type: string
+settings:
+  max_length: 255
+  is_ascii: false
+  case_sensitive: false
+module: core
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/web/modules/contrib/media/config/install/field.storage.media.field_image.yml b/web/modules/contrib/media/config/install/field.storage.media.field_image.yml
new file mode 100644 (file)
index 0000000..14aa06f
--- /dev/null
@@ -0,0 +1,29 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - file
+    - image
+    - media_entity
+id: media.field_image
+field_name: field_image
+entity_type: media
+type: image
+settings:
+  uri_scheme: public
+  default_image:
+    uuid: ''
+    alt: ''
+    title: ''
+    width: null
+    height: null
+  target_type: file
+  display_field: false
+  display_default: false
+module: image
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/web/modules/contrib/media/config/install/field.storage.media.field_instagram_shortcode.yml b/web/modules/contrib/media/config/install/field.storage.media.field_instagram_shortcode.yml
new file mode 100644 (file)
index 0000000..c6ce1d0
--- /dev/null
@@ -0,0 +1,20 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media_entity
+id: media.field_instagram_shortcode
+field_name: field_instagram_shortcode
+entity_type: media
+type: string
+settings:
+  max_length: 255
+  is_ascii: false
+  case_sensitive: false
+module: core
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/web/modules/contrib/media/config/install/field.storage.media.field_instagram_url.yml b/web/modules/contrib/media/config/install/field.storage.media.field_instagram_url.yml
new file mode 100644 (file)
index 0000000..5ee504d
--- /dev/null
@@ -0,0 +1,18 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - link
+    - media_entity
+id: media.field_instagram_url
+field_name: field_instagram_url
+entity_type: media
+type: link
+settings: {  }
+module: link
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/web/modules/contrib/media/config/install/field.storage.media.field_media_in_library.yml b/web/modules/contrib/media/config/install/field.storage.media.field_media_in_library.yml
new file mode 100644 (file)
index 0000000..2bdb4eb
--- /dev/null
@@ -0,0 +1,17 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media_entity
+id: media.field_media_in_library
+field_name: field_media_in_library
+entity_type: media
+type: boolean
+settings: {  }
+module: core
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/web/modules/contrib/media/config/install/field.storage.media.field_mime_type.yml b/web/modules/contrib/media/config/install/field.storage.media.field_mime_type.yml
new file mode 100644 (file)
index 0000000..21f73d9
--- /dev/null
@@ -0,0 +1,20 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media_entity
+id: media.field_mime_type
+field_name: field_mime_type
+entity_type: media
+type: string
+settings:
+  max_length: 255
+  is_ascii: false
+  case_sensitive: false
+module: core
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/web/modules/contrib/media/config/install/field.storage.media.field_slide.yml b/web/modules/contrib/media/config/install/field.storage.media.field_slide.yml
new file mode 100644 (file)
index 0000000..076fc94
--- /dev/null
@@ -0,0 +1,18 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media_entity
+id: media.field_slide
+field_name: field_slide
+entity_type: media
+type: entity_reference
+settings:
+  target_type: media
+module: core
+locked: false
+cardinality: -1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/web/modules/contrib/media/config/install/field.storage.media.field_source.yml b/web/modules/contrib/media/config/install/field.storage.media.field_source.yml
new file mode 100644 (file)
index 0000000..83bf641
--- /dev/null
@@ -0,0 +1,20 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media_entity
+id: media.field_source
+field_name: field_source
+entity_type: media
+type: string
+settings:
+  max_length: 255
+  is_ascii: false
+  case_sensitive: false
+module: core
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/web/modules/contrib/media/config/install/field.storage.media.field_tweet_author.yml b/web/modules/contrib/media/config/install/field.storage.media.field_tweet_author.yml
new file mode 100644 (file)
index 0000000..2b048cd
--- /dev/null
@@ -0,0 +1,20 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media_entity
+id: media.field_tweet_author
+field_name: field_tweet_author
+entity_type: media
+type: string
+settings:
+  max_length: 255
+  is_ascii: false
+  case_sensitive: false
+module: core
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/web/modules/contrib/media/config/install/field.storage.media.field_tweet_id.yml b/web/modules/contrib/media/config/install/field.storage.media.field_tweet_id.yml
new file mode 100644 (file)
index 0000000..afac02a
--- /dev/null
@@ -0,0 +1,20 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media_entity
+id: media.field_tweet_id
+field_name: field_tweet_id
+entity_type: media
+type: string
+settings:
+  max_length: 255
+  is_ascii: false
+  case_sensitive: false
+module: core
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/web/modules/contrib/media/config/install/field.storage.media.field_tweet_url.yml b/web/modules/contrib/media/config/install/field.storage.media.field_tweet_url.yml
new file mode 100644 (file)
index 0000000..c9e0955
--- /dev/null
@@ -0,0 +1,18 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - link
+    - media_entity
+id: media.field_tweet_url
+field_name: field_tweet_url
+entity_type: media
+type: link
+settings: {  }
+module: link
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/web/modules/contrib/media/config/install/field.storage.media.field_video.yml b/web/modules/contrib/media/config/install/field.storage.media.field_video.yml
new file mode 100644 (file)
index 0000000..b53716a
--- /dev/null
@@ -0,0 +1,18 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media_entity
+    - video_embed_field
+id: media.field_video
+field_name: field_video
+entity_type: media
+type: video_embed_field
+settings: {  }
+module: video_embed_field
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/web/modules/contrib/media/config/install/image.style.gallery_item.yml b/web/modules/contrib/media/config/install/image.style.gallery_item.yml
new file mode 100644 (file)
index 0000000..0dcb7d9
--- /dev/null
@@ -0,0 +1,6 @@
+langcode: en
+status: true
+dependencies: {  }
+name: gallery_item
+label: 'Gallery item'
+effects: {  }
diff --git a/web/modules/contrib/media/config/install/image.style.media_crop.yml b/web/modules/contrib/media/config/install/image.style.media_crop.yml
new file mode 100644 (file)
index 0000000..8b2817a
--- /dev/null
@@ -0,0 +1,14 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - crop
+name: media_crop
+label: 'Media Crop'
+effects:
+  39bd5538-f2bc-48a4-b6f2-90ba2175b1eb:
+    uuid: 39bd5538-f2bc-48a4-b6f2-90ba2175b1eb
+    id: crop_crop
+    weight: 2
+    data:
+      crop_type: media_crop
diff --git a/web/modules/contrib/media/config/install/image.style.media_crop_preview.yml b/web/modules/contrib/media/config/install/image.style.media_crop_preview.yml
new file mode 100644 (file)
index 0000000..d086d4e
--- /dev/null
@@ -0,0 +1,14 @@
+langcode: en
+status: true
+dependencies: {  }
+name: media_crop_preview
+label: 'Media Crop Preview'
+effects:
+  30fad5b9-974c-478a-a184-49eceaf12fdb:
+    uuid: 30fad5b9-974c-478a-a184-49eceaf12fdb
+    id: image_scale
+    weight: 1
+    data:
+      width: 800
+      height: null
+      upscale: false
diff --git a/web/modules/contrib/media/config/install/image.style.media_library_item.yml b/web/modules/contrib/media/config/install/image.style.media_library_item.yml
new file mode 100644 (file)
index 0000000..7e00fb9
--- /dev/null
@@ -0,0 +1,16 @@
+langcode: en
+status: true
+dependencies: {  }
+_core:
+  default_config_hash: CxKqipu7yjtFAn40Pe3DhGQ3BbeagRtTMY6r4LbMARM
+name: media_library_item
+label: 'Media library item'
+effects:
+  4d207f58-9aeb-4cc2-9e68-b73febc92d5c:
+    uuid: 4d207f58-9aeb-4cc2-9e68-b73febc92d5c
+    id: image_scale
+    weight: 1
+    data:
+      width: 100
+      height: 100
+      upscale: true
diff --git a/web/modules/contrib/media/config/install/media_entity.bundle.document.yml b/web/modules/contrib/media/config/install/media_entity.bundle.document.yml
new file mode 100644 (file)
index 0000000..4eef3f7
--- /dev/null
@@ -0,0 +1,16 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media_entity_document
+id: document
+label: Document
+description: 'Use Document for uploading document files such as PDF.'
+type: document
+queue_thumbnail_downloads: false
+new_revision: false
+type_configuration:
+  source_field: field_document
+field_map:
+  mime: field_mime_type
+  size: field_document_size
diff --git a/web/modules/contrib/media/config/install/media_entity.bundle.gallery.yml b/web/modules/contrib/media/config/install/media_entity.bundle.gallery.yml
new file mode 100644 (file)
index 0000000..5844a4a
--- /dev/null
@@ -0,0 +1,14 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media_entity_slideshow
+id: gallery
+label: Gallery
+description: 'Use Gallery for creating a collection of different media items.'
+type: slideshow
+queue_thumbnail_downloads: false
+new_revision: false
+type_configuration:
+  source_field: field_slide
+field_map: {  }
diff --git a/web/modules/contrib/media/config/install/media_entity.bundle.image.yml b/web/modules/contrib/media/config/install/media_entity.bundle.image.yml
new file mode 100644 (file)
index 0000000..c54d420
--- /dev/null
@@ -0,0 +1,15 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media_entity_image
+id: image
+label: Image
+description: 'Use Image for uploading locally hosted images.'
+type: image
+queue_thumbnail_downloads: false
+new_revision: true
+type_configuration:
+  source_field: field_image
+  gather_exif: false
+field_map: {  }
diff --git a/web/modules/contrib/media/config/install/media_entity.bundle.instagram.yml b/web/modules/contrib/media/config/install/media_entity.bundle.instagram.yml
new file mode 100644 (file)
index 0000000..f141fd0
--- /dev/null
@@ -0,0 +1,17 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media_entity_instagram
+id: instagram
+label: 'Instagram Post'
+description: 'Use this to attach Instagram posts to your content.'
+type: instagram
+queue_thumbnail_downloads: false
+new_revision: false
+type_configuration:
+  use_instagram_api: false
+  source_field: field_instagram_url
+  client_id: ''
+field_map:
+  shortcode: field_instagram_shortcode
diff --git a/web/modules/contrib/media/config/install/media_entity.bundle.tweet.yml b/web/modules/contrib/media/config/install/media_entity.bundle.tweet.yml
new file mode 100644 (file)
index 0000000..275cacb
--- /dev/null
@@ -0,0 +1,21 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media_entity_twitter
+id: tweet
+label: Tweet
+description: 'Use this to embed Twitter content on your site.'
+type: twitter
+queue_thumbnail_downloads: false
+new_revision: false
+type_configuration:
+  use_twitter_api: false
+  source_field: field_tweet_url
+  consumer_key: ''
+  consumer_secret: ''
+  oauth_access_token: ''
+  oauth_access_token_secret: ''
+field_map:
+  id: field_tweet_id
+  user: field_tweet_author
diff --git a/web/modules/contrib/media/config/install/media_entity.bundle.video.yml b/web/modules/contrib/media/config/install/media_entity.bundle.video.yml
new file mode 100644 (file)
index 0000000..3ecbf97
--- /dev/null
@@ -0,0 +1,18 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - video_embed_media
+_core:
+  default_config_hash: jFflUVocXFBqATOYkClIxBZeuqaCiHiH5B3lwWaCDBY
+id: video
+label: Video
+description: 'Use Video for embedding videos hosted by YouTube, Vimeo, or some other provider.'
+type: video_embed_field
+queue_thumbnail_downloads: false
+new_revision: true
+type_configuration:
+  source_field: field_video
+field_map:
+  id: field_id
+  source_name: field_source
diff --git a/web/modules/contrib/media/config/install/views.view.media_library.yml b/web/modules/contrib/media/config/install/views.view.media_library.yml
new file mode 100644 (file)
index 0000000..d9ac495
--- /dev/null
@@ -0,0 +1,2731 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - core.entity_view_mode.media.media_library
+    - media_entity.bundle.gallery
+  module:
+    - entity_browser
+    - media_entity
+    - user
+id: media_library
+label: 'Media Library'
+module: views
+description: 'Listing of all the media items available.'
+tag: ''
+base_table: media_field_data
+base_field: mid
+core: 8.x
+display:
+  default:
+    display_plugin: default
+    id: default
+    display_title: Master
+    position: 0
+    display_options:
+      access:
+        type: none
+        options: {  }
+      cache:
+        type: tag
+        options: {  }
+      query:
+        type: views_query
+        options:
+          disable_sql_rewrite: false
+          distinct: false
+          replica: false
+          query_comment: ''
+          query_tags: {  }
+      exposed_form:
+        type: basic
+        options:
+          submit_button: Apply
+          reset_button: false
+          reset_button_label: Reset
+          exposed_sorts_label: 'Sort by'
+          expose_sort_order: true
+          sort_asc_label: Asc
+          sort_desc_label: Desc
+      pager:
+        type: mini
+        options:
+          items_per_page: 10
+          offset: 0
+          id: 0
+          total_pages: null
+          expose:
+            items_per_page: false
+            items_per_page_label: 'Items per page'
+            items_per_page_options: '5, 10, 25, 50'
+            items_per_page_options_all: false
+            items_per_page_options_all_label: '- All -'
+            offset: false
+            offset_label: Offset
+          tags:
+            previous: ‹‹
+            next: ››
+      style:
+        type: default
+        options:
+          grouping: {  }
+          row_class: grid-item
+          default_row_class: true
+      row:
+        type: fields
+        options:
+          inline: {  }
+          separator: ''
+          hide_empty: false
+          default_field_elements: true
+      fields:
+        rendered_entity:
+          id: rendered_entity
+          table: media
+          field: rendered_entity
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          view_mode: media_library
+          entity_type: media
+          plugin_id: rendered_entity
+      filters:
+        name:
+          id: name
+          table: media_field_data
+          field: name
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: contains
+          value: ''
+          group: 1
+          exposed: true
+          expose:
+            operator_id: name_op
+            label: 'Media name'
+            description: ''
+            use_operator: false
+            operator: name_op
+            identifier: name
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          entity_type: media
+          entity_field: name
+          plugin_id: string
+        bundle:
+          id: bundle
+          table: media_field_data
+          field: bundle
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: in
+          value: {  }
+          group: 1
+          exposed: true
+          expose:
+            operator_id: bundle_op
+            label: Bundle
+            description: ''
+            use_operator: false
+            operator: bundle_op
+            identifier: bundle
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+            reduce: false
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          entity_type: media
+          entity_field: bundle
+          plugin_id: bundle
+        status:
+          id: status
+          table: media_field_data
+          field: status
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: '='
+          value: true
+          group: 1
+          exposed: true
+          expose:
+            operator_id: ''
+            label: 'Publishing status'
+            description: ''
+            use_operator: false
+            operator: status_op
+            identifier: status
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          plugin_id: boolean
+          entity_type: media
+          entity_field: status
+      sorts:
+        created:
+          id: created
+          table: media_field_data
+          field: created
+          relationship: none
+          group_type: group
+          admin_label: ''
+          order: DESC
+          exposed: true
+          expose:
+            label: Created
+          granularity: second
+          entity_type: media
+          entity_field: created
+          plugin_id: date
+        name:
+          id: name
+          table: media_field_data
+          field: name
+          relationship: none
+          group_type: group
+          admin_label: ''
+          order: ASC
+          exposed: true
+          expose:
+            label: 'Media name'
+          entity_type: media
+          entity_field: name
+          plugin_id: standard
+      header: {  }
+      footer: {  }
+      empty: {  }
+      relationships: {  }
+      arguments: {  }
+      display_extenders: {  }
+      filter_groups:
+        operator: AND
+        groups:
+          1: AND
+      title: 'Media Library'
+    cache_metadata:
+      max-age: -1
+      contexts:
+        - 'languages:language_interface'
+        - url
+        - url.query_args
+        - 'url.query_args:sort_by'
+        - 'url.query_args:sort_order'
+      tags:
+        - 'config:core.entity_view_display.media.document.default'
+        - 'config:core.entity_view_display.media.document.media_library'
+        - 'config:core.entity_view_display.media.gallery.default'
+        - 'config:core.entity_view_display.media.gallery.media_library'
+        - 'config:core.entity_view_display.media.image.default'
+        - 'config:core.entity_view_display.media.image.media_library'
+        - 'config:core.entity_view_display.media.instagram.default'
+        - 'config:core.entity_view_display.media.instagram.media_library'
+        - 'config:core.entity_view_display.media.tweet.default'
+        - 'config:core.entity_view_display.media.tweet.media_library'
+        - 'config:core.entity_view_display.media.video.default'
+        - 'config:core.entity_view_display.media.video.media_library'
+  gallery_media_select_modal:
+    display_plugin: entity_browser
+    id: gallery_media_select_modal
+    display_title: 'Gallery media select modal'
+    position: 1
+    display_options:
+      display_extenders: {  }
+      filters:
+        name:
+          id: name
+          table: media_field_data
+          field: name
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: contains
+          value: ''
+          group: 1
+          exposed: true
+          expose:
+            operator_id: name_op
+            label: 'Media name'
+            description: ''
+            use_operator: false
+            operator: name_op
+            identifier: name
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          entity_type: media
+          entity_field: name
+          plugin_id: string
+        bundle:
+          id: bundle
+          table: media_field_data
+          field: bundle
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: 'not in'
+          value:
+            gallery: gallery
+          group: 1
+          exposed: false
+          expose:
+            operator_id: bundle_op
+            label: Bundle
+            description: ''
+            use_operator: false
+            operator: bundle_op
+            identifier: bundle
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+            reduce: false
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          entity_type: media
+          entity_field: bundle
+          plugin_id: bundle
+        bundle_1:
+          id: bundle_1
+          table: media_field_data
+          field: bundle
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: in
+          value: {  }
+          group: 1
+          exposed: true
+          expose:
+            operator_id: bundle_1_op
+            label: Bundle
+            description: ''
+            use_operator: false
+            operator: bundle_1_op
+            identifier: bundle_1
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+            reduce: false
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          entity_type: media
+          entity_field: bundle
+          plugin_id: bundle
+        status:
+          id: status
+          table: media_field_data
+          field: status
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: '='
+          value: true
+          group: 1
+          exposed: true
+          expose:
+            operator_id: ''
+            label: 'Publishing status'
+            description: ''
+            use_operator: false
+            operator: status_op
+            identifier: status
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          plugin_id: boolean
+          entity_type: media
+          entity_field: status
+        field_media_in_library_value:
+          id: field_media_in_library_value
+          table: media__field_media_in_library
+          field: field_media_in_library_value
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: '='
+          value: true
+          group: 1
+          exposed: false
+          expose:
+            operator_id: ''
+            label: ''
+            description: ''
+            use_operator: false
+            operator: ''
+            identifier: ''
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          plugin_id: boolean
+      defaults:
+        filters: false
+        filter_groups: false
+        fields: false
+        style: false
+        row: false
+        access: false
+      filter_groups:
+        operator: AND
+        groups:
+          1: AND
+      fields:
+        entity_browser_select:
+          id: entity_browser_select
+          table: media
+          field: entity_browser_select
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          entity_type: media
+          plugin_id: entity_browser_select
+        rendered_entity:
+          id: rendered_entity
+          table: media
+          field: rendered_entity
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          view_mode: media_library
+          entity_type: media
+          plugin_id: rendered_entity
+      display_description: 'An entity browser modal to select media while creating gallery.'
+      style:
+        type: default
+        options:
+          grouping: {  }
+          row_class: grid-item
+          default_row_class: true
+      row:
+        type: fields
+        options:
+          inline: {  }
+          separator: ''
+          hide_empty: false
+          default_field_elements: true
+      access:
+        type: perm
+        options:
+          perm: 'access media overview'
+    cache_metadata:
+      max-age: -1
+      contexts:
+        - 'languages:language_interface'
+        - url
+        - url.query_args
+        - 'url.query_args:sort_by'
+        - 'url.query_args:sort_order'
+        - user.permissions
+      tags:
+        - 'config:core.entity_view_display.media.document.default'
+        - 'config:core.entity_view_display.media.document.media_library'
+        - 'config:core.entity_view_display.media.gallery.default'
+        - 'config:core.entity_view_display.media.gallery.media_library'
+        - 'config:core.entity_view_display.media.image.default'
+        - 'config:core.entity_view_display.media.image.media_library'
+        - 'config:core.entity_view_display.media.instagram.default'
+        - 'config:core.entity_view_display.media.instagram.media_library'
+        - 'config:core.entity_view_display.media.tweet.default'
+        - 'config:core.entity_view_display.media.tweet.media_library'
+        - 'config:core.entity_view_display.media.video.default'
+        - 'config:core.entity_view_display.media.video.media_library'
+  gallery_user_media_select_modal:
+    display_plugin: entity_browser
+    id: gallery_user_media_select_modal
+    display_title: 'Gallery User Media select modal'
+    position: 1
+    display_options:
+      display_extenders: {  }
+      filters:
+        name:
+          id: name
+          table: media_field_data
+          field: name
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: contains
+          value: ''
+          group: 1
+          exposed: true
+          expose:
+            operator_id: name_op
+            label: 'Media name'
+            description: ''
+            use_operator: false
+            operator: name_op
+            identifier: name
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          entity_type: media
+          entity_field: name
+          plugin_id: string
+        bundle:
+          id: bundle
+          table: media_field_data
+          field: bundle
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: 'not in'
+          value:
+            gallery: gallery
+          group: 1
+          exposed: false
+          expose:
+            operator_id: bundle_op
+            label: Bundle
+            description: ''
+            use_operator: false
+            operator: bundle_op
+            identifier: bundle
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+            reduce: false
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          entity_type: media
+          entity_field: bundle
+          plugin_id: bundle
+        bundle_1:
+          id: bundle_1
+          table: media_field_data
+          field: bundle
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: in
+          value: {  }
+          group: 1
+          exposed: true
+          expose:
+            operator_id: bundle_1_op
+            label: Bundle
+            description: ''
+            use_operator: false
+            operator: bundle_1_op
+            identifier: bundle_1
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+            reduce: false
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          entity_type: media
+          entity_field: bundle
+          plugin_id: bundle
+        status:
+          id: status
+          table: media_field_data
+          field: status
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: '='
+          value: true
+          group: 1
+          exposed: true
+          expose:
+            operator_id: ''
+            label: 'Publishing status'
+            description: ''
+            use_operator: false
+            operator: status_op
+            identifier: status
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          plugin_id: boolean
+          entity_type: media
+          entity_field: status
+        field_media_in_library_value:
+          id: field_media_in_library_value
+          table: media__field_media_in_library
+          field: field_media_in_library_value
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: '='
+          value: true
+          group: 1
+          exposed: false
+          expose:
+            operator_id: ''
+            label: ''
+            description: ''
+            use_operator: false
+            operator: ''
+            identifier: ''
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          plugin_id: boolean
+      defaults:
+        filters: false
+        filter_groups: false
+        fields: false
+        style: false
+        row: false
+        access: false
+        arguments: false
+        relationships: false
+      filter_groups:
+        operator: AND
+        groups:
+          1: AND
+      fields:
+        entity_browser_select:
+          id: entity_browser_select
+          table: media
+          field: entity_browser_select
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          entity_type: media
+          plugin_id: entity_browser_select
+        rendered_entity:
+          id: rendered_entity
+          table: media
+          field: rendered_entity
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          view_mode: media_library
+          entity_type: media
+          plugin_id: rendered_entity
+      display_description: 'An entity browser modal to select user media while creating gallery'
+      style:
+        type: default
+        options:
+          grouping: {  }
+          row_class: grid-item
+          default_row_class: true
+      row:
+        type: fields
+        options:
+          inline: {  }
+          separator: ''
+          hide_empty: false
+          default_field_elements: true
+      access:
+        type: perm
+        options:
+          perm: 'edit own media content'
+      arguments:
+        uid:
+          id: uid
+          table: media_field_data
+          field: uid
+          relationship: none
+          group_type: group
+          admin_label: ''
+          default_action: default
+          exception:
+            value: all
+            title_enable: false
+            title: All
+          title_enable: false
+          title: ''
+          default_argument_type: current_user
+          default_argument_options: {  }
+          default_argument_skip_url: false
+          summary_options:
+            base_path: ''
+            count: true
+            items_per_page: 25
+            override: false
+          summary:
+            sort_order: asc
+            number_of_records: 0
+            format: default_summary
+          specify_validation: false
+          validate:
+            type: none
+            fail: 'not found'
+          validate_options: {  }
+          break_phrase: false
+          not: false
+          entity_type: media
+          entity_field: uid
+          plugin_id: numeric
+      relationships: {  }
+    cache_metadata:
+      max-age: -1
+      contexts:
+        - 'languages:language_interface'
+        - url
+        - url.query_args
+        - 'url.query_args:sort_by'
+        - 'url.query_args:sort_order'
+        - user
+        - user.permissions
+      tags:
+        - 'config:core.entity_view_display.media.document.default'
+        - 'config:core.entity_view_display.media.document.media_library'
+        - 'config:core.entity_view_display.media.gallery.default'
+        - 'config:core.entity_view_display.media.gallery.media_library'
+        - 'config:core.entity_view_display.media.image.default'
+        - 'config:core.entity_view_display.media.image.media_library'
+        - 'config:core.entity_view_display.media.instagram.default'
+        - 'config:core.entity_view_display.media.instagram.media_library'
+        - 'config:core.entity_view_display.media.tweet.default'
+        - 'config:core.entity_view_display.media.tweet.media_library'
+        - 'config:core.entity_view_display.media.video.default'
+        - 'config:core.entity_view_display.media.video.media_library'
+  global_media_library_page:
+    display_plugin: page
+    id: global_media_library_page
+    display_title: 'Gobal Media Library'
+    position: 2
+    display_options:
+      display_extenders: {  }
+      path: admin/content/media
+      menu:
+        type: tab
+        title: Media
+        description: 'Displays the media items'
+        expanded: false
+        parent: ''
+        weight: 0
+        context: '0'
+        menu_name: main
+      arguments: {  }
+      defaults:
+        arguments: false
+        access: false
+        style: false
+        row: false
+        fields: false
+        filters: false
+        filter_groups: false
+        title: false
+      access:
+        type: perm
+        options:
+          perm: 'access media overview'
+      display_description: 'View for the global media library.'
+      style:
+        type: default
+        options:
+          grouping: {  }
+          row_class: grid-item-library
+          default_row_class: true
+      row:
+        type: fields
+        options:
+          inline: {  }
+          separator: ''
+          hide_empty: false
+          default_field_elements: true
+      fields:
+        rendered_entity:
+          id: rendered_entity
+          table: media
+          field: rendered_entity
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          view_mode: media_library
+          entity_type: media
+          plugin_id: rendered_entity
+        bundle:
+          id: bundle
+          table: media_field_data
+          field: bundle
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: Type
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: true
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          click_sort_column: target_id
+          type: entity_reference_label
+          settings:
+            link: false
+          group_column: target_id
+          group_columns: {  }
+          group_rows: true
+          delta_limit: 0
+          delta_offset: 0
+          delta_reversed: false
+          delta_first_last: false
+          multi_type: separator
+          separator: ', '
+          field_api_classes: false
+          entity_type: media
+          entity_field: bundle
+          plugin_id: field
+        status:
+          id: status
+          table: media_field_data
+          field: status
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: Status
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: true
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          click_sort_column: value
+          type: boolean
+          settings:
+            format: custom
+            format_custom_true: Published
+            format_custom_false: Unpublished
+          group_column: value
+          group_columns: {  }
+          group_rows: true
+          delta_limit: 0
+          delta_offset: 0
+          delta_reversed: false
+          delta_first_last: false
+          multi_type: separator
+          separator: ', '
+          field_api_classes: false
+          entity_type: media
+          entity_field: status
+          plugin_id: field
+        operations:
+          id: operations
+          table: media
+          field: operations
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          destination: true
+          entity_type: media
+          plugin_id: entity_operations
+      filters:
+        name:
+          id: name
+          table: media_field_data
+          field: name
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: contains
+          value: ''
+          group: 1
+          exposed: true
+          expose:
+            operator_id: name_op
+            label: 'Media name'
+            description: ''
+            use_operator: false
+            operator: name_op
+            identifier: name
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          entity_type: media
+          entity_field: name
+          plugin_id: string
+        bundle:
+          id: bundle
+          table: media_field_data
+          field: bundle
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: in
+          value: {  }
+          group: 1
+          exposed: true
+          expose:
+            operator_id: bundle_op
+            label: Bundle
+            description: ''
+            use_operator: false
+            operator: bundle_op
+            identifier: bundle
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+            reduce: false
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          entity_type: media
+          entity_field: bundle
+          plugin_id: bundle
+        status:
+          id: status
+          table: media_field_data
+          field: status
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: '='
+          value: true
+          group: 1
+          exposed: true
+          expose:
+            operator_id: ''
+            label: 'Publishing status'
+            description: ''
+            use_operator: false
+            operator: status_op
+            identifier: status
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          plugin_id: boolean
+          entity_type: media
+          entity_field: status
+        langcode:
+          id: langcode
+          table: media_field_data
+          field: langcode
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: in
+          value: {  }
+          group: 1
+          exposed: true
+          expose:
+            operator_id: langcode_op
+            label: Language
+            description: ''
+            use_operator: false
+            operator: langcode_op
+            identifier: langcode
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+            reduce: false
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          entity_type: media
+          entity_field: langcode
+          plugin_id: language
+      filter_groups:
+        operator: AND
+        groups:
+          1: AND
+      title: 'Media Library'
+    cache_metadata:
+      max-age: -1
+      contexts:
+        - 'languages:language_content'
+        - 'languages:language_interface'
+        - url
+        - url.query_args
+        - 'url.query_args:sort_by'
+        - 'url.query_args:sort_order'
+        - user.permissions
+      tags:
+        - 'config:core.entity_view_display.media.document.default'
+        - 'config:core.entity_view_display.media.document.media_library'
+        - 'config:core.entity_view_display.media.gallery.default'
+        - 'config:core.entity_view_display.media.gallery.media_library'
+        - 'config:core.entity_view_display.media.image.default'
+        - 'config:core.entity_view_display.media.image.media_library'
+        - 'config:core.entity_view_display.media.instagram.default'
+        - 'config:core.entity_view_display.media.instagram.media_library'
+        - 'config:core.entity_view_display.media.tweet.default'
+        - 'config:core.entity_view_display.media.tweet.media_library'
+        - 'config:core.entity_view_display.media.video.default'
+        - 'config:core.entity_view_display.media.video.media_library'
+  media_select_modal:
+    display_plugin: entity_browser
+    id: media_select_modal
+    display_title: 'Media select modal'
+    position: 1
+    display_options:
+      display_extenders: {  }
+      filters:
+        name:
+          id: name
+          table: media_field_data
+          field: name
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: contains
+          value: ''
+          group: 1
+          exposed: true
+          expose:
+            operator_id: name_op
+            label: 'Media name'
+            description: ''
+            use_operator: false
+            operator: name_op
+            identifier: name
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          entity_type: media
+          entity_field: name
+          plugin_id: string
+        bundle:
+          id: bundle
+          table: media_field_data
+          field: bundle
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: in
+          value: {  }
+          group: 1
+          exposed: true
+          expose:
+            operator_id: bundle_op
+            label: Bundle
+            description: ''
+            use_operator: false
+            operator: bundle_op
+            identifier: bundle
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+            reduce: false
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          entity_type: media
+          entity_field: bundle
+          plugin_id: bundle
+        status:
+          id: status
+          table: media_field_data
+          field: status
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: '='
+          value: true
+          group: 1
+          exposed: true
+          expose:
+            operator_id: ''
+            label: 'Publishing status'
+            description: ''
+            use_operator: false
+            operator: status_op
+            identifier: status
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          plugin_id: boolean
+          entity_type: media
+          entity_field: status
+        field_media_in_library_value:
+          id: field_media_in_library_value
+          table: media__field_media_in_library
+          field: field_media_in_library_value
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: '='
+          value: true
+          group: 1
+          exposed: false
+          expose:
+            operator_id: ''
+            label: ''
+            description: ''
+            use_operator: false
+            operator: ''
+            identifier: ''
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          plugin_id: boolean
+      defaults:
+        filters: false
+        filter_groups: false
+        fields: false
+        style: false
+        row: false
+        access: false
+      filter_groups:
+        operator: AND
+        groups:
+          1: AND
+      fields:
+        entity_browser_select:
+          id: entity_browser_select
+          table: media
+          field: entity_browser_select
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          entity_type: media
+          plugin_id: entity_browser_select
+        rendered_entity:
+          id: rendered_entity
+          table: media
+          field: rendered_entity
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          view_mode: media_library
+          entity_type: media
+          plugin_id: rendered_entity
+      display_description: 'An entity browser modal to select media.'
+      style:
+        type: default
+        options:
+          grouping: {  }
+          row_class: grid-item
+          default_row_class: true
+      row:
+        type: fields
+        options:
+          inline: {  }
+          separator: ''
+          hide_empty: false
+          default_field_elements: true
+      access:
+        type: perm
+        options:
+          perm: 'access media overview'
+    cache_metadata:
+      max-age: -1
+      contexts:
+        - 'languages:language_interface'
+        - url
+        - url.query_args
+        - 'url.query_args:sort_by'
+        - 'url.query_args:sort_order'
+        - user.permissions
+      tags:
+        - 'config:core.entity_view_display.media.document.default'
+        - 'config:core.entity_view_display.media.document.media_library'
+        - 'config:core.entity_view_display.media.gallery.default'
+        - 'config:core.entity_view_display.media.gallery.media_library'
+        - 'config:core.entity_view_display.media.image.default'
+        - 'config:core.entity_view_display.media.image.media_library'
+        - 'config:core.entity_view_display.media.instagram.default'
+        - 'config:core.entity_view_display.media.instagram.media_library'
+        - 'config:core.entity_view_display.media.tweet.default'
+        - 'config:core.entity_view_display.media.tweet.media_library'
+        - 'config:core.entity_view_display.media.video.default'
+        - 'config:core.entity_view_display.media.video.media_library'
+  user_media_library:
+    display_plugin: page
+    id: user_media_library
+    display_title: 'User Media Library'
+    position: 2
+    display_options:
+      display_extenders: {  }
+      path: user/%user/media
+      menu:
+        type: tab
+        title: Media
+        description: 'Displays the media items by the user'
+        expanded: false
+        parent: user.page
+        weight: 0
+        context: '0'
+        menu_name: account
+      arguments:
+        uid:
+          id: uid
+          table: media_field_data
+          field: uid
+          relationship: none
+          group_type: group
+          admin_label: ''
+          default_action: default
+          exception:
+            value: all
+            title_enable: false
+            title: All
+          title_enable: false
+          title: ''
+          default_argument_type: user
+          default_argument_options:
+            user: false
+          default_argument_skip_url: false
+          summary_options:
+            base_path: ''
+            count: true
+            items_per_page: 25
+            override: false
+          summary:
+            sort_order: asc
+            number_of_records: 0
+            format: default_summary
+          specify_validation: false
+          validate:
+            type: none
+            fail: 'not found'
+          validate_options: {  }
+          break_phrase: false
+          not: false
+          entity_type: media
+          entity_field: uid
+          plugin_id: numeric
+      defaults:
+        arguments: false
+        access: false
+        style: false
+        row: false
+        fields: false
+        filters: false
+        filter_groups: false
+      access:
+        type: perm
+        options:
+          perm: 'edit own media content'
+      display_description: 'A page on the user profile to display media items by the user.'
+      style:
+        type: default
+        options:
+          grouping: {  }
+          row_class: grid-item-library
+          default_row_class: true
+      row:
+        type: fields
+        options:
+          inline: {  }
+          separator: ''
+          hide_empty: false
+          default_field_elements: true
+      fields:
+        rendered_entity:
+          id: rendered_entity
+          table: media
+          field: rendered_entity
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          view_mode: media_library
+          entity_type: media
+          plugin_id: rendered_entity
+        bundle:
+          id: bundle
+          table: media_field_data
+          field: bundle
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: Type
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: true
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          click_sort_column: target_id
+          type: entity_reference_label
+          settings:
+            link: false
+          group_column: target_id
+          group_columns: {  }
+          group_rows: true
+          delta_limit: 0
+          delta_offset: 0
+          delta_reversed: false
+          delta_first_last: false
+          multi_type: separator
+          separator: ', '
+          field_api_classes: false
+          entity_type: media
+          entity_field: bundle
+          plugin_id: field
+        status:
+          id: status
+          table: media_field_data
+          field: status
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: Status
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: true
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          click_sort_column: value
+          type: boolean
+          settings:
+            format: custom
+            format_custom_true: Published
+            format_custom_false: Unpublished
+          group_column: value
+          group_columns: {  }
+          group_rows: true
+          delta_limit: 0
+          delta_offset: 0
+          delta_reversed: false
+          delta_first_last: false
+          multi_type: separator
+          separator: ', '
+          field_api_classes: false
+          entity_type: media
+          entity_field: status
+          plugin_id: field
+        operations:
+          id: operations
+          table: media
+          field: operations
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          destination: true
+          entity_type: media
+          plugin_id: entity_operations
+      filters:
+        name:
+          id: name
+          table: media_field_data
+          field: name
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: contains
+          value: ''
+          group: 1
+          exposed: true
+          expose:
+            operator_id: name_op
+            label: 'Media name'
+            description: ''
+            use_operator: false
+            operator: name_op
+            identifier: name
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          entity_type: media
+          entity_field: name
+          plugin_id: string
+        bundle:
+          id: bundle
+          table: media_field_data
+          field: bundle
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: in
+          value: {  }
+          group: 1
+          exposed: true
+          expose:
+            operator_id: bundle_op
+            label: Bundle
+            description: ''
+            use_operator: false
+            operator: bundle_op
+            identifier: bundle
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+            reduce: false
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          entity_type: media
+          entity_field: bundle
+          plugin_id: bundle
+        status:
+          id: status
+          table: media_field_data
+          field: status
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: '='
+          value: true
+          group: 1
+          exposed: true
+          expose:
+            operator_id: ''
+            label: 'Publishing status'
+            description: ''
+            use_operator: false
+            operator: status_op
+            identifier: status
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          plugin_id: boolean
+          entity_type: media
+          entity_field: status
+        langcode:
+          id: langcode
+          table: media_field_data
+          field: langcode
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: in
+          value: {  }
+          group: 1
+          exposed: true
+          expose:
+            operator_id: langcode_op
+            label: Language
+            description: ''
+            use_operator: false
+            operator: langcode_op
+            identifier: langcode
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+            reduce: false
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          entity_type: media
+          entity_field: langcode
+          plugin_id: language
+      filter_groups:
+        operator: AND
+        groups:
+          1: AND
+    cache_metadata:
+      max-age: -1
+      contexts:
+        - 'languages:language_content'
+        - 'languages:language_interface'
+        - url
+        - url.query_args
+        - 'url.query_args:sort_by'
+        - 'url.query_args:sort_order'
+        - user.permissions
+      tags:
+        - 'config:core.entity_view_display.media.document.default'
+        - 'config:core.entity_view_display.media.document.media_library'
+        - 'config:core.entity_view_display.media.gallery.default'
+        - 'config:core.entity_view_display.media.gallery.media_library'
+        - 'config:core.entity_view_display.media.image.default'
+        - 'config:core.entity_view_display.media.image.media_library'
+        - 'config:core.entity_view_display.media.instagram.default'
+        - 'config:core.entity_view_display.media.instagram.media_library'
+        - 'config:core.entity_view_display.media.tweet.default'
+        - 'config:core.entity_view_display.media.tweet.media_library'
+        - 'config:core.entity_view_display.media.video.default'
+        - 'config:core.entity_view_display.media.video.media_library'
+  user_media_select_modal:
+    display_plugin: entity_browser
+    id: user_media_select_modal
+    display_title: 'User Media select modal'
+    position: 1
+    display_options:
+      display_extenders: {  }
+      filters:
+        name:
+          id: name
+          table: media_field_data
+          field: name
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: contains
+          value: ''
+          group: 1
+          exposed: true
+          expose:
+            operator_id: name_op
+            label: 'Media name'
+            description: ''
+            use_operator: false
+            operator: name_op
+            identifier: name
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          entity_type: media
+          entity_field: name
+          plugin_id: string
+        bundle:
+          id: bundle
+          table: media_field_data
+          field: bundle
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: in
+          value: {  }
+          group: 1
+          exposed: true
+          expose:
+            operator_id: bundle_op
+            label: Bundle
+            description: ''
+            use_operator: false
+            operator: bundle_op
+            identifier: bundle
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+            reduce: false
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          entity_type: media
+          entity_field: bundle
+          plugin_id: bundle
+        status:
+          id: status
+          table: media_field_data
+          field: status
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: '='
+          value: true
+          group: 1
+          exposed: true
+          expose:
+            operator_id: ''
+            label: 'Publishing status'
+            description: ''
+            use_operator: false
+            operator: status_op
+            identifier: status
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          plugin_id: boolean
+          entity_type: media
+          entity_field: status
+        field_media_in_library_value:
+          id: field_media_in_library_value
+          table: media__field_media_in_library
+          field: field_media_in_library_value
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: '='
+          value: true
+          group: 1
+          exposed: false
+          expose:
+            operator_id: ''
+            label: ''
+            description: ''
+            use_operator: false
+            operator: ''
+            identifier: ''
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          plugin_id: boolean
+      defaults:
+        filters: false
+        filter_groups: false
+        fields: false
+        style: false
+        row: false
+        access: false
+        arguments: false
+        relationships: false
+      filter_groups:
+        operator: AND
+        groups:
+          1: AND
+      fields:
+        entity_browser_select:
+          id: entity_browser_select
+          table: media
+          field: entity_browser_select
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          entity_type: media
+          plugin_id: entity_browser_select
+        rendered_entity:
+          id: rendered_entity
+          table: media
+          field: rendered_entity
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          view_mode: media_library
+          entity_type: media
+          plugin_id: rendered_entity
+      display_description: 'An entity browser modal to select user media'
+      style:
+        type: default
+        options:
+          grouping: {  }
+          row_class: grid-item
+          default_row_class: true
+      row:
+        type: fields
+        options:
+          inline: {  }
+          separator: ''
+          hide_empty: false
+          default_field_elements: true
+      access:
+        type: perm
+        options:
+          perm: 'edit own media content'
+      arguments:
+        uid:
+          id: uid
+          table: media_field_data
+          field: uid
+          relationship: none
+          group_type: group
+          admin_label: ''
+          default_action: default
+          exception:
+            value: all
+            title_enable: false
+            title: All
+          title_enable: false
+          title: ''
+          default_argument_type: current_user
+          default_argument_options: {  }
+          default_argument_skip_url: false
+          summary_options:
+            base_path: ''
+            count: true
+            items_per_page: 25
+            override: false
+          summary:
+            sort_order: asc
+            number_of_records: 0
+            format: default_summary
+          specify_validation: false
+          validate:
+            type: none
+            fail: 'not found'
+          validate_options: {  }
+          break_phrase: false
+          not: false
+          entity_type: media
+          entity_field: uid
+          plugin_id: numeric
+      relationships: {  }
+    cache_metadata:
+      max-age: -1
+      contexts:
+        - 'languages:language_interface'
+        - url
+        - url.query_args
+        - 'url.query_args:sort_by'
+        - 'url.query_args:sort_order'
+        - user
+        - user.permissions
+      tags:
+        - 'config:core.entity_view_display.media.document.default'
+        - 'config:core.entity_view_display.media.document.media_library'
+        - 'config:core.entity_view_display.media.gallery.default'
+        - 'config:core.entity_view_display.media.gallery.media_library'
+        - 'config:core.entity_view_display.media.image.default'
+        - 'config:core.entity_view_display.media.image.media_library'
+        - 'config:core.entity_view_display.media.instagram.default'
+        - 'config:core.entity_view_display.media.instagram.media_library'
+        - 'config:core.entity_view_display.media.tweet.default'
+        - 'config:core.entity_view_display.media.tweet.media_library'
+        - 'config:core.entity_view_display.media.video.default'
+        - 'config:core.entity_view_display.media.video.media_library'
diff --git a/web/modules/contrib/media/css/media.view.css b/web/modules/contrib/media/css/media.view.css
new file mode 100644 (file)
index 0000000..a6a675c
--- /dev/null
@@ -0,0 +1,70 @@
+.view-content, .entities-list{
+    margin: 1.5em 0;
+    padding: 0;
+    -moz-column-gap: 1.5em;
+    -webkit-column-gap: 1.5em;
+    column-gap: 1.5em;
+}
+.grid-item:hover {
+    border: 2px solid #37802f !important;
+    cursor : pointer;
+}
+
+.grid-item.checked {
+    border: 2px solid #37802f !important;
+    background: url("../images/checkmark.svg") no-repeat center center !important;
+}
+.grid-item img, .grid-item-library img {
+    vertical-align: bottom;
+}
+
+.grid-item img::selection , .grid-item-library img::selection{
+    background: transparent;
+}
+
+.views-field-entity-browser-select{
+    display: none;
+}
+
+.grid-item-library, .grid-item, .item-container{
+    display: inline-block;
+    background: #fff;
+    padding: 1.5em;
+    margin: 0 0 1.5em;
+    width: 100%;
+    box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+    box-shadow: 0px 1px 1px 0px rgba(0, 0, 0, 0.18);
+    border-radius: 3px;
+    border: 1px solid #ddd;
+    text-align:center;
+    -moz-border-radius: 3px;
+    -webkit-border-radius: 3px;
+}
+@media only screen and (min-width: 700px) {
+    .view-content, .entities-list{
+        -moz-column-count: 1;
+        -webkit-column-count: 1;
+        column-count: 1;
+    }
+}
+
+@media only screen and (min-width: 900px) {
+    .view-content, .entities-list{
+        -moz-column-count: 2;
+        -webkit-column-count: 2;
+        column-count: 2;
+    }
+}
+
+@media only screen and (min-width: 1100px) {
+    .view-content, .entities-list{
+        -moz-column-count: 4;
+        -webkit-column-count: 4;
+        column-count: 4;
+    }
+}
+#edit-actions{
+    text-align:center;
+}
diff --git a/web/modules/contrib/media/drupal_ti/before/before_script.sh b/web/modules/contrib/media/drupal_ti/before/before_script.sh
new file mode 100644 (file)
index 0000000..fd08817
--- /dev/null
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+# Add an optional statement to see that this is running in Travis CI.
+echo "running drupal_ti/before/before_script.sh"
+
+set -e $DRUPAL_TI_DEBUG
+
+# Ensure the right Drupal version is installed.
+# The first time this is run, it will install Drupal.
+# Note: This function is re-entrant.
+drupal_ti_ensure_drupal
+
+# Change to the Drupal directory
+cd "$DRUPAL_TI_DRUPAL_DIR"
+
+# Create the the module directory (only necessary for D7)
+# For D7, this is sites/default/modules
+# For D8, this is modules
+mkdir -p "$DRUPAL_TI_DRUPAL_DIR/$DRUPAL_TI_LIBRARIES_PATH"
+cd "$DRUPAL_TI_DRUPAL_DIR"
+
+# Manually clone the dependencies
+mkdir libraries
+cd libraries
+git clone --depth 1 https://github.com/enyo/dropzone.git
+
diff --git a/web/modules/contrib/media/files/Test.doc b/web/modules/contrib/media/files/Test.doc
new file mode 100644 (file)
index 0000000..cefae81
Binary files /dev/null and b/web/modules/contrib/media/files/Test.doc differ
diff --git a/web/modules/contrib/media/files/Test.docx b/web/modules/contrib/media/files/Test.docx
new file mode 100644 (file)
index 0000000..2962774
Binary files /dev/null and b/web/modules/contrib/media/files/Test.docx differ
diff --git a/web/modules/contrib/media/files/Test.ods b/web/modules/contrib/media/files/Test.ods
new file mode 100644 (file)
index 0000000..5f406e9
Binary files /dev/null and b/web/modules/contrib/media/files/Test.ods differ
diff --git a/web/modules/contrib/media/files/Test.odt b/web/modules/contrib/media/files/Test.odt
new file mode 100644 (file)
index 0000000..0fc5434
Binary files /dev/null and b/web/modules/contrib/media/files/Test.odt differ
diff --git a/web/modules/contrib/media/files/Test.ott b/web/modules/contrib/media/files/Test.ott
new file mode 100644 (file)
index 0000000..ddfc758
Binary files /dev/null and b/web/modules/contrib/media/files/Test.ott differ
diff --git a/web/modules/contrib/media/files/Test.pdf b/web/modules/contrib/media/files/Test.pdf
new file mode 100644 (file)
index 0000000..83cc3b2
Binary files /dev/null and b/web/modules/contrib/media/files/Test.pdf differ
diff --git a/web/modules/contrib/media/files/Test.ppt b/web/modules/contrib/media/files/Test.ppt
new file mode 100644 (file)
index 0000000..c9517a0
Binary files /dev/null and b/web/modules/contrib/media/files/Test.ppt differ
diff --git a/web/modules/contrib/media/files/Test.pptx b/web/modules/contrib/media/files/Test.pptx
new file mode 100644 (file)
index 0000000..0376e63
Binary files /dev/null and b/web/modules/contrib/media/files/Test.pptx differ
diff --git a/web/modules/contrib/media/files/Test.rtf b/web/modules/contrib/media/files/Test.rtf
new file mode 100644 (file)
index 0000000..fb93201
--- /dev/null
@@ -0,0 +1,34 @@
+{\rtf1\ansi\ansicpg1252\uc0\stshfdbch0\stshfloch0\stshfhich0\stshfbi0\deff0\adeff0{\fonttbl{\f0\froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f1\froman\fcharset2\fprq2{\*\panose 05050102010706020507}Symbol;}{\f2\fswiss\fcharset0\fprq2{\*\panose 020b0604020202020204}Arial;}}{\colortbl;\red0\green0\blue0;\red67\green67\blue67\r
+;\red102\green102\blue102;}{\stylesheet{\s0\snext0\sqformat\spriority0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1 Normal;}{\s1\sbasedon0\snext0\styrsid15694742\r
+\sqformat\spriority0\keep\keepn\fi0\sb400\sa120\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs40\ltrch\b0\i0\fs40\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1 heading 1;}{\s2\sbasedon0\snext0\styrsid15694742\r
+\sqformat\spriority0\keep\keepn\fi0\sb360\sa120\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs32\ltrch\b0\i0\fs32\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1 heading 2;}{\s3\sbasedon0\snext0\styrsid15694742\r
+\sqformat\spriority0\keep\keepn\fi0\sb320\sa80\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs28\ltrch\b0\i0\fs28\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf2 heading 3;}{\s4\sbasedon0\snext0\styrsid15694742\r
+\sqformat\spriority0\keep\keepn\fi0\sb280\sa80\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs24\ltrch\b0\i0\fs24\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf3 heading 4;}{\s5\sbasedon0\snext0\styrsid15694742\r
+\sqformat\spriority0\keep\keepn\fi0\sb240\sa80\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf3 heading 5;}{\s6\sbasedon0\snext0\styrsid15694742\r
+\sqformat\spriority0\keep\keepn\fi0\sb240\sa80\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai\af2\afs22\ltrch\b0\i\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf3 heading 6;}{\*\cs10\additive\ssemihidden\spriority0 Default Paragraph Font\r
+;}{\s15\sbasedon0\snext15\styrsid15694742\sqformat\spriority0\keep\keepn\fi0\sb0\sa60\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs52\ltrch\b0\i0\fs52\loch\af2\dbch\af2\hich\f2\strike0\r
+\ulnone\cf1 Title;}{\s16\sbasedon0\snext16\styrsid15694742\sqformat\spriority0\keep\keepn\fi0\sb0\sa320\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs30\ltrch\b0\i0\fs30\loch\af2\dbch\af2\hich\f2\strike0\r
+\ulnone\cf3 Subtitle;}}{\*\rsidtbl\rsid10976062}{\*\generator Aspose.Words for Java 13.10.0.0;}{\info\version1\edmins0\nofpages1\nofwords0\nofchars0\nofcharsws0}{\mmathPr\mbrkBin0\mbrkBinSub0\mdefJc1\mdispDef1\minterSp0\mintLim0\mintraSp0\mlMargin0\mmathFont0\mnaryLim1\mpostSp0\mpreSp0\mrMargin0\msmallFrac0\mwrapIndent1440\mwrapRight0}\r
+\deflang1033\deflangfe2052\adeflang1025\jexpand\showxmlerrors1\validatexml1{\*\wgrffmtfilter 013f}\viewkind1\viewscale100\fet0\ftnbj\aenddoc\ftnrstcont\aftnrstcont\ftnnar\aftnnrlc\widowctrl\nospaceforul\nolnhtadjtbl\alntblind\lyttblrtgr\dntblnsbdb\noxlattoyen\r
+\wrppunct\nobrkwrptbl\expshrtn\snaptogridincell\asianbrkrule\htmautsp\noultrlspc\useltbaln\splytwnine\ftnlytwnine\lytcalctblwd\allowfieldendsel\lnbrkrule\nouicompat\nofeaturethrottle1\formshade\nojkernpunct\dghspace180\dgvspace180\dghorigin1800\dgvorigin1440\dghshow1\dgvshow1\r
+\dgmargin\pgbrdrhead\pgbrdrfoot\sectd\sectlinegrid360\pgwsxn11909\pghsxn16834\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\guttersxn0\headery708\footery708\colsx708\ltrsect\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\r
+\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1 Test}{\rtlch\ab0\ai0\af2\afs22\r
+\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}{\*\latentstyles\lsdstimax267\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef0{\lsdlockedexcept\lsdqformat1 Normal;\lsdqformat1 heading 1;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 2;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 3\r
+;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 4;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 5;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 6;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 7;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 8\r
+;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 9;\lsdsemihidden1\lsdunhideused1\lsdqformat1 caption;\lsdqformat1 Title;\lsdqformat1 Subtitle;\lsdqformat1 Strong;\lsdqformat1 Emphasis;\lsdsemihidden1\lsdpriority99 Placeholder Text;\lsdqformat1\lsdpriority1 No Spacing\r
+;\lsdpriority60 Light Shading;\lsdpriority61 Light List;\lsdpriority62 Light Grid;\lsdpriority63 Medium Shading 1;\lsdpriority64 Medium Shading 2;\lsdpriority65 Medium List 1;\lsdpriority66 Medium List 2;\lsdpriority67 Medium Grid 1;\lsdpriority68 Medium Grid 2\r
+;\lsdpriority69 Medium Grid 3;\lsdpriority70 Dark List;\lsdpriority71 Colorful Shading;\lsdpriority72 Colorful List;\lsdpriority73 Colorful Grid;\lsdpriority60 Light Shading Accent 1;\lsdpriority61 Light List Accent 1;\lsdpriority62 Light Grid Accent 1;\lsdpriority63 Medium Shading 1 Accent 1\r
+;\lsdpriority64 Medium Shading 2 Accent 1;\lsdpriority65 Medium List 1 Accent 1;\lsdsemihidden1\lsdpriority99 Revision;\lsdqformat1\lsdpriority34 List Paragraph;\lsdqformat1\lsdpriority29 Quote;\lsdqformat1\lsdpriority30 Intense Quote;\lsdpriority66 Medium List 2 Accent 1\r
+;\lsdpriority67 Medium Grid 1 Accent 1;\lsdpriority68 Medium Grid 2 Accent 1;\lsdpriority69 Medium Grid 3 Accent 1;\lsdpriority70 Dark List Accent 1;\lsdpriority71 Colorful Shading Accent 1;\lsdpriority72 Colorful List Accent 1;\lsdpriority73 Colorful Grid Accent 1\r
+;\lsdpriority60 Light Shading Accent 2;\lsdpriority61 Light List Accent 2;\lsdpriority62 Light Grid Accent 2;\lsdpriority63 Medium Shading 1 Accent 2;\lsdpriority64 Medium Shading 2 Accent 2;\lsdpriority65 Medium List 1 Accent 2;\lsdpriority66 Medium List 2 Accent 2\r
+;\lsdpriority67 Medium Grid 1 Accent 2;\lsdpriority68 Medium Grid 2 Accent 2;\lsdpriority69 Medium Grid 3 Accent 2;\lsdpriority70 Dark List Accent 2;\lsdpriority71 Colorful Shading Accent 2;\lsdpriority72 Colorful List Accent 2;\lsdpriority73 Colorful Grid Accent 2\r
+;\lsdpriority60 Light Shading Accent 3;\lsdpriority61 Light List Accent 3;\lsdpriority62 Light Grid Accent 3;\lsdpriority63 Medium Shading 1 Accent 3;\lsdpriority64 Medium Shading 2 Accent 3;\lsdpriority65 Medium List 1 Accent 3;\lsdpriority66 Medium List 2 Accent 3\r
+;\lsdpriority67 Medium Grid 1 Accent 3;\lsdpriority68 Medium Grid 2 Accent 3;\lsdpriority69 Medium Grid 3 Accent 3;\lsdpriority70 Dark List Accent 3;\lsdpriority71 Colorful Shading Accent 3;\lsdpriority72 Colorful List Accent 3;\lsdpriority73 Colorful Grid Accent 3\r
+;\lsdpriority60 Light Shading Accent 4;\lsdpriority61 Light List Accent 4;\lsdpriority62 Light Grid Accent 4;\lsdpriority63 Medium Shading 1 Accent 4;\lsdpriority64 Medium Shading 2 Accent 4;\lsdpriority65 Medium List 1 Accent 4;\lsdpriority66 Medium List 2 Accent 4\r
+;\lsdpriority67 Medium Grid 1 Accent 4;\lsdpriority68 Medium Grid 2 Accent 4;\lsdpriority69 Medium Grid 3 Accent 4;\lsdpriority70 Dark List Accent 4;\lsdpriority71 Colorful Shading Accent 4;\lsdpriority72 Colorful List Accent 4;\lsdpriority73 Colorful Grid Accent 4\r
+;\lsdpriority60 Light Shading Accent 5;\lsdpriority61 Light List Accent 5;\lsdpriority62 Light Grid Accent 5;\lsdpriority63 Medium Shading 1 Accent 5;\lsdpriority64 Medium Shading 2 Accent 5;\lsdpriority65 Medium List 1 Accent 5;\lsdpriority66 Medium List 2 Accent 5\r
+;\lsdpriority67 Medium Grid 1 Accent 5;\lsdpriority68 Medium Grid 2 Accent 5;\lsdpriority69 Medium Grid 3 Accent 5;\lsdpriority70 Dark List Accent 5;\lsdpriority71 Colorful Shading Accent 5;\lsdpriority72 Colorful List Accent 5;\lsdpriority73 Colorful Grid Accent 5\r
+;\lsdpriority60 Light Shading Accent 6;\lsdpriority61 Light List Accent 6;\lsdpriority62 Light Grid Accent 6;\lsdpriority63 Medium Shading 1 Accent 6;\lsdpriority64 Medium Shading 2 Accent 6;\lsdpriority65 Medium List 1 Accent 6;\lsdpriority66 Medium List 2 Accent 6\r
+;\lsdpriority67 Medium Grid 1 Accent 6;\lsdpriority68 Medium Grid 2 Accent 6;\lsdpriority69 Medium Grid 3 Accent 6;\lsdpriority70 Dark List Accent 6;\lsdpriority71 Colorful Shading Accent 6;\lsdpriority72 Colorful List Accent 6;\lsdpriority73 Colorful Grid Accent 6\r
+;\lsdqformat1\lsdpriority19 Subtle Emphasis;\lsdqformat1\lsdpriority21 Intense Emphasis;\lsdqformat1\lsdpriority31 Subtle Reference;\lsdqformat1\lsdpriority32 Intense Reference;\lsdqformat1\lsdpriority33 Book Title;\lsdsemihidden1\lsdunhideused1\lsdpriority37 Bibliography\r
+;\lsdsemihidden1\lsdunhideused1\lsdqformat1\lsdpriority39 TOC Heading;}}}
\ No newline at end of file
diff --git a/web/modules/contrib/media/files/Test.txt b/web/modules/contrib/media/files/Test.txt
new file mode 100644 (file)
index 0000000..6d5e026
--- /dev/null
@@ -0,0 +1 @@
+Test
\ No newline at end of file
diff --git a/web/modules/contrib/media/files/Test.xls b/web/modules/contrib/media/files/Test.xls
new file mode 100644 (file)
index 0000000..b4eb676
Binary files /dev/null and b/web/modules/contrib/media/files/Test.xls differ
diff --git a/web/modules/contrib/media/files/Test.xlsx b/web/modules/contrib/media/files/Test.xlsx
new file mode 100644 (file)
index 0000000..cc845db
Binary files /dev/null and b/web/modules/contrib/media/files/Test.xlsx differ
diff --git a/web/modules/contrib/media/images/checkmark.svg b/web/modules/contrib/media/images/checkmark.svg
new file mode 100755 (executable)
index 0000000..422f215
--- /dev/null
@@ -0,0 +1,16 @@
+<svg width="105" height="105" xmlns="http://www.w3.org/2000/svg">
+ <g>
+  <title>Layer 1</title>
+  <circle filter="url(#svg_3_blur)" r="51.5" cy="52.9" cx="52.55" fill="#000000" id="svg_3" fill-opacity="0.1"/>
+  <circle id="svg_1" r="50" cy="51" cx="52.5" fill="#FFFFFF" fill-opacity="0.7"/>
+  <polygon id="svg_2" points="18.963001251220703,50.50800323486328 29.856000900268555,39.61499786376953 45.177001953125,54.93199920654297 75.13899993896484,24.972000122070312 86.13700103759766,35.974002838134766 45.285003662109375,76.8280029296875 " fill-opacity="0.7" fill="black"/>
+ </g>
+ <defs>
+  <filter id="svg_3_blur" x="-50%" y="-50%" width="200%" height="200%">
+   <feGaussianBlur stdDeviation="1.8"/>
+  </filter>
+ </defs>
+ <filter id="drop-shadow">
+  <feGaussianBlur stdDeviation="2.2"/>
+ </filter>
+</svg>
diff --git a/web/modules/contrib/media/images/icons/application-msword.png b/web/modules/contrib/media/images/icons/application-msword.png
new file mode 100644 (file)
index 0000000..cdfcf6c
Binary files /dev/null and b/web/modules/contrib/media/images/icons/application-msword.png differ
diff --git a/web/modules/contrib/media/images/icons/application-pdf.png b/web/modules/contrib/media/images/icons/application-pdf.png
new file mode 100644 (file)
index 0000000..0f8a3e1
Binary files /dev/null and b/web/modules/contrib/media/images/icons/application-pdf.png differ
diff --git a/web/modules/contrib/media/images/icons/application-rtf.png b/web/modules/contrib/media/images/icons/application-rtf.png
new file mode 100644 (file)
index 0000000..b735802
Binary files /dev/null and b/web/modules/contrib/media/images/icons/application-rtf.png differ
diff --git a/web/modules/contrib/media/images/icons/application-vnd.ms-excel.png b/web/modules/contrib/media/images/icons/application-vnd.ms-excel.png
new file mode 100644 (file)
index 0000000..67c7708
Binary files /dev/null and b/web/modules/contrib/media/images/icons/application-vnd.ms-excel.png differ
diff --git a/web/modules/contrib/media/images/icons/application-vnd.ms-powerpoint.png b/web/modules/contrib/media/images/icons/application-vnd.ms-powerpoint.png
new file mode 100644 (file)
index 0000000..ef39540
Binary files /dev/null and b/web/modules/contrib/media/images/icons/application-vnd.ms-powerpoint.png differ
diff --git a/web/modules/contrib/media/images/icons/application-vnd.oasis.opendocument.spreadsheet.png b/web/modules/contrib/media/images/icons/application-vnd.oasis.opendocument.spreadsheet.png
new file mode 100644 (file)
index 0000000..5f09b0c
Binary files /dev/null and b/web/modules/contrib/media/images/icons/application-vnd.oasis.opendocument.spreadsheet.png differ
diff --git a/web/modules/contrib/media/images/icons/application-vnd.oasis.opendocument.text-template.png b/web/modules/contrib/media/images/icons/application-vnd.oasis.opendocument.text-template.png
new file mode 100644 (file)
index 0000000..18dd9fa
Binary files /dev/null and b/web/modules/contrib/media/images/icons/application-vnd.oasis.opendocument.text-template.png differ
diff --git a/web/modules/contrib/media/images/icons/application-vnd.oasis.opendocument.text.png b/web/modules/contrib/media/images/icons/application-vnd.oasis.opendocument.text.png
new file mode 100644 (file)
index 0000000..607bae5
Binary files /dev/null and b/web/modules/contrib/media/images/icons/application-vnd.oasis.opendocument.text.png differ
diff --git a/web/modules/contrib/media/images/icons/application-vnd.openxmlformats-officedocument.presentationml.presentation.png b/web/modules/contrib/media/images/icons/application-vnd.openxmlformats-officedocument.presentationml.presentation.png
new file mode 100644 (file)
index 0000000..a34df1f
Binary files /dev/null and b/web/modules/contrib/media/images/icons/application-vnd.openxmlformats-officedocument.presentationml.presentation.png differ
diff --git a/web/modules/contrib/media/images/icons/application-vnd.openxmlformats-officedocument.spreadsheetml.sheet.png b/web/modules/contrib/media/images/icons/application-vnd.openxmlformats-officedocument.spreadsheetml.sheet.png
new file mode 100644 (file)
index 0000000..4e83448
Binary files /dev/null and b/web/modules/contrib/media/images/icons/application-vnd.openxmlformats-officedocument.spreadsheetml.sheet.png differ
diff --git a/web/modules/contrib/media/images/icons/application-vnd.openxmlformats-officedocument.wordprocessingml.document.png b/web/modules/contrib/media/images/icons/application-vnd.openxmlformats-officedocument.wordprocessingml.document.png
new file mode 100644 (file)
index 0000000..053d449
Binary files /dev/null and b/web/modules/contrib/media/images/icons/application-vnd.openxmlformats-officedocument.wordprocessingml.document.png differ
diff --git a/web/modules/contrib/media/images/icons/text-plain.png b/web/modules/contrib/media/images/icons/text-plain.png
new file mode 100644 (file)
index 0000000..26909be
Binary files /dev/null and b/web/modules/contrib/media/images/icons/text-plain.png differ
diff --git a/web/modules/contrib/media/images/media_embed_icon.png b/web/modules/contrib/media/images/media_embed_icon.png
new file mode 100644 (file)
index 0000000..1836eaf
Binary files /dev/null and b/web/modules/contrib/media/images/media_embed_icon.png differ
diff --git a/web/modules/contrib/media/js/media.view.js b/web/modules/contrib/media/js/media.view.js
new file mode 100644 (file)
index 0000000..283133a
--- /dev/null
@@ -0,0 +1,32 @@
+/**
+ * @file media.view.js
+ */
+(function ($, Drupal) {
+
+  "use strict";
+
+  /**
+   * Registers behaviours related to view widget.
+   */
+
+  Drupal.behaviors.MediaLibraryView = {
+    attach: function (context, settings) {
+      $('.item-container').css("display", "inline-block");
+      $('.grid-item').once('bind-click-event').click(function () {
+        var input = $(this).find('.views-field-entity-browser-select input');
+        input.prop('checked', !input.prop('checked'));
+        if (input.prop('checked')) {
+          $(this).addClass('checked');
+          var render = $(this).find('.views-field-rendered-entity');
+          $(render).css('opacity',0.3);
+        }
+        else {
+          $(this).removeClass('checked');
+          var render = $(this).find('.views-field-rendered-entity');
+          $(render).css('opacity',1);
+        }
+      });
+    }
+  };
+
+}(jQuery, Drupal));
diff --git a/web/modules/contrib/media/media.info.yml b/web/modules/contrib/media/media.info.yml
new file mode 100644 (file)
index 0000000..96da6fa
--- /dev/null
@@ -0,0 +1,42 @@
+name: Media
+description: 'Media module for Drupal 8'
+type: module
+package: Media
+core: 8.x
+dependencies:
+  - media_entity:media_entity
+  - media_entity_image:media_entity_image
+  - video_embed_field:video_embed_field
+  - video_embed_field:video_embed_media
+  - media_entity_slideshow:media_entity_slideshow
+  - media_entity_instagram:media_entity_instagram
+  - media_entity_twitter:media_entity_twitter
+  - media_entity_document:media_entity_document
+  - slick_media:slick_media
+  - entity_browser:entity_browser
+  - entity_browser:entity_browser_entity_form
+  - entity_embed:entity_embed
+  - dropzonejs:dropzonejs_eb_widget
+  - image_widget_crop:image_widget_crop
+  - drupal:link
+  - drupal:editor
+  - inline_entity_form:inline_entity_form
+test_dependencies:
+  - media_entity:media_entity
+  - media_entity_image:media_entity_image
+  - video_embed_field:video_embed_field
+  - video_embed_field:video_embed_media
+  - media_entity_slideshow:media_entity_slideshow
+  - media_entity_instagram:media_entity_instagram
+  - media_entity_twitter:media_entity_twitter
+  - media_entity_document:media_entity_document
+  - slick_media:slick_media
+  - entity_browser:entity_browser
+  - entity_browser:entity_browser_entity_form
+  - entity_embed:entity_embed
+  - dropzonejs:dropzonejs_eb_widget
+  - image_widget_crop:image_widget_crop
+  - drupal:link
+  - drupal:editor
+  - inline_entity_form:inline_entity_form
+
diff --git a/web/modules/contrib/media/media.install b/web/modules/contrib/media/media.install
new file mode 100644 (file)
index 0000000..e9fc18e
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Defines library requirements for Media module.
+ */
+
+use Drupal\views\Entity\View;
+use \Drupal\Core\Config\ExtensionInstallStorage;
+use Drupal\editor\Entity\Editor;
+
+/**
+ * Implements hook_install().
+ */
+function media_install() {
+  // Disable the media view provided by media_entity.
+  if ($view = View::load('media')) {
+    $view->set('status', FALSE);
+    $view->save();
+  }
+
+  // Copy the document icon files.
+  $source = drupal_get_path('module', 'media') . '/images/icons';
+  $destination = \Drupal::config('media_entity.settings')->get('icon_base');
+  media_entity_copy_icons($source, $destination);
+
+  if (!\Drupal::isConfigSyncing()) {
+    // Add an icon for Media if we aren't installing from configuration.
+    // Read more about this implementation: https://www.drupal.org/node/2696593
+    $data = file_get_contents(dirname(__FILE__) . '/images/media_embed_icon.png');
+    $file = file_save_data($data, 'public://media_embed_icon.png', FILE_EXISTS_REPLACE);
+    // Set file uuid same as default config.
+    $uuid = (new ExtensionInstallStorage(\Drupal::service('config.storage')))->read('embed.button.media')['icon_uuid'];
+    $file->set('uuid', $uuid);
+    $file->save();
+  }
+
+  // Enable the media embed button and modify filters.
+  $filter_formats = \Drupal::entityTypeManager()->getStorage('filter_format')->loadByProperties(array('status' => TRUE));
+  foreach ($filter_formats as $filter_format) {
+    $editor = Editor::load($filter_format->getOriginalId());
+    if ($editor) {
+      // Make the changes to editor and filters only if editor type exists.
+      $editor_settings = $editor->getSettings();
+      $editor_settings['toolbar']['rows'][0][3]['items'][] = 'media';
+      $editor->setSettings($editor_settings);
+      $editor->save();
+      $format = $editor->getFilterFormat();
+      if ($format->filters('filter_html')->settings['allowed_html']) {
+        $format->filters('filter_html')->settings['allowed_html'] .= '<drupal-entity data-entity-type data-entity-uuid data-view-mode data-entity-embed-display data-entity-embed-display-settings data-align data-caption data-embed-button>';
+      }
+      $format->setFilterConfig('entity_embed', ['status' => 1]);
+      $format->setFilterConfig('filter_html_image_secure', ['status' => 0]);
+      $format->save();
+    }
+  }
+}
diff --git a/web/modules/contrib/media/media.libraries.yml b/web/modules/contrib/media/media.libraries.yml
new file mode 100644 (file)
index 0000000..9eab378
--- /dev/null
@@ -0,0 +1,10 @@
+view:
+  version: VERSION
+  js:
+    js/media.view.js: {}
+  css:
+    theme:
+      css/media.view.css: {}
+  dependencies:
+    - core/jquery
+    - core/drupal
diff --git a/web/modules/contrib/media/media.module b/web/modules/contrib/media/media.module
new file mode 100644 (file)
index 0000000..66e50ce
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * @file
+ * Contains media.module.
+ */
+
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\views\ViewExecutable;
+
+/**
+ * Implements hook_help().
+ */
+function media_help($route_name, RouteMatchInterface $route_match) {
+  switch ($route_name) {
+    // Main module help for the media module.
+    case 'help.page.media':
+      $output = '';
+      $output .= '<h3>' . t('About') . '</h3>';
+      $output .= '<p>' . t('Media module for Drupal 8') . '</p>';
+      return $output;
+
+    default:
+  }
+}
+
+/**
+ * Implements hook_form_alter().
+ */
+function media_form_alter(&$form, FormStateInterface &$form_state) {
+  if ($form['#form_id'] == 'entity_browser_media_library_form') {
+    // Style the submit button.
+    $form['actions']['submit']['#attributes']['class'][] = 'button--primary';
+    $form['actions']['submit']['#attributes']['class'][] = 'entity-browser-modal-target';
+  }
+  // Remove the Gallery option from create gallery view's bundle filters.
+  if ($form['#id'] == 'views-exposed-form-media-library-gallery-media-select-modal' || $form['#id'] == 'views-exposed-form-media-library-gallery-user-media-select-modal') {
+    unset($form['bundle_1']['#options']['gallery']);
+  }
+  if ($form['#form_id'] == 'media_gallery_form') {
+    $form['#attached']['library'][] = 'media/view';
+  }
+}
+
+/**
+ * Implements hook_views_pre_render().
+ *
+ * Adds the media.view library to the media views.
+ */
+function media_views_pre_render(ViewExecutable $view) {
+  if (isset($view) && ($view->storage->id() == 'media_library' || $view->storage->id() == 'global_media_library')) {
+    $view->element['#attached']['library'][] = 'media/view';
+  }
+}
+
+/**
+ * Implements hook_menu_local_actions_alter().
+ *
+ * Adds the add media button from media_entity on library pages.
+ */
+function media_menu_local_actions_alter(&$local_actions) {
+  $local_actions['media.add']['appears_on'][] = 'view.media_library.user_media_library';
+  $local_actions['media.add']['appears_on'][] = 'view.media_library.global_media_library_page';
+}
+
+/**
+ * Implements hook_entity_type_alter().
+ */
+function media_entity_type_alter(array &$entity_types) {
+  $field_name = \Drupal::config('media_entity.bundle.gallery')->get('type_configuration.source_field');
+  $entity_types['media']->addConstraint('GalleryMediaBundle', array('sourceFieldName' => $field_name));
+}
+
+/**
+ * Implements hook_menu_links_discovered_alter().
+ */
+function media_menu_links_discovered_alter(&$links) {
+  // Media entity module provides a default view which we disable. Since it also
+  // provides a link entry for it we need to update the route there to point to
+  // the media library we provide.
+  $links['entity.media.collection']['route_name'] = 'view.media_library.global_media_library_page';
+}
diff --git a/web/modules/contrib/media/src/Plugin/Validation/Constraint/GalleryMediaBundleConstraint.php b/web/modules/contrib/media/src/Plugin/Validation/Constraint/GalleryMediaBundleConstraint.php
new file mode 100644 (file)
index 0000000..178d175
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+namespace Drupal\media\Plugin\Validation\Constraint;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * Check that there is no Gallery type item in slideshow items.
+ *
+ * @Constraint(
+ *   id = "GalleryMediaBundle",
+ *   label = @Translation("Gallery media bundle", context = "Validation"),
+ * )
+ */
+class GalleryMediaBundleConstraint extends Constraint {
+
+  /**
+   * Name of the source field for slideshow bundle.
+   *
+   * @var string
+   */
+  public $sourceFieldName;
+
+  /**
+   * The default violation message.
+   *
+   * @var string
+   */
+  public $message = 'Gallery cannot contain gallery type item.';
+}
diff --git a/web/modules/contrib/media/src/Plugin/Validation/Constraint/GalleryMediaBundleConstraintValidator.php b/web/modules/contrib/media/src/Plugin/Validation/Constraint/GalleryMediaBundleConstraintValidator.php
new file mode 100644 (file)
index 0000000..3bfb0fc
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+namespace Drupal\media\Plugin\Validation\Constraint;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+
+/**
+ * Validates the GalleryMediaBundle constraint.
+ */
+class GalleryMediaBundleConstraintValidator extends ConstraintValidator {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate($value, Constraint $constraint) {
+    if (!isset($value)) {
+      return;
+    }
+    if ($value->hasField($constraint->sourceFieldName)) {
+      $slideshowItems = $value->get($constraint->sourceFieldName);
+      foreach ($slideshowItems as $item) {
+        if ($item->entity->getType()->getPluginId() == "slideshow") {
+          $this->context->addViolation($constraint->message);
+        }
+      }
+    }
+  }
+
+}
diff --git a/web/modules/contrib/media/src/Tests/DocumentBundleTest.php b/web/modules/contrib/media/src/Tests/DocumentBundleTest.php
new file mode 100644 (file)
index 0000000..33e6411
--- /dev/null
@@ -0,0 +1,136 @@
+<?php
+
+namespace Drupal\media\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Ensures that media bundle for document can be created.
+ *
+ * @group media
+ */
+class DocumentBundleTest extends WebTestBase {
+  /**
+   * Exempt from strict schema checking.
+   *
+   * @see \Drupal\Core\Config\Testing\ConfigSchemaChecker
+   *
+   * @var bool
+   */
+  protected $strictConfigSchema = FALSE;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = [
+    'media',
+    'media_entity',
+    'media_entity_document',
+    'node',
+    'editor',
+  ];
+
+  /**
+   * The test media bundle.
+   *
+   * @var \Drupal\media_entity\MediaBundleInterface
+   */
+  protected $testBundle;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->testBundle = $this->container->get('entity_type.manager')->getStorage('media_bundle')->load('document');
+
+    $adminUser = $this->drupalCreateUser([
+      'view media',
+      'create media',
+      'update media',
+      'update any media',
+      'delete media',
+      'delete any media',
+      'access media overview',
+    ]);
+    $this->drupalLogin($adminUser);
+  }
+
+  /**
+   * Tests document media bundle creation from config files.
+   */
+  public function testMediaBundleCreationFromModule() {
+    $type_configuration = [
+      'source_field' => 'field_document',
+    ];
+    $field_map = [
+      'mime' => 'field_mime_type',
+      'size' => 'field_document_size',
+    ];
+
+    $this->assertTrue((bool) $this->testBundle, 'The media bundle from default configuration has been created in the database.');
+    $this->assertEqual($this->testBundle->get('label'), 'Document', 'Correct label detected.');
+    $this->assertEqual($this->testBundle->get('description'), 'Use Document for uploading document files such as PDF.', 'Correct description detected.');
+    $this->assertEqual($this->testBundle->get('type'), 'document', 'Correct plugin ID detected.');
+    $this->assertEqual($this->testBundle->get('type_configuration'), $type_configuration, 'Type configuration correct.');
+    $this->assertEqual($this->testBundle->get('field_map'), $field_map, 'Correct field map detected.');
+  }
+
+  /**
+   * Tests thumbnails of the document items.
+   */
+  public function testDocumentItemThumbnail() {
+    // Array of test files and corresponding file icons.
+    $files = [
+      'Test.pdf' => 'public://media-icons/generic/application-pdf.png',
+      'Test.doc' => 'public://media-icons/generic/application-msword.png',
+      'Test.docx' => 'public://media-icons/generic/application-vnd.openxmlformats-officedocument.wordprocessingml.document.png',
+      'Test.ods' => 'public://media-icons/generic/application-vnd.oasis.opendocument.spreadsheet.png',
+      'Test.odt' => 'public://media-icons/generic/application-vnd.oasis.opendocument.text.png',
+      'Test.ott' => 'public://media-icons/generic/application-vnd.oasis.opendocument.text-template.png',
+      'Test.ppt' => 'public://media-icons/generic/application-vnd.ms-powerpoint.png',
+      'Test.pptx' => 'public://media-icons/generic/application-vnd.openxmlformats-officedocument.presentationml.presentation.png',
+      'Test.rtf' => 'public://media-icons/generic/application-rtf.png',
+      'Test.txt' => 'public://media-icons/generic/text-plain.png',
+      'Test.xls' => 'public://media-icons/generic/application-vnd.ms-excel.png',
+      'Test.xlsx' => 'public://media-icons/generic/application-vnd.openxmlformats-officedocument.spreadsheetml.sheet.png',
+    ];
+
+    foreach ($files as $fileName => $thumbnail) {
+      $file = drupal_get_path('module', 'media') . '/files/' . $fileName;
+      $name = $this->randomMachineName();
+      $this->drupalGet('media/add/document');
+      $edit = [
+        'files[field_document_0]' => $file,
+      ];
+      $this->drupalPostAjaxForm(NULL, $edit, "field_document_0_upload_button");
+      $fid = (string) current($this->xpath('//input[@data-drupal-selector="edit-field-document-0-fids"]/@value'));
+      $edit = [
+        'name[0][value]' => $name,
+        'form_id' => 'media_document_form',
+        'field_document[0][fids]' => $fid,
+        'field_document[0][display]' => 1,
+      ];
+      $this->drupalPostForm(NULL, $edit, t('Save and publish'));
+      $recentThumbnail = $this->getMostRecentThumbnail();
+      $this->assertEqual($thumbnail, $recentThumbnail, "Correct thumbnail detected for " . $fileName);
+    }
+  }
+
+  /**
+   * Returns the thumbnail of the most recent document.
+   *
+   * @return string
+   *   Path of the thumbnail.
+   */
+  public function getMostRecentThumbnail() {
+    $document_id = $this->container->get('entity.query')->get('media')->condition('bundle', 'document')->sort('created', 'DESC')->execute();
+    $item = $this->container->get('entity_type.manager')
+      ->getStorage('media')
+      ->loadUnchanged(reset($document_id));
+    return $item->getType()->thumbnail($item);
+  }
+
+}
diff --git a/web/modules/contrib/media/src/Tests/GalleryBundleTest.php b/web/modules/contrib/media/src/Tests/GalleryBundleTest.php
new file mode 100644 (file)
index 0000000..60984ed
--- /dev/null
@@ -0,0 +1,185 @@
+<?php
+
+namespace Drupal\media\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Ensures that media bundle for gallery can be created.
+ *
+ * @group media
+ */
+class GalleryBundleTest extends WebTestBase {
+  /**
+   * Exempt from strict schema checking.
+   *
+   * @see \Drupal\Core\Config\Testing\ConfigSchemaChecker
+   *
+   * @var bool
+   */
+  protected $strictConfigSchema = FALSE;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = [
+    'media',
+    'media_entity',
+    'media_entity_slideshow',
+    'node',
+    'editor',
+  ];
+
+  /**
+   * The test media bundle.
+   *
+   * @var \Drupal\media_entity\MediaBundleInterface
+   */
+  protected $testBundle;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->testBundle = $this->container->get('entity_type.manager')->getStorage('media_bundle')->load('gallery');
+
+    $adminUser = $this->drupalCreateUser([
+      'view media',
+      'create media',
+      'update media',
+      'update any media',
+      'delete media',
+      'delete any media',
+      'access media overview',
+      'access gallery_media_library entity browser pages',
+    ]);
+    $this->drupalLogin($adminUser);
+  }
+
+  /**
+   * Tests gallery media bundle creation from config files.
+   */
+  public function testMediaBundleCreationFromModule() {
+    $type_configuration = [
+      'source_field' => 'field_slide',
+    ];
+
+    $this->assertTrue((bool) $this->testBundle, 'The media bundle from default configuration has been created in the database.');
+    $this->assertEqual($this->testBundle->get('label'), 'Gallery', 'Correct label detected.');
+    $this->assertEqual($this->testBundle->get('description'), 'Use Gallery for creating a collection of different media items.', 'Correct description detected.');
+    $this->assertEqual($this->testBundle->get('type'), 'slideshow', 'Correct plugin ID detected.');
+    $this->assertEqual($this->testBundle->get('type_configuration'), $type_configuration, 'Type configuration correct.');
+    $this->assertEqual($this->testBundle->get('field_map'), [], 'Correct field map detected.');
+  }
+
+  /**
+   * Tests thumbnail of the gallery item.
+   */
+  public function testGalleryItemThumbnail() {
+    // Let's add one image and one video.
+    $imageItem = $this->addImageItem();
+    $videoItem = $this->addVideoItem();
+    $this->drupalGet('media/add/gallery');
+    $pathValue = (string) current($this->xpath('//input[@data-drupal-selector="edit-field-slide-entity-browser-entity-browser-path"]/@value'));
+    $edit = [
+      'name[0][value]' => 'Gallery item',
+      'field_slide[target_id]' => 'media:' . $imageItem['id'] . ' media:' . $videoItem['id'],
+      'field_slide[entity_browser][entity_browser][path]' => $pathValue,
+    ];
+    $this->drupalPostForm(NULL, $edit, t('Save and publish'));
+
+    // Let's load all the media items.
+    $gallery_id = $this->container->get('entity.query')->get('media')->condition('bundle', 'gallery')->sort('created', 'DESC')->execute();
+    $gallery = $this->loadMediaItem(reset($gallery_id));
+    $image = $this->loadMediaItem($imageItem['id']);
+    $video = $this->loadMediaItem($videoItem['id']);
+    // Let's check thumbnail now.
+    $gallery_thumbnail = $gallery->getType()->thumbnail($gallery);
+    $image_thumbnail = $image->getType()->thumbnail($image);
+    $video_thumbnail = $video->getType()->thumbnail($video);
+    $this->assertEqual($gallery_thumbnail, $image_thumbnail, "Correct thumbnail detected.");
+
+    $this->drupalGet('media/add/gallery');
+    $edit = [
+      'name[0][value]' => 'Gallery item 2',
+      'field_slide[target_id]' => 'media:' . $videoItem['id'] . ' media:' . $imageItem['id'],
+      'field_slide[entity_browser][entity_browser][path]' => $pathValue,
+    ];
+    $this->drupalPostForm(NULL, $edit, t('Save and publish'));
+
+    // Let's check the thumbnail again.
+    $gallery_id = $this->container->get('entity.query')->get('media')->condition('bundle', 'gallery')->sort('created', 'DESC')->execute();
+    $gallery = $this->loadMediaItem(reset($gallery_id));
+    $gallery_thumbnail = $gallery->getType()->thumbnail($gallery);
+    $this->assertEqual($gallery_thumbnail, $video_thumbnail, "Correct thumbnail detected.");
+  }
+
+  /**
+   * Tests that gallery option isn't available in gallery create bundle filters.
+   */
+  public function testGalleryOption() {
+    // Open the media library iframe used on add gallery page.
+    $this->drupalGet('entity-browser/modal/gallery_media_library');
+    $this->assertNoOption('edit-bundle-1', 'gallery');
+  }
+
+  /**
+   * Adds image type item.
+   */
+  public function addImageItem() {
+    // Let's add image first.
+    $name = $this->randomMachineName();
+    $testImage = current($this->drupalGetTestFiles('image'));
+    $file_path = $this->container->get('file_system')->realpath($testImage->uri);
+    $edit = [
+      'name[0][value]' => $name,
+      'files[field_image_0]' => $file_path,
+    ];
+    // Save the image.
+    $this->drupalPostForm('media/add/image', $edit, t('Save and publish'));
+    $this->drupalPostForm(NULL, ['field_image[0][alt]' => $name], t('Save and publish'));
+    // Obtain the image id.
+    $media_id = $this->container->get('entity.query')->get('media')->condition('bundle', 'image')->sort('created', 'DESC')->execute();
+    $media_id = reset($media_id);
+    $edit['id'] = $media_id;
+
+    return $edit;
+  }
+
+  /**
+   * Adds video type item.
+   */
+  public function addVideoItem() {
+    $edit = [
+      'name[0][value]' => 'Drupal video!',
+      'field_video[0][value]' => 'https://www.youtube.com/watch?v=XgYu7-DQjDQ',
+    ];
+    $this->drupalPostForm('media/add/video', $edit, t('Save and publish'));
+    // Obtain the video id.
+    $media_id = $this->container->get('entity.query')->get('media')->condition('bundle', 'video')->sort('created', 'DESC')->execute();
+    $media_id = reset($media_id);
+    $edit['id'] = $media_id;
+
+    return $edit;
+  }
+
+  /**
+   * Loads the media entity item.
+   *
+   * @param int $id
+   *   The id of the item.
+   *
+   * @return \Drupal\media_entity\MediaInterface
+   *   The media entity item.
+   */
+  public function loadMediaItem($id) {
+    $item = $this->container->get('entity_type.manager')
+      ->getStorage('media')
+      ->loadUnchanged($id);
+    return $item;
+  }
+
+}
diff --git a/web/modules/contrib/media/src/Tests/ImageBundleTest.php b/web/modules/contrib/media/src/Tests/ImageBundleTest.php
new file mode 100644 (file)
index 0000000..bbe8265
--- /dev/null
@@ -0,0 +1,111 @@
+<?php
+
+namespace Drupal\media\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Ensures that media bundle for images can be created.
+ *
+ * @group media
+ */
+class ImageBundleTest extends WebTestBase {
+  /**
+   * Exempt from strict schema checking.
+   *
+   * @see \Drupal\Core\Config\Testing\ConfigSchemaChecker
+   *
+   * @var bool
+   */
+  protected $strictConfigSchema = FALSE;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = [
+    'media',
+    'media_entity',
+    'media_entity_image',
+    'image',
+    'node',
+    'editor',
+  ];
+
+  /**
+   * The test media bundle.
+   *
+   * @var \Drupal\media_entity\MediaBundleInterface
+   */
+  protected $testBundle;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->testBundle = $this->container->get('entity.manager')->getStorage('media_bundle')->load('image');
+
+    $adminUser = $this->drupalCreateUser([
+      'view media',
+      'create media',
+      'update media',
+      'update any media',
+      'delete media',
+      'delete any media',
+      'access media overview',
+    ]);
+    $this->drupalLogin($adminUser);
+  }
+
+  /**
+   * Tests image media bundle creation from config files.
+   */
+  public function testMediaBundleCreationFromModule() {
+    $type_configuration = [
+      'source_field' => 'field_image',
+      'gather_exif' => FALSE,
+    ];
+
+    $this->assertTrue((bool) $this->testBundle, 'The media bundle from default configuration has been created in the database.');
+    $this->assertEqual($this->testBundle->get('label'), 'Image', 'Correct label detected.');
+    $this->assertEqual($this->testBundle->get('description'), 'Use Image for uploading locally hosted images.', 'Correct description detected.');
+    $this->assertEqual($this->testBundle->get('type'), 'image', 'Correct plugin ID detected.');
+    $this->assertEqual($this->testBundle->get('type_configuration'), $type_configuration, 'Type configuration correct.');
+    $this->assertEqual($this->testBundle->get('field_map'), [], 'Correct field map detected.');
+  }
+
+  /**
+   * Tests item creation and thumbnail.
+   */
+  public function testMediaBundleItemCreation() {
+    // Define the media item name.
+    $name = $this->randomMachineName();
+    $image_files = $this->drupalGetTestFiles('image');
+    $testImage = current($image_files);
+    $file_path = $this->container->get('file_system')->realpath($testImage->uri);
+    $edit = [
+      'name[0][value]' => $name,
+      'files[field_image_0]' => $file_path,
+    ];
+
+    // Save the image.
+    $this->drupalPostForm('media/add/' . $this->testBundle->id(), $edit, t('Save and publish'));
+    $this->drupalPostForm(NULL, ['field_image[0][alt]' => $name], t('Save and publish'));
+
+    // Let's retrieve the media id and corresponding media entity object.
+    $media_id = $this->container->get('entity.query')->get('media')->execute();
+    $media_id = reset($media_id);
+    /** @var \Drupal\media_entity\MediaInterface $media */
+    $media = $this->container->get('entity_type.manager')
+        ->getStorage('media')
+        ->loadUnchanged($media_id);
+    $this->assertEqual($media->get('name')[0]->getValue()['value'], $name, "Correct name stored.");
+    $image = $media->getType();
+    $thumbnail = $image->thumbnail($media);
+    $default_thumbnail = $image->getDefaultThumbnail();
+    $this->assertNotEqual($thumbnail, $default_thumbnail, "The thumbnail generated is different from the default thumbnail.");
+  }
+
+}
diff --git a/web/modules/contrib/media/src/Tests/InstagramBundleTest.php b/web/modules/contrib/media/src/Tests/InstagramBundleTest.php
new file mode 100644 (file)
index 0000000..ba71f29
--- /dev/null
@@ -0,0 +1,112 @@
+<?php
+
+namespace Drupal\media\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Ensures that media bundle for instagram can be created.
+ *
+ * @group media
+ */
+class InstagramBundleTest extends WebTestBase {
+  /**
+   * Exempt from strict schema checking.
+   *
+   * @see \Drupal\Core\Config\Testing\ConfigSchemaChecker
+   *
+   * @var bool
+   */
+  protected $strictConfigSchema = FALSE;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = [
+    'media',
+    'media_entity',
+    'media_entity_instagram',
+    'link',
+    'node',
+    'editor',
+  ];
+
+  /**
+   * The test media bundle.
+   *
+   * @var \Drupal\media_entity\MediaBundleInterface
+   */
+  protected $testBundle;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->testBundle = $this->container->get('entity.manager')->getStorage('media_bundle')->load('instagram');
+
+    $adminUser = $this->drupalCreateUser([
+      'view media',
+      'create media',
+      'update media',
+      'update any media',
+      'delete media',
+      'delete any media',
+      'access media overview',
+    ]);
+    $this->drupalLogin($adminUser);
+  }
+
+  /**
+   * Tests instagram media bundle creation from config files.
+   */
+  public function testMediaBundleCreationFromModule() {
+    $type_configuration = [
+      'use_instagram_api' => FALSE,
+      'source_field' => 'field_instagram_url',
+      'client_id'  => '',
+    ];
+    $field_map = [
+      'shortcode' => 'field_instagram_shortcode',
+    ];
+
+    $this->assertTrue((bool) $this->testBundle, 'The media bundle from default configuration has been created in the database.');
+    $this->assertEqual($this->testBundle->get('label'), 'Instagram Post', 'Correct label detected.');
+    $this->assertEqual($this->testBundle->get('description'), 'Use this to attach Instagram posts to your content.', 'Correct description detected.');
+    $this->assertEqual($this->testBundle->get('type'), 'instagram', 'Correct plugin ID detected.');
+    $this->assertEqual($this->testBundle->get('type_configuration'), $type_configuration, 'Type configuration correct.');
+    $this->assertEqual($this->testBundle->get('field_map'), $field_map, 'Correct field map detected.');
+  }
+
+  /**
+   * Tests item creation and thumbnail.
+   */
+  public function testMediaBundleItemCreation() {
+    // Define the media item name.
+    $name = $this->randomMachineName();
+    $instagram_url = 'https://www.instagram.com/p/C/';
+    $edit = [
+      'name[0][value]' => $name,
+      'field_instagram_url[0][uri]' => $instagram_url,
+    ];
+
+    // Save the Instagram post.
+    $this->drupalPostForm('media/add/' . $this->testBundle->id(), $edit, t('Save and publish'));
+
+    // Assert that the formatter exists on this page.
+    $this->assertFieldByXPath('//iframe');
+
+    // Let's retrieve the media id and corresponding media entity object.
+    $media_id = $this->container->get('entity.query')->get('media')->condition('bundle', 'instagram')->sort('created', 'DESC')->execute();
+    $media_id = reset($media_id);
+    /** @var \Drupal\media_entity\MediaInterface $media */
+    $media = $this->container->get('entity_type.manager')
+      ->getStorage('media')
+      ->loadUnchanged($media_id);
+    $properties = $media->toArray();
+    $this->assertEqual($media->get('field_instagram_shortcode')[0]->getValue()['value'], "C", "Correct shortcode stored.");
+  }
+
+}
diff --git a/web/modules/contrib/media/src/Tests/TweetBundleTest.php b/web/modules/contrib/media/src/Tests/TweetBundleTest.php
new file mode 100644 (file)
index 0000000..4896a12
--- /dev/null
@@ -0,0 +1,112 @@
+<?php
+
+namespace Drupal\media\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Ensures that media bundle for tweets can be created.
+ *
+ * @group media
+ */
+class TweetBundleTest extends WebTestBase {
+  /**
+   * Exempt from strict schema checking.
+   *
+   * @see \Drupal\Core\Config\Testing\ConfigSchemaChecker
+   *
+   * @var bool
+   */
+  protected $strictConfigSchema = FALSE;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = [
+    'media',
+    'media_entity',
+    'media_entity_twitter',
+    'node',
+    'link',
+    'editor',
+  ];
+
+  /**
+   * The test media bundle.
+   *
+   * @var \Drupal\media_entity\MediaBundleInterface
+   */
+  protected $testBundle;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->testBundle = $this->container->get('entity.manager')->getStorage('media_bundle')->load('tweet');
+
+    $adminUser = $this->drupalCreateUser([
+      'view media',
+      'create media',
+      'update media',
+      'update any media',
+      'delete media',
+      'delete any media',
+      'access media overview',
+    ]);
+    $this->drupalLogin($adminUser);
+  }
+
+  /**
+   * Tests tweet media bundle creation from config files.
+   */
+  public function testMediaBundleCreationFromModule() {
+    $type_configuration = [
+      'use_twitter_api' => FALSE,
+      'source_field' => 'field_tweet_url',
+      'consumer_key' => '',
+      'consumer_secret' => '',
+      'oauth_access_token' => '',
+      'oauth_access_token_secret' => '',
+    ];
+    $field_map = [
+      'id' => 'field_tweet_id',
+      'user' => 'field_tweet_author',
+    ];
+
+    $this->assertTrue((bool) $this->testBundle, 'The media bundle from default configuration has been created in the database.');
+    $this->assertEqual($this->testBundle->get('label'), 'Tweet', 'Correct label detected.');
+    $this->assertEqual($this->testBundle->get('description'), 'Use this to embed Twitter content on your site.', 'Correct description detected.');
+    $this->assertEqual($this->testBundle->get('type'), 'twitter', 'Correct plugin ID detected.');
+    $this->assertEqual($this->testBundle->get('type_configuration'), $type_configuration, 'Type configuration correct.');
+    $this->assertEqual($this->testBundle->get('field_map'), $field_map, 'Correct field map detected.');
+  }
+
+  /**
+   * Tests item creation.
+   */
+  public function testMediaBundleItemCreation() {
+    // Define the media item name.
+    $name = $this->randomMachineName();
+    $tweet_url = 'https://twitter.com/jack/status/20';
+    $edit = [
+      'name[0][value]' => $name,
+      'field_tweet_url[0][uri]' => $tweet_url,
+    ];
+
+    // Save the tweet.
+    $this->drupalPostForm('media/add/' . $this->testBundle->id(), $edit, t('Save and publish'));
+    // Let's retrieve the media id.
+    $media_id = $this->container->get('entity.query')->get('media')->condition('bundle', 'tweet')->sort('created', 'DESC')->execute();
+    $media_id = reset($media_id);
+    $media = $this->container->get('entity_type.manager')
+      ->getStorage('media')
+      ->loadUnchanged($media_id);
+    $properties = $media->toArray();
+    $this->assertEqual($media->get('field_tweet_author')[0]->getValue()['value'], "jack", "Correct tweet author stored.");
+    $this->assertEqual($media->get('field_tweet_id')[0]->getValue()['value'], "20", "Correct tweet id stored.");
+  }
+
+}
diff --git a/web/modules/contrib/media/src/Tests/VideoBundleTest.php b/web/modules/contrib/media/src/Tests/VideoBundleTest.php
new file mode 100644 (file)
index 0000000..4d9a64d
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+
+namespace Drupal\media\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Ensures that media bundle for videos can be created.
+ *
+ * @group media
+ */
+class VideoBundleTest extends WebTestBase {
+  /**
+   * Exempt from strict schema checking.
+   *
+   * @see \Drupal\Core\Config\Testing\ConfigSchemaChecker
+   *
+   * @var bool
+   */
+  protected $strictConfigSchema = FALSE;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = [
+    'media',
+    'media_entity',
+    'video_embed_field',
+    'video_embed_media',
+    'node',
+    'editor',
+  ];
+
+  /**
+   * The test media bundle.
+   *
+   * @var \Drupal\media_entity\MediaBundleInterface
+   */
+  protected $testBundle;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->testBundle = $this->container->get('entity.manager')->getStorage('media_bundle')->load('video');
+
+    $adminUser = $this->drupalCreateUser([
+      'view media',
+      'create media',
+      'update media',
+      'update any media',
+      'delete media',
+      'delete any media',
+      'access media overview',
+    ]);
+    $this->drupalLogin($adminUser);
+  }
+
+  /**
+   * Tests video media bundle creation from config files.
+   */
+  public function testMediaBundleCreationFromModule() {
+    $type_configuration = [
+      'source_field' => 'field_video',
+    ];
+
+    $field_map = [
+      'id' => 'field_id',
+      'source_name' => 'field_source',
+    ];
+
+    $this->assertTrue((bool) $this->testBundle, 'The media bundle from default configuration has been created in the database.');
+    $this->assertEqual($this->testBundle->get('label'), 'Video', 'Correct label detected.');
+    $this->assertEqual($this->testBundle->get('description'), 'Use Video for embedding videos hosted by YouTube, Vimeo, or some other provider.', 'Correct description detected.');
+    $this->assertEqual($this->testBundle->get('type'), 'video_embed_field', 'Correct plugin ID detected.');
+    $this->assertEqual($this->testBundle->get('type_configuration'), $type_configuration, 'Type configuration correct.');
+    $this->assertEqual($this->testBundle->get('field_map'), $field_map, 'Correct field map detected.');
+  }
+
+  /**
+   * Tests video media bundle field maps.
+   */
+  public function testBundleFieldMap() {
+    $edit = [
+      'name[0][value]' => 'Drupal video!',
+      'field_video[0][value]' => 'https://www.youtube.com/watch?v=XgYu7-DQjDQ',
+    ];
+    $this->drupalPostForm('media/add/' . $this->testBundle->id(), $edit, t('Save and publish'));
+
+    // Let's retrieve the media id and corresponding media entity object.
+    $media_id = $this->container->get('entity.query')->get('media')->execute();
+    $media_id = reset($media_id);
+    /** @var \Drupal\media_entity\MediaInterface $media */
+    $media = $this->container->get('entity_type.manager')
+      ->getStorage('media')
+      ->loadUnchanged($media_id);
+    $properties = $media->toArray();
+    $this->assertEqual($properties['field_id'][0]['value'], 'XgYu7-DQjDQ', 'Correct video ID detected.');
+    $this->assertEqual($properties['field_source'][0]['value'], 'youtube', 'Correct video source detected.');
+  }
+
+}
diff --git a/web/modules/contrib/media/tests/modules/media_embed_test/config/install/core.entity_form_display.node.page.default.yml b/web/modules/contrib/media/tests/modules/media_embed_test/config/install/core.entity_form_display.node.page.default.yml
new file mode 100644 (file)
index 0000000..1fef06d
--- /dev/null
@@ -0,0 +1,60 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.node.page.body
+    - node.type.page
+  module:
+    - path
+    - text
+id: node.page.default
+targetEntityType: node
+bundle: page
+mode: default
+content:
+  body:
+    type: text_textarea_with_summary
+    weight: 31
+    settings:
+      rows: 9
+      summary_rows: 3
+      placeholder: ''
+    third_party_settings: {  }
+  created:
+    type: datetime_timestamp
+    weight: 10
+    settings: {  }
+    third_party_settings: {  }
+  path:
+    type: path
+    weight: 30
+    settings: {  }
+    third_party_settings: {  }
+  promote:
+    type: boolean_checkbox
+    settings:
+      display_label: true
+    weight: 15
+    third_party_settings: {  }
+  sticky:
+    type: boolean_checkbox
+    settings:
+      display_label: true
+    weight: 16
+    third_party_settings: {  }
+  title:
+    type: string_textfield
+    weight: -5
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+  uid:
+    type: entity_reference_autocomplete
+    weight: 5
+    settings:
+      match_operator: CONTAINS
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+hidden: {  }
diff --git a/web/modules/contrib/media/tests/modules/media_embed_test/config/install/editor.editor.basic_html.yml b/web/modules/contrib/media/tests/modules/media_embed_test/config/install/editor.editor.basic_html.yml
new file mode 100644 (file)
index 0000000..966cec6
--- /dev/null
@@ -0,0 +1,52 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - filter.format.basic_html
+  module:
+    - ckeditor
+format: basic_html
+editor: ckeditor
+settings:
+  toolbar:
+    rows:
+      -
+        -
+          name: Formatting
+          items:
+            - Bold
+            - Italic
+        -
+          name: Linking
+          items:
+            - DrupalLink
+            - DrupalUnlink
+        -
+          name: Lists
+          items:
+            - BulletedList
+            - NumberedList
+        -
+          name: Media
+          items:
+            - Blockquote
+            - DrupalImage
+        -
+          name: 'Block Formatting'
+          items:
+            - Format
+        -
+          name: Tools
+          items:
+            - Source
+  plugins:
+    stylescombo:
+      styles: ''
+image_upload:
+  status: true
+  scheme: public
+  directory: inline-images
+  max_size: ''
+  max_dimensions:
+    width: 0
+    height: 0
diff --git a/web/modules/contrib/media/tests/modules/media_embed_test/config/install/editor.editor.full_html.yml b/web/modules/contrib/media/tests/modules/media_embed_test/config/install/editor.editor.full_html.yml
new file mode 100644 (file)
index 0000000..f5dd7bc
--- /dev/null
@@ -0,0 +1,60 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - filter.format.full_html
+  module:
+    - ckeditor
+format: full_html
+editor: ckeditor
+settings:
+  toolbar:
+    rows:
+      -
+        -
+          name: Formatting
+          items:
+            - Bold
+            - Italic
+            - Strike
+            - Superscript
+            - Subscript
+            - '-'
+            - RemoveFormat
+        -
+          name: Linking
+          items:
+            - DrupalLink
+            - DrupalUnlink
+        -
+          name: Lists
+          items:
+            - BulletedList
+            - NumberedList
+        -
+          name: Media
+          items:
+            - Blockquote
+            - DrupalImage
+            - Table
+            - HorizontalRule
+        -
+          name: 'Block Formatting'
+          items:
+            - Format
+        -
+          name: Tools
+          items:
+            - ShowBlocks
+            - Source
+  plugins:
+    stylescombo:
+      styles: ''
+image_upload:
+  status: true
+  scheme: public
+  directory: inline-images
+  max_size: ''
+  max_dimensions:
+    width: 0
+    height: 0
diff --git a/web/modules/contrib/media/tests/modules/media_embed_test/config/install/field.field.node.page.body.yml b/web/modules/contrib/media/tests/modules/media_embed_test/config/install/field.field.node.page.body.yml
new file mode 100644 (file)
index 0000000..6c09432
--- /dev/null
@@ -0,0 +1,21 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.node.body
+    - node.type.page
+  module:
+    - text
+id: node.page.body
+field_name: body
+entity_type: node
+bundle: page
+label: Body
+description: ''
+required: false
+translatable: true
+default_value: {  }
+default_value_callback: ''
+settings:
+  display_summary: true
+field_type: text_with_summary
diff --git a/web/modules/contrib/media/tests/modules/media_embed_test/config/install/filter.format.basic_html.yml b/web/modules/contrib/media/tests/modules/media_embed_test/config/install/filter.format.basic_html.yml
new file mode 100644 (file)
index 0000000..92224c2
--- /dev/null
@@ -0,0 +1,44 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - editor
+name: 'Basic HTML'
+format: basic_html
+weight: 0
+roles:
+  - authenticated
+filters:
+  filter_html:
+    id: filter_html
+    provider: filter
+    status: true
+    weight: -10
+    settings:
+      allowed_html: '<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id> <p> <br> <span> <img src alt height width data-entity-type data-entity-uuid data-align data-caption>'
+      filter_html_help: false
+      filter_html_nofollow: false
+  filter_align:
+    id: filter_align
+    provider: filter
+    status: true
+    weight: 7
+    settings: {  }
+  filter_caption:
+    id: filter_caption
+    provider: filter
+    status: true
+    weight: 8
+    settings: {  }
+  filter_html_image_secure:
+    id: filter_html_image_secure
+    provider: filter
+    status: true
+    weight: 9
+    settings: {  }
+  editor_file_reference:
+    id: editor_file_reference
+    provider: editor
+    status: true
+    weight: 11
+    settings: {  }
diff --git a/web/modules/contrib/media/tests/modules/media_embed_test/config/install/filter.format.full_html.yml b/web/modules/contrib/media/tests/modules/media_embed_test/config/install/filter.format.full_html.yml
new file mode 100644 (file)
index 0000000..e5febb2
--- /dev/null
@@ -0,0 +1,35 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - editor
+name: 'Full HTML'
+format: full_html
+weight: 1
+roles:
+  - administrator
+filters:
+  filter_align:
+    id: filter_align
+    provider: filter
+    status: true
+    weight: 8
+    settings: {  }
+  filter_caption:
+    id: filter_caption
+    provider: filter
+    status: true
+    weight: 9
+    settings: {  }
+  filter_htmlcorrector:
+    id: filter_htmlcorrector
+    provider: filter
+    status: true
+    weight: 10
+    settings: {  }
+  editor_file_reference:
+    id: editor_file_reference
+    provider: editor
+    status: true
+    weight: 11
+    settings: {  }
diff --git a/web/modules/contrib/media/tests/modules/media_embed_test/config/install/node.type.page.yml b/web/modules/contrib/media/tests/modules/media_embed_test/config/install/node.type.page.yml
new file mode 100644 (file)
index 0000000..57dcc0c
--- /dev/null
@@ -0,0 +1,10 @@
+langcode: en
+status: true
+dependencies: {  }
+name: 'Basic page'
+type: page
+description: 'Use <em>basic pages</em> for your static content, such as an ''About us'' page.'
+help: ''
+new_revision: true
+preview_mode: 1
+display_submitted: false
diff --git a/web/modules/contrib/media/tests/modules/media_embed_test/media_embed_test.info.yml b/web/modules/contrib/media/tests/modules/media_embed_test/media_embed_test.info.yml
new file mode 100644 (file)
index 0000000..8de64b1
--- /dev/null
@@ -0,0 +1,9 @@
+name: 'Media Embed Test'
+description: 'Support module for the media embed tests.'
+type: module
+package: Testing
+core: 8.x
+version: VERSION
+dependencies:
+  - editor:editor
+  - ckeditor:ckeditor
diff --git a/web/modules/contrib/media/tests/src/FunctionalJavascript/EmbedButtonTest.php b/web/modules/contrib/media/tests/src/FunctionalJavascript/EmbedButtonTest.php
new file mode 100644 (file)
index 0000000..15ad406
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+
+namespace Drupal\Tests\media\FunctionalJavascript;
+
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+
+/**
+ * Ensures that embedding functionality works perfectly.
+ *
+ * @group media
+ */
+class EmbedButtonTest extends JavascriptTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'node',
+    'path',
+    'text',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+    // Manually installing modules to preserve the order.
+    $this->installModule('media_embed_test');
+    $this->installModule('media');
+    $adminUser = $this->drupalCreateUser([
+      'access content',
+      'use text format basic_html',
+      'use text format full_html',
+      'access media_embed entity browser pages',
+      'view media',
+      'create media',
+      'update media',
+      'update any media',
+      'delete media',
+      'delete any media',
+      'access media overview',
+      'create page content',
+      'edit any page content',
+    ]);
+    $this->drupalLogin($adminUser);
+  }
+
+  /**
+   * Tests that the entity embed dialog is working.
+   */
+  public function testMediaEmbedDialog() {
+    // Find the button and click it to see if the modal opens.
+    $this->drupalGet('node/add/page');
+    $this->find('.cke_button__media')->click();
+    $this->wait();
+    $this->assertSession()->pageTextContains('Select media to embed');
+
+    // Test for the button in the basic_html editor.
+    $this->drupalGet('entity-embed/dialog/basic_html/media');
+    $this->assertEquals(200, $this->getSession()->getStatusCode());
+    $this->assertSession()->pageTextContains('Select media to embed');
+
+    // Test for the button in the full_html editor.
+    $this->drupalGet('entity-embed/dialog/full_html/media');
+    $this->assertEquals(200, $this->getSession()->getStatusCode());
+    $this->assertSession()->pageTextContains('Select media to embed');
+
+    $this->drupalGet('entity-browser/iframe/media_embed');
+    $this->assertEquals(200, $this->getSession()->getStatusCode());
+    $filter = $this->getSession()->getPage()->find('css', 'input[name="name"]');
+    $this->assertTrue($filter, "Found filter");
+  }
+
+  /**
+   * Installs the module using module_handler service.
+   *
+   * @param string $module_name
+   *   Name of the module to install.
+   */
+  public function installModule($module_name) {
+    if (!$this->container->get('module_handler')->moduleExists($module_name)) {
+      $this->container->get('module_installer')->install(array($module_name));
+    }
+  }
+
+  /**
+   * Wait for AJAX.
+   */
+  protected function wait() {
+    $this->getSession()->wait(20000, '(0 === jQuery.active)');
+  }
+
+  /**
+   * Find an element based on a CSS selector.
+   *
+   * @param string $css_selector
+   *   A css selector to find an element for.
+   *
+   * @return \Behat\Mink\Element\NodeElement|null
+   *   The found element or null.
+   */
+  protected function find($css_selector) {
+    return $this->getSession()->getPage()->find('css', $css_selector);
+  }
+
+}
diff --git a/web/modules/contrib/php b/web/modules/contrib/php
deleted file mode 160000 (submodule)
index 304022b..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 304022be52874ef2d7916ea3acafd312766da9cc
diff --git a/web/modules/contrib/php/composer.json b/web/modules/contrib/php/composer.json
new file mode 100644 (file)
index 0000000..1abc75d
--- /dev/null
@@ -0,0 +1,24 @@
+{
+  "name": "drupal/php",
+  "description": "Allows embedded PHP code/snippets to be evaluated. Enabling this can cause security and performance issues as it allows users to execute PHP code on your site.",
+  "type": "drupal-module",
+  "homepage": "https://www.drupal.org/project/php",
+  "authors": [
+    {
+      "name": "hass",
+      "homepage": "https://www.drupal.org/u/hass"
+    },
+    {
+      "name": "See other contributors",
+      "homepage":"https://www.drupal.org/node/1633456/committers"
+    }
+  ],
+  "support": {
+    "issues": "https://www.drupal.org/project/issues/php",
+    "source": "http://git.drupal.org/project/php.git"
+  },
+  "license": "GPL-2.0+",
+  "require": {
+    "drupal/core": "~8.0"
+  }
+}
diff --git a/web/modules/contrib/php/config/install/filter.format.php_code.yml b/web/modules/contrib/php/config/install/filter.format.php_code.yml
new file mode 100644 (file)
index 0000000..1c584b4
--- /dev/null
@@ -0,0 +1,17 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - php
+name: 'PHP code'
+format: php_code
+weight: 11
+filters:
+  php_code:
+    id: php_code
+    provider: php
+    status: true
+    weight: 0
+    settings: {  }
+roles:
+  - administrator
diff --git a/web/modules/contrib/php/js/php.admin.js b/web/modules/contrib/php/js/php.admin.js
new file mode 100644 (file)
index 0000000..eac5915
--- /dev/null
@@ -0,0 +1,39 @@
+/**
+ * @file
+ * PHP block behaviors.
+ */
+
+(function ($) {
+
+  "use strict";
+
+  /**
+   * Provide the summary information for the block settings vertical tabs.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches the behavior for the block settings summaries.
+   */
+  Drupal.behaviors.phpSettingsSummary = {
+    attach: function () {
+      // The drupalSetSummary method required for this behavior is not available
+      // on the Blocks administration page, so we need to make sure this
+      // behavior is processed only if drupalSetSummary is defined.
+      if (typeof jQuery.fn.drupalSetSummary === 'undefined') {
+        return;
+      }
+
+      $('[data-drupal-selector="edit-visibility-php"]').drupalSetSummary(function (context) {
+        var $code = $(context).find('textarea[name="visibility[php][php]"]');
+        if ($code.val() === '<?php return TRUE; ?>') {
+          return Drupal.t('Not restricted');
+        }
+        else {
+          return Drupal.t('Restricted to certain pages');
+        }
+      });
+    }
+  };
+
+})(jQuery);
diff --git a/web/modules/contrib/php/php.info.yml b/web/modules/contrib/php/php.info.yml
new file mode 100644 (file)
index 0000000..cd4d633
--- /dev/null
@@ -0,0 +1,8 @@
+name: PHP Filter
+type: module
+description: Allows embedded PHP code/snippets to be evaluated. Enabling this can cause security and performance issues as it allows users to execute PHP code on your site.
+core: 8.x
+package: 'Filters'
+configure: filter.admin_overview
+dependencies:
+  - filter
\ No newline at end of file
diff --git a/web/modules/contrib/php/php.libraries.yml b/web/modules/contrib/php/php.libraries.yml
new file mode 100644 (file)
index 0000000..f3e0282
--- /dev/null
@@ -0,0 +1,7 @@
+php.block.admin:
+  version: VERSION
+  js:
+    js/php.admin.js: {}
+  dependencies:
+    - core/jquery
+    - core/drupal
diff --git a/web/modules/contrib/php/php.module b/web/modules/contrib/php/php.module
new file mode 100644 (file)
index 0000000..f25d541
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * @file
+ * Additional filter for PHP input.
+ */
+
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Url;
+
+/**
+ * Implements hook_help().
+ */
+function php_help($route_name, RouteMatchInterface $route_match) {
+  switch ($route_name) {
+    case 'help.page.php':
+      $output = '';
+      $output .= '<h3>' . t('About') . '</h3>';
+      $output .= '<p>' . t('The PHP Filter module adds a PHP filter to your site, for use with <a href=":filter">text formats</a>. This filter adds the ability to execute PHP code in any text field that uses a text format (such as the body of a content item or the text of a comment). <a href=":php-net">PHP</a> is a general-purpose scripting language widely-used for web development, and is the language with which Drupal has been developed. For more information, see the online handbook entry for the <a href=":php">PHP Filter module</a>.', [':filter' => Url::fromRoute('filter.admin_overview'), ':php-net' => 'http://www.php.net', ':php' => 'http://drupal.org/documentation/modules/php']) . '</p>';
+      $output .= '<h3>' . t('Uses') . '</h3>';
+      $output .= '<dl>';
+      $output .= '<dt>' . t('Enabling execution of PHP in text fields') . '</dt>';
+      $output .= '<dd>' . t('The PHP Filter module allows users with the proper permissions to include custom PHP code that will get executed when pages of your site are processed. While this is a powerful and flexible feature if used by a trusted user with PHP experience, it is a significant and dangerous security risk in the hands of a malicious or inexperienced user. Even a trusted user may accidentally compromise the site by entering malformed or incorrect PHP code. Only the most trusted users should be granted permission to use the PHP filter, and all PHP code added through the PHP filter should be carefully examined before use. <a href=":php-snippets">Example PHP snippets</a> can be found on Drupal.org.', [':php-snippets' => 'http://drupal.org/documentation/customization/php-snippets']) . '</dd>';
+      $output .= '</dl>';
+      return $output;
+  }
+}
+
+/**
+ * Implements hook_library_info_alter().
+ */
+function php_library_info_alter(&$libraries, $extension) {
+  // Load php.admin.js whenever drupal.block library is added.
+  if ($extension == 'block' && isset($libraries['drupal.block'])) {
+    $libraries['drupal.block']['dependencies'][] = 'php/php.block.admin';
+  }
+}
+
+/**
+ * Evaluates a string of PHP code.
+ *
+ * This is a wrapper around PHP's eval(). It uses output buffering to capture
+ * both returned and printed text. Unlike eval(), we require code to be
+ * surrounded by <?php ?> tags; in other words, we evaluate the code as if it
+ * were a stand-alone PHP file.
+ *
+ * Using this wrapper also ensures that the PHP code which is evaluated can not
+ * overwrite any variables in the calling code, unlike a regular eval() call.
+ *
+ * This function is also used as an implementation of
+ * hook_filter_FILTER_process().
+ *
+ * @param string $code
+ *   The code to evaluate.
+ *
+ * @return string
+ *   A string containing the printed output of the code, followed by the
+ *   returned output of the code.
+ *
+ * @ingroup php_wrappers
+ *
+ * @see php_filter_info()
+ */
+function php_eval($code) {
+  /* FIXME global $theme_path, $theme_info;
+
+  // Store current theme path.
+  $old_theme_path = $theme_path;
+
+  // Restore theme_path to the theme, as long as php_eval() executes,
+  // so code evaluated will not see the caller module as the current theme.
+  // If theme info is not initialized get the path from default theme.
+  if (!isset($theme_info)) {
+    $theme_path = drupal_get_path('theme', Drupal::config('system.theme')->get('default'));
+  }
+  else {
+    $theme_path = dirname($theme_info->filename);
+  }*/
+
+  ob_start();
+  print eval('?>' . $code);
+  $output = ob_get_contents();
+  ob_end_clean();
+
+  // Recover original theme path.
+  /* $theme_path = $old_theme_path; */
+
+  return $output;
+}
diff --git a/web/modules/contrib/php/php.permissions.yml b/web/modules/contrib/php/php.permissions.yml
new file mode 100644 (file)
index 0000000..35a678f
--- /dev/null
@@ -0,0 +1,4 @@
+use PHP for settings:
+  title: 'Use PHP for settings'
+  description: 'Allows users to enter PHP code in settings.'
+  restrict access: true
diff --git a/web/modules/contrib/php/src/Plugin/Condition/Php.php b/web/modules/contrib/php/src/Plugin/Condition/Php.php
new file mode 100644 (file)
index 0000000..ca13a5a
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\php\Plugin\Condition\Php.
+ */
+
+namespace Drupal\php\Plugin\Condition;
+
+use Drupal\Core\Condition\ConditionPluginBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Provides a 'Php' condition.
+ *
+ * @Condition(
+ *   id = "php",
+ *   label = @Translation("PHP")
+ * )
+ */
+class Php extends ConditionPluginBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultConfiguration() {
+    // By default the PHP snippet need to return TRUE or blocks will silently
+    // disappear after the module has been enabled and/or a block has been
+    // configured without configuring a PHP snippet.
+    return ['php' => '<?php return TRUE; ?>'] + parent::defaultConfiguration();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+    $form['php'] = [
+      '#type' => 'textarea',
+      '#title' => $this->t('When the following PHP return TRUE (experts only)'),
+      '#default_value' => $this->configuration['php'],
+      '#description' => $this->t('Enter PHP code between &lt;?php ?&gt;. Note that executing incorrect PHP code can break your Drupal site. Return TRUE in order for this condition to evaluate as TRUE.'),
+      '#access' => \Drupal::currentUser()->hasPermission('use PHP for settings')
+    ];
+
+    return parent::buildConfigurationForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+    $this->configuration['php'] = $form_state->getValue('php');
+    parent::submitConfigurationForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function summary() {
+    if (!empty($this->configuration['php'])) {
+      return t('When the given PHP evaluates as @state.', ['@state' => !empty($this->configuration['negate']) ? 'FALSE' : 'TRUE']);
+    }
+    else {
+      return t('No PHP code has been provided.');
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function evaluate() {
+    return php_eval($this->configuration['php']);
+  }
+
+}
diff --git a/web/modules/contrib/php/src/Plugin/Filter/Php.php b/web/modules/contrib/php/src/Plugin/Filter/Php.php
new file mode 100644 (file)
index 0000000..496bce8
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\php\Plugin\Filter\Php.
+ */
+
+namespace Drupal\php\Plugin\Filter;
+
+use Drupal\filter\FilterProcessResult;
+use Drupal\filter\Plugin\FilterBase;
+
+/**
+ * Provides PHP code filter. Use with care.
+ *
+ * @Filter(
+ *   id = "php_code",
+ *   module = "php",
+ *   title = @Translation("PHP evaluator"),
+ *   description = @Translation("Executes a piece of PHP code. The usage of this filter should be restricted to administrators only!"),
+ *   type = Drupal\filter\Plugin\FilterInterface::TYPE_MARKUP_LANGUAGE,
+ *   cache = FALSE
+ * )
+ */
+class Php extends FilterBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function process($text, $langcode) {
+    return new FilterProcessResult(php_eval($text));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function tips($long = FALSE) {
+    if ($long) {
+      $output = '<h4>' . t('Using custom PHP code') . '</h4>';
+      $output .= '<p>' . t('Custom PHP code may be embedded in some types of site content, including posts and blocks. While embedding PHP code inside a post or block is a powerful and flexible feature when used by a trusted user with PHP experience, it is a significant and dangerous security risk when used improperly. Even a small mistake when posting PHP code may accidentally compromise your site.') . '</p>';
+      $output .= '<p>' . t('If you are unfamiliar with PHP, SQL, or Drupal, avoid using custom PHP code within posts. Experimenting with PHP may corrupt your database, render your site inoperable, or significantly compromise security.') . '</p>';
+      $output .= '<p>' . t('Notes:') . '</p>';
+      $output .= '<ul><li>' . t('Remember to double-check each line for syntax and logic errors <strong>before</strong> saving.') . '</li>';
+      $output .= '<li>' . t('Statements must be correctly terminated with semicolons.') . '</li>';
+      $output .= '<li>' . t('Global variables used within your PHP code retain their values after your script executes.') . '</li>';
+      $output .= '<li>' . t('<code>register_globals</code> is <strong>turned off</strong>. If you need to use forms, understand and use the functions in <a href=":formapi">the Drupal Form API</a>.', [':formapi' => 'http://api.drupal.org/api/group/form_api/8']) . '</li>';
+      $output .= '<li>' . t('Use a <code>print</code> or <code>return</code> statement in your code to output content.') . '</li>';
+      $output .= '<li>' . t('Develop and test your PHP code using a separate test script and sample database before deploying on a production site.') . '</li>';
+      $output .= '<li>' . t('Consider including your custom PHP code within a site-specific module or theme rather than embedding it directly into a post or block.') . '</li>';
+      $output .= '<li>' . t('Be aware that the ability to embed PHP code within content is provided by the PHP Filter module. If this module is disabled or deleted, then blocks and posts with embedded PHP may display, rather than execute, the PHP code.') . '</li></ul>';
+      $output .= '<p>' . t('A basic example: <em>Creating a "Welcome" block that greets visitors with a simple message.</em>') . '</p>';
+      $output .= '<ul><li>' . t('<p>Add a custom block to your site, named "Welcome". With its text format set to "PHP code" (or another format supporting PHP input), add the following in the Block body:</p>
+  <pre>
+  print t(\'Welcome visitor! Thank you for visiting.\');
+  </pre>') . '</li>';
+      $output .= '<li>' . t('<p>To display the name of a registered user, use this instead:</p>
+  <pre>
+  $account = \Drupal::currentUser();
+  if ($account->isAuthenticated()) {
+    print t(\'Welcome @name! Thank you for visiting.\', [\'@name\' => $account->getDisplayName()]);
+  }
+  else {
+    print t(\'Welcome visitor! Thank you for visiting.\');
+  }
+  </pre>') . '</li></ul>';
+      $output .= '<p>' . t('<a href=":drupal">Drupal.org</a> offers <a href=":php-snippets">some example PHP snippets</a>, or you can create your own with some PHP experience and knowledge of the Drupal system.', [':drupal' => 'http://drupal.org', ':php-snippets' => 'http://drupal.org/documentation/customization/php-snippets']) . '</p>';
+      return $output;
+    }
+    else {
+      return t('You may post PHP code. You should include &lt;?php ?&gt; tags.');
+    }
+  }
+
+}
diff --git a/web/modules/contrib/php/src/Plugin/views/argument_default/Php.php b/web/modules/contrib/php/src/Plugin/views/argument_default/Php.php
new file mode 100644 (file)
index 0000000..3622721
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\php\Plugin\views\argument_default\Php.
+ */
+
+namespace Drupal\php\Plugin\views\argument_default;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\views\Plugin\views\argument_default\ArgumentDefaultPluginBase;
+
+
+/**
+ * Default argument plugin to provide a PHP code block.
+ *
+ * @ViewsArgumentDefault(
+ *   id = "php",
+ *   module = "php",
+ *   title = @Translation("PHP Code")
+ * )
+ */
+class Php extends ArgumentDefaultPluginBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function defineOptions() {
+    $options = parent::defineOptions();
+    $options['code'] = ['default' => ''];
+
+    return $options;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildOptionsForm(&$form, FormStateInterface $form_state) {
+    parent::buildOptionsForm($form, $form_state);
+    $form['code'] = [
+      '#type' => 'textarea',
+      '#title' => $this->t('PHP contextual filter code'),
+      '#default_value' => $this->options['code'],
+      '#description' => $this->t('Enter PHP code that returns a value to use for this filter. Do not use &lt;?php ?&gt;. You must return only a single value for just this filter. Some variables are available: the view object will be "$view". The argument handler will be "$argument", for example you may change the title used for substitutions for this argument by setting "argument->validated_title".'),
+    ];
+
+    // Only do this if using one simple standard form gadget.
+    $this->checkAccess($form, 'code');
+  }
+
+  /**
+   * Only let users with PHP block visibility permissions set/modify this
+   * default plugin.
+   */
+  public function access() {
+    return \Drupal::currentUser()->hasPermission('use PHP for settings');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getArgument() {
+    // Set up variables to make it easier to reference during the argument.
+    $view = &$this->view;
+    $argument = &$this->argument;
+    ob_start();
+    $result = eval($this->options['code']);
+    ob_end_clean();
+    return $result;
+  }
+
+}
diff --git a/web/modules/contrib/php/src/Plugin/views/argument_validator/Php.php b/web/modules/contrib/php/src/Plugin/views/argument_validator/Php.php
new file mode 100644 (file)
index 0000000..deb0d9b
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\views\Plugin\views\argument_validator\Php.
+ */
+
+namespace Drupal\php\Plugin\views\argument_validator;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\views\Plugin\views\argument_validator\ArgumentValidatorPluginBase;
+
+
+/**
+ * Provide PHP code to validate whether or not an argument is ok.
+ *
+ * @ViewsArgumentValidator(
+ *   id = "php",
+ *   module = "php",
+ *   title = @Translation("PHP Code")
+ * )
+ */
+class Php extends ArgumentValidatorPluginBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function defineOptions() {
+    $options = parent::defineOptions();
+    $options['code'] = ['default' => ''];
+
+    return $options;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildOptionsForm(&$form, FormStateInterface $form_state) {
+    parent::buildOptionsForm($form, $form_state);
+    $form['code'] = [
+      '#type' => 'textarea',
+      '#title' => $this->t('PHP validate code'),
+      '#default_value' => $this->options['code'],
+      '#description' => $this->t('Enter PHP code that returns TRUE or FALSE. No return is the same as FALSE, so be SURE to return something if you do not want to declare the argument invalid. Do not use &lt;?php ?&gt;. The argument to validate will be "$argument" and the view will be "$view". You may change the argument by setting "$handler->argument". You may change the title used for substitutions for this argument by setting "$handler->validated_title".'),
+    ];
+
+    $this->checkAccess($form, 'code');
+  }
+
+  /**
+   * Only let users with PHP block visibility permissions set/modify this
+   * validate plugin.
+   */
+  public function access() {
+    return \Drupal::currentUser()->hasPermission('use PHP for settings');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateArgument($argument) {
+    // Set up variables to make it easier to reference during the argument.
+    $view = &$this->view;
+    $handler = &$this->argument;
+
+    ob_start();
+    $result = eval($this->options['code']);
+    ob_end_clean();
+    return $result;
+  }
+
+}
diff --git a/web/modules/contrib/php/src/Tests/Condition/PhpConditionTest.php b/web/modules/contrib/php/src/Tests/Condition/PhpConditionTest.php
new file mode 100644 (file)
index 0000000..84476ed
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\php\Tests\Condition\PhpConditionTest.
+ */
+
+namespace Drupal\php\Tests\Condition;
+
+use \Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Tests that the PHP Condition, provided by php module, is working properly.
+ *
+ * @group PHP
+ */
+class PhpConditionTest extends KernelTestBase {
+
+  /**
+   * The condition plugin manager.
+   *
+   * @var \Drupal\Core\Condition\ConditionManager
+   */
+  protected $manager;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['system', 'php'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->manager = $this->container->get('plugin.manager.condition');
+  }
+
+  /**
+   * Tests conditions.
+   */
+  public function testConditions() {
+    // Grab the PHP condition and configure it to check against a php snippet.
+    $condition = $this->manager->createInstance('php')
+      ->setConfig('php', '<?php return TRUE; ?>');
+    $this->assertTrue($condition->execute(), 'PHP condition passes as expected.');
+    // Check for the proper summary.
+    self::assertEquals($condition->summary(), 'When the given PHP evaluates as TRUE.');
+
+    // Set the PHP snippet to return FALSE.
+    $condition->setConfig('php', '<?php return FALSE; ?>');
+    $this->assertFalse($condition->execute(), 'PHP condition fails as expected.');
+
+    // Negate the condition.
+    $condition->setConfig('negate', TRUE);
+    // Check for the proper summary.
+    self::assertEquals($condition->summary(), 'When the given PHP evaluates as FALSE.');
+
+    // Reverse the negation.
+    $condition->setConfig('negate', FALSE);
+    // Set and empty snippet.
+    $condition->setConfig('php', FALSE);
+    // Check for the proper summary.
+    self::assertEquals($condition->summary(), 'No PHP code has been provided.');
+  }
+
+}
diff --git a/web/modules/contrib/php/src/Tests/PhpAccessTest.php b/web/modules/contrib/php/src/Tests/PhpAccessTest.php
new file mode 100644 (file)
index 0000000..daa9669
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\php\Tests\PhpAccessTest.
+ */
+
+namespace Drupal\php\Tests;
+
+/**
+ * Tests to make sure access to the PHP filter is properly restricted.
+ *
+ * @group PHP
+ */
+class PhpAccessTest extends PhpTestBase {
+
+  /**
+   * Makes sure that the user can't use the PHP filter when not given access.
+   */
+  public function testNoPrivileges() {
+    // Create node with PHP filter enabled.
+    $web_user = $this->drupalCreateUser(['access content', 'create page content', 'edit own page content']);
+    $this->drupalLogin($web_user);
+    $node = $this->createNodeWithCode();
+
+    // Make sure that the PHP code shows up as text.
+    $this->drupalGet('node/' . $node->id());
+    $this->assertText('print', 'PHP code was not evaluated.');
+
+    // Make sure that user doesn't have access to filter.
+    $this->drupalGet('node/' . $node->id() . '/edit');
+    $this->assertNoRaw('<option value="' . $this->phpCodeFormat->id() . '">', 'PHP code format not available.');
+  }
+
+}
diff --git a/web/modules/contrib/php/src/Tests/PhpFilterTest.php b/web/modules/contrib/php/src/Tests/PhpFilterTest.php
new file mode 100644 (file)
index 0000000..20001fc
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\php\Tests\PhpFilterTest.
+ */
+
+namespace Drupal\php\Tests;
+
+/**
+ * Tests to make sure the PHP filter actually evaluates PHP code when used.
+ *
+ * @group PHP
+ */
+class PhpFilterTest extends PhpTestBase {
+
+  /**
+   * Makes sure that the PHP filter evaluates PHP code when used.
+   */
+  public function testPhpFilter() {
+    // Log in as a user with permission to use the PHP code text format.
+    $php_code_permission = entity_load('filter_format', 'php_code')->getPermissionName();
+    $web_user = $this->drupalCreateUser(['access content', 'create page content', 'edit own page content', $php_code_permission]);
+    $this->drupalLogin($web_user);
+
+    // Create a node with PHP code in it.
+    $node = $this->createNodeWithCode();
+
+    // Make sure that the PHP code shows up as text.
+    $this->drupalGet('node/' . $node->id());
+    $this->assertText('php print');
+
+    // Change filter to PHP filter and see that PHP code is evaluated.
+    $edit = [];
+    $edit['body[0][format]'] = $this->phpCodeFormat->id();
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
+    $this->assertRaw(t('Basic page %title has been updated.', ['%title' => $node->label()]), 'PHP code filter turned on.');
+
+    // Make sure that the PHP code shows up as text.
+    $this->assertNoText('print "SimpleTest PHP was executed!"', "PHP code isn't displayed.");
+    $this->assertText('SimpleTest PHP was executed!', 'PHP code has been evaluated.');
+  }
+
+}
diff --git a/web/modules/contrib/php/src/Tests/PhpTestBase.php b/web/modules/contrib/php/src/Tests/PhpTestBase.php
new file mode 100644 (file)
index 0000000..129e487
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\php\Tests\PhpTestBase.
+ */
+
+namespace Drupal\php\Tests;
+
+use Drupal\simpletest\WebTestBase;
+use Drupal\user\RoleInterface;
+
+/**
+ * Test if PHP filter works in general.
+ *
+ * @group PHP
+ */
+abstract class PhpTestBase extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['node', 'php'];
+
+  protected $phpCodeFormat;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    // Create Basic page node type.
+    $this->drupalCreateContentType(['type' => 'page', 'name' => t('Basic page')]);
+
+    // Create and login admin user.
+    $admin_user = $this->drupalCreateUser(['administer filters']);
+    $this->drupalLogin($admin_user);
+
+    // Verify that the PHP code text format was inserted.
+    $php_format_id = 'php_code';
+    $this->phpCodeFormat = \Drupal::entityTypeManager()->getStorage('filter_format')->load($php_format_id);
+
+    $this->assertEqual($this->phpCodeFormat->label(), 'PHP code', 'PHP code text format was created.');
+
+    // Verify that the format has the PHP code filter enabled.
+    $filters = $this->phpCodeFormat->filters();
+    $this->assertTrue($filters->get('php_code')->status, 'PHP code filter is enabled.');
+
+    // Verify that the format exists on the administration page.
+    $this->drupalGet('admin/config/content/formats');
+    $this->assertText('PHP code', 'PHP code text format was created.');
+
+    // Verify that anonymous and authenticated user roles do not have access.
+    $this->drupalGet('admin/config/content/formats/manage/' . $php_format_id);
+    $this->assertFieldByName('roles[' . RoleInterface::ANONYMOUS_ID . ']', FALSE, 'Anonymous users do not have access to PHP code format.');
+    $this->assertFieldByName('roles[' . RoleInterface::AUTHENTICATED_ID . ']', FALSE, 'Authenticated users do not have access to PHP code format.');
+  }
+
+  /**
+   * Creates a test node with PHP code in the body.
+   *
+   * @return \Drupal\node\NodeInterface
+   *   Node object.
+   */
+  public function createNodeWithCode() {
+    return $this->drupalCreateNode(['body' => [['value' => '<?php print "SimpleTest PHP was executed!"; ?>']]]);
+  }
+
+}
diff --git a/web/modules/contrib/php/src/Tests/Plugin/views/PhpArgumentValidatorTest.php b/web/modules/contrib/php/src/Tests/Plugin/views/PhpArgumentValidatorTest.php
new file mode 100644 (file)
index 0000000..baa8e2c
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\php\Tests\Plugin\views\ArgumentValidatorTest.
+ */
+
+namespace Drupal\php\Tests\Plugin\views;
+
+use Drupal\views\Tests\ViewKernelTestBase;
+use Drupal\views\Tests\ViewTestData;
+use Drupal\views\Views;
+
+/**
+ * Tests Views PHP argument validators.
+ *
+ * @group PHP
+ *
+ * @see \Drupal\php\Plugin\views\argument_validator\Php
+ */
+class PhpArgumentValidatorTest extends ViewKernelTestBase {
+
+  /**
+   * Views used by this test.
+   *
+   * @var array
+   */
+  public static $testViews = ['test_view_argument_validate_php'];
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['php', 'php_views_test_config'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp($import_test_views = TRUE) {
+    parent::setUp();
+    if ($import_test_views) {
+      ViewTestData::createTestViews(get_class($this), array('php_views_test_config'));
+    }
+  }
+
+  /**
+   * Tests the validateArgument question.
+   */
+  public function testArgumentValidatePhp() {
+    $string = $this->randomMachineName();
+    $view = Views::getView('test_view_argument_validate_php');
+    $view->setDisplay();
+    $view->displayHandlers->get('default')->options['arguments']['null']['validate_options']['code'] = 'return $argument == \'' . $string . '\';';
+
+    $view->initHandlers();
+    $this->assertTrue($view->argument['null']->validateArgument($string));
+    // Reset saved argument validation.
+    $view->argument['null']->argument_validated = NULL;
+    $this->assertFalse($view->argument['null']->validateArgument($this->randomMachineName()));
+  }
+
+}
diff --git a/web/modules/contrib/php/tests/modules/php_views_test_config/php_views_test_config.info.yml b/web/modules/contrib/php/tests/modules/php_views_test_config/php_views_test_config.info.yml
new file mode 100644 (file)
index 0000000..5d3fb06
--- /dev/null
@@ -0,0 +1,7 @@
+name: 'PHP Views Test Config'
+type: module
+description: 'Provides PHP views for tests.'
+package: Testing
+core: 8.x
+dependencies:
+  - views
diff --git a/web/modules/contrib/php/tests/modules/php_views_test_config/test_views/views.view.test_view_argument_validate_php.yml b/web/modules/contrib/php/tests/modules/php_views_test_config/test_views/views.view.test_view_argument_validate_php.yml
new file mode 100644 (file)
index 0000000..2d409f4
--- /dev/null
@@ -0,0 +1,43 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - php
+id: test_view_argument_validate_php
+label: ''
+module: views
+description: ''
+tag: ''
+base_table: node
+core: '8'
+display:
+  default:
+    display_options:
+      access:
+        type: none
+      arguments:
+        'null':
+          default_argument_type: fixed
+          field: 'null'
+          id: 'null'
+          must_not_be: '0'
+          summary:
+            format: default_summary
+          table: views
+          validate:
+            type: php
+          plugin_id: 'null'
+      cache:
+        type: none
+      exposed_form:
+        type: basic
+      pager:
+        type: full
+      style:
+        type: default
+      row:
+        type: fields
+    display_plugin: default
+    display_title: Master
+    id: default
+    position: '0'
diff --git a/web/modules/contrib/views_responsive_grid b/web/modules/contrib/views_responsive_grid
deleted file mode 160000 (submodule)
index b8478cc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit b8478ccf4cb6dc6837a0c1170a848e418499a357
diff --git a/web/modules/contrib/views_responsive_grid/README.txt b/web/modules/contrib/views_responsive_grid/README.txt
new file mode 100644 (file)
index 0000000..b63e6d0
--- /dev/null
@@ -0,0 +1,46 @@
+CONTENTS OF THIS FILE
+---------------------
+
+ * Introduction
+ * Installation
+ * How to use
+
+
+INTRODUCTION
+------------
+
+Current Maintainers:
+Mark Carver - https://drupal.org/user/501638
+Ian Whitcomb - https://drupal.org/user/771654
+
+Views Responsive Grid provides a views plugin for displaying content in a
+responsive (mobile friendly) grid layout. Rather than trying to force the
+standard Views grid display to work for mobile this provides the same
+functionality, but in DIVs instead of tables. Provided is also the ability to
+specify a horizontal or vertical grid layout which will properly stack the
+content on a mobile display.
+
+
+INSTALLATION
+------------
+
+1. Download module and copy views_responsive_grid folder to /modules
+2. Enable Views and Views Responsive Grid modules.
+
+
+HOW TO USE
+------------
+
+After enabling the module, create a new view with the responsive grid display
+format. Specify the number of columns, alignment and classes of the grid.
+
+If the automatic width option is enabled, the plugin will determine the width of
+the columns automatically. If it is disabled, you may need to specify classes
+for either the rows, columns or both depending on your configuration and theming
+needs.
+
+If a theme automatically injects classes, the automatic width option may also
+interfere with the visual aspect of the grid.
+
+This module does not provide any visual styling for the grid, that responsibility
+is left to themes.
diff --git a/web/modules/contrib/views_responsive_grid/lib/Drupal/views_responsive_grid/Plugin/views/style/ResponsiveGrid.php b/web/modules/contrib/views_responsive_grid/lib/Drupal/views_responsive_grid/Plugin/views/style/ResponsiveGrid.php
new file mode 100644 (file)
index 0000000..634e4d5
--- /dev/null
@@ -0,0 +1,115 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\views_responsive_grid\Plugin\views\style\ResponsiveGrid.
+ */
+
+namespace Drupal\views_responsive_grid\Plugin\views\style;
+
+use Drupal\views\Plugin\views\style\StylePluginBase;
+use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * Style plugin to render each item in a responsive grid "cell".
+ *
+ * @ingroup views_style_plugins
+ *
+ * @Plugin(
+ *   id = "responsive_grid",
+ *   module = "views_responsive_grid",
+ *   title = @Translation("Responsive grid"),
+ *   help = @Translation("Displays rows in a responsive grid."),
+ *   theme = "views_view_responsive_grid",
+ *   theme_file = "views_responsive_grid.theme.inc",
+ *   display_types = {"normal"}
+ * )
+ */
+class ResponsiveGrid extends StylePluginBase {
+
+  /**
+   * Does the style plugin allows to use style plugins.
+   *
+   * @var bool
+   */
+  protected $usesRowPlugin = TRUE;
+
+  /**
+   * Does the style plugin support custom css class for the rows.
+   *
+   * @var bool
+   */
+  protected $usesRowClass = TRUE;
+
+  /**
+   * Set default options
+   */
+  function defineOptions() {
+    $options = parent::defineOptions();
+    $options['columns'] = array('default' => '4');
+    $options['automatic_width'] = array('default' => TRUE, 'bool' => TRUE);
+    $options['alignment'] = array('default' => 'horizontal');
+    $options['col_class'] = array('default' => '');
+    $options['default_col_class'] = array('default' => TRUE, 'bool' => TRUE);
+    $options['col_class_special'] = array('default' => TRUE, 'bool' => TRUE);
+    return $options;
+  }
+
+  /**
+   * Build the options form.
+   */
+  function buildOptionsForm(&$form, &$form_state) {
+    parent::buildOptionsForm($form, $form_state);
+    if (!empty($form['uses_fields'])) {
+      $form['uses_fields']['#weight'] = -10;
+    }
+    $form['default_row_class']['#description'] = t('Add the default row classes like views-row, row-1 and clearfix to the output. You can use this to quickly reduce the amount of markup the view provides by default, at the cost of making it more difficult to apply CSS.');
+    $form['row_class_special']['#description'] = t('Add css classes to the first and last rows, as well as odd/even classes for striping.');
+    $form['columns'] = array(
+      '#type' => 'number',
+      '#title' => t('Number of columns'),
+      '#default_value' => $this->options['columns'],
+      '#required' => TRUE,
+      '#min' => 0,
+      '#weight' => -9,
+    );
+    $form['automatic_width'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Automatic width'),
+      '#description' => t('The width of each column will be calculated automatically based on the number of columns entered. If additional classes are entered or a theme injects additional classes based on a grid system, disabling this option may prove beneficial.'),
+      '#default_value' => $this->options['automatic_width'],
+      '#weight' => -8,
+    );
+    $form['alignment'] = array(
+      '#type' => 'radios',
+      '#title' => t('Alignment'),
+      '#options' => array('horizontal' => t('Horizontal'), 'vertical' => t('Vertical')),
+      '#default_value' => $this->options['alignment'],
+      '#description' => t('Horizontal alignment will place items starting in the upper left and moving right. Vertical alignment will place items starting in the upper left and moving down.'),
+      '#weight' => -7,
+    );
+    $form['col_class'] = array(
+      '#title' => t('Column class'),
+      '#description' => t('The class to provide on each column.'),
+      '#type' => 'textfield',
+      '#default_value' => $this->options['col_class'],
+    );
+    if ($this->usesFields()) {
+      $form['col_class']['#description'] .= ' ' . t('You may use field tokens from as per the "Replacement patterns" used in "Rewrite the output of this field" for all fields.');
+    }
+    $form['default_col_class'] = array(
+      '#title' => t('Add views column classes'),
+      '#description' => t('Add the default column classes like views-col, col-1 and clearfix to the output. You can use this to quickly reduce the amount of markup the view provides by default, at the cost of making it more difficult to apply CSS.'),
+      '#type' => 'checkbox',
+      '#default_value' => $this->options['default_col_class'],
+    );
+    $form['col_class_special'] = array(
+      '#title' => t('Add striping (odd/even), first/last column classes'),
+      '#description' => t('Add css classes to the first and last columns, as well as odd/even classes for striping.'),
+      '#type' => 'checkbox',
+      '#default_value' => $this->options['col_class_special'],
+    );
+  }
+
+}
diff --git a/web/modules/contrib/views_responsive_grid/templates/views-view-responsive-grid.html.twig b/web/modules/contrib/views_responsive_grid/templates/views-view-responsive-grid.html.twig
new file mode 100644 (file)
index 0000000..619879b
--- /dev/null
@@ -0,0 +1,50 @@
+{#
+/**
+ * @file
+ * Default theme implementation for views to display rows in a responsive grid.
+ *
+ * Available variables:
+ * - attributes: HTML attributes for the wrapping div element.
+ * - title: The title of this group of rows.
+ * - view: The view object.
+ * - rows: The rendered view results array.
+ * - options: The view plugin style options.
+ * - items: Contains a nested array of grid items. The structure of this array
+ * - depends on the value of options.alignment.
+ * - row_attributes: HTML attributes for each grid row. The structure of this
+ *   array depends on the value of options.alignment.
+ * - col_attributes: HTML attributes for each grid column. The structure of this
+ *   array depends on the value of options.alignment.
+ *
+ * @see template_process_views_view_responsive_grid()
+ *
+ * @ingroup themeable
+ * @ingroup views_templates
+ */
+#}
+{% if title %}
+<h3>{{ title }}</h3>
+{% endif %}
+<div{{ attributes }}>
+{% if options.alignment == 'horizontal' %}
+  {% for row_key, row in items %}
+  <div{{ row_attributes[row_key] }}>
+    {% for col_key, col in row %}
+    <div{{ col_attributes[row_key][col_key] }}>
+      {{ items[row_key][col_key] }}
+    </div>
+    {% endfor %}
+  </div>
+  {% endfor %}
+{% else %}
+  {% for col_key, column in items %}
+  <div{{ col_attributes[col_key] }}>
+    {% for row_key, row in column %}
+      <div{{ row_attributes[col_key][row_key] }}>
+        {{ items[col_key][row_key] }}
+      </div>
+    {% endfor %}
+    </div>
+  {% endfor %}
+{% endif %}
+</div>
diff --git a/web/modules/contrib/views_responsive_grid/views_responsive_grid.css b/web/modules/contrib/views_responsive_grid/views_responsive_grid.css
new file mode 100644 (file)
index 0000000..46371d8
--- /dev/null
@@ -0,0 +1,8 @@
+.views-responsive-grid .views-row {
+  clear: both;
+  float: left;
+  width: 100%;
+}
+.views-responsive-grid .views-col {
+  float: left;
+}
diff --git a/web/modules/contrib/views_responsive_grid/views_responsive_grid.info.yml b/web/modules/contrib/views_responsive_grid/views_responsive_grid.info.yml
new file mode 100644 (file)
index 0000000..ca47139
--- /dev/null
@@ -0,0 +1,8 @@
+name: Views Responsive Grid
+type: module
+description: Views plugin for displaying views content in a responsive grid.
+package: Views
+version: VERSION
+core: 8.x
+dependencies:
+  - views
diff --git a/web/modules/contrib/views_responsive_grid/views_responsive_grid.module b/web/modules/contrib/views_responsive_grid/views_responsive_grid.module
new file mode 100644 (file)
index 0000000..687cbba
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+/**
+ * @file
+ * Provides a Views style plugin to display content in a responsive grid.
+ */
+
+/**
+ * Implements hook_theme_registry_alter().
+ */
+function views_responsive_grid_theme_registry_alter(&$theme_registry) {
+  // For some issue in core, we need to set our theme path ourselves.
+  // @see http://drupal.org/node/1911492
+  $template =& $theme_registry['views_view_responsive_grid'];
+  $template['theme path'] = drupal_get_path('module', 'views_responsive_grid');
+  $template['path'] = drupal_get_path('module', 'views_responsive_grid') . '/templates';
+}
diff --git a/web/modules/contrib/views_responsive_grid/views_responsive_grid.theme.inc b/web/modules/contrib/views_responsive_grid/views_responsive_grid.theme.inc
new file mode 100644 (file)
index 0000000..bf8cfe9
--- /dev/null
@@ -0,0 +1,158 @@
+<?php
+
+/**
+ * @file
+ * Defines theming processors for views_responsive_grid.
+ */
+
+use Drupal\Core\Template\Attribute;
+
+/**
+ * Process variables for views responsive grid style templates.
+ *
+ * Default template: views-view-responsive-grid.html.twig.
+ *
+ * @param array $vars
+ *   An associative array containing:
+ *   - view: The view object.
+ *   - rows: An array of row items. Each row is an array of content.
+ *   - options: The view object style plugin options.
+ */
+function template_preprocess_views_view_responsive_grid(&$vars) {
+  drupal_add_css(drupal_get_path('module', 'views_responsive_grid') . '/views_responsive_grid.css');
+  
+  $options = $vars['options'] = $vars['view']->style_plugin->options;
+  $horizontal = ($options['alignment'] === 'horizontal');
+
+  // View classes.
+  $vars['attributes'] = new Attribute(array(
+    'class' => array(
+      'views-responsive-grid',
+      $options['alignment'],
+      'cols-' . $options['columns'],
+      'clearfix',
+    ),
+  ));
+
+  // Setup integers.
+  $col = 0;
+  $row = 0;
+  $row_count = count($vars['rows']);
+  $remainders = $row_count % $options['columns'];
+  $num_rows = floor($row_count / $options['columns']);
+  // Iterate over the view rows array.
+  foreach ($vars['rows'] as $row_index => $item) {
+    // Add the current views data row to the rows array.
+    if ($horizontal) {
+      $items[$row][$col] = $item;
+    }
+    else {
+      $items[$col][$row] = $item;
+    }
+
+    // Create attributes for row.
+    $row_attributes = array('class' => array());
+    // Add default views row classes.
+    if ($options['default_row_class']) {
+      $row_attributes['class'][] = 'views-row';
+      $row_attributes['class'][] = 'row-' . ($row + 1);
+    }
+    // Add special views row classes.
+    if ($options['row_class_special']) {
+      // First row.
+      if ($row == 0) {
+        $row_attributes['class'][] = 'first';
+      }
+      // Last row.
+      if (($horizontal && $row == (!$remainders ? $num_rows - 1 : $num_rows)) || (!$horizontal && ((!$remainders && $row == $num_rows - 1) || ($remainders && $row == $num_rows)))) {
+        $row_attributes['class'][] = 'last';
+      }
+      // Zebra striping.
+      $row_attributes['class'][] = (($row + 1) % 2) ? 'odd' : 'even';
+      // Clearfix
+      if ($horizontal) {
+        $row_attributes['class'][] = 'clearfix';
+      }
+    }
+    // Add custom row class.
+    $row_class = array_filter(explode(' ', $options['row_class']));
+    if (!empty($row_class)) {
+      $row_attributes['class'] = array_merge($row_attributes['class'], $row_class);
+    }
+    // Add row attributes to variables array.
+    if ($horizontal) {
+      $vars['row_attributes'][$row] = new Attribute($row_attributes);
+    }
+    else {
+      $vars['row_attributes'][$col][$row] = new Attribute($row_attributes);
+    }
+  
+    // Create attributes for columns.
+    $col_attributes = array('class' => array());
+    // Add default views column classes.
+    if ($options['default_col_class']) {
+      $col_attributes['class'][] = 'views-col';
+      $col_attributes['class'][] = 'col-' . ($col + 1);
+    }
+    // Add special views row classes.
+    if ($options['col_class_special']) {
+      if ($col == 0) {
+        $col_attributes['class'][] = 'first';
+      }
+      if ($col == $options['columns'] - 1 || ($row_count <= $num_rows && $col == $row_count)) {
+        $col_attributes['class'][] = 'last';
+      }
+      // Zebra striping.
+      $col_attributes['class'][] = (($col + 1) % 2) ? 'odd' : 'even';
+      // Clearfix
+      if (!$horizontal) {
+        $col_attributes['class'][] = 'clearfix';
+      }
+    }
+    // Add custom column class.
+    $col_class = array_filter(explode(' ', $options['col_class']));
+    if (!empty($col_class)) {
+      $col_attributes['class'] = array_merge($col_attributes['class'], $col_class);
+    }
+    // Add width to columns.
+    if ($options['columns'] > 1 && $options['automatic_width']) {
+      $col_attributes['style'] = 'width: ' . (100 / $options['columns']) . '%;';
+    }
+    // Add column attributes to variables array.
+    if ($horizontal) {
+      $vars['col_attributes'][$row][$col] = new Attribute($col_attributes);
+    }
+    else {
+      $vars['col_attributes'][$col] = new Attribute($col_attributes);
+    }
+    
+    // Increase, decrease or reset the appropriate integers.
+    if ($horizontal) {
+      if ($col == 0) {
+        $col++;
+      }
+      elseif ($col >= ($options['columns'] - 1)) {
+        $col = 0;
+        $row++;
+      }
+      else {
+        $col++;
+      }
+    }
+    else {
+      $row++;
+      if (!$remainders && $row == $num_rows) {
+        $row = 0;
+        $col++;
+      }
+      elseif ($remainders && $row == $num_rows + 1) {
+        $row = 0;
+        $col++;
+        $remainders--;
+      }
+    }
+    $row_count--;
+  }
+  // Save the grid items to the variables array.
+  $vars['items'] = $items;
+}