From 8acec36f19c470dfcda1ae2336826a782f41874c Mon Sep 17 00:00:00 2001 From: Jeff Veit Date: Fri, 23 Jun 2017 02:05:03 +0100 Subject: [PATCH] Further modules included. --- web/modules/contrib/advanced_help | 1 - web/modules/contrib/advanced_help/README.txt | 49 + .../advanced_help/advanced_help.info.yml | 6 + .../advanced_help/advanced_help.install | 50 + .../advanced_help/advanced_help.libraries.yml | 10 + .../advanced_help.links.menu.yml | 6 + .../advanced_help.links.task.yml | 3 + .../advanced_help/advanced_help.module | 76 + .../advanced_help.permissions.yml | 9 + .../advanced_help/advanced_help.routing.yml | 23 + .../advanced_help/advanced_help.services.yml | 4 + .../contrib/advanced_help/composer.json | 22 + .../contrib/advanced_help/css/help-icon.css | 18 + .../contrib/advanced_help/css/help.css | 69 + .../advanced_help/help/advanced_help.help.yml | 14 + .../contrib/advanced_help/help/ahelp_tab.png | Bin 0 -> 11058 bytes .../contrib/advanced_help/help/click_icon.png | Bin 0 -> 4933 bytes .../contrib/advanced_help/help/ini-file.html | 117 + .../contrib/advanced_help/help/readme.html | 107 + .../advanced_help/help/translation.html | 44 + .../help/using-advanced-help.html | 152 + .../advanced_help/help/why-advanced-help.html | 44 + .../help/180px-Andi_Gutmans_1.jpg | Bin 0 -> 11007 bytes .../help_example/help/180px-Lerdorf.jpg | Bin 0 -> 14535 bytes .../help/180px-PHP_Hello_World_screenshot.png | Bin 0 -> 10133 bytes .../help_example/help/about-php.html | 5 + .../help_example/help/help_example.help.ini | 24 + .../help_example/help/history.html | 23 + .../help_example/help/security.html | 1 + .../help_example/help/syntax.html | 38 + .../help_example/help/usage.html | 9 + .../help_example/help_example.info | 4 + .../help_example/help_example.module | 31 + .../contrib/advanced_help/images/help.png | Bin 0 -> 1025 bytes .../advanced_help/src/AdvancedHelpManager.php | 233 ++ .../src/Controller/AdvancedHelpController.php | 391 +++ .../advanced_help/src/HelpInterface.php | 11 + .../Plugin/Derivative/DynamicLocalTasks.php | 32 + .../src/Plugin/Search/AdvancedHelpSearch.php | 267 ++ .../templates/advanced-help-topic.html.twig | 3 + web/modules/contrib/better_formats | 1 - web/modules/contrib/better_formats/README.txt | 32 + .../better_formats/better_formats.info.yml | 5 + .../better_formats/better_formats.install | 14 + .../better_formats.links.task.yml | 4 + .../better_formats/better_formats.module | 429 +++ .../better_formats.permissions.yml | 10 + .../better_formats/better_formats.routing.yml | 7 + .../contrib/better_formats/composer.json | 8 + .../install/better_formats.settings.yml | 1 + .../config/schema/better_formats.schema.yml | 6 + .../src/BetterFormatsPermissions.php | 61 + .../better_formats/src/Form/SettingsForm.php | 65 + .../BetterFormatsFilterFormatAccessTest.php | 27 + .../src/Tests/BetterFormatsTermTest.php | 27 + web/modules/contrib/ckeditor_widgets | 1 - .../contrib/ckeditor_widgets/CHANGELOG.txt | 7 + .../contrib/ckeditor_widgets/README.md | 34 + .../ckeditor_widgets.info.yml | 10 + .../js/plugins/widgetbootstrap/contents.css | 126 + .../dialogs/widgetbootstrapAlert.js | 67 + .../dialogs/widgetfoundationAccordion.js | 61 + .../icons/widgetbootstrapAccordion.png | Bin 0 -> 182 bytes .../icons/widgetbootstrapAlert.png | Bin 0 -> 162 bytes .../icons/widgetbootstrapLeftCol.png | Bin 0 -> 167 bytes .../icons/widgetbootstrapRightCol.png | Bin 0 -> 170 bytes .../icons/widgetbootstrapThreeCol.png | Bin 0 -> 154 bytes .../icons/widgetbootstrapTwoCol.png | Bin 0 -> 154 bytes .../js/plugins/widgetbootstrap/plugin.js | 326 ++ .../widgetbootstrap/samples/index.html | 179 ++ .../widgetbootstrap/samples/simplebox.html | 86 + .../js/plugins/widgetcommon/contents.css | 48 + .../widgetcommon/dialogs/foundation2Col.js | 43 + .../dialogs/widgetfoundationAccordion.js | 61 + .../widgetcommon/icons/widgetcommonBox.png | Bin 0 -> 286 bytes .../icons/widgetcommonQuotebox.png | Bin 0 -> 597 bytes .../js/plugins/widgetcommon/plugin.js | 104 + .../plugins/widgetcommon/samples/contents.css | 35 + .../widgetcommon/samples/simplebox.html | 86 + .../extraIcons/codesnippet.png | Bin 0 -> 597 bytes .../extraIcons/drupalbreak.png | Bin 0 -> 165 bytes .../extraIcons/fontawesome.png | Bin 0 -> 1228 bytes .../widgettemplatemenu/extraIcons/leaflet.png | Bin 0 -> 269 bytes .../widgettemplatemenu/extraIcons/oembed.png | Bin 0 -> 3143 bytes .../icons/widgettemplatemenu.png | Bin 0 -> 1019 bytes .../js/plugins/widgettemplatemenu/plugin.js | 90 + .../widgettemplatemenu/samples/contents.css | 35 + .../widgettemplatemenu/samples/simplebox.html | 86 + .../Plugin/CKEditorPlugin/WidgetBootstrap.php | 88 + .../Plugin/CKEditorPlugin/WidgetCommon.php | 72 + .../CKEditorPlugin/WidgetTemplateMenu.php | 84 + web/modules/contrib/drupalmoduleupgrader | 1 - .../contrib/drupalmoduleupgrader/.gitignore | 7 + .../contrib/drupalmoduleupgrader/.gitmodules | 0 .../contrib/drupalmoduleupgrader/README.txt | 190 ++ .../drupalmoduleupgrader/composer.json | 33 + .../drupalmoduleupgrader/composer.lock | 1223 ++++++++ ...drupalmoduleupgrader.entity_operations.yml | 19 + .../drupalmoduleupgrader.functions.yml | 1000 ++++++ .../install/drupalmoduleupgrader.grep.yml | 166 + .../install/drupalmoduleupgrader.hooks.yml | 269 ++ .../drupalmoduleupgrader.rewriters.yml | 141 + .../install/drupalmoduleupgrader.tags.yml | 38 + .../drupalmoduleupgrader.drush.inc | 322 ++ .../drupalmoduleupgrader.info.yml | 12 + .../drupalmoduleupgrader.module | 124 + .../drupalmoduleupgrader.services.yml | 95 + .../drupalmoduleupgrader/phpunit.xml.dist | 15 + .../drupalmoduleupgrader/src/AnalyzerBase.php | 39 + .../src/AnalyzerInterface.php | 21 + .../src/Annotation/Analyzer.php | 67 + .../src/Annotation/Converter.php | 58 + .../src/Annotation/Fixer.php | 33 + .../src/Annotation/Indexer.php | 38 + .../src/Annotation/Rewriter.php | 45 + .../drupalmoduleupgrader/src/ArrayIndexer.php | 62 + .../src/ConverterBase.php | 285 ++ .../src/ConverterInterface.php | 30 + .../src/DependencyCollectorTrait.php | 23 + .../drupalmoduleupgrader/src/DeriverBase.php | 40 + .../drupalmoduleupgrader/src/FixerBase.php | 66 + .../src/FixerInterface.php | 26 + .../drupalmoduleupgrader/src/IOException.php | 9 + .../drupalmoduleupgrader/src/IndexerBase.php | 199 ++ .../src/IndexerExecutionInterface.php | 19 + .../src/IndexerInterface.php | 48 + .../src/IndexerUsageInterface.php | 9 + .../drupalmoduleupgrader/src/Issue.php | 231 ++ .../src/IssueInterface.php | 166 + .../src/Plugin/DMU/Analyzer/DB.php | 53 + .../src/Plugin/DMU/Analyzer/DBDeriver.php | 36 + .../src/Plugin/DMU/Analyzer/FlagHook.php | 31 + .../Plugin/DMU/Analyzer/FlagHookDeriver.php | 60 + .../src/Plugin/DMU/Analyzer/FunctionCall.php | 39 + .../DMU/Analyzer/FunctionCallDeriver.php | 63 + .../src/Plugin/DMU/Analyzer/Grep.php | 36 + .../src/Plugin/DMU/Analyzer/HookFormAlter.php | 70 + .../Plugin/DMU/Analyzer/HookPermission.php | 48 + .../src/Plugin/DMU/Analyzer/HookUninstall.php | 47 + .../src/Plugin/DMU/Analyzer/InfoFile.php | 70 + .../src/Plugin/DMU/Analyzer/PSR4.php | 46 + .../src/Plugin/DMU/Analyzer/Tests.php | 63 + .../src/Plugin/DMU/Converter/Blocks.php | 99 + .../src/Plugin/DMU/Converter/EntityHooks.php | 104 + .../Converter/Functions/CToolsGetPlugins.php | 66 + .../Functions/CToolsObjectCacheGet.php | 27 + .../Functions/CToolsObjectCacheSet.php | 35 + .../DMU/Converter/Functions/CacheGet.php | 31 + .../DMU/Converter/Functions/CacheSet.php | 40 + .../DMU/Converter/Functions/CommentLoad.php | 39 + .../src/Plugin/DMU/Converter/Functions/DB.php | 32 + .../DMU/Converter/Functions/DBDeriver.php | 36 + .../DMU/Converter/Functions/Disable.php | 23 + .../Converter/Functions/DisableDeriver.php | 51 + .../Converter/Functions/DrupalGetTitle.php | 28 + .../DMU/Converter/Functions/DrupalIsCLI.php | 24 + .../Converter/Functions/DrupalMapAssoc.php | 30 + .../Converter/Functions/DrupalWriteRecord.php | 48 + .../DMU/Converter/Functions/EntityCreate.php | 30 + .../DMU/Converter/Functions/EntityGetInfo.php | 34 + .../DMU/Converter/Functions/EntityLoad.php | 55 + .../Converter/Functions/EntityOperation.php | 27 + .../Functions/EntityOperationDeriver.php | 60 + .../Functions/FieldInfoFieldTypes.php | 35 + .../Functions/FieldInfoFormatterTypes.php | 35 + .../Functions/FieldInfoWidgetTypes.php | 35 + .../Converter/Functions/FieldUpdateField.php | 24 + .../Functions/FieldUpdateInstance.php | 24 + .../Converter/Functions/FieldViewField.php | 35 + .../Converter/Functions/FieldViewValue.php | 31 + .../Functions/FormExecuteHandlers.php | 37 + .../Converter/Functions/FormLoadInclude.php | 34 + .../DMU/Converter/Functions/FormSetValue.php | 28 + .../Converter/Functions/FormStateDefaults.php | 27 + .../Functions/FormStateValuesClean.php | 24 + .../Functions/FunctionCallModifier.php | 76 + .../Plugin/DMU/Converter/Functions/GetT.php | 24 + .../src/Plugin/DMU/Converter/Functions/L.php | 39 + .../DMU/Converter/Functions/LoadMultiple.php | 53 + .../Functions/LoadMultipleDeriver.php | 34 + .../DMU/Converter/Functions/ModuleInvoke.php | 36 + .../Converter/Functions/ModuleInvokeAll.php | 35 + .../DMU/Converter/Functions/NodeLoad.php | 50 + .../src/Plugin/DMU/Converter/Functions/St.php | 23 + .../Converter/Functions/ThemeGetRegistry.php | 30 + .../Plugin/DMU/Converter/Functions/URL.php | 79 + .../DMU/Converter/Functions/UserAccess.php | 35 + .../DMU/Converter/Functions/UserLoad.php | 39 + .../DMU/Converter/Functions/UserSave.php | 29 + .../DMU/Converter/Functions/VariableAPI.php | 51 + .../DMU/Converter/Functions/VariableDel.php | 30 + .../DMU/Converter/Functions/VariableGet.php | 91 + .../DMU/Converter/Functions/VariableSet.php | 34 + .../DMU/Converter/Functions/Watchdog.php | 63 + .../src/Plugin/DMU/Converter/Grep.php | 80 + .../src/Plugin/DMU/Converter/HookBoot.php | 49 + .../Plugin/DMU/Converter/HookEntityInfo.php | 87 + .../DMU/Converter/HookEntityTypeView.php | 54 + .../src/Plugin/DMU/Converter/HookExit.php | 43 + .../Converter/HookFieldAttachCreateBundle.php | 29 + .../Converter/HookFieldAttachDeleteBundle.php | 38 + .../Converter/HookFieldAttachRenameBundle.php | 29 + .../DMU/Converter/HookFieldFormatterInfo.php | 51 + .../DMU/Converter/HookFieldWidgetInfo.php | 51 + .../Plugin/DMU/Converter/HookFormAlter.php | 43 + .../src/Plugin/DMU/Converter/HookInit.php | 43 + .../src/Plugin/DMU/Converter/HookLibrary.php | 47 + .../Plugin/DMU/Converter/HookMenuAlter.php | 87 + .../Plugin/DMU/Converter/HookNodePrepare.php | 57 + .../Plugin/DMU/Converter/HookPermission.php | 46 + .../DMU/Converter/HookURLOutboundAlter.php | 41 + .../Plugin/DMU/Converter/HookUserLogin.php | 66 + .../src/Plugin/DMU/Converter/HookWatchdog.php | 35 + .../src/Plugin/DMU/Converter/InfoToYAML.php | 97 + .../src/Plugin/DMU/Converter/Links.php | 128 + .../src/Plugin/DMU/Converter/PSR4.php | 60 + .../src/Plugin/DMU/Converter/Routing.php | 90 + .../src/Plugin/DMU/Converter/Tests.php | 201 ++ .../src/Plugin/DMU/Converter/UnitTests.php | 62 + .../src/Plugin/DMU/Converter/UserHooks.php | 89 + .../src/Plugin/DMU/Fixer/CreateClass.php | 84 + .../src/Plugin/DMU/Fixer/Define.php | 29 + .../src/Plugin/DMU/Fixer/Delete.php | 33 + .../src/Plugin/DMU/Fixer/Disable.php | 35 + .../Plugin/DMU/Fixer/FormCallbackToMethod.php | 43 + .../src/Plugin/DMU/Fixer/HookToYAML.php | 26 + .../src/Plugin/DMU/Fixer/Implement.php | 43 + .../src/Plugin/DMU/Fixer/ImplementHook.php | 62 + .../Plugin/DMU/Fixer/NodeCollectorTrait.php | 30 + .../src/Plugin/DMU/Fixer/Notify.php | 72 + .../src/Plugin/DMU/Fixer/PSR4.php | 74 + .../src/Plugin/DMU/Indexer/Classes.php | 118 + .../src/Plugin/DMU/Indexer/Constants.php | 94 + .../src/Plugin/DMU/Indexer/FunctionCalls.php | 69 + .../src/Plugin/DMU/Indexer/Functions.php | 212 ++ .../src/Plugin/DMU/Rewriter/FormState.php | 214 ++ .../src/Plugin/DMU/Rewriter/Generic.php | 306 ++ .../Plugin/DMU/Rewriter/GenericDeriver.php | 56 + .../src/Plugin/DMU/Routing/ContentRoute.php | 241 ++ .../src/Plugin/DMU/Routing/FormRoute.php | 109 + .../drupalmoduleupgrader/src/PluginBase.php | 53 + .../drupalmoduleupgrader/src/Report.php | 45 + .../src/ReportInterface.php | 33 + .../src/RewriterInterface.php | 28 + .../src/Routing/Drupal7/RouteWrapper.php | 257 ++ .../src/Routing/Drupal7/Router.php | 99 + .../src/Routing/Drupal8/RouteWrapper.php | 133 + .../src/Routing/HookMenu.php | 169 + .../src/Routing/LinkBinding/LinkBinding.php | 107 + .../LinkBinding/LinkBindingFactory.php | 51 + .../LinkBinding/LocalActionLinkBinding.php | 20 + .../LinkBinding/LocalTaskLinkBinding.php | 86 + .../Routing/LinkBinding/MenuLinkBinding.php | 29 + .../src/Routing/LinkIndex.php | 60 + .../src/Routing/ParameterBinding.php | 122 + .../src/Routing/ParameterMap.php | 156 + .../src/Routing/RouteConverterInterface.php | 51 + .../src/Routing/RouteWrapperInterface.php | 55 + .../src/Routing/RouterBase.php | 46 + .../src/Routing/RouterBuiltEvent.php | 39 + .../src/Routing/RouterInterface.php | 31 + .../drupalmoduleupgrader/src/Target.php | 279 ++ .../src/TargetInterface.php | 126 + .../Utility/Filter/ContainsLogicFilter.php | 68 + .../src/Utility/Filter/FieldValueFilter.php | 45 + .../Filter/FunctionCallArgumentFilter.php | 40 + .../Utility/Filter/NodeAssignmentFilter.php | 24 + .../src/Utility/FormConverter.php | 178 ++ .../src/Utility/FormConverterFactory.php | 52 + .../Utility/Path/Drupal7/PathComponent.php | 33 + .../src/Utility/Path/Drupal7/PathUtility.php | 64 + .../Utility/Path/Drupal8/PathComponent.php | 24 + .../src/Utility/Path/Drupal8/PathUtility.php | 24 + .../src/Utility/Path/PathComponentBase.php | 34 + .../Utility/Path/PathComponentInterface.php | 34 + .../src/Utility/Path/PathUtilityBase.php | 140 + .../src/Utility/Path/PathUtilityInterface.php | 61 + .../src/Utility/StringTransformTrait.php | 148 + .../templates/Block.html.twig | 64 + .../templates/Controller.html.twig | 15 + .../templates/EntityType.html.twig | 34 + .../templates/EventSubscriber.html.twig | 20 + .../templates/Form.html.twig | 51 + .../templates/Formatter.html.twig | 32 + .../templates/Issue.html.twig | 27 + .../templates/Logger.html.twig | 25 + .../templates/OutboundPathProcessor.html.twig | 14 + .../templates/Report.html.twig | 87 + .../templates/RouteSubscriber.html.twig | 29 + .../templates/Widget.html.twig | 32 + .../drupalmoduleupgrader/tests/bootstrap.php | 4 + .../tests/src/Unit/ContainerMockTrait.php | 46 + .../tests/src/Unit/IssueTest.php | 94 + .../tests/src/Unit/ModuleMockerTrait.php | 31 + .../Plugin/DMU/Analyzer/AnalyzerTestBase.php | 43 + .../src/Unit/Plugin/DMU/Analyzer/DBTest.php | 47 + .../Unit/Plugin/DMU/Analyzer/FlagHookTest.php | 47 + .../Plugin/DMU/Analyzer/FunctionCallTest.php | 45 + .../Plugin/DMU/Analyzer/HookFormAlterTest.php | 61 + .../DMU/Analyzer/HookPermissionTest.php | 53 + .../Plugin/DMU/Analyzer/HookUninstallTest.php | 47 + .../Unit/Plugin/DMU/Analyzer/InfoFileTest.php | 38 + .../src/Unit/Plugin/DMU/Analyzer/PSR4Test.php | 41 + .../Unit/Plugin/DMU/Analyzer/TestsTest.php | 42 + .../Functions/CToolsGetPluginsTest.php | 41 + .../Functions/CToolsObjectCacheGetTest.php | 20 + .../Functions/CToolsObjectCacheSetTest.php | 27 + .../DMU/Converter/Functions/CacheGetTest.php | 27 + .../DMU/Converter/Functions/CacheSetTest.php | 34 + .../Converter/Functions/CommentLoadTest.php | 37 + .../Plugin/DMU/Converter/Functions/DBTest.php | 31 + .../DMU/Converter/Functions/DisableTest.php | 26 + .../Functions/DrupalGetTitleTest.php | 25 + .../Converter/Functions/DrupalIsCLITest.php | 20 + .../Functions/DrupalMapAssocTest.php | 21 + .../Functions/DrupalWriteRecordTest.php | 34 + .../Converter/Functions/EntityCreateTest.php | 20 + .../Converter/Functions/EntityGetInfoTest.php | 27 + .../Functions/FieldInfoFieldTypesTest.php | 27 + .../Functions/FieldInfoFormatterTypesTest.php | 27 + .../Functions/FieldInfoWidgetTypesTest.php | 27 + .../Functions/FieldUpdateFieldTest.php | 20 + .../Functions/FieldUpdateInstanceTest.php | 20 + .../Functions/FieldViewFieldTest.php | 27 + .../Functions/FieldViewValueTest.php | 27 + .../Functions/FormExecuteHandlersTest.php | 33 + .../Functions/FormLoadIncludeTest.php | 27 + .../Converter/Functions/FormSetValueTest.php | 20 + .../Functions/FormStateValuesCleanTest.php | 20 + .../FunctionCallModifierTestBase.php | 32 + .../DMU/Converter/Functions/GetTTest.php | 21 + .../Functions/ModuleInvokeAllTest.php | 27 + .../Converter/Functions/ModuleInvokeTest.php | 27 + .../DMU/Converter/Functions/NodeLoadTest.php | 56 + .../Plugin/DMU/Converter/Functions/StTest.php | 21 + .../Functions/ThemeGetRegistryTest.php | 32 + .../Converter/Functions/UserAccessTest.php | 27 + .../DMU/Converter/Functions/UserLoadTest.php | 37 + .../DMU/Converter/Functions/UserSaveTest.php | 30 + .../Converter/Functions/VariableDelTest.php | 61 + .../Converter/Functions/VariableGetTest.php | 83 + .../Converter/Functions/VariableSetTest.php | 61 + .../DMU/Converter/Functions/WatchdogTest.php | 62 + .../Unit/Plugin/DMU/Fixer/CreateClassTest.php | 58 + .../src/Unit/Plugin/DMU/Fixer/DefineTest.php | 33 + .../src/Unit/Plugin/DMU/Fixer/DeleteTest.php | 53 + .../src/Unit/Plugin/DMU/Fixer/DisableTest.php | 54 + .../DMU/Fixer/FormCallbackToMethodTest.php | 68 + .../Unit/Plugin/DMU/Fixer/HookToYAMLTest.php | 47 + .../Plugin/DMU/Fixer/ImplementHookTest.php | 53 + .../Unit/Plugin/DMU/Fixer/ImplementTest.php | 55 + .../src/Unit/Plugin/DMU/Fixer/NotifyTest.php | 51 + .../src/Unit/Plugin/DMU/Fixer/PSR4Test.php | 40 + .../Unit/Plugin/DMU/Indexer/ClassesTest.php | 29 + .../Unit/Plugin/DMU/Indexer/FunctionsTest.php | 33 + .../Plugin/DMU/Indexer/IndexerTestBase.php | 46 + .../Plugin/DMU/Rewriter/FormStateTest.php | 93 + .../Unit/Plugin/DMU/Rewriter/GenericTest.php | 140 + .../tests/src/Unit/ReportTest.php | 39 + .../Unit/Routing/Drupal7/RouteWrapperTest.php | 128 + .../src/Unit/Routing/Drupal7/RouterTest.php | 171 ++ .../Unit/Routing/Drupal8/RouteWrapperTest.php | 35 + .../Routing/LinkBinding/LinkBindingTest.php | 74 + .../src/Unit/Routing/ParameterBindingTest.php | 117 + .../tests/src/Unit/Routing/RouterBaseTest.php | 68 + .../tests/src/Unit/SQLiteDatabaseTrait.php | 28 + .../tests/src/Unit/TargetTest.php | 115 + .../tests/src/Unit/TestBase.php | 82 + .../Filter/ContainsLogicFilterTest.php | 62 + .../Utility/Filter/FieldValueFilterTest.php | 39 + .../Filter/FunctionCallArgumentFilterTest.php | 40 + .../Filter/NodeAssignmentFilterTest.php | 35 + .../Path/Drupal7/PathComponentTest.php | 25 + .../Utility/Path/Drupal7/PathUtilityTest.php | 113 + .../Path/Drupal8/PathComponentTest.php | 19 + .../Utility/Path/Drupal8/PathUtilityTest.php | 95 + web/modules/contrib/filefield_sources | 1 - .../contrib/filefield_sources/README.txt | 43 + .../filefield_sources.data_types.schema.yml | 5 + .../schema/filefield_sources.schema.yml | 9 + ...d_sources.setting.source_attach.schema.yml | 13 + ...eld_sources.setting.source_imce.schema.yml | 7 + ...ources.setting.source_reference.schema.yml | 10 + ...lefield_sources.setting.sources.schema.yml | 5 + ...filefield_sources.source.attach.schema.yml | 3 + ...efield_sources.source.clipboard.schema.yml | 3 + .../filefield_sources.source.imce.schema.yml | 3 + ...efield_sources.source.reference.schema.yml | 3 + ...filefield_sources.source.remote.schema.yml | 3 + ...filefield_sources.source.upload.schema.yml | 5 + .../css/filefield_sources.css | 44 + .../filefield_sources.api.php | 42 + .../filefield_sources.info.yml | 8 + .../filefield_sources.install | 23 + .../filefield_sources.libraries.yml | 9 + .../filefield_sources.module | 771 +++++ .../filefield_sources.routing.yml | 2 + .../filefield_sources.services.yml | 12 + .../filefield_sources/js/filefield_sources.js | 262 ++ .../src/Access/FieldAccessCheck.php | 38 + .../src/Annotation/FilefieldSource.php | 62 + .../src/Controller/ImceController.php | 111 + .../MimeType/ExtensionMimeTypeGuesser.php | 75 + .../src/FilefieldSourceInterface.php | 55 + .../src/FilefieldSourceManager.php | 54 + .../src/FilefieldSourcesServiceProvider.php | 26 + .../filefield_sources/src/ImceScanner.php | 102 + .../src/Plugin/FilefieldSource/Attach.php | 356 +++ .../src/Plugin/FilefieldSource/Clipboard.php | 152 + .../src/Plugin/FilefieldSource/Imce.php | 216 ++ .../src/Plugin/FilefieldSource/Reference.php | 239 ++ .../src/Plugin/FilefieldSource/Remote.php | 400 +++ .../MimeType/ExtensionMimeTypeGuesser.php | 108 + .../src/Routing/FilefieldSourcesRoutes.php | 35 + .../src/Tests/AttachSourceTest.php | 168 + .../src/Tests/ClipboardSourceTest.php | 37 + .../src/Tests/EmptyValuesTest.php | 90 + .../src/Tests/FileFieldSourcesTestBase.php | 450 +++ .../src/Tests/ImceSourceTest.php | 52 + .../src/Tests/MultipleValuesTest.php | 133 + .../src/Tests/ReferenceSourceTest.php | 109 + .../src/Tests/RemoteSourceTest.php | 38 + .../src/Tests/UploadSourceTest.php | 62 + web/modules/contrib/libraries | 1 - web/modules/contrib/libraries/CHANGELOG.txt | 163 + web/modules/contrib/libraries/README.txt | 40 + web/modules/contrib/libraries/composer.json | 14 + .../config/install/libraries.settings.yml | 10 + .../config/schema/libraries.schema.yml | 53 + .../contrib/libraries/libraries.api.php | 609 ++++ .../contrib/libraries/libraries.drush.inc | 169 + .../contrib/libraries/libraries.info.yml | 4 + .../contrib/libraries/libraries.install | 28 + .../contrib/libraries/libraries.module | 868 ++++++ .../contrib/libraries/libraries.services.yml | 73 + .../libraries/src/Annotation/LibraryType.php | 14 + .../libraries/src/Annotation/Locator.php | 14 + .../src/Annotation/VersionDetector.php | 14 + .../src/Config/LibrariesConfigSubscriber.php | 51 + .../ExternalLibrary/Asset/AssetLibrary.php | 128 + .../Asset/AssetLibraryInterface.php | 65 + ...hableAssetLibraryRegistrationInterface.php | 22 + .../Asset/LocalRemoteAssetTrait.php | 108 + .../Asset/MultipleAssetLibrary.php | 123 + .../Asset/MultipleAssetLibraryInterface.php | 64 + .../Definition/ChainDefinitionDiscovery.php | 79 + .../Definition/DefinitionDiscoveryFactory.php | 104 + .../DefinitionDiscoveryInterface.php | 45 + .../Definition/FileDefinitionDiscovery.php | 36 + .../FileDefinitionDiscoveryBase.php | 82 + .../Definition/GuzzleDefinitionDiscovery.php | 71 + .../WritableDefinitionDiscoveryInterface.php | 25 + .../WritableFileDefinitionDiscovery.php | 20 + .../Dependency/DependentLibraryInterface.php | 22 + .../Dependency/DependentLibraryTrait.php | 29 + .../InvalidLibraryDependencyException.php | 45 + .../LibraryDefinitionNotFoundException.php | 38 + .../LibraryNotInstalledException.php | 39 + .../LibraryTypeNotFoundException.php | 38 + .../UnknownLibraryVersionException.php | 39 + .../src/ExternalLibrary/LibraryBase.php | 83 + .../src/ExternalLibrary/LibraryInterface.php | 44 + .../src/ExternalLibrary/LibraryManager.php | 120 + .../LibraryManagerInterface.php | 51 + .../Local/LocalLibraryInterface.php | 82 + .../Local/LocalLibraryTrait.php | 99 + .../Local/LocatorInterface.php | 22 + .../ExternalLibrary/Local/LocatorManager.php | 34 + .../PhpFile/PhpFileLibrary.php | 73 + .../PhpFile/PhpFileLibraryInterface.php | 22 + .../PhpFile/PhpFileLoaderInterface.php | 20 + .../PhpFile/PhpRequireLoader.php | 22 + .../Remote/RemoteLibraryInterface.php | 39 + .../Remote/RemoteLibraryTrait.php | 44 + .../Type/LibraryCreationListenerInterface.php | 20 + .../Type/LibraryLoadingListenerInterface.php | 20 + .../ExternalLibrary/Type/LibraryTypeBase.php | 87 + .../Type/LibraryTypeFactory.php | 32 + .../Type/LibraryTypeInterface.php | 28 + .../Utility/DependencyAccessorTrait.php | 27 + .../Utility/IdAccessorTrait.php | 30 + .../Utility/LibraryAccessorInterface.php | 18 + .../Utility/LibraryAccessorTrait.php | 27 + .../Utility/LibraryIdAccessorInterface.php | 18 + .../Utility/LibraryIdAccessorTrait.php | 27 + .../Version/VersionDetectorInterface.php | 25 + .../Version/VersionDetectorManager.php | 34 + .../Version/VersionedLibraryInterface.php | 65 + .../Version/VersionedLibraryTrait.php | 85 + .../MissingPluginConfigurationException.php | 50 + .../Plugin/libraries/Locator/ChainLocator.php | 53 + .../libraries/Locator/GlobalLocator.php | 76 + .../Plugin/libraries/Locator/UriLocator.php | 89 + .../libraries/Type/AssetLibraryType.php | 32 + .../Type/MultipleAssetLibraryType.php | 48 + .../libraries/Type/PhpFileLibraryType.php | 72 + .../VersionDetector/LinePatternDetector.php | 89 + .../VersionDetector/StaticDetector.php | 46 + .../StreamWrapper/AssetLibrariesStream.php | 40 + .../LibraryDefinitionsStream.php | 86 + .../StreamWrapper/LocalHiddenStreamTrait.php | 23 + .../StreamWrapper/PhpFileLibrariesStream.php | 40 + .../src/StreamWrapper/PrivateStreamTrait.php | 39 + .../libraries/src/Tests/LibrariesUnitTest.php | 53 + .../libraries/src/Tests/LibrariesWebTest.php | 525 ++++ .../vendor/test_asset_library/example.css | 0 .../vendor/test_asset_library/example.js | 0 .../example.first.css | 0 .../example.first.js | 0 .../example.second.css | 0 .../example.second.js | 0 .../libraries/tests/example/README.txt | 42 + .../libraries/tests/example/example_1.css | 11 + .../libraries/tests/example/example_1.js | 18 + .../libraries/tests/example/example_1.php | 12 + .../libraries/tests/example/example_2.css | 11 + .../libraries/tests/example/example_2.js | 18 + .../libraries/tests/example/example_2.php | 12 + .../libraries/tests/example/example_3.css | 11 + .../libraries/tests/example/example_3.js | 18 + .../libraries/tests/example/example_3.php | 12 + .../libraries/tests/example/example_4.css | 11 + .../libraries/tests/example/example_4.js | 18 + .../libraries/tests/example/example_4.php | 12 + .../example_info_file.libraries.info.yml | 2 + .../test_php_file_library.php | 16 + .../test_asset_library.json | 18 + .../test_asset_multiple_library.json | 32 + .../test_php_file_library.json | 6 + .../modules/libraries_test/libraries_test.css | 12 + .../modules/libraries_test/libraries_test.inc | 12 + .../libraries_test/libraries_test.info.yml | 10 + .../modules/libraries_test/libraries_test.js | 18 + .../libraries_test/libraries_test.module | 497 +++ .../libraries_test/libraries_test.routing.yml | 36 + .../src/Controller/ExampleController.php | 94 + .../DefinitionDiscoveryFactoryTest.php | 118 + .../Asset/AssetLibraryTest.php | 114 + .../Asset/AssetLibraryTestBase.php | 36 + .../Asset/MultipleAssetLibraryTest.php | 172 ++ .../ExternalLibrary/GlobalLocatorTest.php | 53 + .../PhpFile/PhpFileLibraryTest.php | 66 + .../TestLibraryFilesStream.php | 80 + .../src/Kernel/LibraryTypeKernelTestBase.php | 171 ++ .../LinePatternDetectorTest.php | 197 ++ web/modules/contrib/linkchecker | 1 - web/modules/contrib/linkchecker/CHANGELOG.txt | 3 + web/modules/contrib/linkchecker/README.txt | 51 + web/modules/contrib/linkchecker/composer.json | 22 + .../config/install/linkchecker.settings.yml | 34 + .../config/schema/linkchecker.schema.yml | 99 + .../js/linkchecker.content_types.js | 36 + .../contrib/linkchecker/linkchecker.batch.inc | 365 +++ .../contrib/linkchecker/linkchecker.drush.inc | 100 + .../contrib/linkchecker/linkchecker.info.yml | 5 + .../contrib/linkchecker/linkchecker.install | 199 ++ .../linkchecker/linkchecker.libraries.yml | 7 + .../linkchecker/linkchecker.links.menu.yml | 19 + .../linkchecker/linkchecker.links.task.yml | 13 + .../contrib/linkchecker/linkchecker.module | 2614 ++++++++++++++++ .../contrib/linkchecker/linkchecker.pages.inc | 233 ++ .../linkchecker/linkchecker.permissions.yml | 13 + .../linkchecker/linkchecker.redirect.inc | 52 + .../linkchecker/linkchecker.routing.yml | 37 + .../d6_linkchecker_settings.yml | 51 + .../d7_linkchecker_settings.yml | 51 + .../Controller/LinkCheckerAdminReportPage.php | 17 + .../Controller/LinkCheckerUserReportPage.php | 32 + .../src/Form/LinkCheckerAdminSettingsForm.php | 407 +++ .../Form/LinkCheckerEditLinkSettingsForm.php | 117 + .../src/Tests/LinkCheckerInterfaceTest.php | 214 ++ .../Tests/LinkCheckerLinkExtractionTest.php | 395 +++ web/modules/contrib/livereload | 1 - web/modules/contrib/livereload/README.txt | 45 + .../config/install/livereload.settings.yml | 2 + .../config/schema/livereload.schema.yml | 8 + web/modules/contrib/livereload/js/pseudo.js | 7 + .../contrib/livereload/livereload.info.yml | 5 + .../livereload/livereload.libraries.yml | 3 + .../contrib/livereload/livereload.module | 92 + .../livereload/livereload.permissions.yml | 3 + web/modules/contrib/media | 1 - web/modules/contrib/media/.travis.yml | 124 + web/modules/contrib/media/README.md | 44 + web/modules/contrib/media/composer.json | 13 + ...ty_form_display.media.document.default.yml | 40 + ...ity_form_display.media.gallery.default.yml | 42 + ...ntity_form_display.media.image.default.yml | 43 + ...y_form_display.media.instagram.default.yml | 50 + ...ntity_form_display.media.tweet.default.yml | 52 + ...ntity_form_display.media.video.default.yml | 39 + ...ty_view_display.media.document.default.yml | 57 + ...w_display.media.document.media_library.yml | 67 + ...ity_view_display.media.gallery.default.yml | 105 + ...ew_display.media.gallery.media_library.yml | 50 + ...ntity_view_display.media.image.default.yml | 49 + ...view_display.media.image.media_library.yml | 50 + ...y_view_display.media.instagram.default.yml | 50 + ..._display.media.instagram.media_library.yml | 49 + ...ntity_view_display.media.tweet.default.yml | 50 + ...view_display.media.tweet.media_library.yml | 51 + ...ntity_view_display.media.video.default.yml | 54 + ...view_display.media.video.media_library.yml | 54 + .../core.entity_view_mode.media.gallery.yml | 12 + ...e.entity_view_mode.media.media_library.yml | 9 + .../config/install/crop.type.media_crop.yml | 11 + .../config/install/embed.button.media.yml | 19 + ..._browser.browser.gallery_media_library.yml | 59 + .../entity_browser.browser.media_embed.yml | 74 + .../entity_browser.browser.media_library.yml | 62 + ...ld.field.media.document.field_document.yml | 26 + ...eld.media.document.field_document_size.yml | 22 + ....media.document.field_media_in_library.yml | 22 + ...d.field.media.document.field_mime_type.yml | 18 + ...d.media.gallery.field_media_in_library.yml | 22 + .../field.field.media.gallery.field_slide.yml | 37 + .../field.field.media.image.field_image.yml | 37 + ...eld.media.image.field_media_in_library.yml | 22 + ...ia.instagram.field_instagram_shortcode.yml | 18 + ...ld.media.instagram.field_instagram_url.yml | 22 + ...media.instagram.field_media_in_library.yml | 22 + ...eld.media.tweet.field_media_in_library.yml | 22 + ...d.field.media.tweet.field_tweet_author.yml | 18 + ...field.field.media.tweet.field_tweet_id.yml | 18 + ...ield.field.media.tweet.field_tweet_url.yml | 22 + .../field.field.media.video.field_id.yml | 18 + ...eld.media.video.field_media_in_library.yml | 22 + .../field.field.media.video.field_source.yml | 18 + .../field.field.media.video.field_video.yml | 24 + .../field.storage.media.field_document.yml | 22 + ...ield.storage.media.field_document_size.yml | 19 + .../install/field.storage.media.field_id.yml | 20 + .../field.storage.media.field_image.yml | 29 + ...torage.media.field_instagram_shortcode.yml | 20 + ...ield.storage.media.field_instagram_url.yml | 18 + ...d.storage.media.field_media_in_library.yml | 17 + .../field.storage.media.field_mime_type.yml | 20 + .../field.storage.media.field_slide.yml | 18 + .../field.storage.media.field_source.yml | 20 + ...field.storage.media.field_tweet_author.yml | 20 + .../field.storage.media.field_tweet_id.yml | 20 + .../field.storage.media.field_tweet_url.yml | 18 + .../field.storage.media.field_video.yml | 18 + .../install/image.style.gallery_item.yml | 6 + .../config/install/image.style.media_crop.yml | 14 + .../image.style.media_crop_preview.yml | 14 + .../image.style.media_library_item.yml | 16 + .../install/media_entity.bundle.document.yml | 16 + .../install/media_entity.bundle.gallery.yml | 14 + .../install/media_entity.bundle.image.yml | 15 + .../install/media_entity.bundle.instagram.yml | 17 + .../install/media_entity.bundle.tweet.yml | 21 + .../install/media_entity.bundle.video.yml | 18 + .../install/views.view.media_library.yml | 2731 +++++++++++++++++ web/modules/contrib/media/css/media.view.css | 70 + .../media/drupal_ti/before/before_script.sh | 26 + web/modules/contrib/media/files/Test.doc | Bin 0 -> 9216 bytes web/modules/contrib/media/files/Test.docx | Bin 0 -> 4295 bytes web/modules/contrib/media/files/Test.ods | Bin 0 -> 4526 bytes web/modules/contrib/media/files/Test.odt | Bin 0 -> 3940 bytes web/modules/contrib/media/files/Test.ott | Bin 0 -> 8636 bytes web/modules/contrib/media/files/Test.pdf | Bin 0 -> 15354 bytes web/modules/contrib/media/files/Test.ppt | Bin 0 -> 72192 bytes web/modules/contrib/media/files/Test.pptx | Bin 0 -> 27538 bytes web/modules/contrib/media/files/Test.rtf | 34 + web/modules/contrib/media/files/Test.txt | 1 + web/modules/contrib/media/files/Test.xls | Bin 0 -> 5632 bytes web/modules/contrib/media/files/Test.xlsx | Bin 0 -> 3568 bytes .../contrib/media/images/checkmark.svg | 16 + .../media/images/icons/application-msword.png | Bin 0 -> 1965 bytes .../media/images/icons/application-pdf.png | Bin 0 -> 1732 bytes .../media/images/icons/application-rtf.png | Bin 0 -> 1081 bytes .../images/icons/application-vnd.ms-excel.png | Bin 0 -> 1838 bytes .../icons/application-vnd.ms-powerpoint.png | Bin 0 -> 1400 bytes ...ion-vnd.oasis.opendocument.spreadsheet.png | Bin 0 -> 1600 bytes ...n-vnd.oasis.opendocument.text-template.png | Bin 0 -> 1491 bytes ...pplication-vnd.oasis.opendocument.text.png | Bin 0 -> 1594 bytes ...cedocument.presentationml.presentation.png | Bin 0 -> 4087 bytes ...ats-officedocument.spreadsheetml.sheet.png | Bin 0 -> 1751 bytes ...ficedocument.wordprocessingml.document.png | Bin 0 -> 4617 bytes .../contrib/media/images/icons/text-plain.png | Bin 0 -> 1023 bytes .../contrib/media/images/media_embed_icon.png | Bin 0 -> 341 bytes web/modules/contrib/media/js/media.view.js | 32 + web/modules/contrib/media/media.info.yml | 42 + web/modules/contrib/media/media.install | 57 + web/modules/contrib/media/media.libraries.yml | 10 + web/modules/contrib/media/media.module | 83 + .../GalleryMediaBundleConstraint.php | 30 + .../GalleryMediaBundleConstraintValidator.php | 30 + .../media/src/Tests/DocumentBundleTest.php | 136 + .../media/src/Tests/GalleryBundleTest.php | 185 ++ .../media/src/Tests/ImageBundleTest.php | 111 + .../media/src/Tests/InstagramBundleTest.php | 112 + .../media/src/Tests/TweetBundleTest.php | 112 + .../media/src/Tests/VideoBundleTest.php | 105 + ....entity_form_display.node.page.default.yml | 60 + .../install/editor.editor.basic_html.yml | 52 + .../install/editor.editor.full_html.yml | 60 + .../install/field.field.node.page.body.yml | 21 + .../install/filter.format.basic_html.yml | 44 + .../install/filter.format.full_html.yml | 35 + .../config/install/node.type.page.yml | 10 + .../media_embed_test.info.yml | 9 + .../FunctionalJavascript/EmbedButtonTest.php | 107 + web/modules/contrib/php | 1 - web/modules/contrib/php/composer.json | 24 + .../config/install/filter.format.php_code.yml | 17 + web/modules/contrib/php/js/php.admin.js | 39 + web/modules/contrib/php/php.info.yml | 8 + web/modules/contrib/php/php.libraries.yml | 7 + web/modules/contrib/php/php.module | 89 + web/modules/contrib/php/php.permissions.yml | 4 + .../contrib/php/src/Plugin/Condition/Php.php | 75 + .../contrib/php/src/Plugin/Filter/Php.php | 74 + .../src/Plugin/views/argument_default/Php.php | 72 + .../Plugin/views/argument_validator/Php.php | 72 + .../src/Tests/Condition/PhpConditionTest.php | 70 + .../contrib/php/src/Tests/PhpAccessTest.php | 35 + .../contrib/php/src/Tests/PhpFilterTest.php | 44 + .../contrib/php/src/Tests/PhpTestBase.php | 72 + .../Plugin/views/PhpArgumentValidatorTest.php | 63 + .../php_views_test_config.info.yml | 7 + ...s.view.test_view_argument_validate_php.yml | 43 + web/modules/contrib/views_responsive_grid | 1 - .../contrib/views_responsive_grid/README.txt | 46 + .../Plugin/views/style/ResponsiveGrid.php | 115 + .../views-view-responsive-grid.html.twig | 50 + .../views_responsive_grid.css | 8 + .../views_responsive_grid.info.yml | 8 + .../views_responsive_grid.module | 16 + .../views_responsive_grid.theme.inc | 158 + 731 files changed, 48985 insertions(+), 11 deletions(-) delete mode 160000 web/modules/contrib/advanced_help create mode 100644 web/modules/contrib/advanced_help/README.txt create mode 100644 web/modules/contrib/advanced_help/advanced_help.info.yml create mode 100644 web/modules/contrib/advanced_help/advanced_help.install create mode 100644 web/modules/contrib/advanced_help/advanced_help.libraries.yml create mode 100644 web/modules/contrib/advanced_help/advanced_help.links.menu.yml create mode 100644 web/modules/contrib/advanced_help/advanced_help.links.task.yml create mode 100644 web/modules/contrib/advanced_help/advanced_help.module create mode 100644 web/modules/contrib/advanced_help/advanced_help.permissions.yml create mode 100644 web/modules/contrib/advanced_help/advanced_help.routing.yml create mode 100644 web/modules/contrib/advanced_help/advanced_help.services.yml create mode 100644 web/modules/contrib/advanced_help/composer.json create mode 100644 web/modules/contrib/advanced_help/css/help-icon.css create mode 100644 web/modules/contrib/advanced_help/css/help.css create mode 100644 web/modules/contrib/advanced_help/help/advanced_help.help.yml create mode 100755 web/modules/contrib/advanced_help/help/ahelp_tab.png create mode 100644 web/modules/contrib/advanced_help/help/click_icon.png create mode 100644 web/modules/contrib/advanced_help/help/ini-file.html create mode 100644 web/modules/contrib/advanced_help/help/readme.html create mode 100644 web/modules/contrib/advanced_help/help/translation.html create mode 100644 web/modules/contrib/advanced_help/help/using-advanced-help.html create mode 100644 web/modules/contrib/advanced_help/help/why-advanced-help.html create mode 100644 web/modules/contrib/advanced_help/help_example/help/180px-Andi_Gutmans_1.jpg create mode 100644 web/modules/contrib/advanced_help/help_example/help/180px-Lerdorf.jpg create mode 100644 web/modules/contrib/advanced_help/help_example/help/180px-PHP_Hello_World_screenshot.png create mode 100644 web/modules/contrib/advanced_help/help_example/help/about-php.html create mode 100644 web/modules/contrib/advanced_help/help_example/help/help_example.help.ini create mode 100644 web/modules/contrib/advanced_help/help_example/help/history.html create mode 100644 web/modules/contrib/advanced_help/help_example/help/security.html create mode 100644 web/modules/contrib/advanced_help/help_example/help/syntax.html create mode 100644 web/modules/contrib/advanced_help/help_example/help/usage.html create mode 100644 web/modules/contrib/advanced_help/help_example/help_example.info create mode 100644 web/modules/contrib/advanced_help/help_example/help_example.module create mode 100644 web/modules/contrib/advanced_help/images/help.png create mode 100644 web/modules/contrib/advanced_help/src/AdvancedHelpManager.php create mode 100644 web/modules/contrib/advanced_help/src/Controller/AdvancedHelpController.php create mode 100644 web/modules/contrib/advanced_help/src/HelpInterface.php create mode 100644 web/modules/contrib/advanced_help/src/Plugin/Derivative/DynamicLocalTasks.php create mode 100644 web/modules/contrib/advanced_help/src/Plugin/Search/AdvancedHelpSearch.php create mode 100644 web/modules/contrib/advanced_help/templates/advanced-help-topic.html.twig delete mode 160000 web/modules/contrib/better_formats create mode 100644 web/modules/contrib/better_formats/README.txt create mode 100644 web/modules/contrib/better_formats/better_formats.info.yml create mode 100644 web/modules/contrib/better_formats/better_formats.install create mode 100644 web/modules/contrib/better_formats/better_formats.links.task.yml create mode 100644 web/modules/contrib/better_formats/better_formats.module create mode 100644 web/modules/contrib/better_formats/better_formats.permissions.yml create mode 100644 web/modules/contrib/better_formats/better_formats.routing.yml create mode 100644 web/modules/contrib/better_formats/composer.json create mode 100644 web/modules/contrib/better_formats/config/install/better_formats.settings.yml create mode 100644 web/modules/contrib/better_formats/config/schema/better_formats.schema.yml create mode 100644 web/modules/contrib/better_formats/src/BetterFormatsPermissions.php create mode 100644 web/modules/contrib/better_formats/src/Form/SettingsForm.php create mode 100644 web/modules/contrib/better_formats/src/Tests/BetterFormatsFilterFormatAccessTest.php create mode 100644 web/modules/contrib/better_formats/src/Tests/BetterFormatsTermTest.php delete mode 160000 web/modules/contrib/ckeditor_widgets create mode 100644 web/modules/contrib/ckeditor_widgets/CHANGELOG.txt create mode 100644 web/modules/contrib/ckeditor_widgets/README.md create mode 100644 web/modules/contrib/ckeditor_widgets/ckeditor_widgets.info.yml create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/contents.css create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/dialogs/widgetbootstrapAlert.js create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/dialogs/widgetfoundationAccordion.js create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapAccordion.png create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapAlert.png create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapLeftCol.png create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapRightCol.png create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapThreeCol.png create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/icons/widgetbootstrapTwoCol.png create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/plugin.js create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/samples/index.html create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/samples/simplebox.html create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/contents.css create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/dialogs/foundation2Col.js create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/dialogs/widgetfoundationAccordion.js create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/icons/widgetcommonBox.png create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/icons/widgetcommonQuotebox.png create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/plugin.js create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/samples/contents.css create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/samples/simplebox.html create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/extraIcons/codesnippet.png create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/extraIcons/drupalbreak.png create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/extraIcons/fontawesome.png create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/extraIcons/leaflet.png create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/extraIcons/oembed.png create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/icons/widgettemplatemenu.png create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/plugin.js create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/samples/contents.css create mode 100644 web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/samples/simplebox.html create mode 100644 web/modules/contrib/ckeditor_widgets/src/Plugin/CKEditorPlugin/WidgetBootstrap.php create mode 100644 web/modules/contrib/ckeditor_widgets/src/Plugin/CKEditorPlugin/WidgetCommon.php create mode 100644 web/modules/contrib/ckeditor_widgets/src/Plugin/CKEditorPlugin/WidgetTemplateMenu.php delete mode 160000 web/modules/contrib/drupalmoduleupgrader create mode 100644 web/modules/contrib/drupalmoduleupgrader/.gitignore create mode 100644 web/modules/contrib/drupalmoduleupgrader/.gitmodules create mode 100644 web/modules/contrib/drupalmoduleupgrader/README.txt create mode 100644 web/modules/contrib/drupalmoduleupgrader/composer.json create mode 100644 web/modules/contrib/drupalmoduleupgrader/composer.lock create mode 100644 web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.entity_operations.yml create mode 100644 web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.functions.yml create mode 100644 web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.grep.yml create mode 100644 web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.hooks.yml create mode 100644 web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.rewriters.yml create mode 100644 web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.tags.yml create mode 100644 web/modules/contrib/drupalmoduleupgrader/drupalmoduleupgrader.drush.inc create mode 100644 web/modules/contrib/drupalmoduleupgrader/drupalmoduleupgrader.info.yml create mode 100644 web/modules/contrib/drupalmoduleupgrader/drupalmoduleupgrader.module create mode 100644 web/modules/contrib/drupalmoduleupgrader/drupalmoduleupgrader.services.yml create mode 100644 web/modules/contrib/drupalmoduleupgrader/phpunit.xml.dist create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/AnalyzerBase.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/AnalyzerInterface.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Annotation/Analyzer.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Annotation/Converter.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Annotation/Fixer.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Annotation/Indexer.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Annotation/Rewriter.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/ArrayIndexer.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/ConverterBase.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/ConverterInterface.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/DependencyCollectorTrait.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/DeriverBase.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/FixerBase.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/FixerInterface.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/IOException.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/IndexerBase.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/IndexerExecutionInterface.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/IndexerInterface.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/IndexerUsageInterface.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Issue.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/IssueInterface.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/DB.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/DBDeriver.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/FlagHook.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/FlagHookDeriver.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/FunctionCall.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/FunctionCallDeriver.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/Grep.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/HookFormAlter.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/HookPermission.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/HookUninstall.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/InfoFile.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/PSR4.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/Tests.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Blocks.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/EntityHooks.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CToolsGetPlugins.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CToolsObjectCacheGet.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CToolsObjectCacheSet.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CacheGet.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CacheSet.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CommentLoad.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DB.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DBDeriver.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/Disable.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DisableDeriver.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DrupalGetTitle.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DrupalIsCLI.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DrupalMapAssoc.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DrupalWriteRecord.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/EntityCreate.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/EntityGetInfo.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/EntityLoad.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/EntityOperation.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/EntityOperationDeriver.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldInfoFieldTypes.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldInfoFormatterTypes.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldInfoWidgetTypes.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldUpdateField.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldUpdateInstance.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldViewField.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldViewValue.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FormExecuteHandlers.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FormLoadInclude.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FormSetValue.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FormStateDefaults.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FormStateValuesClean.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FunctionCallModifier.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/GetT.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/L.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/LoadMultiple.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/LoadMultipleDeriver.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/ModuleInvoke.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/ModuleInvokeAll.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/NodeLoad.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/St.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/ThemeGetRegistry.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/URL.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/UserAccess.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/UserLoad.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/UserSave.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/VariableAPI.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/VariableDel.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/VariableGet.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/VariableSet.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/Watchdog.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Grep.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookBoot.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookEntityInfo.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookEntityTypeView.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookExit.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFieldAttachCreateBundle.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFieldAttachDeleteBundle.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFieldAttachRenameBundle.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFieldFormatterInfo.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFieldWidgetInfo.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFormAlter.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookInit.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookLibrary.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookMenuAlter.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookNodePrepare.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookPermission.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookURLOutboundAlter.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookUserLogin.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookWatchdog.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/InfoToYAML.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Links.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/PSR4.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Routing.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Tests.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/UnitTests.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/UserHooks.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/CreateClass.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/Define.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/Delete.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/Disable.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/FormCallbackToMethod.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/HookToYAML.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/Implement.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/ImplementHook.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/NodeCollectorTrait.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/Notify.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/PSR4.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Indexer/Classes.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Indexer/Constants.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Indexer/FunctionCalls.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Indexer/Functions.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Rewriter/FormState.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Rewriter/Generic.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Rewriter/GenericDeriver.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Routing/ContentRoute.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Routing/FormRoute.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/PluginBase.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Report.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/ReportInterface.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/RewriterInterface.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Routing/Drupal7/RouteWrapper.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Routing/Drupal7/Router.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Routing/Drupal8/RouteWrapper.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Routing/HookMenu.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkBinding/LinkBinding.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkBinding/LinkBindingFactory.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkBinding/LocalActionLinkBinding.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkBinding/LocalTaskLinkBinding.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkBinding/MenuLinkBinding.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkIndex.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Routing/ParameterBinding.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Routing/ParameterMap.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Routing/RouteConverterInterface.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Routing/RouteWrapperInterface.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Routing/RouterBase.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Routing/RouterBuiltEvent.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Routing/RouterInterface.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Target.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/TargetInterface.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Utility/Filter/ContainsLogicFilter.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Utility/Filter/FieldValueFilter.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Utility/Filter/FunctionCallArgumentFilter.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Utility/Filter/NodeAssignmentFilter.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Utility/FormConverter.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Utility/FormConverterFactory.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/Drupal7/PathComponent.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/Drupal7/PathUtility.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/Drupal8/PathComponent.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/Drupal8/PathUtility.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/PathComponentBase.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/PathComponentInterface.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/PathUtilityBase.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/PathUtilityInterface.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/src/Utility/StringTransformTrait.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/templates/Block.html.twig create mode 100644 web/modules/contrib/drupalmoduleupgrader/templates/Controller.html.twig create mode 100644 web/modules/contrib/drupalmoduleupgrader/templates/EntityType.html.twig create mode 100644 web/modules/contrib/drupalmoduleupgrader/templates/EventSubscriber.html.twig create mode 100644 web/modules/contrib/drupalmoduleupgrader/templates/Form.html.twig create mode 100644 web/modules/contrib/drupalmoduleupgrader/templates/Formatter.html.twig create mode 100644 web/modules/contrib/drupalmoduleupgrader/templates/Issue.html.twig create mode 100644 web/modules/contrib/drupalmoduleupgrader/templates/Logger.html.twig create mode 100644 web/modules/contrib/drupalmoduleupgrader/templates/OutboundPathProcessor.html.twig create mode 100644 web/modules/contrib/drupalmoduleupgrader/templates/Report.html.twig create mode 100644 web/modules/contrib/drupalmoduleupgrader/templates/RouteSubscriber.html.twig create mode 100644 web/modules/contrib/drupalmoduleupgrader/templates/Widget.html.twig create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/bootstrap.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/ContainerMockTrait.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/IssueTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/ModuleMockerTrait.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/AnalyzerTestBase.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/DBTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/FlagHookTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/FunctionCallTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/HookFormAlterTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/HookPermissionTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/HookUninstallTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/InfoFileTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/PSR4Test.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/TestsTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CToolsGetPluginsTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CToolsObjectCacheGetTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CToolsObjectCacheSetTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CacheGetTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CacheSetTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CommentLoadTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DBTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DisableTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DrupalGetTitleTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DrupalIsCLITest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DrupalMapAssocTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DrupalWriteRecordTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/EntityCreateTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/EntityGetInfoTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldInfoFieldTypesTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldInfoFormatterTypesTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldInfoWidgetTypesTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldUpdateFieldTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldUpdateInstanceTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldViewFieldTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldViewValueTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FormExecuteHandlersTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FormLoadIncludeTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FormSetValueTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FormStateValuesCleanTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FunctionCallModifierTestBase.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/GetTTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/ModuleInvokeAllTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/ModuleInvokeTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/NodeLoadTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/StTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/ThemeGetRegistryTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/UserAccessTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/UserLoadTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/UserSaveTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/VariableDelTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/VariableGetTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/VariableSetTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/WatchdogTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/CreateClassTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/DefineTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/DeleteTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/DisableTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/FormCallbackToMethodTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/HookToYAMLTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/ImplementHookTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/ImplementTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/NotifyTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/PSR4Test.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Indexer/ClassesTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Indexer/FunctionsTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Indexer/IndexerTestBase.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Rewriter/FormStateTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Rewriter/GenericTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/ReportTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/Drupal7/RouteWrapperTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/Drupal7/RouterTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/Drupal8/RouteWrapperTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/LinkBinding/LinkBindingTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/ParameterBindingTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/RouterBaseTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/SQLiteDatabaseTrait.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/TargetTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/TestBase.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Filter/ContainsLogicFilterTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Filter/FieldValueFilterTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Filter/FunctionCallArgumentFilterTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Filter/NodeAssignmentFilterTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Path/Drupal7/PathComponentTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Path/Drupal7/PathUtilityTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Path/Drupal8/PathComponentTest.php create mode 100644 web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Path/Drupal8/PathUtilityTest.php delete mode 160000 web/modules/contrib/filefield_sources create mode 100644 web/modules/contrib/filefield_sources/README.txt create mode 100644 web/modules/contrib/filefield_sources/config/schema/filefield_sources.data_types.schema.yml create mode 100644 web/modules/contrib/filefield_sources/config/schema/filefield_sources.schema.yml create mode 100644 web/modules/contrib/filefield_sources/config/schema/filefield_sources.setting.source_attach.schema.yml create mode 100644 web/modules/contrib/filefield_sources/config/schema/filefield_sources.setting.source_imce.schema.yml create mode 100644 web/modules/contrib/filefield_sources/config/schema/filefield_sources.setting.source_reference.schema.yml create mode 100644 web/modules/contrib/filefield_sources/config/schema/filefield_sources.setting.sources.schema.yml create mode 100644 web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.attach.schema.yml create mode 100644 web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.clipboard.schema.yml create mode 100644 web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.imce.schema.yml create mode 100644 web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.reference.schema.yml create mode 100644 web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.remote.schema.yml create mode 100644 web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.upload.schema.yml create mode 100644 web/modules/contrib/filefield_sources/css/filefield_sources.css create mode 100644 web/modules/contrib/filefield_sources/filefield_sources.api.php create mode 100644 web/modules/contrib/filefield_sources/filefield_sources.info.yml create mode 100644 web/modules/contrib/filefield_sources/filefield_sources.install create mode 100644 web/modules/contrib/filefield_sources/filefield_sources.libraries.yml create mode 100644 web/modules/contrib/filefield_sources/filefield_sources.module create mode 100644 web/modules/contrib/filefield_sources/filefield_sources.routing.yml create mode 100644 web/modules/contrib/filefield_sources/filefield_sources.services.yml create mode 100644 web/modules/contrib/filefield_sources/js/filefield_sources.js create mode 100644 web/modules/contrib/filefield_sources/src/Access/FieldAccessCheck.php create mode 100644 web/modules/contrib/filefield_sources/src/Annotation/FilefieldSource.php create mode 100644 web/modules/contrib/filefield_sources/src/Controller/ImceController.php create mode 100644 web/modules/contrib/filefield_sources/src/File/MimeType/ExtensionMimeTypeGuesser.php create mode 100644 web/modules/contrib/filefield_sources/src/FilefieldSourceInterface.php create mode 100644 web/modules/contrib/filefield_sources/src/FilefieldSourceManager.php create mode 100644 web/modules/contrib/filefield_sources/src/FilefieldSourcesServiceProvider.php create mode 100644 web/modules/contrib/filefield_sources/src/ImceScanner.php create mode 100644 web/modules/contrib/filefield_sources/src/Plugin/FilefieldSource/Attach.php create mode 100644 web/modules/contrib/filefield_sources/src/Plugin/FilefieldSource/Clipboard.php create mode 100644 web/modules/contrib/filefield_sources/src/Plugin/FilefieldSource/Imce.php create mode 100644 web/modules/contrib/filefield_sources/src/Plugin/FilefieldSource/Reference.php create mode 100644 web/modules/contrib/filefield_sources/src/Plugin/FilefieldSource/Remote.php create mode 100644 web/modules/contrib/filefield_sources/src/ProxyClass/File/MimeType/ExtensionMimeTypeGuesser.php create mode 100644 web/modules/contrib/filefield_sources/src/Routing/FilefieldSourcesRoutes.php create mode 100644 web/modules/contrib/filefield_sources/src/Tests/AttachSourceTest.php create mode 100644 web/modules/contrib/filefield_sources/src/Tests/ClipboardSourceTest.php create mode 100644 web/modules/contrib/filefield_sources/src/Tests/EmptyValuesTest.php create mode 100644 web/modules/contrib/filefield_sources/src/Tests/FileFieldSourcesTestBase.php create mode 100644 web/modules/contrib/filefield_sources/src/Tests/ImceSourceTest.php create mode 100644 web/modules/contrib/filefield_sources/src/Tests/MultipleValuesTest.php create mode 100644 web/modules/contrib/filefield_sources/src/Tests/ReferenceSourceTest.php create mode 100644 web/modules/contrib/filefield_sources/src/Tests/RemoteSourceTest.php create mode 100644 web/modules/contrib/filefield_sources/src/Tests/UploadSourceTest.php delete mode 160000 web/modules/contrib/libraries create mode 100644 web/modules/contrib/libraries/CHANGELOG.txt create mode 100644 web/modules/contrib/libraries/README.txt create mode 100644 web/modules/contrib/libraries/composer.json create mode 100644 web/modules/contrib/libraries/config/install/libraries.settings.yml create mode 100644 web/modules/contrib/libraries/config/schema/libraries.schema.yml create mode 100644 web/modules/contrib/libraries/libraries.api.php create mode 100644 web/modules/contrib/libraries/libraries.drush.inc create mode 100644 web/modules/contrib/libraries/libraries.info.yml create mode 100644 web/modules/contrib/libraries/libraries.install create mode 100644 web/modules/contrib/libraries/libraries.module create mode 100644 web/modules/contrib/libraries/libraries.services.yml create mode 100644 web/modules/contrib/libraries/src/Annotation/LibraryType.php create mode 100644 web/modules/contrib/libraries/src/Annotation/Locator.php create mode 100644 web/modules/contrib/libraries/src/Annotation/VersionDetector.php create mode 100644 web/modules/contrib/libraries/src/Config/LibrariesConfigSubscriber.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Asset/AssetLibrary.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Asset/AssetLibraryInterface.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Asset/AttachableAssetLibraryRegistrationInterface.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Asset/LocalRemoteAssetTrait.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Asset/MultipleAssetLibrary.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Asset/MultipleAssetLibraryInterface.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Definition/ChainDefinitionDiscovery.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Definition/DefinitionDiscoveryFactory.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Definition/DefinitionDiscoveryInterface.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Definition/FileDefinitionDiscovery.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Definition/FileDefinitionDiscoveryBase.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Definition/GuzzleDefinitionDiscovery.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Definition/WritableDefinitionDiscoveryInterface.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Definition/WritableFileDefinitionDiscovery.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Dependency/DependentLibraryInterface.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Dependency/DependentLibraryTrait.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Exception/InvalidLibraryDependencyException.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Exception/LibraryDefinitionNotFoundException.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Exception/LibraryNotInstalledException.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Exception/LibraryTypeNotFoundException.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Exception/UnknownLibraryVersionException.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/LibraryBase.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/LibraryInterface.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/LibraryManager.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/LibraryManagerInterface.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Local/LocalLibraryInterface.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Local/LocalLibraryTrait.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Local/LocatorInterface.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Local/LocatorManager.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/PhpFile/PhpFileLibrary.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/PhpFile/PhpFileLibraryInterface.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/PhpFile/PhpFileLoaderInterface.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/PhpFile/PhpRequireLoader.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Remote/RemoteLibraryInterface.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Remote/RemoteLibraryTrait.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Type/LibraryCreationListenerInterface.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Type/LibraryLoadingListenerInterface.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Type/LibraryTypeBase.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Type/LibraryTypeFactory.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Type/LibraryTypeInterface.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Utility/DependencyAccessorTrait.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Utility/IdAccessorTrait.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Utility/LibraryAccessorInterface.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Utility/LibraryAccessorTrait.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Utility/LibraryIdAccessorInterface.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Utility/LibraryIdAccessorTrait.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Version/VersionDetectorInterface.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Version/VersionDetectorManager.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Version/VersionedLibraryInterface.php create mode 100644 web/modules/contrib/libraries/src/ExternalLibrary/Version/VersionedLibraryTrait.php create mode 100644 web/modules/contrib/libraries/src/Plugin/MissingPluginConfigurationException.php create mode 100644 web/modules/contrib/libraries/src/Plugin/libraries/Locator/ChainLocator.php create mode 100644 web/modules/contrib/libraries/src/Plugin/libraries/Locator/GlobalLocator.php create mode 100644 web/modules/contrib/libraries/src/Plugin/libraries/Locator/UriLocator.php create mode 100644 web/modules/contrib/libraries/src/Plugin/libraries/Type/AssetLibraryType.php create mode 100644 web/modules/contrib/libraries/src/Plugin/libraries/Type/MultipleAssetLibraryType.php create mode 100644 web/modules/contrib/libraries/src/Plugin/libraries/Type/PhpFileLibraryType.php create mode 100644 web/modules/contrib/libraries/src/Plugin/libraries/VersionDetector/LinePatternDetector.php create mode 100644 web/modules/contrib/libraries/src/Plugin/libraries/VersionDetector/StaticDetector.php create mode 100644 web/modules/contrib/libraries/src/StreamWrapper/AssetLibrariesStream.php create mode 100644 web/modules/contrib/libraries/src/StreamWrapper/LibraryDefinitionsStream.php create mode 100644 web/modules/contrib/libraries/src/StreamWrapper/LocalHiddenStreamTrait.php create mode 100644 web/modules/contrib/libraries/src/StreamWrapper/PhpFileLibrariesStream.php create mode 100644 web/modules/contrib/libraries/src/StreamWrapper/PrivateStreamTrait.php create mode 100644 web/modules/contrib/libraries/src/Tests/LibrariesUnitTest.php create mode 100644 web/modules/contrib/libraries/src/Tests/LibrariesWebTest.php create mode 100644 web/modules/contrib/libraries/tests/assets/vendor/test_asset_library/example.css create mode 100644 web/modules/contrib/libraries/tests/assets/vendor/test_asset_library/example.js create mode 100644 web/modules/contrib/libraries/tests/assets/vendor/test_asset_multiple_library/example.first.css create mode 100644 web/modules/contrib/libraries/tests/assets/vendor/test_asset_multiple_library/example.first.js create mode 100644 web/modules/contrib/libraries/tests/assets/vendor/test_asset_multiple_library/example.second.css create mode 100644 web/modules/contrib/libraries/tests/assets/vendor/test_asset_multiple_library/example.second.js create mode 100644 web/modules/contrib/libraries/tests/example/README.txt create mode 100644 web/modules/contrib/libraries/tests/example/example_1.css create mode 100644 web/modules/contrib/libraries/tests/example/example_1.js create mode 100644 web/modules/contrib/libraries/tests/example/example_1.php create mode 100644 web/modules/contrib/libraries/tests/example/example_2.css create mode 100644 web/modules/contrib/libraries/tests/example/example_2.js create mode 100644 web/modules/contrib/libraries/tests/example/example_2.php create mode 100644 web/modules/contrib/libraries/tests/example/example_3.css create mode 100644 web/modules/contrib/libraries/tests/example/example_3.js create mode 100644 web/modules/contrib/libraries/tests/example/example_3.php create mode 100644 web/modules/contrib/libraries/tests/example/example_4.css create mode 100644 web/modules/contrib/libraries/tests/example/example_4.js create mode 100644 web/modules/contrib/libraries/tests/example/example_4.php create mode 100644 web/modules/contrib/libraries/tests/example/example_info_file.libraries.info.yml create mode 100644 web/modules/contrib/libraries/tests/libraries/test_php_file_library/test_php_file_library.php create mode 100644 web/modules/contrib/libraries/tests/library_definitions/test_asset_library.json create mode 100644 web/modules/contrib/libraries/tests/library_definitions/test_asset_multiple_library.json create mode 100644 web/modules/contrib/libraries/tests/library_definitions/test_php_file_library.json create mode 100644 web/modules/contrib/libraries/tests/modules/libraries_test/libraries_test.css create mode 100644 web/modules/contrib/libraries/tests/modules/libraries_test/libraries_test.inc create mode 100644 web/modules/contrib/libraries/tests/modules/libraries_test/libraries_test.info.yml create mode 100644 web/modules/contrib/libraries/tests/modules/libraries_test/libraries_test.js create mode 100644 web/modules/contrib/libraries/tests/modules/libraries_test/libraries_test.module create mode 100644 web/modules/contrib/libraries/tests/modules/libraries_test/libraries_test.routing.yml create mode 100644 web/modules/contrib/libraries/tests/modules/libraries_test/src/Controller/ExampleController.php create mode 100644 web/modules/contrib/libraries/tests/src/Functional/ExternalLibrary/Definition/DefinitionDiscoveryFactoryTest.php create mode 100644 web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTest.php create mode 100644 web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTestBase.php create mode 100644 web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/Asset/MultipleAssetLibraryTest.php create mode 100644 web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/GlobalLocatorTest.php create mode 100644 web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/PhpFile/PhpFileLibraryTest.php create mode 100644 web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/TestLibraryFilesStream.php create mode 100644 web/modules/contrib/libraries/tests/src/Kernel/LibraryTypeKernelTestBase.php create mode 100644 web/modules/contrib/libraries/tests/src/Unit/Plugin/libraries/VersionDetector/LinePatternDetectorTest.php delete mode 160000 web/modules/contrib/linkchecker create mode 100644 web/modules/contrib/linkchecker/CHANGELOG.txt create mode 100644 web/modules/contrib/linkchecker/README.txt create mode 100644 web/modules/contrib/linkchecker/composer.json create mode 100644 web/modules/contrib/linkchecker/config/install/linkchecker.settings.yml create mode 100644 web/modules/contrib/linkchecker/config/schema/linkchecker.schema.yml create mode 100644 web/modules/contrib/linkchecker/js/linkchecker.content_types.js create mode 100644 web/modules/contrib/linkchecker/linkchecker.batch.inc create mode 100644 web/modules/contrib/linkchecker/linkchecker.drush.inc create mode 100644 web/modules/contrib/linkchecker/linkchecker.info.yml create mode 100644 web/modules/contrib/linkchecker/linkchecker.install create mode 100644 web/modules/contrib/linkchecker/linkchecker.libraries.yml create mode 100644 web/modules/contrib/linkchecker/linkchecker.links.menu.yml create mode 100644 web/modules/contrib/linkchecker/linkchecker.links.task.yml create mode 100644 web/modules/contrib/linkchecker/linkchecker.module create mode 100644 web/modules/contrib/linkchecker/linkchecker.pages.inc create mode 100644 web/modules/contrib/linkchecker/linkchecker.permissions.yml create mode 100644 web/modules/contrib/linkchecker/linkchecker.redirect.inc create mode 100644 web/modules/contrib/linkchecker/linkchecker.routing.yml create mode 100644 web/modules/contrib/linkchecker/migration_templates/d6_linkchecker_settings.yml create mode 100644 web/modules/contrib/linkchecker/migration_templates/d7_linkchecker_settings.yml create mode 100644 web/modules/contrib/linkchecker/src/Controller/LinkCheckerAdminReportPage.php create mode 100644 web/modules/contrib/linkchecker/src/Controller/LinkCheckerUserReportPage.php create mode 100644 web/modules/contrib/linkchecker/src/Form/LinkCheckerAdminSettingsForm.php create mode 100644 web/modules/contrib/linkchecker/src/Form/LinkCheckerEditLinkSettingsForm.php create mode 100644 web/modules/contrib/linkchecker/src/Tests/LinkCheckerInterfaceTest.php create mode 100644 web/modules/contrib/linkchecker/src/Tests/LinkCheckerLinkExtractionTest.php delete mode 160000 web/modules/contrib/livereload create mode 100644 web/modules/contrib/livereload/README.txt create mode 100644 web/modules/contrib/livereload/config/install/livereload.settings.yml create mode 100644 web/modules/contrib/livereload/config/schema/livereload.schema.yml create mode 100644 web/modules/contrib/livereload/js/pseudo.js create mode 100644 web/modules/contrib/livereload/livereload.info.yml create mode 100644 web/modules/contrib/livereload/livereload.libraries.yml create mode 100644 web/modules/contrib/livereload/livereload.module create mode 100644 web/modules/contrib/livereload/livereload.permissions.yml delete mode 160000 web/modules/contrib/media create mode 100644 web/modules/contrib/media/.travis.yml create mode 100644 web/modules/contrib/media/README.md create mode 100644 web/modules/contrib/media/composer.json create mode 100644 web/modules/contrib/media/config/install/core.entity_form_display.media.document.default.yml create mode 100644 web/modules/contrib/media/config/install/core.entity_form_display.media.gallery.default.yml create mode 100644 web/modules/contrib/media/config/install/core.entity_form_display.media.image.default.yml create mode 100644 web/modules/contrib/media/config/install/core.entity_form_display.media.instagram.default.yml create mode 100644 web/modules/contrib/media/config/install/core.entity_form_display.media.tweet.default.yml create mode 100644 web/modules/contrib/media/config/install/core.entity_form_display.media.video.default.yml create mode 100644 web/modules/contrib/media/config/install/core.entity_view_display.media.document.default.yml create mode 100644 web/modules/contrib/media/config/install/core.entity_view_display.media.document.media_library.yml create mode 100644 web/modules/contrib/media/config/install/core.entity_view_display.media.gallery.default.yml create mode 100644 web/modules/contrib/media/config/install/core.entity_view_display.media.gallery.media_library.yml create mode 100644 web/modules/contrib/media/config/install/core.entity_view_display.media.image.default.yml create mode 100644 web/modules/contrib/media/config/install/core.entity_view_display.media.image.media_library.yml create mode 100644 web/modules/contrib/media/config/install/core.entity_view_display.media.instagram.default.yml create mode 100644 web/modules/contrib/media/config/install/core.entity_view_display.media.instagram.media_library.yml create mode 100644 web/modules/contrib/media/config/install/core.entity_view_display.media.tweet.default.yml create mode 100644 web/modules/contrib/media/config/install/core.entity_view_display.media.tweet.media_library.yml create mode 100644 web/modules/contrib/media/config/install/core.entity_view_display.media.video.default.yml create mode 100644 web/modules/contrib/media/config/install/core.entity_view_display.media.video.media_library.yml create mode 100644 web/modules/contrib/media/config/install/core.entity_view_mode.media.gallery.yml create mode 100644 web/modules/contrib/media/config/install/core.entity_view_mode.media.media_library.yml create mode 100644 web/modules/contrib/media/config/install/crop.type.media_crop.yml create mode 100644 web/modules/contrib/media/config/install/embed.button.media.yml create mode 100644 web/modules/contrib/media/config/install/entity_browser.browser.gallery_media_library.yml create mode 100644 web/modules/contrib/media/config/install/entity_browser.browser.media_embed.yml create mode 100644 web/modules/contrib/media/config/install/entity_browser.browser.media_library.yml create mode 100644 web/modules/contrib/media/config/install/field.field.media.document.field_document.yml create mode 100644 web/modules/contrib/media/config/install/field.field.media.document.field_document_size.yml create mode 100644 web/modules/contrib/media/config/install/field.field.media.document.field_media_in_library.yml create mode 100644 web/modules/contrib/media/config/install/field.field.media.document.field_mime_type.yml create mode 100644 web/modules/contrib/media/config/install/field.field.media.gallery.field_media_in_library.yml create mode 100644 web/modules/contrib/media/config/install/field.field.media.gallery.field_slide.yml create mode 100644 web/modules/contrib/media/config/install/field.field.media.image.field_image.yml create mode 100644 web/modules/contrib/media/config/install/field.field.media.image.field_media_in_library.yml create mode 100644 web/modules/contrib/media/config/install/field.field.media.instagram.field_instagram_shortcode.yml create mode 100644 web/modules/contrib/media/config/install/field.field.media.instagram.field_instagram_url.yml create mode 100644 web/modules/contrib/media/config/install/field.field.media.instagram.field_media_in_library.yml create mode 100644 web/modules/contrib/media/config/install/field.field.media.tweet.field_media_in_library.yml create mode 100644 web/modules/contrib/media/config/install/field.field.media.tweet.field_tweet_author.yml create mode 100644 web/modules/contrib/media/config/install/field.field.media.tweet.field_tweet_id.yml create mode 100644 web/modules/contrib/media/config/install/field.field.media.tweet.field_tweet_url.yml create mode 100644 web/modules/contrib/media/config/install/field.field.media.video.field_id.yml create mode 100644 web/modules/contrib/media/config/install/field.field.media.video.field_media_in_library.yml create mode 100644 web/modules/contrib/media/config/install/field.field.media.video.field_source.yml create mode 100644 web/modules/contrib/media/config/install/field.field.media.video.field_video.yml create mode 100644 web/modules/contrib/media/config/install/field.storage.media.field_document.yml create mode 100644 web/modules/contrib/media/config/install/field.storage.media.field_document_size.yml create mode 100644 web/modules/contrib/media/config/install/field.storage.media.field_id.yml create mode 100644 web/modules/contrib/media/config/install/field.storage.media.field_image.yml create mode 100644 web/modules/contrib/media/config/install/field.storage.media.field_instagram_shortcode.yml create mode 100644 web/modules/contrib/media/config/install/field.storage.media.field_instagram_url.yml create mode 100644 web/modules/contrib/media/config/install/field.storage.media.field_media_in_library.yml create mode 100644 web/modules/contrib/media/config/install/field.storage.media.field_mime_type.yml create mode 100644 web/modules/contrib/media/config/install/field.storage.media.field_slide.yml create mode 100644 web/modules/contrib/media/config/install/field.storage.media.field_source.yml create mode 100644 web/modules/contrib/media/config/install/field.storage.media.field_tweet_author.yml create mode 100644 web/modules/contrib/media/config/install/field.storage.media.field_tweet_id.yml create mode 100644 web/modules/contrib/media/config/install/field.storage.media.field_tweet_url.yml create mode 100644 web/modules/contrib/media/config/install/field.storage.media.field_video.yml create mode 100644 web/modules/contrib/media/config/install/image.style.gallery_item.yml create mode 100644 web/modules/contrib/media/config/install/image.style.media_crop.yml create mode 100644 web/modules/contrib/media/config/install/image.style.media_crop_preview.yml create mode 100644 web/modules/contrib/media/config/install/image.style.media_library_item.yml create mode 100644 web/modules/contrib/media/config/install/media_entity.bundle.document.yml create mode 100644 web/modules/contrib/media/config/install/media_entity.bundle.gallery.yml create mode 100644 web/modules/contrib/media/config/install/media_entity.bundle.image.yml create mode 100644 web/modules/contrib/media/config/install/media_entity.bundle.instagram.yml create mode 100644 web/modules/contrib/media/config/install/media_entity.bundle.tweet.yml create mode 100644 web/modules/contrib/media/config/install/media_entity.bundle.video.yml create mode 100644 web/modules/contrib/media/config/install/views.view.media_library.yml create mode 100644 web/modules/contrib/media/css/media.view.css create mode 100644 web/modules/contrib/media/drupal_ti/before/before_script.sh create mode 100644 web/modules/contrib/media/files/Test.doc create mode 100644 web/modules/contrib/media/files/Test.docx create mode 100644 web/modules/contrib/media/files/Test.ods create mode 100644 web/modules/contrib/media/files/Test.odt create mode 100644 web/modules/contrib/media/files/Test.ott create mode 100644 web/modules/contrib/media/files/Test.pdf create mode 100644 web/modules/contrib/media/files/Test.ppt create mode 100644 web/modules/contrib/media/files/Test.pptx create mode 100644 web/modules/contrib/media/files/Test.rtf create mode 100644 web/modules/contrib/media/files/Test.txt create mode 100644 web/modules/contrib/media/files/Test.xls create mode 100644 web/modules/contrib/media/files/Test.xlsx create mode 100755 web/modules/contrib/media/images/checkmark.svg create mode 100644 web/modules/contrib/media/images/icons/application-msword.png create mode 100644 web/modules/contrib/media/images/icons/application-pdf.png create mode 100644 web/modules/contrib/media/images/icons/application-rtf.png create mode 100644 web/modules/contrib/media/images/icons/application-vnd.ms-excel.png create mode 100644 web/modules/contrib/media/images/icons/application-vnd.ms-powerpoint.png create mode 100644 web/modules/contrib/media/images/icons/application-vnd.oasis.opendocument.spreadsheet.png create mode 100644 web/modules/contrib/media/images/icons/application-vnd.oasis.opendocument.text-template.png create mode 100644 web/modules/contrib/media/images/icons/application-vnd.oasis.opendocument.text.png create mode 100644 web/modules/contrib/media/images/icons/application-vnd.openxmlformats-officedocument.presentationml.presentation.png create mode 100644 web/modules/contrib/media/images/icons/application-vnd.openxmlformats-officedocument.spreadsheetml.sheet.png create mode 100644 web/modules/contrib/media/images/icons/application-vnd.openxmlformats-officedocument.wordprocessingml.document.png create mode 100644 web/modules/contrib/media/images/icons/text-plain.png create mode 100644 web/modules/contrib/media/images/media_embed_icon.png create mode 100644 web/modules/contrib/media/js/media.view.js create mode 100644 web/modules/contrib/media/media.info.yml create mode 100644 web/modules/contrib/media/media.install create mode 100644 web/modules/contrib/media/media.libraries.yml create mode 100644 web/modules/contrib/media/media.module create mode 100644 web/modules/contrib/media/src/Plugin/Validation/Constraint/GalleryMediaBundleConstraint.php create mode 100644 web/modules/contrib/media/src/Plugin/Validation/Constraint/GalleryMediaBundleConstraintValidator.php create mode 100644 web/modules/contrib/media/src/Tests/DocumentBundleTest.php create mode 100644 web/modules/contrib/media/src/Tests/GalleryBundleTest.php create mode 100644 web/modules/contrib/media/src/Tests/ImageBundleTest.php create mode 100644 web/modules/contrib/media/src/Tests/InstagramBundleTest.php create mode 100644 web/modules/contrib/media/src/Tests/TweetBundleTest.php create mode 100644 web/modules/contrib/media/src/Tests/VideoBundleTest.php create mode 100644 web/modules/contrib/media/tests/modules/media_embed_test/config/install/core.entity_form_display.node.page.default.yml create mode 100644 web/modules/contrib/media/tests/modules/media_embed_test/config/install/editor.editor.basic_html.yml create mode 100644 web/modules/contrib/media/tests/modules/media_embed_test/config/install/editor.editor.full_html.yml create mode 100644 web/modules/contrib/media/tests/modules/media_embed_test/config/install/field.field.node.page.body.yml create mode 100644 web/modules/contrib/media/tests/modules/media_embed_test/config/install/filter.format.basic_html.yml create mode 100644 web/modules/contrib/media/tests/modules/media_embed_test/config/install/filter.format.full_html.yml create mode 100644 web/modules/contrib/media/tests/modules/media_embed_test/config/install/node.type.page.yml create mode 100644 web/modules/contrib/media/tests/modules/media_embed_test/media_embed_test.info.yml create mode 100644 web/modules/contrib/media/tests/src/FunctionalJavascript/EmbedButtonTest.php delete mode 160000 web/modules/contrib/php create mode 100644 web/modules/contrib/php/composer.json create mode 100644 web/modules/contrib/php/config/install/filter.format.php_code.yml create mode 100644 web/modules/contrib/php/js/php.admin.js create mode 100644 web/modules/contrib/php/php.info.yml create mode 100644 web/modules/contrib/php/php.libraries.yml create mode 100644 web/modules/contrib/php/php.module create mode 100644 web/modules/contrib/php/php.permissions.yml create mode 100644 web/modules/contrib/php/src/Plugin/Condition/Php.php create mode 100644 web/modules/contrib/php/src/Plugin/Filter/Php.php create mode 100644 web/modules/contrib/php/src/Plugin/views/argument_default/Php.php create mode 100644 web/modules/contrib/php/src/Plugin/views/argument_validator/Php.php create mode 100644 web/modules/contrib/php/src/Tests/Condition/PhpConditionTest.php create mode 100644 web/modules/contrib/php/src/Tests/PhpAccessTest.php create mode 100644 web/modules/contrib/php/src/Tests/PhpFilterTest.php create mode 100644 web/modules/contrib/php/src/Tests/PhpTestBase.php create mode 100644 web/modules/contrib/php/src/Tests/Plugin/views/PhpArgumentValidatorTest.php create mode 100644 web/modules/contrib/php/tests/modules/php_views_test_config/php_views_test_config.info.yml create mode 100644 web/modules/contrib/php/tests/modules/php_views_test_config/test_views/views.view.test_view_argument_validate_php.yml delete mode 160000 web/modules/contrib/views_responsive_grid create mode 100644 web/modules/contrib/views_responsive_grid/README.txt create mode 100644 web/modules/contrib/views_responsive_grid/lib/Drupal/views_responsive_grid/Plugin/views/style/ResponsiveGrid.php create mode 100644 web/modules/contrib/views_responsive_grid/templates/views-view-responsive-grid.html.twig create mode 100644 web/modules/contrib/views_responsive_grid/views_responsive_grid.css create mode 100644 web/modules/contrib/views_responsive_grid/views_responsive_grid.info.yml create mode 100644 web/modules/contrib/views_responsive_grid/views_responsive_grid.module create mode 100644 web/modules/contrib/views_responsive_grid/views_responsive_grid.theme.inc diff --git a/web/modules/contrib/advanced_help b/web/modules/contrib/advanced_help deleted file mode 160000 index ed80f440c..000000000 --- a/web/modules/contrib/advanced_help +++ /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 index 000000000..e32ce0275 --- /dev/null +++ b/web/modules/contrib/advanced_help/README.txt @@ -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 index 000000000..28a815a73 --- /dev/null +++ b/web/modules/contrib/advanced_help/advanced_help.info.yml @@ -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 index 000000000..7e735e489 --- /dev/null +++ b/web/modules/contrib/advanced_help/advanced_help.install @@ -0,0 +1,50 @@ + '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 index 000000000..821d1728f --- /dev/null +++ b/web/modules/contrib/advanced_help/advanced_help.libraries.yml @@ -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 index 000000000..5f7ca75d4 --- /dev/null +++ b/web/modules/contrib/advanced_help/advanced_help.links.menu.yml @@ -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 index 000000000..470c9b84e --- /dev/null +++ b/web/modules/contrib/advanced_help/advanced_help.links.task.yml @@ -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 index 000000000..9bfba51e3 --- /dev/null +++ b/web/modules/contrib/advanced_help/advanced_help.module @@ -0,0 +1,76 @@ +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'] = '' . t('Help') . ''; + 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 index 000000000..3c3746537 --- /dev/null +++ b/web/modules/contrib/advanced_help/advanced_help.permissions.yml @@ -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 index 000000000..2fe8b9281 --- /dev/null +++ b/web/modules/contrib/advanced_help/advanced_help.routing.yml @@ -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 index 000000000..130b448a6 --- /dev/null +++ b/web/modules/contrib/advanced_help/advanced_help.services.yml @@ -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 index 000000000..7b7e22c62 --- /dev/null +++ b/web/modules/contrib/advanced_help/composer.json @@ -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 index 000000000..a3cbc5494 --- /dev/null +++ b/web/modules/contrib/advanced_help/css/help-icon.css @@ -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 index 000000000..b4a80870e --- /dev/null +++ b/web/modules/contrib/advanced_help/css/help.css @@ -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 index 000000000..00eb59866 --- /dev/null +++ b/web/modules/contrib/advanced_help/help/advanced_help.help.yml @@ -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 index 0000000000000000000000000000000000000000..37ef6be4f7fe2dfd60b54d6888eb24d807bd40c1 GIT binary patch literal 11058 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 J%G7M%{0F8@>}LP~ literal 0 HcmV?d00001 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 index 000000000..05b85e435 --- /dev/null +++ b/web/modules/contrib/advanced_help/help/ini-file.html @@ -0,0 +1,117 @@ +

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.

+ +

Global settings may be put into a section named [advanced help +settings]. 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.

+ +
+
line break (FALSE)
+
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 br and p tags automatically.
+ +
navigation (TRUE)
+
If set, this navigation will be displayed at the end of the topic: +previous topic, Up (parent), next topic.
+ +
css
+
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.
+ +
name
+
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.
+ +
index name
+
This may be set to change the name of the module or theme in the +module/theme index. It overrides the name setting above, +as well as the project's name in its .info file.
+ +
hide (FALSE)
+
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 Views or derived themes that +extend base themes like Zen. By setting this to TRUE +the project will not appear as its own entry.
+
+ +

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. [ini-file]). The following settings may be set for +each file/topic, with the default value (if any) in brackets.

+ +
+
title
+
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.)
+ +
file (topic name)
+
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.
+ +
weight (0)
+
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.
+ +
parent
+
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 module%topic +or theme%topic as the identifier, +where module or theme is the project's short +name. For example if parent is set to, 'views%display', +the topic will be regarded as a child of the +display topic in the Views module.
+ +
line break (FALSE)
+
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.
+ +
css
+
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.
+ +
popup width (500)
+
The width in pixels of the popup window.
+ +
popup height (500)
+
The height in pixels of the popup window.
+
+ +

For example, here is a version of the advanced_help.help.ini file:

+ +
+[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
+
diff --git a/web/modules/contrib/advanced_help/help/readme.html b/web/modules/contrib/advanced_help/help/readme.html new file mode 100644 index 000000000..99f0e51c6 --- /dev/null +++ b/web/modules/contrib/advanced_help/help/readme.html @@ -0,0 +1,107 @@ +

Synopsis

+ +

The Advanced help module provides a framework that allows +module and theme developers integrate help texts in a Drupal site.

+ +

These help texts are stored in ordinary .html-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 +help 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.

+ +

The help texts can be marked up with standard HTML. They will be +rendered using your site's theme.

+ +

If the module or theme author does not make use of the +Advanced help HTML-framework, but if there is a +README.md or README.txt in the package, +the content of that file will be shown instead.

+ +

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.

+ +

The help texts can be placed in a hierarchy, allowing for top down +navigation of help.

+ +

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.

+ +

Using the module

+ +

When you enable the module, a new tab with the legend “Advanced +help” will show up under “Help”: + +

+ahelp_tab.png +
+ +

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. However, it +comes with a small companion demo module named +Advanced help example to demonstrate how it works. +For more extensive example of use of the advanced help features, see +the Views project.

+ + + +

Support status

+ +

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 issue queue if +you're interested in managing a port of the project to to Drupal +8. Older versions are no longer supported.

+ +

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.

+ +

The primary goal of the module is to remain light-weight +and simple. 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.

+ +

The maintainer hopes that the community is willing to help out by +answering & closing support requests.

+ + + + + +

Credits

+ + diff --git a/web/modules/contrib/advanced_help/help/translation.html b/web/modules/contrib/advanced_help/help/translation.html new file mode 100644 index 000000000..75de70f4c --- /dev/null +++ b/web/modules/contrib/advanced_help/help/translation.html @@ -0,0 +1,44 @@ +

To translate a help-file indexed by Advanced help, +first create a directory +translations/help/language in the project's +root directory. The language is the language code that +appears on the Languages page in the administrative UI.

+ +

Then, copy the .ini file and all +the .html 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.

+ +

In the topics section, the .ini file only needs to +keep the topic names (unaltered) and titles (translated). If there is +a name or index name setting in the +'advanced help settings' portion, that should be retained. Any +retained settings should be translated. The rest of the data in the +.ini file may be discarded or ignored.

+ +

Each .html file should then be translated in place.

+ +

When translating a .html file, you will find that +the &path& 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 &trans_path& instead, which will lead to the +translated directory. This will allow you to pick and choose which +linked items, if any, will be translated.

+ +

If a topic is not translated, the default (untranslated) version +will be shown instead.

+ +

Translating Advanced help's help files

+ +

If you want to help with the translation of +Advanced help 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 issue queue for Advanced help. +If such an issue does not exist, please can create it. +Choose Category “Task”, +Status “Needs review” and +Component “Documentation”. +Upload translated files as an attachment (change the file type from .html to .txt to be allowed to upload).

+ +

Uploaded translations will be included in the next version if +reviewed and approved by other users (i.e. gets to status “RTBC”).

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 index 000000000..c47750bb4 --- /dev/null +++ b/web/modules/contrib/advanced_help/help/using-advanced-help.html @@ -0,0 +1,152 @@ +

The Advanced help module provides a framework that +allows module and theme developers integrate help texts in a Drupal +site. Although the Advanced help 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.

+ +

Modules and themes utilizing Advanced help should +create a subdirectory named help inside their own main +directory. Place the file +MODULENAME.help.ini (resp. THEMENAME.help.ini) in this subdirectory. +formatted similar to the following example:

+ +
+[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
+
+ +

This file defines five help topics (inside the square brackets), and +some settings for them. +See: Advanced help .ini file format for +a list of defined settings.

+ + +

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:

+ + + + +
+$output = theme('advanced_help_topic', array(
+  'module' => 'help_example',
+  'topic' => 'about-php',
+));
+$output .= ' ' . t('Click the help icon!');
+
+ +

This produces the following output:

+ +
+<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">
+<span>Help</span>
+</a>
+ Click the help icon!
+</div>
+
+ +

This produces a clickable help icon like the one shown below:

+ +
+clickable icon +
+ +

Inside your help file, you may link to other help topics using this format:

+
+<a href="&topic:module/topic&">topic</a>
+
+

This format will ensure the popup status remains consistent when +switching between links.

+ +

To reference items within the help directory, such as images you wish to embed within the help text, use:

+ +
+<img src="&path&example.png"/>
+<img src="&trans_path&example.png"/>
+
+ +

The trans_path keyword refers to a translated version of the image in the translation directory and may be used it differs from the original.

+ +

To reference any normal path in the site, use:

+
+<a href="&base_url&admin/settings/site-configuration">anchor text</a>
+
+ +

NOTE: In previous versions Advanced +help did not require the &'s to be wrapped around +topic, path, and base_url. +This is currently still supported, but will be removed in a future +version. By adding the &'s these tokens are now not limited +to href="" and src="" parameters.

+ +

Access control

+ +

When this module is installed, users with the +view advanced help index +permission can access the advanced help index by going to +Administer → Advanced Help +(admin/advanced_help). Additional permissions +view advanced help topic and +view advanced help popup +enable users to access the actual help pages and popups.

+ +

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 +.htaccess in project's help directory:

+ +
+<Files *\.html>
+Order Allow,Deny
+Deny from all
+</Files>
+
+ +

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.

+ +

See also this tracker in the project's issue queue: +#1980936 Typing complete path to .html help files in module bypasses user permissions.

+ + + +

To enable advanced help search, navigate to +Administration → Configuration → Search and metadata → Search settings. +Scroll down to Active search modules and tick the box to the +left of “Advanced help”. The search form will appear on the top of +the advanced help index pages.

+ +

If the core Search 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 Administration → Reports → Status report and +click the link “run cron manually”.

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 index 000000000..3f36b3dda --- /dev/null +++ b/web/modules/contrib/advanced_help/help/why-advanced-help.html @@ -0,0 +1,44 @@ +The Advanced help 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 hook_help(). In Drupal 6 and 7, it actually co-exists with it; in the future, it is hoped that it will completely replace it allowing hook_help() 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. + +

Advanced help is organized by topic

+With the hook_help() 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. + +Advanced help 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. + +

Advanced help topics are processed HTML in their own files

+This separation makes it easy to find and modify. Currently, everything is lumped together in hook_help() in PHP strings that are run through t(), 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. + +

Advanced help files are translated as a file

+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. + +

Advanced help has its own navigation system

+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. +

Advanced help is indexed by the search engine

+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. + +

Inline help can be brought in via popups

+In addition to the manual-like hierarchical navigation, Advanced help 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, a popup will not throw away a user's data. 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 index 0000000000000000000000000000000000000000..b182676fb36ab854729ab629c93be7539866a63a GIT binary patch literal 11007 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 NrryoS9NV{l{|9Ry+E)Mo literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..38f33b81e1d9a6080452ac79b9fc2a2bea34f004 GIT binary patch literal 10133 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 literal 0 HcmV?d00001 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 index 000000000..f19f30c14 --- /dev/null +++ b/web/modules/contrib/advanced_help/help_example/help/about-php.html @@ -0,0 +1,5 @@ +

PHP (PHP: Hypertext Preprocessor) is a computer scripting language, originally designed for producing dynamic web pages. It is mainly used in server-side scripting, but can be used from a command line interface or in standalone graphical applications.[2]

+ +

While PHP was originally created by Rasmus Lerdorf in 1994, the main implementation of PHP is now produced by The PHP Group and serves as the de facto standard for PHP as there is no formal specification.[3] Released under the PHP License, the Free Software Foundation considers it to be free software.[4]

+ +

PHP is a widely-used general-purpose scripting language that is especially suited for web development and can be embedded into HTML. It generally runs on a web server, taking PHP code as its input and creating web pages as output. It can be deployed on most web servers and on almost every operating system and platform free of charge.[5] PHP is installed on more than 20 million websites and 1 million servers, although the number of websites with PHP installed has declined since August 2005.[6] It is also the most popular Apache module among computers using Apache as a web server.[6] The most recent major release of PHP was version 5.2.5 on November 8, 2007.[7]

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 index 000000000..83f9ec761 --- /dev/null +++ b/web/modules/contrib/advanced_help/help_example/help/help_example.help.ini @@ -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 index 000000000..9993403ce --- /dev/null +++ b/web/modules/contrib/advanced_help/help_example/help/history.html @@ -0,0 +1,23 @@ +
+
Rasmus Lerdorf, who wrote the original Common Gateway Interface binaries +
+
+Rasmus Lerdorf, who wrote the original Common Gateway Interface binaries
+
+
+ + +

PHP, standing for Personal Home Page, began as a set of Common Gateway Interface binaries written in the C programming language in 1994 by the Danish/Greenlandic programmer Rasmus Lerdorf. Lerdorf initially created these Personal Home Page Tools to replace a small set of Perl scripts he had been using to maintain his personal homepage. The tools were originally created to perform tasks such as displaying his résumé and recording how much traffic his page was receiving.[3] He combined these binaries with his Form Interpreter to create PHP/FI, which had more functionality. It included a larger C implementation which could communicate with databases and helped build simple, dynamic web applications. He released PHP publicly on June 8, 1995 to speed up the finding of bugs and improving the code.[8] 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.[3]

+ +
+
Andi Gutmans, who, along with Zeev Suraski, rewrote the parser that formed PHP 3 +
+
+Andi Gutmans, who, along with Zeev Suraski, rewrote the parser that formed PHP 3
+
+
+

Zeev Suraski and Andi Gutmans, two Israeli developers at the Technion IIT, rewrote the parser in 1997 and formed the base of PHP 3, changing the language's name to the recursive initialism PHP: Hypertext Preprocessor.[3] The development team officially released PHP/FI 2 in November 1997 after months of beta testing. Afterwards, public testing of PHP 3 began, and the official launch came in June 1998. Suraski and Gutmans then started a new rewrite of PHP's core, producing the Zend Engine in 1999.[9] They also founded Zend Technologies in Ramat Gan, Israel, which manages the development of PHP.[3]

+ +

On May 22, 2000, PHP 4, powered by the Zend Engine 1.0, was released.[3] On July 13, 2004, PHP 5 was released and is powered by the new Zend Engine II.[3] PHP 5 included new features such as improved support for object-oriented programming, the PHP Data Objects extension (which defines a lightweight and consistent interface for accessing databases), and numerous performance enhancements.[10] 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 August 8, 2008.[11]

+ +

PHP 5 is the only stable version still being developed. Late static binding has been missing from PHP and will be added in version 5.3.[12] [13] Development on PHP 4 ceased at the end of 2007, except for the critical security updates for PHP 4 already mentioned.[14][11] PHP 6 is now under development and major changes include the removal of register_globals[15], magic quotes, and safe mode.[11][16] PHP does not have complete native support for Unicode or multibyte strings;[17] unicode support will be added in PHP 6.[18] Many high profile open source projects ceased to support PHP 4 in new code as of February 5, 2008, due to the GoPHP5 initiative, provided by a consortium of PHP developers promoting the transition from PHP 4 to PHP 5.[19][20]

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 index 000000000..31d43dceb --- /dev/null +++ b/web/modules/contrib/advanced_help/help_example/help/security.html @@ -0,0 +1 @@ +

PHP is a popular target of hackers who exploit vulnerable applications written in PHP. Software vulnerabilities related to PHP are identified among the CVE (Common Vulnerabilities and Exposures) records, available from the National Vulnerability Database. 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.[28] 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 register_globals, which is now deprecated.[22] These result in code injection, cross-site scripting and other application security 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.

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 index 000000000..48d8109d1 --- /dev/null +++ b/web/modules/contrib/advanced_help/help_example/help/syntax.html @@ -0,0 +1,38 @@ +

Syntax

+
+
Syntax-highlighted PHP code +
+ +
+Syntax-highlighted PHP code
+
+
+

PHP only parses code within its delimiters. Anything outside its delimiters is sent directly to the output and is not parsed by PHP. The most common delimiters are <?php and ?>, which are open and close delimiters respectively. <script language="php"> and </script> delimiters are also available. Short tags (<? or <?= and ?>) are also commonly used, but like ASP-style tags (<% or <%= and %>), 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.[29] 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.[30]

+ +

Variables are prefixed with a dollar symbol and a type does not need to be specified in advance. Unlike function and class names, variable names are case sensitive. Both double-quoted ("") and heredoc strings allow the ability to embed the variable's value into the string.[31] PHP treats newlines as whitespace in the manner of a free-form language (except when inside string quotes), and statements are terminated by a semicolon.[32] PHP has three types of comment syntax: /* */ serves as block comments, and // as well as # are used for inline comments.[33] To output text to the browser, either the print function or the echo function is used. Both functions are nearly identical; the major difference is that print is slower than echo 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.[34]

+ +

+

Data types

+

PHP stores whole numbers in a platform-dependent range. This range is typically that of 32-bit signed integers. Unsigned integers are converted to signed values in certain situations; this behavior is different from other programming languages.[35] Integer variables can be assigned using decimal (positive and negative), octal, and hexadecimal notations. Real numbers are also stored in a platform-specific range. They can be specified using floating point notation, or two forms of scientific notation.[36] PHP has a native Boolean type that is similar to the native Boolean types in Java and C++. Using the Boolean type conversion rules, non-zero values are interpreted as true and zero as false, as in Perl and C++.[36] The null data type represents a variable that has no value. The only value in the null data type is NULL.[36] 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.[36] 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 hashes with both keys and values, and the two can be intermingled.[36] PHP also supports strings, which can be used with single quotes, double quotes, or heredoc syntax.[37]

+ +

+

Functions

+

PHP has hundreds of base functions and thousands more from extensions. Functions are not first-class functions and can only be referenced by their name. [38] User-defined functions can be created at any time without being prototyped.[38] Functions can be defined inside code blocks, permitting a run-time decision as to whether or not a function should be defined. Function calls must use parentheses, with the exception of zero argument class constructor functions called with the PHP new operator, where parentheses are optional. PHP supports quasi-anonymous functions through the create_function() 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 $function_name();, in PHP.[38]

+ +

+

Objects

+

Basic object-oriented programming functionality was added in PHP 3.[3] Object handling was completely rewritten for PHP 5, expanding the feature set and enhancing performance.[39] In previous versions of PHP, objects were handled like primitive types.[39] 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 handle, and not by value. PHP 5 introduced private and protected member variables and methods, along with abstract classes and final classes as well as abstract methods and final methods. It also introduced a standard way of declaring constructors and destructors, similar to that of other object-oriented languages such as C++, and a standard exception handling model. Furthermore, PHP 5 added interfaces and allowed for multiple interfaces to be implemented. There are special interfaces that allow objects to interact with the runtime system. Objects implementing ArrayAccess can be used with array syntax and objects implementing Iterator or IteratorAggregate can be used with the foreach language construct. There is no virtual table feature in the engine, so static variables are bound with a name instead of a reference at compile time.[40]

+ +

If the developer creates a copy of an object using the reserved word clone, the Zend engine will check if a __clone() method has been defined or not. If not, it will call a default __clone() which will copy the object's properties. If a __clone() 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 replica of the source object and only override properties that need to be changed.[41]

+ +

+

Resources

+

PHP includes free and open source libraries with the core build. PHP is a fundamentally Internet-aware system with modules built in for accessing FTP servers, many database servers, embedded SQL libraries such as embedded MySQL and SQLite, LDAP servers, and others. Many functions familiar to C programmers such as those in the stdio family are available in the standard PHP build.[42] PHP has traditionally used features such as "magic_quotes_gpc" 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 SQL injection 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.[43] 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.[44]

+ +

PHP allows developers to write extensions in C 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 Windows API, process management on Unix-like operating systems, multibyte strings (Unispan), cURL, and several popular compression formats. Some more unusual features include integration with Internet relay chat, dynamic generation of images and Adobe Flash content, and even speech synthesis. The PHP Extension Community Library (PECL) project is a repository for extensions to the PHP language.[45]

+ +

As with many scripting languages, PHP scripts are normally kept as human-readable source code, even on production web servers.[46] 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.[citation needed]

+

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 compiler is such that there are often opportunities for span optimization[47], and an example of a code optimizer is the Zend Optimizer PHP extension.[48]

+ +

PHP accelerators can offer significant performance gains by caching the compiled form of a PHP script in shared memory to avoid the overhead of parsing and compiling the code every time the script runs. They may also perform span optimization to provide increased execution performance.[citation needed]

+ 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 index 000000000..8f1e9ad43 --- /dev/null +++ b/web/modules/contrib/advanced_help/help_example/help/usage.html @@ -0,0 +1,9 @@ +

PHP is a general-purpose scripting language that is especially suited for web development. It is the fourth most popular computer programming language, ranking behind Java, C, and Visual Basic.[23] PHP generally runs on a web server, taking PHP code as its input and creating web pages as output. It can also be used for command-line scripting and client-side GUI applications. PHP can be deployed on most web servers, many operating systems and platforms, and can be used with many relational database management systems. 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.[5]

+ +

PHP primarily acts as a filter[24], 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 parser compiles input to produce bytecode for processing by the Zend Engine, giving improved performance over its interpreter predecessor.[25]

+ +

Originally designed to create dynamic web pages, PHP's principal focus is server-side scripting[26], and it is similar to other server-side scripting languages that provide dynamic content from a web server to a client, such as Microsoft's ASP.NET system, Sun Microsystems' JavaServer Pages[27], and mod_perl. PHP has also attracted the development of many frameworks that provide building blocks and a design structure to promote rapid application development (RAD). Some of these include CakePHP, PRADO, Symfony and Zend Framework, offering features similar to other web application frameworks.

+ +

The LAMP architecture has become popular in the web industry as a way of deploying web applications. PHP is commonly used as the P in this bundle alongside Linux, Apache and MySQL, although the P can also refer to Python or Perl.

+ +

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.[6]

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 index 000000000..f396100c2 --- /dev/null +++ b/web/modules/contrib/advanced_help/help_example/help_example.info @@ -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 index 000000000..18151128e --- /dev/null +++ b/web/modules/contrib/advanced_help/help_example/help_example.module @@ -0,0 +1,31 @@ + '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 .= ' ' . 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 index 0000000000000000000000000000000000000000..7645023a6cd23b71a728376bd7352989020b90d0 GIT binary patch literal 1025 zcmV+c1pfPpP)~!JX~fxAQ!H{1~i|mzS48NJxkQ zXcNPaA3qpmWMmjTJw3kyHK_vx5DQR#8qj2(hYuezFflPPn3|e0n46n3eEIT)ft#C~ zVcWKC40rC_VK{g09JiH~)eL|DVgj1rdhOaZuwoE+{`@(^nl)<}7A#o6@c#XK21!Xt zu-w(FR~f#4|Ly<~KujM$e&hjJ3jvASOOOK2Qjt2x@6*fjtXyDh#l*voic-n|>n2L(My4N&btfB*tHDGM0RU*+WF;F^?_lo-s+%)o4rn6$Js zC@#JMxtRa~1dBLFpc6I$gO2C=_3I2@zkUTP1_i5zh6YISSD@*EK%w0L0mOimG^Bxe zGf?rzXV0Gf1z`{yD7F=dWnt<70*D34ufIX~^Iw+#cfNBm0Hqn!d075)LBxN-6afSf zJT1NX`g?Q3?gzXJPQPII`VFM!KLZyhD?_}A1Ve+n;ukS)R&ZJZ2q17;dj0kH9sdPa zc#c2%2sTBWhmAp9l#}7o^REmae*9rDljURBoS^$vjE7YnAb>#bTARJ$)_;cVUH=)% z_W#e@c>Dj~fB*j9e*NwL&p&_vJI=iDpCNt6|IGEb{)7Ao5I{^Xzx;MxcHtER11H2g zCm(-gFr0XXLACoN*ak~^K43a$VOVO3>?f1pMXxg_UbD)KmcJ)ONv4q3~OVx86uZmW7vQ9Jp%(HG{%7# zAb^-O#JIi)f}Hd3KU_nMp*X|KFTWVpTz(A^044xFpe@>B++P6#hzS@Xdn5G47#M#3 zg=<)H{w3ISH~`8;7>F}S@v$8Q2q17;YH(5bYNjT{!0_!4*d{LxVTM{41z?l`Z3X(z zSVe%L$weNVmH+|>o|ayH`Ms%e_kEs~7hf~H{rD5?Zec!lh7dhbh6Y!KuM)g$;Isq~ vKp1K1>Bk=*_uP8-7lfaD_<@m@00ImE^cvDM(Wb9`00000NkvXXu0mjf!H>e} literal 0 HcmV?d00001 diff --git a/web/modules/contrib/advanced_help/src/AdvancedHelpManager.php b/web/modules/contrib/advanced_help/src/AdvancedHelpManager.php new file mode 100644 index 000000000..db9abe5ba --- /dev/null +++ b/web/modules/contrib/advanced_help/src/AdvancedHelpManager.php @@ -0,0 +1,233 @@ +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 index 000000000..8e06706d9 --- /dev/null +++ b/web/modules/contrib/advanced_help/src/Controller/AdvancedHelpController.php @@ -0,0 +1,391 @@ +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'] = '
' . Xss::filterAdmin(MarkdownExtra::defaultTransform($build['#markup'])) . '
'; + } + 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
to the filter. + $build['#markup'] = preg_replace('/^\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 = '
'; + + 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 .= '
'; + $build['#markup'] .= $navigation; + } + } + $build['#markup'] = '
' . $build['#markup'] . '
'; +// 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 index 000000000..116874974 --- /dev/null +++ b/web/modules/contrib/advanced_help/src/HelpInterface.php @@ -0,0 +1,11 @@ +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 index 000000000..3618df02d --- /dev/null +++ b/web/modules/contrib/advanced_help/src/Plugin/Search/AdvancedHelpSearch.php @@ -0,0 +1,267 @@ +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 index 000000000..29c4d3464 --- /dev/null +++ b/web/modules/contrib/advanced_help/templates/advanced-help-topic.html.twig @@ -0,0 +1,3 @@ +{% if topic_exists %} + {{ text|raw }} +{% endif %} diff --git a/web/modules/contrib/better_formats b/web/modules/contrib/better_formats deleted file mode 160000 index 9b3b5fa79..000000000 --- a/web/modules/contrib/better_formats +++ /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 index 000000000..3bd2bb971 --- /dev/null +++ b/web/modules/contrib/better_formats/README.txt @@ -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 index 000000000..02abe2eed --- /dev/null +++ b/web/modules/contrib/better_formats/better_formats.info.yml @@ -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 index 000000000..36bfae68f --- /dev/null +++ b/web/modules/contrib/better_formats/better_formats.install @@ -0,0 +1,14 @@ +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. Warning: 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 only on entity creation.'), + '#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 index 000000000..75c2dd584 --- /dev/null +++ b/web/modules/contrib/better_formats/better_formats.permissions.yml @@ -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 index 000000000..d2a20ebc7 --- /dev/null +++ b/web/modules/contrib/better_formats/better_formats.routing.yml @@ -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 index 000000000..27f828e4c --- /dev/null +++ b/web/modules/contrib/better_formats/composer.json @@ -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 index 000000000..8fbd024a3 --- /dev/null +++ b/web/modules/contrib/better_formats/config/install/better_formats.settings.yml @@ -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 index 000000000..a767614fb --- /dev/null +++ b/web/modules/contrib/better_formats/config/schema/better_formats.schema.yml @@ -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 index 000000000..a023808f7 --- /dev/null +++ b/web/modules/contrib/better_formats/src/BetterFormatsPermissions.php @@ -0,0 +1,61 @@ +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 index 000000000..7e28e53c7 --- /dev/null +++ b/web/modules/contrib/better_formats/src/Form/SettingsForm.php @@ -0,0 +1,65 @@ +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 index 000000000..850451579 --- /dev/null +++ b/web/modules/contrib/better_formats/src/Tests/BetterFormatsFilterFormatAccessTest.php @@ -0,0 +1,27 @@ +Albatross Digital. 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 index 000000000..2edd26cbf --- /dev/null +++ b/web/modules/contrib/ckeditor_widgets/ckeditor_widgets.info.yml @@ -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 index 000000000..eeba9d395 --- /dev/null +++ b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/contents.css @@ -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 index 000000000..223d1c3fd --- /dev/null +++ b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/dialogs/widgetbootstrapAlert.js @@ -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 index 000000000..4ad7e2e91 --- /dev/null +++ b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/dialogs/widgetfoundationAccordion.js @@ -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 index 0000000000000000000000000000000000000000..6ae2f5be08407d363c6ad7fdf9471f068245c4ac GIT binary patch literal 182 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmPq7q|Mj literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..d1cde64cc0b9b5c3ce4fbeaebe95b96b09939e41 GIT binary patch literal 167 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmPq7qv274!UiXFMh{F4kKxnOj1$l<{c771rj1tPDT2rFThunzsz7pTX1B&t;uc GLK6Va!ZE)9 literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..432a14252e2723daa20225a9ae06b85eda5b05f7 GIT binary patch literal 170 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmPq7qr;B5V#p&cPU%&o6@4%3AZ_mt) zYuECwwwiW!wz;uU07FDaDWe~=Tdc!QZVAm&#-j;WSep;BGNhf-mj3JUSO{nYgQu&X J%Q~loCID0sF_{1W literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..58a3d2e6d5ae6c7c7006c0f4063a10d19d615703 GIT binary patch literal 154 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmPq7qs^ED7?bDSWlw#ph6)dL+}}Y%VVcK{{pozc)I$ztaD0e0swzwE)@U( literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..9e8e9f11b7e21f2cc23afd3494f8b0c9ff0d42cb GIT binary patch literal 154 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmPq7q7?7HjaT$?*{rL#3m>W%%*0Oh7FRp00i_>zopr0FT=(ApigX literal 0 HcmV?d00001 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 index 000000000..e670302ef --- /dev/null +++ b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/plugin.js @@ -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: + '
' + + '

' + + '

Content

' + + '
', + + 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: + '
' + + '

Content

' + + '

' + + '
', + + 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: + '
' + + '

Content

' + + '

Content

' + + '
', + + 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: + '
' + + '

Text below

' + + '

Text below

' + + '

Text below

' + + '
', + + 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: '
Some Text
', + + 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: + '
', + + + 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 = + '
' + + '
Heading '+i+'
' + + '
' + + '' + + '
' + '
' + 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 index 000000000..51d3fe66b --- /dev/null +++ b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/samples/index.html @@ -0,0 +1,179 @@ + + + + + + Bootstrap, from Twitter + + + + + + + + + + + + + + + + + + + + + +
+

CKEditor Bootstrap Widgets

+

+ Give your content authors the ability to add responsive layout elements to their pages. CKEditor widgets are provided for common + Bootstrap layouts: +

+
    +
  • Left sidebar image
  • +
  • Right sidebar image
  • +
  • Two columns
  • +
  • Three columns
  • +
  • Alert box
  • +
+

Each widget can have its own button, or you can use the Widget Template Menu plugin to create a dropdown menu of templates.

+

Watch a demo: http://albatrossdigital.com/node/41

+ +

Using

+

Install the plugin using the standard CKEditor plugin installation instructions. + We recommend manually building your editor on ckeditor.com, or using our base editor build, which will include everything you need.

+ +

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.

+ + +

Widget Template Menu

+ +
+        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}(*);'
+        } );
+      
+ + + + + + +

Toolbar Groups

+
+        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}(*);'
+        } );
+      
+ + + + + +

About the Creators

+
+
+

Albatross Digital

+
+ +
+

Albatross Digital 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, have a look at our work or send us a note at hello@albatrossdigital.com. +

+
+
+ + + + 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 index 000000000..7fbfef244 --- /dev/null +++ b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetbootstrap/samples/simplebox.html @@ -0,0 +1,86 @@ + + + + + Tutorial — Simple Box Widget, part 2 + + + + +

+ Tutorial — Simple Box Widget, part 2 +

+ + + + + + + \ 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 index 000000000..7ff971c3e --- /dev/null +++ b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/contents.css @@ -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 index 000000000..82d683e4c --- /dev/null +++ b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/dialogs/foundation2Col.js @@ -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 index 000000000..4ad7e2e91 --- /dev/null +++ b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/dialogs/widgetfoundationAccordion.js @@ -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 index 0000000000000000000000000000000000000000..6a5e3131a5df3aa4ab6092ebd71709df7e94864b GIT binary patch literal 286 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf2?p zUk71ECym(^Ktah8*NBqf{Irtt#G+J&^73-M%)IR4|1Qb=M1#Umk--=gUtKyzfd}z`i>xyud#G-TWjTAa-@Asxv-Zrx| zezDiysMBzkH=E(H<6zopr0E%^F9smFU literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..714bee1271ee37b6bb48cda617578f90af3e44d9 GIT binary patch literal 597 zcmV-b0;>IqP)9(Tkeg@%HxZSY+2Mr`R~vn>X{#FJ|CBMP-j_by@*MKm!S2>-7a0 z1P1+7?sFetR#uubaoktFA3XjGtb<_nw6*c<#KeJn6{+S1aJ)c`@z+GB0+Njj4aFz1`i_U8-+4t6>}ABxC?b52CmY!C$RNC&~zR(A$C zlxDx~`~FHEZnrm%I+vVt&H=m(g5Ygdo!)`uYO066AHK@Nz)-ileKCar{U|?JJvq65 z446vwZO<#$Jg;&xgMm1TqT{JOf@H??N|yjttxhW)$0LW?-G?J0QZq(<2*dC!5Cd(X zl-h@ZJ0iR(mrHX%Q5)mFwOueZc`82E-VBEsFZXdU?711z5{qr4%AU zzaRHf7!f`WCQ*t+gd`mz_qEo)Vdor5F~18W4*;7_0_t_KSlG^1d8E;3UP+SVaumg) zHP3xNe38#usZ>rB3Z|wMWu?@6V+wZ{7eDo~0DIY4=kN9ULNeHIwK}bValimB@0YD| j0&M={`i9dM{' + + '
Title
' + + '
Content
' + + '
', + + 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: + '
' + + '
Quote
' + + '' + + '
', + + 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 index 000000000..bd413ac60 --- /dev/null +++ b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/samples/contents.css @@ -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 index 000000000..7fbfef244 --- /dev/null +++ b/web/modules/contrib/ckeditor_widgets/js/plugins/widgetcommon/samples/simplebox.html @@ -0,0 +1,86 @@ + + + + + Tutorial — Simple Box Widget, part 2 + + + + +

+ Tutorial — Simple Box Widget, part 2 +

+ + + + + + + \ 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 index 0000000000000000000000000000000000000000..187d188019c74ddff755068f64bfe8f2a95ff467 GIT binary patch literal 597 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b zKpodXn9)gNb_D|iW1gprV~EA+sgn+NNjVC%?T;!FJhnjmq~j#P)OAiB3PPMuo*B2) z`tk3a#m-^rdLvP>LUOU3(9+Asm+SegU+>zjW*r^y$2G8|HuUPVy~i!vRa6qwwwkmw z6!S57R!wUYVAygqXUB?3HtzYF40>mH7>o~}332y-^`4_@@4Z6)|DP@T`c)>qt6sp8{=1?xPOPH$RXUNCvvw3)RlAGOZh%d+NX&b>c; zo+Z0u=Ndl{{h;eP$$rv{@4qcSh`yL}p}TCi?t_Kj8(+UHiSqar+50owXy%jyq5|^k zH{8vW&#keMtC!$wUmv}8+S%uy{TaHCJ~HZ3lJ0f;m}6GWzF?CMRdP`(kYX@0Ff`FMFxE9P4>2^fGBLI?vD7v&v@$S|3;Op8MMG|WN@iLmZVmbT zZec(Tk{}y`^V3So6N^$A%FE03GV`*FlM@S4_413-XTP(N0xDwgboFyt=akR{0I06@ A761SM literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..16fd067330fdbbc046499facf90147abfcea7a07 GIT binary patch literal 165 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmPq7cYyxOswARTA+}Tr;B5V#p&dfhKBzc-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxKsVXI%s|1+P|wiV z#N6CmN5ROz&_Lh7NZ-&%*U;R`*vQJjKmiJrfVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8EkR}&8R-I5=oVMzl_XZ^<`pZ$OmImpPAEg{v+u2}(t{7puX=A(aKG z`a!A1`K3k4z=%sz23b{L^kHi(Dwi8grF9e7Llf2sSTVSlCnz0k z5m8h)mm02gN3xGw?EB$On|J?_-@Mtnr{jRB4Bz~N@}+y@Bd|F2%UK?A1ltRcR2odV%Ta?QCG*(DjT^e%T~_VIqkG+ z;rs1>cHPxWiCpKqRma3?uHRo#yNmxBPeol}-lWr>q#mreJ%TvE&4uh`_GhPCjDdI v{T*WR=EAFQMPC2vHsSPBrCUF}8yFcTObP0l+XkKSpl?W literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..803b31a71706a6666b7cb5e830681b7359971966 GIT binary patch literal 269 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 VN}v7CMhd8i!PC{xWt~$(69ATN$p`=d literal 0 HcmV?d00001 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 index 000000000..b8b437495 --- /dev/null +++ b/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/plugin.js @@ -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 index 000000000..bd413ac60 --- /dev/null +++ b/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/samples/contents.css @@ -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 index 000000000..7fbfef244 --- /dev/null +++ b/web/modules/contrib/ckeditor_widgets/js/plugins/widgettemplatemenu/samples/simplebox.html @@ -0,0 +1,86 @@ + + + + + Tutorial — Simple Box Widget, part 2 + + + + +

+ Tutorial — Simple Box Widget, part 2 +

+ + + + + + + \ 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 index 000000000..9e574cce3 --- /dev/null +++ b/web/modules/contrib/ckeditor_widgets/src/Plugin/CKEditorPlugin/WidgetBootstrap.php @@ -0,0 +1,88 @@ + 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 index 000000000..a8021f40b --- /dev/null +++ b/web/modules/contrib/ckeditor_widgets/src/Plugin/CKEditorPlugin/WidgetCommon.php @@ -0,0 +1,72 @@ + 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 index 000000000..f5db7c197 --- /dev/null +++ b/web/modules/contrib/ckeditor_widgets/src/Plugin/CKEditorPlugin/WidgetTemplateMenu.php @@ -0,0 +1,84 @@ + 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 index 8bf2d9c14..000000000 --- a/web/modules/contrib/drupalmoduleupgrader +++ /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 index 000000000..f350693d8 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/.gitignore @@ -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 index 000000000..e69de29bb diff --git a/web/modules/contrib/drupalmoduleupgrader/README.txt b/web/modules/contrib/drupalmoduleupgrader/README.txt new file mode 100644 index 000000000..b44d7b675 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/README.txt @@ -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 index 000000000..0e09eb096 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/composer.json @@ -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 index 000000000..377a8d81b --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/composer.lock @@ -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 index 000000000..93e55d4a7 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.entity_operations.yml @@ -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 index 000000000..a7243c516 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.functions.yml @@ -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 route.' + documentation: + - + url: 'https://www.drupal.org/node/2382211' + title: '`current_path()` replaced by the 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 index 000000000..9de89bf88 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.grep.yml @@ -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("")->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 index 000000000..5be992672 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.hooks.yml @@ -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 index 000000000..6c74cb08f --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.rewriters.yml @@ -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 index 000000000..e8faf6ec0 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/config/install/drupalmoduleupgrader.tags.yml @@ -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 index 000000000..ee17fe54a --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/drupalmoduleupgrader.drush.inc @@ -0,0 +1,322 @@ + '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 index 000000000..d7f0c6a8d --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/drupalmoduleupgrader.info.yml @@ -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 index 000000000..b01d5e5bb --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/drupalmoduleupgrader.module @@ -0,0 +1,124 @@ + [ + '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 index 000000000..045bdaaf2 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/drupalmoduleupgrader.services.yml @@ -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 index 000000000..011f984b4 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/phpunit.xml.dist @@ -0,0 +1,15 @@ + + + + + + + + + + + + ./tests + + + diff --git a/web/modules/contrib/drupalmoduleupgrader/src/AnalyzerBase.php b/web/modules/contrib/drupalmoduleupgrader/src/AnalyzerBase.php new file mode 100644 index 000000000..d2e483409 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/AnalyzerBase.php @@ -0,0 +1,39 @@ +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 index 000000000..d315bdd01 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/AnalyzerInterface.php @@ -0,0 +1,21 @@ +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 index 000000000..8b2c4e488 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/ConverterBase.php @@ -0,0 +1,285 @@ +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 index 000000000..59484b828 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/ConverterInterface.php @@ -0,0 +1,30 @@ +newInstanceArgs($arguments); + } + +} diff --git a/web/modules/contrib/drupalmoduleupgrader/src/DeriverBase.php b/web/modules/contrib/drupalmoduleupgrader/src/DeriverBase.php new file mode 100644 index 000000000..e8b251ad9 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/DeriverBase.php @@ -0,0 +1,40 @@ +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 index 000000000..52ebe5191 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/FixerBase.php @@ -0,0 +1,66 @@ +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 index 000000000..16427f5db --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/FixerInterface.php @@ -0,0 +1,26 @@ +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 index 000000000..224c5e032 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/IndexerExecutionInterface.php @@ -0,0 +1,19 @@ +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 index 000000000..1661b1e36 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/IssueInterface.php @@ -0,0 +1,166 @@ +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 index 000000000..03401cdb0 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/DBDeriver.php @@ -0,0 +1,36 @@ +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 index 000000000..08882c79b --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/FlagHook.php @@ -0,0 +1,31 @@ +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 index 000000000..09a671615 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/FlagHookDeriver.php @@ -0,0 +1,60 @@ +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 index 000000000..c022d9bb4 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/FunctionCall.php @@ -0,0 +1,39 @@ +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 index 000000000..77400c0cf --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/FunctionCallDeriver.php @@ -0,0 +1,63 @@ +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 index 000000000..ce065e10e --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/Grep.php @@ -0,0 +1,36 @@ +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 index 000000000..bfcea8a95 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/HookFormAlter.php @@ -0,0 +1,70 @@ +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 index 000000000..c0125870c --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/HookPermission.php @@ -0,0 +1,48 @@ +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 index 000000000..a6ff1207f --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/HookUninstall.php @@ -0,0 +1,47 @@ +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 index 000000000..1e21e9503 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/InfoFile.php @@ -0,0 +1,70 @@ +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 index 000000000..d18a9293b --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/PSR4.php @@ -0,0 +1,46 @@ +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 index 000000000..611c4454e --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Analyzer/Tests.php @@ -0,0 +1,63 @@ +getInfo() in test classes replaced by doc comments") + * }, + * { + * "url" = "https://www.drupal.org/node/1710766", + * "title" = @Translation("Test classes should define a $modules property declaring dependencies") + * }, + * { + * "url" = "https://www.drupal.org/node/1911318", + * "title" = @Translation("SimpleTest tests now use empty "testing" profile by default") + * }, + * { + * "url" = "https://www.drupal.org/node/1829160", + * "title" = @Translation("New KernelTestBase 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 index 000000000..8edaba596 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Blocks.php @@ -0,0 +1,99 @@ +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 index 000000000..ee8a6f684 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/EntityHooks.php @@ -0,0 +1,104 @@ +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 index 000000000..baf51962d --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CToolsGetPlugins.php @@ -0,0 +1,66 @@ +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 index 000000000..be7762aee --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CToolsObjectCacheGet.php @@ -0,0 +1,27 @@ +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 index 000000000..b122060dd --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CToolsObjectCacheSet.php @@ -0,0 +1,35 @@ +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 index 000000000..786d465a1 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CacheGet.php @@ -0,0 +1,31 @@ +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 index 000000000..d53968236 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CacheSet.php @@ -0,0 +1,40 @@ +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 index 000000000..d56484ae1 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/CommentLoad.php @@ -0,0 +1,39 @@ +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 index 000000000..3ce82a413 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DB.php @@ -0,0 +1,32 @@ +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 index 000000000..c2bc8614d --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DBDeriver.php @@ -0,0 +1,36 @@ + $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 index 000000000..32a4207ba --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/Disable.php @@ -0,0 +1,23 @@ +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 index 000000000..0b9b81c33 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DrupalGetTitle.php @@ -0,0 +1,28 @@ +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 index 000000000..6f4165afc --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DrupalIsCLI.php @@ -0,0 +1,24 @@ +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 index 000000000..c836a8943 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/DrupalWriteRecord.php @@ -0,0 +1,48 @@ +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 index 000000000..4d4df1347 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/EntityCreate.php @@ -0,0 +1,30 @@ +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 index 000000000..f61db9c71 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/EntityGetInfo.php @@ -0,0 +1,34 @@ +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 index 000000000..f58c951e8 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/EntityLoad.php @@ -0,0 +1,55 @@ +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 index 000000000..73e9ab2ae --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/EntityOperation.php @@ -0,0 +1,27 @@ +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 index 000000000..efe757043 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/EntityOperationDeriver.php @@ -0,0 +1,60 @@ +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 index 000000000..2eb734af2 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldInfoFieldTypes.php @@ -0,0 +1,35 @@ +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 index 000000000..76fab59f1 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldInfoFormatterTypes.php @@ -0,0 +1,35 @@ +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 index 000000000..c132184f6 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldInfoWidgetTypes.php @@ -0,0 +1,35 @@ +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 index 000000000..96408a193 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldUpdateField.php @@ -0,0 +1,24 @@ +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 index 000000000..79bdf043d --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldUpdateInstance.php @@ -0,0 +1,24 @@ +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 index 000000000..0a0ac2d16 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldViewField.php @@ -0,0 +1,35 @@ +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 index 000000000..4eadf3590 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FieldViewValue.php @@ -0,0 +1,31 @@ +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 index 000000000..751426943 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FormExecuteHandlers.php @@ -0,0 +1,37 @@ +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 index 000000000..0c5332c00 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FormLoadInclude.php @@ -0,0 +1,34 @@ +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 index 000000000..1d3aa8404 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FormSetValue.php @@ -0,0 +1,28 @@ +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 index 000000000..191cb5dc8 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FormStateDefaults.php @@ -0,0 +1,27 @@ +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 index 000000000..b53e0b261 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/FunctionCallModifier.php @@ -0,0 +1,76 @@ +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 index 000000000..76bcd7185 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/GetT.php @@ -0,0 +1,24 @@ +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 index 000000000..57d2915e9 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/LoadMultiple.php @@ -0,0 +1,53 @@ +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 index 000000000..6bb7bc325 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/LoadMultipleDeriver.php @@ -0,0 +1,34 @@ + $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 index 000000000..39acaa381 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/ModuleInvoke.php @@ -0,0 +1,36 @@ +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 index 000000000..e1a60c0d2 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/ModuleInvokeAll.php @@ -0,0 +1,35 @@ +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 index 000000000..405b0ea11 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/NodeLoad.php @@ -0,0 +1,50 @@ +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 index 000000000..e1b267764 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/St.php @@ -0,0 +1,23 @@ +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 index 000000000..731999694 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/ThemeGetRegistry.php @@ -0,0 +1,30 @@ +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 index 000000000..b1966884c --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/URL.php @@ -0,0 +1,79 @@ +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 index 000000000..8bed401fa --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/UserAccess.php @@ -0,0 +1,35 @@ +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 index 000000000..7ff09735b --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/UserLoad.php @@ -0,0 +1,39 @@ +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 index 000000000..0a76940be --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/UserSave.php @@ -0,0 +1,29 @@ +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 index 000000000..b60dab5e3 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/VariableAPI.php @@ -0,0 +1,51 @@ +getStatement(); + $arguments = $call->getArguments(); + + if ($arguments[0] instanceof StringNode) { + $key = $arguments[0]->toValue(); + + if (strPos($key, $target->id() . '_') === 0) { + return TRUE; + } + else { + $comment = <<buildFixMe($comment)->prependTo($statement); + return FALSE; + } + } + else { + $comment = <<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 index 000000000..965ca7320 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/VariableDel.php @@ -0,0 +1,30 @@ +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 index 000000000..915edb356 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/VariableGet.php @@ -0,0 +1,91 @@ +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 = << $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 index 000000000..682e10c21 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/VariableSet.php @@ -0,0 +1,34 @@ +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 index 000000000..91a84f936 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Functions/Watchdog.php @@ -0,0 +1,63 @@ +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 index 000000000..7fbf063d6 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Grep.php @@ -0,0 +1,80 @@ + $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 index 000000000..6c687b121 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookBoot.php @@ -0,0 +1,49 @@ +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 index 000000000..60f7601c6 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookEntityInfo.php @@ -0,0 +1,87 @@ +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 index 000000000..8bfb9b37e --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookEntityTypeView.php @@ -0,0 +1,54 @@ +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 index 000000000..25196f2a2 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookExit.php @@ -0,0 +1,43 @@ +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 index 000000000..95cecd7c0 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFieldAttachCreateBundle.php @@ -0,0 +1,29 @@ +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 index 000000000..e8f567f16 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFieldAttachDeleteBundle.php @@ -0,0 +1,38 @@ +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 index 000000000..291c0520c --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFieldAttachRenameBundle.php @@ -0,0 +1,29 @@ +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 index 000000000..0eebc622a --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFieldFormatterInfo.php @@ -0,0 +1,51 @@ +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 index 000000000..f69565e20 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFieldWidgetInfo.php @@ -0,0 +1,51 @@ +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 index 000000000..0ee54c209 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookFormAlter.php @@ -0,0 +1,43 @@ +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 index 000000000..0d13efd09 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookInit.php @@ -0,0 +1,43 @@ +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 index 000000000..d1a8e5f08 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookLibrary.php @@ -0,0 +1,47 @@ +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 index 000000000..7a3c66531 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookMenuAlter.php @@ -0,0 +1,87 @@ +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 index 000000000..44a9a531e --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookNodePrepare.php @@ -0,0 +1,57 @@ +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 index 000000000..6949f9031 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookPermission.php @@ -0,0 +1,46 @@ +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 index 000000000..09a4e079d --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookURLOutboundAlter.php @@ -0,0 +1,41 @@ +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 index 000000000..70c47826b --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookUserLogin.php @@ -0,0 +1,66 @@ +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 index 000000000..aff15a2e8 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/HookWatchdog.php @@ -0,0 +1,35 @@ +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 index 000000000..2d0dca4f4 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/InfoToYAML.php @@ -0,0 +1,97 @@ +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 index 000000000..386fed475 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Links.php @@ -0,0 +1,128 @@ +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 index 000000000..67b621b8c --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/PSR4.php @@ -0,0 +1,60 @@ +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 index 000000000..8b88019c3 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Routing.php @@ -0,0 +1,90 @@ +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 index 000000000..d3e8b66e9 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/Tests.php @@ -0,0 +1,201 @@ +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 index 000000000..f6e0fb85d --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Converter/UnitTests.php @@ -0,0 +1,62 @@ +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 = <<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', "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 index 000000000..45d4734c7 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/CreateClass.php @@ -0,0 +1,84 @@ +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 index 000000000..8a2bd1d12 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/Define.php @@ -0,0 +1,29 @@ +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 index 000000000..389ee24ff --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/Delete.php @@ -0,0 +1,33 @@ +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 index 000000000..8fc1fa055 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/Disable.php @@ -0,0 +1,35 @@ +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 index 000000000..a19c2a127 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/FormCallbackToMethod.php @@ -0,0 +1,43 @@ +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 index 000000000..96938d35d --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/HookToYAML.php @@ -0,0 +1,26 @@ +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 index 000000000..7498ab1fa --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/Implement.php @@ -0,0 +1,43 @@ +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 index 000000000..165bf0828 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/ImplementHook.php @@ -0,0 +1,62 @@ +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 index 000000000..7ae15b0a9 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/NodeCollectorTrait.php @@ -0,0 +1,30 @@ +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 index 000000000..44f8db4c8 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/Notify.php @@ -0,0 +1,72 @@ +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 index 000000000..984b5fb20 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Fixer/PSR4.php @@ -0,0 +1,74 @@ +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 index 000000000..e5d962497 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Indexer/Classes.php @@ -0,0 +1,118 @@ +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 index 000000000..e4b80496d --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Indexer/Constants.php @@ -0,0 +1,94 @@ +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 index 000000000..cc5ac7821 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Indexer/FunctionCalls.php @@ -0,0 +1,69 @@ +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 index 000000000..ad575854b --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Indexer/Functions.php @@ -0,0 +1,212 @@ +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 index 000000000..b710df9df --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Rewriter/FormState.php @@ -0,0 +1,214 @@ +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 index 000000000..e3f07b40e --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Rewriter/Generic.php @@ -0,0 +1,306 @@ +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 index 000000000..9ca0960c7 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Rewriter/GenericDeriver.php @@ -0,0 +1,56 @@ +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 index 000000000..77bb9c5c6 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Routing/ContentRoute.php @@ -0,0 +1,241 @@ +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 index 000000000..b60727c19 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Routing/FormRoute.php @@ -0,0 +1,109 @@ +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 index 000000000..506a3a139 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/PluginBase.php @@ -0,0 +1,53 @@ +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 index 000000000..995a1f48b --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Report.php @@ -0,0 +1,45 @@ +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 index 000000000..cb5e73cf8 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/ReportInterface.php @@ -0,0 +1,33 @@ +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 index 000000000..655420eb2 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Routing/Drupal7/RouteWrapper.php @@ -0,0 +1,257 @@ +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(); + // $path needs to be explicitly cast to a string, 'cause strPos() won't do + // it, even though trim() and similar functions will. + 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(); + // strPos(), in its wisdom, won't cast to string. + 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 index 000000000..38e0a5735 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Routing/Drupal7/Router.php @@ -0,0 +1,99 @@ +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 index 000000000..5266b4d54 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Routing/Drupal8/RouteWrapper.php @@ -0,0 +1,133 @@ +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 index 000000000..d5666d8af --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Routing/HookMenu.php @@ -0,0 +1,169 @@ +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 index 000000000..217d8d7a5 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkBinding/LinkBinding.php @@ -0,0 +1,107 @@ +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 index 000000000..1168bdee6 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkBinding/LinkBindingFactory.php @@ -0,0 +1,51 @@ +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 index 000000000..4aedc6946 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkBinding/LocalActionLinkBinding.php @@ -0,0 +1,20 @@ +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 index 000000000..39ae82b25 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkBinding/LocalTaskLinkBinding.php @@ -0,0 +1,86 @@ +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 index 000000000..854718b09 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkBinding/MenuLinkBinding.php @@ -0,0 +1,29 @@ +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 index 000000000..71a0846fa --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Routing/LinkIndex.php @@ -0,0 +1,60 @@ +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 index 000000000..d43ef40c6 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Routing/ParameterBinding.php @@ -0,0 +1,122 @@ +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 index 000000000..65eb3affb --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Routing/ParameterMap.php @@ -0,0 +1,156 @@ +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 index 000000000..22b1c7772 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Routing/RouteConverterInterface.php @@ -0,0 +1,51 @@ +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 index 000000000..c4f230716 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Routing/RouterBuiltEvent.php @@ -0,0 +1,39 @@ +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 index 000000000..02660338d --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Routing/RouterInterface.php @@ -0,0 +1,31 @@ +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 index 000000000..9e98baecd --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/TargetInterface.php @@ -0,0 +1,126 @@ + 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 index 000000000..82535df7e --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Utility/Filter/ContainsLogicFilter.php @@ -0,0 +1,68 @@ +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 index 000000000..63d9af918 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Utility/Filter/FieldValueFilter.php @@ -0,0 +1,45 @@ +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 index 000000000..97623ab70 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Utility/Filter/FunctionCallArgumentFilter.php @@ -0,0 +1,40 @@ +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 index 000000000..dd83849ce --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Utility/Filter/NodeAssignmentFilter.php @@ -0,0 +1,24 @@ +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 index 000000000..9baf2accf --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Utility/FormConverter.php @@ -0,0 +1,178 @@ +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 index 000000000..de35ebf36 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Utility/FormConverterFactory.php @@ -0,0 +1,52 @@ +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 index 000000000..f29b2e1ec --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/Drupal7/PathComponent.php @@ -0,0 +1,33 @@ +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 index 000000000..2adf5a956 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/Drupal7/PathUtility.php @@ -0,0 +1,64 @@ +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 index 000000000..07d1661ce --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/Drupal8/PathComponent.php @@ -0,0 +1,24 @@ +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 index 000000000..824636751 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/Drupal8/PathUtility.php @@ -0,0 +1,24 @@ +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 index 000000000..5b3cf6c8b --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/PathComponentInterface.php @@ -0,0 +1,34 @@ +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 index 000000000..f9a65aa05 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/src/Utility/Path/PathUtilityInterface.php @@ -0,0 +1,61 @@ +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 index 000000000..5b8da6e46 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/templates/Block.html.twig @@ -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 index 000000000..fc045ddc0 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/templates/Controller.html.twig @@ -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 index 000000000..a3b941797 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/templates/EntityType.html.twig @@ -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 index 000000000..7c7c42152 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/templates/EventSubscriber.html.twig @@ -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 index 000000000..86746eb19 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/templates/Form.html.twig @@ -0,0 +1,51 @@ +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 index 000000000..e55cc584c --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/templates/Formatter.html.twig @@ -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 index 000000000..320d3fe63 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/templates/Issue.html.twig @@ -0,0 +1,27 @@ +{% autoescape false %} +
+ {{ issue.Title() }} + + {% if issue.Summary is not empty %}
{{ issue.Summary }}
{% endif %} + + {% if issue.Documentation is not empty %} +
Documentation
+
    + {% for doc in issue.Documentation %} +
  • {{ doc.title }}
  • + {% endfor %} +
+ {% endif %} + + {% if issue.Violations is not empty %} +
Files Affected
+
    + {% for violation in issue.Violations %} +
  • {{ violation.file }}{% if violation.line_number %}, line {{ violation.line_number }}{% endif %}
  • + {% endfor %} +
+ {% endif %} + {% if issue.hasTag('fixable') %}

This issue can be fixed automatically.

{% endif %} + {% if issue.Detectors is not empty %}{% endif %} +
+{% 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 index 000000000..34af48fe7 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/templates/Logger.html.twig @@ -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 index 000000000..d8978f0cd --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/templates/OutboundPathProcessor.html.twig @@ -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 index 000000000..8d9e8d7ed --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/templates/Report.html.twig @@ -0,0 +1,87 @@ + + + + + + + + {% for tag, tagged_issues in issues %} +
+ {{ tag }} + {{ tagged_issues }} +
+ {% endfor %} + + diff --git a/web/modules/contrib/drupalmoduleupgrader/templates/RouteSubscriber.html.twig b/web/modules/contrib/drupalmoduleupgrader/templates/RouteSubscriber.html.twig new file mode 100644 index 000000000..32429bdc2 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/templates/RouteSubscriber.html.twig @@ -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 index 000000000..18c1fbe68 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/templates/Widget.html.twig @@ -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 index 000000000..4e074a1fb --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/bootstrap.php @@ -0,0 +1,4 @@ +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 index 000000000..e4813dc84 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/IssueTest.php @@ -0,0 +1,94 @@ +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("

Lorem ipsum dolor sit amet, consectetuer adipiscing elit.

\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' +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 index 000000000..1115939b1 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/ModuleMockerTrait.php @@ -0,0 +1,31 @@ +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 index 000000000..b855360a7 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/AnalyzerTestBase.php @@ -0,0 +1,43 @@ + $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 index 000000000..0f96372f9 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/DBTest.php @@ -0,0 +1,47 @@ +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 index 000000000..338d66fcb --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/FlagHookTest.php @@ -0,0 +1,47 @@ +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 index 000000000..e245b9c0c --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/FunctionCallTest.php @@ -0,0 +1,45 @@ +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 index 000000000..5dd474d73 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/HookFormAlterTest.php @@ -0,0 +1,61 @@ +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 index 000000000..3ce57df7f --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/HookPermissionTest.php @@ -0,0 +1,53 @@ +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 index 000000000..59f04e80e --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/HookUninstallTest.php @@ -0,0 +1,47 @@ +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 index 000000000..edd054c2e --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/InfoFileTest.php @@ -0,0 +1,38 @@ +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 index 000000000..ccd0ae3e8 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/PSR4Test.php @@ -0,0 +1,41 @@ +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 index 000000000..7880e3b78 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Analyzer/TestsTest.php @@ -0,0 +1,42 @@ +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 index 000000000..0c5858b51 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CToolsGetPluginsTest.php @@ -0,0 +1,41 @@ +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 index 000000000..7d3075fd5 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CToolsObjectCacheGetTest.php @@ -0,0 +1,20 @@ +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 index 000000000..5dff00840 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CToolsObjectCacheSetTest.php @@ -0,0 +1,27 @@ +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 index 000000000..508392a2d --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CacheGetTest.php @@ -0,0 +1,27 @@ +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 index 000000000..19f3d8186 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CacheSetTest.php @@ -0,0 +1,34 @@ +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 index 000000000..9ff350301 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/CommentLoadTest.php @@ -0,0 +1,37 @@ +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 index 000000000..398df54e1 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DBTest.php @@ -0,0 +1,31 @@ +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 index 000000000..01f9c7987 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DisableTest.php @@ -0,0 +1,26 @@ +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 index 000000000..85530e0df --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DrupalGetTitleTest.php @@ -0,0 +1,25 @@ +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 index 000000000..7ec922449 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DrupalIsCLITest.php @@ -0,0 +1,20 @@ +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 index 000000000..7c93a2344 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DrupalMapAssocTest.php @@ -0,0 +1,21 @@ +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 index 000000000..5dd812fa6 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/DrupalWriteRecordTest.php @@ -0,0 +1,34 @@ +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 index 000000000..d8bb3a5dc --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/EntityCreateTest.php @@ -0,0 +1,20 @@ +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 index 000000000..7f6725360 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/EntityGetInfoTest.php @@ -0,0 +1,27 @@ +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 index 000000000..f3bee1301 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldInfoFieldTypesTest.php @@ -0,0 +1,27 @@ +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 index 000000000..45c4775ec --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldInfoFormatterTypesTest.php @@ -0,0 +1,27 @@ +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 index 000000000..8330797e7 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldInfoWidgetTypesTest.php @@ -0,0 +1,27 @@ +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 index 000000000..1722eeb9b --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldUpdateFieldTest.php @@ -0,0 +1,20 @@ +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 index 000000000..aa8ccf3bc --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldUpdateInstanceTest.php @@ -0,0 +1,20 @@ +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 index 000000000..2ec55c9f9 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldViewFieldTest.php @@ -0,0 +1,27 @@ +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 index 000000000..4f6ac6eda --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FieldViewValueTest.php @@ -0,0 +1,27 @@ +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 index 000000000..9a0bded7d --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FormExecuteHandlersTest.php @@ -0,0 +1,33 @@ +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 index 000000000..406c6b3e7 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FormLoadIncludeTest.php @@ -0,0 +1,27 @@ +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 index 000000000..e8d91d5c7 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FormSetValueTest.php @@ -0,0 +1,20 @@ +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 index 000000000..5337e38bb --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FormStateValuesCleanTest.php @@ -0,0 +1,20 @@ +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 index 000000000..1947b8343 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/FunctionCallModifierTestBase.php @@ -0,0 +1,32 @@ +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 index 000000000..6ab4d67a7 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/GetTTest.php @@ -0,0 +1,21 @@ +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 index 000000000..a31e5b9c6 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/ModuleInvokeAllTest.php @@ -0,0 +1,27 @@ +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 index 000000000..c59261949 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/ModuleInvokeTest.php @@ -0,0 +1,27 @@ +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 index 000000000..342c9c0a0 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/NodeLoadTest.php @@ -0,0 +1,56 @@ +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 index 000000000..3bb94574b --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/StTest.php @@ -0,0 +1,21 @@ +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 index 000000000..820c54e86 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/ThemeGetRegistryTest.php @@ -0,0 +1,32 @@ +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 index 000000000..0dbf92fe8 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/UserAccessTest.php @@ -0,0 +1,27 @@ +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 index 000000000..01ae76ba6 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/UserLoadTest.php @@ -0,0 +1,37 @@ +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 index 000000000..4c76947a0 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/UserSaveTest.php @@ -0,0 +1,30 @@ +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 index 000000000..248376ab1 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/VariableDelTest.php @@ -0,0 +1,61 @@ +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' +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 index 000000000..2c5f69099 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/VariableGetTest.php @@ -0,0 +1,83 @@ +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' +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' +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 index 000000000..22bc27828 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/VariableSetTest.php @@ -0,0 +1,61 @@ +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' +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 index 000000000..1e7c89e60 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Converter/Functions/WatchdogTest.php @@ -0,0 +1,62 @@ +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 index 000000000..ab1cdc78e --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/CreateClassTest.php @@ -0,0 +1,58 @@ +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 index 000000000..86dcfe856 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/DefineTest.php @@ -0,0 +1,33 @@ + '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 = <<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 index 000000000..c8be99ed4 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/DeleteTest.php @@ -0,0 +1,53 @@ + 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("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 index 000000000..13e8c601e --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/DisableTest.php @@ -0,0 +1,54 @@ +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 = <<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 index 000000000..b205e7eb8 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/FormCallbackToMethodTest.php @@ -0,0 +1,68 @@ +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 index 000000000..300bcaf13 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/HookToYAMLTest.php @@ -0,0 +1,47 @@ + [ + '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 index 000000000..f91687a14 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/ImplementHookTest.php @@ -0,0 +1,53 @@ + '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 index 000000000..6e04517f1 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/ImplementTest.php @@ -0,0 +1,55 @@ +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 index 000000000..0ba10b06a --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/NotifyTest.php @@ -0,0 +1,51 @@ +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 = <<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 index 000000000..54c42bc3d --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Fixer/PSR4Test.php @@ -0,0 +1,40 @@ +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 index 000000000..1d2e837a0 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Indexer/ClassesTest.php @@ -0,0 +1,29 @@ +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 index 000000000..d75d4c369 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Indexer/FunctionsTest.php @@ -0,0 +1,33 @@ +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 index 000000000..97c69563c --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Indexer/IndexerTestBase.php @@ -0,0 +1,46 @@ +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 index 000000000..ef15773c9 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Rewriter/FormStateTest.php @@ -0,0 +1,93 @@ + [ + '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 index 000000000..fdf5aafbd --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Plugin/DMU/Rewriter/GenericTest.php @@ -0,0 +1,140 @@ + [ + '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 index 000000000..68b5800ee --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/ReportTest.php @@ -0,0 +1,39 @@ +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 index 000000000..19b8bbc99 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/Drupal7/RouteWrapperTest.php @@ -0,0 +1,128 @@ + '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 index 000000000..f65ccd08b --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/Drupal7/RouterTest.php @@ -0,0 +1,171 @@ +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 index 000000000..db74e5859 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/Drupal8/RouteWrapperTest.php @@ -0,0 +1,35 @@ +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 index 000000000..7b9719892 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/LinkBinding/LinkBindingTest.php @@ -0,0 +1,74 @@ + '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 index 000000000..6d4d5bc57 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/ParameterBindingTest.php @@ -0,0 +1,117 @@ +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 index 000000000..ead1d4e6e --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Routing/RouterBaseTest.php @@ -0,0 +1,68 @@ +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 index 000000000..4a9b692d7 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/SQLiteDatabaseTrait.php @@ -0,0 +1,28 @@ +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 index 000000000..b48043bad --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/TargetTest.php @@ -0,0 +1,115 @@ +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 index 000000000..d3c324f9e --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/TestBase.php @@ -0,0 +1,82 @@ +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 index 000000000..44d32e956 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Filter/ContainsLogicFilterTest.php @@ -0,0 +1,62 @@ +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 index 000000000..44e3cd8e7 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Filter/FieldValueFilterTest.php @@ -0,0 +1,39 @@ +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 index 000000000..a967436d4 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Filter/FunctionCallArgumentFilterTest.php @@ -0,0 +1,40 @@ +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 index 000000000..e5806d0a7 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Filter/NodeAssignmentFilterTest.php @@ -0,0 +1,35 @@ +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 index 000000000..253b4457f --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Path/Drupal7/PathComponentTest.php @@ -0,0 +1,25 @@ +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 index 000000000..2f971101c --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Path/Drupal7/PathUtilityTest.php @@ -0,0 +1,113 @@ +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 index 000000000..9fba1ff7e --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Path/Drupal8/PathComponentTest.php @@ -0,0 +1,19 @@ +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 index 000000000..b3475cba6 --- /dev/null +++ b/web/modules/contrib/drupalmoduleupgrader/tests/src/Unit/Utility/Path/Drupal8/PathUtilityTest.php @@ -0,0 +1,95 @@ +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 index b19c6a839..000000000 --- a/web/modules/contrib/filefield_sources +++ /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 index 000000000..c0dd03aa5 --- /dev/null +++ b/web/modules/contrib/filefield_sources/README.txt @@ -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 index 000000000..1d0e67dc6 --- /dev/null +++ b/web/modules/contrib/filefield_sources/config/schema/filefield_sources.data_types.schema.yml @@ -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 index 000000000..a4bb0e7b6 --- /dev/null +++ b/web/modules/contrib/filefield_sources/config/schema/filefield_sources.schema.yml @@ -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 index 000000000..161685631 --- /dev/null +++ b/web/modules/contrib/filefield_sources/config/schema/filefield_sources.setting.source_attach.schema.yml @@ -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 index 000000000..cd51b21f4 --- /dev/null +++ b/web/modules/contrib/filefield_sources/config/schema/filefield_sources.setting.source_imce.schema.yml @@ -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 index 000000000..444e9acee --- /dev/null +++ b/web/modules/contrib/filefield_sources/config/schema/filefield_sources.setting.source_reference.schema.yml @@ -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 index 000000000..cd50a5110 --- /dev/null +++ b/web/modules/contrib/filefield_sources/config/schema/filefield_sources.setting.sources.schema.yml @@ -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 index 000000000..ab7a53f26 --- /dev/null +++ b/web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.attach.schema.yml @@ -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 index 000000000..e400679e9 --- /dev/null +++ b/web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.clipboard.schema.yml @@ -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 index 000000000..ec3b5a37c --- /dev/null +++ b/web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.imce.schema.yml @@ -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 index 000000000..4f77b1356 --- /dev/null +++ b/web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.reference.schema.yml @@ -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 index 000000000..a26f8dcae --- /dev/null +++ b/web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.remote.schema.yml @@ -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 index 000000000..35208c72b --- /dev/null +++ b/web/modules/contrib/filefield_sources/config/schema/filefield_sources.source.upload.schema.yml @@ -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 index 000000000..4754fde22 --- /dev/null +++ b/web/modules/contrib/filefield_sources/css/filefield_sources.css @@ -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 index 000000000..ecb9a1cba --- /dev/null +++ b/web/modules/contrib/filefield_sources/filefield_sources.api.php @@ -0,0 +1,42 @@ + '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[] = '' . $source['label'] . ''; + } + return '
' . implode(' | ', $links) . '
'; +} + +/** + * 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 .= '
  • ' . implode('
  • ', $errors) . '
'; + } + 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('
', $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 index 000000000..002d37c28 --- /dev/null +++ b/web/modules/contrib/filefield_sources/filefield_sources.routing.yml @@ -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 index 000000000..354207bc6 --- /dev/null +++ b/web/modules/contrib/filefield_sources/filefield_sources.services.yml @@ -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 index 000000000..1f66a3734 --- /dev/null +++ b/web/modules/contrib/filefield_sources/js/filefield_sources.js @@ -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 index 000000000..b70f53574 --- /dev/null +++ b/web/modules/contrib/filefield_sources/src/Access/FieldAccessCheck.php @@ -0,0 +1,38 @@ +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 index 000000000..3a873fea3 --- /dev/null +++ b/web/modules/contrib/filefield_sources/src/Annotation/FilefieldSource.php @@ -0,0 +1,62 @@ +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 index 000000000..2c44e97ff --- /dev/null +++ b/web/modules/contrib/filefield_sources/src/File/MimeType/ExtensionMimeTypeGuesser.php @@ -0,0 +1,75 @@ +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 index 000000000..8ea45ca7c --- /dev/null +++ b/web/modules/contrib/filefield_sources/src/FilefieldSourceInterface.php @@ -0,0 +1,55 @@ +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 index 000000000..8f2a65fbf --- /dev/null +++ b/web/modules/contrib/filefield_sources/src/FilefieldSourcesServiceProvider.php @@ -0,0 +1,26 @@ +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 index 000000000..1993c0e77 --- /dev/null +++ b/web/modules/contrib/filefield_sources/src/ImceScanner.php @@ -0,0 +1,102 @@ +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 index 000000000..ca965f0d5 --- /dev/null +++ b/web/modules/contrib/filefield_sources/src/Plugin/FilefieldSource/Attach.php @@ -0,0 +1,356 @@ +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 .= '
' . 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 .= ''; + } + // Get rendered select. + $size = !empty($element['filename']['#size']) ? ' size="' . $element['filename']['#size'] . '"' : ''; + $element['filename']['#attributes']['class'][] = 'form-select'; + $multiple = !empty($element['#multiple']); + $output = ''; + } + + $output .= drupal_render($element['attach']); + $element['#children'] = $output; + $element['#theme_wrappers'] = array('form_element'); + return '
' . drupal_render($element) . '
'; + } + + /** + * 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.') . ' ' . t('This file source will ignore file size checking when used.') . '', + '#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 File attach location 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 File attach path 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 index 000000000..b0ed27151 --- /dev/null +++ b/web/modules/contrib/filefield_sources/src/Plugin/FilefieldSource/Clipboard.php @@ -0,0 +1,152 @@ +limited browser support)"), + * 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' => '
example_filename.png
' . t('ctrl + v') . '', + '#description' => t('Enter a file name and paste an image from the clipboard. This feature only works in limited browsers.'), + ); + + $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 '
' . drupal_render_children($element) . '
'; + } + +} 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 index 000000000..5830a862f --- /dev/null +++ b/web/modules/contrib/filefield_sources/src/Plugin/FilefieldSource/Imce.php @@ -0,0 +1,216 @@ +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' => '' . t('No file selected') . ' (' . t('browse') . ')', + ); + + $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 '
' . $output . '
'; + } + + /** + * 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 IMCE configuration profiles. 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 index 000000000..6d47f1293 --- /dev/null +++ b/web/modules/contrib/filefield_sources/src/Plugin/FilefieldSource/Reference.php @@ -0,0 +1,239 @@ + 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 '
' . drupal_render($element['autocomplete']) . '
'; + } + + /** + * 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 index 000000000..7bce7b99f --- /dev/null +++ b/web/modules/contrib/filefield_sources/src/Plugin/FilefieldSource/Remote.php @@ -0,0 +1,400 @@ + 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 '
' . drupal_render($element['url']) . '
'; + } + + /** + * 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('Filefield sources: 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 index 000000000..178bcf697 --- /dev/null +++ b/web/modules/contrib/filefield_sources/src/ProxyClass/File/MimeType/ExtensionMimeTypeGuesser.php @@ -0,0 +1,108 @@ +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 index 000000000..0530566fe --- /dev/null +++ b/web/modules/contrib/filefield_sources/src/Routing/FilefieldSourcesRoutes.php @@ -0,0 +1,35 @@ +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 index 000000000..71ff7b174 --- /dev/null +++ b/web/modules/contrib/filefield_sources/src/Tests/AttachSourceTest.php @@ -0,0 +1,168 @@ +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 index 000000000..215883a06 --- /dev/null +++ b/web/modules/contrib/filefield_sources/src/Tests/ClipboardSourceTest.php @@ -0,0 +1,37 @@ +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 index 000000000..065ff5709 --- /dev/null +++ b/web/modules/contrib/filefield_sources/src/Tests/EmptyValuesTest.php @@ -0,0 +1,90 @@ +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 index 000000000..14a16da68 --- /dev/null +++ b/web/modules/contrib/filefield_sources/src/Tests/FileFieldSourcesTestBase.php @@ -0,0 +1,450 @@ +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 index 000000000..5b0ea2cb0 --- /dev/null +++ b/web/modules/contrib/filefield_sources/src/Tests/ImceSourceTest.php @@ -0,0 +1,52 @@ +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 index 000000000..97052ee23 --- /dev/null +++ b/web/modules/contrib/filefield_sources/src/Tests/MultipleValuesTest.php @@ -0,0 +1,133 @@ +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 index 000000000..9b3927838 --- /dev/null +++ b/web/modules/contrib/filefield_sources/src/Tests/ReferenceSourceTest.php @@ -0,0 +1,109 @@ +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 index 000000000..2cc55002f --- /dev/null +++ b/web/modules/contrib/filefield_sources/src/Tests/RemoteSourceTest.php @@ -0,0 +1,38 @@ +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 index 000000000..01afc53be --- /dev/null +++ b/web/modules/contrib/filefield_sources/src/Tests/UploadSourceTest.php @@ -0,0 +1,62 @@ +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 index 061ead081..000000000 --- a/web/modules/contrib/libraries +++ /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 index 000000000..6d1daa835 --- /dev/null +++ b/web/modules/contrib/libraries/CHANGELOG.txt @@ -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 index 000000000..9ff0ece53 --- /dev/null +++ b/web/modules/contrib/libraries/README.txt @@ -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 index 000000000..4af11e12e --- /dev/null +++ b/web/modules/contrib/libraries/composer.json @@ -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 index 000000000..0981884b1 --- /dev/null +++ b/web/modules/contrib/libraries/config/install/libraries.settings.yml @@ -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 index 000000000..72c4c52f2 --- /dev/null +++ b/web/modules/contrib/libraries/config/schema/libraries.schema.yml @@ -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 index 000000000..859e6023e --- /dev/null +++ b/web/modules/contrib/libraries/libraries.api.php @@ -0,0 +1,609 @@ +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 index 000000000..22b7d62da --- /dev/null +++ b/web/modules/contrib/libraries/libraries.drush.inc @@ -0,0 +1,169 @@ + '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 index 000000000..1891a6ffb --- /dev/null +++ b/web/modules/contrib/libraries/libraries.info.yml @@ -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 index 000000000..a2edea492 --- /dev/null +++ b/web/modules/contrib/libraries/libraries.install @@ -0,0 +1,28 @@ +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 index 000000000..3006c345c --- /dev/null +++ b/web/modules/contrib/libraries/libraries.module @@ -0,0 +1,868 @@ +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//*. + $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 index 000000000..b2880ad57 --- /dev/null +++ b/web/modules/contrib/libraries/libraries.services.yml @@ -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 index 000000000..3cf549116 --- /dev/null +++ b/web/modules/contrib/libraries/src/Annotation/LibraryType.php @@ -0,0 +1,14 @@ +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 index 000000000..50a318686 --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/Asset/AssetLibrary.php @@ -0,0 +1,128 @@ +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 index 000000000..eac629024 --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/Asset/AssetLibraryInterface.php @@ -0,0 +1,65 @@ +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 index 000000000..f2c3d3c46 --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/Asset/MultipleAssetLibrary.php @@ -0,0 +1,123 @@ +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 index 000000000..5dc43ffdc --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/Asset/MultipleAssetLibraryInterface.php @@ -0,0 +1,64 @@ +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 index 000000000..970385e5f --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/Definition/DefinitionDiscoveryFactory.php @@ -0,0 +1,104 @@ +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 index 000000000..61fc589cc --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/Definition/DefinitionDiscoveryInterface.php @@ -0,0 +1,45 @@ +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 index 000000000..0151f2794 --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/Definition/FileDefinitionDiscoveryBase.php @@ -0,0 +1,82 @@ +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 index 000000000..59745b78c --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/Definition/GuzzleDefinitionDiscovery.php @@ -0,0 +1,71 @@ +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 index 000000000..f644a0d36 --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/Definition/WritableDefinitionDiscoveryInterface.php @@ -0,0 +1,25 @@ +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 index 000000000..fd838101f --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/Dependency/DependentLibraryInterface.php @@ -0,0 +1,22 @@ +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 index 000000000..7ab3eeb7e --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/Exception/InvalidLibraryDependencyException.php @@ -0,0 +1,45 @@ +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 index 000000000..c274302d6 --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/Exception/LibraryDefinitionNotFoundException.php @@ -0,0 +1,38 @@ +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 index 000000000..ed48a629c --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/Exception/LibraryNotInstalledException.php @@ -0,0 +1,39 @@ +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 index 000000000..b5655bba0 --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/Exception/LibraryTypeNotFoundException.php @@ -0,0 +1,38 @@ +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 index 000000000..4afe0593f --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/Exception/UnknownLibraryVersionException.php @@ -0,0 +1,39 @@ +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 index 000000000..8571552c7 --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/LibraryBase.php @@ -0,0 +1,83 @@ +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 index 000000000..ebf43a9b9 --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/LibraryInterface.php @@ -0,0 +1,44 @@ +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 index 000000000..0c4383f37 --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/LibraryManagerInterface.php @@ -0,0 +1,51 @@ +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 index 000000000..a1be89440 --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/Local/LocatorInterface.php @@ -0,0 +1,22 @@ +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 index 000000000..bf3c68328 --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/PhpFile/PhpFileLibrary.php @@ -0,0 +1,73 @@ +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 index 000000000..01ca45371 --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/PhpFile/PhpFileLibraryInterface.php @@ -0,0 +1,22 @@ +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 index 000000000..e86115f26 --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/Type/LibraryCreationListenerInterface.php @@ -0,0 +1,20 @@ +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 index 000000000..6689f51b8 --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/Type/LibraryTypeFactory.php @@ -0,0 +1,32 @@ +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 index 000000000..d6537fb37 --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/Type/LibraryTypeInterface.php @@ -0,0 +1,28 @@ +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 index 000000000..34476f2cc --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/Utility/IdAccessorTrait.php @@ -0,0 +1,30 @@ +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 index 000000000..72b0093c4 --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/Utility/LibraryAccessorInterface.php @@ -0,0 +1,18 @@ +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 index 000000000..98928e9de --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/Utility/LibraryIdAccessorInterface.php @@ -0,0 +1,18 @@ +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 index 000000000..8b6d2a5bd --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/Version/VersionDetectorInterface.php @@ -0,0 +1,25 @@ +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 index 000000000..abeb758cd --- /dev/null +++ b/web/modules/contrib/libraries/src/ExternalLibrary/Version/VersionedLibraryInterface.php @@ -0,0 +1,65 @@ + 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 index 000000000..515f13340 --- /dev/null +++ b/web/modules/contrib/libraries/src/Plugin/MissingPluginConfigurationException.php @@ -0,0 +1,50 @@ +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 index 000000000..3501338d4 --- /dev/null +++ b/web/modules/contrib/libraries/src/Plugin/libraries/Locator/GlobalLocator.php @@ -0,0 +1,76 @@ +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 index 000000000..348be0c24 --- /dev/null +++ b/web/modules/contrib/libraries/src/Plugin/libraries/Locator/UriLocator.php @@ -0,0 +1,89 @@ +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 index 000000000..ce0b276b1 --- /dev/null +++ b/web/modules/contrib/libraries/src/Plugin/libraries/Type/AssetLibraryType.php @@ -0,0 +1,32 @@ +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 index 000000000..31ec6a62c --- /dev/null +++ b/web/modules/contrib/libraries/src/Plugin/libraries/Type/MultipleAssetLibraryType.php @@ -0,0 +1,48 @@ +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 index 000000000..aa93b003c --- /dev/null +++ b/web/modules/contrib/libraries/src/Plugin/libraries/Type/PhpFileLibraryType.php @@ -0,0 +1,72 @@ +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 index 000000000..25a799c53 --- /dev/null +++ b/web/modules/contrib/libraries/src/Plugin/libraries/VersionDetector/LinePatternDetector.php @@ -0,0 +1,89 @@ + '', + '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 index 000000000..ed697de50 --- /dev/null +++ b/web/modules/contrib/libraries/src/Plugin/libraries/VersionDetector/StaticDetector.php @@ -0,0 +1,46 @@ + 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 index 000000000..54893a7d0 --- /dev/null +++ b/web/modules/contrib/libraries/src/StreamWrapper/AssetLibrariesStream.php @@ -0,0 +1,40 @@ +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 index 000000000..f71c0de99 --- /dev/null +++ b/web/modules/contrib/libraries/src/StreamWrapper/LocalHiddenStreamTrait.php @@ -0,0 +1,23 @@ +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 index 000000000..f616a92b8 --- /dev/null +++ b/web/modules/contrib/libraries/src/Tests/LibrariesUnitTest.php @@ -0,0 +1,53 @@ +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 index 000000000..2c9254c45 --- /dev/null +++ b/web/modules/contrib/libraries/src/Tests/LibrariesWebTest.php @@ -0,0 +1,525 @@ +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:
$error_message"); + $this->verbose('Actual:
' . $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:
$error_message"); + $this->verbose('Actual:
' . $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:
' . var_export($expected, TRUE) . '
'); + $this->verbose('Actual:
' . var_export($actual, TRUE) . '
'); + $this->assertEqual($actual, $expected, 'libraries_scan_info_files() correctly finds the example info file.'); + $this->verbose('
' . var_export(libraries_scan_info_files(), TRUE) . '
'); + } + + /** + * 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:
' . var_export($expected, TRUE) . '
'); + $this->verbose('Actual:
' . var_export($library, TRUE) . '
'); + $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:
' . var_export($expected, TRUE) . '
'); + $this->verbose('Actual:
' . var_export($library, TRUE) . '
'); + $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('
' . var_export($library, TRUE) . '
'); + $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('
' . var_export($library, TRUE) . '
'); + $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('
' . var_export($library, TRUE) . '
'); + $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('
' . var_export($library, TRUE) . '
'); + $this->assertEqual($library['installed'], TRUE, 'Supported library version found.'); + + // Test libraries_get_version(). + $library = libraries_detect('example_default_version_callback'); + $this->verbose('
' . var_export($library, TRUE) . '
'); + $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('
' . var_export($library, TRUE) . '
'); + $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('
' . var_export($library, TRUE) . '
'); + $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('
' . var_export($library, TRUE) . '
'); + $this->assertEqual($library['files'], $files, 'Version-specific library files found.'); + + // Test missing variant. + $library = libraries_detect('example_variant_missing'); + $this->verbose('
' . var_export($library, TRUE) . '
'); + $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('
' . var_export($library, TRUE) . '
'); + $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('
' . var_export($library, TRUE) . '
'); + $this->assertFalse($library['loaded'], 'Library with missing dependency cannot be loaded'); + $library = libraries_load('example_dependency_incompatible'); + $this->verbose('
' . var_export($library, TRUE) . '
'); + $this->assertFalse($library['loaded'], 'Library with incompatible dependency cannot be loaded'); + $library = libraries_load('example_dependency_compatible'); + $this->verbose('
' . var_export($library, TRUE) . '
'); + $this->assertEqual($library['loaded'], 1, 'Library with compatible dependency is loaded'); + $loaded = &drupal_static('libraries_load'); + $this->verbose('
' . var_export($loaded, TRUE) . '
'); + $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:
' . var_export($expected, TRUE) . '
'); + $this->verbose('Actual:
' . var_export($library, TRUE) . '
'); + $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:
' . var_export($expected, TRUE) . '
'); + $this->verbose('Actual:
' . var_export($library, TRUE) . '
'); + $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:
' . var_export($expected, TRUE) . '
'); + $this->verbose('Actual:
' . var_export($library, TRUE) . '
'); + $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:
' . var_export($expected, TRUE) . '
'); + $this->verbose('Actual:
' . var_export($library, TRUE) . '
'); + $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 info callback group was invoked.', 'Info callback invoked for uncached libraries.'); + $this->assertRaw('The pre-detect callback group was invoked.', 'Pre-detect callback invoked for uncached libraries.'); + $this->assertRaw('The post-detect callback group was invoked.', 'Post-detect callback invoked for uncached libraries.'); + $this->assertRaw('The pre-load callback group was invoked.', 'Pre-load callback invoked for uncached libraries.'); + $this->assertRaw('The post-load 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 info callback group was not invoked.', 'Info callback not invoked for cached libraries.'); + $this->assertNoRaw('The pre-detect callback group was not invoked.', 'Pre-detect callback not invoked for cached libraries.'); + $this->assertNoRaw('The post-detect callback group was not invoked.', 'Post-detect callback not invoked for cached libraries.'); + $this->assertRaw('The pre-load callback group was invoked.', 'Pre-load callback invoked for cached libraries.'); + $this->assertRaw('The post-load 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(''), + 'css' => array(''), + // PHP files do not get added to the DOM directly. + // @see _libraries_test_load() + 'php' => array('
  • ', '
  • '), + ); + + $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 = '
    • ' . implode('
    • ', $html_expected) . '
    '; + $html_not_expected = '
    • ' . implode('
    • ', $html_not_expected) . '
    '; + $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 index 000000000..e69de29bb 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 index 000000000..e69de29bb 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 index 000000000..e69de29bb 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 index 000000000..e69de29bb 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 index 000000000..e69de29bb 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 index 000000000..e69de29bb diff --git a/web/modules/contrib/libraries/tests/example/README.txt b/web/modules/contrib/libraries/tests/example/README.txt new file mode 100644 index 000000000..e3582c2ab --- /dev/null +++ b/web/modules/contrib/libraries/tests/example/README.txt @@ -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 index 000000000..a732bda5f --- /dev/null +++ b/web/modules/contrib/libraries/tests/example/example_1.css @@ -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 index 000000000..8a1b9a27f --- /dev/null +++ b/web/modules/contrib/libraries/tests/example/example_1.js @@ -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 index 000000000..92e6711a7 --- /dev/null +++ b/web/modules/contrib/libraries/tests/example/example_1.php @@ -0,0 +1,12 @@ + '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 $group 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 index 000000000..a11f0bece --- /dev/null +++ b/web/modules/contrib/libraries/tests/modules/libraries_test/libraries_test.routing.yml @@ -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 index 000000000..217ebf2f8 --- /dev/null +++ b/web/modules/contrib/libraries/tests/modules/libraries_test/src/Controller/ExampleController.php @@ -0,0 +1,94 @@ +JavaScript'; + $output .= '
    '; + $output .= 'If this text shows up, no JavaScript test file was loaded.'; + $output .= '
    '; + + // For easier debugging of CSS loading, the loaded CSS files will color the + // following text. + $output .= '

    CSS

    '; + $output .= '
    '; + $output .= 'If one of the CSS test files has been loaded, this text will be colored:'; + $output .= '
      '; + // Do not reference the actual CSS files (i.e. including '.css'), because that + // breaks testing. + $output .= '
    • example_1: red
    • '; + $output .= '
    • example_2: green
    • '; + $output .= '
    • example_3: orange
    • '; + $output .= '
    • example_4: blue
    • '; + $output .= '
    • libraries_test: purple
    • '; + $output .= '
    '; + $output .= '
    '; + + $output .= '

    PHP

    '; + $output .= '
    '; + $output .= 'The following is a list of all loaded test PHP files:'; + $output .= '
      '; + $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 .= '
    • ' . str_replace(DRUPAL_ROOT . '/', '', $file) . '
    • '; + } + } + $output .= '
    '; + $output .= '
    '; + + 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 index 000000000..14bf74e4f --- /dev/null +++ b/web/modules/contrib/libraries/tests/src/Functional/ExternalLibrary/Definition/DefinitionDiscoveryFactoryTest.php @@ -0,0 +1,118 @@ +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 index 000000000..cb2551e22 --- /dev/null +++ b/web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTest.php @@ -0,0 +1,114 @@ +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 index 000000000..4f835b725 --- /dev/null +++ b/web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTestBase.php @@ -0,0 +1,36 @@ +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 index 000000000..4a2af5c3d --- /dev/null +++ b/web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/Asset/MultipleAssetLibraryTest.php @@ -0,0 +1,172 @@ +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 index 000000000..84eb86eb1 --- /dev/null +++ b/web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/GlobalLocatorTest.php @@ -0,0 +1,53 @@ +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 index 000000000..10c1d2cfe --- /dev/null +++ b/web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/PhpFile/PhpFileLibraryTest.php @@ -0,0 +1,66 @@ +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 index 000000000..ec20647ea --- /dev/null +++ b/web/modules/contrib/libraries/tests/src/Kernel/ExternalLibrary/TestLibraryFilesStream.php @@ -0,0 +1,80 @@ +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 index 000000000..818579476 --- /dev/null +++ b/web/modules/contrib/libraries/tests/src/Kernel/LibraryTypeKernelTestBase.php @@ -0,0 +1,171 @@ +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 index 000000000..5e4e34b93 --- /dev/null +++ b/web/modules/contrib/libraries/tests/src/Unit/Plugin/libraries/VersionDetector/LinePatternDetectorTest.php @@ -0,0 +1,197 @@ +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, <<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, <<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 index 513771361..000000000 --- a/web/modules/contrib/linkchecker +++ /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 index 000000000..57ee5ff1a --- /dev/null +++ b/web/modules/contrib/linkchecker/CHANGELOG.txt @@ -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 index 000000000..96c733be9 --- /dev/null +++ b/web/modules/contrib/linkchecker/README.txt @@ -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 index 000000000..32b062996 --- /dev/null +++ b/web/modules/contrib/linkchecker/composer.json @@ -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 index 000000000..7fc94d67f --- /dev/null +++ b/web/modules/contrib/linkchecker/config/install/linkchecker.settings.yml @@ -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 index 000000000..a2de036b2 --- /dev/null +++ b/web/modules/contrib/linkchecker/config/schema/linkchecker.schema.yml @@ -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 and tags' + from_audio: + type: boolean + label: 'Extract links in